Compare commits
38 Commits
805a3928a7
...
card0614
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fee02d82e | ||
|
|
15d14f7d59 | ||
|
|
9579c4f75e | ||
|
|
d54e6ce1d6 | ||
|
|
d06c99e2f1 | ||
|
|
76082d8e1f | ||
|
|
695a9e2b66 | ||
|
|
c6b5e1431f | ||
|
|
7fa18329d6 | ||
|
|
a2c397120c | ||
|
|
cb2a72da52 | ||
|
|
9da3e2d9fd | ||
|
|
eb4b370533 | ||
|
|
a3375ec3b6 | ||
|
|
011e7b3dde | ||
|
|
038abdc5d2 | ||
|
|
c27c81cfa6 | ||
|
|
a905db8e48 | ||
|
|
3060ee7df7 | ||
|
|
ab6a3b7824 | ||
|
|
a5dc1ef540 | ||
|
|
db98b60737 | ||
|
|
ccce8a11ea | ||
|
|
762dc91a11 | ||
|
|
4594f673b4 | ||
|
|
426dc24899 | ||
|
|
1609d3971a | ||
|
|
ae6c69e4f3 | ||
|
|
450a8b81e5 | ||
|
|
e328892679 | ||
|
|
486c223b65 | ||
|
|
c65c8a36ab | ||
|
|
f251e57c3c | ||
|
|
3c9496d753 | ||
|
|
409113e269 | ||
|
|
d57f4c14e0 | ||
|
|
b13178166a | ||
|
|
d692251467 |
@@ -1,49 +0,0 @@
|
||||
# 技能配置重构实施计划
|
||||
|
||||
## 1. 探索阶段总结 (Current State Analysis)
|
||||
经过对代码库的探索,当前技能触发系统及相关配置状态如下:
|
||||
- `SkillSet.ts` 包含了所有技能的基座配置 `SkillConfig` 和技能字典 `SkillSet`。
|
||||
- `heroSet.ts` 中的 `HeroInfo` 存放英雄和怪物的配置,目前 `call`, `dead`, `fstart`, `fend` 被定义为 `number[]`,而 `atking` 和 `atked` 被定义为 `{s_uuid: number, t_num: number}[]`。
|
||||
- `HeroAttrsComp.ts` 内部存储了与配置一致的触发技能结构。
|
||||
- `SkillTriggerHelper.ts` 负责判定并向外派发 `GameEvent.TriggerSkill` 事件,由 `SCastSystem.ts` 监听并执行 `forceCastTriggerSkill`。
|
||||
- 目前 `SCastSystem.ts` 在收集技能目标和施放技能时,都是直接从 `SkillSet[s_uuid]` 读取 `config`,没有针对具体角色的差异化机制。
|
||||
|
||||
## 2. 拟议变更 (Proposed Changes)
|
||||
|
||||
### 2.1 修改 `SkillSet.ts`
|
||||
- **新增接口**:定义 `SkillOverrides` 接口,包含所有可被角色覆盖的技能参数(如 `TGroup`, `ap`, `hit_count`, `buffs` 等,全部为可选字段)。
|
||||
- **新增函数**:编写 `mergeSkillParams(config, overrides?)` 函数,将基座 `config` 和角色覆盖 `overrides` 进行合并,返回一个新的 `SkillConfig` 对象。
|
||||
|
||||
### 2.2 修改 `heroSet.ts`
|
||||
- **扩展接口**:
|
||||
- 在 `HSkillInfo` 接口中增加 `overrides?: SkillOverrides;`。
|
||||
- 将 `heroInfo` 接口中的触发字段 `call`, `dead`, `fstart`, `fend`, `atking`, `atked` 统一更新为 `{ s_uuid: number; t_num: number; overrides?: SkillOverrides }[]` 结构。
|
||||
- **更新配置示例**:按照设计文档更新英雄 `5001`, `5002`, `5301`, `5302` 的配置,为特定的触发技能添加 `overrides` 字段(如盾骑士 5002 全队护盾覆盖)。
|
||||
|
||||
### 2.3 修改 `HeroAttrsComp.ts`
|
||||
- **同步类型**:将 `call`, `dead`, `fstart`, `fend`, `atking`, `atked` 的类型同步改为与 `heroInfo` 一致的 `{ s_uuid: number; t_num: number; overrides?: SkillOverrides }[]`。
|
||||
|
||||
### 2.4 修改 `SkillTriggerHelper.ts`
|
||||
- **派发支持**:
|
||||
- 更新 `dispatchSingle` 方法签名,增加 `overrides?: SkillOverrides` 参数,并在 `oops.message.dispatchEvent(GameEvent.TriggerSkill, {...})` 中将其传入。
|
||||
- 更新 `handleCall`, `handleDead`, `handleArrayTrigger` 处理逻辑,将原先对 `number[]` 的处理改为对 `{s_uuid, t_num, overrides}` 对象数组的处理,并提取 `overrides` 传递给 `dispatchArray`。
|
||||
- 更新 `handleAtking`, `handleAtked` 中的 `dispatchSingle` 调用,传入 `atkConfig.overrides`。
|
||||
|
||||
### 2.5 修改 `SCastSystem.ts` (关键运行时逻辑)
|
||||
- **事件监听更新**:在 `onTriggerSkill` 方法的 `args` 参数定义中补充 `overrides?: SkillOverrides`,并传递给 `forceCastTriggerSkill`。
|
||||
- **合并逻辑上移(架构优化)**:
|
||||
- 在 `forceCastTriggerSkill` 和 `castSkill` 的**方法入口处**(而非 `applyFriendlySkillEffects` 内部),第一时间调用 `mergeSkillParams(config, overrides)` 获取 `effective` 技能配置。
|
||||
- 将后续所有关于阵营判定(如 `effective.TGroup`)、目标收集、以及传递给 `applyFriendlySkillEffects` / `applyEnemySkillEffects` 的参数全部替换为 `effective`。
|
||||
- **为何如此设计**:如果在原设计中仅在 `applyFriendlySkillEffects` 入口处合并,那么前置的**目标选择逻辑**(依赖 `TGroup` 判定是 `Self` 还是 `Team`)将会使用未合并的基础配置,导致类似“自己加盾变为全队加盾”的 `TGroup` 覆盖无法生效。将合并操作前置可以彻底解决这一问题。
|
||||
- **主动技能支持**:在 `pickCastSkill` 中,读取 `heroAttrs.skills[s_uuid]?.overrides` 并进行合并判定,同时将 `overrides` 放入返回的 `castPlan` 中,以便 `castSkill` 使用。
|
||||
|
||||
## 3. 假设与决策 (Assumptions & Decisions)
|
||||
- **统一触发结构**:虽然 `call`, `dead`, `fstart`, `fend` 不严格需要 `t_num`,但为了类型统一并完全遵守设计规范,统一采用了包含 `t_num` 的对象结构。
|
||||
- **合并前置决策**:如上所述,坚决在施放方法入口处进行 `mergeSkillParams` 以保证目标收集逻辑能够感知到 `TGroup` 的变化。这比设计规范中要求的修改范围略有扩大,但对于系统功能的正确实现是必须的。
|
||||
- **卡牌技能影响**:卡牌技能(`forceCastCardSkill`)当前没有绑定角色的 `overrides`,因此维持读取基础 `SkillSet` 逻辑不变。
|
||||
|
||||
## 4. 验证步骤 (Verification steps)
|
||||
1. 编译 TypeScript 代码,确保 `HeroAttrsComp`, `heroSet`, `SCastSystem` 等修改后的接口和类型无报错。
|
||||
2. 启动游戏或运行测试,确认 `5001` (见习战士) 触发的基础护盾只对自己生效。
|
||||
3. 确认 `5002` (盾骑士) 受击触发的护盾技能,正确地为全队附加护盾,并且护盾值(ap)与次数(hit_count)符合 `overrides` 配置。
|
||||
4. 确认所有旧版英雄技能在无 `overrides` 时能够正确回退到 `SkillSet` 的默认配置,游戏运转正常无异常日志。
|
||||
@@ -1,55 +0,0 @@
|
||||
# 重构场上英雄UI表现及交互计划
|
||||
|
||||
## 1. 目标与现状分析
|
||||
|
||||
**现状**:
|
||||
目前游戏中 `HInfoComp.ts` 负责在界面下方显示场上英雄的信息(生命、攻击、出售),由 `MissionCardComp` 管理 6 个固定槽位。
|
||||
`HeroViewComp.ts` 负责战斗场景中英雄实体的动画表现。
|
||||
|
||||
**目标**:
|
||||
1. 保留 `HInfoComp.ts` 组件及预制体,但**取消其在底部的常驻显示**,将其改造为**弹窗形式**(类似 `IBoxComp`)。
|
||||
2. 在战斗或准备阶段,玩家**直接点击场上的英雄模型**(`HeroViewComp`)时,弹出 `HInfoComp` 面板。
|
||||
3. 清理 `MissionCardComp.ts` 中管理底层 `HInfoComp` 的旧逻辑。
|
||||
|
||||
## 2. 具体修改步骤
|
||||
|
||||
### 2.1 注册 HInfo 为独立弹窗
|
||||
* 修改 `assets/script/game/common/config/GameUIConfig.ts`:
|
||||
* 在 `UIID` 枚举中添加 `HInfo`。
|
||||
* 在 `UIConfigData` 中注册:`[UIID.HInfo]: { layer: LayerType.UI, prefab: "gui/element/hnode" }`。
|
||||
|
||||
### 2.2 改造 HInfoComp.ts
|
||||
* **数据传入**:添加 `onAdded(args: { eid: number })`,根据 `eid` 查询 `HeroAttrsComp` 实体进行数据绑定。
|
||||
* **自驱动刷新**:原先由外部驱动刷新,现在添加 `update(dt: number)` 生命周期,在内部调用 `this.refresh()` 以保持血量等信息实时更新。
|
||||
* **移除旧逻辑**:删除 `node_index`、`refreshByNodeIndex` 等固定槽位相关的代码。
|
||||
* **交互恢复**:取消注释 `bindEvents` 和 `unbindEvents`,恢复出售按钮的点击事件。出售完成后调用 `oops.gui.remove(UIID.HInfo)`。打开 `IBox` 的点击逻辑可保持不变(或者作为详情按钮)。
|
||||
* **添加关闭机制**:考虑到它是弹窗,可以添加一个点击非按钮区域关闭自身的功能,或者点击英雄之外的区域关闭。为简单起见,可以暂时复用点击面板打开 IBox(同时关闭 HInfo),并在 HInfo 添加额外的关闭按钮,或由 UI 框架自动处理(如果注册为 PopUp 并带有背景)。如果它是纯 UI,可以点击其他地方关闭。这里我们让它在打开 `IBox` 后关闭自己:`oops.gui.remove(UIID.HInfo)`。
|
||||
|
||||
### 2.3 清理 MissionCardComp.ts
|
||||
* **移除属性**:删除 `@property(Node) hero_info_node` 和 `@property(Prefab) hero_info_prefab` 及其编辑器绑定。
|
||||
* **移除内部状态**:删除 `cachedHInfoComps` 和 `heroInfoSyncTimer`。
|
||||
* **移除生命周期调用**:在 `onLoad`、`update`、`onMissionStart`、`onMissionEnd`、`onDestroy`、`reset`、`enterPreparePhase`、`enterBattlePhase` 中,删除所有涉及 `HInfoComp` 实例创建、刷新、显隐控制、销毁的代码。
|
||||
|
||||
### 2.4 修改 HeroViewComp.ts 添加点击交互
|
||||
* **绑定事件**:在 `onLoad` 中为英雄模型节点绑定点击事件 `this.node.on(NodeEventType.TOUCH_END, this.onHeroClicked, this);`,并在 `reset` 等清理处解绑。
|
||||
* **点击回调逻辑**:
|
||||
```typescript
|
||||
private onHeroClicked(event: EventTouch) {
|
||||
if (!this.model) return;
|
||||
if (this.model.fac !== FacSet.HERO) return; // 仅对玩家英雄生效
|
||||
|
||||
const eid = this.ent?.eid;
|
||||
if (!eid) return;
|
||||
|
||||
// 呼出英雄信息弹窗
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
oops.gui.open(UIID.HInfo, { eid: eid });
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 验证步骤
|
||||
1. 进入战斗,确认下方不再有常驻的英雄信息面板。
|
||||
2. 点击场上的英雄模型,确认能弹出该英雄的 `HInfoComp` 弹窗。
|
||||
3. 观察弹窗内的血量和攻击力是否能随战斗实时刷新。
|
||||
4. 点击弹窗上的出售按钮,确认英雄消失、金币增加且弹窗关闭。
|
||||
5. 点击弹窗上的信息区域,确认能弹出 `IBoxComp` 详情面板。
|
||||
247
.trae/documents/plan_spawn_flow_improvement.md
Normal file
247
.trae/documents/plan_spawn_flow_improvement.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# 刷怪心流改造 · 实施步骤与执行方案
|
||||
|
||||
> 基于对 RogueConfig.ts / MissionMonComp.ts / MissionComp.ts 的多 agent 调研与方案讨论产出。
|
||||
> 目标:让刷怪节奏产生"爽感"与心流(Flow),核心公式:**爽感 = 密度 × 可清性**,**心流 = 铺垫 → 峰值 → 释放循环 + 即时正反馈**。
|
||||
|
||||
---
|
||||
|
||||
## 阶段划分总览
|
||||
|
||||
```
|
||||
阶段一(P0 修复,4 项) → 安全网与曲线修正,不动体验结构
|
||||
阶段二(P1 节奏与 DDA,5 项) → 核心体验重塑,依赖链严格
|
||||
阶段三(P2-P3 反馈层,5 项) → 情绪反馈补全,可并行开发
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 阶段一:P0 修复
|
||||
|
||||
### Step 1.1 — P0-3 刷怪暂停死链路(先做:泄压阀)
|
||||
|
||||
**改动文件**:MissionMonComp.ts、MissionComp.ts
|
||||
|
||||
| # | 操作 | 位置 |
|
||||
|---|---|---|
|
||||
| 1 | `update()` 在 pending 统计之后、批次推进之前插入 `if (smc.mission.stop_spawn_mon) return;` | MissionMonComp L106 后 |
|
||||
| 2 | 阈值对齐:`maxMonsterCount` 80→60,`resumeMonsterCount` 45→40 | MissionComp L86-88 |
|
||||
| 3 | `changePhase(PrepareEnd)` 分支补 `smc.mission.stop_spawn_mon = false;`(防开局冻结 4 秒) | MissionComp L492-495 |
|
||||
|
||||
**验证**:临时 `maxMonsterCount=20` 进放松回合,确认到 20 只停刷、降 15 后续刷;wave 1 开局节奏无延迟。
|
||||
|
||||
**Commit**:`fix(mission): consume stop_spawn_mon in MissionMonComp update`
|
||||
|
||||
---
|
||||
|
||||
### Step 1.2 — P0-4 回合僵局超时(安全网)
|
||||
|
||||
**改动文件**:RogueConfig.ts、MissionComp.ts
|
||||
|
||||
| # | 操作 | 位置 |
|
||||
|---|---|---|
|
||||
| 1 | 新增常量 `WAVE_TIMEOUT = 75`(含 JSDoc) | RogueConfig 节奏常量区 |
|
||||
| 2 | MissionComp 新增字段 `skipRemainScoreOnBattleEnd`,`data_init` 复位 | MissionComp 运行时状态区 + L774 |
|
||||
| 3 | Battle 阶段 update 追加超时检测 `if (this.clearTime >= timeout) this.onBattleTimeout();`(timeout 取 `isBossWave ? 90 : WAVE_TIMEOUT`) | MissionComp L245-251 |
|
||||
| 4 | 新增 `onBattleTimeout()`:先扣 `wave_remain_monsters` → 销毁全部 MON 实体 → `mon_num=0` → 走 `TimeUpAdvanceWave` / `open_Victory` | MissionComp 回合管理区 |
|
||||
| 5 | BattleEnd 的 `wave_remain_monsters += mon_num` 加防重判断 | MissionComp L522-523 |
|
||||
|
||||
**验证**:临时 `WAVE_TIMEOUT=8`,确认 8 秒准点收束、评分已扣、factor 放水;TestModeConfig 高血怪模拟僵局验证 75s 前不结束。
|
||||
|
||||
**Commit**:`feat(mission): add wave battle timeout fallback`
|
||||
|
||||
---
|
||||
|
||||
### Step 1.3 — P0-1 Wave 1 错位修正
|
||||
|
||||
**改动文件**:RogueConfig.ts(单文件)
|
||||
|
||||
| # | 操作 | 位置 |
|
||||
|---|---|---|
|
||||
| 1 | `getWaveType`:`wave % 5 === 1` → `wave % 5 === 4` | L80 |
|
||||
| 2 | 同步 4 处注释:WaveType 枚举注释(L37)、文件头节奏注释(L21-23)、WaveConfigs 表头注释(L329-332)、表内"放松回合"行内注释从 wave 6/11/16 搬到 4/9/14 | 见左 |
|
||||
|
||||
**验证**:`getWaveType(1..20)` 断言输出 `N N N R P` × 4 循环;实机确认 wave 1 = 18 只、wave 4 ≈ 40 只低强度。
|
||||
|
||||
**Commit**:`fix(rogue): move relax wave to pre-boss slot (wave%5==4)`
|
||||
|
||||
---
|
||||
|
||||
### Step 1.4 — P0-2 Boss 压轴 + 预警
|
||||
|
||||
**改动文件**:RogueConfig.ts、MissionComp.ts、GameEvent.ts
|
||||
|
||||
| # | 操作 | 位置 |
|
||||
|---|---|---|
|
||||
| 1 | `SquadLibrary` 新增 `boss_guard`(weight:0,1 重甲+2 近战) | RogueConfig L191-198 |
|
||||
| 2 | `GeneratedMonster` 增加可选字段 `isBossGuard?: boolean` | RogueConfig L426-450 |
|
||||
| 3 | `generateWave` 重构第 3/4/8 步:Boss 不 push 首位 → 小队拼装 → 护卫队 `makeBossGuards()` → Boss 压队尾 → 批次统一分配后强制 Boss+护卫 `batch = BATCH_COUNT-1`;`remaining` 改为 `-= 4` | RogueConfig L509-554 |
|
||||
| 4 | `makeBoss` 的 `spawnIndex:0, batch:0` 改为占位默认值+注释 | RogueConfig L660-687 |
|
||||
| 5 | GameEvent 新增 `BossWarning = "BossWarning"` | GameEvent.ts |
|
||||
| 6 | `changePhase(BattleStart)`:`if (this.isBossWave)` 派发 `BossWarning { wave, eta: 20 }` | MissionComp L497-500 |
|
||||
| 7 | MissionComp 从 RogueConfig 补导入 `BATCH_INTERVAL, BATCH_COUNT` | MissionComp L51 |
|
||||
|
||||
**验证**:wave 5 确认 0s/10s 两批纯杂兵、20s Boss 带 3 护卫进场、总怪数=21;`BossWarning` 仅在 5/10/15/20 派发。
|
||||
|
||||
**Commit**:`feat(rogue): spawn boss in final batch with guards and warning event`
|
||||
|
||||
---
|
||||
|
||||
## 阶段二:P1 节奏与 DDA(依赖链严格)
|
||||
|
||||
依赖链:**P1-5 → P1-1 → P1-2 + P1-3(同批)→ P1-4**
|
||||
|
||||
### Step 2.1 — P1-5 数值语义澄清(等价变换先行)
|
||||
|
||||
**改动文件**:RogueConfig.ts(单文件)
|
||||
|
||||
| # | 操作 |
|
||||
|---|---|
|
||||
| 1 | 删除第 5 步(乘 hp_mul/ap_mul),第 6 步改为拆分 `hpScale = targetPower × hp_mul / totalBasePower`、`apScale = targetPower × ap_mul / totalBasePower` |
|
||||
| 2 | 更新文件头公式注释(L12-16)与 `hp_mul/ap_mul` 字段注释 |
|
||||
| 3 | 17/18/19 启用 `power_adjust: 1.05 / 1.10 / 1.20` |
|
||||
| 4 | `validateRogueConfig` 增加 `power_adjust ∈ [0.8, 1.3]` 校验 |
|
||||
|
||||
**验证**:改造前后固定 heroPower 打桩,逐怪 hp/ap 断言相等(等价回归);17/18/19 总强度阶梯断言。
|
||||
|
||||
**Commit**:`refactor(rogue): unify hp/ap mul into power scaling formula`
|
||||
|
||||
---
|
||||
|
||||
### Step 2.2 — P1-1 批次递增 + 间隔参数化
|
||||
|
||||
**改动文件**:RogueConfig.ts、MissionMonComp.ts
|
||||
|
||||
| # | 操作 |
|
||||
|---|---|
|
||||
| 1 | 新增 `BATCH_RATIO = [0.25, 0.35, 0.40]`、`FINALE_SQUAD_COUNT = 2`、`SPAWN_INTERVAL_BY_TYPE`(Normal 0.18 / Pressure 0.25 / Relax 0.12) |
|
||||
| 2 | 批次分配从 `i % 3` 改为 `assignBatches()`:配额切分 + 第三批补 2 个最强小队(`pickStrongestSquad` 按 `calcHeroPower(样本)×count` 评分);放松回合跳过收尾加压;`totalCount` 预留收尾小队名额防 `slice` 截掉 |
|
||||
| 3 | MissionMonComp:`MON_SPAWN_INTERVAL` 静态常量改为实例字段 `spawnInterval`,`onPhasePrepareEnd` 按 `getWaveType(currentWave)` 查表;`releaseCurrentBatch` 末尾的 spawnTimer 初始化同步改 |
|
||||
|
||||
**与 P0-2 的合并点**:`assignBatches` 需保留"Boss+护卫强制最后一批"逻辑。
|
||||
|
||||
**验证**:debugMode 日志打印每批只数与最强小队 id;放松回合 54 只应 ~6.5s 倾泻完。
|
||||
|
||||
**Commit**:`feat(rogue): progressive batch ratio and per-type spawn interval`
|
||||
|
||||
---
|
||||
|
||||
### Step 2.3 — P1-2 + P1-3 清场加速与 DDA 重做(**必须同 commit**)
|
||||
|
||||
**改动文件**:RogueConfig.ts、MissionMonComp.ts、MissionComp.ts、GameSet.ts
|
||||
|
||||
P1-2 部分(MissionMonComp):
|
||||
|
||||
| # | 操作 |
|
||||
|---|---|
|
||||
| 1 | 新增状态:`batchReleasedCount`、`batchFastForwarded`、`aliveCheckTimer`、`waveEarlySkipTotal`(public 只读) |
|
||||
| 2 | `releaseCurrentBatch` 记录当批数量;update 中 0.2s 节流调 `checkBatchEarlyAdvance()` |
|
||||
| 3 | `checkBatchEarlyAdvance`:存活比例 ≤25% 且本批已放完 → `batchTimer += 2s`、`waveEarlySkipTotal += 2`(每批一次) |
|
||||
|
||||
P1-3 部分(RogueConfig + MissionComp + GameSet):
|
||||
|
||||
| # | 操作 |
|
||||
|---|---|
|
||||
| 1 | `DynamicTuner` 重写:连续映射 `desired = 1 + (0.8 - clearTime/30) × 0.5`(死亡锚定 0.8)、滞回 2 回合、指数靠拢 50%、区间 [0.7, 1.3]、新增 `streak` 字段 |
|
||||
| 2 | MissionComp BattleEnd:`effectiveClearTime = clearTime + MonComp.waveEarlySkipTotal` 后传入 adjust;顺手写入 `lastWaveDeathCount`/`lastWaveClearTime` 快照(供 Step 2.4) |
|
||||
| 3 | `FightSet.WAVE_HEAL_RATE` 0.5→0.4;修正 MissionComp L676"恢复70%"幽灵注释 |
|
||||
|
||||
**验证**:脚本模拟三种 clearTime 曲线(恒 12s / 恒 25s / 交替)打印 20 回合 factor 轨迹;强 build 单回合时长应从 ~30s 压到 ~24-26s。
|
||||
|
||||
**Commit**:`feat(rogue): early batch advance and continuous DDA with hysteresis`
|
||||
|
||||
---
|
||||
|
||||
### Step 2.4 — P1-4 动态回合倒计时
|
||||
|
||||
**改动文件**:MissionComp.ts(单文件)
|
||||
|
||||
| # | 操作 |
|
||||
|---|---|
|
||||
| 1 | 新增档位常量 `COUNTDOWN_FAST=2.5 / NORMAL=4.0 / FULL=5.0`,删除/降级 `WAVE_COUNTDOWN_DURATION` |
|
||||
| 2 | `startWaveCountdown` 改调 `computeCountdown()`:有死亡→5s,clearTime<65%→2.5s,否则 4s(读 Step 2.3 写入的快照字段) |
|
||||
|
||||
**验证**:快清场回合倒计时显示 3→2→1;有死亡回合给满 5s。
|
||||
|
||||
**Commit**:`feat(mission): adaptive wave countdown based on last wave performance`
|
||||
|
||||
---
|
||||
|
||||
## 阶段三:P2-P3 反馈层
|
||||
|
||||
### Step 3.1 — 事件与工具基建 + P2-2 清屏庆祝 + P2-3 Boss 演出
|
||||
|
||||
**改动文件**:GameEvent.ts、MissionComp.ts、MissionMonComp.ts、**新增** ScreenShake.ts、**新增** BattleBannerComp.ts、mission.prefab(编辑器)
|
||||
|
||||
| # | 操作 |
|
||||
|---|---|
|
||||
| 1 | GameEvent 新增:`WaveClear` / `BossWarning`(Step 1.4 已加则跳过)/ `BossSpawn` / `CoinFly` / `ComboReach`,启用 `MonDead` |
|
||||
| 2 | 新增 `ScreenShake` 静态工具(抖 `smc.map.MapView.node`,强度/时长参数,收敛回原位) |
|
||||
| 3 | 新增 `BattleBannerComp`:统一横幅通道(右进 backOut → 停留 → 左出 backIn),监听 WaveClear/BossWarning/ComboReach,0.3s 去抖 |
|
||||
| 4 | MissionComp 清屏分支 dispatch `WaveClear { wave, clearTime, allAlive, fastClear }`(通关分支不派发) |
|
||||
| 5 | MissionMonComp `addMonsterAtGrid` isBoss 分支 dispatch `BossSpawn { pos }` |
|
||||
| 6 | 奖励规则:fastClear=1 → +2 金,=2 → +5 金,allAlive → +1 刷新石(走 MissionEconomy) |
|
||||
| 7 | **编辑器操作**:mission.prefab 新增 `banner` 节点(Label+背景),挂 BattleBannerComp |
|
||||
|
||||
**Commit**:`feat(battle): wave clear banner, boss warning and screen shake`
|
||||
|
||||
---
|
||||
|
||||
### Step 3.2 — P2-1 波次 HUD + P2-4 金币飞行
|
||||
|
||||
**改动文件**:**新增** WaveHudComp.ts、**新增** CoinFlyComp.ts、HeroAtkSystem.ts、mission.prefab(编辑器)
|
||||
|
||||
| # | 操作 |
|
||||
|---|---|
|
||||
| 1 | `WaveHudComp`:监听 NewWave 缓存 total;0.2s 轮询 `mon_num + pending_mon_num` 刷新进度条;静态生成 20 格旗帜(`getWaveType(i)===Pressure` 置 Boss 帧),当前回合脉动 |
|
||||
| 2 | HeroAtkSystem `scheduleDrop` 后 dispatch `CoinFly { worldPos, gold, isBoss }`;`onDeath` MON 分支 dispatch `MonDead` |
|
||||
| 3 | `CoinFlyComp`:独立 NodePool(上限 30),3-5 枚(Boss 8 枚)金币抛物线散开→加速飞向钱包;金币帧运行时取编辑器绑定的 `coinIconSrc.spriteFrame`;**账务立即结算、飞行纯表现** |
|
||||
| 4 | **编辑器操作**:prefab 新增 `wave_hud`(wave_lab/remain_lab/progress/flags)与 `fly_layer`(最高 sibling),挂组件并拖绑定 |
|
||||
|
||||
**Commit**:`feat(battle): wave progress HUD and coin fly animation`
|
||||
|
||||
---
|
||||
|
||||
### Step 3.3 — P3-1 连杀 Combo
|
||||
|
||||
**改动文件**:**新增** ComboComp.ts、mission.prefab(编辑器)
|
||||
|
||||
| # | 操作 |
|
||||
|---|---|
|
||||
| 1 | `ComboComp`:监听 MonDead,2s 滑动窗口计数,5/10/20 阈值派发 `ComboReach { count, tier }`;MissionEnd 清零 |
|
||||
| 2 | BattleBannerComp 消费 ComboReach:分级文案/颜色/震屏(tier2 `shake(8,0.3)`)+ 金币爆发 2/5/10 |
|
||||
| 3 | **编辑器操作**:prefab 挂 ComboComp |
|
||||
|
||||
**Commit**:`feat(battle): kill combo system with tiered rewards`
|
||||
|
||||
---
|
||||
|
||||
## 执行约束
|
||||
|
||||
**每次 commit 前必做**:
|
||||
1. `validateRogueConfig()` 返回空数组
|
||||
2. GetDiagnostics 检查改动文件无 TS 错误
|
||||
3. 手动验证项按各 Step 的验证清单执行
|
||||
|
||||
**Commit message** 遵循项目规范(Conventional Commits、英文、祈使句、≤50 字符)。
|
||||
|
||||
**资源依赖**(不阻塞开发,可后期补):Boss 旗帜帧 ×1、清屏/Boss 预警/连杀音效 ×5、金币入袋音效 ×1——占位方案:flash/dun/Hit/Critical/Fire/button 复用。
|
||||
|
||||
**风险最高的两步**:Step 1.1(PrepareEnd 时序坑)和 Step 2.3(P1-2/P1-3 必须同批)。实施时优先单独验证这两点。
|
||||
|
||||
---
|
||||
|
||||
## 附录:关键数值速查
|
||||
|
||||
| 参数 | 现值 | 目标值 | 所属 Step |
|
||||
|---|---|---|---|
|
||||
| getWaveType Relax 位 | wave%5==1 | wave%5==4 | 1.3 |
|
||||
| Boss 批次 | batch 0 | batch 2 + 3 护卫 | 1.4 |
|
||||
| maxMonsterCount / resume | 80 / 45 | 60 / 40 | 1.1 |
|
||||
| WAVE_TIMEOUT | 无 | 75s(Boss 90s) | 1.2 |
|
||||
| BATCH_RATIO | 33/33/33 | 25/35/40 + 收尾小队×2 | 2.2 |
|
||||
| 刷怪间隔 | 0.3s 统一 | Normal 0.18 / Pressure 0.25 / Relax 0.12 | 2.2 |
|
||||
| 清场加速 | 无 | 存活≤25% 时 batchTimer += 2s | 2.3 |
|
||||
| DynamicTuner | ±0.05 步进 [0.5, 2.0] | 连续映射+滞回 [0.7, 1.3] | 2.3 |
|
||||
| WAVE_HEAL_RATE | 0.5 | 0.4 | 2.3 |
|
||||
| 回合间倒计时 | 固定 5s | 2.5 / 4 / 5 三档 | 2.4 |
|
||||
| power_adjust 17/18/19 | 未使用 | 1.05 / 1.10 / 1.20 | 2.1 |
|
||||
@@ -38,10 +38,13 @@
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 110
|
||||
},
|
||||
{
|
||||
"__id__": 112
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 112
|
||||
"__id__": 114
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
@@ -95,7 +98,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 9.114,
|
||||
"x": 0,
|
||||
"y": 10,
|
||||
"z": 0
|
||||
},
|
||||
@@ -217,7 +220,7 @@
|
||||
"__id__": 17
|
||||
}
|
||||
],
|
||||
"_active": false,
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 25
|
||||
@@ -235,7 +238,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": -9.738,
|
||||
"y": -7.84,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -287,7 +290,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -35,
|
||||
"x": -20,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
@@ -328,8 +331,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 70,
|
||||
"height": 10
|
||||
"width": 40,
|
||||
"height": 5
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -506,8 +509,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 240,
|
||||
"height": 40
|
||||
"width": 140,
|
||||
"height": 23.333333333333336
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -631,8 +634,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 70,
|
||||
"height": 10
|
||||
"width": 40,
|
||||
"height": 5
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -859,7 +862,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 40,
|
||||
"height": 11
|
||||
"height": 9
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -1037,7 +1040,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 140,
|
||||
"height": 43.333333333333336
|
||||
"height": 36.66666666666667
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -1162,7 +1165,7 @@
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 40,
|
||||
"height": 11
|
||||
"height": 9
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -1347,7 +1350,7 @@
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -25,
|
||||
"x": -20,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
@@ -1388,8 +1391,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 50,
|
||||
"height": 10
|
||||
"width": 40,
|
||||
"height": 9
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -1691,8 +1694,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 50,
|
||||
"height": 10
|
||||
"width": 40,
|
||||
"height": 9
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -1813,7 +1816,7 @@
|
||||
"__id__": 99
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_active": false,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 105
|
||||
@@ -2520,8 +2523,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 70,
|
||||
"height": 20
|
||||
"width": 40,
|
||||
"height": 11
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -2534,6 +2537,33 @@
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "cdvV5hAkdEaY6AT6urHKuf"
|
||||
},
|
||||
{
|
||||
"__type__": "c4181/FEWhKbY0D0PLdCyKi",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 113
|
||||
},
|
||||
"hpProgressBar": {
|
||||
"__id__": 53
|
||||
},
|
||||
"lvLabel": null,
|
||||
"shieldProgressBar": {
|
||||
"__id__": 77
|
||||
},
|
||||
"hpBarSprite": {
|
||||
"__id__": 36
|
||||
},
|
||||
"_id": "a77QVRkP5NX4b+LyqmW1KJ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "3eCKBFOJhLHKqcnak/mi3d"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
|
||||
@@ -1398,7 +1398,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 21.504,
|
||||
"y": -31.947,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -1875,7 +1875,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 21.504,
|
||||
"y": -50.813,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -2352,7 +2352,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"y": -150,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
@@ -2618,6 +2618,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "6bE0ieRMBGyYHdxvuJ03FA",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -3432,6 +3434,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "256TxMVzhHQopA08O7v8nq",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -3471,6 +3475,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "5dWwAe3IFHRJuoSicXBEs8",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -3710,6 +3716,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "b9zLdCeJlE5pPiK++dLgBX",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -4388,6 +4396,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "9d2p/canpFGof2O/w8JyTj",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -4427,6 +4437,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "6bkL20JyRDOI6ycVYKl/ie",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -4495,6 +4507,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "14GnCNU65BbJPIzLWE/Oiz",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -7363,6 +7377,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "c4IqMKgXNExa5w4EAciRxE",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -8202,7 +8218,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "LINE2",
|
||||
"_name": "BOSS",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
@@ -8273,7 +8289,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "ea767WaZRHo4s0bZQfN8zP"
|
||||
"fileId": "3fVB8r2UFAB5QujD6LOw6s"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
@@ -8283,14 +8299,14 @@
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "60UdPtJn9ALLHCveLwT1bT",
|
||||
"fileId": "f4iFaXCXNKorfGJFH32POa",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "HERO",
|
||||
"_name": "MON",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
@@ -8361,7 +8377,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "dasmpuTSVM7qA3QW9/3XjQ"
|
||||
"fileId": "ea767WaZRHo4s0bZQfN8zP"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
@@ -8371,14 +8387,14 @@
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "eaiasKNEFO6qvodGhL4a0b",
|
||||
"fileId": "60UdPtJn9ALLHCveLwT1bT",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "LINE1",
|
||||
"_name": "HERO",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
@@ -8449,7 +8465,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "b8C5CFg5lM4oUHMeSrc/sV"
|
||||
"fileId": "dasmpuTSVM7qA3QW9/3XjQ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
@@ -8459,14 +8475,14 @@
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "4dcNJaDM1CLI9q3fIqHZ/U",
|
||||
"fileId": "eaiasKNEFO6qvodGhL4a0b",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "LINE3",
|
||||
"_name": "LINE1",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
@@ -8537,7 +8553,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "a5LMIIbHtOKK10K5do5dSQ"
|
||||
"fileId": "b8C5CFg5lM4oUHMeSrc/sV"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
@@ -8547,14 +8563,14 @@
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "a6CBSkrzZIurlX0D/HzXDp",
|
||||
"fileId": "4dcNJaDM1CLI9q3fIqHZ/U",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "BOOS",
|
||||
"_name": "LINE3",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
@@ -8625,7 +8641,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "3fVB8r2UFAB5QujD6LOw6s"
|
||||
"fileId": "a5LMIIbHtOKK10K5do5dSQ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
@@ -8635,7 +8651,7 @@
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "f4iFaXCXNKorfGJFH32POa",
|
||||
"fileId": "a6CBSkrzZIurlX0D/HzXDp",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
@@ -8893,6 +8909,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "35iAcUUYpE8akTtTCX1ITo",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
@@ -8932,6 +8950,8 @@
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "68Q5vEQWBINrggngtTZ7wh",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,15 +7,26 @@
|
||||
"embeddedPlayerGroups": []
|
||||
},
|
||||
"_native": "",
|
||||
"sample": 8,
|
||||
"sample": 24,
|
||||
"speed": 1,
|
||||
"wrapMode": 2,
|
||||
"wrapMode": 1,
|
||||
"enableTrsBlending": false,
|
||||
"_duration": 0,
|
||||
"_hash": 500763545,
|
||||
"_tracks": [],
|
||||
"_exoticAnimation": null,
|
||||
"_events": [],
|
||||
"_events": [
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "atk",
|
||||
"params": []
|
||||
}
|
||||
],
|
||||
"_embeddedPlayers": [],
|
||||
"_additiveSettings": {
|
||||
"__id__": 1
|
||||
|
||||
116
assets/resources/game/skill/anm/atk/atk02.anim
Normal file
116
assets/resources/game/skill/anm/atk/atk02.anim
Normal file
@@ -0,0 +1,116 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.AnimationClip",
|
||||
"_name": "atk01",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {
|
||||
"embeddedPlayerGroups": []
|
||||
},
|
||||
"_native": "",
|
||||
"sample": 24,
|
||||
"speed": 1,
|
||||
"wrapMode": 1,
|
||||
"enableTrsBlending": false,
|
||||
"_duration": 0.20833333333333334,
|
||||
"_hash": 500763545,
|
||||
"_tracks": [
|
||||
{
|
||||
"__id__": 1
|
||||
}
|
||||
],
|
||||
"_exoticAnimation": null,
|
||||
"_events": [
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "atk",
|
||||
"params": []
|
||||
}
|
||||
],
|
||||
"_embeddedPlayers": [],
|
||||
"_additiveSettings": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_auxiliaryCurveEntries": []
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ObjectTrack",
|
||||
"_binding": {
|
||||
"__type__": "cc.animation.TrackBinding",
|
||||
"path": {
|
||||
"__id__": 2
|
||||
},
|
||||
"proxy": null
|
||||
},
|
||||
"_channel": {
|
||||
"__id__": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.TrackPath",
|
||||
"_paths": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 4
|
||||
},
|
||||
"spriteFrame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.HierarchyPath",
|
||||
"path": "Node"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ComponentPath",
|
||||
"component": "cc.Sprite"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.Channel",
|
||||
"_curve": {
|
||||
"__id__": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.ObjectCurve",
|
||||
"_times": [
|
||||
0,
|
||||
0.041666666666666664,
|
||||
0.08333333333333333,
|
||||
0.125,
|
||||
0.16666666666666666
|
||||
],
|
||||
"_values": [
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@c9de1",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@1ebb0",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e5a2",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@360bd",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.AnimationClipAdditiveSettings",
|
||||
"enabled": false,
|
||||
"refClip": null
|
||||
}
|
||||
]
|
||||
13
assets/resources/game/skill/anm/atk/atk02.anim.meta
Normal file
13
assets/resources/game/skill/anm/atk/atk02.anim.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "2.0.3",
|
||||
"importer": "animation-clip",
|
||||
"imported": true,
|
||||
"uuid": "6b9c9f74-0168-4b57-824d-bfcc2cd1785d",
|
||||
"files": [
|
||||
".cconb"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"name": "atk02"
|
||||
}
|
||||
}
|
||||
116
assets/resources/game/skill/anm/atk/atk03.anim
Normal file
116
assets/resources/game/skill/anm/atk/atk03.anim
Normal file
@@ -0,0 +1,116 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.AnimationClip",
|
||||
"_name": "atk01",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {
|
||||
"embeddedPlayerGroups": []
|
||||
},
|
||||
"_native": "",
|
||||
"sample": 24,
|
||||
"speed": 1,
|
||||
"wrapMode": 1,
|
||||
"enableTrsBlending": false,
|
||||
"_duration": 0.20833333333333334,
|
||||
"_hash": 500763545,
|
||||
"_tracks": [
|
||||
{
|
||||
"__id__": 1
|
||||
}
|
||||
],
|
||||
"_exoticAnimation": null,
|
||||
"_events": [
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "atk",
|
||||
"params": []
|
||||
}
|
||||
],
|
||||
"_embeddedPlayers": [],
|
||||
"_additiveSettings": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_auxiliaryCurveEntries": []
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ObjectTrack",
|
||||
"_binding": {
|
||||
"__type__": "cc.animation.TrackBinding",
|
||||
"path": {
|
||||
"__id__": 2
|
||||
},
|
||||
"proxy": null
|
||||
},
|
||||
"_channel": {
|
||||
"__id__": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.TrackPath",
|
||||
"_paths": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 4
|
||||
},
|
||||
"spriteFrame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.HierarchyPath",
|
||||
"path": "Node"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ComponentPath",
|
||||
"component": "cc.Sprite"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.Channel",
|
||||
"_curve": {
|
||||
"__id__": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.ObjectCurve",
|
||||
"_times": [
|
||||
0,
|
||||
0.041666666666666664,
|
||||
0.08333333333333333,
|
||||
0.125,
|
||||
0.16666666666666666
|
||||
],
|
||||
"_values": [
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@c9de1",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@1ebb0",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e5a2",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@360bd",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.AnimationClipAdditiveSettings",
|
||||
"enabled": false,
|
||||
"refClip": null
|
||||
}
|
||||
]
|
||||
13
assets/resources/game/skill/anm/atk/atk03.anim.meta
Normal file
13
assets/resources/game/skill/anm/atk/atk03.anim.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "2.0.3",
|
||||
"importer": "animation-clip",
|
||||
"imported": true,
|
||||
"uuid": "26398731-5541-44ca-b2b7-4947dc9bf7f7",
|
||||
"files": [
|
||||
".cconb"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"name": "atk03"
|
||||
}
|
||||
}
|
||||
116
assets/resources/game/skill/anm/atk/atk04.anim
Normal file
116
assets/resources/game/skill/anm/atk/atk04.anim
Normal file
@@ -0,0 +1,116 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.AnimationClip",
|
||||
"_name": "atk01",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {
|
||||
"embeddedPlayerGroups": []
|
||||
},
|
||||
"_native": "",
|
||||
"sample": 24,
|
||||
"speed": 1,
|
||||
"wrapMode": 1,
|
||||
"enableTrsBlending": false,
|
||||
"_duration": 0.20833333333333334,
|
||||
"_hash": 500763545,
|
||||
"_tracks": [
|
||||
{
|
||||
"__id__": 1
|
||||
}
|
||||
],
|
||||
"_exoticAnimation": null,
|
||||
"_events": [
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "atk",
|
||||
"params": []
|
||||
}
|
||||
],
|
||||
"_embeddedPlayers": [],
|
||||
"_additiveSettings": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_auxiliaryCurveEntries": []
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ObjectTrack",
|
||||
"_binding": {
|
||||
"__type__": "cc.animation.TrackBinding",
|
||||
"path": {
|
||||
"__id__": 2
|
||||
},
|
||||
"proxy": null
|
||||
},
|
||||
"_channel": {
|
||||
"__id__": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.TrackPath",
|
||||
"_paths": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 4
|
||||
},
|
||||
"spriteFrame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.HierarchyPath",
|
||||
"path": "Node"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ComponentPath",
|
||||
"component": "cc.Sprite"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.Channel",
|
||||
"_curve": {
|
||||
"__id__": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.ObjectCurve",
|
||||
"_times": [
|
||||
0,
|
||||
0.041666666666666664,
|
||||
0.08333333333333333,
|
||||
0.125,
|
||||
0.16666666666666666
|
||||
],
|
||||
"_values": [
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@c9de1",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@1ebb0",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e5a2",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@360bd",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.AnimationClipAdditiveSettings",
|
||||
"enabled": false,
|
||||
"refClip": null
|
||||
}
|
||||
]
|
||||
13
assets/resources/game/skill/anm/atk/atk04.anim.meta
Normal file
13
assets/resources/game/skill/anm/atk/atk04.anim.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "2.0.3",
|
||||
"importer": "animation-clip",
|
||||
"imported": true,
|
||||
"uuid": "0ad94bf2-3b2d-4f80-8500-68c5e33e2ed1",
|
||||
"files": [
|
||||
".cconb"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"name": "atk04"
|
||||
}
|
||||
}
|
||||
116
assets/resources/game/skill/anm/atk/atk05.anim
Normal file
116
assets/resources/game/skill/anm/atk/atk05.anim
Normal file
@@ -0,0 +1,116 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.AnimationClip",
|
||||
"_name": "atk01",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {
|
||||
"embeddedPlayerGroups": []
|
||||
},
|
||||
"_native": "",
|
||||
"sample": 24,
|
||||
"speed": 1,
|
||||
"wrapMode": 1,
|
||||
"enableTrsBlending": false,
|
||||
"_duration": 0.20833333333333334,
|
||||
"_hash": 500763545,
|
||||
"_tracks": [
|
||||
{
|
||||
"__id__": 1
|
||||
}
|
||||
],
|
||||
"_exoticAnimation": null,
|
||||
"_events": [
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"frame": 0.08333333333333333,
|
||||
"func": "atk",
|
||||
"params": []
|
||||
}
|
||||
],
|
||||
"_embeddedPlayers": [],
|
||||
"_additiveSettings": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_auxiliaryCurveEntries": []
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ObjectTrack",
|
||||
"_binding": {
|
||||
"__type__": "cc.animation.TrackBinding",
|
||||
"path": {
|
||||
"__id__": 2
|
||||
},
|
||||
"proxy": null
|
||||
},
|
||||
"_channel": {
|
||||
"__id__": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.TrackPath",
|
||||
"_paths": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 4
|
||||
},
|
||||
"spriteFrame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.HierarchyPath",
|
||||
"path": "Node"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.ComponentPath",
|
||||
"component": "cc.Sprite"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.animation.Channel",
|
||||
"_curve": {
|
||||
"__id__": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.ObjectCurve",
|
||||
"_times": [
|
||||
0,
|
||||
0.041666666666666664,
|
||||
0.08333333333333333,
|
||||
0.125,
|
||||
0.16666666666666666
|
||||
],
|
||||
"_values": [
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@c9de1",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@1ebb0",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e5a2",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
{
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@360bd",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"__type__": "cc.AnimationClipAdditiveSettings",
|
||||
"enabled": false,
|
||||
"refClip": null
|
||||
}
|
||||
]
|
||||
13
assets/resources/game/skill/anm/atk/atk05.anim.meta
Normal file
13
assets/resources/game/skill/anm/atk/atk05.anim.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "2.0.3",
|
||||
"importer": "animation-clip",
|
||||
"imported": true,
|
||||
"uuid": "ba0a95bf-2ea1-4d27-9c4f-cbd970ca558c",
|
||||
"files": [
|
||||
".cconb"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"name": "atk05"
|
||||
}
|
||||
}
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "atk",
|
||||
"_name": "atk1",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "atk",
|
||||
"_name": "atk1",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
@@ -108,8 +108,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -0.5,
|
||||
"y": 0.5,
|
||||
"x": 1.3,
|
||||
"y": 1.1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -136,8 +136,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 139,
|
||||
"height": 128
|
||||
"width": 95,
|
||||
"height": 96
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -186,7 +186,7 @@
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": {
|
||||
"__uuid__": "3d46f945-3f07-477e-a95a-b49557d552c6",
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
@@ -222,8 +222,8 @@
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 30,
|
||||
"height": 50
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
@@ -248,8 +248,12 @@
|
||||
"__prefab": {
|
||||
"__id__": 11
|
||||
},
|
||||
"atk_x": 0,
|
||||
"atk_y": 0,
|
||||
"atk_x": 20,
|
||||
"atk_y": 40,
|
||||
"runType": 2,
|
||||
"endType": 0,
|
||||
"speed": 720,
|
||||
"time": 0,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
@@ -315,8 +319,8 @@
|
||||
},
|
||||
"_size": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 30,
|
||||
"height": 50
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
@@ -336,7 +340,7 @@
|
||||
"__prefab": {
|
||||
"__id__": 17
|
||||
},
|
||||
"playOnLoad": false,
|
||||
"playOnLoad": true,
|
||||
"_clips": [
|
||||
{
|
||||
"__uuid__": "45017156-064b-43f2-a24e-176b6ce17ad8",
|
||||
@@ -8,6 +8,6 @@
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "atk"
|
||||
"syncNodeName": "atk1"
|
||||
}
|
||||
}
|
||||
375
assets/resources/game/skill/atk/atk2.prefab
Normal file
375
assets/resources/game/skill/atk/atk2.prefab
Normal file
@@ -0,0 +1,375 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "atk2",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"data": {
|
||||
"__id__": 1
|
||||
},
|
||||
"optimizationPolicy": 0,
|
||||
"persistent": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "atk2",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 2
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 8
|
||||
},
|
||||
{
|
||||
"__id__": 10
|
||||
},
|
||||
{
|
||||
"__id__": 12
|
||||
},
|
||||
{
|
||||
"__id__": 14
|
||||
},
|
||||
{
|
||||
"__id__": 16
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Node",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1.3,
|
||||
"y": 1.1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 4
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 95,
|
||||
"height": 96
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "93pNmvtQlLSqtTgIepyEmA"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 6
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 1,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": {
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "12eKc8gltBz50frJCS5+ww"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
|
||||
},
|
||||
{
|
||||
"__type__": "57aabs7TE1J5obTAZczc+64",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 11
|
||||
},
|
||||
"atk_x": 20,
|
||||
"atk_y": 40,
|
||||
"runType": 2,
|
||||
"endType": 0,
|
||||
"speed": 720,
|
||||
"time": 0,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.RigidBody2D",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 13
|
||||
},
|
||||
"enabledContactListener": true,
|
||||
"bullet": false,
|
||||
"awakeOnLoad": true,
|
||||
"_group": 1,
|
||||
"_type": 1,
|
||||
"_allowSleep": false,
|
||||
"_gravityScale": 1,
|
||||
"_linearDamping": 0,
|
||||
"_angularDamping": 0,
|
||||
"_linearVelocity": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_angularVelocity": 0,
|
||||
"_fixedRotation": false,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "57cr7S0YpK4oEZCavwXtIX"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.BoxCollider2D",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 15
|
||||
},
|
||||
"tag": 0,
|
||||
"_group": 1,
|
||||
"_density": 1,
|
||||
"_sensor": true,
|
||||
"_friction": 0.2,
|
||||
"_restitution": 0,
|
||||
"_offset": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_size": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Animation",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 17
|
||||
},
|
||||
"playOnLoad": true,
|
||||
"_clips": [
|
||||
{
|
||||
"__uuid__": "6b9c9f74-0168-4b57-824d-bfcc2cd1785d",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
}
|
||||
],
|
||||
"_defaultClip": {
|
||||
"__uuid__": "6b9c9f74-0168-4b57-824d-bfcc2cd1785d",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "20U0hP/NZErJJXRCEib1lq"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
13
assets/resources/game/skill/atk/atk2.prefab.meta
Normal file
13
assets/resources/game/skill/atk/atk2.prefab.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "ff6e9a75-b1b9-448e-9cbd-d30de98fd21b",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "atk2"
|
||||
}
|
||||
}
|
||||
375
assets/resources/game/skill/atk/atk3.prefab
Normal file
375
assets/resources/game/skill/atk/atk3.prefab
Normal file
@@ -0,0 +1,375 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "atk3",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"data": {
|
||||
"__id__": 1
|
||||
},
|
||||
"optimizationPolicy": 0,
|
||||
"persistent": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "atk3",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 2
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 8
|
||||
},
|
||||
{
|
||||
"__id__": 10
|
||||
},
|
||||
{
|
||||
"__id__": 12
|
||||
},
|
||||
{
|
||||
"__id__": 14
|
||||
},
|
||||
{
|
||||
"__id__": 16
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Node",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1.3,
|
||||
"y": 1.1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 4
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 95,
|
||||
"height": 96
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "93pNmvtQlLSqtTgIepyEmA"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 6
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 1,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": {
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "12eKc8gltBz50frJCS5+ww"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
|
||||
},
|
||||
{
|
||||
"__type__": "57aabs7TE1J5obTAZczc+64",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 11
|
||||
},
|
||||
"atk_x": 20,
|
||||
"atk_y": 40,
|
||||
"runType": 2,
|
||||
"endType": 0,
|
||||
"speed": 720,
|
||||
"time": 0,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.RigidBody2D",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 13
|
||||
},
|
||||
"enabledContactListener": true,
|
||||
"bullet": false,
|
||||
"awakeOnLoad": true,
|
||||
"_group": 1,
|
||||
"_type": 1,
|
||||
"_allowSleep": false,
|
||||
"_gravityScale": 1,
|
||||
"_linearDamping": 0,
|
||||
"_angularDamping": 0,
|
||||
"_linearVelocity": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_angularVelocity": 0,
|
||||
"_fixedRotation": false,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "57cr7S0YpK4oEZCavwXtIX"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.BoxCollider2D",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 15
|
||||
},
|
||||
"tag": 0,
|
||||
"_group": 1,
|
||||
"_density": 1,
|
||||
"_sensor": true,
|
||||
"_friction": 0.2,
|
||||
"_restitution": 0,
|
||||
"_offset": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_size": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Animation",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 17
|
||||
},
|
||||
"playOnLoad": true,
|
||||
"_clips": [
|
||||
{
|
||||
"__uuid__": "26398731-5541-44ca-b2b7-4947dc9bf7f7",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
}
|
||||
],
|
||||
"_defaultClip": {
|
||||
"__uuid__": "26398731-5541-44ca-b2b7-4947dc9bf7f7",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "20U0hP/NZErJJXRCEib1lq"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
13
assets/resources/game/skill/atk/atk3.prefab.meta
Normal file
13
assets/resources/game/skill/atk/atk3.prefab.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "16ca6f33-f910-4202-add4-2862be6c9127",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "atk3"
|
||||
}
|
||||
}
|
||||
375
assets/resources/game/skill/atk/atk4.prefab
Normal file
375
assets/resources/game/skill/atk/atk4.prefab
Normal file
@@ -0,0 +1,375 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "atk4",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"data": {
|
||||
"__id__": 1
|
||||
},
|
||||
"optimizationPolicy": 0,
|
||||
"persistent": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "atk4",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 2
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 8
|
||||
},
|
||||
{
|
||||
"__id__": 10
|
||||
},
|
||||
{
|
||||
"__id__": 12
|
||||
},
|
||||
{
|
||||
"__id__": 14
|
||||
},
|
||||
{
|
||||
"__id__": 16
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Node",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1.3,
|
||||
"y": 1.1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 4
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 95,
|
||||
"height": 96
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "93pNmvtQlLSqtTgIepyEmA"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 6
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 1,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": {
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "12eKc8gltBz50frJCS5+ww"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
|
||||
},
|
||||
{
|
||||
"__type__": "57aabs7TE1J5obTAZczc+64",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 11
|
||||
},
|
||||
"atk_x": 20,
|
||||
"atk_y": 40,
|
||||
"runType": 2,
|
||||
"endType": 0,
|
||||
"speed": 720,
|
||||
"time": 0,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.RigidBody2D",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 13
|
||||
},
|
||||
"enabledContactListener": true,
|
||||
"bullet": false,
|
||||
"awakeOnLoad": true,
|
||||
"_group": 1,
|
||||
"_type": 1,
|
||||
"_allowSleep": false,
|
||||
"_gravityScale": 1,
|
||||
"_linearDamping": 0,
|
||||
"_angularDamping": 0,
|
||||
"_linearVelocity": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_angularVelocity": 0,
|
||||
"_fixedRotation": false,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "57cr7S0YpK4oEZCavwXtIX"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.BoxCollider2D",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 15
|
||||
},
|
||||
"tag": 0,
|
||||
"_group": 1,
|
||||
"_density": 1,
|
||||
"_sensor": true,
|
||||
"_friction": 0.2,
|
||||
"_restitution": 0,
|
||||
"_offset": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_size": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Animation",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 17
|
||||
},
|
||||
"playOnLoad": true,
|
||||
"_clips": [
|
||||
{
|
||||
"__uuid__": "0ad94bf2-3b2d-4f80-8500-68c5e33e2ed1",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
}
|
||||
],
|
||||
"_defaultClip": {
|
||||
"__uuid__": "0ad94bf2-3b2d-4f80-8500-68c5e33e2ed1",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "20U0hP/NZErJJXRCEib1lq"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
13
assets/resources/game/skill/atk/atk4.prefab.meta
Normal file
13
assets/resources/game/skill/atk/atk4.prefab.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "7f30cd3c-57d1-476a-96bb-02c4eb082576",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "atk4"
|
||||
}
|
||||
}
|
||||
375
assets/resources/game/skill/atk/atk5.prefab
Normal file
375
assets/resources/game/skill/atk/atk5.prefab
Normal file
@@ -0,0 +1,375 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.Prefab",
|
||||
"_name": "atk5",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"data": {
|
||||
"__id__": 1
|
||||
},
|
||||
"optimizationPolicy": 0,
|
||||
"persistent": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "atk5",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 2
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 8
|
||||
},
|
||||
{
|
||||
"__id__": 10
|
||||
},
|
||||
{
|
||||
"__id__": 12
|
||||
},
|
||||
{
|
||||
"__id__": 14
|
||||
},
|
||||
{
|
||||
"__id__": 16
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 18
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Node",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 3
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
}
|
||||
],
|
||||
"_prefab": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1.3,
|
||||
"y": 1.1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 4
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 95,
|
||||
"height": 96
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "93pNmvtQlLSqtTgIepyEmA"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Sprite",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 6
|
||||
},
|
||||
"_customMaterial": null,
|
||||
"_srcBlendFactor": 2,
|
||||
"_dstBlendFactor": 4,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 255,
|
||||
"b": 255,
|
||||
"a": 255
|
||||
},
|
||||
"_spriteFrame": {
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@3c745",
|
||||
"__expectedType__": "cc.SpriteFrame"
|
||||
},
|
||||
"_type": 0,
|
||||
"_fillType": 0,
|
||||
"_sizeMode": 1,
|
||||
"_fillCenter": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_fillStart": 0,
|
||||
"_fillRange": 0,
|
||||
"_isTrimmedMode": true,
|
||||
"_useGrayscale": false,
|
||||
"_atlas": {
|
||||
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
|
||||
"__expectedType__": "cc.SpriteAtlas"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "12eKc8gltBz50frJCS5+ww"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
|
||||
"instance": null,
|
||||
"targetOverrides": null,
|
||||
"nestedPrefabInstanceRoots": null
|
||||
},
|
||||
{
|
||||
"__type__": "cc.UITransform",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_contentSize": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_anchorPoint": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0.5,
|
||||
"y": 0.5
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
|
||||
},
|
||||
{
|
||||
"__type__": "57aabs7TE1J5obTAZczc+64",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 11
|
||||
},
|
||||
"atk_x": 20,
|
||||
"atk_y": 40,
|
||||
"runType": 2,
|
||||
"endType": 0,
|
||||
"speed": 720,
|
||||
"time": 0,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.RigidBody2D",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 13
|
||||
},
|
||||
"enabledContactListener": true,
|
||||
"bullet": false,
|
||||
"awakeOnLoad": true,
|
||||
"_group": 1,
|
||||
"_type": 1,
|
||||
"_allowSleep": false,
|
||||
"_gravityScale": 1,
|
||||
"_linearDamping": 0,
|
||||
"_angularDamping": 0,
|
||||
"_linearVelocity": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_angularVelocity": 0,
|
||||
"_fixedRotation": false,
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "57cr7S0YpK4oEZCavwXtIX"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.BoxCollider2D",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 15
|
||||
},
|
||||
"tag": 0,
|
||||
"_group": 1,
|
||||
"_density": 1,
|
||||
"_sensor": true,
|
||||
"_friction": 0.2,
|
||||
"_restitution": 0,
|
||||
"_offset": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"_size": {
|
||||
"__type__": "cc.Size",
|
||||
"width": 120,
|
||||
"height": 120
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Animation",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": {
|
||||
"__id__": 17
|
||||
},
|
||||
"playOnLoad": true,
|
||||
"_clips": [
|
||||
{
|
||||
"__uuid__": "ba0a95bf-2ea1-4d27-9c4f-cbd970ca558c",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
}
|
||||
],
|
||||
"_defaultClip": {
|
||||
"__uuid__": "ba0a95bf-2ea1-4d27-9c4f-cbd970ca558c",
|
||||
"__expectedType__": "cc.AnimationClip"
|
||||
},
|
||||
"_id": ""
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
"fileId": "20U0hP/NZErJJXRCEib1lq"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PrefabInfo",
|
||||
"root": {
|
||||
"__id__": 1
|
||||
},
|
||||
"asset": {
|
||||
"__id__": 0
|
||||
},
|
||||
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
13
assets/resources/game/skill/atk/atk5.prefab.meta
Normal file
13
assets/resources/game/skill/atk/atk5.prefab.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "7227b42b-9cc7-41bd-8858-9d05c4cc758b",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "atk5"
|
||||
}
|
||||
}
|
||||
@@ -108,8 +108,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.9,
|
||||
"y": 0.9,
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -282,7 +282,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 25,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -368,4 +372,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -108,8 +108,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.9,
|
||||
"y": 0.9,
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -282,7 +282,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 25,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -368,4 +372,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -108,8 +108,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.8,
|
||||
"y": 0.8,
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -282,7 +282,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 25,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -368,4 +372,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.35,
|
||||
"y": 0.35,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.35,
|
||||
"y": 0.35,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
@@ -253,7 +253,11 @@
|
||||
},
|
||||
"atk_x": 0,
|
||||
"atk_y": 0,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -368,4 +372,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -389,7 +389,11 @@
|
||||
},
|
||||
"atk_x": 0,
|
||||
"atk_y": 0,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 0,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -504,4 +508,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.4,
|
||||
"y": 0.4,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
@@ -250,7 +250,11 @@
|
||||
},
|
||||
"atk_x": 10,
|
||||
"atk_y": 15,
|
||||
"_id": ""
|
||||
"_id": "",
|
||||
"runType": 1,
|
||||
"endType": 2,
|
||||
"speed": 720,
|
||||
"time": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.CompPrefabInfo",
|
||||
@@ -336,4 +340,4 @@
|
||||
"instance": null,
|
||||
"targetOverrides": null
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"x": 0.3,
|
||||
"y": 0.3,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 926 KiB After Width: | Height: | Size: 982 KiB |
@@ -4746,7 +4746,7 @@
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 22.263000000000016,
|
||||
"y": 14.014,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -232,7 +232,7 @@
|
||||
"_top": 0,
|
||||
"_bottom": 546.004,
|
||||
"_horizontalCenter": 0,
|
||||
"_verticalCenter": 143,
|
||||
"_verticalCenter": -7,
|
||||
"_isAbsLeft": true,
|
||||
"_isAbsRight": true,
|
||||
"_isAbsTop": true,
|
||||
@@ -276,7 +276,7 @@
|
||||
"value": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 783,
|
||||
"y": 633,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@ export function getCardIconId(cardData: CardConfig | null): string {
|
||||
|
||||
// 2. 装备卡:取第一个 field 词条图标
|
||||
if (cardData.field && cardData.field.length > 0) {
|
||||
const fieldUuid = cardData.field[0];
|
||||
const fieldUuid = cardData.field[0].uuid;
|
||||
return FieldSkillSet[fieldUuid]?.icon || `${fieldUuid}`;
|
||||
}
|
||||
|
||||
|
||||
45
assets/script/game/common/ScreenShake.ts
Normal file
45
assets/script/game/common/ScreenShake.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @file ScreenShake.ts
|
||||
* @description 屏幕震动静态工具(表现层)
|
||||
*
|
||||
* 项目内无现成相机震动能力(MapViewScene.camera 可能为 null 且从未使用),
|
||||
* 改为抖动地图根节点 smc.map.MapView.node(地图+实体一起震,UI 层不受影响)。
|
||||
*
|
||||
* 用法:
|
||||
* ScreenShake.shake(10, 0.4); // 强度 10 像素,持续 0.4 秒
|
||||
*/
|
||||
import { tween, Tween, v3, Vec3 } from "cc";
|
||||
import { smc } from "./SingletonModuleComp";
|
||||
|
||||
export class ScreenShake {
|
||||
/** 地图根节点的基准位置(首次震动时快照,用于收敛回原位,防多次震动漂移) */
|
||||
private static basePos: Vec3 | null = null;
|
||||
|
||||
/**
|
||||
* 触发一次屏幕震动
|
||||
* @param strength 像素振幅(建议 4~10)
|
||||
* @param duration 总时长(秒,建议 0.2~0.4)
|
||||
*/
|
||||
static shake(strength: number = 8, duration: number = 0.3) {
|
||||
const target = smc.map?.MapView?.node;
|
||||
if (!target || !target.isValid) return;
|
||||
|
||||
if (!this.basePos) this.basePos = target.position.clone();
|
||||
|
||||
// 打断进行中的震动(新震动覆盖旧震动,不做强度叠加,避免失控)
|
||||
Tween.stopAllByTarget(target);
|
||||
|
||||
const steps = Math.max(1, Math.floor(duration / 0.04));
|
||||
const t = tween(target);
|
||||
for (let i = 0; i < steps; i++) {
|
||||
t.to(0.04, {
|
||||
position: v3(
|
||||
this.basePos.x + (Math.random() * 2 - 1) * strength,
|
||||
this.basePos.y + (Math.random() * 2 - 1) * strength,
|
||||
this.basePos.z),
|
||||
});
|
||||
}
|
||||
// 收敛回原位
|
||||
t.to(0.05, { position: this.basePos }).start();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "a3113d69-645e-4a4b-bf68-a434fcbd6d8d",
|
||||
"uuid": "a7e31780-029a-4f20-90ad-3e5575c9dac1",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
@@ -38,6 +38,23 @@ export interface CloudData {
|
||||
openid: string;
|
||||
data: GameDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 死亡英雄快照:英雄真实死亡时记录,供后续复活机制(重新召唤)消费。
|
||||
* 仅记录静态/成长数据,不含战斗内临时状态(buff、CD 等)。
|
||||
*/
|
||||
export interface DeadHeroRecord {
|
||||
/** 英雄模板 uuid(决定重新召唤哪个英雄) */
|
||||
uuid: number;
|
||||
/** 死亡时的英雄等级(复活后继承) */
|
||||
lv: number;
|
||||
/** 卡牌等级(驱动 bg_node 颜色) */
|
||||
card_lv: number;
|
||||
/** 死亡时占用的站位索引(复活时优先归位,-1 表示未分配) */
|
||||
posIndex: number;
|
||||
/** 死亡时的回合数(用于统计/按回合清理) */
|
||||
wave: number;
|
||||
}
|
||||
/** 游戏模块 */
|
||||
@ecs.register('SingletonModule')
|
||||
export class SingletonModuleComp extends ecs.Comp {
|
||||
@@ -58,8 +75,10 @@ export class SingletonModuleComp extends ecs.Comp {
|
||||
in_select: false,
|
||||
in_fight: false,
|
||||
stop_mon_action: false,
|
||||
heroGrid: [-1, -1, -1, -1, -1, -1],
|
||||
heroGrid: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
|
||||
monGrid: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
|
||||
/** 本局真实死亡的英雄快照列表,供后续复活机制消费;整局结束(MissionEnd)时清空 */
|
||||
dead_heroes: [] as DeadHeroRecord[],
|
||||
};
|
||||
data: any = {
|
||||
openid: '',
|
||||
@@ -85,6 +104,8 @@ export class SingletonModuleComp extends ecs.Comp {
|
||||
game_pause: false,
|
||||
mission_data: {
|
||||
mon_num: 0,//怪物数量
|
||||
pending_mon_num: 0,//待刷出怪物数量(MissionMonComp 每帧写入,供回合结束检测与 HUD 进度)
|
||||
wave_early_skip: 0,//本回合清场加速累计节省的秒数(MissionComp 用于还原 DDA 判定口径)
|
||||
hero_num: 0,//英雄数量
|
||||
hero_max_num: FightSet.HERO_MAX_NUM,//英雄可召唤上限
|
||||
hero_extend_max_num: FightSet.HERO_MAX_NUM + 1,//英雄可拓展上限
|
||||
|
||||
@@ -104,12 +104,12 @@ export const BuffList: Record<number, BuffConfig> = {
|
||||
modifiers: [{ attr: Attrs.critical, op: ModOp.Flat, value: 30 }],
|
||||
info: "暴击率提升 30%,持续 5 秒",
|
||||
},
|
||||
// 击退概率强化(限时)
|
||||
// 击退距离强化(限时)
|
||||
7013: {
|
||||
id: 7013, name: "击退爆发", icon: "Stat_Stun_01",
|
||||
category: BuffCategory.Buff, duration: 5, max_stack: 1,
|
||||
modifiers: [{ attr: Attrs.knockback_chance, op: ModOp.Flat, value: 30 }],
|
||||
info: "击退概率提升 30%,持续 5 秒",
|
||||
info: "击退距离提升 30,持续 5 秒",
|
||||
},
|
||||
// 穿透强化(限时)
|
||||
7014: {
|
||||
@@ -167,4 +167,11 @@ export const BuffList: Record<number, BuffConfig> = {
|
||||
modifiers: [{ attr: Attrs.freeze_chance, op: ModOp.Flat, value: 5 }],
|
||||
info: "冰冻概率提升 5%,持续 5 秒",
|
||||
},
|
||||
// 穿透伤害强化(限时)
|
||||
7022: {
|
||||
id: 7022, name: "穿透伤害爆发", icon: "Stat_Tripleshot",
|
||||
category: BuffCategory.Buff, duration: 5, max_stack: 1,
|
||||
modifiers: [{ attr: Attrs.puncture_dmg_bonus, op: ModOp.Flat, value: 30 }],
|
||||
info: "穿透后伤害提升 30%,持续 5 秒",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as exp from "constants"
|
||||
import { HeroInfo, HeroList, HType } from "./heroSet"
|
||||
import { HeroInfo, HeroList, HType, FieldEntry } from "./heroSet"
|
||||
import { oops } from "db://oops-framework/core/Oops"
|
||||
import { SkillOverrides, TGroup } from "./SkillSet"
|
||||
|
||||
@@ -73,7 +73,7 @@ export interface CardConfig {
|
||||
t_inv?: number // 触发间隔(秒)
|
||||
keep_waves?: number // 维持的回合数(-1表示持续到战斗结束,0或undefined表示仅本回合)
|
||||
overrides?: SkillOverrides // 技能参数覆写(如自定义伤害ap、buff值、金币数等)
|
||||
field?: number[] // 装备词条数组:驻场光环卡(trigger_type=Field)的属性词条,支持单卡多词条(如 [7002, 7004, 7005] 同时提供攻击/暴击/暴伤)
|
||||
field?: FieldEntry[] // 装备词条数组:驻场光环卡(trigger_type=Field)的属性词条,支持单卡多词条;value 为实际生效数值(如 [{uuid:7002,value:0.1},{uuid:7004,value:0.1}] 同时提供攻击/暴击)
|
||||
|
||||
/** 触发类型(必填,技能卡专用;功能卡/英雄卡可缺省) */
|
||||
trigger_type?: CardTriggerType;
|
||||
@@ -102,17 +102,26 @@ function getHeroCost(cardLv: number): number {
|
||||
return 15;
|
||||
}
|
||||
|
||||
/**
|
||||
* 英雄卡按 card_lv 分配抽取权重(几何递减,高等级越稀有)。
|
||||
* 梯度:lv1=40, lv2=25, lv3=15, lv4=8, lv5=3
|
||||
*/
|
||||
const HERO_WEIGHT_BY_LV: Record<number, number> = {
|
||||
1: 40, 2: 25, 3: 15, 4: 8, 5: 3,
|
||||
};
|
||||
function getHeroWeight(cardLv: number): number {
|
||||
return HERO_WEIGHT_BY_LV[cardLv] ?? HERO_WEIGHT_BY_LV[1];
|
||||
}
|
||||
|
||||
HeroList.forEach(uuid => {
|
||||
const hero = HeroInfo[uuid];
|
||||
if (!hero) return;
|
||||
|
||||
const baseWeight = 25;
|
||||
|
||||
CardPoolList.push({
|
||||
uuid: hero.uuid,
|
||||
type: CardType.Hero,
|
||||
cost: getHeroCost(hero.card_lv ?? 1),
|
||||
weight: baseWeight,
|
||||
weight: getHeroWeight(hero.card_lv ?? 1),
|
||||
card_lv: hero.card_lv ?? 1,
|
||||
kind: CKind.Hero,
|
||||
hero_lv: 1,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* - FieldSkillSet(SkillSet)—— 属性词条配置
|
||||
*/
|
||||
import { CardConfig, CardType, CKind, CardTriggerType } from "./CardSet";
|
||||
import { FieldEntry } from "./heroSet";
|
||||
|
||||
/** 装备卡原始数据(仅装备相关字段,由 EquipSet 内部转换为 CardConfig) */
|
||||
interface EquipRawData {
|
||||
@@ -23,63 +24,66 @@ interface EquipRawData {
|
||||
name: string;
|
||||
info: string;
|
||||
icon?: string; // 图标ID(对应 ui6 图集 frame 名)
|
||||
field: number[];
|
||||
field: FieldEntry[]; // 驻场光环词条:uuid 指向 FieldSkillSet 底座,value 为实际生效数值
|
||||
cost?: number;
|
||||
}
|
||||
|
||||
/** 装备卡原始数据表 */
|
||||
const EquipRawList: EquipRawData[] = [
|
||||
// ==================== card_lv 1 档(基础强度) ====================
|
||||
{ uuid: 8701, card_lv: 1, icon: "Helmet-FA_Helmet_021_Gray", name: "战后恢复", info: "战斗结束生命回复量+10%", field: [7001] },
|
||||
{ uuid: 8702, card_lv: 1, icon: "Sword-FA_WP_Main_Sword_003_GrayGold", name: "攻击加成", info: "全体攻击力+10%", field: [7002] },
|
||||
{ uuid: 8703, card_lv: 1, icon: "Blunt-FA_WP_Main_Blunt_002_Wood", name: "击晕加成", info: "全体击晕概率+10%", field: [7003] },
|
||||
{ uuid: 8704, card_lv: 1, icon: "Axe-FA_WP_Main_Axe_001_WoodGray", name: "暴击加成", info: "全体暴击率+10%", field: [7004] },
|
||||
{ uuid: 8705, card_lv: 1, icon: "Bow-FA_WP_Main_Bow_003_WoodGreen", name: "暴伤加成", info: "全体暴击伤害+20%", field: [7005] },
|
||||
{ uuid: 8706, card_lv: 1, icon: "Crossbow-FA_WP_Main_Crossbow_001_WoodWhite", name: "攻速加成", info: "全体攻击速度+10%", field: [7006] },
|
||||
{ uuid: 8707, card_lv: 1, icon: "Chest-FA_Chest_014_Gray", name: "生命加成", info: "全体最大生命+10%", field: [7007] },
|
||||
{ uuid: 8708, card_lv: 1, icon: "Wand-FA_WP_Main_Wand_001_GrayGreen", name: "风怒加成", info: "全体风怒概率+10%", field: [7008] },
|
||||
{ uuid: 8709, card_lv: 1, icon: "Spear-FA_WP_Main_Spear_004_WoodBlue", name: "穿刺加成", info: "全体穿刺概率+10%", field: [7009] },
|
||||
{ uuid: 8710, card_lv: 1, icon: "Shield-FA_WP_Sub_Shield_007_GoldGray", name: "金币收益", info: "每回合金币收益+1", field: [7010] },
|
||||
{ uuid: 8711, card_lv: 1, icon: "Staff-FA_WP_Main_Staff_003_WoodSkyblue", name: "出售强化", info: "卖出英雄金币+1", field: [7011] },
|
||||
{ uuid: 8701, card_lv: 1, icon: "Helmet-FA_Helmet_021_Gray", name: "战后恢复", info: "战斗结束生命回复量+10%", field: [{ uuid: 7001, value: 0.1 }] },
|
||||
{ uuid: 8702, card_lv: 1, icon: "Sword-FA_WP_Main_Sword_003_GrayGold", name: "攻击加成", info: "全体攻击力+10%", field: [{ uuid: 7002, value: 0.1 }] },
|
||||
{ uuid: 8703, card_lv: 1, icon: "Blunt-FA_WP_Main_Blunt_002_Wood", name: "击晕加成", info: "全体击晕概率+10%", field: [{ uuid: 7003, value: 0.1 }] },
|
||||
{ uuid: 8704, card_lv: 1, icon: "Axe-FA_WP_Main_Axe_001_WoodGray", name: "暴击加成", info: "全体暴击率+10%", field: [{ uuid: 7004, value: 0.1 }] },
|
||||
{ uuid: 8705, card_lv: 1, icon: "Bow-FA_WP_Main_Bow_003_WoodGreen", name: "暴伤加成", info: "全体暴击伤害+20%", field: [{ uuid: 7005, value: 0.2 }] },
|
||||
{ uuid: 8706, card_lv: 1, icon: "Crossbow-FA_WP_Main_Crossbow_001_WoodWhite", name: "攻速加成", info: "全体攻击速度+10%", field: [{ uuid: 7006, value: 0.1 }] },
|
||||
{ uuid: 8707, card_lv: 1, icon: "Chest-FA_Chest_014_Gray", name: "生命加成", info: "全体最大生命+10%", field: [{ uuid: 7007, value: 0.1 }] },
|
||||
{ uuid: 8708, card_lv: 1, icon: "Wand-FA_WP_Main_Wand_001_GrayGreen", name: "风怒加成", info: "全体风怒概率+10%", field: [{ uuid: 7008, value: 0.1 }] },
|
||||
{ uuid: 8709, card_lv: 1, icon: "Spear-FA_WP_Main_Spear_004_WoodBlue", name: "穿刺加成", info: "全体穿刺概率+10%", field: [{ uuid: 7009, value: 0.1 }] },
|
||||
{ uuid: 8712, card_lv: 1, icon: "Spear-FA_WP_Main_Spear_004_WoodBlue", name: "破甲加成", info: "全体穿透后伤害+10%", field: [{ uuid: 7020, value: 0.1 }] },
|
||||
{ uuid: 8710, card_lv: 1, icon: "Shield-FA_WP_Sub_Shield_007_GoldGray", name: "金币收益", info: "每回合金币收益+1", field: [{ uuid: 7010, value: 1 }] },
|
||||
{ uuid: 8711, card_lv: 1, icon: "Staff-FA_WP_Main_Staff_003_WoodSkyblue", name: "出售强化", info: "卖出英雄金币+1", field: [{ uuid: 7011, value: 1 }] },
|
||||
// 多词条装备
|
||||
{ uuid: 8720, card_lv: 1, icon: "Shield-FA_WP_Sub_Shield_007_GoldGray", name: "勇者护符", info: "全体攻击力+10%, 暴击率+10%, 暴击伤害+20%", field: [7002, 7004, 7005] },
|
||||
{ uuid: 8720, card_lv: 1, icon: "Shield-FA_WP_Sub_Shield_007_GoldGray", name: "勇者护符", info: "全体攻击力+10%, 暴击率+10%, 暴击伤害+20%", field: [{ uuid: 7002, value: 0.1 }, { uuid: 7004, value: 0.1 }, { uuid: 7005, value: 0.2 }] },
|
||||
|
||||
// ==================== card_lv 2 档(强度 ×2) ====================
|
||||
{ uuid: 8751, card_lv: 2, icon: "Helmet-FA_Helmet_022_DarkBlue", name: "战后恢复", info: "战斗结束生命回复量+20%", field: [7201] },
|
||||
{ uuid: 8752, card_lv: 2, icon: "Sword-FA_WP_Main_Sword_007_Blue", name: "攻击加成", info: "全体攻击力+20%", field: [7202] },
|
||||
{ uuid: 8753, card_lv: 2, icon: "Blunt-FA_WP_Main_Blunt_004_Gray", name: "击晕加成", info: "全体击晕概率+20%", field: [7203] },
|
||||
{ uuid: 8754, card_lv: 2, icon: "Axe-FA_WP_Main_Axe_006_WoodBronze", name: "暴击加成", info: "全体暴击率+20%", field: [7204] },
|
||||
{ uuid: 8755, card_lv: 2, icon: "Bow-FA_WP_Main_Bow_010_Blue", name: "暴伤加成", info: "全体暴击伤害+40%", field: [7205] },
|
||||
{ uuid: 8756, card_lv: 2, icon: "Crossbow-FA_WP_Main_Crossbow_004_Gray", name: "攻速加成", info: "全体攻击速度+20%", field: [7206] },
|
||||
{ uuid: 8757, card_lv: 2, icon: "Chest-FA_Chest_014_BlueGold", name: "生命加成", info: "全体最大生命+20%", field: [7207] },
|
||||
{ uuid: 8758, card_lv: 2, icon: "Wand-FA_WP_Main_Wand_003_BlueWood", name: "风怒加成", info: "全体风怒概率+20%", field: [7208] },
|
||||
{ uuid: 8759, card_lv: 2, icon: "Spear-FA_WP_Main_Spear_007_GraySkyblue", name: "穿刺加成", info: "全体穿刺概率+20%", field: [7209] },
|
||||
{ uuid: 8760, card_lv: 2, icon: "Shield-FA_WP_Sub_Shield_008_BlueGold", name: "金币收益", info: "每回合金币收益+2", field: [7210] },
|
||||
{ uuid: 8761, card_lv: 2, icon: "Shield-FA_WP_Sub_Shield_008_BlueGold", name: "购买优惠", info: "购买卡牌费用-2金币", field: [7212] },
|
||||
{ uuid: 8762, card_lv: 2, icon: "Staff-FA_WP_Main_Staff_007_WoodGray", name: "刷新优惠", info: "刷新卡牌费用-1金币", field: [7213] },
|
||||
{ uuid: 8751, card_lv: 2, icon: "Helmet-FA_Helmet_022_DarkBlue", name: "战后恢复", info: "战斗结束生命回复量+20%", field: [{ uuid: 7001, value: 0.2 }] },
|
||||
{ uuid: 8752, card_lv: 2, icon: "Sword-FA_WP_Main_Sword_007_Blue", name: "攻击加成", info: "全体攻击力+20%", field: [{ uuid: 7002, value: 0.2 }] },
|
||||
{ uuid: 8753, card_lv: 2, icon: "Blunt-FA_WP_Main_Blunt_004_Gray", name: "击晕加成", info: "全体击晕概率+20%", field: [{ uuid: 7003, value: 0.2 }] },
|
||||
{ uuid: 8754, card_lv: 2, icon: "Axe-FA_WP_Main_Axe_006_WoodBronze", name: "暴击加成", info: "全体暴击率+20%", field: [{ uuid: 7004, value: 0.2 }] },
|
||||
{ uuid: 8755, card_lv: 2, icon: "Bow-FA_WP_Main_Bow_010_Blue", name: "暴伤加成", info: "全体暴击伤害+40%", field: [{ uuid: 7005, value: 0.4 }] },
|
||||
{ uuid: 8756, card_lv: 2, icon: "Crossbow-FA_WP_Main_Crossbow_004_Gray", name: "攻速加成", info: "全体攻击速度+20%", field: [{ uuid: 7006, value: 0.2 }] },
|
||||
{ uuid: 8757, card_lv: 2, icon: "Chest-FA_Chest_014_BlueGold", name: "生命加成", info: "全体最大生命+20%", field: [{ uuid: 7007, value: 0.2 }] },
|
||||
{ uuid: 8758, card_lv: 2, icon: "Wand-FA_WP_Main_Wand_003_BlueWood", name: "风怒加成", info: "全体风怒概率+20%", field: [{ uuid: 7008, value: 0.2 }] },
|
||||
{ uuid: 8759, card_lv: 2, icon: "Spear-FA_WP_Main_Spear_007_GraySkyblue", name: "穿刺加成", info: "全体穿刺概率+20%", field: [{ uuid: 7009, value: 0.2 }] },
|
||||
{ uuid: 8763, card_lv: 2, icon: "Spear-FA_WP_Main_Spear_007_GraySkyblue", name: "破甲加成", info: "全体穿透后伤害+20%", field: [{ uuid: 7020, value: 0.2 }] },
|
||||
{ uuid: 8760, card_lv: 2, icon: "Shield-FA_WP_Sub_Shield_008_BlueGold", name: "金币收益", info: "每回合金币收益+2", field: [{ uuid: 7010, value: 2 }] },
|
||||
{ uuid: 8761, card_lv: 2, icon: "Shield-FA_WP_Sub_Shield_008_BlueGold", name: "购买优惠", info: "购买卡牌费用-2金币", field: [{ uuid: 7012, value: 2 }] },
|
||||
{ uuid: 8762, card_lv: 2, icon: "Staff-FA_WP_Main_Staff_007_WoodGray", name: "刷新优惠", info: "刷新卡牌费用-1金币", field: [{ uuid: 7013, value: 1 }] },
|
||||
// 多词条装备
|
||||
{ uuid: 8770, card_lv: 2, icon: "Chest-FA_Chest_014_BlueGold", name: "守卫护甲", info: "全体最大生命+20%, 战后生命回复+20%, 攻速+20%", field: [7207, 7201, 7206] },
|
||||
{ uuid: 8770, card_lv: 2, icon: "Chest-FA_Chest_014_BlueGold", name: "守卫护甲", info: "全体最大生命+20%, 战后生命回复+20%, 攻速+20%", field: [{ uuid: 7007, value: 0.2 }, { uuid: 7001, value: 0.2 }, { uuid: 7006, value: 0.2 }] },
|
||||
|
||||
// ==================== card_lv 3 档(强度 ×3) ====================
|
||||
{ uuid: 8801, card_lv: 3, icon: "Helmet-FA_Helmet_021_RedGold", name: "战后恢复", info: "战斗结束生命回复量+30%", field: [7401] },
|
||||
{ uuid: 8802, card_lv: 3, icon: "Sword-FA_WP_Main_Sword_001_Gold", name: "攻击加成", info: "全体攻击力+30%", field: [7402] },
|
||||
{ uuid: 8803, card_lv: 3, icon: "Blunt-FA_WP_Main_Blunt_006_OrangeGray", name: "击晕加成", info: "全体击晕概率+30%", field: [7403] },
|
||||
{ uuid: 8804, card_lv: 3, icon: "Axe-FA_WP_Main_Axe_012_WoodGold", name: "暴击加成", info: "全体暴击率+30%", field: [7404] },
|
||||
{ uuid: 8805, card_lv: 3, icon: "Bow-FA_WP_Main_Bow_010_Gold", name: "暴伤加成", info: "全体暴击伤害+60%", field: [7405] },
|
||||
{ uuid: 8806, card_lv: 3, icon: "Crossbow-FA_WP_Main_Crossbow_005_WoodGold", name: "攻速加成", info: "全体攻击速度+30%", field: [7406] },
|
||||
{ uuid: 8807, card_lv: 3, icon: "Chest-FA_Chest_015_RedGold", name: "生命加成", info: "全体最大生命+30%", field: [7407] },
|
||||
{ uuid: 8808, card_lv: 3, icon: "Wand-FA_WP_Main_Wand_004_WoodGold", name: "风怒加成", info: "全体风怒概率+30%", field: [7408] },
|
||||
{ uuid: 8809, card_lv: 3, icon: "Spear-FA_WP_Main_Spear_008_GrayGold", name: "穿刺加成", info: "全体穿刺概率+30%", field: [7409] },
|
||||
{ uuid: 8810, card_lv: 3, icon: "Shield-FA_WP_Sub_Shield_009_RedGold", name: "金币收益", info: "每回合金币收益+3", field: [7410] },
|
||||
{ uuid: 8801, card_lv: 3, icon: "Helmet-FA_Helmet_021_RedGold", name: "战后恢复", info: "战斗结束生命回复量+30%", field: [{ uuid: 7001, value: 0.3 }] },
|
||||
{ uuid: 8802, card_lv: 3, icon: "Sword-FA_WP_Main_Sword_001_Gold", name: "攻击加成", info: "全体攻击力+30%", field: [{ uuid: 7002, value: 0.3 }] },
|
||||
{ uuid: 8803, card_lv: 3, icon: "Blunt-FA_WP_Main_Blunt_006_OrangeGray", name: "击晕加成", info: "全体击晕概率+30%", field: [{ uuid: 7003, value: 0.3 }] },
|
||||
{ uuid: 8804, card_lv: 3, icon: "Axe-FA_WP_Main_Axe_012_WoodGold", name: "暴击加成", info: "全体暴击率+30%", field: [{ uuid: 7004, value: 0.3 }] },
|
||||
{ uuid: 8805, card_lv: 3, icon: "Bow-FA_WP_Main_Bow_010_Gold", name: "暴伤加成", info: "全体暴击伤害+60%", field: [{ uuid: 7005, value: 0.6 }] },
|
||||
{ uuid: 8806, card_lv: 3, icon: "Crossbow-FA_WP_Main_Crossbow_005_WoodGold", name: "攻速加成", info: "全体攻击速度+30%", field: [{ uuid: 7006, value: 0.3 }] },
|
||||
{ uuid: 8807, card_lv: 3, icon: "Chest-FA_Chest_015_RedGold", name: "生命加成", info: "全体最大生命+30%", field: [{ uuid: 7007, value: 0.3 }] },
|
||||
{ uuid: 8808, card_lv: 3, icon: "Wand-FA_WP_Main_Wand_004_WoodGold", name: "风怒加成", info: "全体风怒概率+30%", field: [{ uuid: 7008, value: 0.3 }] },
|
||||
{ uuid: 8809, card_lv: 3, icon: "Spear-FA_WP_Main_Spear_008_GrayGold", name: "穿刺加成", info: "全体穿刺概率+30%", field: [{ uuid: 7009, value: 0.3 }] },
|
||||
{ uuid: 8817, card_lv: 3, icon: "Spear-FA_WP_Main_Spear_008_GrayGold", name: "破甲加成", info: "全体穿透后伤害+30%", field: [{ uuid: 7020, value: 0.3 }] },
|
||||
{ uuid: 8810, card_lv: 3, icon: "Shield-FA_WP_Sub_Shield_009_RedGold", name: "金币收益", info: "每回合金币收益+3", field: [{ uuid: 7010, value: 3 }] },
|
||||
// 触发次数强化
|
||||
{ uuid: 8811, card_lv: 3, icon: "Wand-FA_WP_Main_Wand_004_WoodGold", name: "召唤强化", info: "召唤触发技能次数+1", field: [7014] },
|
||||
{ uuid: 8812, card_lv: 3, icon: "Staff-FA_WP_Main_Staff_005_WoodGold", name: "死亡强化", info: "死亡触发技能次数+1", field: [7015] },
|
||||
{ uuid: 8813, card_lv: 3, icon: "Spear-FA_WP_Main_Spear_008_GrayGold", name: "开场强化", info: "战斗开始触发技能次数+1", field: [7016] },
|
||||
{ uuid: 8814, card_lv: 3, icon: "Blunt-FA_WP_Main_Blunt_006_OrangeGray", name: "结束强化", info: "战斗结束触发技能次数+1", field: [7017] },
|
||||
{ uuid: 8815, card_lv: 3, icon: "Sword-FA_WP_Main_Sword_001_Gold", name: "攻击强化", info: "攻击触发技能次数+1", field: [7018] },
|
||||
{ uuid: 8816, card_lv: 3, icon: "Shield-FA_WP_Sub_Shield_009_RedGold", name: "受击强化", info: "被攻击触发技能次数+1", field: [7019] },
|
||||
{ uuid: 8811, card_lv: 3, icon: "Wand-FA_WP_Main_Wand_004_WoodGold", name: "召唤强化", info: "召唤触发技能次数+1", field: [{ uuid: 7014, value: 1 }] },
|
||||
{ uuid: 8812, card_lv: 3, icon: "Staff-FA_WP_Main_Staff_005_WoodGold", name: "死亡强化", info: "死亡触发技能次数+1", field: [{ uuid: 7015, value: 1 }] },
|
||||
{ uuid: 8813, card_lv: 3, icon: "Spear-FA_WP_Main_Spear_008_GrayGold", name: "开场强化", info: "战斗开始触发技能次数+1", field: [{ uuid: 7016, value: 1 }] },
|
||||
{ uuid: 8814, card_lv: 3, icon: "Blunt-FA_WP_Main_Blunt_006_OrangeGray", name: "结束强化", info: "战斗结束触发技能次数+1", field: [{ uuid: 7017, value: 1 }] },
|
||||
{ uuid: 8815, card_lv: 3, icon: "Sword-FA_WP_Main_Sword_001_Gold", name: "攻击强化", info: "攻击触发技能次数+1", field: [{ uuid: 7018, value: 1 }] },
|
||||
{ uuid: 8816, card_lv: 3, icon: "Shield-FA_WP_Sub_Shield_009_RedGold", name: "受击强化", info: "被攻击触发技能次数+1", field: [{ uuid: 7019, value: 1 }] },
|
||||
// 多词条装备
|
||||
{ uuid: 8820, card_lv: 3, icon: "Sword-FA_WP_Main_Sword_001_Gold", name: "神圣之刃", info: "全体攻击力+30%, 暴击率+30%, 暴击伤害+60%, 攻速+30%", field: [7402, 7404, 7405, 7406] },
|
||||
{ uuid: 8820, card_lv: 3, icon: "Sword-FA_WP_Main_Sword_001_Gold", name: "神圣之刃", info: "全体攻击力+30%, 暴击率+30%, 暴击伤害+60%, 攻速+30%", field: [{ uuid: 7002, value: 0.3 }, { uuid: 7004, value: 0.3 }, { uuid: 7005, value: 0.6 }, { uuid: 7006, value: 0.3 }] },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,5 +88,14 @@ export enum GameEvent {
|
||||
UseEquipCard = "UseEquipCard", // 装备卡购买使用事件
|
||||
RemoveEquipBox = "RemoveEquipBox", // 装备盒销毁事件
|
||||
HeroBoxEmptyClick = "HeroBoxEmptyClick", // 英雄面板空槽位点击事件(展示英雄卡池)
|
||||
BossWarning = "BossWarning", // Boss 回合战斗开始预警(payload: { wave, eta },eta 为 Boss 预计进场秒数)
|
||||
/** 回合清屏(场上+待刷怪全灭),payload: { wave, clearTime, allAlive, fastClear } */
|
||||
WaveClear = "WaveClear",
|
||||
/** Boss 实体刷出瞬间(payload: { pos },供登场震屏/音效定位) */
|
||||
BossSpawn = "BossSpawn",
|
||||
/** 金币飞行表现(payload: { worldPos, gold, isBoss },账务已即时结算,纯视觉事件) */
|
||||
CoinFly = "CoinFly",
|
||||
/** 连杀达阈值(payload: { count, tier },tier 0/1/2 对应 5/10/20 连杀) */
|
||||
ComboReach = "ComboReach",
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export enum BoxSet {
|
||||
LETF_END = -360,
|
||||
RIGHT_END = 360,
|
||||
//游戏地平线
|
||||
GAME_LINE = 160,
|
||||
GAME_LINE = 10,
|
||||
/** 技能/装备各自独立的上限 */
|
||||
BOX_MAX = 6,
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export enum FightSet {
|
||||
CRIT_DAMAGE = 50,//暴击伤害
|
||||
MORE_RC = 10,//更多次数 广告获取的次数
|
||||
HEARTPOS = -320,//基地位置
|
||||
HERO_MAX_NUM = 3,//英雄最大数量
|
||||
HERO_MAX_NUM = 10,//英雄最大数量
|
||||
/** 英雄可升级到的最高等级(通过升级卡提升) */
|
||||
HERO_MAX_LV = 6,
|
||||
/** 英雄技能等级上限(无限升级下技能 5 级封顶,超出等级只长属性) */
|
||||
@@ -39,15 +39,17 @@ export enum FightSet {
|
||||
// BACK_RANG=30,//后退范围
|
||||
BACK_RANG = 30,//后退范围
|
||||
FiIGHT_TIME = 30,//战斗时间
|
||||
// BACK_CHANCE=40,//击退概率
|
||||
FROST_TIME = 3,//冰冻时间
|
||||
STUN_TIME = 2,//击晕时间
|
||||
SKILL_CAST_DELAY = 0.15,
|
||||
CSKILL_START_X = -340,
|
||||
CSKILL_START_Y = 30,
|
||||
/** 护盾数值上限(数值减免型:1 点护盾吸收 1 点伤害) */
|
||||
SHIELD_MAX = 5,
|
||||
WAVE_HEAL_RATE = 0.5, // 回合结束时所有英雄恢复最大生命值的比例
|
||||
WAVE_HEAL_RATE = 0.4, // 回合结束时所有英雄恢复最大生命值的比例(与 DDA 放水兜底分层:回血保节奏,放水防崩盘)
|
||||
PUNCTURE_DOWN = 50,
|
||||
/** 穿透伤害衰减系数:单体技能命中第2个及以后目标时按此比例折算伤害 */
|
||||
PUNCTURE_DMG_RATIO = 0.5,
|
||||
REFRESH_COST = 2,
|
||||
BASE_COST = 5,
|
||||
INIT_COIN = 7, // 初始金币数
|
||||
|
||||
@@ -31,11 +31,12 @@ export enum Attrs {
|
||||
freeze_res = "freeze_res", // 冰冻抗性
|
||||
stun_chance = "stun_chance", // 击晕概率
|
||||
stun_res = "stun_res", // 击晕抗性
|
||||
knockback_chance = "knockback_chance", // 击退概率
|
||||
knockback_chance = "knockback_chance", // 击退强化(原击退概率,现转换为击退距离增量)
|
||||
knockback_distance = "knockback_distance", // 击退距离强化
|
||||
knockback_res = "knockback_res", // 击退抗性
|
||||
invincible_time = "invincible_time",// 无敌时间
|
||||
puncture_chance = "puncture_chance", // 穿透概率
|
||||
puncture_dmg_bonus = "puncture_dmg_bonus", // 穿透后伤害增量(百分点;最终穿透系数 = PUNCTURE_DMG_RATIO * (1 + bonus/100))
|
||||
wfuny = "wfuny", // 风怒
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
| 复活 | 不灭、凤凰、涅槃 |
|
||||
| 攻速 | 风怒、迅捷 |
|
||||
| 光环 | 战歌、光环、领域 |
|
||||
| 穿透伤害 | 破甲、贯甲、穿心 |
|
||||
|
||||
### 预设命名示例
|
||||
|
||||
@@ -113,6 +114,7 @@
|
||||
| fstart | 攻击 | 破晓先知、战吼法师、晨光祭司 |
|
||||
| fstart | 护盾 | 圣光祭司、守护贤者、壁垒法师 |
|
||||
| field | 攻速 | 战歌法师、风怒先知、迅捷贤者 |
|
||||
| field | 穿透伤害 | 破甲先知、贯甲贤者、穿心祭司 |
|
||||
| revive | 复活 | 不灭战魂、凤凰祭司、涅槃贤者 |
|
||||
|
||||
## 规则 4:info 描述
|
||||
@@ -125,6 +127,7 @@
|
||||
| 编号 | 名称 | card_lv | 触发类型 | 能力 | 状态 |
|
||||
|------|------|---------|---------|------|------|
|
||||
| 5001 | 铁壁战士 | 1 | atked | 护盾 | 保留(原 5011) |
|
||||
| 5106 | 破甲射手 | 2 | atking | 穿透伤害(计时buff,技能 6512 + Buff 7022) | 已创建 |
|
||||
| 5002+ | 待定 | 待定 | 待定 | 待定 | 待创建 |
|
||||
|
||||
## 重构代码变更清单
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/**
|
||||
* @file HeroSkillDesc.ts
|
||||
* @description 英雄技能/能力描述的【唯一文字表达标准层】
|
||||
*
|
||||
@@ -28,7 +28,7 @@
|
||||
* - SkillDescSet:结构化描述模板
|
||||
* - FieldSkillSet(驻场技能)、BuffList(计时 buff 默认值兜底)、Attrs(属性枚举)
|
||||
*/
|
||||
import { SkillTriggerName, SkillTriggerType, TriggerGrouped, LvTriggerEntry, LvFieldEntry, LvReviveEntry, LvBonusEntry, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, getTriggerSkillName, HGrowType, calcGrowBonus } from "./heroSet";
|
||||
import { SkillTriggerName, SkillTriggerType, TriggerGrouped, LvTriggerEntry, LvFieldEntry, LvReviveEntry, LvBonusEntry, FieldEntry, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, getTriggerSkillName, HGrowType, calcGrowBonus } from "./heroSet";
|
||||
import { FieldSkillSet, mergeSkillParams, SkillConfig, SkillKind, SkillOverrides, SkillSet, TGroup } from "./SkillSet";
|
||||
import { FieldDescSet, FieldValueFmt, SkillDescSet } from "./SkillDescSet";
|
||||
import { BuffList } from "./BuffSet";
|
||||
@@ -242,26 +242,26 @@ function buildEffectLine(head: string, skill: SkillConfig): ISkillLine {
|
||||
* @param showName 是否在行中包含「技能名」片段(英雄面板需要,装备商店不需要)
|
||||
* @param nameOverride 技能名覆盖(英雄语境传入 heroSet 语境专属命名,缺省用 FieldSkillSet 通用名)
|
||||
*/
|
||||
function buildFieldLine(head: string, uuid: number, showName: boolean = true, nameOverride?: string): ISkillLine | null {
|
||||
const fs = FieldSkillSet[uuid];
|
||||
function buildFieldLine(head: string, entry: FieldEntry, showName: boolean = true, nameOverride?: string): ISkillLine | null {
|
||||
const fs = FieldSkillSet[entry.uuid];
|
||||
if (!fs) return null;
|
||||
const desc = FieldDescSet[fs.type];
|
||||
const nameSeg = showName ? `「${nameOverride ?? fs.name}」 ` : "";
|
||||
// 未入表类型回退基础备注 info,整行不高亮
|
||||
if (!desc) return { prefix: `${head}${nameSeg}${fs.info}`, value: "", suffix: "" };
|
||||
|
||||
// 数值按格式渲染为高亮片段
|
||||
// 数值按格式渲染为高亮片段(数值取自词条自身 value)
|
||||
let valueText: string;
|
||||
switch (desc.fmt) {
|
||||
case FieldValueFmt.Percent:
|
||||
valueText = `+${Math.round(fs.value * 100)}%`;
|
||||
valueText = `+${Math.round(entry.value * 100)}%`;
|
||||
break;
|
||||
case FieldValueFmt.IntMinus:
|
||||
valueText = `-${fs.value}`;
|
||||
valueText = `-${entry.value}`;
|
||||
break;
|
||||
case FieldValueFmt.Int:
|
||||
default:
|
||||
valueText = `+${fs.value}`;
|
||||
valueText = `+${entry.value}`;
|
||||
break;
|
||||
}
|
||||
return { prefix: `${head}${nameSeg}${desc.verb}`, value: valueText, suffix: "" };
|
||||
@@ -433,8 +433,8 @@ function buildSkillLines(source: ISkillDescSource, showName: boolean = true, sho
|
||||
if (fieldEntries?.length) {
|
||||
const tpl = SkillTriggerName[SkillTriggerType.Field] ?? "光环";
|
||||
if (!showLocked) {
|
||||
for (const uuid of resolveFieldByLv(fieldEntries, lv)) {
|
||||
const line = buildFieldLine(`${tpl}:`, uuid, showName);
|
||||
for (const entry of resolveFieldByLv(fieldEntries, lv)) {
|
||||
const line = buildFieldLine(`${tpl}:`, entry, showName);
|
||||
if (line) lines.push(line);
|
||||
}
|
||||
} else {
|
||||
@@ -444,15 +444,15 @@ function buildSkillLines(source: ISkillDescSource, showName: boolean = true, sho
|
||||
let activeTier: LvFieldEntry | undefined;
|
||||
for (const tier of sorted) { if (tier.lv <= lv) activeTier = tier; }
|
||||
if (activeTier) {
|
||||
for (const uuid of activeTier.uuids) {
|
||||
const line = buildFieldLine(`Lv${activeTier.lv}: ${tpl} `, uuid, showName);
|
||||
for (const entry of activeTier.uuids) {
|
||||
const line = buildFieldLine(`Lv${activeTier.lv}: ${tpl} `, entry, showName);
|
||||
if (line) lines.push(line);
|
||||
}
|
||||
}
|
||||
for (const tier of sorted) {
|
||||
if (tier.lv <= lv) continue;
|
||||
for (const uuid of tier.uuids) {
|
||||
const line = buildFieldLine(`Lv${tier.lv}: ${tpl} `, uuid, showName);
|
||||
for (const entry of tier.uuids) {
|
||||
const line = buildFieldLine(`Lv${tier.lv}: ${tpl} `, entry, showName);
|
||||
if (!line) continue;
|
||||
line.locked = true;
|
||||
lines.push(line);
|
||||
@@ -525,15 +525,15 @@ export function buildSingleSkillRich(skillUuid: number, overrides?: SkillOverrid
|
||||
|
||||
/**
|
||||
* 装备/驻场光环详情富文本:展示全部词条,数值绿色高亮。
|
||||
* @param fieldUuids FieldSkillSet 驻场光环 UUID 数组
|
||||
* @param showName 是否包含「技能名」片段(英雄面板 true,装备商店 false)
|
||||
* @returns 多行富文本串,每行一个词条
|
||||
* @param entries 驻场光环词条数组(uuid 指向 FieldSkillSet 底座,value 为生效数值)
|
||||
* @param showName 是否包含「技能名」片段(英雄面板 true,装备商店 false)
|
||||
* @returns 多行富文本串,每行一个词条
|
||||
*/
|
||||
export function buildEquipRich(fieldUuids: number[], showName: boolean = false): string {
|
||||
if (!fieldUuids?.length) return "无词条";
|
||||
export function buildEquipRich(entries: FieldEntry[], showName: boolean = false): string {
|
||||
if (!entries?.length) return "无词条";
|
||||
const lines: string[] = [];
|
||||
for (const uuid of fieldUuids) {
|
||||
const line = buildFieldLine("", uuid, showName);
|
||||
for (const entry of entries) {
|
||||
const line = buildFieldLine("", entry, showName);
|
||||
if (!line) continue;
|
||||
const prefix = line.prefix.replace(SKILL_NAME_PATTERN, (m, g1) => skillNameRich(g1));
|
||||
const text = line.value
|
||||
@@ -554,10 +554,11 @@ export function buildEquipRich(fieldUuids: number[], showName: boolean = false):
|
||||
* 数据源为运行时 HeroAttrsComp(lv 取当前英雄等级),逐触发类型逐技能渲染。
|
||||
* 技能等级取 LvTriggerEntry.s_lv,未标注时按档位序号(1/2/3...)兜底。
|
||||
*
|
||||
* @param source 英雄运行时数据源(含 lv 与触发技能分组)
|
||||
* @returns 多行富文本串,\n 分隔
|
||||
* @param source 英雄运行时数据源(含 lv 与触发技能分组)
|
||||
* @param showName 是否展示「技能名」片段(英雄信息弹窗 true,英雄卡池卡片 false)
|
||||
* @returns 多行富文本串,\n 分隔
|
||||
*/
|
||||
export function buildSkillDescMoreRich(source: ISkillDescSource): string {
|
||||
export function buildSkillDescMoreRich(source: ISkillDescSource, showName: boolean = true): string {
|
||||
const lv = Math.max(1, source.lv ?? 1);
|
||||
const lines: string[] = [];
|
||||
|
||||
@@ -598,20 +599,18 @@ export function buildSkillDescMoreRich(source: ISkillDescSource): string {
|
||||
const curTrig = needCount
|
||||
? `${trigName}${valueRich(cur.t_num)}次`
|
||||
: trigName;
|
||||
// 技能名 + 档位数字后缀(如 "不屈壁垒1"),与光环命名口径一致
|
||||
const curHead = `「${skillName}${curSLv}」: ${curTrig}, `;
|
||||
// 技能名直接展示,不附加档位数字后缀(由 lv 字段控制生效档,玩家无需关注内部档号)
|
||||
const curHead = showName ? `「${skillName}」: ${curTrig}, ` : `${curTrig}, `;
|
||||
lines.push(effectRich(buildEffectLine(curHead, mergeSkillParams(base, cur.overrides))));
|
||||
}
|
||||
}
|
||||
|
||||
// 驻场光环(field):技能名按档位带数字后缀(如 [攻击光环1]/[攻击光环2]),便于玩家识别成长
|
||||
// 驻场光环(field):直接展示技能名,不附加档位数字后缀
|
||||
const fieldEntries = source[SkillTriggerType.Field] as LvFieldEntry[] | undefined;
|
||||
if (fieldEntries?.length) {
|
||||
for (const uuid of resolveFieldByLv(fieldEntries, lv)) {
|
||||
// uuid 百位编码档位:7002 基础(0)=1、7202 +(2)=2、7402 ++(4)=3
|
||||
const tier = Math.floor((uuid % 1000) / 100) / 2 + 1;
|
||||
const baseName = getTriggerSkillName(SkillTriggerType.Field, uuid);
|
||||
const line = buildFieldLine("", uuid, true, `${baseName}${tier}`);
|
||||
for (const entry of resolveFieldByLv(fieldEntries, lv)) {
|
||||
const baseName = getTriggerSkillName(SkillTriggerType.Field, entry.uuid);
|
||||
const line = buildFieldLine("", entry, showName, baseName);
|
||||
if (line) lines.push(effectRich(line));
|
||||
}
|
||||
}
|
||||
@@ -620,7 +619,9 @@ export function buildSkillDescMoreRich(source: ISkillDescSource): string {
|
||||
const revive = resolveReviveByLv(source[SkillTriggerType.Revive] as LvReviveEntry[] | undefined, lv);
|
||||
if (revive) {
|
||||
const reviveName = getTriggerSkillName(SkillTriggerType.Revive, revive.s_uuid);
|
||||
lines.push(`${skillNameRich(reviveName)}: 每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(revive.upr * 100)}%`)}血量原地重生`);
|
||||
lines.push(showName
|
||||
? `${skillNameRich(reviveName)}: 每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(revive.upr * 100)}%`)}血量原地重生`
|
||||
: `每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(revive.upr * 100)}%`)}血量原地重生`);
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
|
||||
@@ -38,7 +38,7 @@ const ItemCardData: ItemCardRaw[] = [
|
||||
// ==================== 计时性强化(统一 5s) ====================
|
||||
{ uuid: 9102, skill: 6503, icon: "Sub_Item-FA_WP_Sub_Potion_001_Red", name: "攻击药水", info: "全体友方攻击力提升", cost: 0, weight: 15 },
|
||||
{ uuid: 9103, skill: 6504, icon: "Sub_Item-FA_WP_Sub_Potion_001_Yellow", name: "暴击药水", info: "全体友方暴击率提升", cost: 0, weight: 15 },
|
||||
{ uuid: 9104, skill: 6505, icon: "Sub_Item-FA_WP_Sub_Potion_001_Purple", name: "击退药水", info: "全体友方击退概率提升", cost: 0, weight: 12 },
|
||||
{ uuid: 9104, skill: 6505, icon: "Sub_Item-FA_WP_Sub_Potion_001_Purple", name: "击退药水", info: "全体友方击退距离提升", cost: 0, weight: 12 },
|
||||
{ uuid: 9105, skill: 6506, icon: "Sub_Item-FA_WP_Sub_Potion_001_Blue", name: "穿透药水", info: "全体友方穿透概率提升", cost: 0, weight: 12 },
|
||||
{ uuid: 9106, skill: 6507, icon: "Sub_Item-FA_WP_Sub_Potion_001_Red", name: "攻速药水", info: "全体友方攻击速度提升", cost: 0, weight: 15 },
|
||||
{ uuid: 9107, skill: 6508, icon: "Sub_Item-FA_WP_Sub_Potion_001_Purple", name: "风怒药水", info: "全体友方风怒概率提升", cost: 0, weight: 12 },
|
||||
|
||||
@@ -221,7 +221,7 @@ export const SkillDescSet: Record<number, SkillDescConfig> = {
|
||||
templTimed: "{target}{verb}{value}{unit},持续{dur}秒",
|
||||
},
|
||||
6505: {
|
||||
verb: "击退概率提升", unit: "%", permField: "ap", timedField: "buff_value",
|
||||
verb: "击退距离提升", unit: "", permField: "ap", timedField: "buff_value",
|
||||
templPerm: "{target}{verb}{value}{unit}",
|
||||
templTimed: "{target}{verb}{value}{unit},持续{dur}秒",
|
||||
},
|
||||
@@ -281,7 +281,7 @@ export interface FieldDescConfig {
|
||||
|
||||
/**
|
||||
* 驻场光环描述表,key = FieldSkillType。
|
||||
* 数值永远取 FieldSkillConfig.value 实时渲染,替代手写 info,杜绝漂移。
|
||||
* 数值取配置处词条(FieldEntry.value)实时渲染,替代手写 info,杜绝漂移。
|
||||
*/
|
||||
export const FieldDescSet: Record<number, FieldDescConfig> = {
|
||||
[FieldSkillType.SummonCount]: { verb: "召唤触发技能次数", fmt: FieldValueFmt.Int },
|
||||
@@ -301,6 +301,7 @@ export const FieldDescSet: Record<number, FieldDescConfig> = {
|
||||
[FieldSkillType.HeroHp]: { verb: "全体最大生命", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroWindFury]: { verb: "全体风怒概率", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroPuncture]: { verb: "全体穿刺概率", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroPunctureDmg]: { verb: "全体穿透后伤害", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.AtkCount]: { verb: "攻击触发技能次数", fmt: FieldValueFmt.Int },
|
||||
[FieldSkillType.BeAtkCount]: { verb: "被攻击触发技能次数", fmt: FieldValueFmt.Int },
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// ========== 从 HeroAttrs.ts 导入属性相关定义 ==========
|
||||
// ========== 从 HeroAttrs.ts 导入属性相关定义 ==========
|
||||
import { Attrs } from "./HeroAttrs";
|
||||
|
||||
export enum HSSet {
|
||||
@@ -152,7 +152,7 @@ export interface SkillConfig {
|
||||
crt?: number, // 额外暴击率
|
||||
frz?: number, // 额外冰冻概率
|
||||
stun?: number, // 额外击晕概率
|
||||
bck?: number, // 额外击退概率
|
||||
bck?: number, // 额外击退强化(击退距离增量)
|
||||
buff_type?: Attrs, // Buff 类型 (单一职责)
|
||||
timed_buff_id?: number, // 计时性 buff 配置 id(指向 BuffList),存在时走 buffManager 而非永久 add_*
|
||||
buff_value?: number, // 覆写计时 buff 的修饰数值(modifiers.value / tick.damage_or_heal),由药水卡自定义档位
|
||||
@@ -216,37 +216,37 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6001: {
|
||||
uuid: 6001, name: "飞剑", sp_name: "a1", icon: "Sword-FA_WP_Main_Sword_003_PurpleSilver", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6002: {
|
||||
uuid: 6002, name: "飞刀", sp_name: "a2", icon: "Sword-FA_WP_Main_Sword_009_GraySilver", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6003: {
|
||||
uuid: 6003, name: "匕首", sp_name: "a3", icon: "Sword-FA_WP_Main_Sword_019_WoodSilver", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6004: {
|
||||
uuid: 6004, name: "飞斧", sp_name: "a4", icon: "Axe-FA_WP_Main_Axe_004_WoodGray", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6005: {
|
||||
uuid: 6005, name: "飞枪", sp_name: "a5", icon: "Spear-FA_WP_Main_Spear_010_MetalBlue", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6006: {
|
||||
uuid: 6006, name: "攻击", sp_name: "atk", icon: "Stat_Attack_01", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6007: {
|
||||
uuid: 6007, name: "飞棒", sp_name: "b1", icon: "Blunt-FA_WP_Main_Blunt_002_Wood", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6008: {
|
||||
uuid: 6008, name: "射击", sp_name: "aw2", icon: "Stat_RangedAttack", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
@@ -266,32 +266,32 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6011: {
|
||||
uuid: 6011, name: "飞锤", sp_name: "b2", icon: "Blunt-FA_WP_Main_Blunt_005_Gray", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6012: {
|
||||
uuid: 6012, name: "飞斧", sp_name: "b2", icon: "Axe-FA_WP_Main_Axe_002_WoodGray", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6013: {
|
||||
uuid: 6013, name: "蓝魔球", sp_name: "m1", icon: "Sub_Item-FA_WP_Sub_MagicBall_001_Blue", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6014: {
|
||||
uuid: 6014, name: "紫魔球", sp_name: "m2", icon: " Sub_Item-FA_WP_Sub_MagicBall_001_Purple", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6015: {
|
||||
uuid: 6015, name: "绿药瓶", sp_name: "h1", icon: "Sub_Item-FA_WP_Sub_Potion_001_Green", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
6016: {
|
||||
uuid: 6016, name: "红药瓶", sp_name: "h2", icon: "Sub_Item-FA_WP_Sub_Potion_001_Red", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害",
|
||||
},
|
||||
/*** ======中阶范围攻击技能 ====
|
||||
* 都带有穿刺属性,至少穿透1 个目标 攻击2 个目标, 升级方向是 最多 穿透3 个攻击 4 个目标
|
||||
@@ -303,7 +303,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6101: {
|
||||
uuid: 6101, name: "火球", sp_name: "fb-1", icon: "Stat_FireDamage", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, stun: 0, ap: 100, hit_count: 2, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
|
||||
RType: RType.linear, EType: EType.collision, info: "造成攻击力100%的伤害, 一定几率暴击, 高阶技能",
|
||||
RType: RType.bezier, EType: EType.collision, info: "造成攻击力100%的伤害, 一定几率暴击, 高阶技能",
|
||||
},
|
||||
//怪物法师统一使用 暗影球
|
||||
6102: {
|
||||
@@ -363,7 +363,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6406: {
|
||||
uuid: 6406, name: "击退强化", sp_name: "buff_wind", icon: "Stat_Stun_01", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 1, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.knockback_chance, timed_buff_id: 7013, info: "全体友方击退概率限时提升, 持续5秒",
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.knockback_chance, timed_buff_id: 7013, info: "全体友方击退距离限时提升, 持续5秒",
|
||||
},
|
||||
6407: {
|
||||
uuid: 6407, name: "距推强化", sp_name: "buff_wind", icon: "Stat_Stun_01", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
@@ -385,6 +385,11 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 1, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.freeze_chance, timed_buff_id: 7021, info: "全体友方冰冻概率限时提升, 持续5秒",
|
||||
},
|
||||
6411: {
|
||||
uuid: 6411, name: "穿透伤害强化", sp_name: "buff_wind", icon: "Stat_Tripleshot", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 10, hit_count: 1, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.puncture_dmg_bonus, info: "全体友方穿透后伤害提升10%(百分点), 永久生效",
|
||||
},
|
||||
6501: {
|
||||
uuid: 6501, name: "复活", sp_name: "buff_wind", icon: "Stat_HolyDamage", TGroup: TGroup.Self, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 50, hit_count: 3, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
@@ -411,7 +416,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
6505: {
|
||||
uuid: 6505, name: "击退爆发", sp_name: "buff_wind", icon: "Stat_Stun_01", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 0, hit_count: 3, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.knockback_chance, timed_buff_id: 7013, info: "全体友方击退概率提升30%, 持续5秒",
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.knockback_chance, timed_buff_id: 7013, info: "全体友方击退距离提升30, 持续5秒",
|
||||
},
|
||||
6506: {
|
||||
uuid: 6506, name: "穿透爆发", sp_name: "buff_wind", icon: "Stat_Tripleshot", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
@@ -442,6 +447,11 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
uuid: 6511, name: "冰冻抗性", sp_name: "buff_wind", icon: "Stat_Freeze", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 0, hit_count: 3, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.freeze_res, timed_buff_id: 7019, info: "全体友方冰冻抗性提升50%, 持续5秒",
|
||||
},
|
||||
6512: {
|
||||
uuid: 6512, name: "穿透伤害爆发", sp_name: "buff_wind", icon: "Stat_Tripleshot", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
|
||||
DTType: DTType.single, kind: SkillKind.Support, ap: 0, hit_count: 3, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.support,
|
||||
RType: RType.fixed, EType: EType.animationEnd, buff_type: Attrs.puncture_dmg_bonus, timed_buff_id: 7022, info: "全体友方穿透后伤害提升30%, 持续5秒",
|
||||
}
|
||||
|
||||
};
|
||||
@@ -468,6 +478,7 @@ export enum FieldSkillType {
|
||||
HeroPuncture = 18, // 英雄穿刺概率加成
|
||||
AtkCount = 19, // 攻击触发技能次数提升
|
||||
BeAtkCount = 20, // 被攻击触发技能次数提升
|
||||
HeroPunctureDmg = 21, // 英雄穿透后伤害加成(百分点;乘算:PUNCTURE_DMG_RATIO * (1 + bonus/100))
|
||||
}
|
||||
|
||||
export interface FieldSkillConfig {
|
||||
@@ -475,68 +486,37 @@ export interface FieldSkillConfig {
|
||||
name: string;
|
||||
icon: string; // 新增图标字段
|
||||
type: FieldSkillType;
|
||||
value: number; // 提升的数值
|
||||
info: string;
|
||||
info: string; // 描述模板(不含具体数值,数值由配置处携带)
|
||||
}
|
||||
|
||||
/**
|
||||
* 驻场光环底座表:每个 FieldSkillType 仅保留一条基础配置。
|
||||
*
|
||||
* Why: 同一光环因数值不同拆成多个 uuid(7002/7202/7402)会造成大量重复条目。
|
||||
* 合并后数值由配置处(heroSet/EquipSet 的 field 条目)携带,此处只维护
|
||||
* 类型/图标/名称等元数据。所有光环 uuid 统一使用 7001~7020 基础段。
|
||||
*/
|
||||
export const FieldSkillSet: Record<number, FieldSkillConfig> = {
|
||||
7001: { uuid: 7001, name: "战后恢复", icon: "Stat_PotionBoost", type: FieldSkillType.WaveHeal, info: "战斗结束生命回复量提升" },
|
||||
7002: { uuid: 7002, name: "攻击光环", icon: "Stat_Attack_03", type: FieldSkillType.HeroAtk, info: "英雄攻击力提升" },
|
||||
7003: { uuid: 7003, name: "击晕光环", icon: "Stat_Stun_01", type: FieldSkillType.HeroStun, info: "英雄击晕概率提升" },
|
||||
7004: { uuid: 7004, name: "暴击光环", icon: "Stat_CriticalChance_02", type: FieldSkillType.HeroCrit, info: "英雄暴击率提升" },
|
||||
7005: { uuid: 7005, name: "暴伤光环", icon: "Stat_Critical_01", type: FieldSkillType.HeroCritDamage, info: "英雄暴击伤害提升" },
|
||||
7006: { uuid: 7006, name: "攻速光环", icon: "Stat_AttackSpeed_02", type: FieldSkillType.HeroSpeed, info: "英雄攻击速度提升" },
|
||||
7007: { uuid: 7007, name: "生命光环", icon: "Stat_Hp_02", type: FieldSkillType.HeroHp, info: "英雄最大生命提升" },
|
||||
7008: { uuid: 7008, name: "风怒光环", icon: "Stat_CriticalComboChance", type: FieldSkillType.HeroWindFury, info: "英雄风怒概率提升" },
|
||||
7009: { uuid: 7009, name: "穿刺光环", icon: "Stat_Tripleshot", type: FieldSkillType.HeroPuncture, info: "英雄穿刺概率提升" },
|
||||
|
||||
7010: { uuid: 7010, name: "金币收益", icon: "Stat_InventorySlotIncrease", type: FieldSkillType.WaveGold, info: "每回合金币收益提升" },
|
||||
7011: { uuid: 7011, name: "出售强化", icon: "Stat_GoldGainIncrease_01", type: FieldSkillType.SellGold, info: "卖出英雄金币提升" },
|
||||
7012: { uuid: 7012, name: "购买优惠", icon: "Stat_KeyCapacityIncrease", type: FieldSkillType.BuyDiscount, info: "购买卡牌费用减免" },
|
||||
7013: { uuid: 7013, name: "刷新优惠", icon: "Stat_RandomBonus", type: FieldSkillType.RefreshDiscount, info: "刷新卡牌费用减免" },
|
||||
|
||||
7001: { uuid: 7001, name: "战后恢复", icon: "Stat_PotionBoost", type: FieldSkillType.WaveHeal, value: 0.1, info: "战斗结束生命回复量+10%" },
|
||||
7002: { uuid: 7002, name: "攻击光环", icon: "Stat_Attack_03", type: FieldSkillType.HeroAtk, value: 0.1, info: "英雄攻击力+10%" },
|
||||
7003: { uuid: 7003, name: "击晕光环", icon: "Stat_Stun_01", type: FieldSkillType.HeroStun, value: 0.1, info: "英雄击晕概率+10%" },
|
||||
7004: { uuid: 7004, name: "暴击光环", icon: "Stat_CriticalChance_02", type: FieldSkillType.HeroCrit, value: 0.1, info: "英雄暴击率+10%" },
|
||||
7005: { uuid: 7005, name: "暴伤光环", icon: "Stat_Critical_01", type: FieldSkillType.HeroCritDamage, value: 0.2, info: "英雄暴击伤害+20%" },
|
||||
7006: { uuid: 7006, name: "攻速光环", icon: "Stat_AttackSpeed_02", type: FieldSkillType.HeroSpeed, value: 0.1, info: "英雄攻击速度+10%" },
|
||||
7007: { uuid: 7007, name: "生命光环", icon: "Stat_Hp_02", type: FieldSkillType.HeroHp, value: 0.1, info: "英雄最大生命+10%" },
|
||||
7008: { uuid: 7008, name: "风怒光环", icon: "Stat_CriticalComboChance", type: FieldSkillType.HeroWindFury, value: 0.1, info: "英雄风怒概率+10%" },
|
||||
7009: { uuid: 7009, name: "穿刺光环", icon: "Stat_Tripleshot", type: FieldSkillType.HeroPuncture, value: 0.1, info: "英雄穿刺概率+10%" },
|
||||
|
||||
7010: { uuid: 7010, name: "金币收益", icon: "Stat_InventorySlotIncrease", type: FieldSkillType.WaveGold, value: 1, info: "每回合金币收益+1" },
|
||||
7011: { uuid: 7011, name: "出售强化", icon: "Stat_GoldGainIncrease_01", type: FieldSkillType.SellGold, value: 1, info: "卖出英雄金币+1" },
|
||||
7012: { uuid: 7012, name: "购买优惠", icon: "Stat_KeyCapacityIncrease", type: FieldSkillType.BuyDiscount, value: 1, info: "购买卡牌费用-1金币" },
|
||||
7013: { uuid: 7013, name: "刷新优惠", icon: "Stat_RandomBonus", type: FieldSkillType.RefreshDiscount, value: 1, info: "刷新卡牌费用-1金币" },
|
||||
|
||||
7014: { uuid: 7014, name: "召唤强化", icon: "Stat_UnitSummonIncrease_02", type: FieldSkillType.SummonCount, value: 1, info: "召唤触发技能次数+1" },
|
||||
7015: { uuid: 7015, name: "死亡强化", icon: "Stat_PoisonChanceIncrease", type: FieldSkillType.DeadCount, value: 1, info: "死亡触发技能次数+1" },
|
||||
7016: { uuid: 7016, name: "开场强化", icon: "Stat_AttackRangeIncrease_01", type: FieldSkillType.StartCount, value: 1, info: "战斗开始触发技能次数+1" },
|
||||
7017: { uuid: 7017, name: "结束强化", icon: "Stat_UnitSummonIncrease_01", type: FieldSkillType.EndCount, value: 1, info: "战斗结束触发技能次数+1" },
|
||||
7018: { uuid: 7018, name: "攻击强化", icon: "Stat_Attack_03", type: FieldSkillType.AtkCount, value: 1, info: "攻击触发技能次数+1" },
|
||||
7019: { uuid: 7019, name: "受击强化", icon: "Stat_Armor_01", type: FieldSkillType.BeAtkCount, value: 1, info: "被攻击触发技能次数+1" },
|
||||
|
||||
// ============ wave5 档(原值 ×2) ============
|
||||
|
||||
7201: { uuid: 7201, name: "战后恢复+", icon: "Stat_PotionBoost", type: FieldSkillType.WaveHeal, value: 0.2, info: "战斗结束生命回复量+20%" },
|
||||
7202: { uuid: 7202, name: "攻击光环+", icon: "Stat_Attack_03", type: FieldSkillType.HeroAtk, value: 0.2, info: "英雄攻击力+20%" },
|
||||
7203: { uuid: 7203, name: "击晕光环+", icon: "Stat_Stun_01", type: FieldSkillType.HeroStun, value: 0.2, info: "英雄击晕概率+20%" },
|
||||
7204: { uuid: 7204, name: "暴击光环+", icon: "Stat_CriticalChance_02", type: FieldSkillType.HeroCrit, value: 0.2, info: "英雄暴击率+20%" },
|
||||
7205: { uuid: 7205, name: "暴伤光环+", icon: "Stat_Critical_01", type: FieldSkillType.HeroCritDamage, value: 0.4, info: "英雄暴击伤害+40%" },
|
||||
7206: { uuid: 7206, name: "攻速光环+", icon: "Stat_AttackSpeed_02", type: FieldSkillType.HeroSpeed, value: 0.2, info: "英雄攻击速度+20%" },
|
||||
7207: { uuid: 7207, name: "生命光环+", icon: "Stat_Hp_02", type: FieldSkillType.HeroHp, value: 0.2, info: "英雄最大生命+20%" },
|
||||
7208: { uuid: 7208, name: "风怒光环+", icon: "Stat_CriticalComboChance", type: FieldSkillType.HeroWindFury, value: 0.2, info: "英雄风怒概率+20%" },
|
||||
7209: { uuid: 7209, name: "穿刺光环+", icon: "Stat_Tripleshot", type: FieldSkillType.HeroPuncture, value: 0.2, info: "英雄穿刺概率+20%" },
|
||||
|
||||
|
||||
7210: { uuid: 7210, name: "金币收益+", icon: "Stat_InventorySlotIncrease", type: FieldSkillType.WaveGold, value: 2, info: "每回合金币收益+2" },
|
||||
7211: { uuid: 7211, name: "出售强化+", icon: "Stat_GoldGainIncrease_01", type: FieldSkillType.SellGold, value: 2, info: "卖出英雄金币+2" },
|
||||
7212: { uuid: 7212, name: "购买优惠+", icon: "Stat_KeyCapacityIncrease", type: FieldSkillType.BuyDiscount, value: 2, info: "购买卡牌费用-2金币" },
|
||||
7213: { uuid: 7213, name: "刷新优惠+", icon: "Stat_RandomBonus", type: FieldSkillType.RefreshDiscount, value: 2, info: "刷新卡牌费用-2金币" },
|
||||
|
||||
|
||||
|
||||
7401: { uuid: 7401, name: "战后恢复++", icon: "Stat_PotionBoost", type: FieldSkillType.WaveHeal, value: 0.3, info: "战斗结束生命回复量+30%" },
|
||||
7402: { uuid: 7402, name: "攻击光环++", icon: "Stat_Attack_03", type: FieldSkillType.HeroAtk, value: 0.3, info: "英雄攻击力+30%" },
|
||||
7403: { uuid: 7403, name: "击晕光环++", icon: "Stat_Stun_01", type: FieldSkillType.HeroStun, value: 0.3, info: "英雄击晕概率+30%" },
|
||||
7404: { uuid: 7404, name: "暴击光环++", icon: "Stat_CriticalChance_02", type: FieldSkillType.HeroCrit, value: 0.3, info: "英雄暴击率+30%" },
|
||||
7405: { uuid: 7405, name: "暴伤光环++", icon: "Stat_Critical_01", type: FieldSkillType.HeroCritDamage, value: 0.6, info: "英雄暴击伤害+60%" },
|
||||
7406: { uuid: 7406, name: "攻速光环++", icon: "Stat_AttackSpeed_02", type: FieldSkillType.HeroSpeed, value: 0.3, info: "英雄攻击速度+30%" },
|
||||
7407: { uuid: 7407, name: "生命光环++", icon: "Stat_Hp_02", type: FieldSkillType.HeroHp, value: 0.3, info: "英雄最大生命+30%" },
|
||||
7408: { uuid: 7408, name: "风怒光环++", icon: "Stat_CriticalComboChance", type: FieldSkillType.HeroWindFury, value: 0.3, info: "英雄风怒概率+30%" },
|
||||
7409: { uuid: 7409, name: "穿刺光环++", icon: "Stat_Tripleshot", type: FieldSkillType.HeroPuncture, value: 0.3, info: "英雄穿刺概率+30%" },
|
||||
|
||||
|
||||
7410: { uuid: 7410, name: "金币收益++", icon: "Stat_InventorySlotIncrease", type: FieldSkillType.WaveGold, value: 3, info: "每回合金币收益+3" },
|
||||
7411: { uuid: 7411, name: "出售强化++", icon: "Stat_GoldGainIncrease_01", type: FieldSkillType.SellGold, value: 3, info: "卖出英雄金币+3" },
|
||||
7412: { uuid: 7412, name: "购买优惠++", icon: "Stat_KeyCapacityIncrease", type: FieldSkillType.BuyDiscount, value: 3, info: "购买卡牌费用-3金币" },
|
||||
7413: { uuid: 7413, name: "刷新优惠++", icon: "Stat_RandomBonus", type: FieldSkillType.RefreshDiscount, value: 3, info: "刷新卡牌费用-3金币" },
|
||||
7014: { uuid: 7014, name: "召唤强化", icon: "Stat_UnitSummonIncrease_02", type: FieldSkillType.SummonCount, info: "召唤触发技能次数提升" },
|
||||
7015: { uuid: 7015, name: "死亡强化", icon: "Stat_PoisonChanceIncrease", type: FieldSkillType.DeadCount, info: "死亡触发技能次数提升" },
|
||||
7016: { uuid: 7016, name: "开场强化", icon: "Stat_AttackRangeIncrease_01", type: FieldSkillType.StartCount, info: "战斗开始触发技能次数提升" },
|
||||
7017: { uuid: 7017, name: "结束强化", icon: "Stat_UnitSummonIncrease_01", type: FieldSkillType.EndCount, info: "战斗结束触发技能次数提升" },
|
||||
7018: { uuid: 7018, name: "攻击强化", icon: "Stat_Attack_03", type: FieldSkillType.AtkCount, info: "攻击触发技能次数提升" },
|
||||
7019: { uuid: 7019, name: "受击强化", icon: "Stat_Armor_01", type: FieldSkillType.BeAtkCount, info: "被攻击触发技能次数提升" },
|
||||
7020: { uuid: 7020, name: "穿透强化", icon: "Stat_Tripleshot", type: FieldSkillType.HeroPunctureDmg, info: "英雄穿透后伤害提升(百分点)" },
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"ver": "1.0.1",
|
||||
"importer": "text",
|
||||
"imported": true,
|
||||
"uuid": "83563f4f-dcd3-4a6f-a2d8-735a643f593e",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ export const MonTypeName: Record<number, string> = {
|
||||
* 英雄攻击速度分级配置 (数值为攻击间隔cd,越小越快)
|
||||
* 每档3个速度级别:Lv1(最快) → Lv2(中间) → Lv3(最慢)
|
||||
* 6档共18级,覆盖所有英雄/怪物的攻击间隔
|
||||
* 注:最慢档 Slow3/VerySlow 系列上限 1.10s,保证战斗节奏紧凑
|
||||
*/
|
||||
export enum AtkSpeedLv {
|
||||
VeryFast1 = 1, VeryFast2 = 2, VeryFast3 = 3,
|
||||
@@ -55,22 +56,22 @@ export enum AtkSpeedLv {
|
||||
export const AtkSpeedSet = {
|
||||
[AtkSpeedLv.VeryFast1]: { name: "极速++", cd: 0.15 },
|
||||
[AtkSpeedLv.VeryFast2]: { name: "极速+", cd: 0.20 },
|
||||
[AtkSpeedLv.VeryFast3]: { name: "极速", cd: 0.30 },
|
||||
[AtkSpeedLv.Fast1]: { name: "快速++", cd: 0.40 },
|
||||
[AtkSpeedLv.Fast2]: { name: "快速+", cd: 0.50 },
|
||||
[AtkSpeedLv.Fast3]: { name: "快速", cd: 0.70 },
|
||||
[AtkSpeedLv.Normal1]: { name: "中速++", cd: 0.80 },
|
||||
[AtkSpeedLv.Normal2]: { name: "中速+", cd: 0.90 },
|
||||
[AtkSpeedLv.Normal3]: { name: "中速", cd: 1.00 },
|
||||
[AtkSpeedLv.Mid1]: { name: "一般+", cd: 1.10 },
|
||||
[AtkSpeedLv.Mid2]: { name: "一般", cd: 1.20 },
|
||||
[AtkSpeedLv.Mid3]: { name: "一般-", cd: 1.30 },
|
||||
[AtkSpeedLv.Slow1]: { name: "慢", cd: 1.50 },
|
||||
[AtkSpeedLv.Slow2]: { name: "慢+", cd: 1.60 },
|
||||
[AtkSpeedLv.Slow3]: { name: "慢++", cd: 1.80 },
|
||||
[AtkSpeedLv.VerySlow1]: { name: "很慢", cd: 2.30 },
|
||||
[AtkSpeedLv.VerySlow2]: { name: "很慢+", cd: 2.50 },
|
||||
[AtkSpeedLv.VerySlow3]: { name: "很慢++", cd: 2.80 },
|
||||
[AtkSpeedLv.VeryFast3]: { name: "极速", cd: 0.28 },
|
||||
[AtkSpeedLv.Fast1]: { name: "快速++", cd: 0.36 },
|
||||
[AtkSpeedLv.Fast2]: { name: "快速+", cd: 0.44 },
|
||||
[AtkSpeedLv.Fast3]: { name: "快速", cd: 0.52 },
|
||||
[AtkSpeedLv.Normal1]: { name: "中速++", cd: 0.60 },
|
||||
[AtkSpeedLv.Normal2]: { name: "中速+", cd: 0.68 },
|
||||
[AtkSpeedLv.Normal3]: { name: "中速", cd: 0.76 },
|
||||
[AtkSpeedLv.Mid1]: { name: "一般+", cd: 0.84 },
|
||||
[AtkSpeedLv.Mid2]: { name: "一般", cd: 0.92 },
|
||||
[AtkSpeedLv.Mid3]: { name: "一般-", cd: 1.00 },
|
||||
[AtkSpeedLv.Slow1]: { name: "慢", cd: 1.05 },
|
||||
[AtkSpeedLv.Slow2]: { name: "慢+", cd: 1.08 },
|
||||
[AtkSpeedLv.Slow3]: { name: "慢++", cd: 1.10 },
|
||||
[AtkSpeedLv.VerySlow1]: { name: "很慢", cd: 1.10 },
|
||||
[AtkSpeedLv.VerySlow2]: { name: "很慢+", cd: 1.10 },
|
||||
[AtkSpeedLv.VerySlow3]: { name: "很慢++", cd: 1.10 },
|
||||
};
|
||||
|
||||
export const HeroPos = {
|
||||
@@ -85,10 +86,11 @@ export const FormationPointX = {
|
||||
[HType.Long]: 100,
|
||||
} as const;
|
||||
|
||||
/** 各攻击定位的默认攻击距离(像素):近战100 / 中程300 / 远程450;英雄配置了 dis 时优先取配置值 */
|
||||
export const HeroDisVal: Record<HType.Melee | HType.Mid | HType.Long, number> = {
|
||||
[HType.Melee]: 120,
|
||||
[HType.Mid]: 720,
|
||||
[HType.Long]: 720,
|
||||
[HType.Melee]: 100,
|
||||
[HType.Mid]: 300,
|
||||
[HType.Long]: 450,
|
||||
}
|
||||
|
||||
export const resolveFormationTargetX = (fac: FacSet, type: HType): number => {
|
||||
@@ -128,6 +130,29 @@ export const HERO_GROW_COEF: Record<HGrowType, { hp: number; ap: number }> = {
|
||||
[HGrowType.Support]: { hp: 1.1, ap: 0.7 },
|
||||
};
|
||||
|
||||
/**
|
||||
* 英雄位置类型(职业定位)。
|
||||
* Why: grow_type 仅驱动数值成长曲线,无法表达"这个英雄在阵容里干什么";
|
||||
* role 面向 UI 展示与阵容推荐(如 HeroComboSet 的铁三角校验),与成长解耦。
|
||||
*/
|
||||
export enum HRole {
|
||||
Tank = 0, // 坦克:前排承伤,受击发动机
|
||||
Warrior = 1, // 战士:前排输出/献祭位
|
||||
Assassin = 2, // 刺客:近战爆发主C
|
||||
Archer = 3, // 射手:远程物理主C
|
||||
Mage = 4, // 法师:远程先手增益
|
||||
Support = 5, // 辅助:光环/治疗
|
||||
}
|
||||
|
||||
export const HRoleName: Record<HRole, string> = {
|
||||
[HRole.Tank]: "坦克",
|
||||
[HRole.Warrior]: "战士",
|
||||
[HRole.Assassin]: "刺客",
|
||||
[HRole.Archer]: "射手",
|
||||
[HRole.Mage]: "法师",
|
||||
[HRole.Support]: "辅助",
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算英雄在目标等级下的累计属性成长增量(线性:每级加一份"基础属性×系数")。
|
||||
* 公式:累计 = 基础属性 × 类型系数 × (lv - 1)。
|
||||
@@ -258,14 +283,8 @@ export const TriggerSkillNameSet: Record<string, Record<number, string>> = {
|
||||
export function getTriggerSkillName(type: SkillTriggerType, s_uuid: number): string {
|
||||
const group = TriggerSkillNameSet[type];
|
||||
if (group) {
|
||||
// 直接命中
|
||||
const direct = group[s_uuid];
|
||||
if (direct) return direct;
|
||||
// 驻场光环 +/++ 档(如 7202/7402)回退到基础档命名(7002)
|
||||
if (type === SkillTriggerType.Field) {
|
||||
const base = group[7000 + (s_uuid % 100)];
|
||||
if (base) return base;
|
||||
}
|
||||
}
|
||||
// 驻场光环 uuid 存于 FieldSkillSet,触发技能存于 SkillSet,分别兜底
|
||||
if (type === SkillTriggerType.Field) {
|
||||
@@ -288,6 +307,7 @@ export interface heroInfo {
|
||||
card_lv?: number; // 卡牌等级(驱动 bg_node 颜色:1=green 2=blue 3=purple 4=red 5=yellow)
|
||||
type: HType; // 攻击定位(近战/中程/远程)
|
||||
grow_type?: HGrowType; // 成长类型(决定每级 hp/ap 标准增量,默认 Warrior)
|
||||
role?: HRole; // 位置类型/职业定位(英雄专用,怪物用 monType 表达)
|
||||
monType?: MonType; // 怪物专属类型
|
||||
hp: number; // 生命值上限
|
||||
ap: number; // 攻击力
|
||||
@@ -337,11 +357,20 @@ export interface LvTriggerEntry {
|
||||
export type TriggerGrouped = Record<number /*s_uuid*/, LvTriggerEntry[]>;
|
||||
|
||||
/**
|
||||
* 驻场光环按等级解锁:运行时取 ≤英雄等级 的最大 lv 档的 uuids。
|
||||
* 驻场光环词条:uuid 指向 FieldSkillSet 底座(类型/图标/名称元数据),
|
||||
* value 为该词条实际生效数值(由配置处按档位指定,不再随 uuid 分档)。
|
||||
*/
|
||||
export interface FieldEntry {
|
||||
uuid: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 驻场光环按等级解锁:运行时取 ≤英雄等级 的最大 lv 档的词条列表。
|
||||
*/
|
||||
export interface LvFieldEntry {
|
||||
lv: number;
|
||||
uuids: number[];
|
||||
uuids: FieldEntry[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -375,8 +404,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
|
||||
// 5001 铁壁战士 — atked 护盾(标杆,原5011)
|
||||
5001: {
|
||||
uuid: 5001, name: "铁壁战士", path: "hk1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 300, ap: 28,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
|
||||
uuid: 5001, name: "铁壁战士", path: "hk1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 300, ap: 22,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
atked: {
|
||||
6301: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 2 } },
|
||||
@@ -388,8 +417,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5002 影袭刺客 — atking 自身攻击
|
||||
5002: {
|
||||
uuid: 5002, name: "影袭刺客", path: "hc1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, hp: 300, ap: 28,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
|
||||
uuid: 5002, name: "影袭刺客", path: "hc1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, role: HRole.Assassin, hp: 300, ap: 22,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
|
||||
atking: {
|
||||
6401: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 1 } },
|
||||
@@ -401,8 +430,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5003 遗志将军 — dead 全队攻击
|
||||
5003: {
|
||||
uuid: 5003, name: "遗志将军", path: "hk2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, hp: 300, ap: 28,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
|
||||
uuid: 5003, name: "遗志将军", path: "hk2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 300, ap: 22,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
dead: {
|
||||
6401: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 3 } },
|
||||
@@ -414,8 +443,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5004 破晓先知 — fstart 全队攻击
|
||||
5004: {
|
||||
uuid: 5004, name: "破晓先知", path: "hm1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 143, ap: 40,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
|
||||
uuid: 5004, name: "破晓先知", path: "hm1", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 143, ap: 32,
|
||||
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
|
||||
fstart: {
|
||||
6401: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 3 } },
|
||||
@@ -427,12 +456,12 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5005 战歌法师 — field 攻击光环
|
||||
5005: {
|
||||
uuid: 5005, name: "战歌法师", path: "hm2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 143, ap: 40,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
|
||||
uuid: 5005, name: "战歌法师", path: "hm2", fac: FacSet.HERO, card_lv: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Support, hp: 143, ap: 32,
|
||||
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
field: [
|
||||
{ lv: 1, uuids: [7002] },
|
||||
{ lv: 3, uuids: [7202] },
|
||||
{ lv: 5, uuids: [7402] },
|
||||
{ lv: 1, uuids: [{ uuid: 7002, value: 0.1 }] },
|
||||
{ lv: 3, uuids: [{ uuid: 7002, value: 0.2 }] },
|
||||
{ lv: 5, uuids: [{ uuid: 7002, value: 0.3 }] },
|
||||
],
|
||||
info: "",
|
||||
},
|
||||
@@ -441,8 +470,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
|
||||
// 5101 不死斗士 — atked 回血(更高数值)
|
||||
5101: {
|
||||
uuid: 5101, name: "不死斗士", path: "hk3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 600, ap: 57,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
|
||||
uuid: 5101, name: "不死斗士", path: "hk3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 600, ap: 46,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
atked: {
|
||||
6302: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 200 } },
|
||||
@@ -454,8 +483,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5102 致命杀手 — atking 暴击(更高数值)
|
||||
5102: {
|
||||
uuid: 5102, name: "致命杀手", path: "hc2", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, hp: 600, ap: 57,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
|
||||
uuid: 5102, name: "致命杀手", path: "hc2", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, role: HRole.Assassin, hp: 600, ap: 46,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
|
||||
atking: {
|
||||
6403: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, timed_buff_id: 7012, buff_value: 5, buff_duration: 5 } },
|
||||
@@ -467,8 +496,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5103 殉道勇士 — dead 全队护盾(更高数值)
|
||||
5103: {
|
||||
uuid: 5103, name: "殉道勇士", path: "hk4", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, hp: 600, ap: 57,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
|
||||
uuid: 5103, name: "殉道勇士", path: "hk4", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 600, ap: 46,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
dead: {
|
||||
6301: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 3 } },
|
||||
@@ -480,8 +509,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5104 圣光祭司 — fstart 全队护盾(更高数值)
|
||||
5104: {
|
||||
uuid: 5104, name: "圣光祭司", path: "hm3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 287, ap: 80,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow1].cd, ccd: 0 } },
|
||||
uuid: 5104, name: "圣光祭司", path: "hm3", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 287, ap: 64,
|
||||
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
|
||||
fstart: {
|
||||
6301: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 3 } },
|
||||
@@ -493,22 +522,35 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5105 风怒先知 — field 攻速光环
|
||||
5105: {
|
||||
uuid: 5105, name: "风怒先知", path: "hm1", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 287, ap: 80,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
uuid: 5105, name: "风怒先知", path: "hm1", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Support, hp: 287, ap: 64,
|
||||
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
field: [
|
||||
{ lv: 1, uuids: [7006] },
|
||||
{ lv: 3, uuids: [7206] },
|
||||
{ lv: 5, uuids: [7406] },
|
||||
{ lv: 1, uuids: [{ uuid: 7006, value: 0.1 }] },
|
||||
{ lv: 3, uuids: [{ uuid: 7006, value: 0.2 }] },
|
||||
{ lv: 5, uuids: [{ uuid: 7006, value: 0.3 }] },
|
||||
],
|
||||
info: "",
|
||||
},
|
||||
// 5106 破甲射手 — atking 自身穿透伤害(计时buff增强)
|
||||
5106: {
|
||||
uuid: 5106, name: "破甲射手", path: "ha1", fac: FacSet.HERO, card_lv: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 287, ap: 64,
|
||||
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
|
||||
atking: {
|
||||
6512: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, timed_buff_id: 7022, buff_value: 5, buff_duration: 5 } },
|
||||
{ lv: 3, s_lv: SkillLv.Lv2, t_num: 5, overrides: { TGroup: TGroup.Self, timed_buff_id: 7022, buff_value: 7, buff_duration: 5 } },
|
||||
{ lv: 5, s_lv: SkillLv.Lv3, t_num: 4, overrides: { TGroup: TGroup.Self, timed_buff_id: 7022, buff_value: 10, buff_duration: 6 } },
|
||||
],
|
||||
},
|
||||
info: "",
|
||||
},
|
||||
|
||||
// ========== card_lv=3(5201-5205)范围扩大 ==========
|
||||
|
||||
// 5201 怒血勇士 — atked 全队攻击(范围扩大)
|
||||
5201: {
|
||||
uuid: 5201, name: "怒血勇士", path: "hk5", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 900, ap: 85,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
|
||||
uuid: 5201, name: "怒血勇士", path: "hk5", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 900, ap: 68,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
atked: {
|
||||
6401: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Team, ap: 1 } },
|
||||
@@ -520,8 +562,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5202 援护射手 — atking 队友攻击(范围扩大)
|
||||
5202: {
|
||||
uuid: 5202, name: "援护射手", path: "ha1", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, hp: 430, ap: 120,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
|
||||
uuid: 5202, name: "援护射手", path: "ha1", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 430, ap: 96,
|
||||
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
|
||||
atking: {
|
||||
6401: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Team, hit_count: 1, ap: 1 } },
|
||||
@@ -533,8 +575,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5203 传承卫士 — dead 全队生命(范围扩大)
|
||||
5203: {
|
||||
uuid: 5203, name: "传承卫士", path: "hk1", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, hp: 900, ap: 85,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
|
||||
uuid: 5203, name: "传承卫士", path: "hk1", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 900, ap: 68,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
dead: {
|
||||
6402: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 10 } },
|
||||
@@ -546,8 +588,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5204 晨光贤者 — fstart 全队暴击(范围扩大)
|
||||
5204: {
|
||||
uuid: 5204, name: "晨光贤者", path: "hm2", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 430, ap: 120,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
|
||||
uuid: 5204, name: "晨光贤者", path: "hm2", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 430, ap: 96,
|
||||
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
|
||||
fstart: {
|
||||
6403: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, timed_buff_id: 7012, buff_value: 5, buff_duration: 5 } },
|
||||
@@ -559,12 +601,12 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5205 精准术士 — field 暴击光环(范围扩大)
|
||||
5205: {
|
||||
uuid: 5205, name: "精准术士", path: "hm3", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 430, ap: 120,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
uuid: 5205, name: "精准术士", path: "hm3", fac: FacSet.HERO, card_lv: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Support, hp: 430, ap: 96,
|
||||
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
field: [
|
||||
{ lv: 1, uuids: [7004] },
|
||||
{ lv: 3, uuids: [7204] },
|
||||
{ lv: 5, uuids: [7404] },
|
||||
{ lv: 1, uuids: [{ uuid: 7004, value: 0.1 }] },
|
||||
{ lv: 3, uuids: [{ uuid: 7004, value: 0.2 }] },
|
||||
{ lv: 5, uuids: [{ uuid: 7004, value: 0.3 }] },
|
||||
],
|
||||
info: "",
|
||||
},
|
||||
@@ -573,8 +615,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
|
||||
// 5301 复仇守护者 — atked 全队暴击(范围+数值)
|
||||
5301: {
|
||||
uuid: 5301, name: "复仇守护者", path: "hk2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 1200, ap: 113,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow1].cd, ccd: 0 } },
|
||||
uuid: 5301, name: "复仇守护者", path: "hk2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 1200, ap: 90,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
atked: {
|
||||
6403: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Team, timed_buff_id: 7012, buff_value: 5, buff_duration: 5 } },
|
||||
@@ -586,8 +628,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5302 破灭游侠 — atking 队友暴伤(范围+数值)
|
||||
5302: {
|
||||
uuid: 5302, name: "破灭游侠", path: "ha2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, hp: 573, ap: 160,
|
||||
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal1].cd, ccd: 0 } },
|
||||
uuid: 5302, name: "破灭游侠", path: "ha2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 573, ap: 128,
|
||||
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
|
||||
atking: {
|
||||
6404: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Team, hit_count: 2, timed_buff_id: 7020, buff_value: 5, buff_duration: 5 } },
|
||||
@@ -599,8 +641,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5303 亡语刺客 — dead 全队暴击+暴伤(双效果)
|
||||
5303: {
|
||||
uuid: 5303, name: "亡语刺客", path: "hc3", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, hp: 1200, ap: 113,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast3].cd, ccd: 0 } },
|
||||
uuid: 5303, name: "亡语刺客", path: "hc3", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Assassin, role: HRole.Assassin, hp: 1200, ap: 90,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
|
||||
dead: {
|
||||
6403: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, timed_buff_id: 7012, buff_value: 5, buff_duration: 5 } },
|
||||
@@ -617,8 +659,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5304 战吼术士 — fstart 全队攻击+生命(双效果)
|
||||
5304: {
|
||||
uuid: 5304, name: "战吼术士", path: "hm1", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 573, ap: 160,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
|
||||
uuid: 5304, name: "战吼术士", path: "hm1", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 573, ap: 128,
|
||||
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
|
||||
fstart: {
|
||||
6401: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 5 } },
|
||||
@@ -635,12 +677,12 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5305 领域贤者 — field 暴伤+攻速光环(双效果)
|
||||
5305: {
|
||||
uuid: 5305, name: "领域贤者", path: "hm2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 573, ap: 160,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
uuid: 5305, name: "领域贤者", path: "hm2", fac: FacSet.HERO, card_lv: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Support, hp: 573, ap: 128,
|
||||
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
field: [
|
||||
{ lv: 1, uuids: [7005, 7006] },
|
||||
{ lv: 3, uuids: [7205, 7206] },
|
||||
{ lv: 5, uuids: [7405, 7406] },
|
||||
{ lv: 1, uuids: [{ uuid: 7005, value: 0.2 }, { uuid: 7006, value: 0.1 }] },
|
||||
{ lv: 3, uuids: [{ uuid: 7005, value: 0.4 }, { uuid: 7006, value: 0.2 }] },
|
||||
{ lv: 5, uuids: [{ uuid: 7005, value: 0.6 }, { uuid: 7006, value: 0.3 }] },
|
||||
],
|
||||
info: "",
|
||||
},
|
||||
@@ -649,8 +691,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
|
||||
// 5401 不灭战魂 — atked 攻击 + revive(双技能)
|
||||
5401: {
|
||||
uuid: 5401, name: "不灭战魂", path: "hk3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, hp: 1500, ap: 142,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
|
||||
uuid: 5401, name: "不灭战魂", path: "hk3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: 1500, ap: 114,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
atked: {
|
||||
6401: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 2 } },
|
||||
@@ -667,7 +709,7 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5402 战歌游侠 — atking 攻击 + field 攻速光环(双技能)
|
||||
5402: {
|
||||
uuid: 5402, name: "战歌游侠", path: "ha3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, hp: 717, ap: 200,
|
||||
uuid: 5402, name: "战歌游侠", path: "ha3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Assassin, role: HRole.Archer, hp: 717, ap: 160,
|
||||
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast3].cd, ccd: 0 } },
|
||||
atking: {
|
||||
6401: [
|
||||
@@ -677,16 +719,16 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
],
|
||||
},
|
||||
field: [
|
||||
{ lv: 1, uuids: [7006] },
|
||||
{ lv: 3, uuids: [7206] },
|
||||
{ lv: 5, uuids: [7406] },
|
||||
{ lv: 1, uuids: [{ uuid: 7006, value: 0.1 }] },
|
||||
{ lv: 3, uuids: [{ uuid: 7006, value: 0.2 }] },
|
||||
{ lv: 5, uuids: [{ uuid: 7006, value: 0.3 }] },
|
||||
],
|
||||
info: "",
|
||||
},
|
||||
// 5403 凤凰祭司 — dead 攻击+护盾 + revive(双技能)
|
||||
5403: {
|
||||
uuid: 5403, name: "凤凰祭司", path: "hk4", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, hp: 1500, ap: 142,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
|
||||
uuid: 5403, name: "凤凰祭司", path: "hk4", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: 1500, ap: 114,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
|
||||
dead: {
|
||||
6401: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 5 } },
|
||||
@@ -708,8 +750,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5404 涅槃贤者 — fstart 护盾+攻击 + revive(双技能)
|
||||
5404: {
|
||||
uuid: 5404, name: "涅槃贤者", path: "hm3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, hp: 717, ap: 200,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal3].cd, ccd: 0 } },
|
||||
uuid: 5404, name: "涅槃贤者", path: "hm3", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: 717, ap: 160,
|
||||
skills: { 6013: { uuid: 6013, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
|
||||
fstart: {
|
||||
6301: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 5 } },
|
||||
@@ -731,8 +773,8 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
},
|
||||
// 5405 圣言诗人 — atking 治疗 + field 生命光环(双技能)
|
||||
5405: {
|
||||
uuid: 5405, name: "圣言诗人", path: "hh1", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Support, hp: 717, ap: 200,
|
||||
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
|
||||
uuid: 5405, name: "圣言诗人", path: "hh1", fac: FacSet.HERO, card_lv: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Support, role: HRole.Support, hp: 717, ap: 160,
|
||||
skills: { 6015: { uuid: 6015, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
|
||||
atking: {
|
||||
6302: [
|
||||
{ lv: 1, s_lv: SkillLv.Lv1, t_num: 5, overrides: { TGroup: TGroup.Team, ap: 200 } },
|
||||
@@ -741,84 +783,84 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
],
|
||||
},
|
||||
field: [
|
||||
{ lv: 1, uuids: [7007] },
|
||||
{ lv: 3, uuids: [7207] },
|
||||
{ lv: 5, uuids: [7407] },
|
||||
{ lv: 1, uuids: [{ uuid: 7007, value: 0.1 }] },
|
||||
{ lv: 3, uuids: [{ uuid: 7007, value: 0.2 }] },
|
||||
{ lv: 5, uuids: [{ uuid: 7007, value: 0.3 }] },
|
||||
],
|
||||
info: "",
|
||||
},
|
||||
|
||||
/*
|
||||
*=============怪物配置列表================
|
||||
* 基础近战型(lv:1) : SPEED:800 |AP:12 | HP:360 | skills[0].cd=0.65
|
||||
* 重型坦克型(lv:1) : SPEED:800 |AP:30 | HP:1050 | skills[0].cd=2
|
||||
* 远程dps(lv:1) : SPEED:800 |AP:45 | HP:240 | skills[0].cd=1.5
|
||||
* 远程辅助(lv:1) : SPEED:800 |AP:20 | HP:240 | skills[0].cd=1
|
||||
* 精英 (lv:1) : SPEED:800 |AP:20 | HP:4500 | skills[0].cd=1
|
||||
*=============怪物配置列表================
|
||||
* 性能基准:1 级怪物与 1 级英雄(card_lv=1)单体性能持平 ——
|
||||
* 近战怪镜像 5001(hp300/ap22/cd0.92),远程怪镜像 5004(hp160/ap28/cd0.84),
|
||||
* 重型/Boss 以更高 hp 换取更低 DPS,承担"墙"与"压场"定位。
|
||||
* 运行时最终属性 = 本表基础值 × RogueConfig.MON_HP_MUL / MON_AP_MUL
|
||||
* (Boss 额外 × BOSS_HP_BONUS_MUL),校准目标:整场战斗双方对砍 ≈ 30s。
|
||||
*/
|
||||
|
||||
|
||||
// 基础怪物 (全部固定点位站桩攻击,HType仅决定是前排还是后排)
|
||||
// 前排怪物 (站在前排,承受更多伤害) — v5: TD节奏CD,多而弱爽感设计
|
||||
// 前排怪物 — 近战镜像 1 级英雄基准(hp300/ap22/cd0.92),变体微分定位
|
||||
6001: {
|
||||
uuid: 6001, name: "兽人战士", path: "m1", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 220, ap: 10, speed: 70,
|
||||
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "基础前排怪"
|
||||
uuid: 6001, name: "兽人战士", path: "m1", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 300, ap: 22, speed: 70,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } }, info: "基础前排怪, 与1级近战英雄持平"
|
||||
},
|
||||
6002: {
|
||||
uuid: 6002, name: "兽人精锐战士", path: "m2", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 300, ap: 14, speed: 110,
|
||||
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "进阶前排怪, 更快更痛"
|
||||
uuid: 6002, name: "兽人精锐战士", path: "m2", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 340, ap: 26, speed: 110,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } }, info: "进阶前排怪, 更快更痛"
|
||||
},
|
||||
6003: {
|
||||
uuid: 6003, name: "兽人重装兵", path: "m3", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Heavy, hp: 850, ap: 20, speed: 50,
|
||||
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "重型坦克怪, 高HP慢攻"
|
||||
uuid: 6003, name: "兽人重装兵", path: "m3", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Heavy, hp: 800, ap: 14, speed: 50,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } }, info: "重型坦克怪, 高HP低攻承伤位"
|
||||
},
|
||||
// 后排怪物 (站在后排,输出更高)
|
||||
// 后排怪物 — 远程镜像 1 级法师基准(hp160/ap28/cd0.84),变体微分定位
|
||||
6004: {
|
||||
uuid: 6004, name: "兽人射手", path: "m4", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Long, hp: 190, ap: 35, speed: 70,
|
||||
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "后排高DPS怪"
|
||||
uuid: 6004, name: "兽人射手", path: "m4", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Long, hp: 160, ap: 28, speed: 70,
|
||||
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } }, info: "后排输出怪, 与1级远程英雄持平"
|
||||
},
|
||||
6005: {
|
||||
uuid: 6005, name: "兽人刺客", path: "m5", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Assassin, hp: 210, ap: 38, speed: 130,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "高AP快速攻击刺客"
|
||||
uuid: 6005, name: "兽人刺客", path: "m5", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Assassin, hp: 130, ap: 26, speed: 130,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal3].cd, ccd: 0 } }, info: "高攻速低HP刺客"
|
||||
},
|
||||
// 特殊位怪物
|
||||
6006: {
|
||||
uuid: 6006, name: "骷髅领主", path: "m6", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.MeleeBoss, hp: 5000, ap: 20, speed: 60,
|
||||
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "前排MiniBoss级坦克"
|
||||
uuid: 6006, name: "骷髅领主", path: "m6", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.MeleeBoss, hp: 900, ap: 20, speed: 60,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } }, info: "前排MiniBoss级坦克"
|
||||
},
|
||||
6007: {
|
||||
uuid: 6007, name: "兽人术士", path: "m7", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Support, hp: 300, ap: 24, speed: 70,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "后排法师怪, 魔法攻击"
|
||||
uuid: 6007, name: "兽人术士", path: "m7", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Support, hp: 240, ap: 18, speed: 70,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } }, info: "后排辅助法师怪"
|
||||
},
|
||||
6008: {
|
||||
uuid: 6008, name: "兽人火法", path: "m8", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Summoner, hp: 270, ap: 32, speed: 70,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "后排高输出法师怪"
|
||||
uuid: 6008, name: "兽人火法", path: "m8", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Summoner, hp: 220, ap: 24, speed: 70,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } }, info: "后排召唤系法师怪"
|
||||
},
|
||||
|
||||
// BOSS怪物 — Boss节奏1.2-1.5s,删除不存在的6206技能
|
||||
// BOSS怪物 — 与6006同基准,以高HP压场,血量经 BOSS_HP_BONUS_MUL 单独放大
|
||||
6101: {
|
||||
uuid: 6101, name: "兽人首领-双刀战士", path: "mb1", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1900, ap: 30, speed: 120,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "前排Boss, 高攻速"
|
||||
uuid: 6101, name: "兽人首领-双刀战士", path: "mb1", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1200, ap: 24, speed: 120,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } }, info: "前排Boss, 攻速型"
|
||||
},
|
||||
6102: {
|
||||
uuid: 6102, name: "兽人首领-斧头战士", path: "mb2", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 7500, ap: 26, speed: 60,
|
||||
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "前排Boss, 超高HP"
|
||||
uuid: 6102, name: "兽人首领-斧头战士", path: "mb2", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1500, ap: 18, speed: 60,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } }, info: "前排Boss, 超高HP"
|
||||
},
|
||||
6103: {
|
||||
uuid: 6103, name: "兽人首领-魔法师", path: "mb3", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 2250, ap: 38, speed: 110,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "后排法系Boss, 高AP"
|
||||
uuid: 6103, name: "兽人首领-魔法师", path: "mb3", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1000, ap: 28, speed: 110,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } }, info: "后排法系Boss"
|
||||
},
|
||||
6104: {
|
||||
uuid: 6104, name: "兽人首领-射手", path: "mb4", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 6800, ap: 30, speed: 70,
|
||||
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "后排位Boss, 均衡型"
|
||||
uuid: 6104, name: "兽人首领-射手", path: "mb4", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1100, ap: 24, speed: 70,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } }, info: "后排位Boss, 均衡型"
|
||||
},
|
||||
6105: {
|
||||
uuid: 6105, name: "亡灵首领-法师", path: "mb5", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 2600, ap: 42, speed: 110,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "后排高AP Boss"
|
||||
uuid: 6105, name: "亡灵首领-法师", path: "mb5", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1000, ap: 30, speed: 110,
|
||||
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } }, info: "后排高AP Boss"
|
||||
},
|
||||
6106: {
|
||||
uuid: 6106, name: "亡灵首领-骑马战士", path: "mb6", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 9000, ap: 26, speed: 130,
|
||||
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "前排终极Boss, 最高HP+高速"
|
||||
uuid: 6106, name: "亡灵首领-骑马战士", path: "mb6", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1800, ap: 20, speed: 130,
|
||||
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } }, info: "前排终极Boss, 最高HP+高速"
|
||||
},
|
||||
|
||||
|
||||
@@ -828,7 +870,7 @@ export const HeroList: number[] = [
|
||||
// card_lv=1
|
||||
5001, 5002, 5003, 5004, 5005,
|
||||
// card_lv=2
|
||||
5101, 5102, 5103, 5104, 5105,
|
||||
5101, 5102, 5103, 5104, 5105, 5106,
|
||||
// card_lv=3
|
||||
5201, 5202, 5203, 5204, 5205,
|
||||
// card_lv=4
|
||||
@@ -837,6 +879,73 @@ export const HeroList: number[] = [
|
||||
5401, 5402, 5403, 5404, 5405,
|
||||
];
|
||||
|
||||
// ==================== 英雄推荐阵容配置 ====================
|
||||
|
||||
/**
|
||||
* 推荐阵容条目。
|
||||
* Why: 场上最多 3 个英雄(FightSet.HERO_MAX_NUM),围绕"发动机 → 增益 → 主C"铁三角结构
|
||||
* 预置官方配合组合,供抽卡引导、阵容推荐 UI 与 AI 自动布阵消费。
|
||||
*/
|
||||
export interface HeroComboInfo {
|
||||
uuid: number; // 阵容唯一标识(8000 段)
|
||||
name: string; // 阵容名称(体现流派)
|
||||
heroes: number[]; // 英雄 uuid 列表,固定 3 个;索引 0=前排,其余按站位从近到远
|
||||
desc: string; // 配合思路说明(信息面板展示)
|
||||
low_heroes?: number[]; // 低配替代阵容(card_lv 1~2,供新手期引导)
|
||||
}
|
||||
|
||||
/**
|
||||
* 推荐阵容表。
|
||||
* 设计原则:
|
||||
* 1. 铁三角结构:1 前排(坦克/献祭)+ 1 增益(光环/先手)+ 1 主C
|
||||
* 2. 乘区分散:攻击/暴击/暴伤/攻速各占独立乘区,组合优先覆盖不同乘区避免稀释
|
||||
* 3. 发动机冗余:献祭/反击流的发动机依赖前排挨打或死亡,revive 卡(5401/5403/5404)负责重复点火
|
||||
*/
|
||||
export const HeroComboSet: Record<number, HeroComboInfo> = {
|
||||
// 8001 暴击爆发流 — 暴击率与暴伤双乘区互不稀释,成型最强
|
||||
8001: {
|
||||
uuid: 8001, name: "暴击爆发流",
|
||||
heroes: [5301, 5302, 5402],
|
||||
desc: "复仇守护者挨打为全队提供暴击率,破灭游侠普攻为队友叠加暴伤,战歌游侠以极速普攻吃满暴击×暴伤双乘区完成收割。",
|
||||
},
|
||||
// 8002 献祭复活流 — revive 让死亡发动机循环点火
|
||||
8002: {
|
||||
uuid: 8002, name: "献祭复活流",
|
||||
heroes: [5403, 5303, 5102],
|
||||
desc: "凤凰祭司死亡为全队加攻击与护盾,复活后可再次献祭;亡语刺客死亡送出暴击+暴伤双增益,全部灌给主C致命杀手。",
|
||||
low_heroes: [5003, 5103, 5002],
|
||||
},
|
||||
// 8003 铁桶反击流 — 挨打次数越多增益越多,最稳
|
||||
8003: {
|
||||
uuid: 8003, name: "铁桶反击流",
|
||||
heroes: [5401, 5201, 5405],
|
||||
desc: "双前排持续挨打:怒血勇士为全队永久叠加攻击,不灭战魂自我强化并以复活兜底;圣言诗人治疗+生命光环保证前排挨得起。",
|
||||
},
|
||||
// 8004 先手速攻流 — 开局 buff 窗口内快速收割,短回合最强
|
||||
8004: {
|
||||
uuid: 8004, name: "先手速攻流",
|
||||
heroes: [5304, 5004, 5002],
|
||||
desc: "战吼术士与破晓先知双重开局战吼直接拉满全队攻击,影袭刺客凭最快攻速与自身攻击滚雪球在增益窗口内结束战斗。",
|
||||
low_heroes: [5004, 5005, 5002],
|
||||
},
|
||||
// 8005 双光环平A流 — 攻击×攻速光环乘算,低配易成型
|
||||
8005: {
|
||||
uuid: 8005, name: "双光环平A流",
|
||||
heroes: [5101, 5005, 5105],
|
||||
desc: "战歌法师攻击光环与风怒先知攻速光环互相乘算,不死斗士前排自我回血稳站,全队普攻输出被双光环持续放大。",
|
||||
low_heroes: [5001, 5005, 5105],
|
||||
},
|
||||
// 8006 续航消耗流 — 双奶抬血线,克制高血 Boss 的持久战
|
||||
8006: {
|
||||
uuid: 8006, name: "续航消耗流",
|
||||
heroes: [5101, 5405, 5106],
|
||||
desc: "不死斗士受击回血顶前排,圣言诗人治疗+生命光环抬全队血线,破甲射手以穿透增益磨掉高血 Boss,专打持久战。",
|
||||
},
|
||||
};
|
||||
|
||||
/** 推荐阵容 id 列表(按品质/成型难度排序,供 UI 顺序展示) */
|
||||
export const HeroComboList: number[] = [8001, 8002, 8003, 8004, 8005, 8006];
|
||||
|
||||
// ==================== 英雄强度评分公式 ====================
|
||||
|
||||
/** 英雄强度公式调参权重(可在编辑器或运行时热调)
|
||||
@@ -885,8 +994,8 @@ export function resolveTriggerByLv(grouped: TriggerGrouped | undefined, lv: numb
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 解析驻场光环在目标等级下的有效 uuid 列表(取 ≤lv 的最大档) */
|
||||
export function resolveFieldByLv(field: LvFieldEntry[] | undefined, lv: number): number[] {
|
||||
/** 解析驻场光环在目标等级下的有效词条列表(取 ≤lv 的最大档) */
|
||||
export function resolveFieldByLv(field: LvFieldEntry[] | undefined, lv: number): FieldEntry[] {
|
||||
if (!field || field.length === 0) return [];
|
||||
let pick: LvFieldEntry | undefined;
|
||||
for (const e of field) {
|
||||
@@ -1053,7 +1162,9 @@ function calcSupportEffectValue(config: SkillConfig, apEff: number, cd: number,
|
||||
return (value / 100) * (apEff / cd) * 0.05 * weights.W_DPS_PER_SEC * seconds;
|
||||
case Attrs.stun_chance:
|
||||
case Attrs.freeze_chance:
|
||||
return value * weights.W_control;
|
||||
case Attrs.knockback_chance:
|
||||
// 击退强化已改为击退距离增量,按距离价值折算
|
||||
return value * weights.W_control;
|
||||
case Attrs.speed:
|
||||
return (value / 100) * (apEff / cd) * weights.W_DPS_PER_SEC * seconds;
|
||||
@@ -1075,7 +1186,8 @@ function calcSkillEffectValue(skill: SkillConfig, overrides: SkillOverrides | un
|
||||
case SkillKind.Heal:
|
||||
return ((config.ap || 0) / 100) * apEff * weights.W_heal;
|
||||
case SkillKind.Shield:
|
||||
return (config.ap || 0) * weights.expectedEnemyDmg * weights.W_shield;
|
||||
// 护盾为数值减免型:1 点护盾 ≈ 1 点预期伤害减免
|
||||
return (config.ap || 0) * weights.W_shield;
|
||||
case SkillKind.Support:
|
||||
return calcSupportEffectValue(config, apEff, cd, seconds);
|
||||
default:
|
||||
@@ -1171,34 +1283,35 @@ function calcTriggerPowerDetailed(
|
||||
return { total, items };
|
||||
}
|
||||
|
||||
/** 计算驻场光环技能在时间窗内的总价值 */
|
||||
function calcFieldSkillValue(field: FieldSkillConfig, hpEff: number, apEff: number, cd: number, scopeMul: number, seconds: number): number {
|
||||
/** 计算驻场光环技能在时间窗内的总价值(value 由词条携带,fs 提供类型元数据) */
|
||||
function calcFieldSkillValue(fs: FieldSkillConfig, value: number, hpEff: number, apEff: number, cd: number, scopeMul: number, seconds: number): number {
|
||||
const weights = HERO_POWER_WEIGHTS;
|
||||
const dps = apEff / cd;
|
||||
|
||||
switch (field.type) {
|
||||
switch (fs.type) {
|
||||
case FieldSkillType.HeroAtk:
|
||||
return field.value * dps * scopeMul * weights.W_DPS_PER_SEC * seconds;
|
||||
return value * dps * scopeMul * weights.W_DPS_PER_SEC * seconds;
|
||||
case FieldSkillType.HeroHp:
|
||||
return field.value * hpEff * scopeMul * weights.W_HP;
|
||||
return value * hpEff * scopeMul * weights.W_HP;
|
||||
case FieldSkillType.HeroSpeed:
|
||||
return field.value * dps * scopeMul * weights.W_DPS_PER_SEC * seconds;
|
||||
return value * dps * scopeMul * weights.W_DPS_PER_SEC * seconds;
|
||||
case FieldSkillType.HeroCrit:
|
||||
return field.value * dps * scopeMul * 0.5 * weights.W_DPS_PER_SEC * seconds;
|
||||
return value * dps * scopeMul * 0.5 * weights.W_DPS_PER_SEC * seconds;
|
||||
case FieldSkillType.HeroCritDamage:
|
||||
return field.value * dps * scopeMul * 0.05 * weights.W_DPS_PER_SEC * seconds;
|
||||
return value * dps * scopeMul * 0.05 * weights.W_DPS_PER_SEC * seconds;
|
||||
case FieldSkillType.HeroStun:
|
||||
case FieldSkillType.HeroWindFury:
|
||||
case FieldSkillType.HeroPuncture:
|
||||
return field.value * scopeMul * weights.W_control * 100;
|
||||
case FieldSkillType.HeroPunctureDmg:
|
||||
return value * scopeMul * weights.W_control * 100;
|
||||
default:
|
||||
return field.value * scopeMul * weights.W_control * 10;
|
||||
return value * scopeMul * weights.W_control * 10;
|
||||
}
|
||||
}
|
||||
|
||||
/** 计算驻场光环的详细价值 */
|
||||
function calcFieldPowerDetailed(fieldUuids: number[] | undefined, hero: heroInfo, targetLv: number, seconds: number): { total: number, items: FieldSkillEval[] } {
|
||||
if (!fieldUuids || fieldUuids.length === 0) return { total: 0, items: [] };
|
||||
function calcFieldPowerDetailed(entries: FieldEntry[] | undefined, hero: heroInfo, targetLv: number, seconds: number): { total: number, items: FieldSkillEval[] } {
|
||||
if (!entries || entries.length === 0) return { total: 0, items: [] };
|
||||
const weights = HERO_POWER_WEIGHTS;
|
||||
const { hp: hpEff, ap: apEff } = getEffectiveStats(hero, targetLv);
|
||||
const cd = getAttackCd(hero);
|
||||
@@ -1206,12 +1319,12 @@ function calcFieldPowerDetailed(fieldUuids: number[] | undefined, hero: heroInfo
|
||||
let total = 0;
|
||||
const items: FieldSkillEval[] = [];
|
||||
|
||||
for (const uuid of fieldUuids) {
|
||||
const field = FieldSkillSet[uuid];
|
||||
if (!field) continue;
|
||||
const value = calcFieldSkillValue(field, hpEff, apEff, cd, scopeMul, seconds) * weights.W_field_alive;
|
||||
for (const entry of entries) {
|
||||
const fs = FieldSkillSet[entry.uuid];
|
||||
if (!fs) continue;
|
||||
const value = calcFieldSkillValue(fs, entry.value, hpEff, apEff, cd, scopeMul, seconds) * weights.W_field_alive;
|
||||
total += value;
|
||||
items.push({ uuid, name: field.name, value });
|
||||
items.push({ uuid: entry.uuid, name: fs.name, value });
|
||||
}
|
||||
return { total, items };
|
||||
}
|
||||
@@ -1255,8 +1368,8 @@ export function calcHeroPowerDetailed(hero: heroInfo, targetLv: number = 1, seco
|
||||
const fstart = hero.fstart ? calcTriggerPowerDetailed(resolveTriggerByLv(hero.fstart, targetLv), hero, targetLv, SkillTriggerType.FStart, seconds) : { total: 0, items: [] as TriggerSkillEval[] };
|
||||
const fend = hero.fend ? calcTriggerPowerDetailed(resolveTriggerByLv(hero.fend, targetLv), hero, targetLv, SkillTriggerType.FEnd, seconds) : { total: 0, items: [] as TriggerSkillEval[] };
|
||||
const dead = hero.dead ? calcTriggerPowerDetailed(resolveTriggerByLv(hero.dead, targetLv), hero, targetLv, SkillTriggerType.Dead, seconds) : { total: 0, items: [] as TriggerSkillEval[] };
|
||||
const fieldUuids = resolveFieldByLv(hero.field, targetLv);
|
||||
const field = fieldUuids.length > 0 ? calcFieldPowerDetailed(fieldUuids, hero, targetLv, seconds) : { total: 0, items: [] as FieldSkillEval[] };
|
||||
const fieldEntries = resolveFieldByLv(hero.field, targetLv);
|
||||
const field = fieldEntries.length > 0 ? calcFieldPowerDetailed(fieldEntries, hero, targetLv, seconds) : { total: 0, items: [] as FieldSkillEval[] };
|
||||
|
||||
let reviveValue = 0;
|
||||
const revive = resolveReviveByLv(hero.revive, targetLv);
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { FacSet } from "../common/config/GameSet";
|
||||
import { FieldSkillSet, FieldSkillType } from "../common/config/SkillSet";
|
||||
import { FieldEntry } from "../common/config/heroSet";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { SkillBoxComp } from "../map/SkillBoxComp";
|
||||
import { EquipBoxComp } from "../map/EquipBoxComp";
|
||||
|
||||
/**
|
||||
* 驻场技能(光环属性)计算辅助类
|
||||
*
|
||||
*
|
||||
* 核心职责:
|
||||
* 统一管理全场存活英雄带来的全局加成属性计算。
|
||||
* 解耦原先写在 HeroAttrsComp 中的静态计算逻辑,符合单一职责原则。
|
||||
*
|
||||
* 数据结构:field 列表元素为 FieldEntry({uuid,value}),uuid 仅用于查 FieldSkillSet
|
||||
* 的类型元数据,实际加成数值取自词条自身的 value。
|
||||
*/
|
||||
export class FieldSkillHelper {
|
||||
/** 获取指定驻场技能类型的总加成值(计算存活的友方英雄 + 场上的驻场技能卡) */
|
||||
@@ -27,47 +31,37 @@ export class FieldSkillHelper {
|
||||
public static getFieldSkillTotalValueForFac(type: FieldSkillType, fac: number = FacSet.HERO): number {
|
||||
let total = 0;
|
||||
|
||||
// 累加一组驻场词条中匹配类型的数值
|
||||
const accumulate = (fields: FieldEntry[] | undefined): void => {
|
||||
if (!fields) return;
|
||||
for (const entry of fields) {
|
||||
const fs = FieldSkillSet[entry.uuid];
|
||||
if (fs && fs.type === type) {
|
||||
total += entry.value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 1. 统计英雄/怪物带来的驻场技能加成
|
||||
// 读 model.runtime_field(已按当前等级 resolve 的 uuid 列表)
|
||||
// 读 model.runtime_field(已按当前等级 resolve 的词条列表)
|
||||
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
|
||||
const model = entity.get(HeroAttrsComp);
|
||||
if (!model || model.is_dead || model.fac !== fac) return;
|
||||
const fields = model.runtime_field;
|
||||
if (fields) {
|
||||
for (const skillUuid of fields) {
|
||||
const skillConfig = FieldSkillSet[skillUuid];
|
||||
if (skillConfig && skillConfig.type === type) {
|
||||
total += skillConfig.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
accumulate(model.runtime_field);
|
||||
});
|
||||
|
||||
// 2. 统计技能盒子(技能卡)带来的驻场技能加成(仅英雄阵营)
|
||||
// 2. 统计技能盒子(技能卡)与装备盒(装备卡)带来的驻场技能加成(仅英雄阵营)
|
||||
if (fac === FacSet.HERO) {
|
||||
ecs.query(ecs.allOf(SkillBoxComp)).forEach((entity: ecs.Entity) => {
|
||||
const skillBox = entity.get(SkillBoxComp);
|
||||
if (!skillBox || !skillBox.field || skillBox.field.length === 0) return;
|
||||
|
||||
for (const skillUuid of skillBox.field) {
|
||||
const skillConfig = FieldSkillSet[skillUuid];
|
||||
if (skillConfig && skillConfig.type === type) {
|
||||
total += skillConfig.value;
|
||||
}
|
||||
}
|
||||
if (!skillBox) return;
|
||||
accumulate(skillBox.field);
|
||||
});
|
||||
|
||||
// 3. 统计装备盒(装备卡)带来的驻场技能加成(仅英雄阵营)
|
||||
ecs.query(ecs.allOf(EquipBoxComp)).forEach((entity: ecs.Entity) => {
|
||||
const equipBox = entity.get(EquipBoxComp);
|
||||
if (!equipBox || !equipBox.field || equipBox.field.length === 0) return;
|
||||
|
||||
for (const skillUuid of equipBox.field) {
|
||||
const skillConfig = FieldSkillSet[skillUuid];
|
||||
if (skillConfig && skillConfig.type === type) {
|
||||
total += skillConfig.value;
|
||||
}
|
||||
}
|
||||
if (!equipBox) return;
|
||||
accumulate(equipBox.field);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import { smc } from "../common/SingletonModuleComp";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { HeroViewComp } from "./HeroViewComp";
|
||||
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
|
||||
import { HeroInfo, HeroPos, resolveFormationTargetX, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, calcGrowBonus } from "../common/config/heroSet";
|
||||
import { HeroInfo, resolveFormationTargetX, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, calcGrowBonus, HeroDisVal } from "../common/config/heroSet";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { SkillTriggerType } from "../common/config/heroSet";
|
||||
import { SkillTriggerType, HType } from "../common/config/heroSet";
|
||||
import { SkillTriggerHelper } from "./SkillTriggerHelper";
|
||||
import { Attrs } from "../common/config/HeroAttrs";
|
||||
import { MoveComp } from "./MoveComp";
|
||||
@@ -76,8 +76,8 @@ export class Hero extends ecs.Entity {
|
||||
load(pos: Vec3 = Vec3.ZERO, scale: number = 1, uuid: number = 1001, dropToY: number = pos.y, hero_lv: number = 1, card_lv: number = 1, posIndex: number = -1) {
|
||||
// 英雄始终朝右,表现缩放固定为正向
|
||||
scale = 1
|
||||
// 英雄等级在当前规则下上限为 3,避免超配表范围
|
||||
if (hero_lv > 3) hero_lv = 3
|
||||
// 英雄等级上限以配置为准,合成可升到 HERO_MAX_LV
|
||||
if (hero_lv > FightSet.HERO_MAX_LV) hero_lv = FightSet.HERO_MAX_LV
|
||||
// 英雄尺寸随等级做轻量放大,强化成长反馈
|
||||
let size = 1.15
|
||||
// 根据配置路径加载英雄预制体
|
||||
@@ -120,8 +120,10 @@ export class Hero extends ecs.Entity {
|
||||
model.base_card_lv = hero.card_lv ?? 1;
|
||||
model.type = hero.type;
|
||||
model.grow_type = hero.grow_type;
|
||||
model.role = hero.role;
|
||||
model.fac = FacSet.HERO;
|
||||
model.dis = hero.dis ?? 720;
|
||||
// 攻击距离:配置优先;未配置按攻击定位取默认值(近战100 / 中程300 / 远程450)
|
||||
model.dis = hero.dis ?? HeroDisVal[hero.type as HType.Melee | HType.Mid | HType.Long];
|
||||
model.posIndex = posIndex;
|
||||
if (posIndex >= 0) {
|
||||
smc.mission.heroGrid[posIndex] = this.eid;
|
||||
@@ -155,7 +157,7 @@ export class Hero extends ecs.Entity {
|
||||
model.hp = model.hp_max = base_hp;
|
||||
}
|
||||
|
||||
model.speed = hero.speed ?? 800;
|
||||
model.speed = hero.speed ?? 200;
|
||||
|
||||
// 构建技能表并注入运行时冷却字段 ccd
|
||||
model.skills = {};
|
||||
|
||||
@@ -146,15 +146,15 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
}
|
||||
}
|
||||
mLogger.log(this.debugMode, 'HeroAtkSystem', " after crit", damage)
|
||||
// 护盾吸收
|
||||
// 护盾数值减免(1 点护盾吸收 1 点伤害,扣完为止,溢出部分继续扣血)
|
||||
const shieldResult = this.absorbShield(TAttrsComp, damage);
|
||||
damage = shieldResult.remainingDamage;
|
||||
if (shieldResult.absorbedDamage > 0) {
|
||||
// 【评分系统 - 防御分】统计护盾成功抵挡伤害的次数
|
||||
smc.vmdata.scores.shield_block_count += 1;
|
||||
// 【评分系统 - 防御分】统计护盾累计吸收的伤害量
|
||||
smc.vmdata.scores.shield_block_count += Math.floor(shieldResult.absorbedDamage);
|
||||
}
|
||||
mLogger.log(this.debugMode, 'HeroAtkSystem', " after shield", damage)
|
||||
// 显示护盾格挡飘字
|
||||
// 显示护盾减免飘字
|
||||
if (shieldResult.absorbedDamage > 0 && targetView) {
|
||||
targetView.shield_tip(shieldResult.absorbedDamage);
|
||||
}
|
||||
@@ -175,11 +175,6 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
TAttrsComp.atked_count++;
|
||||
this.checkAndTriggerAtkedSkills(TAttrsComp, targetView);
|
||||
|
||||
// 受击者产生击退效果
|
||||
// if (damage > 0 && targetView) {
|
||||
// targetView.back();
|
||||
// }
|
||||
|
||||
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 英雄${TAttrsComp.hero_name} (uuid: ${TAttrsComp.hero_uuid}) 受到 eid:${casterEid} 的 伤害 ${damage},${isCrit ? "暴击" : "普通"}攻击,技能ID ${damageEvent.s_uuid}`);
|
||||
|
||||
// 冰冻判定
|
||||
@@ -190,13 +185,14 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
const stunChance = (damageEvent.Attrs[Attrs.stun_chance] || 0) - TAttrsComp.getFinalStunRes();
|
||||
const isStun = !TAttrsComp.isStun() && this.checkChance(stunChance);
|
||||
|
||||
// 击退判定
|
||||
const knockbackChance = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.knockback_res || 0);
|
||||
const isKnockback = this.checkChance(knockbackChance);
|
||||
// 击退:受击默认后退,击退属性不再作为概率,而是转换为后退距离的增量
|
||||
// 击退增量 = 施法者击退强化(原击退概率)- 被攻击者击退抗性 + 击退距离强化
|
||||
const knockbackBonus = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.getFinalKnockbackRes() || 0);
|
||||
const knockbackExtra = Math.max(0, knockbackBonus + (damageEvent.Attrs[Attrs.knockback_distance] || 0));
|
||||
|
||||
// ✅ 触发视图层表现(伤害数字、受击动画、冰冻、击晕、击退)
|
||||
if (targetView) {
|
||||
targetView.do_atked(damage, isCrit, damageEvent.s_uuid, false);
|
||||
targetView.do_atked(damage, isCrit, damageEvent.s_uuid, knockbackExtra);
|
||||
targetView.playEnd(skillConf.endAnm);
|
||||
if (isFrost) {
|
||||
TAttrsComp.toFrost();
|
||||
@@ -212,18 +208,16 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
smc.vmdata.scores.stun_count++;
|
||||
}
|
||||
}
|
||||
if (isKnockback) {
|
||||
targetView.back(damageEvent.Attrs[Attrs.knockback_distance] || 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查死亡
|
||||
if (TAttrsComp.hp <= 0) {
|
||||
// 先触发死亡技能(如亡语),不管后续是否复活都应触发
|
||||
// 先触发死亡技能(如亡语),不管后续是否续命都应触发
|
||||
this.triggerDeadSkills(target);
|
||||
|
||||
// 复活机制:如果玩家属性内的复活属性值>=1 则执行复活,原地50%血量复活
|
||||
// 濒死回血(revive 系):不是真正复活,而是血量归零瞬间消耗 1 次续命次数,
|
||||
// 按比例回血继续战斗。次数每回合重置(见 MissionComp.healAllHeroes)。
|
||||
let canRevive = false;
|
||||
let maxReviveCount = 0;
|
||||
let reviveHpPercent = 50; // 默认恢复50%
|
||||
@@ -338,16 +332,16 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
|
||||
/**
|
||||
* 处理角色死亡
|
||||
*
|
||||
*
|
||||
* 死亡处理流程:
|
||||
* 1. 标记死亡状态(is_dead = true)
|
||||
* 2. 触发死亡事件(onDeath)
|
||||
* 3. 记录调试信息(如启用调试模式)
|
||||
*
|
||||
* 2. 英雄阵营:快照死亡状态到 smc.mission.dead_heroes(供后续复活机制重新召唤)
|
||||
* 3. 触发死亡事件(onDeath)
|
||||
*
|
||||
* @param entity 死亡的实体
|
||||
*
|
||||
*
|
||||
* @important 死亡状态一旦设置,该实体将不再处理新的伤害事件
|
||||
* 这确保了死亡逻辑的单一性和一致性
|
||||
* 英雄为真实死亡:视图动画结束后由 HeroViewComp.realDead 销毁实体
|
||||
*/
|
||||
private doDead(entity: ecs.Entity): void {
|
||||
const TAttrsComp = entity.get(HeroAttrsComp);
|
||||
@@ -355,6 +349,11 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
|
||||
TAttrsComp.is_dead = true;
|
||||
|
||||
// 英雄真实死亡:记录快照,供后续复活机制(重新召唤)消费
|
||||
if (TAttrsComp.fac === FacSet.HERO) {
|
||||
this.recordDeadHero(TAttrsComp);
|
||||
}
|
||||
|
||||
// 触发死亡事件
|
||||
this.onDeath(entity);
|
||||
|
||||
@@ -363,6 +362,24 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 快照死亡英雄的静态/成长数据到 smc.mission.dead_heroes。
|
||||
* 仅记录重新召唤所需字段,不含战斗内临时状态(buff、技能 CD 等)。
|
||||
*
|
||||
* @param attrs 死亡英雄的属性组件
|
||||
*/
|
||||
private recordDeadHero(attrs: HeroAttrsComp): void {
|
||||
smc.mission.dead_heroes.push({
|
||||
uuid: attrs.hero_uuid,
|
||||
lv: attrs.lv,
|
||||
card_lv: attrs.card_lv,
|
||||
posIndex: attrs.posIndex,
|
||||
wave: Math.max(1, smc.vmdata?.mission_data?.level ?? 1),
|
||||
});
|
||||
mLogger.log(this.debugMode, 'HeroAtkSystem',
|
||||
` 死亡英雄已记录: ${attrs.hero_name}(uuid=${attrs.hero_uuid}) lv=${attrs.lv} pos=${attrs.posIndex} wave=${smc.vmdata?.mission_data?.level}, 当前共 ${smc.mission.dead_heroes.length} 个`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -371,7 +388,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
* 用于所有概率相关的判定:
|
||||
* - 暴击率判定
|
||||
* - 闪避率判定
|
||||
* - 击退概率判定
|
||||
* - 冰冻/击晕概率判定
|
||||
* - 其他特殊效果概率
|
||||
*
|
||||
* @param rate 概率值(0-100的百分比)
|
||||
@@ -396,29 +413,26 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
|
||||
|
||||
/**
|
||||
* 护盾吸收伤害
|
||||
*
|
||||
* 护盾吸收逻辑:
|
||||
* 1. 护盾值大于 0 时,抵挡本次伤害(免伤)
|
||||
* 2. 每次抵挡消耗 1 点护盾值
|
||||
* 3. 护盾归零时,重置护盾最大值属性
|
||||
*
|
||||
* 护盾数值减免伤害
|
||||
*
|
||||
* 护盾为数值减免型:
|
||||
* 1. 1 点护盾吸收 1 点伤害
|
||||
* 2. 护盾扣减实际吸收量,溢出部分继续结算到血量
|
||||
*
|
||||
* @param TAttrsComp 被攻击者的属性组件(包含当前护盾值)
|
||||
* @param damage 原始伤害值
|
||||
* @returns {remainingDamage, absorbedDamage} 剩余伤害值和被格挡的伤害值
|
||||
* @returns {remainingDamage, absorbedDamage} 剩余伤害值和被护盾减免的伤害值
|
||||
*/
|
||||
private absorbShield(TAttrsComp: HeroAttrsComp, damage: number): { remainingDamage: number, absorbedDamage: number } {
|
||||
if (TAttrsComp.shield <= 0) {
|
||||
mLogger.log(this.debugMode, 'HeroAtkSystem', " 护盾值小于等于0,无法吸收伤害");
|
||||
return { remainingDamage: damage, absorbedDamage: 0 };
|
||||
}
|
||||
TAttrsComp.shield = Math.max(0, TAttrsComp.shield - 1);
|
||||
if (TAttrsComp.shield <= 0) {
|
||||
TAttrsComp.shield = 0;
|
||||
}
|
||||
const absorbed = Math.min(TAttrsComp.shield, damage);
|
||||
TAttrsComp.shield = Math.max(0, TAttrsComp.shield - absorbed);
|
||||
TAttrsComp.dirty_shield = true;
|
||||
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 护盾抵挡1次伤害,剩余次数 ${TAttrsComp.shield}`);
|
||||
return { remainingDamage: 0, absorbedDamage: damage };
|
||||
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 护盾减免 ${absorbed.toFixed(1)} 点伤害,剩余护盾 ${TAttrsComp.shield.toFixed(1)}`);
|
||||
return { remainingDamage: damage - absorbed, absorbedDamage: absorbed };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,6 +482,11 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
if (TAttrsComp.fac === FacSet.MON) {
|
||||
// 怪物死亡处理
|
||||
this.scheduleDrop(entity);
|
||||
// oops.message 全局事件总线:怪物死亡(连杀计数等表现层消费)
|
||||
oops.message.dispatchEvent(GameEvent.MonDead, {
|
||||
isBoss: !!TAttrsComp.is_boss,
|
||||
worldPos: entity.get(HeroViewComp)?.node?.worldPosition?.clone() ?? null,
|
||||
});
|
||||
} else if (TAttrsComp.fac === FacSet.HERO) {
|
||||
// 英雄死亡处理
|
||||
this.scheduleHeroDeath(entity);
|
||||
@@ -495,6 +514,12 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
if (gold <= 0) return;
|
||||
|
||||
MissionEconomy.addCoin(gold);
|
||||
// oops.message 全局事件总线:金币飞行表现(账务已即时结算,飞行纯视觉)
|
||||
oops.message.dispatchEvent(GameEvent.CoinFly, {
|
||||
worldPos: entity.get(HeroViewComp)?.node?.worldPosition?.clone() ?? null,
|
||||
gold,
|
||||
isBoss: !!TAttrsComp.is_boss,
|
||||
});
|
||||
mLogger.log(this.debugMode, 'HeroAtkSystem',
|
||||
` ${TAttrsComp.hero_name} 死亡掉落金币 ${gold}(monType=${monType}, isBoss=${TAttrsComp.is_boss})`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType, TriggerGrouped, LvFieldEntry, LvReviveEntry, LvBonusEntry } from "../common/config/heroSet";
|
||||
import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType, TriggerGrouped, LvFieldEntry, LvReviveEntry, LvBonusEntry, FieldEntry, HRole } from "../common/config/heroSet";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { FacSet, FightSet } from "../common/config/GameSet";
|
||||
import { FieldSkillSet, FieldSkillType, SkillOverrides } from "../common/config/SkillSet";
|
||||
@@ -29,6 +29,7 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
type: number = 0; // 0近战 1远程 2辅助
|
||||
fac: number = 0; // 0:hero 1:monster
|
||||
grow_type?: number; // 成长类型(HGrowType),驱动每级 hp/ap 线性成长
|
||||
role?: HRole; // 位置类型/职业定位(驱动编队站位优先级)
|
||||
// ==================== 基础属性(有初始值) ====================
|
||||
base_ap: number = 0; // 原始基础攻击(无任何加成)
|
||||
base_hp: number = 0; // 原始基础血量(无任何加成)
|
||||
@@ -69,8 +70,8 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
runtime_fstart?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
/** 当前等级下生效的战后触发条目 */
|
||||
runtime_fend?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
/** 当前等级下生效的驻场光环 uuid 列表 */
|
||||
runtime_field?: number[];
|
||||
/** 当前等级下生效的驻场光环词条列表 */
|
||||
runtime_field?: FieldEntry[];
|
||||
/** 当前等级下生效的复活配置 */
|
||||
runtime_revive?: { s_uuid: number; r_num: number; upr: number };
|
||||
|
||||
@@ -81,11 +82,12 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
freeze_res: number = 0; // 冰冻抗性
|
||||
stun_chance: number = 0; // 击晕概率
|
||||
stun_res: number = 0; // 击晕抗性
|
||||
knockback_chance: number = 0; // 击退概率
|
||||
knockback_chance: number = 0; // 击退强化(原击退概率,现转换为击退距离增量)
|
||||
knockback_distance: number = 0; // 击退距离强化
|
||||
knockback_res: number = 0; // 击退抗性
|
||||
knockback_res: number = 0; // 击退抗性(削减击退距离增量)
|
||||
crit_damage: number = 0; // 额外暴击伤害
|
||||
puncture_chance: number = 0; // 穿透概率
|
||||
puncture_dmg_bonus: number = 0; // 穿透后伤害增量(百分点,乘算加成)
|
||||
wfuny: number = 0; // 风怒
|
||||
|
||||
revived_count: number = 0; // 已复活次数
|
||||
@@ -151,11 +153,11 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
const addValue = Math.max(0, Math.floor(value));
|
||||
if (addValue <= 0) return;
|
||||
this.shield += addValue;
|
||||
this.shield = Math.min(this.shield, FightSet.SHIELD_MAX); // 限制护盾最大层数
|
||||
this.shield = Math.min(this.shield, FightSet.SHIELD_MAX); // 限制护盾值上限(数值减免型)
|
||||
if (this.shield < 0) this.shield = 0;
|
||||
this.dirty_shield = true; // 标记护盾需要更新
|
||||
if (this.debugMode) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 护盾次数变更: ${this.hero_name}, 变化=${addValue}, ${Math.floor(oldShield)} -> ${Math.floor(this.shield)}`);
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 护盾值变更: ${this.hero_name}, 变化=${addValue}, ${Math.floor(oldShield)} -> ${Math.floor(this.shield)}`);
|
||||
}
|
||||
}
|
||||
add_hp_max(value: number) {
|
||||
@@ -326,6 +328,12 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
return this.puncture_chance + HeroAttrsComp.getFieldPercentValue(FieldSkillType.HeroPuncture);
|
||||
}
|
||||
|
||||
/** 英雄实时穿透伤害加成 = 基础加成 + 驻场加成。 */
|
||||
public getRuntimePunctureDmgBonus(): number {
|
||||
if (this.fac !== FacSet.HERO) return this.puncture_dmg_bonus;
|
||||
return this.puncture_dmg_bonus + HeroAttrsComp.getFieldPercentValue(FieldSkillType.HeroPunctureDmg);
|
||||
}
|
||||
|
||||
/** 英雄实时暴击伤害 = 基础额外暴伤 + 驻场暴伤。 */
|
||||
public getRuntimeCritDamageBonus(): number {
|
||||
if (this.fac !== FacSet.HERO) return this.crit_damage;
|
||||
@@ -441,7 +449,7 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
return base + mods.flatSum + mods.pctSum;
|
||||
}
|
||||
|
||||
/** 最终击退概率 = 基础击退 + 限时修饰(加法模型) */
|
||||
/** 最终击退强化 = 基础击退强化 + 限时修饰(加法模型,转换为击退距离增量) */
|
||||
public getFinalKnockbackChance(): number {
|
||||
const mods = this.aggregateTimedMods(Attrs.knockback_chance);
|
||||
return this.knockback_chance + mods.flatSum + mods.pctSum;
|
||||
@@ -467,6 +475,13 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
return base + mods.flatSum + mods.pctSum;
|
||||
}
|
||||
|
||||
/** 最终穿透伤害加成 = 驻场实时加成 + 限时修饰(加法模型) */
|
||||
public getFinalPunctureDmgBonus(): number {
|
||||
const base = this.getRuntimePunctureDmgBonus();
|
||||
const mods = this.aggregateTimedMods(Attrs.puncture_dmg_bonus);
|
||||
return base + mods.flatSum + mods.pctSum;
|
||||
}
|
||||
|
||||
/** 最终风怒概率 = 驻场实时风怒 + 限时修饰(加法模型) */
|
||||
public getFinalWindFury(): number {
|
||||
const base = this.getRuntimeWindFury();
|
||||
@@ -549,6 +564,8 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
this.lv = 1;
|
||||
this.type = 0;
|
||||
this.fac = 0;
|
||||
this.grow_type = undefined;
|
||||
this.role = undefined;
|
||||
this.base_ap = 0;
|
||||
this.base_hp = 0;
|
||||
this.ap = 0;
|
||||
@@ -591,6 +608,7 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
this.revived_count = 0;
|
||||
this.invincible_time = 0;
|
||||
this.puncture_chance = 0;
|
||||
this.puncture_dmg_bonus = 0;
|
||||
this.wfuny = 0;
|
||||
this.boom = false;
|
||||
|
||||
|
||||
173
assets/script/game/hero/HeroTopBarComp.ts
Normal file
173
assets/script/game/hero/HeroTopBarComp.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { _decorator, ProgressBar, Sprite, UIOpacity, Vec3, v3, tween, Tween, Color, clamp } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { FacSet, FightSet } from "../common/config/GameSet";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* 英雄头顶状态栏组件
|
||||
*
|
||||
* 挂载在英雄节点下的 "top" 子节点上,独立管理:
|
||||
* - 血条进度(ProgressBar)
|
||||
* - 护盾数值条(ProgressBar,top/shield 节点,显示 shield / SHIELD_MAX)
|
||||
* - 透明度激活 / 闲置
|
||||
* - 受击抖动动画
|
||||
*
|
||||
* 数据流:HeroViewComp 将 HeroAttrsComp 引用传入,本组件通过 model 读取属性。
|
||||
* 注意:英雄根节点下的 "shielded" 是护盾特效动画节点,由 HeroViewComp 管理,不在本组件职责内。
|
||||
*/
|
||||
@ccclass('HeroTopBarComp')
|
||||
@ecs.register('HeroTopBar', false)
|
||||
export class HeroTopBarComp extends CCComp {
|
||||
|
||||
// ==================== @property 绑定(编辑器拖拽) ====================
|
||||
@property({ type: ProgressBar, tooltip: "血条进度条" })
|
||||
private hpProgressBar: ProgressBar = null!;
|
||||
|
||||
@property({ type: ProgressBar, tooltip: "护盾数值条(top 下的 shield 节点)" })
|
||||
private shieldProgressBar: ProgressBar = null!;
|
||||
|
||||
@property({ type: Sprite, tooltip: "血条填充 Sprite(用于设置阵营颜色)" })
|
||||
private hpBarSprite: Sprite = null!;
|
||||
|
||||
// ==================== 内部引用 ====================
|
||||
private topOpacity: UIOpacity = null!;
|
||||
private basePos: Vec3 = v3();
|
||||
|
||||
// ==================== 配置 ====================
|
||||
private hp_height: number = 90;
|
||||
private boss_hp_height: number = 160;
|
||||
|
||||
// ==================== 透明度 ====================
|
||||
private readonly barIdleOpacity: number = 153;
|
||||
private readonly barActiveOpacity: number = 255;
|
||||
private readonly idleOpacityDelay: number = 0.25;
|
||||
|
||||
/** 外部赋值:英雄数据模型 */
|
||||
private _model: HeroAttrsComp = null!;
|
||||
public get model(): HeroAttrsComp { return this._model; }
|
||||
public set model(value: HeroAttrsComp) { this._model = value; }
|
||||
|
||||
private readonly restoreBarIdleOpacity = () => {
|
||||
this.setTopBarOpacity(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化 / 对象池复用时重置
|
||||
* @param model 英雄属性组件
|
||||
* @param direction 显示方向(1 或 -1)
|
||||
*/
|
||||
public init(model: HeroAttrsComp, direction: number = 1): void {
|
||||
this._model = model;
|
||||
|
||||
// UIOpacity 无 @property 绑定,运行时获取或添加
|
||||
this.topOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
|
||||
|
||||
// 方向取 abs,避免对象池复用时 scale 累乘导致血条反转
|
||||
this.node.setScale(direction * Math.abs(this.node.scale.x), 1 * this.node.scale.y);
|
||||
|
||||
// 位置
|
||||
const height = model.is_boss ? this.boss_hp_height : this.hp_height;
|
||||
this.node.setPosition(0, height, 0);
|
||||
this.basePos = this.node.position.clone();
|
||||
|
||||
// 血条颜色(英雄阵营为绿色)
|
||||
if (model.fac === FacSet.HERO && this.hpBarSprite) {
|
||||
this.hpBarSprite.color = new Color("#2ECC71");
|
||||
}
|
||||
|
||||
// 确保血条等 UI 始终在最上层
|
||||
this.node.setSiblingIndex(999);
|
||||
|
||||
// 重置显示状态
|
||||
this.node.getChildByName("hp")!.active = true;
|
||||
this.node.getChildByName("cd")!.active = false;
|
||||
this.node.getChildByName("lv")!.active = false;
|
||||
this.node.active = true;
|
||||
|
||||
this.setTopBarOpacity(false);
|
||||
this.hpShow();
|
||||
this.shieldShow(model.shield);
|
||||
}
|
||||
|
||||
/** 更新血量进度条 */
|
||||
public hpShow(): void {
|
||||
if (!this._model || !this.hpProgressBar) return;
|
||||
const hp = this._model.hp;
|
||||
const hpMax = this._model.hp_max;
|
||||
let targetProgress = hpMax > 0 ? hp / hpMax : 0;
|
||||
targetProgress = clamp(targetProgress, 0, 1);
|
||||
|
||||
const prevProgress = this.hpProgressBar.progress;
|
||||
if (targetProgress < prevProgress) {
|
||||
this.activateTopBar();
|
||||
this.playHpBarShake();
|
||||
}
|
||||
this.hpProgressBar.progress = targetProgress;
|
||||
if (targetProgress > prevProgress) {
|
||||
this.setTopBarOpacity(false);
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新护盾数值条(百分比 = shield / SHIELD_MAX) */
|
||||
public shieldShow(shield: number = 0): void {
|
||||
if (!this.shieldProgressBar) return;
|
||||
const progress = FightSet.SHIELD_MAX > 0 ? shield / FightSet.SHIELD_MAX : 0;
|
||||
this.shieldProgressBar.progress = clamp(progress, 0, 1);
|
||||
// 护盾节点显隐
|
||||
this.shieldProgressBar.node.active = shield > 0;
|
||||
}
|
||||
|
||||
/** 激活顶栏(高亮 + 延迟恢复闲置透明度) */
|
||||
public activateTopBar(): void {
|
||||
this.setTopBarOpacity(true);
|
||||
this.unschedule(this.restoreBarIdleOpacity);
|
||||
this.scheduleOnce(this.restoreBarIdleOpacity, this.idleOpacityDelay);
|
||||
}
|
||||
|
||||
/** 设置顶栏透明度 */
|
||||
public setTopBarOpacity(isActive: boolean): void {
|
||||
if (!this.node || !this.node.isValid) return;
|
||||
if (this.topOpacity) {
|
||||
this.topOpacity.opacity = isActive ? this.barActiveOpacity : this.barIdleOpacity;
|
||||
}
|
||||
this.node.active = true;
|
||||
}
|
||||
|
||||
/** 显示顶栏 */
|
||||
public show(): void {
|
||||
if (this.node && this.node.isValid) this.node.active = true;
|
||||
}
|
||||
|
||||
/** 隐藏顶栏 */
|
||||
public hide(): void {
|
||||
if (this.node && this.node.isValid) this.node.active = false;
|
||||
}
|
||||
|
||||
/** 血条受击抖动动画 */
|
||||
private playHpBarShake(): void {
|
||||
if (!this.node || !this.node.isValid) return;
|
||||
Tween.stopAllByTarget(this.node);
|
||||
this.node.setPosition(this.basePos);
|
||||
tween(this.node)
|
||||
.by(0.04, { position: v3(-3, 0, 0) })
|
||||
.by(0.04, { position: v3(6, 0, 0) })
|
||||
.by(0.04, { position: v3(-5, 0, 0) })
|
||||
.by(0.04, { position: v3(2, 0, 0) })
|
||||
.call(() => {
|
||||
this.node.setPosition(this.basePos);
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/** 对象池回收 / 组件重置时调用 */
|
||||
public reset(): void {
|
||||
this.unscheduleAllCallbacks();
|
||||
if (this.node && this.node.isValid) {
|
||||
Tween.stopAllByTarget(this.node);
|
||||
this.node.setPosition(this.basePos);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/script/game/hero/HeroTopBarComp.ts.meta
Normal file
9
assets/script/game/hero/HeroTopBarComp.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "c4181fc5-1168-4a6d-8d03-d0f2dd0b22a2",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Vec3, _decorator, v3, Collider2D, Contact2DType, Label, Node, Prefab, instantiate, ProgressBar, Component, Material, Sprite, math, clamp, Game, tween, Tween, Color, BoxCollider2D, UITransform, UIOpacity } from "cc";
|
||||
import { Vec3, _decorator, v3, Collider2D, Contact2DType, Node, Prefab, instantiate, tween, Tween, BoxCollider2D, UITransform } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { HeroSpine } from "./HeroSpine";
|
||||
import { HeroTopBarComp } from "./HeroTopBarComp";
|
||||
import { BoxSet, FacSet, FightSet, NumberFormatter, TooltipTypes } from "../common/config/GameSet";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { SkillSet, } from "../common/config/SkillSet";
|
||||
@@ -37,21 +38,10 @@ export class HeroViewComp extends CCComp {
|
||||
realDeadTime: number = 0.1
|
||||
deadCD: number = 0
|
||||
monDeadTime: number = 0.1
|
||||
hp_height: number = 90
|
||||
boss_hp_height: number = 160
|
||||
// 血条显示相关
|
||||
lastBarUpdateTime: number = 0; // 最后一次血条/蓝条/护盾更新时间
|
||||
// ==================== UI 节点引用 ====================
|
||||
private top_node: Node = null!;
|
||||
private topOpacity: UIOpacity = null!;
|
||||
private topBasePos: Vec3 = v3();
|
||||
private lvLabel: Label | null = null; // 等级显示文本
|
||||
private readonly barIdleOpacity: number = 153;
|
||||
private readonly barActiveOpacity: number = 255;
|
||||
private readonly idleOpacityDelay: number = 0.25;
|
||||
private readonly restoreBarIdleOpacity = () => {
|
||||
this.setTopBarOpacity(false);
|
||||
};
|
||||
private topBar: HeroTopBarComp = null!;
|
||||
|
||||
// ==================== 直接访问 HeroAttrsComp ====================
|
||||
get model() {
|
||||
@@ -96,67 +86,26 @@ export class HeroViewComp extends CCComp {
|
||||
this.lastBarUpdateTime = 0;
|
||||
this.status_change("idle");
|
||||
|
||||
// 初始化 UI 节点
|
||||
this.initUINodes();
|
||||
// 初始化头顶状态栏
|
||||
const topNode = this.node.getChildByName("top");
|
||||
this.topBar = topNode.getComponent(HeroTopBarComp) || topNode.addComponent(HeroTopBarComp);
|
||||
this.topBar.init(this.model, this.scale);
|
||||
|
||||
/** 方向 */
|
||||
this.node.setScale(this.scale * Math.abs(this.node.scale.x), 1 * this.node.scale.y); // 确保 scale.x 为正后再乘方向
|
||||
// top_node 取 abs 避免对象池复用时 scale 累乘导致血条方向反转
|
||||
this.top_node.setScale(this.scale * Math.abs(this.top_node.scale.x), 1 * this.top_node.scale.y);
|
||||
/* 显示角色血*/
|
||||
this.top_node.getChildByName("hp").active = true;
|
||||
this.top_node.getChildByName("cd").active = false;
|
||||
this.top_node.getChildByName("shield").active = false;
|
||||
this.top_node.getChildByName("lv").active = true;
|
||||
this.top_node.active = true;
|
||||
this.setTopBarOpacity(false);
|
||||
|
||||
// 初始化等级显示
|
||||
this.lv_show();
|
||||
|
||||
// 🔥 重置血条 UI 显示状态
|
||||
// 🔥 重置描边
|
||||
if (this.model) {
|
||||
this.hp_show();
|
||||
|
||||
// 根据英雄模型中的等级数据设置描边
|
||||
// 注意:HeroViewComp 是挂在 node 上的,而 FlashSprite 可能挂在子节点(如 Sprite/anm 节点)
|
||||
let flashSprite = this.node.getComponent(FlashSprite);
|
||||
if (!flashSprite) {
|
||||
// 如果当前节点没有,尝试在所有子节点中查找
|
||||
flashSprite = this.node.getComponentInChildren(FlashSprite);
|
||||
}
|
||||
|
||||
if (flashSprite) {
|
||||
flashSprite.setOutlineByLevel(this.model.lv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 UI 节点引用 */
|
||||
private initUINodes() {
|
||||
this.top_node = this.node.getChildByName("top");
|
||||
this.topOpacity = this.top_node.getComponent(UIOpacity) || this.top_node.addComponent(UIOpacity);
|
||||
this.top_node.setPosition(0, this.hp_height, 0);
|
||||
if (this.model.is_boss) this.top_node.setPosition(0, this.boss_hp_height, 0);
|
||||
this.topBasePos = this.top_node.position.clone();
|
||||
const hpNode = this.top_node.getChildByName("hp");
|
||||
if (this.model.fac == FacSet.HERO) {
|
||||
hpNode.getChildByName("Bar").getComponent(Sprite).color = new Color("#2ECC71")
|
||||
}
|
||||
|
||||
// 确保血条等UI始终在最上层显示
|
||||
this.top_node.setSiblingIndex(999);
|
||||
|
||||
// 缓存等级显示 Label(lv 节点下的 Label 子节点)
|
||||
const lvNode = this.top_node.getChildByName("lv");
|
||||
if (lvNode) {
|
||||
this.lvLabel = lvNode.getChildByName("Label")?.getComponent(Label) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* View 层每帧更新
|
||||
* 注意:数据更新逻辑已移到 HeroAttrSystem,这里只负责显示
|
||||
@@ -181,103 +130,21 @@ export class HeroViewComp extends CCComp {
|
||||
|
||||
// ✅ 按需更新 UI(脏标签模式)- 只在属性变化时更新
|
||||
if (this.model.dirty_hp) {
|
||||
this.hp_show();
|
||||
this.topBar.hpShow();
|
||||
this.model.dirty_hp = false;
|
||||
}
|
||||
|
||||
|
||||
if (this.model.dirty_shield) {
|
||||
this.show_shield(this.model.shield);
|
||||
this.topBar.shieldShow(this.model.shield);
|
||||
this.model.dirty_shield = false;
|
||||
}
|
||||
|
||||
if (this.model.dirty_lv) {
|
||||
this.lv_show();
|
||||
this.model.dirty_lv = false;
|
||||
}
|
||||
}
|
||||
|
||||
public cd_show() {
|
||||
return;
|
||||
}
|
||||
|
||||
/** 显示护盾 */
|
||||
private show_shield(shield: number = 0) {
|
||||
this.lastBarUpdateTime = Date.now() / 1000;
|
||||
this.node.getChildByName("shielded").active = shield > 0;
|
||||
}
|
||||
|
||||
/** 显示血量 */
|
||||
private hp_show() {
|
||||
this.lastBarUpdateTime = Date.now() / 1000;
|
||||
// 不再基于血量是否满来决定显示状态,只更新进度条
|
||||
let hp = this.model.hp;
|
||||
let hp_max = this.model.hp_max;
|
||||
// mLogger.log(this.debugMode, 'HeroViewComp', "hp_show",hp,hp_max)
|
||||
|
||||
let targetProgress = hp_max > 0 ? hp / hp_max : 0;
|
||||
targetProgress = clamp(targetProgress, 0, 1);
|
||||
let hpNode = this.top_node.getChildByName("hp");
|
||||
let hpProgressBar = hpNode.getComponent(ProgressBar);
|
||||
|
||||
const prevProgress = hpProgressBar.progress;
|
||||
if (targetProgress < hpProgressBar.progress) {
|
||||
this.activateTopBar();
|
||||
this.playHpBarShake();
|
||||
}
|
||||
hpProgressBar.progress = targetProgress;
|
||||
if (targetProgress > prevProgress) {
|
||||
this.setTopBarOpacity(false);
|
||||
}
|
||||
}
|
||||
|
||||
private isFullHpAndNoShield(): boolean {
|
||||
if (!this.model) return false;
|
||||
if (this.model.hp_max <= 0) return false;
|
||||
return this.model.hp >= this.model.hp_max;
|
||||
}
|
||||
|
||||
/** 更新等级显示 */
|
||||
private lv_show() {
|
||||
if (!this.lvLabel || !this.model) return;
|
||||
this.lvLabel.string = String(this.model.lv);
|
||||
}
|
||||
|
||||
private setTopBarOpacity(isActive: boolean) {
|
||||
if (!this.top_node || !this.top_node.isValid) return;
|
||||
if (this.topOpacity) {
|
||||
this.topOpacity.opacity = this.barActiveOpacity;
|
||||
}
|
||||
if (isActive) {
|
||||
this.top_node.active = true;
|
||||
return;
|
||||
}
|
||||
this.top_node.active = true;
|
||||
}
|
||||
|
||||
private activateTopBar() {
|
||||
this.setTopBarOpacity(true);
|
||||
this.unschedule(this.restoreBarIdleOpacity);
|
||||
this.scheduleOnce(this.restoreBarIdleOpacity, this.idleOpacityDelay);
|
||||
}
|
||||
|
||||
private playHpBarShake() {
|
||||
if (!this.top_node || !this.top_node.isValid) return;
|
||||
Tween.stopAllByTarget(this.top_node);
|
||||
this.top_node.setPosition(this.topBasePos);
|
||||
tween(this.top_node)
|
||||
.by(0.04, { position: v3(-3, 0, 0) })
|
||||
.by(0.04, { position: v3(6, 0, 0) })
|
||||
.by(0.04, { position: v3(-5, 0, 0) })
|
||||
.by(0.04, { position: v3(2, 0, 0) })
|
||||
.call(() => {
|
||||
this.top_node.setPosition(this.topBasePos);
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 升级特效 */
|
||||
public lv_up() {
|
||||
this.spawnTimedFx("game/skill/buff/buff_lvup", this.node, 1.0);
|
||||
@@ -349,7 +216,7 @@ export class HeroViewComp extends CCComp {
|
||||
Tooltip.load(pos, type, value, s_uuid, this.node.parent, 1, this.model?.fac ?? FacSet.MON);
|
||||
}
|
||||
|
||||
/** 护盾格挡提示 */
|
||||
/** 护盾减免提示 */
|
||||
shield_tip(absorbed: number) {
|
||||
this.hp_tip(TooltipTypes.shield, NumberFormatter.formatNumber(Math.max(0, Math.floor(absorbed))));
|
||||
}
|
||||
@@ -432,7 +299,7 @@ export class HeroViewComp extends CCComp {
|
||||
}
|
||||
add_shield(shield: number) {
|
||||
// 护盾数据更新由 Model 层处理,这里只负责视图表现
|
||||
if (this.model && this.model.shield > 0) this.show_shield(this.model.shield);
|
||||
if (this.model && this.model.shield > 0) this.topBar.shieldShow(this.model.shield);
|
||||
}
|
||||
|
||||
health(hp: number = 0) {
|
||||
@@ -458,7 +325,7 @@ export class HeroViewComp extends CCComp {
|
||||
}
|
||||
|
||||
// 恢复UI
|
||||
this.top_node.active = true;
|
||||
this.topBar.show();
|
||||
|
||||
this.status_change("idle");
|
||||
|
||||
@@ -504,7 +371,7 @@ export class HeroViewComp extends CCComp {
|
||||
}
|
||||
|
||||
// 隐藏UI
|
||||
this.top_node.active = false;
|
||||
this.topBar.hide();
|
||||
|
||||
// 在销毁实体前先禁用碰撞体,从源头减少"尸体"参与碰撞
|
||||
const collider = this.node.getComponent(Collider2D);
|
||||
@@ -516,23 +383,19 @@ export class HeroViewComp extends CCComp {
|
||||
let isHero = this.model.fac === FacSet.HERO;
|
||||
let dirX = isHero ? -800 : 800;
|
||||
|
||||
// 死亡往后飞出屏幕动画
|
||||
// 死亡往后飞出屏幕动画,结束后统一销毁实体
|
||||
// 英雄为真实死亡:快照已在 doDead 阶段写入 smc.mission.dead_heroes,此处直接销毁
|
||||
tween(this.node)
|
||||
.by(0.5, { position: v3(dirX, 700, 0) }, { easing: "quadOut" })
|
||||
.call(() => {
|
||||
if (isHero) {
|
||||
// 将英雄移到玩家看不到的墓地,留待下回合上场
|
||||
this.node.setPosition(v3(-2000, -2000, 0));
|
||||
} else {
|
||||
// 动画结束后销毁怪物实体
|
||||
this.ent.destroy();
|
||||
}
|
||||
this.ent.destroy();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
do_atked(damage: number, isCrit: boolean, s_uuid: number, isBack: boolean = false) {
|
||||
/** 受击表现(伤害数字、受击动画、击退) */
|
||||
do_atked(damage: number, isCrit: boolean, s_uuid: number, knockbackExtra: number = 0) {
|
||||
// 受到攻击时更新最后更新时间
|
||||
this.activateTopBar();
|
||||
this.topBar.activateTopBar();
|
||||
this.lastBarUpdateTime = Date.now() / 1000;
|
||||
|
||||
if (damage <= 0) return;
|
||||
@@ -547,7 +410,7 @@ export class HeroViewComp extends CCComp {
|
||||
// 视图层表现
|
||||
let SConf = SkillSet[s_uuid]
|
||||
const hitAnm = SConf?.DAnm || "atked";
|
||||
if (isBack) this.back()
|
||||
this.back(knockbackExtra)
|
||||
this.in_atked(hitAnm, this.model.fac == FacSet.HERO ? 1 : -1);
|
||||
this.showDamage(damage, isCrit);
|
||||
}
|
||||
@@ -634,7 +497,7 @@ export class HeroViewComp extends CCComp {
|
||||
if (!this.model) return;
|
||||
|
||||
const damageText = NumberFormatter.formatNumber(Math.max(0, Math.floor(damage)));
|
||||
this.hp_show();
|
||||
this.topBar.hpShow();
|
||||
if (isCrit) {
|
||||
this.hp_tip(TooltipTypes.crit, damageText);
|
||||
} else {
|
||||
@@ -645,10 +508,7 @@ export class HeroViewComp extends CCComp {
|
||||
// 清理残留的定时器和缓动
|
||||
this.unscheduleAllCallbacks();
|
||||
Tween.stopAllByTarget(this.node);
|
||||
if (this.top_node && this.top_node.isValid) {
|
||||
Tween.stopAllByTarget(this.top_node);
|
||||
this.top_node.setPosition(this.topBasePos);
|
||||
}
|
||||
this.topBar.reset();
|
||||
|
||||
// 清理碰撞器事件监听
|
||||
const collider = this.getComponent(Collider2D);
|
||||
@@ -657,7 +517,6 @@ export class HeroViewComp extends CCComp {
|
||||
}
|
||||
this.deadCD = 0
|
||||
this.lastBarUpdateTime = 0
|
||||
this.unschedule(this.restoreBarIdleOpacity);
|
||||
|
||||
// 清理伤害队列
|
||||
this.damageQueue.length = 0;
|
||||
|
||||
@@ -3,13 +3,12 @@ import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/O
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
|
||||
import { HeroInfo, resolveTriggerByLv, resolveReviveByLv } from "../common/config/heroSet";
|
||||
import { HeroInfo, resolveTriggerByLv, resolveReviveByLv, HeroDisVal } from "../common/config/heroSet";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { HeroViewComp } from "./HeroViewComp";
|
||||
import { MoveComp } from "./MoveComp";
|
||||
import { MonMoveComp } from "./MonMoveComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { SkillTriggerType } from "../common/config/heroSet";
|
||||
import { SkillTriggerType, HType } from "../common/config/heroSet";
|
||||
import { SkillTriggerHelper } from "./SkillTriggerHelper";
|
||||
/** 怪物实体:负责怪物对象池复用、属性初始化、入场动画与回收 */
|
||||
@ecs.register(`Monster`)
|
||||
@@ -19,7 +18,7 @@ export class Monster extends ecs.Entity {
|
||||
/** 怪物表现组件引用 */
|
||||
HeroView!: HeroViewComp;
|
||||
/** 怪物移动组件引用 */
|
||||
MonMove!: MonMoveComp;
|
||||
MonMove!: MoveComp;
|
||||
/** 调试开关,控制生命周期日志输出 */
|
||||
private debugMode: boolean = false;
|
||||
|
||||
@@ -98,7 +97,7 @@ export class Monster extends ecs.Entity {
|
||||
/** 注册实体必需组件:移动 + 属性 */
|
||||
protected init() {
|
||||
this.addComponents<ecs.Comp>(
|
||||
MonMoveComp,
|
||||
MoveComp,
|
||||
HeroAttrsComp,
|
||||
);
|
||||
}
|
||||
@@ -149,8 +148,9 @@ export class Monster extends ecs.Entity {
|
||||
(node as any)._baseScale = node.scale.clone();
|
||||
}
|
||||
|
||||
// 统一挂到实体显示层 HERO 节点下
|
||||
node.parent = scene.entityLayer!.node!.getChildByName("HERO")!;
|
||||
// 按身份挂载显示层:Boss → BOSS 层(最底),小怪 → MON 层(编辑器已配置好层级顺序)
|
||||
const layerName = is_boss ? "BOSS" : "MON";
|
||||
node.parent = scene.entityLayer!.node!.getChildByName(layerName)!;
|
||||
var view = node.getComponent(HeroViewComp)!;
|
||||
const collider = node.getComponent(BoxCollider2D);
|
||||
// 入场期间关闭碰撞,防止下落时提前参与战斗
|
||||
@@ -175,7 +175,8 @@ export class Monster extends ecs.Entity {
|
||||
model.speed = hero.speed ?? 800;
|
||||
model.type = hero.type;
|
||||
model.fac = FacSet.MON;
|
||||
model.dis = hero.dis ?? 720;
|
||||
// 攻击距离:配置优先;未配置按攻击定位取默认值(近战100 / 中程300 / 远程450)
|
||||
model.dis = hero.dis ?? HeroDisVal[hero.type as HType.Melee | HType.Mid | HType.Long];
|
||||
model.posIndex = laneIndex;
|
||||
if (laneIndex >= 0) {
|
||||
smc.mission.monGrid[laneIndex] = this.eid;
|
||||
@@ -248,7 +249,7 @@ export class Monster extends ecs.Entity {
|
||||
// 广播怪物加载事件,供刷怪与战斗系统联动
|
||||
oops.message.dispatchEvent("monster_load", this)
|
||||
// 初始化移动参数:方向、目标 X、站位基准 Y
|
||||
const move = this.get(MonMoveComp);
|
||||
const move = this.get(MoveComp);
|
||||
move.reset();
|
||||
move.direction = -1;
|
||||
move.targetX = pos.x;
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { HeroViewComp } from "./HeroViewComp";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { BoxSet, FacSet } from "../common/config/GameSet";
|
||||
import { Node } from "cc";
|
||||
|
||||
@ecs.register('MonMoveComp')
|
||||
export class MonMoveComp extends ecs.Comp {
|
||||
/** 朝向:1=向右,-1=向左 */
|
||||
direction: number = -1;
|
||||
/** 当前移动目标 X */
|
||||
targetX: number = 0;
|
||||
/** 是否允许移动(出生落地前会短暂关闭) */
|
||||
moving: boolean = true;
|
||||
/** 站位基准 Y */
|
||||
baseY: number = 0;
|
||||
/** 出生序,用于同条件渲染排序稳定 */
|
||||
spawnOrder: number = 0;
|
||||
|
||||
reset() {
|
||||
this.direction = -1;
|
||||
this.targetX = 0;
|
||||
this.moving = true;
|
||||
this.baseY = 0;
|
||||
this.spawnOrder = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ecs.register('MonMoveSystem')
|
||||
export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||
/** 怪物接近英雄后的停船距离:小于该阈值即停下交战 */
|
||||
private readonly combatHaltDistance = 80;
|
||||
/** 渲染层级重排节流,避免每帧排序 */
|
||||
private readonly renderSortInterval = 0.05;
|
||||
private lastRenderSortAt = 0;
|
||||
private monMoveMatcher: ecs.IMatcher | null = null;
|
||||
private heroViewMatcher: ecs.IMatcher | null = null;
|
||||
private readonly renderEntries: { node: Node; bossPriority: number; frontScore: number; spawnOrder: number; eid: number }[] = [];
|
||||
private renderEntryCount = 0;
|
||||
|
||||
private getMonMoveMatcher(): ecs.IMatcher {
|
||||
if (!this.monMoveMatcher) {
|
||||
this.monMoveMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp, MonMoveComp);
|
||||
}
|
||||
return this.monMoveMatcher;
|
||||
}
|
||||
|
||||
private getHeroViewMatcher(): ecs.IMatcher {
|
||||
if (!this.heroViewMatcher) {
|
||||
this.heroViewMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp);
|
||||
}
|
||||
return this.heroViewMatcher;
|
||||
}
|
||||
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(MonMoveComp, HeroViewComp, HeroAttrsComp);
|
||||
}
|
||||
|
||||
update(e: ecs.Entity) {
|
||||
/** 战斗未开始/暂停时不驱动移动 */
|
||||
if (!smc.mission.play || smc.mission.pause) return;
|
||||
|
||||
const model = e.get(HeroAttrsComp);
|
||||
const move = e.get(MonMoveComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
if (!model || !move || !view || !view.node) return;
|
||||
if (model.fac !== FacSet.MON) return;
|
||||
if (!move.moving) return;
|
||||
|
||||
/** 关卡阶段性冻结怪物行为 */
|
||||
if (smc.mission.stop_mon_action) {
|
||||
this.clearCombatTarget(model);
|
||||
view.status_change("idle");
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.is_stop || model.is_dead || model.is_reviving || model.isFrost() || model.isStun()) {
|
||||
this.clearCombatTarget(model);
|
||||
if (!model.is_reviving) view.status_change("idle");
|
||||
return;
|
||||
}
|
||||
|
||||
/** 所有移动都锁定在 baseY,避免出现“漂移” */
|
||||
if (view.node.position.y !== move.baseY) {
|
||||
view.node.setPosition(view.node.position.x, move.baseY, 0);
|
||||
}
|
||||
// 渲染层级统交由 MoveSystem 统一处理,避免两个 System 争抢 setSiblingIndex
|
||||
|
||||
// 仅在战斗中才处理索敌;非战斗阶段也允许向左推进,让怪物自然逼近英雄阵地
|
||||
const nearestEnemy = smc.mission.in_fight ? this.findNearestEnemy(e) : null;
|
||||
if (nearestEnemy) {
|
||||
/** 有敌人:边移动边攻击 */
|
||||
this.processCombatLogic(e, move, view, model, nearestEnemy);
|
||||
this.syncCombatTarget(model, view, nearestEnemy);
|
||||
} else {
|
||||
/** 无敌人:清目标并继续向左推进 */
|
||||
this.clearCombatTarget(model);
|
||||
model.is_atking = false;
|
||||
this.moveLeft(view, move, model);
|
||||
}
|
||||
}
|
||||
|
||||
private clearCombatTarget(model: HeroAttrsComp): void {
|
||||
model.combat_target_eid = -1;
|
||||
model.enemy_in_cast_range = false;
|
||||
}
|
||||
|
||||
private syncCombatTarget(model: HeroAttrsComp, selfView: HeroViewComp, enemyView: HeroViewComp): void {
|
||||
if (!enemyView || !enemyView.node || !enemyView.ent) {
|
||||
this.clearCombatTarget(model);
|
||||
return;
|
||||
}
|
||||
const enemyAttrs = enemyView.ent.get(HeroAttrsComp);
|
||||
if (!enemyAttrs || enemyAttrs.is_dead || enemyAttrs.is_reviving || enemyAttrs.fac === model.fac) {
|
||||
this.clearCombatTarget(model);
|
||||
return;
|
||||
}
|
||||
model.combat_target_eid = enemyView.ent.eid;
|
||||
model.enemy_in_cast_range = this.isEnemyInAttackRange(model, selfView.node.position.x, enemyView.node.position.x);
|
||||
}
|
||||
|
||||
private isEnemyInAttackRange(model: HeroAttrsComp, selfX: number, enemyX: number): boolean {
|
||||
const dist = Math.abs(selfX - enemyX);
|
||||
const attackRange = model.dis;
|
||||
return dist <= attackRange;
|
||||
}
|
||||
|
||||
private processCombatLogic(e: ecs.Entity, move: MonMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||
const selfX = view.node.position.x;
|
||||
const enemyX = enemy.node.position.x;
|
||||
// 停船判定使用固定阈值(80),不依赖技能攻击范围
|
||||
const inRange = Math.abs(selfX - enemyX) <= this.combatHaltDistance;
|
||||
|
||||
if (inRange) {
|
||||
// 接触到英雄方:停止移动,进入攻击状态,由 SCastSystem 负责技能释放
|
||||
model.is_atking = true;
|
||||
const dir = enemyX > selfX ? 1 : -1;
|
||||
view.scale = dir;
|
||||
if (view.status === "move") {
|
||||
view.status_change("idle");
|
||||
}
|
||||
} else {
|
||||
// 未接触:继续向左推进
|
||||
model.is_atking = false;
|
||||
this.moveLeft(view, move, model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 持续向左推进:怪物不再固定站位,而是以 speed/3 的速度向左移动,
|
||||
* 直到抵达左边界(基地位置)后转入待机,由 SCastSystem 继续负责技能释放。
|
||||
*/
|
||||
private moveLeft(view: HeroViewComp, move: MonMoveComp, model: HeroAttrsComp) {
|
||||
const currentX = view.node.position.x;
|
||||
if (currentX <= BoxSet.LETF_END) {
|
||||
if (view.status !== "atk") {
|
||||
view.status_change("idle");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const dir = -1;
|
||||
move.direction = dir;
|
||||
const speed = model.speed / 3;
|
||||
const delta = speed * this.dt * dir;
|
||||
const newX = Math.max(BoxSet.LETF_END, currentX + delta);
|
||||
if (Math.abs(newX - currentX) >= 0.01) {
|
||||
view.node.setPosition(newX, view.node.position.y, 0);
|
||||
view.status_change("move");
|
||||
}
|
||||
}
|
||||
|
||||
private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null {
|
||||
const currentView = entity.get(HeroViewComp);
|
||||
if (!currentView?.node) return null;
|
||||
|
||||
const currentPos = currentView.node.position;
|
||||
const myFac = entity.get(HeroAttrsComp).fac;
|
||||
|
||||
let nearest: HeroViewComp | null = null;
|
||||
let minDis = Infinity;
|
||||
|
||||
/** 遍历筛出最近敌人:以 X 轴距离为主,Y 轴距离作为同排的决胜权重,使角色优先攻击同路的敌人 */
|
||||
ecs.query(this.getHeroViewMatcher()).forEach(e => {
|
||||
const m = e.get(HeroAttrsComp);
|
||||
if (m.fac !== myFac && !m.is_dead) {
|
||||
const v = e.get(HeroViewComp);
|
||||
if (v?.node) {
|
||||
const dx = Math.abs(currentPos.x - v.node.position.x);
|
||||
const dy = Math.abs(currentPos.y - v.node.position.y);
|
||||
const d = dx + dy * 0.1; // Y轴权重较小,仅在 X 相近时起决定作用
|
||||
if (d < minDis) {
|
||||
minDis = d;
|
||||
nearest = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return nearest;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,31 @@
|
||||
/**
|
||||
* @file MoveComp.ts
|
||||
* @description 统一移动组件与系统(英雄 + 怪物)
|
||||
*
|
||||
* 核心规则:
|
||||
* 1. 准备阶段:双方原地待命,不推进不索敌。
|
||||
* 2. 战斗阶段:双方向最近敌人推进,进入攻击范围(model.dis)后停止移动并攻击。
|
||||
* 3. 场上无敌人:停止移动(战斗结束,由 MissionComp 进入结算)。
|
||||
* 4. 击退/位移不会把单位推出屏幕:HERO 不越过 BoxSet.LETF_END,MON 不越过 BoxSet.RIGHT_END。
|
||||
* 5. 推进方向无上限限制,直到接触敌人或抵达敌方边界。
|
||||
*/
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { HeroViewComp } from "./HeroViewComp";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { BoxSet, FacSet } from "../common/config/GameSet";
|
||||
import { HeroDisVal, HType } from "../common/config/heroSet";
|
||||
import { BoxCollider2D, Node } from "cc";
|
||||
import { MonMoveComp } from "./MonMoveComp";
|
||||
import { Node } from "cc";
|
||||
|
||||
@ecs.register('MoveComp')
|
||||
export class MoveComp extends ecs.Comp {
|
||||
/** 朝向:1=向右,-1=向左 */
|
||||
/** 朝向:1=向右(HERO),-1=向左(MON),由系统根据 fac 自动维护 */
|
||||
direction: number = 1;
|
||||
/** 当前移动目标 X(战斗/回位都会更新) */
|
||||
/** 当前移动目标 X */
|
||||
targetX: number = 0;
|
||||
/** 是否允许移动(出生落地前会短暂关闭) */
|
||||
moving: boolean = true;
|
||||
/** 预留:目标 Y(当前逻辑主要使用 baseY 锁定地平线) */
|
||||
targetY: number = 0;
|
||||
/** 角色所属车道的地平线 Y,移动时强制贴地 */
|
||||
/** 站位基准 Y,移动时强制贴地 */
|
||||
baseY: number = 0;
|
||||
/** 预留:车道索引 */
|
||||
lane: number = 0;
|
||||
/** 出生序,用于同条件渲染排序稳定 */
|
||||
spawnOrder: number = 0;
|
||||
|
||||
@@ -28,70 +33,20 @@ export class MoveComp extends ecs.Comp {
|
||||
this.direction = 1;
|
||||
this.targetX = 0;
|
||||
this.moving = true;
|
||||
this.targetY = 0;
|
||||
this.baseY = 0;
|
||||
this.lane = 0;
|
||||
this.spawnOrder = 0;
|
||||
}
|
||||
}
|
||||
|
||||
interface MoveFacConfig {
|
||||
/** 阵营可前进边界(靠近敌方一侧) */
|
||||
moveFrontX: number;
|
||||
/** 阵营可后退边界(靠近己方一侧) */
|
||||
moveBackX: number;
|
||||
/** 被逼退时前侧安全边界 */
|
||||
retreatFrontX: number;
|
||||
/** 被逼退时后侧安全边界 */
|
||||
retreatBackX: number;
|
||||
}
|
||||
|
||||
import { MissionHeroComp } from "../map/MissionHeroComp";
|
||||
|
||||
@ecs.register('MoveSystem')
|
||||
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||
private readonly heroFrontAnchorX = -200;
|
||||
private readonly monFrontAnchorX = 0;
|
||||
/** 常规同阵营横向最小间距(英雄) */
|
||||
private readonly heroAllySpacingX = 100;
|
||||
/** 常规同阵营横向最小间距(怪物) */
|
||||
private readonly monAllySpacingX = 75;
|
||||
/** 纵向判定为同排的最大 Y 差 */
|
||||
private readonly minSpacingY = 30;
|
||||
/** 渲染层级重排节流,避免每帧排序 */
|
||||
private readonly renderSortInterval = 0.05;
|
||||
private lastRenderSortAt = 0;
|
||||
private heroMoveMatcher: ecs.IMatcher | null = null;
|
||||
private heroViewMatcher: ecs.IMatcher | null = null;
|
||||
private readonly renderEntries: { node: Node; bossPriority: number; frontScore: number; spawnOrder: number; eid: number; laneScore: number }[] = [];
|
||||
private renderEntryCount = 0;
|
||||
|
||||
/**
|
||||
* 阵营可移动边界配置。
|
||||
* HERO 在左侧出生并向右推进;MON 在右侧出生并向左推进。
|
||||
*/
|
||||
private readonly facConfigs: Record<number, MoveFacConfig> = {
|
||||
[FacSet.HERO]: {
|
||||
moveFrontX: 999999,
|
||||
moveBackX: -999999,
|
||||
retreatFrontX: 999999,
|
||||
retreatBackX: -999999,
|
||||
},
|
||||
[FacSet.MON]: {
|
||||
moveFrontX: -999999,
|
||||
moveBackX: 999999,
|
||||
retreatFrontX: -999999,
|
||||
retreatBackX: 999999,
|
||||
}
|
||||
};
|
||||
|
||||
private getHeroMoveMatcher(): ecs.IMatcher {
|
||||
if (!this.heroMoveMatcher) {
|
||||
this.heroMoveMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp, MoveComp);
|
||||
}
|
||||
return this.heroMoveMatcher;
|
||||
}
|
||||
|
||||
private getHeroViewMatcher(): ecs.IMatcher {
|
||||
if (!this.heroViewMatcher) {
|
||||
this.heroViewMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp);
|
||||
@@ -106,75 +61,54 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
update(e: ecs.Entity) {
|
||||
/** 战斗未开始/暂停时不驱动移动 */
|
||||
if (!smc.mission.play || smc.mission.pause) return;
|
||||
|
||||
|
||||
const model = e.get(HeroAttrsComp);
|
||||
const move = e.get(MoveComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
if (!model || !move || !view || !view.node) return;
|
||||
if (model.fac !== FacSet.HERO) return; // 只处理英雄移动
|
||||
if (!move.moving) return;
|
||||
|
||||
if (model.is_stop || model.is_dead || model.is_reviving || model.isFrost()) {
|
||||
/** 关卡阶段性冻结怪物行为 */
|
||||
if (smc.mission.stop_mon_action && model.fac === FacSet.MON) {
|
||||
this.clearCombatTarget(model);
|
||||
view.status_change("idle");
|
||||
return;
|
||||
}
|
||||
|
||||
/** 死亡/复活/控制状态:停止移动与索敌 */
|
||||
if (model.is_stop || model.is_dead || model.is_reviving || model.isFrost() || model.isStun()) {
|
||||
this.clearCombatTarget(model);
|
||||
if (!model.is_reviving) view.status_change("idle");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 获取全局排位目标
|
||||
const slot = this.getGlobalFormationSlot(e, model);
|
||||
move.baseY = slot.targetY;
|
||||
move.targetX = slot.targetX;
|
||||
|
||||
// 战斗阶段不移动
|
||||
const canMove = !smc.mission.in_fight;
|
||||
|
||||
// 2. 平滑 Y 轴换路
|
||||
let isChangingLane = false;
|
||||
if (canMove) {
|
||||
if (Math.abs(view.node.position.y - move.baseY) > 2) {
|
||||
const currentY = view.node.position.y;
|
||||
const deltaY = move.baseY - currentY;
|
||||
const step = 400 * this.dt; // 换路速度
|
||||
const newY = currentY + Math.sign(deltaY) * Math.min(Math.abs(deltaY), step);
|
||||
view.node.setPosition(view.node.position.x, newY, 0);
|
||||
isChangingLane = true;
|
||||
} else {
|
||||
view.node.setPosition(view.node.position.x, move.baseY, 0);
|
||||
}
|
||||
/** 所有移动都锁定在 baseY,避免出现“漂移” */
|
||||
if (view.node.position.y !== move.baseY) {
|
||||
view.node.setPosition(view.node.position.x, move.baseY, 0);
|
||||
}
|
||||
|
||||
// 渲染层级重排
|
||||
this.updateRenderOrder();
|
||||
|
||||
/** 准备阶段:双方原地待命,不推进不索敌 */
|
||||
if (!smc.mission.in_fight) {
|
||||
this.clearCombatTarget(model);
|
||||
model.is_atking = false;
|
||||
if (view.status !== "atk") view.status_change("idle");
|
||||
return;
|
||||
}
|
||||
|
||||
/** 战斗阶段:索敌并推进 */
|
||||
const nearestEnemy = this.findNearestEnemy(e);
|
||||
if (nearestEnemy) {
|
||||
/** 有敌人:进入战斗位移逻辑 */
|
||||
if (canMove) {
|
||||
this.processCombatLogic(e, move, view, model, nearestEnemy);
|
||||
} else {
|
||||
model.is_atking = true; // 战斗中不移动,但保持攻击状态
|
||||
}
|
||||
this.processCombatLogic(e, move, view, model, nearestEnemy);
|
||||
this.syncCombatTarget(model, view, nearestEnemy);
|
||||
} else {
|
||||
/** 无敌人:清目标并回归编队站位 */
|
||||
/** 无敌人:停止移动,等待战斗结束结算 */
|
||||
this.clearCombatTarget(model);
|
||||
if (canMove) {
|
||||
this.moveToSlot(view, move, model, move.targetX);
|
||||
}
|
||||
model.is_atking = false;
|
||||
if (view.status !== "atk") view.status_change("idle");
|
||||
}
|
||||
|
||||
if (canMove) {
|
||||
// 如果只在 Y 轴移动,也要播放 move 动画
|
||||
if (isChangingLane && view.status !== "move" && view.status !== "atk") {
|
||||
view.status_change("move");
|
||||
}
|
||||
} else {
|
||||
// 战斗阶段如果不移动,确保不在攻击时恢复 idle 状态
|
||||
if (!model.is_atking && view.status === "move") {
|
||||
view.status_change("idle");
|
||||
}
|
||||
}
|
||||
/** 渲染层级重排(双阵营统一处理) */
|
||||
this.updateRenderOrder();
|
||||
}
|
||||
|
||||
private clearCombatTarget(model: HeroAttrsComp): void {
|
||||
@@ -202,175 +136,67 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
return dist <= attackRange;
|
||||
}
|
||||
|
||||
private processCombatLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
|
||||
switch (rangeType) {
|
||||
case HType.Melee:
|
||||
this.processMeleeLogic(e, move, view, model, enemy);
|
||||
break;
|
||||
case HType.Mid:
|
||||
this.processMidLogic(e, move, view, model, enemy);
|
||||
break;
|
||||
case HType.Long:
|
||||
this.processLongLogic(e, move, view, model, enemy);
|
||||
break;
|
||||
default:
|
||||
this.processMidLogic(e, move, view, model, enemy); // 默认中程
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 战斗位移:向最近敌人推进,进入攻击范围后停止。
|
||||
* 推进无上限限制,但击退/位移受阵营后退边界保护。
|
||||
*/
|
||||
private processCombatLogic(_e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||
const selfX = view.node.position.x;
|
||||
const enemyX = enemy.node.position.x;
|
||||
const dist = Math.abs(selfX - enemyX);
|
||||
const attackRange = model.dis;
|
||||
/** 近战贴身作战:攻击距离 dis(100) 大于技能范围(60),推进到与敌人相隔 60 才停下,保证技能可命中且不过度贴身 */
|
||||
const stopRange = model.type === 0 ? 80 : attackRange;
|
||||
const inRange = dist <= stopRange;
|
||||
|
||||
private processMeleeLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||
this.processFormationCombat(e, move, view, model);
|
||||
}
|
||||
|
||||
private processMidLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||
this.processFormationCombat(e, move, view, model);
|
||||
}
|
||||
|
||||
private processLongLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||
this.processFormationCombat(e, move, view, model);
|
||||
}
|
||||
|
||||
private processFormationCombat(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) {
|
||||
this.moveToSlot(view, move, model, move.targetX);
|
||||
model.is_atking = true;
|
||||
}
|
||||
|
||||
private getGlobalFormationSlot(self: ecs.Entity, model: HeroAttrsComp): { targetX: number, targetY: number } {
|
||||
const allAllies: ecs.Entity[] = [];
|
||||
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
const move = e.get(MoveComp);
|
||||
if (!attrs || !view?.node || !move) return;
|
||||
if (attrs.is_dead || attrs.is_reviving) return;
|
||||
if (attrs.fac !== model.fac) return;
|
||||
allAllies.push(e);
|
||||
});
|
||||
|
||||
allAllies.sort((a, b) => {
|
||||
const attrsA = a.get(HeroAttrsComp);
|
||||
const attrsB = b.get(HeroAttrsComp);
|
||||
const priorityA = attrsA ? this.getCombatPriority(attrsA) : 0;
|
||||
const priorityB = attrsB ? this.getCombatPriority(attrsB) : 0;
|
||||
if (priorityA !== priorityB) return priorityB - priorityA;
|
||||
|
||||
const deadA = (attrsA?.dead && attrsA.dead.length > 0) ? 1 : 0;
|
||||
const deadB = (attrsB?.dead && attrsB.dead.length > 0) ? 1 : 0;
|
||||
if (deadA !== deadB) return deadB - deadA;
|
||||
|
||||
const lvA = attrsA?.lv ?? 1;
|
||||
const lvB = attrsB?.lv ?? 1;
|
||||
if (lvA !== lvB) return lvB - lvA;
|
||||
const moveA = a.get(MoveComp);
|
||||
const moveB = b.get(MoveComp);
|
||||
const orderA = moveA?.spawnOrder ?? 0;
|
||||
const orderB = moveB?.spawnOrder ?? 0;
|
||||
if (orderA !== orderB) return orderA - orderB;
|
||||
return a.eid - b.eid;
|
||||
});
|
||||
|
||||
const slotIndex = Math.max(0, allAllies.findIndex(entity => entity === self));
|
||||
|
||||
// 优先中前(1) -> 上前(0) -> 下前(2) -> 中后(4) -> 上后(3) -> 下后(5)
|
||||
const slotPriority = [1, 0, 2, 4, 3, 5];
|
||||
const posIndex = slotPriority[slotIndex % 6];
|
||||
const pos = MissionHeroComp.HERO_POSITIONS[posIndex];
|
||||
|
||||
return { targetX: pos.x, targetY: pos.y };
|
||||
}
|
||||
|
||||
private moveToSlot(view: HeroViewComp, move: MoveComp, model: HeroAttrsComp, targetX: number) {
|
||||
const currentX = view.node.position.x;
|
||||
const currentY = view.node.position.y;
|
||||
|
||||
// 当 X 和 Y 都到达目标时,才算真正到达
|
||||
if (Math.abs(currentX - targetX) <= 2 && Math.abs(currentY - move.baseY) <= 2) {
|
||||
view.node.setPosition(targetX, move.baseY, 0);
|
||||
view.status_change("idle");
|
||||
return;
|
||||
}
|
||||
const dir = targetX > currentX ? 1 : -1;
|
||||
move.direction = dir;
|
||||
const speed = model.speed / 3;
|
||||
this.moveEntity(view, dir, speed, targetX);
|
||||
}
|
||||
|
||||
private moveEntity(view: HeroViewComp, direction: number, speed: number, stopAtX?: number) {
|
||||
const model = view.ent.get(HeroAttrsComp);
|
||||
const move = view.ent.get(MoveComp);
|
||||
if (!model || !move) return;
|
||||
/** 按阵营边界裁剪,防止跑出战场可移动区域 */
|
||||
const cfg = this.facConfigs[model.fac] || this.facConfigs[FacSet.HERO];
|
||||
const moveMinX = Math.min(cfg.moveBackX, cfg.moveFrontX);
|
||||
const moveMaxX = Math.max(cfg.moveBackX, cfg.moveFrontX);
|
||||
const currentX = view.node.position.x;
|
||||
const currentY = view.node.position.y;
|
||||
|
||||
const delta = speed * this.dt * direction;
|
||||
let newX = view.node.position.x + delta;
|
||||
if (currentX < moveMinX && direction < 0) {
|
||||
// X 轴到底了,如果 Y 轴还在换路,继续维持 move 状态
|
||||
if (Math.abs(currentY - move.baseY) > 2) {
|
||||
view.status_change("move");
|
||||
} else {
|
||||
if (inRange) {
|
||||
/** 进入攻击范围:停止移动,进入攻击状态,由 SCastSystem 负责技能释放 */
|
||||
model.is_atking = true;
|
||||
const dir = enemyX > selfX ? 1 : -1;
|
||||
view.scale = dir;
|
||||
if (view.status === "move") {
|
||||
view.status_change("idle");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (currentX > moveMaxX && direction > 0) {
|
||||
if (Math.abs(currentY - move.baseY) > 2) {
|
||||
} else {
|
||||
/** 未接触:向敌人方向推进 */
|
||||
model.is_atking = false;
|
||||
const dir = enemyX > selfX ? 1 : -1;
|
||||
move.direction = dir;
|
||||
const speed = model.speed / 3;
|
||||
const delta = speed * this.dt * dir;
|
||||
let newX = selfX + delta;
|
||||
|
||||
/** 后退边界钳制:防止被击退/位移推出屏幕 */
|
||||
newX = this.clampByFacBoundary(newX, model.fac);
|
||||
|
||||
if (Math.abs(newX - selfX) >= 0.01) {
|
||||
view.node.setPosition(newX, view.node.position.y, 0);
|
||||
view.status_change("move");
|
||||
} else {
|
||||
view.status_change("idle");
|
||||
}
|
||||
return;
|
||||
}
|
||||
newX = Math.max(moveMinX, Math.min(moveMaxX, newX));
|
||||
if (stopAtX !== undefined) {
|
||||
/** 指定停止点时,限制不越过 stopAtX */
|
||||
newX = direction > 0 ? Math.min(newX, stopAtX) : Math.max(newX, stopAtX);
|
||||
}
|
||||
if (Math.abs(newX - currentX) < 0.01) {
|
||||
// X轴虽然没变化,但如果Y轴还在移动,依然是move状态
|
||||
if (Math.abs(currentY - move.baseY) > 2) {
|
||||
view.status_change("move");
|
||||
} else {
|
||||
view.status_change("idle");
|
||||
}
|
||||
return;
|
||||
}
|
||||
view.node.setPosition(newX, view.node.position.y, 0); // 注意:这里只更新 X,Y 在外部平滑逻辑更新
|
||||
view.status_change("move");
|
||||
}
|
||||
|
||||
private getCombatPriority(model: HeroAttrsComp): number {
|
||||
/** 数值越大越靠前:近战 > 中程 > 远程 */
|
||||
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
|
||||
if (rangeType === HType.Melee) return 3;
|
||||
if (rangeType === HType.Mid) return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
private resolveCombatRange(model: HeroAttrsComp, defaultMin: number, defaultMax: number): [number, number] {
|
||||
const minRange = model.getCachedMinSkillDistance();
|
||||
const maxRange = model.getCachedMaxSkillDistance();
|
||||
if (maxRange <= 0) return [defaultMin, defaultMax];
|
||||
const safeMin = Math.max(0, Math.min(minRange, maxRange - 20));
|
||||
return [safeMin, maxRange];
|
||||
/**
|
||||
* 按阵营裁剪 X 坐标,防止被击退/位移推出屏幕。
|
||||
* HERO 后退不越过 BoxSet.LETF_END;MON 后退不越过 BoxSet.RIGHT_END。
|
||||
*/
|
||||
private clampByFacBoundary(x: number, fac: number): number {
|
||||
if (fac === FacSet.HERO) {
|
||||
return Math.max(BoxSet.LETF_END, x);
|
||||
}
|
||||
return Math.min(BoxSet.RIGHT_END, x);
|
||||
}
|
||||
|
||||
private findNearestEnemy(entity: ecs.Entity): HeroViewComp | null {
|
||||
const currentView = entity.get(HeroViewComp);
|
||||
if (!currentView?.node) return null;
|
||||
|
||||
|
||||
const currentPos = currentView.node.position;
|
||||
const myFac = entity.get(HeroAttrsComp).fac;
|
||||
|
||||
|
||||
let nearest: HeroViewComp | null = null;
|
||||
let minDis = Infinity;
|
||||
|
||||
|
||||
/** 遍历筛出最近敌人:以 X 轴距离为主,Y 轴距离作为同排的决胜权重,使角色优先攻击同路的敌人 */
|
||||
ecs.query(this.getHeroViewMatcher()).forEach(e => {
|
||||
const m = e.get(HeroAttrsComp);
|
||||
@@ -392,38 +218,43 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
|
||||
private updateRenderOrder() {
|
||||
const scene = smc.map?.MapView?.scene;
|
||||
const actorRoot = scene?.entityLayer?.node?.getChildByName("HERO");
|
||||
if (!actorRoot) return;
|
||||
const layerNode = scene?.entityLayer?.node;
|
||||
if (!layerNode) return;
|
||||
|
||||
const now = Date.now() / 1000;
|
||||
if (now - this.lastRenderSortAt < this.renderSortInterval) return;
|
||||
this.lastRenderSortAt = now;
|
||||
|
||||
const heroRoot = layerNode.getChildByName("HERO");
|
||||
const monRoot = layerNode.getChildByName("MON");
|
||||
const bossRoot = layerNode.getChildByName("BOSS");
|
||||
|
||||
/** 分层重排同层节点:各阵营层内按 Y 轴路次 -> 前后位置 -> 出生序 -> eid 排序
|
||||
* (Boss 已有独立 BOSS 层固定在最底,无需 bossPriority 参与排序) */
|
||||
this.renderEntryCount = 0;
|
||||
ecs.query(this.getHeroViewMatcher()).forEach(e => {
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
const actorView = e.get(HeroViewComp);
|
||||
if (!attrs || !actorView?.node || attrs.is_dead) return;
|
||||
if (attrs.fac !== FacSet.HERO && attrs.fac !== FacSet.MON) return;
|
||||
|
||||
if (actorView.node.parent !== actorRoot) {
|
||||
actorView.node.parent = actorRoot;
|
||||
|
||||
/** 按阵营/身份归位到对应显示层 */
|
||||
let targetRoot: Node | null | undefined;
|
||||
if (attrs.fac === FacSet.HERO) {
|
||||
targetRoot = heroRoot;
|
||||
} else {
|
||||
targetRoot = attrs.is_boss ? (bossRoot ?? monRoot ?? heroRoot) : (monRoot ?? heroRoot);
|
||||
}
|
||||
if (!targetRoot) return;
|
||||
if (actorView.node.parent !== targetRoot) {
|
||||
actorView.node.parent = targetRoot;
|
||||
}
|
||||
|
||||
// 获取 spawnOrder,由于可能挂载 MoveComp 或 MonMoveComp,我们需要动态获取
|
||||
let spawnOrder = 0;
|
||||
const heroMove = e.get(MoveComp);
|
||||
if (heroMove) {
|
||||
spawnOrder = heroMove.spawnOrder;
|
||||
} else {
|
||||
const monMove = e.get(MonMoveComp);
|
||||
if (monMove) {
|
||||
spawnOrder = monMove.spawnOrder;
|
||||
}
|
||||
}
|
||||
/** 统一从 MoveComp 获取 spawnOrder */
|
||||
const move = e.get(MoveComp);
|
||||
const spawnOrder = move?.spawnOrder ?? 0;
|
||||
|
||||
// 按 Y 轴计算渲染层级权重(Y 越小,说明越靠近屏幕下方,应该渲染在越前面)
|
||||
// 之前的 isFly 逻辑已被移除,统一按 Y 轴处理三路渲染
|
||||
const laneScore = -actorView.node.position.y;
|
||||
// X 轴权重:站在前排的(交战处)优先渲染
|
||||
const frontScore = attrs.fac === FacSet.HERO ? actorView.node.position.x : -actorView.node.position.x;
|
||||
@@ -435,7 +266,6 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
this.renderEntries.push(entry);
|
||||
}
|
||||
entry.node = actorView.node;
|
||||
entry.bossPriority = attrs.is_boss ? 1 : 0;
|
||||
entry.laneScore = laneScore;
|
||||
entry.frontScore = frontScore;
|
||||
entry.spawnOrder = spawnOrder;
|
||||
@@ -444,9 +274,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
});
|
||||
this.renderEntries.length = this.renderEntryCount;
|
||||
|
||||
/** 定时重排同层节点:Boss 优先级 -> Y 轴路次 -> 前后位置 -> 出生序 -> eid */
|
||||
this.renderEntries.sort((a, b) => {
|
||||
if (a.bossPriority !== b.bossPriority) return a.bossPriority - b.bossPriority;
|
||||
// Y轴靠下的(laneScore 较大)应该在后面,从而渲染在上面
|
||||
if (Math.abs(a.laneScore - b.laneScore) > 10) return a.laneScore - b.laneScore;
|
||||
if (a.frontScore !== b.frontScore) return a.frontScore - b.frontScore;
|
||||
@@ -454,8 +282,14 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
return a.eid - b.eid;
|
||||
});
|
||||
|
||||
this.renderEntries.forEach((item, index) => {
|
||||
/** 分层应用 siblingIndex:各层内部从 0 递增 */
|
||||
const layerCounters = new Map<Node, number>();
|
||||
this.renderEntries.forEach((item) => {
|
||||
const parent = item.node.parent;
|
||||
if (!parent) return;
|
||||
const index = layerCounters.get(parent) ?? 0;
|
||||
item.node.setSiblingIndex(index);
|
||||
layerCounters.set(parent, index + 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
143
assets/script/game/map/BattleBannerComp.ts
Normal file
143
assets/script/game/map/BattleBannerComp.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* @file BattleBannerComp.ts
|
||||
* @description 战斗横幅统一通道组件(表现层)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 监听清屏(WaveClear)/ Boss 预警(BossWarning)/ 连杀(ComboReach)事件,播分级横幅。
|
||||
* 2. 横幅动画:右侧飞入(backOut)→ 中央停留 → 左侧飞出(backIn),与 MissionComp.playTooltipAnim 同风格。
|
||||
* 3. 清屏奖励结算(fastClear 金币 / allAlive 刷新石),走 MissionEconomy 静态接口。
|
||||
* 4. Boss 登场(BossSpawn)触发震屏 + 音效。
|
||||
*
|
||||
* 使用 oops-framework 模块:oops.message(事件解耦)、oops.audio(音效)。
|
||||
* 编辑器绑定:bannerNode(Label+背景节点,初始 active=false)。
|
||||
*/
|
||||
import { _decorator, Node, Label, tween, Tween, v3, Color } from "cc";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { MissionEconomy } from "./MissionEconomy";
|
||||
import { ScreenShake } from "../common/ScreenShake";
|
||||
import { WAVE_DURATION } from "./RogueConfig";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/** 清屏奖励:迅速清场(< 75% 时长) */
|
||||
const FAST_CLEAR_COIN = 2;
|
||||
/** 清屏奖励:极速清场(< 50% 时长) */
|
||||
const ULTRA_CLEAR_COIN = 5;
|
||||
/** 连杀分级金币爆发(tier 0/1/2 对应 5/10/20 连杀) */
|
||||
const COMBO_COIN = [2, 5, 10];
|
||||
|
||||
@ccclass('BattleBannerComp')
|
||||
export class BattleBannerComp extends CCComp {
|
||||
/** 横幅节点(Label+背景,初始隐藏),编辑器拖拽绑定 */
|
||||
@property({ type: Node, tooltip: "横幅节点(Label+背景,初始 active=false)" })
|
||||
bannerNode: Node | null = null;
|
||||
|
||||
/** 上次横幅播放时间戳(去抖:0.3s 内的新横幅直接打断旧的,不叠加 tween) */
|
||||
private lastBannerTime: number = 0;
|
||||
|
||||
onLoad() {
|
||||
// oops.message 全局事件总线:清屏 / Boss 预警 / Boss 登场 / 连杀
|
||||
oops.message.on(GameEvent.WaveClear, this.onWaveClear, this);
|
||||
oops.message.on(GameEvent.BossWarning, this.onBossWarning, this);
|
||||
oops.message.on(GameEvent.BossSpawn, this.onBossSpawn, this);
|
||||
oops.message.on(GameEvent.ComboReach, this.onComboReach, this);
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
oops.message.off(GameEvent.WaveClear, this.onWaveClear, this);
|
||||
oops.message.off(GameEvent.BossWarning, this.onBossWarning, this);
|
||||
oops.message.off(GameEvent.BossSpawn, this.onBossSpawn, this);
|
||||
oops.message.off(GameEvent.ComboReach, this.onComboReach, this);
|
||||
}
|
||||
|
||||
// ======================== 事件处理 ========================
|
||||
|
||||
/** 清屏庆祝:分级文案 + 奖励结算 */
|
||||
private onWaveClear(event: string, data: { wave: number; clearTime: number; allAlive: boolean; fastClear: number }) {
|
||||
if (data.allAlive) {
|
||||
this.playBanner("Perfect!", new Color(255, 80, 80), 1.2);
|
||||
MissionEconomy.addRefreshStone(1);
|
||||
oops.audio.playEffect("music/flash");
|
||||
} else if (data.fastClear === 2) {
|
||||
this.playBanner("极速清场!", new Color(255, 170, 0), 1.15);
|
||||
MissionEconomy.addCoin(ULTRA_CLEAR_COIN);
|
||||
oops.audio.playEffect("music/flash");
|
||||
} else if (data.fastClear === 1) {
|
||||
this.playBanner("迅速清场!", new Color(255, 220, 60), 1.05);
|
||||
MissionEconomy.addCoin(FAST_CLEAR_COIN);
|
||||
} else {
|
||||
this.playBanner("Clear!", new Color(255, 255, 255), 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
/** Boss 回合预警:红字横幅 */
|
||||
private onBossWarning(event: string, data: { wave: number; eta: number }) {
|
||||
this.playBanner("强敌来袭!", new Color(255, 60, 60), 1.3);
|
||||
oops.audio.playEffect("music/flash");
|
||||
}
|
||||
|
||||
/** Boss 登场:震屏 + 闷响 */
|
||||
private onBossSpawn() {
|
||||
ScreenShake.shake(10, 0.4);
|
||||
oops.audio.playEffect("music/dun");
|
||||
}
|
||||
|
||||
/** 连杀达阈值:分级文案 + 震屏 + 金币爆发 */
|
||||
private onComboReach(event: string, data: { count: number; tier: number }) {
|
||||
const tier = Math.min(data.tier, 2);
|
||||
const colors = [new Color(255, 230, 80), new Color(255, 150, 30), new Color(255, 60, 60)];
|
||||
const scales = [1.0, 1.2, 1.4];
|
||||
this.playBanner(`${data.count} 连杀!`, colors[tier], scales[tier]);
|
||||
if (tier === 1) ScreenShake.shake(4, 0.2);
|
||||
if (tier === 2) ScreenShake.shake(8, 0.3);
|
||||
MissionEconomy.addCoin(COMBO_COIN[tier]);
|
||||
// 占位音效分级(后续可替换为升调 combo 音效)
|
||||
oops.audio.playEffect(tier === 2 ? "music/Fire" : tier === 1 ? "music/Critical" : "music/Hit");
|
||||
}
|
||||
|
||||
// ======================== 横幅播放 ========================
|
||||
|
||||
/**
|
||||
* 播放横幅:右进 → 中央停留 → 左出
|
||||
* @param text 文案
|
||||
* @param color 文字颜色
|
||||
* @param scale 整体缩放(分级表现力)
|
||||
*/
|
||||
private playBanner(text: string, color: Color, scale: number = 1.0) {
|
||||
if (!this.bannerNode || !this.bannerNode.isValid) return;
|
||||
|
||||
// 去抖:0.3s 内的新横幅直接打断旧的
|
||||
const now = Date.now();
|
||||
if (now - this.lastBannerTime < 300) {
|
||||
Tween.stopAllByTarget(this.bannerNode);
|
||||
}
|
||||
this.lastBannerTime = now;
|
||||
|
||||
this.bannerNode.active = true;
|
||||
this.bannerNode.setScale(v3(scale, scale, 1));
|
||||
|
||||
const label = this.bannerNode.getComponentInChildren(Label);
|
||||
if (label) {
|
||||
label.string = text;
|
||||
label.color = color;
|
||||
label.updateRenderData(true);
|
||||
}
|
||||
|
||||
Tween.stopAllByTarget(this.bannerNode);
|
||||
const startPos = v3(1200, 0, 0);
|
||||
const centerPos = v3(0, 0, 0);
|
||||
const endPos = v3(-1200, 0, 0);
|
||||
this.bannerNode.setPosition(startPos);
|
||||
|
||||
tween(this.bannerNode)
|
||||
.to(0.4, { position: centerPos }, { easing: "backOut" })
|
||||
.to(0.8, { position: v3(-40, 0, 0) }, { easing: "sineInOut" })
|
||||
.to(0.35, { position: endPos }, { easing: "backIn" })
|
||||
.call(() => {
|
||||
this.bannerNode!.active = false;
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
||||
9
assets/script/game/map/BattleBannerComp.ts.meta
Normal file
9
assets/script/game/map/BattleBannerComp.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "676e7123-f3e8-4e9e-8942-b1c025c6708e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -285,11 +285,11 @@ export class CHeroComp extends CCComp {
|
||||
// 背景:按 card_lv 激活对应颜色子节点(1→green 2→blue 3→purple 4→red 5→yellow)
|
||||
this.updateBgNode();
|
||||
|
||||
// 描述富文本:不再单独手写 info,统一套用英雄 1 级能力介绍(buildSkillDescMoreRich)
|
||||
// 描述富文本:统一套用英雄 5 级能力介绍(buildSkillDescMoreRich)
|
||||
// 与英雄信息弹窗/升级确认框同一套文案规则,杜绝手写文案与数值漂移
|
||||
// outlineRich 为整段富文本包裹描边,提升深色面板可读性
|
||||
if (this.info_rich) {
|
||||
const desc = hero ? buildSkillDescMoreRich(this.buildHeroDescSource(hero)) : (this.cardData.info || "");
|
||||
const desc = hero ? buildSkillDescMoreRich(this.buildHeroDescSource(hero), false) : (this.cardData.info || "");
|
||||
this.info_rich.string = outlineRich(desc);
|
||||
this.info_rich.node.active = !!desc;
|
||||
}
|
||||
@@ -467,14 +467,14 @@ export class CHeroComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造英雄 1 级能力描述数据源(ISkillDescSource)。
|
||||
* lv 恒为 1,由各 resolver 取 1 级生效档,与英雄信息弹窗初始展示口径一致。
|
||||
* 构造英雄 5 级能力描述数据源(ISkillDescSource)。
|
||||
* lv 恒为 5,由各 resolver 取 5 级生效档,展示英雄满级能力信息。
|
||||
* @param hero HeroInfo 静态配置项
|
||||
* @returns 描述数据源
|
||||
*/
|
||||
private buildHeroDescSource(hero: typeof HeroInfo[number]): ISkillDescSource {
|
||||
return {
|
||||
lv: 1,
|
||||
lv: 5,
|
||||
call: hero.call,
|
||||
dead: hero.dead,
|
||||
fstart: hero.fstart,
|
||||
|
||||
101
assets/script/game/map/CoinFlyComp.ts
Normal file
101
assets/script/game/map/CoinFlyComp.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @file CoinFlyComp.ts
|
||||
* @description 金币飞行表现组件(表现层,纯视觉)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 监听 CoinFly 事件(怪物死亡掉金币,账务已由 MissionEconomy 即时结算)。
|
||||
* 2. 从怪物位置生成金币 icon,抛物线散开后加速飞入钱包 icon。
|
||||
* 3. 独立 NodePool 管理金币节点(上限 30),不复用伤害飘字池。
|
||||
*
|
||||
* 使用 oops-framework 模块:oops.message(事件解耦)。
|
||||
* 编辑器绑定:flyLayer(全屏容器,最高 sibling)、coinIconSrc(钱包 coin/icon 节点,取其 spriteFrame)。
|
||||
*/
|
||||
import { _decorator, Node, Sprite, UITransform, tween, v3, Vec3, NodePool } from "cc";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/** 普通怪金币枚数 */
|
||||
const COIN_COUNT_NORMAL = 3;
|
||||
/** Boss 金币枚数 */
|
||||
const COIN_COUNT_BOSS = 8;
|
||||
/** 对象池上限(防节点泄漏) */
|
||||
const POOL_MAX = 30;
|
||||
|
||||
@ccclass('CoinFlyComp')
|
||||
export class CoinFlyComp extends CCComp {
|
||||
@property({ type: Node, tooltip: "金币飞行容器(全屏节点,置于最高 sibling 盖在所有 UI 之上)" })
|
||||
flyLayer: Node | null = null;
|
||||
|
||||
@property({ type: Node, tooltip: "钱包金币 icon 节点(运行期取其 spriteFrame 克隆)" })
|
||||
coinIconSrc: Node | null = null;
|
||||
|
||||
/** 金币节点对象池 */
|
||||
private pool: NodePool = new NodePool();
|
||||
|
||||
onLoad() {
|
||||
// oops.message 全局事件总线:金币飞行
|
||||
oops.message.on(GameEvent.CoinFly, this.onCoinFly, this);
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
oops.message.off(GameEvent.CoinFly, this.onCoinFly, this);
|
||||
this.pool.clear();
|
||||
}
|
||||
|
||||
private onCoinFly(event: string, data: { worldPos: Vec3 | null; gold: number; isBoss: boolean }) {
|
||||
if (!this.flyLayer || !this.coinIconSrc || !data.worldPos) return;
|
||||
|
||||
const uiTransform = this.flyLayer.getComponent(UITransform);
|
||||
if (!uiTransform) return;
|
||||
|
||||
const startPos = uiTransform.convertToNodeSpaceAR(data.worldPos);
|
||||
const endPos = uiTransform.convertToNodeSpaceAR(this.coinIconSrc.worldPosition);
|
||||
const count = data.isBoss ? COIN_COUNT_BOSS : COIN_COUNT_NORMAL;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.spawnCoin(startPos, endPos, i * 0.03);
|
||||
}
|
||||
}
|
||||
|
||||
/** 生成一枚金币:上抛散开 → 加速飞向钱包 → 回收 */
|
||||
private spawnCoin(startPos: Vec3, endPos: Vec3, delay: number) {
|
||||
const coin = this.pool.size() > 0 ? this.pool.get()! : this.createCoinNode();
|
||||
coin.parent = this.flyLayer;
|
||||
coin.setPosition(startPos);
|
||||
coin.setScale(v3(1, 1, 1));
|
||||
|
||||
const scatter = v3(
|
||||
startPos.x + (Math.random() * 120 - 60),
|
||||
startPos.y + 60 + Math.random() * 40,
|
||||
0);
|
||||
|
||||
tween(coin)
|
||||
.delay(delay)
|
||||
.to(0.25, { position: scatter }, { easing: "quadOut" })
|
||||
.to(0.4, { position: endPos }, { easing: "quadIn" })
|
||||
.call(() => this.recycleCoin(coin))
|
||||
.start();
|
||||
}
|
||||
|
||||
private createCoinNode(): Node {
|
||||
const node = new Node("coin_fly");
|
||||
node.addComponent(UITransform).setContentSize(32, 32);
|
||||
const sp = node.addComponent(Sprite);
|
||||
const srcSp = this.coinIconSrc?.getComponent(Sprite);
|
||||
if (srcSp?.spriteFrame) {
|
||||
sp.spriteFrame = srcSp.spriteFrame;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private recycleCoin(coin: Node) {
|
||||
if (this.pool.size() >= POOL_MAX) {
|
||||
coin.destroy();
|
||||
return;
|
||||
}
|
||||
this.pool.put(coin);
|
||||
}
|
||||
}
|
||||
9
assets/script/game/map/CoinFlyComp.ts.meta
Normal file
9
assets/script/game/map/CoinFlyComp.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "f607a68a-c2e6-4ebb-8439-569a24143e6e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
72
assets/script/game/map/ComboComp.ts
Normal file
72
assets/script/game/map/ComboComp.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @file ComboComp.ts
|
||||
* @description 连杀 Combo 计数组件(逻辑层,事件驱动)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 监听 MonDead 事件,2s 滑动窗口内累计连杀数。
|
||||
* 2. 达阈值(5/10/20)时派发 ComboReach 事件,由 BattleBannerComp 消费分级表现。
|
||||
* 3. 窗口超时或整局结束(MissionEnd)清零。
|
||||
*
|
||||
* 使用 oops-framework 模块:oops.message(事件解耦)。
|
||||
* 本组件无 UI 绑定,挂 mission.prefab 任意节点即可。
|
||||
*/
|
||||
import { _decorator } from "cc";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
/** 连杀窗口(秒):窗口内每次击杀续期 */
|
||||
const COMBO_WINDOW = 2;
|
||||
/** 连杀阈值档位:x5 / x10 / x20 */
|
||||
const COMBO_TIERS = [5, 10, 20];
|
||||
|
||||
@ccclass('ComboComp')
|
||||
export class ComboComp extends CCComp {
|
||||
/** 当前连杀数 */
|
||||
private comboCount: number = 0;
|
||||
/** 滑动窗口剩余时间(秒) */
|
||||
private windowTimer: number = 0;
|
||||
|
||||
onLoad() {
|
||||
// oops.message 全局事件总线:怪物死亡 / 整局结束
|
||||
oops.message.on(GameEvent.MonDead, this.onMonDead, this);
|
||||
oops.message.on(GameEvent.MissionEnd, this.resetCombo, this);
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
oops.message.off(GameEvent.MonDead, this.onMonDead, this);
|
||||
oops.message.off(GameEvent.MissionEnd, this.resetCombo, this);
|
||||
}
|
||||
|
||||
private onMonDead() {
|
||||
this.comboCount++;
|
||||
this.windowTimer = COMBO_WINDOW;
|
||||
|
||||
const tier = COMBO_TIERS.indexOf(this.comboCount);
|
||||
if (tier >= 0) {
|
||||
// oops.message 全局事件总线:连杀达阈值(横幅/震屏/金币爆发由 BattleBannerComp 消费)
|
||||
oops.message.dispatchEvent(GameEvent.ComboReach, {
|
||||
count: this.comboCount,
|
||||
tier,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private resetCombo() {
|
||||
this.comboCount = 0;
|
||||
this.windowTimer = 0;
|
||||
}
|
||||
|
||||
protected update(dt: number) {
|
||||
if (!smc.mission.play || smc.mission.pause) return;
|
||||
if (this.windowTimer > 0) {
|
||||
this.windowTimer -= dt;
|
||||
if (this.windowTimer <= 0) {
|
||||
this.comboCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/script/game/map/ComboComp.ts.meta
Normal file
9
assets/script/game/map/ComboComp.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "1bf46bb7-2f09-4487-9551-3672912c0afb",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/modu
|
||||
import { CardTriggerType } from "../common/config/CardSet";
|
||||
import { EquipPoolList } from "../common/config/EquipSet";
|
||||
import { SkillSet, SkillOverrides, FieldSkillSet } from "../common/config/SkillSet";
|
||||
import { FieldEntry } from "../common/config/heroSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
@@ -93,8 +94,8 @@ export class EquipBoxComp extends CCComp {
|
||||
private keep_waves: number = 0;
|
||||
/** 技能覆写参数(自定义伤害、Buff等) */
|
||||
private overrides?: SkillOverrides;
|
||||
/** 驻场技能 UUID 列表 */
|
||||
public field: number[] = [];
|
||||
/** 驻场技能词条列表(uuid 指向 FieldSkillSet 底座,value 为生效数值) */
|
||||
public field: FieldEntry[] = [];
|
||||
/** 卡牌自定义图标ID(优先级最高,未定义则按 trigger_type 自动取) */
|
||||
private card_icon?: string;
|
||||
|
||||
@@ -425,7 +426,7 @@ export class EquipBoxComp extends CCComp {
|
||||
if (this.trigger_type === CardTriggerType.Interval) {
|
||||
iconId = this.s_uuid ? SkillSet[this.s_uuid]?.icon : undefined;
|
||||
} else if (this.trigger_type === CardTriggerType.Field) {
|
||||
const fieldUuid = this.field?.[0];
|
||||
const fieldUuid = this.field?.[0]?.uuid;
|
||||
iconId = fieldUuid ? FieldSkillSet[fieldUuid]?.icon : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,7 +584,7 @@ export class HInfoComp extends CCComp {
|
||||
if (config.trigger_type === CardTriggerType.Interval) {
|
||||
iconId = config.skill ? SkillSet[config.skill]?.icon : undefined;
|
||||
} else if (config.trigger_type === CardTriggerType.Field) {
|
||||
const fieldUuid = config.field?.[0];
|
||||
const fieldUuid = config.field?.[0]?.uuid;
|
||||
iconId = fieldUuid ? FieldSkillSet[fieldUuid]?.icon : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@ export class HeroMoreCom extends Component {
|
||||
/** 冰冻率附加值 */
|
||||
@property(Label)
|
||||
private freeze_plus_label: Label = null;
|
||||
/** 击退率总值(百分点) */
|
||||
/** 击退强化总值(击退距离增量) */
|
||||
@property(Label)
|
||||
private knockback_all_label: Label = null;
|
||||
/** 击退率附加值 */
|
||||
/** 击退强化附加值 */
|
||||
@property(Label)
|
||||
private knockback_plus_label: Label = null;
|
||||
/** 穿透率总值(百分点) */
|
||||
@@ -177,12 +177,13 @@ export class HeroMoreCom extends Component {
|
||||
const finalCritDmg = this.model.getFinalCritDamage();
|
||||
this.setStatRow(this.crit_dmg_all_label, this.crit_dmg_plus_label, finalCritDmg, finalCritDmg - (this.model.crit_damage ?? 0), "%");
|
||||
|
||||
// ---- 冰冻率 / 击退率 / 穿透率 / 风怒率(百分点) ----
|
||||
// ---- 冰冻率 / 击退强化 / 穿透率 / 风怒率 ----
|
||||
const finalFreeze = this.model.getFinalFreezeChance();
|
||||
this.setStatRow(this.freeze_all_label, this.freeze_plus_label, finalFreeze, finalFreeze - (this.model.freeze_chance ?? 0), "%");
|
||||
|
||||
// 击退强化已改为击退距离增量,不再按百分比显示
|
||||
const finalKnockback = this.model.getFinalKnockbackChance();
|
||||
this.setStatRow(this.knockback_all_label, this.knockback_plus_label, finalKnockback, finalKnockback - (this.model.knockback_chance ?? 0), "%");
|
||||
this.setStatRow(this.knockback_all_label, this.knockback_plus_label, finalKnockback, finalKnockback - (this.model.knockback_chance ?? 0), "");
|
||||
|
||||
const finalPuncture = this.model.getFinalPunctureChance();
|
||||
this.setStatRow(this.puncture_all_label, this.puncture_plus_label, finalPuncture, finalPuncture - (this.model.puncture_chance ?? 0), "%");
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 职责:
|
||||
* 1. **卡牌分发管理** —— 从卡池抽取 3 张英雄卡,分发到 3 个 CHeroComp 槽位。
|
||||
* 抽卡规则:从基础英雄卡池按权重抽取,同一次抽卡内英雄不重复;
|
||||
* 场上已召唤的英雄不再刷出。
|
||||
* 不过滤已召唤英雄,抽到同 uuid 可触发二合一合成升级。
|
||||
* 2. **金币费用管理** —— 抽卡费用(refreshCost)的扣除。
|
||||
* 3. **英雄数量上限校验** —— 在 UseHeroCard 事件的 guard 阶段判断是否允许再召唤英雄。
|
||||
* 4. **场上英雄信息面板(HInfoComp 列表)同步** ——
|
||||
@@ -160,9 +160,9 @@ export class MissionCardComp extends CCComp {
|
||||
/** 药品刷新按钮节点 */
|
||||
@property(Node)
|
||||
shop_refresh: Node = null!;
|
||||
/** 抽卡(刷新)按钮节点 */
|
||||
/** 刷新卡池按钮节点 */
|
||||
@property(Node)
|
||||
cards_chou: Node = null!
|
||||
cards_refresh: Node = null!
|
||||
|
||||
/** 金币显示节点(含 icon + num 子节点) */
|
||||
@property(Node)
|
||||
@@ -192,6 +192,10 @@ export class MissionCardComp extends CCComp {
|
||||
private itemCardComps: ItemListComp[] = [];
|
||||
/** 是否已召唤过英雄(用于控制其他面板按钮是否可点击) */
|
||||
private hasCalledHero: boolean = false;
|
||||
/** 开局免费连续三选一的总次数 */
|
||||
private static readonly OPENING_FREE_PICK_COUNT = 3;
|
||||
/** 开局免费三选一剩余次数(>0 时招募不扣费,选完 1 张自动弹出下一组) */
|
||||
private openingFreePicks: number = 0;
|
||||
|
||||
// ======================== 面板划出状态 ========================
|
||||
|
||||
@@ -271,10 +275,10 @@ export class MissionCardComp extends CCComp {
|
||||
// 清理新手按钮指引的调度与动画,防止回调泄漏
|
||||
this.stopButtonGuide();
|
||||
this.unscheduleAllCallbacks();
|
||||
if (this.cards_chou && this.cards_chou.isValid) {
|
||||
this.cards_chou.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
this.cards_chou.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
|
||||
this.cards_chou.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
|
||||
if (this.cards_refresh && this.cards_refresh.isValid) {
|
||||
this.cards_refresh.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
this.cards_refresh.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
|
||||
this.cards_refresh.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
|
||||
}
|
||||
if (this.showHeros && this.showHeros.isValid) {
|
||||
this.showHeros.off(NodeEventType.TOUCH_END, this.onShowHerosClick, this);
|
||||
@@ -324,7 +328,7 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
this.layoutCardSlots();
|
||||
this.clearAllCards();
|
||||
this.resetButtonScale(this.cards_chou);
|
||||
this.resetButtonScale(this.cards_refresh);
|
||||
this.resetPanelOpenCosts();
|
||||
this.updateCoinAndCostUI();
|
||||
this.updateHeroNumUI(false, false);
|
||||
@@ -352,7 +356,12 @@ export class MissionCardComp extends CCComp {
|
||||
this.updatePanelButtonsInteractable();
|
||||
|
||||
// 初始化英雄信息盒(3 个空槽位)
|
||||
this.refreshHeroBoxSlots();
|
||||
// 暂不启用 herobox 同步显示英雄信息(后续可能恢复),保留代码备查
|
||||
// this.refreshHeroBoxSlots();
|
||||
|
||||
// 重置开局免费三选一次数,随后立即弹出第 1 组三选一
|
||||
this.openingFreePicks = MissionCardComp.OPENING_FREE_PICK_COUNT;
|
||||
this.showCardsPanel();
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "mission start");
|
||||
}
|
||||
@@ -399,7 +408,7 @@ export class MissionCardComp extends CCComp {
|
||||
* 绑定所有事件监听:
|
||||
* - 节点级事件:MissionStart / MissionEnd / FightStart
|
||||
* - 全局消息:CoinAdd / MasterCalled / HeroDead / UseHeroCard / UseSpecialCard
|
||||
* - 按钮触控:抽卡(cards_chou)
|
||||
* - 按钮触控:刷新卡池(cards_refresh)
|
||||
*/
|
||||
private bindEvents() {
|
||||
/** 生命周期事件(节点级) */
|
||||
@@ -422,9 +431,9 @@ export class MissionCardComp extends CCComp {
|
||||
oops.message.on(GameEvent.HeroUpgrade, this.onHeroUpgrade, this);
|
||||
|
||||
/** 按钮触控事件:抽卡 */
|
||||
this.cards_chou?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
this.cards_chou?.on(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
|
||||
this.cards_chou?.on(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
|
||||
this.cards_refresh?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
this.cards_refresh?.on(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
|
||||
this.cards_refresh?.on(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
|
||||
|
||||
|
||||
/** 隐藏卡牌面板按钮 */
|
||||
@@ -512,7 +521,7 @@ export class MissionCardComp extends CCComp {
|
||||
let targetNode: Node | null = null;
|
||||
switch (type) {
|
||||
case "refresh_coin":
|
||||
targetNode = this.cards_chou;
|
||||
targetNode = this.cards_refresh;
|
||||
break;
|
||||
case "buy_coin":
|
||||
targetNode = this.coins_node;
|
||||
@@ -686,10 +695,10 @@ export class MissionCardComp extends CCComp {
|
||||
oops.message.off(GameEvent.CardUsed, this.onCardUsed, this);
|
||||
oops.message.off(GameEvent.HeroBoxEmptyClick, this.onHeroBoxEmptyClick, this);
|
||||
oops.message.off(GameEvent.HeroUpgrade, this.onHeroUpgrade, this);
|
||||
if (this.cards_chou && this.cards_chou.isValid) {
|
||||
this.cards_chou.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
this.cards_chou.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
|
||||
this.cards_chou.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
|
||||
if (this.cards_refresh && this.cards_refresh.isValid) {
|
||||
this.cards_refresh.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
this.cards_refresh.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
|
||||
this.cards_refresh.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
|
||||
}
|
||||
if (this.showHeros && this.showHeros.isValid) {
|
||||
this.showHeros.off(NodeEventType.TOUCH_END, this.onShowHerosClick, this);
|
||||
@@ -746,19 +755,22 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
|
||||
// 英雄登场后同步英雄信息盒
|
||||
this.refreshHeroBoxSlots();
|
||||
// 暂不启用 herobox 同步(后续可能恢复)
|
||||
// this.refreshHeroBoxSlots();
|
||||
}
|
||||
|
||||
/** 英雄死亡事件回调:刷新面板列表并更新英雄数量 UI */
|
||||
private onHeroDead() {
|
||||
this.updateHeroNumUI(true, false);
|
||||
this.refreshHeroBoxSlots();
|
||||
// 暂不启用 herobox 同步(后续可能恢复)
|
||||
// this.refreshHeroBoxSlots();
|
||||
}
|
||||
|
||||
/** 英雄被出售事件回调:更新英雄数量 UI 并清空对应英雄信息盒 */
|
||||
private onHeroSell() {
|
||||
this.updateHeroNumUI(true, false);
|
||||
this.refreshHeroBoxSlots();
|
||||
// 暂不启用 herobox 同步(后续可能恢复)
|
||||
// this.refreshHeroBoxSlots();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -782,7 +794,8 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
/** 英雄升级事件回调:刷新英雄信息盒显示 */
|
||||
private onHeroLvUp() {
|
||||
this.refreshHeroBoxSlots();
|
||||
// 暂不启用 herobox 同步(后续可能恢复)
|
||||
// this.refreshHeroBoxSlots();
|
||||
}
|
||||
|
||||
/** 英雄面板空槽位点击回调:展示英雄卡池选择界面 */
|
||||
@@ -864,8 +877,18 @@ export class MissionCardComp extends CCComp {
|
||||
if (heroIdx >= 0) {
|
||||
const cards = this.buildDrawCards();
|
||||
this.dispatchCardsToSlots(cards);
|
||||
// 抽 1 张后关闭英雄卡池面板
|
||||
this.hideCardsPanel();
|
||||
// 开局免费三选一次数未用完时保持面板展开,连续弹出下一组三选一
|
||||
if (this.openingFreePicks > 0) {
|
||||
this.openingFreePicks--;
|
||||
if (this.openingFreePicks <= 0) {
|
||||
// 最后一组选完,免费次数耗尽,收起面板并通知自动进入战斗
|
||||
this.hideCardsPanel();
|
||||
oops.message.dispatchEvent("OpeningPicksDone");
|
||||
}
|
||||
} else {
|
||||
// 抽 1 张后关闭英雄卡池面板
|
||||
this.hideCardsPanel();
|
||||
}
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "refresh hero pool after buy", {
|
||||
triggerSlot: heroIdx
|
||||
});
|
||||
@@ -910,16 +933,16 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
/** 抽卡按钮按下反馈 */
|
||||
private onDrawTouchStart() {
|
||||
this.playButtonPressAnim(this.cards_chou);
|
||||
this.playButtonPressAnim(this.cards_refresh);
|
||||
}
|
||||
/** 抽卡按钮释放 → 执行抽卡逻辑 */
|
||||
private onDrawTouchEnd() {
|
||||
oops.audio.playEffect("music/button");
|
||||
this.playButtonClickAnim(this.cards_chou, () => this.onClickDraw());
|
||||
this.playButtonClickAnim(this.cards_refresh, () => this.onClickDraw());
|
||||
}
|
||||
/** 抽卡按钮取消 → 恢复缩放 */
|
||||
private onDrawTouchCancel() {
|
||||
this.playButtonResetAnim(this.cards_chou);
|
||||
this.playButtonResetAnim(this.cards_refresh);
|
||||
}
|
||||
|
||||
// ======================== 新手按钮指引 ========================
|
||||
@@ -1214,24 +1237,21 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 英雄面板按钮回调:
|
||||
* 英雄面板(herosPanNode)为常驻基准面板,固定不动;
|
||||
* 点击按钮收起装备/技能/药品覆盖面板,并显示英雄抽卡面板(cardPanNode)。
|
||||
* 英雄招募按钮:每次都展开三选一面板,玩家选 1;
|
||||
* 开局免费三选一次数未用完时不扣面板开启费。
|
||||
* 满员(HERO_MAX_NUM)时气泡提示并拦截,不扣费、不计数。
|
||||
*/
|
||||
private onShowHerosClick() {
|
||||
oops.audio.playEffect("music/button");
|
||||
// 已展开则不重复开启(不重复扣费)
|
||||
if (this.isCardPanelShown) return;
|
||||
// 英雄槽位已满:气泡提示并中止,不再展开三选一,也不扣开启费用
|
||||
// 满员拦截
|
||||
if (this.getAliveHeroCount() >= this.getMissionHeroMaxNum()) {
|
||||
this.showSmallTip("panel_hero_full", "英雄槽已满");
|
||||
return;
|
||||
}
|
||||
// 开启英雄抽卡面板需支付金币,金币不足则提示并中止
|
||||
if (!this.tryPayPanelOpenCost(0)) return;
|
||||
// 收起装备/技能/药品覆盖面板
|
||||
|
||||
// 开局免费次数内不扣面板开启费
|
||||
if (this.openingFreePicks <= 0 && !this.tryPayPanelOpenCost(0)) return;
|
||||
this.slideToPanel(0);
|
||||
// 显示英雄抽卡面板(缩放 0→1,并点亮按钮 active 高亮)
|
||||
this.showCardsPanel();
|
||||
}
|
||||
|
||||
@@ -1244,8 +1264,12 @@ export class MissionCardComp extends CCComp {
|
||||
* 3. 多余槽位清空数据并隐藏(默认全部隐藏,召唤一个激活一个)。
|
||||
*
|
||||
* Why: 英雄召唤 / 死亡 / 卖出 / 升级后调用,保证面板与场上一一对应。
|
||||
*
|
||||
* 备注:当前版本暂不启用 herobox 同步显示英雄信息(后续可能恢复),
|
||||
* 方法体已整体注释,所有调用点也已注释。恢复时取消方法体与调用点注释。
|
||||
*/
|
||||
private refreshHeroBoxSlots() {
|
||||
/* 暂不启用 herobox 同步,保留实现备查
|
||||
if (!this.herosBoxNode || !this.herosBoxNode.isValid) return;
|
||||
|
||||
this.ensureHeroBoxSlots();
|
||||
@@ -1276,6 +1300,7 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
// 列表高度只按可见槽位(存活英雄数)计算,空槽位已隐藏不再占位
|
||||
this.resizeListBox(this.herosBoxNode, actors.length);
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1752,10 +1777,10 @@ export class MissionCardComp extends CCComp {
|
||||
// ======================== 核心业务:抽卡 ========================
|
||||
|
||||
/**
|
||||
* 抽卡按钮核心逻辑:
|
||||
* 1. 检查金币是否足够 → 不够则 toast 提示。
|
||||
* 刷新卡池按钮核心逻辑:
|
||||
* 1. 检查金币/刷新石是否足够 → 不够则 toast 提示。
|
||||
* 2. 扣除费用、播放金币动画。
|
||||
* 3. 重新布局槽位 → 从卡池构建 3 张卡 → 分发到槽位。
|
||||
* 3. 重新布局槽位 → 从卡池构建 3 张卡 → 分发到槽位(三选一展示)。
|
||||
*/
|
||||
private onClickDraw() {
|
||||
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
|
||||
@@ -1770,7 +1795,7 @@ export class MissionCardComp extends CCComp {
|
||||
return;
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "click draw", {
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "click refresh", {
|
||||
cost,
|
||||
leftCoin: MissionEconomy.getCoin()
|
||||
});
|
||||
@@ -1779,6 +1804,14 @@ export class MissionCardComp extends CCComp {
|
||||
this.dispatchCardsToSlots(cards);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建随机召唤卡池:全英雄卡。
|
||||
* @returns 卡配置数组
|
||||
*/
|
||||
private buildRandomSummonPool(): CardConfig[] {
|
||||
return CardPoolList.filter(c => c.type === CardType.Hero);
|
||||
}
|
||||
|
||||
// ======================== 阶段切换 ========================
|
||||
|
||||
/** 缓存卡牌面板的基准缩放值(从场景初始状态读取,仅缓存一次) */
|
||||
@@ -1852,15 +1885,13 @@ export class MissionCardComp extends CCComp {
|
||||
*
|
||||
* 规则:
|
||||
* 1. 从基础英雄卡池按权重抽取,同一次抽卡内英雄不重复。
|
||||
* 2. 过滤掉场上已召唤的英雄(已召唤的英雄不再刷出,仅通过升级卡升级)。
|
||||
* 2. 不过滤已召唤英雄:抽到同 uuid 可触发二合一合成升级。
|
||||
* 3. 不足 3 张时循环补齐(允许重复)。
|
||||
*
|
||||
* 注:升级卡机制已移除,英雄卡池只出普通英雄卡。
|
||||
*/
|
||||
private buildDrawCards(): CardConfig[] {
|
||||
const heroCards = CardPoolList.filter(c => c.type === CardType.Hero);
|
||||
const aliveHeroUuids = this.getAliveHeroUuids();
|
||||
const availableHeroCards = heroCards.filter(c => !aliveHeroUuids.has(c.uuid));
|
||||
const availableHeroCards = CardPoolList.filter(c => c.type === CardType.Hero);
|
||||
|
||||
if (availableHeroCards.length === 0) return [];
|
||||
|
||||
@@ -1903,12 +1934,10 @@ export class MissionCardComp extends CCComp {
|
||||
heroType,
|
||||
unique: true, // 保证一次刷新内的英雄卡不重复
|
||||
});
|
||||
// 过滤掉场上已召唤英雄的普通英雄卡(已召唤的英雄不再重复刷出)
|
||||
const aliveHeroUuids = this.getAliveHeroUuids();
|
||||
const available = cards.filter(c => !aliveHeroUuids.has(c.uuid));
|
||||
if (available.length <= 0) return false;
|
||||
// 不过滤已召唤英雄:抽到同 uuid 可触发二合一合成升级
|
||||
if (cards.length <= 0) return false;
|
||||
this.layoutCardSlots();
|
||||
this.dispatchCardsToSlots(available.slice(0, 3));
|
||||
this.dispatchCardsToSlots(cards.slice(0, 3));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2029,7 +2058,7 @@ export class MissionCardComp extends CCComp {
|
||||
private updateDrawCostUI() {
|
||||
const stones = MissionEconomy.getRefreshStone();
|
||||
const stoneCost = MissionEconomy.getRefreshStoneCost();
|
||||
this.updateOneRefreshBtnUI(this.cards_chou, stones, stoneCost);
|
||||
this.updateOneRefreshBtnUI(this.cards_refresh, stones, stoneCost);
|
||||
this.updateOneRefreshBtnUI(this.equip_refresh, stones, stoneCost);
|
||||
this.updateOneRefreshBtnUI(this.shop_refresh, stones, stoneCost);
|
||||
this.updateOneRefreshBtnUI(this.skill_refresh, stones, stoneCost);
|
||||
@@ -2163,22 +2192,6 @@ export class MissionCardComp extends CCComp {
|
||||
return actors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场上所有存活英雄的 hero_uuid 集合。
|
||||
* 用于抽卡时过滤:已召唤的英雄不再从普通英雄卡池中刷出。
|
||||
*/
|
||||
private getAliveHeroUuids(): Set<number> {
|
||||
const uuids = new Set<number>();
|
||||
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
|
||||
const model = entity.get(HeroAttrsComp);
|
||||
if (!model) return;
|
||||
if (model.fac !== FacSet.HERO) return;
|
||||
if (model.is_dead) return;
|
||||
uuids.add(model.hero_uuid);
|
||||
});
|
||||
return uuids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 eid 精确升级场上对应英雄实体。
|
||||
*
|
||||
@@ -2343,7 +2356,7 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||
reset() {
|
||||
this.resetButtonScale(this.cards_chou);
|
||||
this.resetButtonScale(this.cards_refresh);
|
||||
|
||||
// 关键:在 reset/销毁 时将 Map 置空,彻底切断引用
|
||||
this.cardComps = [];
|
||||
|
||||
@@ -48,7 +48,7 @@ import { Tooltip } from "../skill/Tooltip";
|
||||
import { Timer } from "db://oops-framework/core/common/timer/Timer";
|
||||
import { FieldSkillType } from "../common/config/SkillSet";
|
||||
import { FieldSkillHelper } from "../hero/FieldSkillHelper";
|
||||
import { spawningEngine, MAX_WAVE, DynamicTuner } from "./RogueConfig";
|
||||
import { spawningEngine, WAVE_DURATION, BATTLE_TIMEOUT } from "./RogueConfig";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/** 任务(关卡)生命周期阶段 */
|
||||
@@ -82,11 +82,6 @@ export class MissionComp extends CCComp {
|
||||
|
||||
// ======================== 配置参数 ========================
|
||||
|
||||
/** 怪物数量上限(超过后暂停刷怪) */
|
||||
private maxMonsterCount: number = 80;
|
||||
/** 怪物数量恢复阈值(降至此值以下恢复刷怪) */
|
||||
private resumeMonsterCount: number = 45;
|
||||
|
||||
// ======================== 编辑器绑定节点 ========================
|
||||
|
||||
/** 开始战斗按钮 */
|
||||
@@ -128,8 +123,12 @@ export class MissionComp extends CCComp {
|
||||
PhaseTime: Timer = new Timer(1)
|
||||
/** 回合间倒计时(秒) */
|
||||
private waveCountdown: number = 0;
|
||||
/** 回合间倒计时总时长(秒) */
|
||||
private readonly WAVE_COUNTDOWN_DURATION: number = 5;
|
||||
/** 倒计时档位:快清场(压缩垃圾时间,奖励强 build) */
|
||||
private static readonly COUNTDOWN_FAST = 2.5;
|
||||
/** 倒计时档位:普通 */
|
||||
private static readonly COUNTDOWN_NORMAL = 4.0;
|
||||
/** 倒计时档位:有英雄死亡(保留满运营窗口:调整阵型/复活/买卡) */
|
||||
private static readonly COUNTDOWN_FULL = 5.0;
|
||||
/** 上一次显示的时间字符串(避免重复设置) */
|
||||
private lastTimeStr: string = "";
|
||||
/** 上一次显示的秒数(避免重复计算) */
|
||||
@@ -160,6 +159,12 @@ export class MissionComp extends CCComp {
|
||||
private currentWave: number = 0;
|
||||
/** 是否为Boss回合 */
|
||||
private isBossWave: boolean = false;
|
||||
/** 超时回合已在 onBattleTimeout 预扣留存分,BattleEnd 需跳过重复累加 */
|
||||
private skipRemainScoreOnBattleEnd: boolean = false;
|
||||
/** 上回合英雄死亡数快照(BattleEnd 写入,供下一回合倒计时档位判定) */
|
||||
private lastWaveDeathCount: number = 0;
|
||||
/** 上回合计时口径清场时间快照(含清场加速还原,供下一回合倒计时档位判定) */
|
||||
private lastWaveClearTime: number = 0;
|
||||
/** 当前任务阶段 */
|
||||
public currentPhase: MissionPhase = MissionPhase.None;
|
||||
/** 是否处于回合间倒计时状态 */
|
||||
@@ -186,8 +191,8 @@ export class MissionComp extends CCComp {
|
||||
this.on(GameEvent.NewWave, this.onNewWave, this)
|
||||
this.on(GameEvent.DO_AD_BACK, this.do_ad, this)
|
||||
this.start_btn?.on(NodeEventType.TOUCH_END, this.onStartFightBtnClick, this)
|
||||
// 第一回合准备阶段,首个英雄召唤后自动开始战斗(无需玩家点击开始按钮)
|
||||
oops.message.on(GameEvent.MasterCalled, this.onHeroSummoned, this);
|
||||
// 开局免费三选一选完后自动进入战斗
|
||||
oops.message.on("OpeningPicksDone", this.onOpeningPicksDone, this);
|
||||
this.removeMemoryPanel()
|
||||
}
|
||||
|
||||
@@ -209,7 +214,7 @@ export class MissionComp extends CCComp {
|
||||
if (this.start_btn && this.start_btn.isValid) {
|
||||
this.start_btn.off(NodeEventType.TOUCH_END, this.onStartFightBtnClick, this);
|
||||
}
|
||||
oops.message.off(GameEvent.MasterCalled, this.onHeroSummoned, this);
|
||||
oops.message.off("OpeningPicksDone", this.onOpeningPicksDone, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,6 +253,11 @@ export class MissionComp extends CCComp {
|
||||
smc.vmdata.mission_data.fight_time += dt
|
||||
this.clearTime += dt
|
||||
this.update_time();
|
||||
|
||||
// 战斗超时兜底:僵局(坦克 vs 高血 Boss 互磨不死)时强制结算整局
|
||||
if (this.clearTime >= BATTLE_TIMEOUT) {
|
||||
this.onBattleTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,13 +285,20 @@ export class MissionComp extends CCComp {
|
||||
|
||||
// ======================== 回合倒计时 ========================
|
||||
|
||||
/** 进入回合间倒计时:重置倒计时并显示提示 */
|
||||
/** 进入回合间倒计时:按上回合战况分档(快清场压缩垃圾时间,有死亡保留运营窗口) */
|
||||
private startWaveCountdown() {
|
||||
this.waveCountdown = this.WAVE_COUNTDOWN_DURATION;
|
||||
this.waveCountdown = this.computeCountdown();
|
||||
this.isWaveCountdown = true;
|
||||
this.updateCountdownUI(true);
|
||||
}
|
||||
|
||||
/** 计算下一回合倒计时档位:读 BattleEnd 写入的快照(onNewWave 会清零 clearTime,不能现读) */
|
||||
private computeCountdown(): number {
|
||||
if (this.lastWaveDeathCount > 0) return MissionComp.COUNTDOWN_FULL;
|
||||
if (this.lastWaveClearTime > 0 && this.lastWaveClearTime < WAVE_DURATION * 0.65) return MissionComp.COUNTDOWN_FAST;
|
||||
return MissionComp.COUNTDOWN_NORMAL;
|
||||
}
|
||||
|
||||
/** 更新倒计时 UI(显示剩余秒数) */
|
||||
private updateCountdownUI(force: boolean = false) {
|
||||
if (!this.isWaveCountdown) return;
|
||||
@@ -491,12 +508,21 @@ export class MissionComp extends CCComp {
|
||||
|
||||
case MissionPhase.PrepareEnd:
|
||||
// 不隐藏开始按钮
|
||||
// 开闸刷怪:MissionMonComp 在本阶段启动分批释放,需解除暂停标志让第一批按时出场
|
||||
smc.mission.stop_spawn_mon = false;
|
||||
oops.message.dispatchEvent("PhasePrepareEnd");
|
||||
break;
|
||||
|
||||
case MissionPhase.BattleStart:
|
||||
// 触发战斗开始技能(fstart)
|
||||
this.triggerHeroBattleSkills(true);
|
||||
// Boss 预警:Boss 同步场立即登场(eta=0)
|
||||
if (this.isBossWave) {
|
||||
oops.message.dispatchEvent(GameEvent.BossWarning, {
|
||||
wave: this.currentWave,
|
||||
eta: 0,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case MissionPhase.Battle:
|
||||
@@ -520,40 +546,40 @@ export class MissionComp extends CCComp {
|
||||
// 【评分系统 - 战绩分】每回合胜利加分
|
||||
smc.vmdata.scores.wave_win_count++;
|
||||
// 【评分系统 - 战绩分】记录每回合结束时场上留存的敌人数量(扣分项)
|
||||
smc.vmdata.scores.wave_remain_monsters += smc.vmdata.mission_data.mon_num;
|
||||
// 超时回合已在 onBattleTimeout 预扣,跳过防重
|
||||
if (!this.skipRemainScoreOnBattleEnd) {
|
||||
smc.vmdata.scores.wave_remain_monsters += smc.vmdata.mission_data.mon_num;
|
||||
}
|
||||
this.skipRemainScoreOnBattleEnd = false;
|
||||
|
||||
let allAlive = true;
|
||||
let hasHero = false;
|
||||
let heroDeathCount = 0;
|
||||
ecs.query(this.heroAttrsMatcher).forEach(entity => {
|
||||
const attrs = entity.get(HeroAttrsComp);
|
||||
if (attrs && attrs.fac === FacSet.HERO) {
|
||||
hasHero = true;
|
||||
if (attrs.is_dead) {
|
||||
allAlive = false;
|
||||
heroDeathCount++;
|
||||
}
|
||||
}
|
||||
});
|
||||
// 英雄为真实死亡(实体已销毁),本回合死亡数取死亡记录长度
|
||||
const heroDeathCount = smc.mission.dead_heroes.length;
|
||||
if (heroDeathCount > 0) allAlive = false;
|
||||
|
||||
// 【动态难度调节】根据本回合战况自动放水 / 加压
|
||||
DynamicTuner.adjust(this.clearTime, heroDeathCount);
|
||||
mLogger.log(this.debugMode, 'MissionComp', `[DynamicTuner] wave=${this.currentWave} clearTime=${this.clearTime.toFixed(1)}s deaths=${heroDeathCount} factor=${DynamicTuner.factor.toFixed(2)}`);
|
||||
// 快照供倒计时档位判定(onNewWave 会清零 clearTime,必须提前快照)
|
||||
this.lastWaveDeathCount = heroDeathCount;
|
||||
this.lastWaveClearTime = this.clearTime;
|
||||
// 【评分系统 - 战绩分】记录全员存活的胜利回合数(额外加分)
|
||||
if (hasHero && allAlive) {
|
||||
smc.vmdata.scores.wave_all_alive_count++;
|
||||
}
|
||||
|
||||
// 【评分系统 - 战绩分】判断是否通过最后一关(第20回合)
|
||||
if (this.currentWave === MAX_WAVE) {
|
||||
smc.vmdata.scores.passed_wave_20 = true;
|
||||
}
|
||||
// 【评分系统 - 战绩分】单场 Boss 战定胜负,通过即视为通关
|
||||
smc.vmdata.scores.passed_wave_20 = true;
|
||||
}
|
||||
|
||||
// 触发战斗结束技能(fend)
|
||||
this.triggerHeroBattleSkills(false);
|
||||
|
||||
// 战斗结束阶段,给予所有英雄恢复70%血量的技能效果
|
||||
// 战斗结束阶段,按 FightSet.WAVE_HEAL_RATE 恢复所有英雄血量
|
||||
this.healAllHeroes();
|
||||
|
||||
// 【新增】派发每回合战斗结束事件,供卡牌技能监听(区别于整局结束的 MissionEnd)
|
||||
@@ -674,7 +700,7 @@ export class MissionComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 战斗结束阶段治疗所有英雄(包括墓地英雄),恢复70%最大生命值
|
||||
* 战斗结束阶段治疗所有英雄(包括墓地英雄),按 FightSet.WAVE_HEAL_RATE 恢复最大生命值比例
|
||||
*/
|
||||
private healAllHeroes() {
|
||||
const healRateBoost = FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.WaveHeal);
|
||||
@@ -710,13 +736,8 @@ export class MissionComp extends CCComp {
|
||||
this.to_fight();
|
||||
}
|
||||
|
||||
/**
|
||||
* 英雄召唤事件回调:
|
||||
* 第一回合准备阶段(Prepare)只有第 1 回合才会进入,
|
||||
* 此时首个英雄一旦召唤即自动开战,无需玩家点击开始按钮。
|
||||
* 后续回合走 PrepareStart 倒计时自动流转,不会进入 Prepare。
|
||||
*/
|
||||
private onHeroSummoned() {
|
||||
/** 开局免费三选一选完,自动进入战斗(等效于点击开始按钮) */
|
||||
private onOpeningPicksDone() {
|
||||
if (this.currentPhase !== MissionPhase.Prepare) return;
|
||||
if (!smc.mission.play || smc.mission.pause) return;
|
||||
this.to_fight();
|
||||
@@ -784,10 +805,12 @@ export class MissionComp extends CCComp {
|
||||
this.clearTime = 0
|
||||
smc.vmdata.mission_data.mon_num = 0
|
||||
smc.vmdata.mission_data.level = 1
|
||||
smc.vmdata.mission_data.mon_max = Math.max(1, Math.floor(this.maxMonsterCount))
|
||||
this.currentPhase = MissionPhase.None;
|
||||
this.currentWave = 1;
|
||||
this.isBossWave = false;
|
||||
this.skipRemainScoreOnBattleEnd = false;
|
||||
this.lastWaveDeathCount = 0;
|
||||
this.lastWaveClearTime = 0;
|
||||
this.rewards = []
|
||||
this.revive_times = 1;
|
||||
this.lastTimeStr = "";
|
||||
@@ -833,7 +856,7 @@ export class MissionComp extends CCComp {
|
||||
const wave = Number(data?.wave ?? 0);
|
||||
if (wave <= 0) return;
|
||||
|
||||
this.isBossWave = !!data?.bossWave;
|
||||
this.isBossWave = true; // 单场 Boss 战,恒为 Boss 战(超时阈值/预警走 Boss 档)
|
||||
|
||||
if (wave > 1) {
|
||||
// 在新一回合到来时,先进入 BattleEnd,触发上一回合的战斗结束技能 (fend),2秒后自动进入下一回合的准备阶段
|
||||
@@ -857,21 +880,10 @@ export class MissionComp extends CCComp {
|
||||
|
||||
// ======================== 怪物数量管理 ========================
|
||||
|
||||
/**
|
||||
* 获取怪物数量阈值配置。
|
||||
* @returns { max: 刷怪上限, resume: 恢复刷怪阈值 }
|
||||
*/
|
||||
private getMonsterThresholds(): { max: number; resume: number } {
|
||||
const max = Math.max(1, Math.floor(this.maxMonsterCount));
|
||||
const resume = Math.min(max - 1, Math.max(0, Math.floor(this.resumeMonsterCount)));
|
||||
return { max, resume };
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步怪物刷新状态(降频执行,每 0.2 秒一次):
|
||||
* 1. 遍历所有 HeroAttrsComp 实体,统计怪物和英雄数量。
|
||||
* 2. 检测英雄全灭。
|
||||
* 3. 根据 max/resume 阈值切换 stop_spawn_mon 状态。
|
||||
* 2. 检测英雄全灭 / 怪物全灭。
|
||||
*
|
||||
* @param dt 帧间隔
|
||||
*/
|
||||
@@ -894,29 +906,37 @@ export class MissionComp extends CCComp {
|
||||
});
|
||||
this.handleHeroWipe(heroCount);
|
||||
|
||||
// 怪物全灭检测:如果战斗阶段场上没有任何活着的怪物,且待刷新的怪物队列也为空,直接结束战斗进入下一回合的准备阶段
|
||||
// 怪物全灭检测:一场定胜负,全灭即整局通关,直接进入结算
|
||||
const pendingCount = smc.vmdata.mission_data.pending_mon_num || 0;
|
||||
if (monsterCount === 0 && pendingCount === 0 && smc.mission.play && !smc.mission.pause && this.currentPhase === MissionPhase.Battle) {
|
||||
if (this.currentWave >= MAX_WAVE) {
|
||||
// 20 回合通关
|
||||
this.open_Victory(null, false);
|
||||
} else {
|
||||
oops.message.dispatchEvent("TimeUpAdvanceWave");
|
||||
}
|
||||
// oops.message 全局事件总线:清屏庆祝(横幅/奖励由 BattleBannerComp 消费),需在结算前派发(clearTime 归零前取值)
|
||||
const allAlive = this.checkAllHeroAlive();
|
||||
const fastClear = this.clearTime < WAVE_DURATION * 0.5 ? 2
|
||||
: this.clearTime < WAVE_DURATION * 0.75 ? 1 : 0;
|
||||
oops.message.dispatchEvent(GameEvent.WaveClear, {
|
||||
wave: this.currentWave,
|
||||
clearTime: this.clearTime,
|
||||
allAlive,
|
||||
fastClear,
|
||||
});
|
||||
this.open_Victory(null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
smc.vmdata.mission_data.mon_num = monsterCount;
|
||||
const { max, resume } = this.getMonsterThresholds();
|
||||
smc.vmdata.mission_data.mon_max = max;
|
||||
const stopSpawn = !!smc.mission.stop_spawn_mon;
|
||||
if (stopSpawn) {
|
||||
// 降至恢复阈值以下 → 恢复刷怪
|
||||
if (monsterCount <= resume) smc.mission.stop_spawn_mon = false;
|
||||
return;
|
||||
}
|
||||
// 超过上限 → 暂停刷怪
|
||||
if (monsterCount >= max) smc.mission.stop_spawn_mon = true;
|
||||
}
|
||||
|
||||
/** 检测场上英雄是否全员存活(清屏 Perfect 判定用,独立于评分统计逻辑) */
|
||||
private checkAllHeroAlive(): boolean {
|
||||
let hasHero = false;
|
||||
ecs.query(this.heroAttrsMatcher).forEach(entity => {
|
||||
const attrs = entity.get(HeroAttrsComp);
|
||||
if (attrs && attrs.fac === FacSet.HERO) {
|
||||
hasHero = true;
|
||||
}
|
||||
});
|
||||
// 英雄为真实死亡(实体已销毁),有死亡记录即非全员存活
|
||||
return hasHero && smc.mission.dead_heroes.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -930,6 +950,32 @@ export class MissionComp extends CCComp {
|
||||
this.open_Victory(null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 战斗超时强制结束(僵局兜底):
|
||||
* 1. 预扣留存分(残留怪销毁后 mon_num 会被下一次同步清零,BattleEnd 的累加会漏扣,故在此显式预扣并置防重标志)。
|
||||
* 2. 销毁全部残留怪(不触发 MonDead、不掉金币——超时是对"清不掉"的惩罚)。
|
||||
* 3. 一场定胜负,超时按怪物全灭同等处理——直接结算整局。
|
||||
*/
|
||||
private onBattleTimeout() {
|
||||
// 防重入:只触发一次
|
||||
if (this.currentPhase !== MissionPhase.Battle) return;
|
||||
|
||||
smc.vmdata.scores.wave_remain_monsters += smc.vmdata.mission_data.mon_num;
|
||||
this.skipRemainScoreOnBattleEnd = true;
|
||||
|
||||
ecs.query(this.heroAttrsMatcher).forEach(entity => {
|
||||
const attrs = entity.get(HeroAttrsComp);
|
||||
if (attrs && attrs.fac === FacSet.MON && !attrs.is_dead) {
|
||||
entity.destroy();
|
||||
}
|
||||
});
|
||||
smc.vmdata.mission_data.mon_num = 0;
|
||||
|
||||
mLogger.log(this.debugMode, 'MissionComp', `[BattleTimeout] 超时强制结算`);
|
||||
|
||||
this.open_Victory(null, false);
|
||||
}
|
||||
|
||||
// ======================== 清理 ========================
|
||||
|
||||
/** 清理所有英雄和技能 ECS 实体 */
|
||||
@@ -959,21 +1005,17 @@ export class MissionComp extends CCComp {
|
||||
this.clearBattleSceneNodes();
|
||||
}
|
||||
|
||||
/** 清理战斗场景中的 HERO 和 SKILL 根节点下的所有子节点 */
|
||||
/** 清理战斗场景中的 HERO/MON/BOSS 和 SKILL 根节点下的所有子节点 */
|
||||
private clearBattleSceneNodes() {
|
||||
const scene = smc.map?.MapView?.scene;
|
||||
const layer = scene?.entityLayer?.node;
|
||||
if (!layer) return;
|
||||
const heroRoot = layer.getChildByName("HERO");
|
||||
const skillRoot = layer.getChildByName("SKILL");
|
||||
if (heroRoot) {
|
||||
for (let i = heroRoot.children.length - 1; i >= 0; i--) {
|
||||
heroRoot.children[i].destroy();
|
||||
}
|
||||
}
|
||||
if (skillRoot) {
|
||||
for (let i = skillRoot.children.length - 1; i >= 0; i--) {
|
||||
skillRoot.children[i].destroy();
|
||||
for (const name of ["HERO", "MON", "BOSS", "SKILL"]) {
|
||||
const root = layer.getChildByName(name);
|
||||
if (root) {
|
||||
for (let i = root.children.length - 1; i >= 0; i--) {
|
||||
root.children[i].destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,17 @@
|
||||
*
|
||||
* 关键设计:
|
||||
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**。
|
||||
* - handleSingleSummon() 仅负责生成英雄;英雄升级由升级卡系统(MissionCardComp)独立处理。
|
||||
* - handleSingleSummon() 负责生成英雄;同 uuid 重复召唤走二合一合成升级。
|
||||
*
|
||||
* 历史:
|
||||
* 旧版本曾包含"三合一合成 + 链式合成"机制,已移除。
|
||||
* 英雄等级提升现在仅通过升级卡(SpecialUpgrade)实现。
|
||||
* 合成规则:
|
||||
* 场上存在同 uuid 英雄时再次召唤触发二合一:
|
||||
* 旧英雄移动合并销毁,新英雄以 **两者最高等级 + 1** 落地(封顶 HERO_MAX_LV),
|
||||
* 等级已达上限时拒绝召唤。英雄也可通过升级卡(SpecialUpgrade)升级。
|
||||
*
|
||||
* 死亡机制:
|
||||
* 英雄为真实死亡(HeroAtkSystem.doDead → 快照至 smc.mission.dead_heroes → 实体销毁),
|
||||
* 本组件不做回合自动复活;复活由后续复活机制读取 dead_heroes 重新召唤。
|
||||
* revive 系技能为"濒死回血"(血量归零时消耗次数回血续命),不触发真死。
|
||||
*
|
||||
* 依赖:
|
||||
* - Hero(hero/Hero.ts)—— 英雄 ECS 实体类
|
||||
@@ -47,14 +53,16 @@ const { ccclass } = _decorator;
|
||||
export class MissionHeroComp extends CCComp {
|
||||
// ======================== 常量 ========================
|
||||
|
||||
/** 硬编码的6个英雄占位点 */
|
||||
/**
|
||||
* 英雄登场占位点(固定 3 列):
|
||||
* - index 0/1/2 分别对应 x=-320/-260/-200。
|
||||
* - 分配规则:近战优先靠前(x 更大),远程/中程优先靠后(x 更小);
|
||||
* 同类型按召唤先后从该类型的一侧开始依次填充。
|
||||
*/
|
||||
public static readonly HERO_POSITIONS: Vec3[] = [
|
||||
v3(-220, BoxSet.GAME_LINE, 0), // index 0 (node_index 1): Top Front 第二
|
||||
v3(-140, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front 第一
|
||||
v3(-300, BoxSet.GAME_LINE, 0), // index 2 (node_index 3): Bot Front 第三
|
||||
// v3(-280, BoxSet.GAME_LINE + 100, 0), // index 3 (node_index 4): Top Back
|
||||
// v3(-280, BoxSet.GAME_LINE, 0), // index 4 (node_index 5): Mid Back
|
||||
// v3(-280, BoxSet.GAME_LINE - 100, 0), // index 5 (node_index 6): Bot Back
|
||||
v3(-320, BoxSet.GAME_LINE, 0),
|
||||
v3(-260, BoxSet.GAME_LINE, 0),
|
||||
v3(-200, BoxSet.GAME_LINE, 0),
|
||||
];
|
||||
|
||||
/** 英雄出生时的掉落高度(从空中落到地面的像素差) */
|
||||
@@ -101,34 +109,28 @@ export class MissionHeroComp extends CCComp {
|
||||
|
||||
// ======================== 事件处理 ========================
|
||||
|
||||
/** 关卡结束时清理全部英雄 ECS 实体 */
|
||||
/** 关卡结束时清理全部存活英雄 ECS 实体,并清空本局死亡英雄记录 */
|
||||
clear_heros() {
|
||||
const heroes = this.getAllHeroes();
|
||||
for (let i = 0; i < heroes.length; i++) {
|
||||
heroes[i].destroy();
|
||||
}
|
||||
// 整局结束,死亡记录失效(复活机制仅限局内)
|
||||
smc.mission.dead_heroes = [];
|
||||
}
|
||||
|
||||
/** 战斗准备阶段:重置出战英雄计数,恢复满血重新登场 */
|
||||
/**
|
||||
* 战斗准备阶段:刷新存活英雄计数与血条。
|
||||
*
|
||||
* 说明:英雄为真实死亡(实体已销毁,快照存于 smc.mission.dead_heroes),
|
||||
* 本阶段不再做回合自动复活;复活由后续复活机制(重新召唤)显式触发。
|
||||
*/
|
||||
fight_ready() {
|
||||
const heroes = this.getAllHeroes();
|
||||
smc.vmdata.mission_data.hero_num = heroes.length;
|
||||
for (let i = 0; i < heroes.length; i++) {
|
||||
const hero = heroes[i];
|
||||
const model = hero.get(HeroAttrsComp);
|
||||
const view = hero.get(HeroViewComp);
|
||||
if (model && view) {
|
||||
if (model.is_dead) {
|
||||
view.alive();
|
||||
const posIndex = this.pickPositionIndexForHero([hero.eid]);
|
||||
const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
|
||||
// 计算出生点(空中)
|
||||
const spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
|
||||
view.node.setPosition(spawnPos);
|
||||
model.posIndex = posIndex;
|
||||
if (posIndex >= 0) smc.mission.heroGrid[posIndex] = hero.eid;
|
||||
hero.playDropAnim(spawnPos, landingPos.y);
|
||||
}
|
||||
const model = heroes[i].get(HeroAttrsComp);
|
||||
if (model) {
|
||||
model.dirty_hp = true;
|
||||
}
|
||||
}
|
||||
@@ -142,7 +144,8 @@ export class MissionHeroComp extends CCComp {
|
||||
/**
|
||||
* 召唤请求入口:
|
||||
* 从事件参数中提取 uuid / hero_lv / card_lv,放入串行队列。
|
||||
* 防御:已召唤的英雄 uuid 拒绝重复召唤(只能通过升级卡升级)。
|
||||
* 二合一:同 uuid 已召唤且等级未达上限时允许入队,由 handleSingleSummon 执行合成;
|
||||
* 同 uuid 且等级已达上限时拒绝(只能通过升级卡继续提升之外无法再合成)。
|
||||
*
|
||||
* @param event 事件名
|
||||
* @param args { uuid, hero_lv, card_lv }
|
||||
@@ -153,10 +156,14 @@ export class MissionHeroComp extends CCComp {
|
||||
const hero_lv = Math.max(1, Number(payload?.hero_lv ?? 1));
|
||||
const card_lv = Math.max(1, Number(payload?.card_lv ?? 1));
|
||||
|
||||
// 防御:场上已有同 uuid 的存活英雄时,拒绝重复召唤
|
||||
// 二合一前置校验:同 uuid 已存在且合成后等级会超上限时,直接拒绝
|
||||
if (this.isHeroAlreadySummoned(uuid)) {
|
||||
oops.gui.toast(`该英雄已召唤,只能通过升级卡升级`);
|
||||
return;
|
||||
const existing = this.findHeroByUuid(uuid);
|
||||
const existingLv = existing ? (existing.get(HeroAttrsComp)?.lv ?? 1) : 1;
|
||||
if (Math.max(existingLv, hero_lv) + 1 > FightSet.HERO_MAX_LV) {
|
||||
oops.gui.toast(`该英雄已达等级上限,无法继续合成`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.summon_queue.push({ uuid, hero_lv, card_lv });
|
||||
@@ -169,14 +176,23 @@ export class MissionHeroComp extends CCComp {
|
||||
* @returns true 表示场上已有该英雄,不可重复召唤
|
||||
*/
|
||||
private isHeroAlreadySummoned(uuid: number): boolean {
|
||||
let exists = false;
|
||||
return this.findHeroByUuid(uuid) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找场上指定 uuid 的存活英雄实体。
|
||||
* @param uuid 英雄模板 uuid
|
||||
* @returns 命中的 Hero 实体,未命中返回 null
|
||||
*/
|
||||
private findHeroByUuid(uuid: number): Hero | null {
|
||||
let found: Hero | null = null;
|
||||
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
|
||||
if (exists) return;
|
||||
if (found) return;
|
||||
const model = entity.get(HeroAttrsComp);
|
||||
if (!model || model.fac !== FacSet.HERO || model.is_dead) return;
|
||||
if (model.hero_uuid === uuid) exists = true;
|
||||
if (model.hero_uuid === uuid) found = entity as Hero;
|
||||
});
|
||||
return exists;
|
||||
return found;
|
||||
}
|
||||
|
||||
// ======================== 英雄生成 ========================
|
||||
@@ -185,7 +201,7 @@ export class MissionHeroComp extends CCComp {
|
||||
* 动态分配英雄上场的位置
|
||||
* @param excludeEids 排除计算的实体ID数组(避免复活时把自己算成占据的位置)
|
||||
*/
|
||||
private pickPositionIndexForHero(excludeEids: number[] = []): number {
|
||||
private pickPositionIndexForHero(excludeEids: number[] = [], heroType: HType = HType.Melee): number {
|
||||
const heroes = this.getAllHeroes().filter(h => {
|
||||
const m = h.get(HeroAttrsComp);
|
||||
return m && !m.is_dead && !excludeEids.includes(h.eid);
|
||||
@@ -197,16 +213,18 @@ export class MissionHeroComp extends CCComp {
|
||||
if (m && m.posIndex >= 0) occupied.add(m.posIndex);
|
||||
}
|
||||
|
||||
// 优先中前(1) -> 上前(0) -> 下前(2) -> 中后(4) -> 上后(3) -> 下后(5)
|
||||
const slotPriority = [1, 0, 2, 4, 3, 5];
|
||||
// 近战优先靠前(x 更大,索引更大);远程/中程优先靠后(x 更小,索引更小)
|
||||
const slotPriority = heroType === HType.Melee
|
||||
? [2, 1, 0]
|
||||
: [0, 1, 2];
|
||||
for (const idx of slotPriority) {
|
||||
if (!occupied.has(idx)) {
|
||||
if (!occupied.has(idx) && MissionHeroComp.HERO_POSITIONS[idx]) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
// 溢出:默认中前
|
||||
return 1;
|
||||
// 溢出兜底:复用 index 0(坐标有效,避免越界崩溃)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,12 +238,28 @@ export class MissionHeroComp extends CCComp {
|
||||
* @returns 创建的 Hero 实体
|
||||
*/
|
||||
private addHero(uuid: number = 1001, hero_lv: number = 1, card_lv: number = 1) {
|
||||
return this.spawnHeroAt(uuid, hero_lv, card_lv, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定站位生成英雄:
|
||||
* - posIndex < 0 时自动分配空位。
|
||||
* - 计算出生点(空中)和落点(地面),播放掉落动画。
|
||||
*
|
||||
* @param uuid 英雄 UUID
|
||||
* @param hero_lv 英雄等级
|
||||
* @param card_lv 卡牌等级(驱动 bg_node 颜色)
|
||||
* @param posIndex 指定站位索引,-1 表示自动分配
|
||||
* @returns 创建的 Hero 实体
|
||||
*/
|
||||
private spawnHeroAt(uuid: number, hero_lv: number, card_lv: number, posIndex: number) {
|
||||
let hero = ecs.getEntity<Hero>(Hero);
|
||||
let scale = 1
|
||||
const posIndex = this.pickPositionIndexForHero();
|
||||
const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
|
||||
const finalPosIndex = posIndex >= 0 ? posIndex : this.pickPositionIndexForHero([], HeroInfo[uuid]?.type ?? HType.Melee);
|
||||
// 兜底:索引越界或点位缺失时复用 index 0,保证 landingPos 一定有效
|
||||
const landingPos = MissionHeroComp.HERO_POSITIONS[finalPosIndex] ?? MissionHeroComp.HERO_POSITIONS[0];
|
||||
let spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
|
||||
hero.load(spawnPos, scale, uuid, landingPos.y, hero_lv, card_lv, posIndex);
|
||||
hero.load(spawnPos, scale, uuid, landingPos.y, hero_lv, card_lv, finalPosIndex);
|
||||
|
||||
// 召唤完成后,派发事件以更新英雄面板
|
||||
const model = hero.get(HeroAttrsComp);
|
||||
@@ -275,14 +309,56 @@ export class MissionHeroComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单次召唤:仅生成英雄,不再触发合成。
|
||||
* 处理单次召唤:
|
||||
* - 场上无同 uuid 英雄:直接生成新英雄。
|
||||
* - 场上已有同 uuid 英雄:触发二合一,旧英雄合并销毁后生成等级 +1 的新英雄。
|
||||
*
|
||||
* @param uuid 英雄 UUID
|
||||
* @param hero_lv 英雄等级
|
||||
* @param card_lv 卡牌等级
|
||||
*/
|
||||
private async handleSingleSummon(uuid: number, hero_lv: number, card_lv: number = 1) {
|
||||
this.addHero(uuid, hero_lv, card_lv);
|
||||
const existing = this.findHeroByUuid(uuid);
|
||||
if (!existing) {
|
||||
this.addHero(uuid, hero_lv, card_lv);
|
||||
return;
|
||||
}
|
||||
|
||||
const oldModel = existing.get(HeroAttrsComp);
|
||||
const oldLv = oldModel?.lv ?? 1;
|
||||
// 合成后等级 = 两者最高等级 + 1,封顶 HERO_MAX_LV
|
||||
const mergedLv = Math.min(FightSet.HERO_MAX_LV, Math.max(oldLv, hero_lv) + 1);
|
||||
// 继承旧英雄站位,避免新英雄重新抢位导致阵型跳动
|
||||
const posIndex = oldModel?.posIndex ?? -1;
|
||||
|
||||
await this.mergeHeroAndRespawn(existing, uuid, mergedLv, card_lv, posIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 二合一合成流程:
|
||||
* 1. 旧英雄播放移动合并动画并销毁(释放站位与实体)。
|
||||
* 2. 在原站位生成 mergedLv 级新英雄并播放入场动画。
|
||||
*
|
||||
* @param oldHero 场上已有的同 uuid 英雄
|
||||
* @param uuid 英雄 UUID
|
||||
* @param mergedLv 合成后的英雄等级
|
||||
* @param card_lv 卡牌等级
|
||||
* @param posIndex 继承的站位索引(-1 表示重新分配)
|
||||
*/
|
||||
private mergeHeroAndRespawn(oldHero: Hero, uuid: number, mergedLv: number, card_lv: number, posIndex: number): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
// 以旧英雄节点位置作为合并目标点,保证动画收拢到原站位
|
||||
const oldView = oldHero.get(HeroViewComp);
|
||||
const birthPos = oldView?.node?.isValid
|
||||
? oldView.node.getPosition()
|
||||
: (posIndex >= 0 ? MissionHeroComp.HERO_POSITIONS[posIndex] : MissionHeroComp.HERO_POSITIONS[1]);
|
||||
|
||||
oldHero.mergeToBirthAndDestroy(birthPos, () => {
|
||||
// 旧实体销毁后 posIndex 已释放,直接按原站位生成新英雄
|
||||
this.spawnHeroAt(uuid, mergedLv, card_lv, posIndex);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
/**
|
||||
* @file MissionMonComp.ts
|
||||
* @description 怪物(Monster)回合刷新管理组件(逻辑层)
|
||||
* @description 怪物(Monster)登场管理组件(逻辑层)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 管理每一回合怪物的生成计划:根据 RogueConfig 生成怪物。
|
||||
* 2. 自动推进回合:在准备阶段结束时(PhasePrepareEnd)启动分批释放。
|
||||
* 3. 分批刷怪:每回合固定 3 批,每 10 秒释放一批,批内按 MON_SPAWN_INTERVAL 逐个刷出。
|
||||
* 1. 预生成战斗怪物:FightReady 时通过 RogueSpawningEngine.generateBossCombo 随机一组 Boss 组合,并立即登场展示(供玩家侦查后再选英雄)。
|
||||
* 2. 战斗开始:PhasePrepareEnd 时恢复怪物行动(in_fight 前怪物仅原地待命,不会推进/索敌)。
|
||||
*
|
||||
* 关键设计:
|
||||
* - 所有怪物统一从右侧 X=400 出生点逐个刷出(MON_SPAWN_INTERVAL 节奏控制)。
|
||||
* - 实际阵型与推进由 MonMoveComp 在战斗中向左移动自然形成,不再使用固定网格点。
|
||||
* - 槽位索引(3行×4列)仅用于 monGrid 寻路与 SCastSystem 索敌定位。
|
||||
* - 上一回合残留怪在回合结束/开始时统一清理。
|
||||
* - 5 只怪在 X∈[100,320] 内按登场序号均匀铺开,Boss/小怪同时登场,玩家看清阵容后三选一。
|
||||
* - 实际推进由统一 MoveComp 在战斗中向敌人移动自然形成,不再使用固定网格点。
|
||||
* - 槽位索引仅用于 monGrid 寻路与 SCastSystem 索敌定位。
|
||||
*/
|
||||
import { _decorator, v3, Vec3 } from "cc";
|
||||
import { mLogger } from "../common/Logger";
|
||||
@@ -21,10 +19,10 @@ import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/O
|
||||
import { Monster } from "../hero/Mon";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { BoxSet, FacSet } from "../common/config/GameSet";
|
||||
import { spawningEngine, GeneratedMonster, TestModeConfig, MAX_MONSTERS, BATCH_COUNT, BATCH_INTERVAL } from "./RogueConfig";
|
||||
import { BoxSet } from "../common/config/GameSet";
|
||||
import { spawningEngine, GeneratedMonster, TestModeConfig, BOSS_COUNT, MINION_COUNT } from "./RogueConfig";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { MonMoveComp } from "../hero/MonMoveComp";
|
||||
import { MoveComp } from "../hero/MoveComp";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@@ -35,30 +33,25 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
/** 怪物出生掉落高度 */
|
||||
private static readonly MON_DROP_HEIGHT = 0;
|
||||
/** 怪物统一从右侧 X=400 出生点逐个刷出,随后向左推进 */
|
||||
private static readonly MON_SPAWN_X = 400;
|
||||
/** 逐个刷怪间隔(秒):保证怪物排成纵队,避免堆叠 */
|
||||
private static readonly MON_SPAWN_INTERVAL = 0.3;
|
||||
/** 怪物登场 X 范围下限(靠近英雄侧) */
|
||||
private static readonly SPAWN_X_MIN = 100;
|
||||
/** 怪物登场 X 范围上限(靠近右侧) */
|
||||
private static readonly SPAWN_X_MAX = 320;
|
||||
|
||||
/**
|
||||
* 怪物占位点:所有槽位统一以右侧出生点 (X=400) 作为坐标,
|
||||
* 实际阵型由 MonMoveComp 在战斗中向左推进时自然拉开。
|
||||
* 槽位索引仍保留 3 行 × 4 列结构,供 monGrid 寻路与 SCastSystem 索敌使用。
|
||||
* 超出 12 个槽位的怪物(放松回合)按 spawnIndex % 12 复用槽位。
|
||||
*/
|
||||
/** 槽位基准 Y(3行×4列结构仅供 monGrid 寻路与 SCastSystem 索敌定位) */
|
||||
public static readonly MON_POSITIONS: Vec3[] = [
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 0: Col1-Top
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 1: Col1-Mid
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 2: Col1-Bot
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 3: Col2-Top
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 4: Col2-Mid
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 5: Col2-Bot
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 6: Col3-Top
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 7: Col3-Mid
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 8: Col3-Bot
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 9: Col4-Top
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 10: Col4-Mid
|
||||
v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 11: Col4-Bot
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 0: Col1-Top
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 1: Col1-Mid
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 2: Col1-Bot
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 3: Col2-Top
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 4: Col2-Mid
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 5: Col2-Bot
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 6: Col3-Top
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 7: Col3-Mid
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 8: Col3-Bot
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 9: Col4-Top
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 10: Col4-Mid
|
||||
v3(0, BoxSet.GAME_LINE, 0), // index 11: Col4-Bot
|
||||
];
|
||||
|
||||
// ======================== 编辑器属性 ========================
|
||||
@@ -70,89 +63,30 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
/** 全局生成顺序计数器(用于渲染层级排序) */
|
||||
private globalSpawnOrder: number = 0;
|
||||
/** 当前回合数 */
|
||||
private currentWave: number = 0;
|
||||
/** 当前回合的目标怪物总数 */
|
||||
/** 战斗场次(本局恒为 1,保留供 HUD/事件协议) */
|
||||
private currentWave: number = 1;
|
||||
/** 本场战斗的怪物总数 */
|
||||
private waveTargetCount: number = 0;
|
||||
/** 当前回合已生成的怪物数量 */
|
||||
/** 已登场的怪物数量 */
|
||||
private waveSpawnedCount: number = 0;
|
||||
/** 等待生成的怪物队列(按批次分组,batch 0~2) */
|
||||
private pendingBatches: GeneratedMonster[][] = [[], [], []];
|
||||
/** 当前正在释放的批次索引 */
|
||||
private currentBatch: number = 0;
|
||||
/** 批次释放计时器(秒) */
|
||||
private batchTimer: number = 0;
|
||||
/** 是否正在分批释放中 */
|
||||
private isReleasing: boolean = false;
|
||||
/** 逐个刷怪队列:从当前批次转入,按节奏在 update 中释放 */
|
||||
/** 待登场队列(FightReady 时整组进入,PhasePrepareEnd 时一次全量刷出) */
|
||||
private spawnQueue: GeneratedMonster[] = [];
|
||||
/** 逐个刷怪累计计时(秒) */
|
||||
private spawnTimer: number = 0;
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
onLoad() {
|
||||
this.on(GameEvent.FightReady, this.fight_ready, this);
|
||||
this.on("PhasePrepareEnd", this.onPhasePrepareEnd, this);
|
||||
this.on("TimeUpAdvanceWave", this.onTimeUpAdvanceWave, this);
|
||||
}
|
||||
|
||||
protected update(dt: number): void {
|
||||
// 统计待刷出的怪物总数(未释放的批次 + 正在释放的队列)
|
||||
let pendingCount = this.spawnQueue.length;
|
||||
for (let i = this.currentBatch; i < BATCH_COUNT; i++) {
|
||||
pendingCount += this.pendingBatches[i].length;
|
||||
}
|
||||
smc.vmdata.mission_data.pending_mon_num = pendingCount;
|
||||
|
||||
// 分批释放:按 BATCH_INTERVAL 节奏推进批次
|
||||
if (this.isReleasing) {
|
||||
this.batchTimer += dt;
|
||||
if (this.batchTimer >= BATCH_INTERVAL && this.currentBatch < BATCH_COUNT - 1) {
|
||||
this.batchTimer = 0;
|
||||
this.advanceBatch();
|
||||
}
|
||||
}
|
||||
|
||||
// 逐个刷怪:按 MON_SPAWN_INTERVAL 节奏从队列释放
|
||||
if (this.spawnQueue.length > 0) {
|
||||
this.spawnTimer += dt;
|
||||
if (this.spawnTimer >= MissionMonCompComp.MON_SPAWN_INTERVAL) {
|
||||
this.spawnTimer = 0;
|
||||
const monData = this.spawnQueue.shift()!;
|
||||
const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MON_POSITIONS.length;
|
||||
this.addMonsterAtGrid(targetPosIndex, monData, this.currentWave);
|
||||
this.waveSpawnedCount++;
|
||||
}
|
||||
}
|
||||
// 待登场统计(战斗结束检测与 HUD 进度消费)
|
||||
smc.vmdata.mission_data.pending_mon_num = this.spawnQueue.length;
|
||||
}
|
||||
|
||||
start() {}
|
||||
|
||||
private setupWaveData(monsters: GeneratedMonster[]) {
|
||||
// 按批次分组
|
||||
this.pendingBatches = [[], [], []];
|
||||
for (const m of monsters) {
|
||||
const batch = Math.min(m.batch, BATCH_COUNT - 1);
|
||||
this.pendingBatches[batch].push(m);
|
||||
}
|
||||
|
||||
this.waveTargetCount = monsters.length;
|
||||
smc.vmdata.mission_data.pending_mon_num = this.waveTargetCount;
|
||||
|
||||
let hasBoss = monsters.some(m => m.isBoss);
|
||||
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', `[MissionMonComp] 回合 ${this.currentWave} 生成怪物总数: ${this.waveTargetCount}`);
|
||||
|
||||
oops.message.dispatchEvent(GameEvent.NewWave, {
|
||||
wave: this.currentWave,
|
||||
total: this.waveTargetCount,
|
||||
bossWave: hasBoss,
|
||||
});
|
||||
}
|
||||
start() { }
|
||||
|
||||
/**
|
||||
* 战斗准备:重置所有运行时状态并开始第一回合。
|
||||
* 战斗准备:重置运行时状态并预生成本场 Boss 组合。
|
||||
*/
|
||||
fight_ready() {
|
||||
smc.vmdata.mission_data.mon_num = 0;
|
||||
@@ -161,115 +95,40 @@ export class MissionMonCompComp extends CCComp {
|
||||
this.currentWave = 1;
|
||||
this.waveTargetCount = 0;
|
||||
this.waveSpawnedCount = 0;
|
||||
this.pendingBatches = [[], [], []];
|
||||
this.currentBatch = 0;
|
||||
this.batchTimer = 0;
|
||||
this.isReleasing = false;
|
||||
this.spawnQueue = [];
|
||||
this.spawnTimer = 0;
|
||||
|
||||
// 预生成第一回合数据以获取数量和 Boss 信息
|
||||
const monsters = spawningEngine.generateWave(this.currentWave);
|
||||
this.setupWaveData(monsters);
|
||||
// 随机一组 Boss 组合(最终 Boss 战强度)
|
||||
const monsters = spawningEngine.generateBossCombo();
|
||||
this.spawnQueue = monsters;
|
||||
this.waveTargetCount = monsters.length;
|
||||
smc.vmdata.mission_data.pending_mon_num = this.waveTargetCount;
|
||||
|
||||
// 怪物先行登场:玩家侦查 Boss 组合后再做英雄三选一(in_fight 前怪物原地待命,不推进不索敌)
|
||||
const squad = this.spawnQueue;
|
||||
this.spawnQueue = [];
|
||||
smc.vmdata.mission_data.pending_mon_num = 0;
|
||||
for (const monData of squad) {
|
||||
this.addMonsterAtGrid(this.waveSpawnedCount, monData, this.currentWave);
|
||||
this.waveSpawnedCount++;
|
||||
}
|
||||
|
||||
if (TestModeConfig.enable) {
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] 测试模式已开启");
|
||||
}
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', `[MissionMonComp] Boss 组合生成完成,怪物总数: ${this.waveTargetCount}`);
|
||||
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
|
||||
}
|
||||
|
||||
// ======================== 回合管理 ========================
|
||||
|
||||
/**
|
||||
* 开始下一回合:回合数 +1 并预生成数据
|
||||
*/
|
||||
private onTimeUpAdvanceWave() {
|
||||
this.currentWave += 1;
|
||||
smc.vmdata.mission_data.level = this.currentWave;
|
||||
|
||||
const monsters = spawningEngine.generateWave(this.currentWave);
|
||||
this.setupWaveData(monsters);
|
||||
}
|
||||
|
||||
private onPhasePrepareEnd() {
|
||||
this.resetSlotSpawnData();
|
||||
|
||||
// 准备结束阶段:启动分批释放,
|
||||
// 第一批立即转入 spawnQueue,后续批次由 update 按 BATCH_INTERVAL 推进。
|
||||
this.startBatchRelease();
|
||||
}
|
||||
|
||||
// ======================== 分批释放 ========================
|
||||
|
||||
/** 启动分批释放:立即释放第一批,启动批次计时器 */
|
||||
private startBatchRelease() {
|
||||
this.currentBatch = 0;
|
||||
this.batchTimer = 0;
|
||||
this.isReleasing = true;
|
||||
this.releaseCurrentBatch();
|
||||
}
|
||||
|
||||
/** 推进到下一批次 */
|
||||
private advanceBatch() {
|
||||
if (this.currentBatch >= BATCH_COUNT - 1) {
|
||||
this.isReleasing = false;
|
||||
return;
|
||||
}
|
||||
this.currentBatch++;
|
||||
this.releaseCurrentBatch();
|
||||
}
|
||||
|
||||
/** 将当前批次的怪物转入 spawnQueue,等待逐个刷出 */
|
||||
private releaseCurrentBatch() {
|
||||
const batch = this.pendingBatches[this.currentBatch];
|
||||
if (batch.length === 0) {
|
||||
// 当前批次为空,尝试推进到下一批
|
||||
if (this.currentBatch < BATCH_COUNT - 1) {
|
||||
this.currentBatch++;
|
||||
this.releaseCurrentBatch();
|
||||
} else {
|
||||
this.isReleasing = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 将批次怪物转入逐个刷怪队列
|
||||
for (const m of batch) {
|
||||
this.spawnQueue.push(m);
|
||||
}
|
||||
batch.length = 0;
|
||||
|
||||
// 让首个怪物在下一帧立即刷出,避免额外延迟
|
||||
this.spawnTimer = MissionMonCompComp.MON_SPAWN_INTERVAL;
|
||||
}
|
||||
|
||||
// ======================== 槽位管理 ========================
|
||||
|
||||
/**
|
||||
* 清理上一回合残留怪物,并重置生成计数与逐个刷怪队列
|
||||
*/
|
||||
private resetSlotSpawnData() {
|
||||
ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => {
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
if (attrs && attrs.fac === FacSet.MON && !attrs.is_dead) {
|
||||
e.destroy();
|
||||
}
|
||||
oops.message.dispatchEvent(GameEvent.NewWave, {
|
||||
wave: this.currentWave,
|
||||
total: this.waveTargetCount,
|
||||
bossWave: monsters.some(m => m.isBoss),
|
||||
});
|
||||
|
||||
this.waveSpawnedCount = 0;
|
||||
// 同步丢弃上一回合未释放的队列,避免与新一回合混合
|
||||
this.spawnQueue = [];
|
||||
this.spawnTimer = 0;
|
||||
this.isReleasing = false;
|
||||
this.currentBatch = 0;
|
||||
this.batchTimer = 0;
|
||||
}
|
||||
|
||||
// ======================== 怪物生成 ========================
|
||||
|
||||
/**
|
||||
* 在指定位置索引处生成一个怪物
|
||||
* 5 只怪在 X∈[100,320] 内按登场序号均匀铺开,Boss/小怪同时登场直接开打
|
||||
*/
|
||||
private addMonsterAtGrid(
|
||||
posIndex: number,
|
||||
@@ -280,8 +139,10 @@ export class MissionMonCompComp extends CCComp {
|
||||
let scale = -1;
|
||||
|
||||
const basePos = MissionMonCompComp.MON_POSITIONS[posIndex % MissionMonCompComp.MON_POSITIONS.length];
|
||||
const spawnX = basePos.x;
|
||||
const landingY = basePos.y + (monData.isBoss ? 6 : 0);
|
||||
const spawnX = MissionMonCompComp.SPAWN_X_MIN
|
||||
+ (MissionMonCompComp.SPAWN_X_MAX - MissionMonCompComp.SPAWN_X_MIN)
|
||||
* (posIndex / Math.max(1, BOSS_COUNT + MINION_COUNT - 1));
|
||||
const landingY = basePos.y;
|
||||
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
|
||||
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
|
||||
|
||||
@@ -292,12 +153,17 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, posIndex);
|
||||
|
||||
const move = mon.get(MonMoveComp);
|
||||
// oops.message 全局事件总线:Boss 登场(震屏/音效由表现层消费)
|
||||
if (monData.isBoss) {
|
||||
oops.message.dispatchEvent(GameEvent.BossSpawn, { pos: spawnPos.clone() });
|
||||
}
|
||||
|
||||
const move = mon.get(MoveComp);
|
||||
if (move) {
|
||||
move.spawnOrder = this.globalSpawnOrder;
|
||||
}
|
||||
|
||||
// 应用新引擎计算好的最终属性
|
||||
// 应用引擎计算好的最终属性
|
||||
const model = mon.get(HeroAttrsComp);
|
||||
if (model) {
|
||||
model.ap = monData.ap;
|
||||
@@ -307,5 +173,5 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
|
||||
/** ECS 组件移除时触发 */
|
||||
reset() {}
|
||||
reset() { }
|
||||
}
|
||||
|
||||
@@ -1,133 +1,51 @@
|
||||
/**
|
||||
* @file RogueConfig.ts
|
||||
* @description 肉鸽刷怪系统(基于英雄强度的动态难度 + 心流循环)
|
||||
* @description Boss 组合刷怪系统(单场 Boss 战定胜负)
|
||||
*
|
||||
* 游戏机制:
|
||||
* - 整局只有一场战斗:开局英雄三次三选一后自动开战。
|
||||
* - 怪物从 BossComboLibrary 按权重随机抽一组(1 Boss + 4 小怪),
|
||||
* 战斗开始瞬间一次全量登场(X∈[100,320] 均匀铺开),双方立即互相攻击。
|
||||
* - 怪物全灭 → 胜利结算;英雄全灭 → 失败结算;无回合递进、无分批刷怪。
|
||||
*
|
||||
* 设计层次:
|
||||
* 1. WaveType - 回合类型(普通 / 压力 / 放松),5 回合一个心流循环
|
||||
* 2. WaveConfig - 每回合硬编码(数量 + 小队池 + HP/AP 倍率 + 强度微调)
|
||||
* 3. DynamicTuner - 动态难度调节器(系统根据战况放水 / 加压)
|
||||
* 4. MonSkillSet - 怪物技能池(atking / atked / dead 等全触发类型)
|
||||
* 5. RogueSpawningEngine - 生成引擎(按英雄强度反推怪物强度)
|
||||
* 1. BossComboLibrary - Boss 组合库(小怪构成 + Boss/小怪技能池 + 抽取权重)
|
||||
* 2. MonSkillPool/BossSkillPool - 怪物技能池(atking / atked / dead 等全触发类型)
|
||||
* 3. RogueSpawningEngine - 生成引擎(基础属性 × 可调系数)
|
||||
*
|
||||
* 核心公式:
|
||||
* heroPower = Σ calcHeroPower(HeroInfo[uuid], lv) (场上存活英雄)
|
||||
* targetPower = heroPower × 回合类型系数 × wave.power_adjust × DynamicTuner.factor
|
||||
* scale = targetPower ÷ Σ 怪物基础强度
|
||||
* 每只怪: hp ×= scale, ap ×= scale
|
||||
*
|
||||
* 回合节奏:
|
||||
* - 最大 20 回合,第 20 回合通关
|
||||
* - 每回合 30 秒,固定分 3 批,每 10 秒释放一批
|
||||
* - 普通回合 18~36 只,放松回合 × 1.5 = 27~54 只
|
||||
* - wave % 5 === 0 → 压力回合(必带 Boss,强度高、数量少)
|
||||
* - wave % 5 === 1 → 放松回合(数量 × 1.5,强度低,爽快清屏)
|
||||
* 核心公式(基础属性 × 可调系数):
|
||||
* 怪物 hp = HeroInfo 基础hp × MON_HP_MUL(Boss 额外 × BOSS_HP_BONUS_MUL)
|
||||
* 怪物 ap = HeroInfo 基础ap × MON_AP_MUL
|
||||
* 怪物基础面板与 1 级英雄持平(近战镜像 hp300/ap22/cd1.8),默认系数校准为
|
||||
* 整场战斗(3 英雄 vs 4 小怪 + 1 Boss)双方对砍 ≈ WAVE_DURATION(30s)。
|
||||
* 手感微调只改 MON_HP_MUL / MON_AP_MUL / BOSS_HP_BONUS_MUL 三个系数即可。
|
||||
*/
|
||||
|
||||
import { HeroInfo, MonType, MonTypeName, calcHeroPower, TriggerGrouped, LvReviveEntry, heroInfo } from "../common/config/heroSet";
|
||||
import { HeroInfo, MonType, MonTypeName, TriggerGrouped, LvReviveEntry } from "../common/config/heroSet";
|
||||
import { SkillOverrides } from "../common/config/SkillSet";
|
||||
import { FacSet } from "../common/config/GameSet";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
|
||||
// ======================== 1. 回合类型与节奏常量 ========================
|
||||
// ======================== 1. 战斗常量 ========================
|
||||
|
||||
/** 回合类型枚举 */
|
||||
export enum WaveType {
|
||||
Normal = 0, // 普通回合
|
||||
Pressure = 1, // 压力回合(wave % 5 === 0,必带 Boss)
|
||||
Relax = 2, // 放松回合(wave % 5 === 1,量大强度低)
|
||||
}
|
||||
|
||||
/** 回合类型名称 */
|
||||
export const WaveTypeName: Record<WaveType, string> = {
|
||||
[WaveType.Normal]: "普通",
|
||||
[WaveType.Pressure]: "压力",
|
||||
[WaveType.Relax]: "放松",
|
||||
};
|
||||
|
||||
/** 回合类型强度系数(硬编码) */
|
||||
export const WAVE_TYPE_POWER_RATIO: Record<WaveType, number> = {
|
||||
[WaveType.Normal]: 0.9, // 普通回合:标准强度
|
||||
[WaveType.Pressure]: 1.2, // 压力回合:强度高、数量少
|
||||
[WaveType.Relax]: 0.6, // 放松回合:量大、强度低
|
||||
};
|
||||
|
||||
/** 放松回合数量倍率(相对普通回合) */
|
||||
export const RELAX_COUNT_MUL = 1.5;
|
||||
|
||||
/** 最大回合数(第 20 回合通关) */
|
||||
export const MAX_WAVE = 20;
|
||||
|
||||
/** 每回合时长(秒) */
|
||||
/** 单场战斗时长基准(秒):供清屏速度评价(BattleBannerComp 清屏奖励)等使用 */
|
||||
export const WAVE_DURATION = 30;
|
||||
|
||||
/** 每回合分批次数 */
|
||||
export const BATCH_COUNT = 3;
|
||||
/** 怪物登场固定构成:1 个 Boss + 4 个小怪(一次全量登场,打完即整局结束) */
|
||||
export const BOSS_COUNT = 1;
|
||||
export const MINION_COUNT = 4;
|
||||
|
||||
/** 每批间隔(秒):30 秒 / 3 批 = 10 秒 */
|
||||
export const BATCH_INTERVAL = WAVE_DURATION / BATCH_COUNT;
|
||||
/** 怪物血量可调系数:最终 hp = HeroInfo 基础hp × MON_HP_MUL。
|
||||
* 2.6 = 校准后怪物总血量 / 3 英雄 30s 输出(≈30 DPS),双方对砍 ≈ 30s 分胜负 */
|
||||
export const MON_HP_MUL = 2.6;
|
||||
/** 怪物攻击可调系数:最终 ap = HeroInfo 基础ap × MON_AP_MUL。
|
||||
* 0.9 使 4 小怪+1 Boss 的总 DPS ≈ 20,30s 恰好磨光 3 英雄总血量(≈600) */
|
||||
export const MON_AP_MUL = 0.9;
|
||||
/** Boss 血量额外加成(在 MON_HP_MUL 之上乘算,突出压场定位) */
|
||||
export const BOSS_HP_BONUS_MUL = 1.6;
|
||||
/** 战斗超时(秒):超过后强制结算整局(残留怪销毁并扣留存分) */
|
||||
export const BATTLE_TIMEOUT = 90;
|
||||
|
||||
/** 每回合怪物硬上限(放松回合 36 × 1.5 = 54) */
|
||||
export const MAX_MONSTERS = 54;
|
||||
|
||||
/**
|
||||
* 获取指定回合的回合类型
|
||||
* @param wave 回合(1 起)
|
||||
* @returns WaveType
|
||||
*/
|
||||
export function getWaveType(wave: number): WaveType {
|
||||
if (wave % 5 === 0) return WaveType.Pressure;
|
||||
if (wave % 5 === 1) return WaveType.Relax;
|
||||
return WaveType.Normal;
|
||||
}
|
||||
|
||||
// ======================== 2. 动态难度调节器 ========================
|
||||
|
||||
/**
|
||||
* 动态难度调节器(系统控制放水 / 加压)
|
||||
* 与硬编码系数并存,用于根据战况实时微调难度。
|
||||
*
|
||||
* 用法示例(MissionComp 每回合结束时调用):
|
||||
* DynamicTuner.adjust(clearTime, heroDeathCount);
|
||||
*
|
||||
* 调节规则(内部硬编码):
|
||||
* - 清场时间 < 20s 且无英雄死亡 → factor += 0.05(加压)
|
||||
* - 清场时间 > 28s 或有英雄死亡 → factor -= 0.05(放水)
|
||||
* - factor 范围钳制 [0.5, 2.0]
|
||||
*/
|
||||
export const DynamicTuner = {
|
||||
/** 当前难度系数(默认 1.0,>1 加压,<1 放水) */
|
||||
factor: 1.0,
|
||||
|
||||
/** 系数下限(最多放水到 50%) */
|
||||
MIN_FACTOR: 0.5,
|
||||
/** 系数上限(最多加压到 200%) */
|
||||
MAX_FACTOR: 2.0,
|
||||
/** 单次调节步长 */
|
||||
STEP: 0.05,
|
||||
|
||||
/**
|
||||
* 根据上一回合战况自动调节难度
|
||||
* @param clearTime 清场耗时(秒)
|
||||
* @param heroDeathCount 英雄死亡数
|
||||
*/
|
||||
adjust(clearTime: number, heroDeathCount: number): void {
|
||||
if (heroDeathCount > 0 || clearTime > WAVE_DURATION * 0.95) {
|
||||
// 有英雄死亡或清场过慢 → 放水
|
||||
this.factor = Math.max(this.MIN_FACTOR, this.factor - this.STEP);
|
||||
} else if (clearTime < WAVE_DURATION * 0.65 && heroDeathCount === 0) {
|
||||
// 清场过快且无死亡 → 加压
|
||||
this.factor = Math.min(this.MAX_FACTOR, this.factor + this.STEP);
|
||||
}
|
||||
},
|
||||
|
||||
/** 重置调节器(每局开始时调用) */
|
||||
reset(): void {
|
||||
this.factor = 1.0;
|
||||
},
|
||||
};
|
||||
|
||||
// ======================== 3. 怪物 UUID 池 ========================
|
||||
// ======================== 2. 怪物 UUID 池 ========================
|
||||
|
||||
/** 按 MonType 分组的怪物 uuid 池,动态从 HeroInfo 提取,避免硬编码 */
|
||||
export const MonList: Record<number, number[]> = {};
|
||||
@@ -141,7 +59,7 @@ for (const key in HeroInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 3.5 怪物金币掉落配置 ========================
|
||||
// ======================== 2.5 怪物金币掉落配置 ========================
|
||||
|
||||
/**
|
||||
* 怪物金币掉落配置(按 MonType 分类)
|
||||
@@ -171,33 +89,78 @@ export function getMonsterGoldDrop(monType: number, isBoss: boolean): number {
|
||||
return Math.max(0, Math.floor(isBoss ? cfg.boss : cfg.base));
|
||||
}
|
||||
|
||||
// ======================== 4. 小队模板库 ========================
|
||||
// ======================== 3. Boss 组合库 ========================
|
||||
|
||||
/** 小队内单种怪物的槽位定义 */
|
||||
export interface SquadSlot {
|
||||
/** Boss 组合中小怪的槽位定义(type + 数量,Boss 另由 BOSS_COUNT 提供) */
|
||||
export interface BossComboMinionSlot {
|
||||
type: MonType;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** 小队模板(3-4 只怪的组合单元) */
|
||||
export interface SquadConfig {
|
||||
/** Boss 组合配置:一场战斗随机抽一组登场(1 Boss + minions 小怪) */
|
||||
export interface BossComboConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
slots: SquadSlot[];
|
||||
weight: number; // 在小队池中的抽取权重
|
||||
/** 小怪构成(合计数量建议等于 MINION_COUNT,超出截断、不足自动补近战) */
|
||||
minions: BossComboMinionSlot[];
|
||||
/** Boss 技能池 id(引用 BossSkillPool,可选) */
|
||||
boss_skill_pool?: string[];
|
||||
/** 小怪技能池 id(引用 MonSkillPool,可选) */
|
||||
skill_pool?: string[];
|
||||
/** 在组合库中的抽取权重 */
|
||||
weight: number;
|
||||
}
|
||||
|
||||
/** 小队模板库(硬编码) */
|
||||
export const SquadLibrary: Record<string, SquadConfig> = {
|
||||
melee_grunt: { id: "melee_grunt", name: "近战步兵组", weight: 10, slots: [{ type: MonType.Melee, count: 3 }] },
|
||||
assassin_squad: { id: "assassin_squad", name: "刺客突袭组", weight: 6, slots: [{ type: MonType.Assassin, count: 2 }, { type: MonType.Support, count: 1 }] },
|
||||
mixed_balanced: { id: "mixed_balanced", name: "平衡混合组", weight: 8, slots: [{ type: MonType.Melee, count: 1 }, { type: MonType.Long, count: 1 }, { type: MonType.Heavy, count: 1 }] },
|
||||
long_line: { id: "long_line", name: "远程线列组", weight: 7, slots: [{ type: MonType.Long, count: 2 }, { type: MonType.Support, count: 1 }] },
|
||||
heavy_shield: { id: "heavy_shield", name: "重盾堡垒组", weight: 5, slots: [{ type: MonType.Heavy, count: 1 }, { type: MonType.Melee, count: 2 }] },
|
||||
summoner_cult: { id: "summoner_cult", name: "召唤教派组", weight: 4, slots: [{ type: MonType.Summoner, count: 1 }, { type: MonType.Long, count: 2 }] },
|
||||
/** Boss 组合库(硬编码):每场战斗按权重随机抽一组 */
|
||||
export const BossComboLibrary: Record<string, BossComboConfig> = {
|
||||
rage_charge: {
|
||||
id: "rage_charge", name: "狂暴冲锋组", weight: 10,
|
||||
minions: [{ type: MonType.Melee, count: 3 }, { type: MonType.Heavy, count: 1 }],
|
||||
boss_skill_pool: ["boss_rage"],
|
||||
},
|
||||
iron_wall: {
|
||||
id: "iron_wall", name: "铁壁推进组", weight: 8,
|
||||
minions: [{ type: MonType.Heavy, count: 2 }, { type: MonType.Support, count: 1 }, { type: MonType.Long, count: 1 }],
|
||||
boss_skill_pool: ["boss_iron"],
|
||||
skill_pool: ["tough"],
|
||||
},
|
||||
doom_ritual: {
|
||||
id: "doom_ritual", name: "毁灭仪式组", weight: 6,
|
||||
minions: [{ type: MonType.Summoner, count: 1 }, { type: MonType.Long, count: 2 }, { type: MonType.Support, count: 1 }],
|
||||
boss_skill_pool: ["boss_doom"],
|
||||
skill_pool: ["warcry"],
|
||||
},
|
||||
assassin_raid: {
|
||||
id: "assassin_raid", name: "刺客突袭组", weight: 7,
|
||||
minions: [{ type: MonType.Assassin, count: 2 }, { type: MonType.Melee, count: 2 }],
|
||||
boss_skill_pool: ["boss_rage", "boss_doom"],
|
||||
skill_pool: ["berserk"],
|
||||
},
|
||||
blood_legion: {
|
||||
id: "blood_legion", name: "吸血军团组", weight: 5,
|
||||
minions: [{ type: MonType.Melee, count: 2 }, { type: MonType.Long, count: 1 }, { type: MonType.Summoner, count: 1 }],
|
||||
boss_skill_pool: ["boss_iron", "boss_doom"],
|
||||
skill_pool: ["leech"],
|
||||
},
|
||||
};
|
||||
|
||||
// ======================== 5. 怪物技能池 ========================
|
||||
/**
|
||||
* 按权重随机抽取一组 Boss 组合
|
||||
* @returns 组合配置,库为空时返回 null
|
||||
*/
|
||||
export function pickBossCombo(): BossComboConfig | null {
|
||||
const combos = Object.values(BossComboLibrary).filter(c => c && c.weight > 0);
|
||||
if (combos.length === 0) return null;
|
||||
const total = combos.reduce((s, c) => s + c.weight, 0);
|
||||
let r = Math.random() * total;
|
||||
for (const c of combos) {
|
||||
r -= c.weight;
|
||||
if (r <= 0) return c;
|
||||
}
|
||||
return combos[combos.length - 1];
|
||||
}
|
||||
|
||||
// ======================== 4. 怪物技能池 ========================
|
||||
|
||||
/**
|
||||
* 怪物技能套装(一套完整的触发技能配置)
|
||||
@@ -228,8 +191,6 @@ export interface MonSkillSet {
|
||||
|
||||
/**
|
||||
* 怪物技能池(硬编码)
|
||||
* 压力回合 / Boss 可配置专属技能,普通回合随机挂载增加变数。
|
||||
*
|
||||
* 技能 uuid 引用 SkillSet 中的 6000~6500 段触发技能。
|
||||
*/
|
||||
export const MonSkillPool: Record<string, MonSkillSet> = {
|
||||
@@ -270,7 +231,7 @@ export const MonSkillPool: Record<string, MonSkillSet> = {
|
||||
},
|
||||
};
|
||||
|
||||
/** Boss 专属技能池(压力回合 Boss 随机挂载) */
|
||||
/** Boss 专属技能池 */
|
||||
export const BossSkillPool: Record<string, MonSkillSet> = {
|
||||
/** 狂暴领主:攻击触发全队攻击提升 */
|
||||
boss_rage: {
|
||||
@@ -301,126 +262,55 @@ export const BossSkillPool: Record<string, MonSkillSet> = {
|
||||
},
|
||||
};
|
||||
|
||||
// ======================== 6. 回合配置表 ========================
|
||||
|
||||
/** 单回合完整配置 */
|
||||
export interface WaveConfig {
|
||||
/** 基础怪物总数(普通回合 36 为上限,放松回合自动 × 1.5 = 54) */
|
||||
base_count: number;
|
||||
/** 可选小队 id 池,引擎按权重抽取拼装到 base_count */
|
||||
squad_pool: string[];
|
||||
/** HP 强化倍率(硬编码,逐回合递进) */
|
||||
hp_mul: number;
|
||||
/** AP 强化倍率(硬编码,逐回合递进) */
|
||||
ap_mul: number;
|
||||
/** 强度微调(放水 / 加压,默认 1.0) */
|
||||
power_adjust?: number;
|
||||
/** 是否 Boss 回合(压力回合必为 true) */
|
||||
boss_wave?: boolean;
|
||||
/** 普通怪技能池 id(可选,随机挂载) */
|
||||
skill_pool?: string[];
|
||||
/** Boss 技能池 id(Boss 回合专用,随机挂载) */
|
||||
boss_skill_pool?: string[];
|
||||
}
|
||||
// ======================== 5. 配置校验 ========================
|
||||
|
||||
/**
|
||||
* 回合配置表(硬编码 wave 1~20)
|
||||
*
|
||||
* 心流循环(5 回合一循环):
|
||||
* wave % 5 === 0 → 压力回合(必带 Boss,强度高、数量少)
|
||||
* wave % 5 === 1 → 放松回合(数量 × 1.5,强度低)
|
||||
* 其余 → 普通回合(标准强度)
|
||||
*
|
||||
* 强度递进:hp_mul / ap_mul 每 5 回合一档,压力回合额外提升。
|
||||
*/
|
||||
export const WaveConfigs: Record<number, WaveConfig> = {
|
||||
// ===== 第一循环:教学期 =====
|
||||
1: { base_count: 18, squad_pool: ["melee_grunt"], hp_mul: 1.00, ap_mul: 1.00 },
|
||||
2: { base_count: 21, squad_pool: ["melee_grunt", "mixed_balanced"], hp_mul: 1.00, ap_mul: 1.00 },
|
||||
3: { base_count: 24, squad_pool: ["melee_grunt", "mixed_balanced", "long_line"], hp_mul: 1.05, ap_mul: 1.00 },
|
||||
4: { base_count: 27, squad_pool: ["mixed_balanced", "long_line", "heavy_shield"], hp_mul: 1.10, ap_mul: 1.05 },
|
||||
// 压力回合:第一 Boss
|
||||
5: { base_count: 21, squad_pool: ["melee_grunt", "mixed_balanced", "heavy_shield"], hp_mul: 1.15, ap_mul: 1.10, boss_wave: true, boss_skill_pool: ["boss_rage"] },
|
||||
|
||||
// ===== 第二循环:引入技能怪 =====
|
||||
// 放松回合:量大好清
|
||||
6: { base_count: 30, squad_pool: ["melee_grunt", "mixed_balanced", "long_line"], hp_mul: 1.10, ap_mul: 1.05 },
|
||||
7: { base_count: 30, squad_pool: ["melee_grunt", "heavy_shield", "long_line"], hp_mul: 1.15, ap_mul: 1.10, skill_pool: ["tough"] },
|
||||
8: { base_count: 33, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], hp_mul: 1.20, ap_mul: 1.15, skill_pool: ["berserk"] },
|
||||
9: { base_count: 33, squad_pool: ["heavy_shield", "long_line", "mixed_balanced"], hp_mul: 1.25, ap_mul: 1.20, skill_pool: ["berserk", "tough"] },
|
||||
// 压力回合:第二 Boss
|
||||
10: { base_count: 24, squad_pool: ["melee_grunt", "assassin_squad", "mixed_balanced"], hp_mul: 1.30, ap_mul: 1.25, boss_wave: true, boss_skill_pool: ["boss_rage", "boss_iron"] },
|
||||
|
||||
// ===== 第三循环:组合多样化 =====
|
||||
// 放松回合
|
||||
11: { base_count: 33, squad_pool: ["assassin_squad", "long_line", "summoner_cult", "mixed_balanced"], hp_mul: 1.25, ap_mul: 1.20, skill_pool: ["leech"] },
|
||||
12: { base_count: 33, squad_pool: ["heavy_shield", "melee_grunt", "mixed_balanced", "long_line"], hp_mul: 1.30, ap_mul: 1.25, skill_pool: ["tough", "warcry"] },
|
||||
13: { base_count: 36, squad_pool: ["assassin_squad", "summoner_cult", "mixed_balanced"], hp_mul: 1.35, ap_mul: 1.30, skill_pool: ["berserk", "leech"] },
|
||||
14: { base_count: 36, squad_pool: ["heavy_shield", "long_line", "melee_grunt"], hp_mul: 1.40, ap_mul: 1.35, skill_pool: ["berserk", "tough", "legacy"] },
|
||||
// 压力回合:第三 Boss(中期高潮)
|
||||
15: { base_count: 27, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], hp_mul: 1.50, ap_mul: 1.40, boss_wave: true, boss_skill_pool: ["boss_rage", "boss_iron", "boss_doom"] },
|
||||
|
||||
// ===== 第四循环:终极阶段 =====
|
||||
// 放松回合
|
||||
16: { base_count: 36, squad_pool: ["assassin_squad", "heavy_shield", "long_line"], hp_mul: 1.45, ap_mul: 1.40, skill_pool: ["leech", "warcry"] },
|
||||
17: { base_count: 36, squad_pool: ["melee_grunt", "summoner_cult", "mixed_balanced"], hp_mul: 1.50, ap_mul: 1.45, skill_pool: ["berserk", "legacy"] },
|
||||
18: { base_count: 36, squad_pool: ["assassin_squad", "long_line", "heavy_shield"], hp_mul: 1.55, ap_mul: 1.50, skill_pool: ["berserk", "tough", "leech"] },
|
||||
19: { base_count: 36, squad_pool: ["mixed_balanced", "summoner_cult", "melee_grunt"], hp_mul: 1.60, ap_mul: 1.55, skill_pool: ["berserk", "tough", "legacy", "warcry"] },
|
||||
// 压力回合:最终 Boss
|
||||
20: { base_count: 30, squad_pool: ["assassin_squad", "heavy_shield", "summoner_cult", "mixed_balanced"], hp_mul: 1.80, ap_mul: 1.70, boss_wave: true, boss_skill_pool: ["boss_doom", "boss_rage"] },
|
||||
};
|
||||
|
||||
// ======================== 7. 配置校验 ========================
|
||||
|
||||
/**
|
||||
* 校验 WaveConfigs / SquadLibrary / MonSkillPool 引用一致性
|
||||
* 校验 BossComboLibrary / MonSkillPool 引用一致性
|
||||
* 建议在游戏启动时调用一次,便于发现配置错误
|
||||
* @returns 错误信息数组,空数组表示校验通过
|
||||
*/
|
||||
export function validateRogueConfig(): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
// 1. 校验 WaveConfigs 引用与范围
|
||||
for (const wave in WaveConfigs) {
|
||||
const cfg = WaveConfigs[wave];
|
||||
for (const squadId of cfg.squad_pool) {
|
||||
if (!SquadLibrary[squadId]) {
|
||||
errors.push(`Wave ${wave} 引用了不存在的小队: ${squadId}`);
|
||||
// 1. 校验组合引用与数量
|
||||
for (const id in BossComboLibrary) {
|
||||
const combo = BossComboLibrary[id];
|
||||
let minionTotal = 0;
|
||||
for (const slot of combo.minions) {
|
||||
minionTotal += slot.count;
|
||||
if (!MonList[slot.type] || MonList[slot.type].length === 0) {
|
||||
errors.push(`组合 ${id} 引用的怪物类型 ${slot.type}(${MonTypeName[slot.type] || "?"}) 在 MonList 中无可用 uuid`);
|
||||
}
|
||||
}
|
||||
if (cfg.skill_pool) {
|
||||
for (const skillId of cfg.skill_pool) {
|
||||
if (minionTotal !== MINION_COUNT) {
|
||||
errors.push(`组合 ${id} 小怪数量=${minionTotal} 与 MINION_COUNT=${MINION_COUNT} 不一致`);
|
||||
}
|
||||
if (combo.skill_pool) {
|
||||
for (const skillId of combo.skill_pool) {
|
||||
if (!MonSkillPool[skillId]) {
|
||||
errors.push(`Wave ${wave} 引用了不存在的技能: ${skillId}`);
|
||||
errors.push(`组合 ${id} 引用了不存在的技能: ${skillId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cfg.boss_skill_pool) {
|
||||
for (const skillId of cfg.boss_skill_pool) {
|
||||
if (combo.boss_skill_pool) {
|
||||
for (const skillId of combo.boss_skill_pool) {
|
||||
if (!BossSkillPool[skillId]) {
|
||||
errors.push(`Wave ${wave} 引用了不存在的 Boss 技能: ${skillId}`);
|
||||
errors.push(`组合 ${id} 引用了不存在的 Boss 技能: ${skillId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cfg.base_count < 1 || cfg.base_count > MAX_MONSTERS) {
|
||||
errors.push(`Wave ${wave} base_count=${cfg.base_count} 越界 (1~${MAX_MONSTERS})`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 校验 SquadLibrary 中所有 type 在 MonList 中有怪
|
||||
for (const id in SquadLibrary) {
|
||||
const squad = SquadLibrary[id];
|
||||
for (const slot of squad.slots) {
|
||||
if (!MonList[slot.type] || MonList[slot.type].length === 0) {
|
||||
errors.push(`小队 ${id} 引用的怪物类型 ${slot.type}(${MonTypeName[slot.type] || "?"}) 在 MonList 中无可用 uuid`);
|
||||
}
|
||||
}
|
||||
// 2. 校验 Boss 类型在 MonList 中有怪
|
||||
if ((!MonList[MonType.MeleeBoss] || MonList[MonType.MeleeBoss].length === 0)
|
||||
&& (!MonList[MonType.LongBoss] || MonList[MonType.LongBoss].length === 0)) {
|
||||
errors.push("MonList 中无任何 Boss 类型(MeleeBoss/LongBoss)可用 uuid");
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
// ======================== 8. 生成结果接口 ========================
|
||||
// ======================== 6. 生成结果接口 ========================
|
||||
|
||||
/** 生成结果(传递给 MissionMonComp 用于实例化怪物) */
|
||||
export interface GeneratedMonster {
|
||||
@@ -429,8 +319,10 @@ export interface GeneratedMonster {
|
||||
hp: number;
|
||||
ap: number;
|
||||
isBoss: boolean;
|
||||
/** 是否为 Boss 护卫队成员(供 UI/统计识别) */
|
||||
isBossGuard?: boolean;
|
||||
spawnIndex: number;
|
||||
/** 本怪所属批次(0~2,由 MissionMonComp 按 BATCH_INTERVAL 释放) */
|
||||
/** 本怪所属批次(保留字段,单场全量登场恒为 0) */
|
||||
batch: number;
|
||||
|
||||
/**
|
||||
@@ -449,13 +341,12 @@ export interface GeneratedMonster {
|
||||
};
|
||||
}
|
||||
|
||||
// ======================== 9. 测试模式配置 ========================
|
||||
// ======================== 7. 测试模式配置 ========================
|
||||
|
||||
export const TestModeConfig = {
|
||||
enable: false, // 默认关闭测试模式
|
||||
baseHp: 150,
|
||||
baseAp: 12,
|
||||
growthRatePerWave: 0.2,
|
||||
monType: MonType.Melee,
|
||||
monUuid: 6001,
|
||||
spawnCount: 1,
|
||||
@@ -468,144 +359,73 @@ export const TestModeConfig = {
|
||||
fend: undefined as TriggerGrouped | undefined,
|
||||
};
|
||||
|
||||
// ======================== 10. 生成引擎 ========================
|
||||
// ======================== 8. 生成引擎 ========================
|
||||
|
||||
/**
|
||||
* 肉鸽刷怪生成引擎
|
||||
* 按英雄强度反推怪物强度,结合回合类型系数与动态调节器生成怪物列表
|
||||
* Boss 组合生成引擎
|
||||
* 随机抽一组 Boss 组合,怪物最终属性 = HeroInfo 基础属性 × 可调系数
|
||||
*/
|
||||
export class RogueSpawningEngine {
|
||||
|
||||
/**
|
||||
* 生成指定回合的怪物列表
|
||||
* @param waveNumber 回合(1 起,>MAX_WAVE 时复用 wave MAX_WAVE 配置)
|
||||
* @returns 怪物列表,长度 ≤ MAX_MONSTERS
|
||||
* 生成一场战斗的怪物列表:随机一组 Boss 组合(1 Boss + 4 小怪,属性走基础×系数)。
|
||||
* Boss 压队尾(登场时由 MissionMonComp 统一布位)。
|
||||
* @returns 怪物列表(小怪在前、Boss 在尾)
|
||||
*/
|
||||
generateWave(waveNumber: number): GeneratedMonster[] {
|
||||
if (waveNumber < 1) return [];
|
||||
|
||||
generateBossCombo(): GeneratedMonster[] {
|
||||
// 测试模式拦截:完全绕过生成引擎,直接返回硬编码测试怪
|
||||
if (TestModeConfig.enable) {
|
||||
return this.generateTestWave(waveNumber);
|
||||
return this.generateTestWave();
|
||||
}
|
||||
|
||||
const wave = Math.min(waveNumber, MAX_WAVE);
|
||||
const cfg = WaveConfigs[wave];
|
||||
const waveType = getWaveType(wave);
|
||||
const typeRatio = WAVE_TYPE_POWER_RATIO[waveType];
|
||||
|
||||
// 1. 计算目标强度
|
||||
const heroPower = this.getCurrentHeroPower();
|
||||
const powerAdjust = cfg.power_adjust ?? 1.0;
|
||||
const targetPower = heroPower * typeRatio * powerAdjust * DynamicTuner.factor;
|
||||
|
||||
// 2. 确定怪物总数(放松回合 × 1.5)
|
||||
let totalCount = cfg.base_count;
|
||||
if (waveType === WaveType.Relax) {
|
||||
totalCount = Math.round(totalCount * RELAX_COUNT_MUL);
|
||||
}
|
||||
totalCount = Math.min(totalCount, MAX_MONSTERS);
|
||||
|
||||
// 3. Boss 位(压力回合必带 Boss,占 1 个名额)
|
||||
const combo = pickBossCombo();
|
||||
const monsters: GeneratedMonster[] = [];
|
||||
let remaining = totalCount;
|
||||
if (cfg.boss_wave) {
|
||||
monsters.push(this.makeBoss(wave, cfg));
|
||||
remaining -= 1;
|
||||
}
|
||||
|
||||
// 4. 小队拼装:按权重从 squad_pool 抽小队,累计到 remaining
|
||||
const squadMonsters = this.assembleSquads(cfg.squad_pool, remaining, wave);
|
||||
monsters.push(...squadMonsters);
|
||||
|
||||
// 5. 应用硬编码 HP/AP 倍率
|
||||
for (const m of monsters) {
|
||||
m.hp = Math.max(1, Math.round(m.hp * cfg.hp_mul));
|
||||
m.ap = Math.max(1, Math.round(m.ap * cfg.ap_mul));
|
||||
}
|
||||
|
||||
// 6. 按英雄强度反推缩放系数
|
||||
const totalBasePower = monsters.reduce((sum, m) => {
|
||||
const info = HeroInfo[m.uuid];
|
||||
return sum + (info ? calcHeroPower(info, 1) : m.hp + m.ap);
|
||||
}, 0);
|
||||
|
||||
if (totalBasePower > 0 && targetPower > 0) {
|
||||
const scale = targetPower / totalBasePower;
|
||||
for (const m of monsters) {
|
||||
m.hp = Math.max(1, Math.round(m.hp * scale));
|
||||
m.ap = Math.max(1, Math.round(m.ap * scale));
|
||||
if (combo) {
|
||||
for (const slot of combo.minions) {
|
||||
for (let i = 0; i < slot.count; i++) {
|
||||
monsters.push(this.makeMonster(slot.type, monsters.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 挂载技能(普通怪随机技能池,Boss 专属技能池)
|
||||
for (const m of monsters) {
|
||||
if (m.isBoss && cfg.boss_skill_pool) {
|
||||
m.skills = this.pickSkillSet(cfg.boss_skill_pool, BossSkillPool);
|
||||
} else if (!m.isBoss && cfg.skill_pool) {
|
||||
m.skills = this.pickSkillSet(cfg.skill_pool, MonSkillPool);
|
||||
}
|
||||
// 组合库为空或数量不足兜底:补近战小怪至 MINION_COUNT
|
||||
while (monsters.length < MINION_COUNT) {
|
||||
monsters.push(this.makeMonster(MonType.Melee, monsters.length));
|
||||
}
|
||||
|
||||
// Boss 压队尾
|
||||
for (let i = 0; i < BOSS_COUNT; i++) {
|
||||
monsters.push(this.makeBoss());
|
||||
}
|
||||
|
||||
// 属性缩放:基础属性 × 可调系数(Boss 血量额外加成),校准目标双方对砍 ≈ 30s
|
||||
for (const m of monsters) {
|
||||
m.hp = Math.max(1, Math.round(m.hp * MON_HP_MUL * (m.isBoss ? BOSS_HP_BONUS_MUL : 1)));
|
||||
m.ap = Math.max(1, Math.round(m.ap * MON_AP_MUL));
|
||||
}
|
||||
|
||||
// 挂载组合技能(Boss 专属池 / 小怪普通池)
|
||||
for (const m of monsters) {
|
||||
if (m.isBoss && combo?.boss_skill_pool) {
|
||||
m.skills = this.pickSkillSet(combo.boss_skill_pool, BossSkillPool);
|
||||
} else if (!m.isBoss && combo?.skill_pool) {
|
||||
m.skills = this.pickSkillSet(combo.skill_pool, MonSkillPool);
|
||||
}
|
||||
m.batch = 0;
|
||||
}
|
||||
|
||||
// 8. 分配批次(0~2,均匀分布)
|
||||
for (let i = 0; i < monsters.length; i++) {
|
||||
monsters[i].batch = i % BATCH_COUNT;
|
||||
monsters[i].spawnIndex = i;
|
||||
}
|
||||
|
||||
// 9. 硬上限保护
|
||||
return monsters.slice(0, MAX_MONSTERS);
|
||||
return monsters;
|
||||
}
|
||||
|
||||
/** 重置引擎状态(每局开始时调用) */
|
||||
/** 重置引擎状态(每局开始时调用,预留) */
|
||||
reset(): void {
|
||||
DynamicTuner.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定回合的怪物总数
|
||||
* @param waveNumber 目标回合数
|
||||
* @returns 预计生成的怪物总数
|
||||
*/
|
||||
getWaveMonsterCount(waveNumber: number): number {
|
||||
if (waveNumber < 1) return 0;
|
||||
if (TestModeConfig.enable) {
|
||||
return Math.max(1, TestModeConfig.spawnCount || 1);
|
||||
}
|
||||
const wave = Math.min(waveNumber, MAX_WAVE);
|
||||
const cfg = WaveConfigs[wave];
|
||||
const waveType = getWaveType(wave);
|
||||
let count = cfg.base_count;
|
||||
if (waveType === WaveType.Relax) {
|
||||
count = Math.round(count * RELAX_COUNT_MUL);
|
||||
}
|
||||
return Math.min(count, MAX_MONSTERS);
|
||||
}
|
||||
|
||||
// ======================== 私有生成子算法 ========================
|
||||
|
||||
/**
|
||||
* 计算场上存活英雄的总强度
|
||||
* 通过 ECS 查询所有 HeroAttrsComp,累加 calcHeroPower
|
||||
*/
|
||||
private getCurrentHeroPower(): number {
|
||||
let total = 0;
|
||||
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
|
||||
const attrs = entity.get(HeroAttrsComp);
|
||||
if (!attrs || attrs.is_dead || attrs.fac !== FacSet.HERO) return;
|
||||
const info = HeroInfo[attrs.hero_uuid];
|
||||
if (!info) return;
|
||||
// 使用英雄当前等级(升级卡驱动),保证强度评估与实战一致
|
||||
const lv = Math.max(1, attrs.lv || 1);
|
||||
total += calcHeroPower(info, lv);
|
||||
});
|
||||
// 兜底:无英雄时返回基准强度,避免除零
|
||||
return Math.max(total, 100);
|
||||
}
|
||||
|
||||
/** 测试模式:完全绕过引擎逻辑 */
|
||||
private generateTestWave(waveNumber: number): GeneratedMonster[] {
|
||||
const growth = 1 + (waveNumber - 1) * TestModeConfig.growthRatePerWave;
|
||||
private generateTestWave(): GeneratedMonster[] {
|
||||
const count = Math.max(1, TestModeConfig.spawnCount || 1);
|
||||
const monsters: GeneratedMonster[] = [];
|
||||
|
||||
@@ -613,11 +433,11 @@ export class RogueSpawningEngine {
|
||||
monsters.push({
|
||||
uuid: TestModeConfig.monUuid,
|
||||
type: TestModeConfig.monType,
|
||||
hp: Math.round(TestModeConfig.baseHp * growth),
|
||||
ap: Math.round(TestModeConfig.baseAp * growth),
|
||||
hp: TestModeConfig.baseHp,
|
||||
ap: TestModeConfig.baseAp,
|
||||
isBoss: false,
|
||||
spawnIndex: i,
|
||||
batch: i % BATCH_COUNT,
|
||||
batch: 0,
|
||||
skills: {
|
||||
skill: TestModeConfig.skill,
|
||||
atking: TestModeConfig.atking,
|
||||
@@ -631,33 +451,8 @@ export class RogueSpawningEngine {
|
||||
return monsters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 小队拼装:按权重从 squad_pool 抽小队,累计到 target 数量
|
||||
* 超出部分按剩余位截断
|
||||
*/
|
||||
private assembleSquads(pool: string[], target: number, wave: number): GeneratedMonster[] {
|
||||
const result: GeneratedMonster[] = [];
|
||||
let filled = 0;
|
||||
let safety = 20; // 防御性循环上限
|
||||
|
||||
while (filled < target && safety-- > 0) {
|
||||
const squad = this.pickWeightedSquad(pool);
|
||||
if (!squad) break;
|
||||
|
||||
const remaining = target - filled;
|
||||
for (const slot of squad.slots) {
|
||||
for (let i = 0; i < slot.count && filled < remaining; i++) {
|
||||
result.push(this.makeMonster(slot.type, wave, filled));
|
||||
filled++;
|
||||
}
|
||||
if (filled >= remaining) break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 生成 Boss(首位) */
|
||||
private makeBoss(wave: number, cfg: WaveConfig): GeneratedMonster {
|
||||
/** 生成 Boss(基础血量带最终战加成,属性随后由总强度框架统一缩放) */
|
||||
private makeBoss(): GeneratedMonster {
|
||||
const isMeleeBoss = Math.random() < 0.5;
|
||||
const type = isMeleeBoss ? MonType.MeleeBoss : MonType.LongBoss;
|
||||
|
||||
@@ -672,22 +467,19 @@ export class RogueSpawningEngine {
|
||||
const baseHp = baseInfo ? baseInfo.hp : 500;
|
||||
const baseAp = baseInfo ? baseInfo.ap : 30;
|
||||
|
||||
// Boss 自带额外血量加成:每 5 回合递增 20%(wave 5=1.0, 10=1.2, 15=1.4...)
|
||||
const bossBonusHpMul = 1 + 0.20 * Math.floor(Math.max(0, wave - 5) / 5);
|
||||
|
||||
return {
|
||||
uuid,
|
||||
type,
|
||||
hp: Math.round(baseHp * bossBonusHpMul),
|
||||
hp: Math.round(baseHp * BOSS_HP_BONUS_MUL),
|
||||
ap: baseAp,
|
||||
isBoss: true,
|
||||
spawnIndex: 0,
|
||||
batch: 0, // Boss 固定第一批
|
||||
spawnIndex: 0, // 占位值,由 generateBossCombo 统一分配
|
||||
batch: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** 生成普通怪物 */
|
||||
private makeMonster(type: MonType, wave: number, spawnIndex: number): GeneratedMonster {
|
||||
private makeMonster(type: MonType, spawnIndex: number): GeneratedMonster {
|
||||
let uuids = MonList[type];
|
||||
if (!uuids || uuids.length === 0) {
|
||||
// 兜底 Melee
|
||||
@@ -706,7 +498,7 @@ export class RogueSpawningEngine {
|
||||
ap: baseAp,
|
||||
isBoss: false,
|
||||
spawnIndex,
|
||||
batch: 0, // 后续统一分配
|
||||
batch: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -728,13 +520,6 @@ export class RogueSpawningEngine {
|
||||
};
|
||||
}
|
||||
|
||||
/** 按权重从小队 id 池抽 1 个小队 */
|
||||
private pickWeightedSquad(pool: string[]): SquadConfig | null {
|
||||
const valid = pool.map(id => SquadLibrary[id]).filter(s => s);
|
||||
if (valid.length === 0) return null;
|
||||
return this.pickWeighted(valid);
|
||||
}
|
||||
|
||||
/** 通用加权随机抽取 */
|
||||
private pickWeighted<T extends { weight: number }>(items: T[]): T | null {
|
||||
if (items.length === 0) return null;
|
||||
@@ -749,57 +534,6 @@ export class RogueSpawningEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 11. 全局单例 & 向后兼容导出 ========================
|
||||
// ======================== 9. 全局单例 ========================
|
||||
|
||||
export const spawningEngine = new RogueSpawningEngine();
|
||||
|
||||
/** 历史接口:回合槽位(保留兼容) */
|
||||
export interface IWaveSlot {
|
||||
type: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定回合预计的怪物总数量
|
||||
* @param waveNumber 目标回合数
|
||||
*/
|
||||
export function getWaveMonsterCount(waveNumber: number): number {
|
||||
return spawningEngine.getWaveMonsterCount(waveNumber);
|
||||
}
|
||||
|
||||
/** 获取回合槽位配置(向后兼容,从 generateWave 反推) */
|
||||
export function getWaveSlotConfig(waveNumber: number): IWaveSlot[] {
|
||||
const generated = spawningEngine.generateWave(waveNumber);
|
||||
const slotMap = new Map<number, number>();
|
||||
|
||||
for (const m of generated) {
|
||||
slotMap.set(m.type, (slotMap.get(m.type) || 0) + 1);
|
||||
}
|
||||
|
||||
return Array.from(slotMap.entries()).map(([type, count]) => ({ type, count }));
|
||||
}
|
||||
|
||||
export const DefaultWaveSlot: IWaveSlot[] = [
|
||||
{ type: MonType.Melee, count: 20 },
|
||||
{ type: MonType.Long, count: 15 },
|
||||
{ type: MonType.Support, count: 5 },
|
||||
];
|
||||
|
||||
/** 回合槽位配置代理(按需生成) */
|
||||
export const WaveSlotConfig: { [wave: number]: IWaveSlot[] } = new Proxy(
|
||||
{} as { [wave: number]: IWaveSlot[] },
|
||||
{
|
||||
get(_target, prop: string) {
|
||||
const wave = parseInt(prop, 10);
|
||||
if (!isNaN(wave) && wave >= 1) {
|
||||
return getWaveSlotConfig(wave);
|
||||
}
|
||||
if (prop === "toJSON") return () => ({});
|
||||
return undefined;
|
||||
},
|
||||
has(_target, prop: string) {
|
||||
const wave = parseInt(prop, 10);
|
||||
return !isNaN(wave) && wave >= 1;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -36,6 +36,7 @@ import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/modu
|
||||
import { CardTriggerType, CardConfig } from "../common/config/CardSet";
|
||||
import { SkillCardList } from "../common/config/SCardSet";
|
||||
import { SkillOverrides } from "../common/config/SkillSet";
|
||||
import { FieldEntry } from "../common/config/heroSet";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
@@ -93,8 +94,8 @@ export class SkillBoxComp extends CCComp {
|
||||
private keep_waves: number = 0;
|
||||
/** 技能覆写参数(自定义伤害、Buff等) */
|
||||
private overrides?: SkillOverrides;
|
||||
/** 驻场技能 UUID 列表 */
|
||||
public field: number[] = [];
|
||||
/** 驻场技能词条列表(uuid 指向 FieldSkillSet 底座,value 为生效数值) */
|
||||
public field: FieldEntry[] = [];
|
||||
/** 卡牌自定义图标ID(优先级最高,未定义则按 trigger_type 自动取) */
|
||||
private card_icon?: string;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user