62 Commits

Author SHA1 Message Date
pan
6c2f1defa9 refactor(monster): 重构波次怪物管理逻辑,优化配置与流程
1.  更新怪物配置注释,修正近战/远程怪物的描述与分类
2.  移除MissionComp中过时的自适应刷怪逻辑
3.  重构MissionMonComp:删除插队刷怪队列、简化波次流程、统一怪物生成逻辑
4.  移除冗余日志与注释,优化代码可读性
5.  调整波次准备阶段的怪物生成时机与数据处理
2026-06-23 17:05:07 +08:00
pan
46fa481607 feat(skill): add movement acceleration support for skills
1. 新增SMoveComp的isAccelerate字段控制加速逻辑
2. 添加技能配置is_accel字段启用加速效果
3. 实现二次方缓入加速曲线,兼顾起步速度与最终加速度
4. 为6001号技能默认开启加速效果并调整其移动类型为直线运动
2026-06-23 11:07:57 +08:00
pan
72cdf32a75 refactor(TalentItemComp): 优化天赋卡牌UI显示逻辑
1.  复用CardConfig中的pool_lv统一卡牌背景色,与技能卡保持一致
2.  新增对驻场天赋卡的图标显示支持,使用FieldSkillSet获取图标
3.  重构图标获取逻辑,与SCardComp保持对齐避免显示异常
4.  移除冗余的wave映射背景色代码
2026-06-22 16:24:23 +08:00
pan
a8642cb788 chore: 批量更新UI预制体资源配置
1.  更新引导文案文本内容
2.  调整卡牌控件的位置、尺寸、字体样式与布局参数
3.  更新轻量卡牌控件的激活状态与精灵帧引用
4.  调整方块控件的尺寸、缩放比例与填充参数
5.  移除废弃的精灵帧元数据配置
2026-06-22 15:08:01 +08:00
panFD
d60b66350a chore(.claude): update claude settings permissions and adjust config order
1. 调整statusLine配置项的位置到文件末尾
2. 新增WebSearch、git add/commit、npm install的权限许可
2026-06-21 17:33:17 +08:00
panFD
bfa434634c fix(config-editor): main entry exports methods object + load/unload (Cocos 3.8)
Root cause of 'Method does not exist / The methods of the module is undefined':
Cocos 3.8 expects handlers inside a 'methods' export (plus load/unload hooks),
not flat on module.exports. Handlers receive args directly (no event); return
value is the request reply. Verified vs official 3.8 first/messages docs.
Also: menu path + panel title switched to literal strings (i18n showed 'undefined').
2026-06-21 16:30:39 +08:00
panFD
fb65fa79c8 test(config-editor): record Plan A verification evidence
Task 13 of plan 2026-06-20-config-editor-foundation. Captures:
- Automated BLOCKING gate: 36/36 unit tests pass
- Automated BLOCKING gate: build produces dist/main.js (9.5mb) + dist/panels/default.js (628kb)
- Necessary esbuild.config.mjs fix documented (node:fs/node:path external for panel)
- ADVISORY in-editor checklist left for human completion
- DoD mapping
2026-06-21 09:57:19 +08:00
panFD
24b5c49891 feat(config-editor): extension entry + minimal Vue panel proving end-to-end IPC
Task 12 of plan 2026-06-20-config-editor-foundation. Adds:
- src/main/index.ts: onLoad + message handlers (return value = request resolve,
  per Cocos 3.x verified IPC mechanism; fallback note left in plan)
- src/panels/default/{index,app}.ts: Editor.Panel.define host + Vue 3 minimal
  app (table switcher, key list, record JSON dump)
- static/template/default/index.html + static/style/default/index.css

Deviation from plan (necessary, flagged): esbuild.config.mjs now marks
node:fs/node:path as external for the panel entry (platform:'browser').
The plan's panel reads static template/style at runtime via Node fs, which
requires these builtins; Cocos panel runs in an Electron renderer that
provides them. Without this, esbuild errors with 'Could not resolve node:fs'.

Build verified: dist/main.js (9.5mb, typescript compiler API bundled) and
dist/panels/default.js (628kb, vue.esm-bundler bundled) both generate.
2026-06-21 09:56:02 +08:00
panFD
e3102c63ff feat(config-editor): main-process store (in-memory truth + message impls + asset-db refresh)
Task 11 of plan 2026-06-20-config-editor-foundation. Holds three TsConfigFile
instances (hero/skill/field), implements query*/validate/saveRecord/revertRecord
message handlers. saveRecord validates before persisting, rolls back on error,
and refreshes asset-db on success. HeroList read via regex.
2026-06-21 09:26:03 +08:00
panFD
6a81630f6f feat(config-editor): port buildSkillDesc to JS for panel preview 2026-06-21 09:17:50 +08:00
panFD
4a5659b7ec feat(config-editor): add validation rules (dup/required/enum/ref/overrides/herolist) with tests 2026-06-21 09:11:47 +08:00
panFD
4df88c1c90 feat(config-editor): TsConfigFile load/read/patch/add/delete/save (entry-level AST patch, .bak + syntax check)
26 IO tests green: serializer, parser (speed/enumRef/raw), TsConfigFile
round-trip (load/patch/add/delete/save preserving symbolic expressions).
2026-06-21 00:13:42 +08:00
panFD
0d28ad7a5e test(config-editor): add hero/skill fixtures mirroring real config shape 2026-06-21 00:07:00 +08:00
panFD
0a960b737c feat(config-editor): add AST parser (speed/enumRef/raw) with tests 2026-06-21 00:06:28 +08:00
panFD
7bb5f8bacc feat(config-editor): add RecordValue serializer with tests 2026-06-21 00:05:53 +08:00
panFD
c0755b3b8d feat(config-editor): add schema types and hero/skill/field table schemas 2026-06-20 23:52:27 +08:00
panFD
88c1a28c80 feat(config-editor): add RecordValue type and enum mirror 2026-06-20 23:46:54 +08:00
panFD
acb038a70a feat(config-editor): scaffold extension manifest, build, i18n 2026-06-20 23:46:15 +08:00
panFD
315a1a6af9 docs(config-editor): add Plan A implementation plan (foundation + IO + tests) 2026-06-20 23:20:59 +08:00
panFD
5170b2d0dc fix(hero config): 调整全英雄基础属性与数值
对所有星级英雄的生命值、攻击力进行了统一平衡性调整,修正部分英雄的面板数值与技能加成匹配度,优化游戏前期和后期的战斗体验平衡。
2026-06-20 23:00:55 +08:00
panFD
d8f02b568b docs(config-editor): add design spec for hero/skill visual config editor extension
Schema-driven Cocos Creator 3.8.6 extension that round-trips the existing
Record<number,X> .ts configs via the TypeScript compiler API (preserves
symbolic AtkSpeedSet expressions and hand-written comments). Non-invasive:
zero changes to game runtime code.
2026-06-20 22:59:20 +08:00
panFD
b7388615ed feat(map): 为英雄信息弹窗和卡牌组件添加点击外部关闭交互
1.  全局添加触摸结束监听,实现点击弹窗/卡牌外区域自动关闭/隐藏控件
2.  通过包围盒检测避免误触内部元素,无需额外遮罩节点
3.  统一管理事件的绑定与解绑,防止内存泄漏
2026-06-20 21:59:29 +08:00
panFD
3056b61ced fix: 修复战斗相关UI显示与技能配置问题
1. 调整RPG地图预制件与UI元素的激活状态,修复战斗面板显示异常
2. 移除技能卡片多余的t_times配置项,简化技能触发逻辑
3. 优化战斗结束后战斗框的显隐控制
2026-06-20 17:53:04 +08:00
panFD
b634cf5383 fix(ui prefab): 调整引导UI位置、文案与样式
1. 修正guide1、guide3、guide4的控件垂直位置
2. 更新guide3的引导提示文案
3. 调整sbox的多处控件颜色为深灰色
4. 移除部分prefab的冗余targetOverrides字段
2026-06-20 17:24:27 +08:00
panFD
57d2805761 fix(map): 调整信息组件等级节点显示逻辑
根据卡片类型区分显示等级节点:技能卡隐藏等级,英雄卡正常显示等级
2026-06-20 16:20:21 +08:00
panFD
62af155ce8 refactor(skill-card): 统一技能卡牌波次配置为单一数据源
1. 新增SKILL_CARD_WAVES常量管理技能卡牌出现波次档位
2. 替换MissionCardComp中硬编码的波次判断逻辑
3. 重构CardSet中的技能卡牌波次映射和配置项,实现自动关联档位
4. 确保所有技能卡牌配置与波次配置严格对齐,避免抽卡池为空的问题
2026-06-20 16:13:13 +08:00
panFD
735bf205fd feat(skillBox): 优化技能框UI表现与图标逻辑
1. 调整ui3.plist.meta的边框内边距为25
2. 新增技能框背景颜色节点,根据等级切换对应配色
3. 增加自定义图标支持,优化多类型技能图标加载逻辑
2026-06-20 16:00:52 +08:00
panFD
4d6403e362 feat(card&ui): add custom card icon support and optimize icon display
1. 新增CardConfig的icon字段用于配置自定义卡牌图标,优先级最高
2. 为HInfoComp新增技能图标节点,区分英雄卡和技能卡的图标展示
3. 重构updateSkillAnimation方法,支持按配置优先级加载图标
4. 优化两种卡牌的图标显示互斥逻辑
2026-06-20 15:11:37 +08:00
panFD
0a281a95d1 refactor(config): 统一初始金币配置并调整刷新时机
1. 将分散的初始金币常量迁移到FightSet枚举中,删除冗余的CardInitCoins
2. 调整刷新费用UI更新时机,确保驻场技能效果正确生效
2026-06-20 14:42:19 +08:00
panFD
107e7fde96 fix(map): 修复卡牌描述逻辑的优先级顺序
调整卡牌描述的获取逻辑,先处理驻场技能卡的描述获取,再 fallback 到其他配置来源,修正原有的优先级混乱问题
2026-06-20 12:42:45 +08:00
panFD
d456b2d61f fix(config): 修正技能卡牌的uuid编号错误
对wave1、wave5、wave8三个难度档位的驻场卡、范围攻击卡的uuid进行了统一修正,确保卡牌编号逻辑一致,避免出现编号冲突或混乱的问题。
2026-06-20 12:32:49 +08:00
panFD
f61c4a506f refactor(config): 整理驻场技能ID与配置映射
本次提交统一调整了所有驻场相关技能的ID编号,将原有分散的技能配置按功能类型重新规整排序,同时同步更新了英雄配置、语言文件、卡牌配置中的技能ID引用,确保所有配置项的ID保持一致且逻辑清晰,修复了亡语法师技能ID不匹配的问题,优化了后续配置扩展的可读性和维护性。
2026-06-20 12:28:23 +08:00
panFD
4247299a86 fix(config): 调整战场技能配置的顺序和位置
将wave5档的基础增益技能和强化增益技能分别整理到正确的代码区块中,修复技能配置乱序的问题
2026-06-20 10:43:30 +08:00
panFD
48a174902d refactor(config): 重新排序野外技能配置条目
将原分散的触发类技能配置统一整理到配置块开头,优化配置可读性与维护性
2026-06-20 10:42:39 +08:00
panFD
b9a3c704c7 fix(config): 调整场地技能配置的位置和冗余注释
清理了旧的注释内容,将购买优惠、刷新优惠类技能调整到正确的分组位置,移除重复冗余的配置代码
2026-06-20 10:40:01 +08:00
panFD
ea34367d7b feat: 新增攻击/受击触发技能次数加成机制
1.  新增FieldSkillType枚举的AtkCount和BeAtkCount类型,添加对应强化技能配置
2.  调整战斗内波次金币、刷新/购买成本参数
3.  重构技能触发逻辑,支持根据字段技能调整触发次数
4.  新增三档强度的技能卡牌配置,优化卡牌池等级映射规则
2026-06-20 10:38:08 +08:00
panFD
1eaaf4ccc5 fix(config): 修改召唤类卡牌的触发波次为1
将原wave值为5的雷墙、火墙等6张技能卡牌的触发波次调整为1,让它们可以在战斗开局就生效。
2026-06-20 00:03:36 +08:00
panFD
e422844717 refactor(skillConfig&prefab): 调整技能配置分组与UI控制器尺寸
1.  重构技能卡牌配置表,调整技能波次分组逻辑
2.  更新角色控制器预制体的尺寸与目标覆盖配置
2026-06-20 00:01:05 +08:00
panFD
5d244e8091 refactor(ui): 移除废弃的技能UI预制件并调整布局
1. 删除了mskills.prefab及其元数据文件
2. 调整了mission.prefab中三处文本的实际字体大小从31改为25
3. 重新排布了MissSkillsComp的技能槽位置
2026-06-19 23:23:56 +08:00
panFD
dc8391847b refactor(cardSkill): 完成卡牌技能触发机制类型化改造
本次提交为全量的卡牌技能触发系统重构,主要变更包括:
1.  新增CardTriggerType枚举,统一卡牌触发类型定义
2.  补全依赖事件派发:每波战斗结束FightEnd、英雄死亡HeroDead(带阵营过滤)、复活成功ReviveSuccess
3.  重构SkillBoxComp,按触发类型动态注册事件监听,拆分即时/定时/驻场/事件型逻辑
4.  批量迁移所有卡牌配置,为旧技能补充显式触发类型
5.  新增全局触发次数上限机制,区分每波/全局触发计数规则
6.  新增配套设计文档,记录改造背景与方案细节

本次重构彻底解决了原有隐式配置难以维护、无法支持事件型触发的痛点,实现了技能触发逻辑的标准化与可扩展性。
2026-06-19 23:01:24 +08:00
panFD
a866cba8d1 fix(config): 调整技能卡牌的配置逻辑与描述
将原有一次性触发的技能改为周期性持续生效,更新技能描述文本,注释掉未完成的复活技能配置
2026-06-19 21:30:02 +08:00
panFD
25346c44a2 fix(card): 修复卡牌信息显示逻辑并调整默认状态
1.  将卡牌预制件默认激活状态改为false
2.  移除冗余的info_node和lvl_node配置,新增lvl_node为空引用
3.  重构卡牌信息文本的获取逻辑,增加多源优先级判断
4.  优化信息节点的Label组件查找方式,适配更灵活的节点结构
2026-06-19 18:05:54 +08:00
panFD
17452167c3 refactor(card): 重构卡牌触摸交互逻辑,替换长按为点击触发信息面板
1. 移除长按相关逻辑,改用点击触发英雄卡信息面板
2. 新增卡牌点击选中联动机制,统一管理召唤按钮显示
3. 调整触摸位移阈值,优化点击和拖拽的判定逻辑
4. 新增卡牌选中事件,实现多卡牌间的UI联动
5. 修复预制体默认激活状态,统一初始UI状态
2026-06-19 15:51:17 +08:00
panFD
c30900e508 feat(CardComp): add call_btn node reference
新增卡片组件的呼叫按钮节点绑定,同步更新预制件配置添加对应的节点id引用
2026-06-19 15:40:39 +08:00
panFD
3d7c9bfe54 feat: 新增技能触发类型标识与列表预制体,优化技能提示UI
1.  新增技能触发类型背景标识,支持追击/反击/复活等状态显示
2.  扩展技能提示接口,新增触发类型参数传递
3.  新增list-me列表预制体及其元数据
4.  调整部分UI精灵帧与布局参数
5.  修复技能名称显示调用参数不匹配问题
2026-06-19 15:40:28 +08:00
panFD
9220254c56 refactor(skill tooltip): 优化技能触发类型命名与 tooltip 背景配置
1. 修正 SkillTriggerName 中的技能触发类型文本翻译,使其更贴合游戏内实际表述
2. 为 tooltip 预制体和组件新增6种状态的背景节点引用
2026-06-19 15:10:19 +08:00
panFD
2d4bc1fd05 fix(ui/skillBox): 修复技能选择框布局与文本问题
调整了技能框预制体的节点位置、尺寸、字体大小,新增了标题文本"选择战斗技能",修正了资源引用id和布局偏移量,优化界面显示效果
2026-06-19 15:02:59 +08:00
panFD
18c873999b fix(missionCard): 修复卡牌升级提示逻辑并优化显示
1.  新增全局配置常量CARD_POOL_UPGRADE_WAVES,优先使用任务运行时配置
2.  重构升级波次计算逻辑,新增多种状态的提示文本:本回合升级、即将升级
3.  修复prefab布局,更新升级提示UI的样式和引用
4.  补充边界情况处理,避免配置耗尽后显示异常
2026-06-19 08:58:56 +08:00
panFD
40c27e04f2 refactor(config): 统一卡池等级与升级波次的数据源
1. 将卡池等级上限从硬编码改为引用FightSet.MAX_CARD_POOL_LEVEL
2. 提取卡池升级波次配置到GameSet中作为单一数据源
3. 移除MissionComp中的硬编码波次配置,改为引用全局配置
2026-06-19 08:38:39 +08:00
panFD
875d2d68b5 refactor: 调整测试模式、英雄等级和卡池配置
1. 开启单挑测试模式
2. 将英雄最大等级从3下调至2
3. 更新卡池升级波次配置为[4,7,10,13]
4. 修复任务预制体的精灵帧和节点缩放配置
2026-06-18 23:26:17 +08:00
panFD
9f738ab881 fix(map,card): 优化卡牌抽取逻辑,新增去重机制
1. 为drawCardsByRule新增unique参数,实现抽取卡牌不重复
2. 修复 fallback 抽取时的重复问题,优先选择未抽到过的卡牌
3. 修复驻场技能卡的图标显示逻辑,使用FieldSkillSet配置
2026-06-18 22:18:05 +08:00
panFD
e0c6622bec refactor(skill config): 统一技能图标资源路径并清理废弃技能
1.  修正预加载的图集资源路径
2.  批量更新所有技能的图标为统一命名的资源
3.  删除冗余的废弃技能配置条目
4.  为场地技能配置新增图标字段并补全对应资源路径
2026-06-18 21:46:40 +08:00
panFD
40cc9ed0f9 fix(gui): 修复多个UI预制体的精灵帧和布局配置
变更包括:
1. 更新talents、heros、sbox预制体的精灵帧资源关联,替换为图集资源
2. 调整部分UI元素的位置、缩放和尺寸参数
3. 修复sbox预制体的默认激活状态
2026-06-18 21:14:23 +08:00
panFD
4d51249b61 style(assets): 更新ui3.png界面资源
替换了assets/resources/gui目录下的ui3.png图片资源
2026-06-18 19:09:30 +08:00
pan
7165fe60d9 refactor(hero-config): 重构英雄配置系统并更新技能弹道类型
1.  将全部技能的线性弹道修改为贝塞尔曲线弹道
2.  重构英雄配置体系,按触发类型重新分类整合所有英雄数据
3.  更新英雄列表排序,适配新的配置结构
2026-06-18 16:41:35 +08:00
pan
8cb81e2db6 docs(heros config): 更新英雄配置文档到v4版本
重构了文档结构,按触发类型重新分组英雄,新增设计原则,补充了完整的新英雄配置和分类说明,移除了旧的流派体系。
2026-06-18 16:08:06 +08:00
panFD
6c6efb640e refactor(guide prefab): 重构新手引导预制体结构
统一将guide1-4预制体改为使用预制件实例嵌套方式,替换原有的硬编码节点组件结构,补充完整嵌套预制体根节点信息,调整部分弹窗位置参数
2026-06-17 23:06:34 +08:00
panFD
c35d14b5b5 fix(map): 修复任务卡片池等级UI显示逻辑
1. 移除了旧的卡池升级按钮UI注释代码
2. 新增卡池等级升级倒计时显示逻辑
3. 修复updateCoinAndCostUI调用updatePoolLvUI的逻辑
4. 从MissionComp获取卡池升级所需波次配置
2026-06-17 23:06:16 +08:00
panFD
eec455cbd9 fix(MissionCardComp): 优化任务卡片提示弹窗动画流程
重新实现提示弹窗的显示动画:添加初始缩放归零设置,调整为弹出回弹、停留再消失的完整动画逻辑
2026-06-17 22:54:13 +08:00
panFD
e6395ba018 refactor(map): 统一使用事件驱动的小提示替代硬编码toast
将多处分散的金币不足、英雄已满等提示逻辑,统一替换为通过GameEvent.ShowSmallTip事件触发的通用小提示组件,替换原有的oops.gui.toast调用,新增通用提示显示逻辑与事件监听
2026-06-17 22:47:45 +08:00
panFD
575b9cf4d3 fix(ui/smalltip): 关闭小提示预制体的默认激活状态
将smalltip.prefab的初始激活状态从true改为false,避免场景加载时自动显示该元素
2026-06-17 21:51:21 +08:00
panFD
3d13b6be46 feat(gui): 新增浮动小提示预制体并更新关联资源
新增smalltip浮动小提示UI预制体,更新技能攻击线预制体的元数据UUID,同时修改ui3.png纹理资源
2026-06-17 21:51:02 +08:00
97 changed files with 35020 additions and 19679 deletions

View File

@@ -1,9 +1,5 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"statusLine": {
"type": "command",
"command": "bash .claude/statusline.sh"
},
"permissions": {
"allow": [
"Bash(git status*)",
@@ -15,7 +11,11 @@
"Bash(dir *)",
"Bash(python -m json.tool*)",
"Bash(python -m pytest*)",
"Bash(py -m pytest*)"
"Bash(py -m pytest*)",
"WebSearch",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(npm install *)"
],
"deny": [
"Bash(rm -rf *)",
@@ -155,5 +155,9 @@
]
}
]
},
"statusLine": {
"type": "command",
"command": "bash .claude/statusline.sh"
}
}

3
.gitignore vendored
View File

@@ -20,7 +20,8 @@ native
# WebStorm
#//////////////////////////
.idea/
extensions/
extensions/*
!extensions/pixelhero-config-editor/
extensions/oops-plugin-framework
# === IDE and Editor ===
.vs/

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "2b7fa972-969e-4bf6-903c-2e8fa9403c01",
"uuid": "2a95d196-6be3-4be8-a810-c179f3125ff5",
"files": [
".json"
],

View File

@@ -164,12 +164,12 @@
"a": 206
},
"_spriteFrame": {
"__uuid__": "2423272e-e63b-4736-b15b-30b40cf98a23@b0413",
"__uuid__": "2423272e-e63b-4736-b15b-30b40cf98a23@586a7",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1,
"_fillType": 1,
"_sizeMode": 1,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -50,19 +50,22 @@
},
{
"__id__": 295
},
{
"__id__": 307
}
],
"_active": true,
"_components": [
{
"__id__": 307
"__id__": 323
},
{
"__id__": 309
"__id__": 325
}
],
"_prefab": {
"__id__": 311
"__id__": 327
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -2599,8 +2602,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -1.378,
"y": 1.378,
"x": 2.438,
"y": 0,
"z": 0
},
"_lrot": {
@@ -2640,8 +2643,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 17.90380859375,
"height": 35.5
"width": 34.748356206795606,
"height": 67
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -2679,16 +2682,16 @@
"_string": "3",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 25,
"_fontSize": 25,
"_actualFontSize": 40,
"_fontSize": 40,
"_fontFamily": "Arial",
"_lineHeight": 25,
"_lineHeight": 50,
"_overflow": 0,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isItalic": true,
"_isBold": true,
"_isUnderline": false,
"_underlineHeight": 2,
@@ -5390,7 +5393,7 @@
"__id__": 296
}
],
"_active": true,
"_active": false,
"_components": [
{
"__id__": 302
@@ -5667,6 +5670,396 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "call",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 308
},
{
"__id__": 314
}
],
"_active": false,
"_components": [
{
"__id__": 320
}
],
"_prefab": {
"__id__": 322
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -23.213,
"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": "btn_yellow",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 307
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 309
},
{
"__id__": 311
}
],
"_prefab": {
"__id__": 313
},
"_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": 0.8,
"y": 0.8,
"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__": 308
},
"_enabled": true,
"__prefab": {
"__id__": 310
},
"_contentSize": {
"__type__": "cc.Size",
"width": 185,
"height": 81
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "f5+WQKeepDpI7jXqgvmtiO"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 308
},
"_enabled": true,
"__prefab": {
"__id__": 312
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "6165ffc9-a838-4a33-b569-bdbaaab0e6b4@b2501",
"__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": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "08MkVcRXlJh64uV97FC5zt"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "979t7Rjp5IGpxx6E++HMV4",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "Label",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 307
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 315
},
{
"__id__": 317
}
],
"_prefab": {
"__id__": 319
},
"_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.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 314
},
"_enabled": true,
"__prefab": {
"__id__": 316
},
"_contentSize": {
"__type__": "cc.Size",
"width": 63.5999755859375,
"height": 54.4
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "feOuVKuYxJK6z+M3tV8Ey3"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 314
},
"_enabled": true,
"__prefab": {
"__id__": 318
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_string": "召唤",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 29.8,
"_fontSize": 29.8,
"_fontFamily": "Arial",
"_lineHeight": 40,
"_overflow": 0,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": true,
"_isUnderline": false,
"_underlineHeight": 2,
"_cacheMode": 0,
"_enableOutline": true,
"_outlineColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_outlineWidth": 2,
"_enableShadow": false,
"_shadowColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_shadowOffset": {
"__type__": "cc.Vec2",
"x": 2,
"y": 2
},
"_shadowBlur": 2,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "00BfCWkAJL/p90pcsuwUDH"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "83KbPaQKpJ3p+hfLZ1PBTs",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 307
},
"_enabled": true,
"__prefab": {
"__id__": 321
},
"_contentSize": {
"__type__": "cc.Size",
"width": 148,
"height": 64.8
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "94GKNoSM1MwIqd0zy183DD"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "57KHqoGQRBmIdHCIXwuEoC",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -5677,7 +6070,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 308
"__id__": 324
},
"_contentSize": {
"__type__": "cc.Size",
@@ -5705,10 +6098,13 @@
},
"_enabled": true,
"__prefab": {
"__id__": 310
"__id__": 326
},
"Lock": null,
"unLock": null,
"call_btn": {
"__id__": 307
},
"name_node": {
"__id__": 257
},
@@ -5725,11 +6121,9 @@
"__id__": 10
},
"info_node": {
"__id__": 295
},
"lvl_node": {
"__id__": 254
"__id__": 296
},
"lvl_node": null,
"ap_node": {
"__id__": 195
},

View File

@@ -1928,7 +1928,7 @@
"__id__": 129
}
],
"_active": false,
"_active": true,
"_components": [
{
"__id__": 135
@@ -1945,7 +1945,7 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 69.648,
"x": 64.648,
"y": 75.307,
"z": 0
},
@@ -2073,7 +2073,7 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4b5bf",
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@23e64",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
@@ -2134,8 +2134,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -1.378,
"y": 1.378,
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
@@ -2175,8 +2175,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 17.90380859375,
"height": 35.5
"width": 34.748356206795606,
"height": 67
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -2214,16 +2214,16 @@
"_string": "3",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 25,
"_fontSize": 25,
"_actualFontSize": 40,
"_fontSize": 40,
"_fontFamily": "Arial",
"_lineHeight": 25,
"_lineHeight": 50,
"_overflow": 0,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isItalic": true,
"_isBold": true,
"_isUnderline": false,
"_underlineHeight": 2,

View File

@@ -28,17 +28,17 @@
"_active": true,
"_components": [
{
"__id__": 51
"__id__": 60
},
{
"__id__": 53
"__id__": 62
},
{
"__id__": 55
"__id__": 64
}
],
"_prefab": {
"__id__": 57
"__id__": 66
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -385,28 +385,28 @@
"__id__": 17
},
{
"__id__": 25
"__id__": 34
},
{
"__id__": 31
"__id__": 40
}
],
"_active": true,
"_components": [
{
"__id__": 46
"__id__": 55
},
{
"__id__": 48
"__id__": 57
}
],
"_prefab": {
"__id__": 50
"__id__": 59
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"y": -71.244,
"z": 0
},
"_lrot": {
@@ -434,178 +434,218 @@
},
{
"__type__": "cc.Node",
"_name": "popupb",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 16
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 18
},
"_prefab": {
"__id__": 18
},
"__editorExtras__": {}
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 17
},
"asset": {
"__uuid__": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
"__expectedType__": "cc.Prefab"
},
"fileId": "d3jYvT7whFS5scQFZeO5AK",
"instance": {
"__id__": 19
},
"targetOverrides": null
},
{
"__type__": "cc.PrefabInstance",
"fileId": "14YFVIWXtFxKTjZkEPn9Vm",
"prefabRootNode": {
"__id__": 1
},
"mountedChildren": [],
"mountedComponents": [],
"propertyOverrides": [
{
"__id__": 20
},
{
"__id__": 22
},
{
"__id__": 23
},
{
"__id__": 24
},
{
"__id__": 25
},
{
"__id__": 26
},
{
"__id__": 28
},
{
"__id__": 30
},
{
"__id__": 32
}
],
"_prefab": {
"__id__": 24
"removedComponents": []
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lpos": {
"propertyPath": [
"_name"
],
"value": "smalltip"
},
{
"__type__": "cc.TargetInfo",
"localID": [
"d3jYvT7whFS5scQFZeO5AK"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lrot": {
"propertyPath": [
"_lrot"
],
"value": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"propertyPath": [
"_euler"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
}
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
},
"_enabled": true,
"__prefab": {
"__id__": 19
},
"_contentSize": {
"__type__": "cc.Size",
"width": 670,
"height": 200
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "7fwZ1GVRVCnq9wVyxvQDkj"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
},
"_enabled": true,
"__prefab": {
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
"propertyPath": [
"_active"
],
"value": true
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 27
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@ec552",
"__expectedType__": "cc.SpriteFrame"
"propertyPath": [
"_active"
],
"value": false
},
{
"__type__": "cc.TargetInfo",
"localID": [
"a3o8h0yV1IRpWNvk7tyKLF"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 29
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 600,
"height": 150
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"3aTUQpkypKh5v6YnH99gAD"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 31
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
"y": -75.679,
"z": 0
}
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "2dDVBrpM5HWZzeeWVlLvsd"
"__type__": "cc.TargetInfo",
"localID": [
"ceD+T7pqtNKrlNgs4enPQc"
]
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 33
},
"_enabled": true,
"__prefab": {
"__id__": 23
},
"_alignFlags": 40,
"_target": null,
"_left": 25,
"_right": 25,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 480,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": -0.557,
"z": 0
}
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d1/xSKIHBPGImeKy2z8ABs"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "43t47xyhJOxrZNbgyVjchG",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
"__type__": "cc.TargetInfo",
"localID": [
"06aFK/VuNKJJkB5vy5eTap"
]
},
{
"__type__": "cc.Node",
@@ -619,14 +659,14 @@
"_active": true,
"_components": [
{
"__id__": 26
"__id__": 35
},
{
"__id__": 28
"__id__": 37
}
],
"_prefab": {
"__id__": 30
"__id__": 39
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -663,11 +703,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 25
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 27
"__id__": 36
},
"_contentSize": {
"__type__": "cc.Size",
@@ -691,11 +731,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 25
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 29
"__id__": 38
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -776,23 +816,23 @@
},
"_children": [
{
"__id__": 32
"__id__": 41
}
],
"_active": false,
"_components": [
{
"__id__": 38
"__id__": 47
},
{
"__id__": 40
"__id__": 49
},
{
"__id__": 42
"__id__": 51
}
],
"_prefab": {
"__id__": 45
"__id__": 54
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -829,20 +869,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 31
"__id__": 40
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 33
"__id__": 42
},
{
"__id__": 35
"__id__": 44
}
],
"_prefab": {
"__id__": 37
"__id__": 46
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -879,11 +919,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 32
"__id__": 41
},
"_enabled": true,
"__prefab": {
"__id__": 34
"__id__": 43
},
"_contentSize": {
"__type__": "cc.Size",
@@ -907,11 +947,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 32
"__id__": 41
},
"_enabled": true,
"__prefab": {
"__id__": 36
"__id__": 45
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -988,11 +1028,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 39
"__id__": 48
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1016,11 +1056,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 41
"__id__": 50
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1061,15 +1101,15 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 43
"__id__": 52
},
"clickEvents": [
{
"__id__": 44
"__id__": 53
}
],
"_interactable": true,
@@ -1148,7 +1188,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 47
"__id__": 56
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1176,7 +1216,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 49
"__id__": 58
},
"_alignFlags": 40,
"_target": null,
@@ -1225,7 +1265,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 52
"__id__": 61
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1253,7 +1293,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 54
"__id__": 63
},
"_alignFlags": 45,
"_target": null,
@@ -1289,7 +1329,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 56
"__id__": 65
},
"guide_id": 1,
"_id": ""
@@ -1308,6 +1348,11 @@
},
"fileId": "6dh4o/8p1Cy5An1p6o4Bc3",
"instance": null,
"targetOverrides": null
"targetOverrides": null,
"nestedPrefabInstanceRoots": [
{
"__id__": 17
}
]
}
]

View File

@@ -28,17 +28,17 @@
"_active": true,
"_components": [
{
"__id__": 51
"__id__": 60
},
{
"__id__": 53
"__id__": 62
},
{
"__id__": 55
"__id__": 64
}
],
"_prefab": {
"__id__": 57
"__id__": 66
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -385,23 +385,23 @@
"__id__": 17
},
{
"__id__": 25
"__id__": 34
},
{
"__id__": 31
"__id__": 40
}
],
"_active": true,
"_components": [
{
"__id__": 46
"__id__": 55
},
{
"__id__": 48
"__id__": 57
}
],
"_prefab": {
"__id__": 50
"__id__": 59
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -434,178 +434,218 @@
},
{
"__type__": "cc.Node",
"_name": "popupb",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 16
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 18
},
"_prefab": {
"__id__": 18
},
"__editorExtras__": {}
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 17
},
"asset": {
"__uuid__": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
"__expectedType__": "cc.Prefab"
},
"fileId": "d3jYvT7whFS5scQFZeO5AK",
"instance": {
"__id__": 19
},
"targetOverrides": null
},
{
"__type__": "cc.PrefabInstance",
"fileId": "26atbF+JFPsKsIxnYa2ikH",
"prefabRootNode": {
"__id__": 1
},
"mountedChildren": [],
"mountedComponents": [],
"propertyOverrides": [
{
"__id__": 20
},
{
"__id__": 22
},
{
"__id__": 23
},
{
"__id__": 24
},
{
"__id__": 25
},
{
"__id__": 26
},
{
"__id__": 28
},
{
"__id__": 30
},
{
"__id__": 32
}
],
"_prefab": {
"__id__": 24
"removedComponents": []
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lpos": {
"propertyPath": [
"_name"
],
"value": "smalltip"
},
{
"__type__": "cc.TargetInfo",
"localID": [
"d3jYvT7whFS5scQFZeO5AK"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lrot": {
"propertyPath": [
"_lrot"
],
"value": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"propertyPath": [
"_euler"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
}
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
},
"_enabled": true,
"__prefab": {
"__id__": 19
},
"_contentSize": {
"__type__": "cc.Size",
"width": 670,
"height": 200
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "7fwZ1GVRVCnq9wVyxvQDkj"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
},
"_enabled": true,
"__prefab": {
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
"propertyPath": [
"_active"
],
"value": true
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 27
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@ec552",
"__expectedType__": "cc.SpriteFrame"
"propertyPath": [
"_active"
],
"value": false
},
{
"__type__": "cc.TargetInfo",
"localID": [
"a3o8h0yV1IRpWNvk7tyKLF"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 29
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 600,
"height": 150
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"3aTUQpkypKh5v6YnH99gAD"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 31
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
"y": -75.679,
"z": 0
}
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "2dDVBrpM5HWZzeeWVlLvsd"
"__type__": "cc.TargetInfo",
"localID": [
"ceD+T7pqtNKrlNgs4enPQc"
]
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 33
},
"_enabled": true,
"__prefab": {
"__id__": 23
},
"_alignFlags": 40,
"_target": null,
"_left": 25,
"_right": 25,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 480,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": -0.557,
"z": 0
}
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d1/xSKIHBPGImeKy2z8ABs"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "43t47xyhJOxrZNbgyVjchG",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
"__type__": "cc.TargetInfo",
"localID": [
"06aFK/VuNKJJkB5vy5eTap"
]
},
{
"__type__": "cc.Node",
@@ -619,14 +659,14 @@
"_active": true,
"_components": [
{
"__id__": 26
"__id__": 35
},
{
"__id__": 28
"__id__": 37
}
],
"_prefab": {
"__id__": 30
"__id__": 39
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -663,11 +703,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 25
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 27
"__id__": 36
},
"_contentSize": {
"__type__": "cc.Size",
@@ -691,11 +731,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 25
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 29
"__id__": 38
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -707,7 +747,7 @@
"b": 255,
"a": 255
},
"_string": "选择一个战斗技能",
"_string": "选择一个战斗「天赋」",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 46,
@@ -776,23 +816,23 @@
},
"_children": [
{
"__id__": 32
"__id__": 41
}
],
"_active": false,
"_components": [
{
"__id__": 38
"__id__": 47
},
{
"__id__": 40
"__id__": 49
},
{
"__id__": 42
"__id__": 51
}
],
"_prefab": {
"__id__": 45
"__id__": 54
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -829,20 +869,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 31
"__id__": 40
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 33
"__id__": 42
},
{
"__id__": 35
"__id__": 44
}
],
"_prefab": {
"__id__": 37
"__id__": 46
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -879,11 +919,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 32
"__id__": 41
},
"_enabled": true,
"__prefab": {
"__id__": 34
"__id__": 43
},
"_contentSize": {
"__type__": "cc.Size",
@@ -907,11 +947,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 32
"__id__": 41
},
"_enabled": true,
"__prefab": {
"__id__": 36
"__id__": 45
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -988,11 +1028,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 39
"__id__": 48
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1016,11 +1056,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 41
"__id__": 50
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1061,15 +1101,15 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 43
"__id__": 52
},
"clickEvents": [
{
"__id__": 44
"__id__": 53
}
],
"_interactable": true,
@@ -1148,7 +1188,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 47
"__id__": 56
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1176,7 +1216,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 49
"__id__": 58
},
"_alignFlags": 42,
"_target": null,
@@ -1225,7 +1265,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 52
"__id__": 61
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1253,7 +1293,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 54
"__id__": 63
},
"_alignFlags": 45,
"_target": null,
@@ -1289,7 +1329,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 56
"__id__": 65
},
"guide_id": 2,
"_id": ""
@@ -1308,6 +1348,11 @@
},
"fileId": "6dh4o/8p1Cy5An1p6o4Bc3",
"instance": null,
"targetOverrides": null
"targetOverrides": null,
"nestedPrefabInstanceRoots": [
{
"__id__": 17
}
]
}
]

View File

@@ -28,17 +28,17 @@
"_active": true,
"_components": [
{
"__id__": 51
"__id__": 60
},
{
"__id__": 53
"__id__": 62
},
{
"__id__": 55
"__id__": 64
}
],
"_prefab": {
"__id__": 57
"__id__": 66
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -381,28 +381,28 @@
"__id__": 17
},
{
"__id__": 25
"__id__": 34
},
{
"__id__": 31
"__id__": 40
}
],
"_active": true,
"_components": [
{
"__id__": 46
"__id__": 55
},
{
"__id__": 48
"__id__": 57
}
],
"_prefab": {
"__id__": 50
"__id__": 59
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 248.35000000000002,
"y": -216.83000000000004,
"z": 0
},
"_lrot": {
@@ -430,178 +430,218 @@
},
{
"__type__": "cc.Node",
"_name": "popupb",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 16
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 18
},
"_prefab": {
"__id__": 18
},
"__editorExtras__": {}
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 17
},
"asset": {
"__uuid__": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
"__expectedType__": "cc.Prefab"
},
"fileId": "d3jYvT7whFS5scQFZeO5AK",
"instance": {
"__id__": 19
},
"targetOverrides": null
},
{
"__type__": "cc.PrefabInstance",
"fileId": "10lq435ItJs7YGjSiwNDfo",
"prefabRootNode": {
"__id__": 1
},
"mountedChildren": [],
"mountedComponents": [],
"propertyOverrides": [
{
"__id__": 20
},
{
"__id__": 22
},
{
"__id__": 23
},
{
"__id__": 24
},
{
"__id__": 25
},
{
"__id__": 26
},
{
"__id__": 28
},
{
"__id__": 30
},
{
"__id__": 32
}
],
"_prefab": {
"__id__": 24
"removedComponents": []
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lpos": {
"propertyPath": [
"_name"
],
"value": "smalltip"
},
{
"__type__": "cc.TargetInfo",
"localID": [
"d3jYvT7whFS5scQFZeO5AK"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"y": -2.664,
"z": 0
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lrot": {
"propertyPath": [
"_lrot"
],
"value": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"propertyPath": [
"_euler"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
}
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
},
"_enabled": true,
"__prefab": {
"__id__": 19
},
"_contentSize": {
"__type__": "cc.Size",
"width": 670,
"height": 200
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "7fwZ1GVRVCnq9wVyxvQDkj"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
},
"_enabled": true,
"__prefab": {
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
"propertyPath": [
"_active"
],
"value": true
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 27
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@ec552",
"__expectedType__": "cc.SpriteFrame"
"propertyPath": [
"_active"
],
"value": false
},
{
"__type__": "cc.TargetInfo",
"localID": [
"a3o8h0yV1IRpWNvk7tyKLF"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 29
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 600,
"height": 150
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"3aTUQpkypKh5v6YnH99gAD"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 31
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
"y": -75.679,
"z": 0
}
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "2dDVBrpM5HWZzeeWVlLvsd"
"__type__": "cc.TargetInfo",
"localID": [
"ceD+T7pqtNKrlNgs4enPQc"
]
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 33
},
"_enabled": true,
"__prefab": {
"__id__": 23
},
"_alignFlags": 40,
"_target": null,
"_left": 25,
"_right": 25,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 480,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": -0.557,
"z": 0
}
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d1/xSKIHBPGImeKy2z8ABs"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "43t47xyhJOxrZNbgyVjchG",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
"__type__": "cc.TargetInfo",
"localID": [
"06aFK/VuNKJJkB5vy5eTap"
]
},
{
"__type__": "cc.Node",
@@ -615,14 +655,14 @@
"_active": true,
"_components": [
{
"__id__": 26
"__id__": 35
},
{
"__id__": 28
"__id__": 37
}
],
"_prefab": {
"__id__": 30
"__id__": 39
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -659,11 +699,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 25
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 27
"__id__": 36
},
"_contentSize": {
"__type__": "cc.Size",
@@ -687,11 +727,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 25
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 29
"__id__": 38
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -703,7 +743,7 @@
"b": 255,
"a": 255
},
"_string": "选中卡牌向上滑动,召唤英雄",
"_string": "上划或点击召唤英雄",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 41,
@@ -772,23 +812,23 @@
},
"_children": [
{
"__id__": 32
"__id__": 41
}
],
"_active": false,
"_components": [
{
"__id__": 38
"__id__": 47
},
{
"__id__": 40
"__id__": 49
},
{
"__id__": 42
"__id__": 51
}
],
"_prefab": {
"__id__": 45
"__id__": 54
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -825,20 +865,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 31
"__id__": 40
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 33
"__id__": 42
},
{
"__id__": 35
"__id__": 44
}
],
"_prefab": {
"__id__": 37
"__id__": 46
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -875,11 +915,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 32
"__id__": 41
},
"_enabled": true,
"__prefab": {
"__id__": 34
"__id__": 43
},
"_contentSize": {
"__type__": "cc.Size",
@@ -903,11 +943,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 32
"__id__": 41
},
"_enabled": true,
"__prefab": {
"__id__": 36
"__id__": 45
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -984,11 +1024,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 39
"__id__": 48
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1012,11 +1052,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 41
"__id__": 50
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1057,15 +1097,15 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 43
"__id__": 52
},
"clickEvents": [
{
"__id__": 44
"__id__": 53
}
],
"_interactable": true,
@@ -1144,7 +1184,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 47
"__id__": 56
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1172,14 +1212,14 @@
},
"_enabled": true,
"__prefab": {
"__id__": 49
"__id__": 58
},
"_alignFlags": 44,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 590,
"_bottom": 788.35,
"_bottom": 323.16999999999996,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
@@ -1221,7 +1261,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 52
"__id__": 61
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1249,7 +1289,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 54
"__id__": 63
},
"_alignFlags": 45,
"_target": null,
@@ -1285,7 +1325,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 56
"__id__": 65
},
"guide_id": 3,
"_id": ""
@@ -1304,6 +1344,10 @@
},
"fileId": "6dh4o/8p1Cy5An1p6o4Bc3",
"instance": null,
"targetOverrides": null
"nestedPrefabInstanceRoots": [
{
"__id__": 17
}
]
}
]

View File

@@ -28,17 +28,17 @@
"_active": true,
"_components": [
{
"__id__": 51
"__id__": 60
},
{
"__id__": 53
"__id__": 62
},
{
"__id__": 55
"__id__": 64
}
],
"_prefab": {
"__id__": 57
"__id__": 66
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -385,28 +385,28 @@
"__id__": 17
},
{
"__id__": 25
"__id__": 34
},
{
"__id__": 31
"__id__": 40
}
],
"_active": true,
"_components": [
{
"__id__": 46
"__id__": 55
},
{
"__id__": 48
"__id__": 57
}
],
"_prefab": {
"__id__": 50
"__id__": 59
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"y": -307.501,
"z": 0
},
"_lrot": {
@@ -434,178 +434,218 @@
},
{
"__type__": "cc.Node",
"_name": "popupb",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 16
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 18
},
"_prefab": {
"__id__": 18
},
"__editorExtras__": {}
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 17
},
"asset": {
"__uuid__": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
"__expectedType__": "cc.Prefab"
},
"fileId": "d3jYvT7whFS5scQFZeO5AK",
"instance": {
"__id__": 19
},
"targetOverrides": null
},
{
"__type__": "cc.PrefabInstance",
"fileId": "94hfqwQnBNSJgHYVjgX7oO",
"prefabRootNode": {
"__id__": 1
},
"mountedChildren": [],
"mountedComponents": [],
"propertyOverrides": [
{
"__id__": 20
},
{
"__id__": 22
},
{
"__id__": 23
},
{
"__id__": 24
},
{
"__id__": 25
},
{
"__id__": 26
},
{
"__id__": 28
},
{
"__id__": 30
},
{
"__id__": 32
}
],
"_prefab": {
"__id__": 24
"removedComponents": []
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lpos": {
"propertyPath": [
"_name"
],
"value": "smalltip"
},
{
"__type__": "cc.TargetInfo",
"localID": [
"d3jYvT7whFS5scQFZeO5AK"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lrot": {
"propertyPath": [
"_lrot"
],
"value": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
}
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"propertyPath": [
"_euler"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
}
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
},
"_enabled": true,
"__prefab": {
"__id__": 19
},
"_contentSize": {
"__type__": "cc.Size",
"width": 670,
"height": 200
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "7fwZ1GVRVCnq9wVyxvQDkj"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
},
"_enabled": true,
"__prefab": {
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 21
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
"propertyPath": [
"_active"
],
"value": true
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 27
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@ec552",
"__expectedType__": "cc.SpriteFrame"
"propertyPath": [
"_active"
],
"value": false
},
{
"__type__": "cc.TargetInfo",
"localID": [
"a3o8h0yV1IRpWNvk7tyKLF"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 29
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 600,
"height": 150
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"3aTUQpkypKh5v6YnH99gAD"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 31
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
"y": -75.679,
"z": 0
}
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "2dDVBrpM5HWZzeeWVlLvsd"
"__type__": "cc.TargetInfo",
"localID": [
"ceD+T7pqtNKrlNgs4enPQc"
]
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 17
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 33
},
"_enabled": true,
"__prefab": {
"__id__": 23
},
"_alignFlags": 40,
"_target": null,
"_left": 25,
"_right": 25,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 480,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": -0.557,
"z": 0
}
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d1/xSKIHBPGImeKy2z8ABs"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "43t47xyhJOxrZNbgyVjchG",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
"__type__": "cc.TargetInfo",
"localID": [
"06aFK/VuNKJJkB5vy5eTap"
]
},
{
"__type__": "cc.Node",
@@ -619,14 +659,14 @@
"_active": true,
"_components": [
{
"__id__": 26
"__id__": 35
},
{
"__id__": 28
"__id__": 37
}
],
"_prefab": {
"__id__": 30
"__id__": 39
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -663,11 +703,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 25
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 27
"__id__": 36
},
"_contentSize": {
"__type__": "cc.Size",
@@ -691,11 +731,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 25
"__id__": 34
},
"_enabled": true,
"__prefab": {
"__id__": 29
"__id__": 38
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -776,23 +816,23 @@
},
"_children": [
{
"__id__": 32
"__id__": 41
}
],
"_active": false,
"_components": [
{
"__id__": 38
"__id__": 47
},
{
"__id__": 40
"__id__": 49
},
{
"__id__": 42
"__id__": 51
}
],
"_prefab": {
"__id__": 45
"__id__": 54
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -829,20 +869,20 @@
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 31
"__id__": 40
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 33
"__id__": 42
},
{
"__id__": 35
"__id__": 44
}
],
"_prefab": {
"__id__": 37
"__id__": 46
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -879,11 +919,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 32
"__id__": 41
},
"_enabled": true,
"__prefab": {
"__id__": 34
"__id__": 43
},
"_contentSize": {
"__type__": "cc.Size",
@@ -907,11 +947,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 32
"__id__": 41
},
"_enabled": true,
"__prefab": {
"__id__": 36
"__id__": 45
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -988,11 +1028,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 39
"__id__": 48
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1016,11 +1056,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 41
"__id__": 50
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1061,15 +1101,15 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 31
"__id__": 40
},
"_enabled": true,
"__prefab": {
"__id__": 43
"__id__": 52
},
"clickEvents": [
{
"__id__": 44
"__id__": 53
}
],
"_interactable": true,
@@ -1148,7 +1188,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 47
"__id__": 56
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1176,7 +1216,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 49
"__id__": 58
},
"_alignFlags": 40,
"_target": null,
@@ -1225,7 +1265,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 52
"__id__": 61
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1253,7 +1293,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 54
"__id__": 63
},
"_alignFlags": 45,
"_target": null,
@@ -1289,7 +1329,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 56
"__id__": 65
},
"guide_id": 4,
"_id": ""
@@ -1308,6 +1348,10 @@
},
"fileId": "6dh4o/8p1Cy5An1p6o4Bc3",
"instance": null,
"targetOverrides": null
"nestedPrefabInstanceRoots": [
{
"__id__": 17
}
]
}
]

View File

@@ -7265,7 +7265,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 656.792,
"y": 641.191,
"z": 0
},
"_lrot": {
@@ -7277,8 +7277,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1.9,
"y": 1.9,
"x": 1.8,
"y": 1.8,
"z": 1
},
"_mobility": 0,
@@ -7342,12 +7342,12 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@3c27d",
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4dd1c",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
@@ -7357,7 +7357,10 @@
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_atlas": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
@@ -7380,7 +7383,7 @@
"_target": null,
"_left": 0,
"_right": 0,
"_top": -119.39200000000005,
"_top": -98.39100000000008,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,12 @@
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "0f2aeee0-d590-4c36-9f20-a93b058d5b91",
"uuid": "b2a3067a-af88-4ff7-acdd-7474b8a163d8",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "mskills"
"syncNodeName": "list-me"
}
}

View File

@@ -185,10 +185,10 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d1468",
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d5229",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
@@ -200,7 +200,10 @@
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_atlas": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
@@ -279,8 +282,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -297.949,
"y": 0,
"x": -276.372,
"y": 8.188,
"z": 0
},
"_lrot": {
@@ -418,8 +421,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -299.563,
"y": 0,
"x": -277.986,
"y": 8.188,
"z": 0
},
"_lrot": {
@@ -577,8 +580,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -217.727,
"y": 0,
"x": -214.902,
"y": 8.188,
"z": 0
},
"_lrot": {
@@ -590,8 +593,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.35,
"y": 0.35,
"x": 0.3,
"y": 0.3,
"z": 1
},
"_mobility": 0,
@@ -713,8 +716,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -139.467,
"y": 0,
"x": -136.642,
"y": 8.188,
"z": 0
},
"_lrot": {
@@ -872,8 +875,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 225.016,
"y": 0,
"x": 167.228,
"y": 8.1,
"z": 0
},
"_lrot": {
@@ -952,8 +955,8 @@
"_string": "999999",
"_horizontalAlign": 0,
"_verticalAlign": 1,
"_actualFontSize": 26,
"_fontSize": 25,
"_actualFontSize": 31,
"_fontSize": 30,
"_fontFamily": "Arial",
"_lineHeight": 40,
"_overflow": 2,
@@ -1031,8 +1034,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 181.966,
"y": 0,
"x": 140.032,
"y": 8.1,
"z": 0
},
"_lrot": {
@@ -1044,8 +1047,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.4,
"y": 0.4,
"x": 0.3,
"y": 0.3,
"z": 1
},
"_mobility": 0,

File diff suppressed because it is too large Load Diff

View File

@@ -1,203 +0,0 @@
[
{
"__type__": "cc.Prefab",
"_name": "mskills",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "mskills",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [],
"_active": true,
"_components": [
{
"__id__": 2
},
{
"__id__": 4
},
{
"__id__": 6
},
{
"__id__": 8
}
],
"_prefab": {
"__id__": 10
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -180,
"y": 990,
"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": 1,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 3
},
"_contentSize": {
"__type__": "cc.Size",
"width": 360,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "4dNsDtN3ZGiZ+om9xELLFQ"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 5
},
"_alignFlags": 10,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 350,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "baKsIzXktAyqqNBZKVQXO+"
},
{
"__type__": "68387wnH45AVo6Nco+pXtG5",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 7
},
"skill_box": {
"__uuid__": "d19cde30-f5d0-47de-a0d5-3a272b696343",
"__expectedType__": "cc.Prefab"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d4AyZdLXNB07iSaFvEU6BB"
},
{
"__type__": "cc.Layout",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 9
},
"_resizeMode": 0,
"_layoutType": 1,
"_cellSize": {
"__type__": "cc.Size",
"width": 40,
"height": 40
},
"_startAxis": 0,
"_paddingLeft": 10,
"_paddingRight": 0,
"_paddingTop": 0,
"_paddingBottom": 0,
"_spacingX": 10,
"_spacingY": 0,
"_verticalDirection": 1,
"_horizontalDirection": 0,
"_constraint": 0,
"_constraintNum": 2,
"_affectedByScale": false,
"_isAlign": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "786tQWlIxKHLEpV0oUK9dj"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "5622mxbS1PNqMFP0FH5Mir",
"targetOverrides": null
}
]

View File

@@ -22,32 +22,32 @@
"__id__": 2
},
{
"__id__": 35
"__id__": 63
},
{
"__id__": 41
"__id__": 69
},
{
"__id__": 47
"__id__": 75
},
{
"__id__": 53
"__id__": 81
}
],
"_active": true,
"_components": [
{
"__id__": 59
"__id__": 87
},
{
"__id__": 61
"__id__": 89
},
{
"__id__": 63
"__id__": 91
}
],
"_prefab": {
"__id__": 65
"__id__": 93
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -169,6 +169,48 @@
},
{
"__id__": 34
},
{
"__id__": 35
},
{
"__id__": 37
},
{
"__id__": 39
},
{
"__id__": 41
},
{
"__id__": 43
},
{
"__id__": 45
},
{
"__id__": 47
},
{
"__id__": 49
},
{
"__id__": 51
},
{
"__id__": 53
},
{
"__id__": 55
},
{
"__id__": 57
},
{
"__id__": 59
},
{
"__id__": 61
}
],
"removedComponents": []
@@ -474,6 +516,280 @@
],
"value": 0
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 36
},
"propertyPath": [
"_active"
],
"value": true
},
{
"__type__": "cc.TargetInfo",
"localID": [
"4aTybcwdBL1IFgubnxRuUh"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 38
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 220,
"height": 270
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"eedc9qvxRPQpepecxrSorR"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 40
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 440,
"height": 540
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"deY6hTveBKBIzSn0k9oD+7"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 42
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 432,
"height": 528
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"b7HYchf9tMK7WtemLDPjlE"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 44
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 190,
"height": 140
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"25j0n7apFBratTNBweGTa7"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 46
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 50,
"z": 0
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"93O2t0s3dBQYpkATdTKBPZ"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 48
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 380,
"height": 280
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"1fWMnHXs1IPZv24wLXcOc2"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 50
},
"propertyPath": [
"_active"
],
"value": true
},
{
"__type__": "cc.TargetInfo",
"localID": [
"34+q4uHrVMer4aH0jqs5JM"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 52
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 220,
"height": 270
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"2bJbEaLWxKYIZXGRHTBM1m"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 54
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 440,
"height": 540
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"8b8xEuZsBB+KDupKZRRki9"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 56
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 432,
"height": 528
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"30uXpT+kpFI5rT0dm/gN9k"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 58
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 190,
"height": 140
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"c6lDSuSgFMJaDP3gZWwz54"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 60
},
"propertyPath": [
"_lpos"
],
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 50,
"z": 0
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"24Xd/896JIz4KMqBZHi94n"
]
},
{
"__type__": "CCPropertyOverrideInfo",
"targetInfo": {
"__id__": 62
},
"propertyPath": [
"_contentSize"
],
"value": {
"__type__": "cc.Size",
"width": 380,
"height": 280
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"1dy2U4eNdI6KgBqcoGyOOb"
]
},
{
"__type__": "cc.Node",
"_name": "icon",
@@ -486,14 +802,14 @@
"_active": true,
"_components": [
{
"__id__": 36
"__id__": 64
},
{
"__id__": 38
"__id__": 66
}
],
"_prefab": {
"__id__": 40
"__id__": 68
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -530,11 +846,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 35
"__id__": 63
},
"_enabled": true,
"__prefab": {
"__id__": 37
"__id__": 65
},
"_contentSize": {
"__type__": "cc.Size",
@@ -558,11 +874,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 35
"__id__": 63
},
"_enabled": true,
"__prefab": {
"__id__": 39
"__id__": 67
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -625,14 +941,14 @@
"_active": true,
"_components": [
{
"__id__": 42
"__id__": 70
},
{
"__id__": 44
"__id__": 72
}
],
"_prefab": {
"__id__": 46
"__id__": 74
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -669,11 +985,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 41
"__id__": 69
},
"_enabled": true,
"__prefab": {
"__id__": 43
"__id__": 71
},
"_contentSize": {
"__type__": "cc.Size",
@@ -697,11 +1013,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 41
"__id__": 69
},
"_enabled": true,
"__prefab": {
"__id__": 45
"__id__": 73
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -784,14 +1100,14 @@
"_active": true,
"_components": [
{
"__id__": 48
"__id__": 76
},
{
"__id__": 50
"__id__": 78
}
],
"_prefab": {
"__id__": 52
"__id__": 80
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -828,11 +1144,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 47
"__id__": 75
},
"_enabled": true,
"__prefab": {
"__id__": 49
"__id__": 77
},
"_contentSize": {
"__type__": "cc.Size",
@@ -856,11 +1172,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 47
"__id__": 75
},
"_enabled": true,
"__prefab": {
"__id__": 51
"__id__": 79
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -943,14 +1259,14 @@
"_active": false,
"_components": [
{
"__id__": 54
"__id__": 82
},
{
"__id__": 56
"__id__": 84
}
],
"_prefab": {
"__id__": 58
"__id__": 86
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -987,11 +1303,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 53
"__id__": 81
},
"_enabled": true,
"__prefab": {
"__id__": 55
"__id__": 83
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1015,11 +1331,11 @@
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 53
"__id__": 81
},
"_enabled": true,
"__prefab": {
"__id__": 57
"__id__": 85
},
"_customMaterial": null,
"_srcBlendFactor": 2,
@@ -1100,7 +1416,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 60
"__id__": 88
},
"_contentSize": {
"__type__": "cc.Size",
@@ -1128,7 +1444,7 @@
},
"_enabled": false,
"__prefab": {
"__id__": 62
"__id__": 90
},
"_alignFlags": 40,
"_target": null,
@@ -1164,16 +1480,16 @@
},
"_enabled": true,
"__prefab": {
"__id__": 64
"__id__": 92
},
"lbl_name": {
"__id__": 44
"__id__": 72
},
"lbl_info": {
"__id__": 50
"__id__": 78
},
"icon": {
"__id__": 38
"__id__": 66
},
"bg": null,
"_id": ""
@@ -1194,7 +1510,7 @@
"instance": null,
"targetOverrides": [
{
"__id__": 66
"__id__": 94
}
],
"nestedPrefabInstanceRoots": [
@@ -1206,7 +1522,7 @@
{
"__type__": "cc.TargetOverrideInfo",
"source": {
"__id__": 63
"__id__": 91
},
"sourceInfo": null,
"propertyPath": [
@@ -1216,7 +1532,7 @@
"__id__": 2
},
"targetInfo": {
"__id__": 67
"__id__": 95
}
},
{

View File

@@ -3292,7 +3292,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 617.5,
"y": 657.577,
"z": 0
},
"_lrot": {
@@ -3304,8 +3304,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"x": 1.5,
"y": 1.5,
"z": 1
},
"_mobility": 0,
@@ -3332,8 +3332,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 740,
"height": 55
"width": 540,
"height": 117
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -3369,12 +3369,12 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@98637",
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4dd1c",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
@@ -3384,7 +3384,10 @@
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_atlas": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
@@ -3399,15 +3402,15 @@
"node": {
"__id__": 134
},
"_enabled": true,
"_enabled": false,
"__prefab": {
"__id__": 140
},
"_alignFlags": 41,
"_target": null,
"_left": -10,
"_right": -10,
"_top": -5,
"_left": 90,
"_right": 0,
"_top": -89.59100000000001,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
@@ -3629,10 +3632,10 @@
"__id__": 151
},
"asset": {
"__uuid__": "0c80c911-642f-423a-8ebb-98aebbbe2ef0",
"__uuid__": "b2a3067a-af88-4ff7-acdd-7474b8a163d8",
"__expectedType__": "cc.Prefab"
},
"fileId": "70V66T2DlAP7ksTOaOBDja",
"fileId": "43WU05gfVAPr5YF69YW+M4",
"instance": {
"__id__": 153
},
@@ -3640,7 +3643,7 @@
},
{
"__type__": "cc.PrefabInstance",
"fileId": "50AgFB9kROhppT8Q5wAY6S",
"fileId": "e722VC+kdPfqeaFe1joI7B",
"prefabRootNode": {
"__id__": 1
},
@@ -3670,12 +3673,12 @@
"propertyPath": [
"_name"
],
"value": "melist"
"value": "list-me"
},
{
"__type__": "cc.TargetInfo",
"localID": [
"70V66T2DlAP7ksTOaOBDja"
"43WU05gfVAPr5YF69YW+M4"
]
},
{
@@ -4318,7 +4321,7 @@
"__expectedType__": "cc.Prefab"
},
"melist_prefab": {
"__uuid__": "0c80c911-642f-423a-8ebb-98aebbbe2ef0",
"__uuid__": "b2a3067a-af88-4ff7-acdd-7474b8a163d8",
"__expectedType__": "cc.Prefab"
},
"_id": ""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,583 @@
[
{
"__type__": "cc.Prefab",
"_name": "smalltip",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "smalltip",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": false,
"_components": [
{
"__id__": 22
}
],
"_prefab": {
"__id__": 24
},
"_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": "box",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 3
},
{
"__id__": 11
}
],
"_active": true,
"_components": [
{
"__id__": 17
},
{
"__id__": 19
}
],
"_prefab": {
"__id__": 21
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 63.35300000000001,
"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": "Arrow",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 4
},
{
"__id__": 6
},
{
"__id__": 8
}
],
"_prefab": {
"__id__": 10
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -35.679,
"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.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": {
"__id__": 5
},
"_contentSize": {
"__type__": "cc.Size",
"width": 38,
"height": 35
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "e3LajWvQdNHo2ofzBHiAy7"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": {
"__id__": 7
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4bcea",
"__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": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "517oJrn2hJXKJIupkBAN7f"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": {
"__id__": 9
},
"_alignFlags": 20,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": -18.179000000000002,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "82QThq5MNOsYFImPKMW3Re"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "ceD+T7pqtNKrlNgs4enPQc",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "Label",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 12
},
{
"__id__": 14
}
],
"_prefab": {
"__id__": 16
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 2.877,
"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.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 11
},
"_enabled": true,
"__prefab": {
"__id__": 13
},
"_contentSize": {
"__type__": "cc.Size",
"width": 84,
"height": 54.4
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "11pqUo9t9IWYWj/LjK3fNo"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 11
},
"_enabled": true,
"__prefab": {
"__id__": 15
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_string": "卡成升级",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 20,
"_fontSize": 20,
"_fontFamily": "Arial",
"_lineHeight": 40,
"_overflow": 0,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": true,
"_isUnderline": false,
"_underlineHeight": 2,
"_cacheMode": 0,
"_enableOutline": true,
"_outlineColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_outlineWidth": 2,
"_enableShadow": false,
"_shadowColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_shadowOffset": {
"__type__": "cc.Vec2",
"x": 2,
"y": 2
},
"_shadowBlur": 2,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "52GlHcXJZDJqYfTH62X5lp"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "a3o8h0yV1IRpWNvk7tyKLF",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 18
},
"_contentSize": {
"__type__": "cc.Size",
"width": 150,
"height": 70
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "3aTUQpkypKh5v6YnH99gAD"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 20
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@d552a",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "a6LRCKiMBA7pI2xOs2dxQb"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "06aFK/VuNKJJkB5vy5eTap",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 23
},
"_contentSize": {
"__type__": "cc.Size",
"width": 100,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "1ctkO0TStCjK1FILIEJzwK"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "d3jYvT7whFS5scQFZeO5AK",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "86a3c4cd-71c7-4502-b713-65f0ace7912a",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "smalltip"
}
}

View File

@@ -1519,12 +1519,12 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@3c27d",
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@4dd1c",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
@@ -1534,7 +1534,10 @@
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_atlas": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{

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: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -21,12 +21,12 @@
"role_power": "Power",
"role_physical": "Physical",
"role_agile": "Agile",
"fskill_name_7009": "Frost Domain",
"fskill_name_7010": "Deadly Focus",
"fskill_name_7011": "Rending Strike",
"fskill_name_7012": "Gale March",
"fskill_info_7009": "Increase all allied heroes' freeze chance by {0}%",
"fskill_info_7010": "Increase all allied heroes' critical chance by {0}%",
"fskill_info_7011": "Increase all allied heroes' critical damage by {0}%",
"fskill_info_7012": "Increase all allied heroes' attack speed by {0}%"
"fskill_name_7003": "Frost Domain",
"fskill_name_7004": "Deadly Focus",
"fskill_name_7005": "Rending Strike",
"fskill_name_7006": "Gale March",
"fskill_info_7003": "Increase all allied heroes' freeze chance by {0}%",
"fskill_info_7004": "Increase all allied heroes' critical chance by {0}%",
"fskill_info_7005": "Increase all allied heroes' critical damage by {0}%",
"fskill_info_7006": "Increase all allied heroes' attack speed by {0}%"
}

View File

@@ -144,31 +144,31 @@
"scard_info_7102": "刷新卡池,都是远程英雄",
"scard_info_7103": "刷新卡池都是3级卡池等级英雄",
"fskill_name_7001": "召唤精通",
"fskill_name_7002": "亡灵统御",
"fskill_name_7003": "先发制人",
"fskill_name_7004": "余音绕梁",
"fskill_name_7005": "理财专家",
"fskill_name_7006": "商业大亨",
"fskill_name_7007": "神圣恢复",
"fskill_name_7008": "战鼓激昂",
"fskill_name_7009": "寒霜领域",
"fskill_name_7010": "致命专注",
"fskill_name_7011": "裂伤打击",
"fskill_name_7012": "疾风战歌",
"fskill_name_7014": "召唤精通",
"fskill_name_7015": "亡灵统御",
"fskill_name_7016": "先发制人",
"fskill_name_7017": "余音绕梁",
"fskill_name_7010": "理财专家",
"fskill_name_7011": "商业大亨",
"fskill_name_7001": "神圣恢复",
"fskill_name_7002": "战鼓激昂",
"fskill_name_7003": "寒霜领域",
"fskill_name_7004": "致命专注",
"fskill_name_7005": "裂伤打击",
"fskill_name_7006": "疾风战歌",
"fskill_info_7001": "场上所有友方召唤触发技能触发次数+{0}",
"fskill_info_7002": "场上所有友方死亡触发技能触发次数+{0}",
"fskill_info_7003": "场上所有友方战斗开始触发技能触发次数+{0}",
"fskill_info_7004": "场上所有友方战斗结束触发技能触发次数+{0}",
"fskill_info_7005": "每回合结束时金币收益提升{0}",
"fskill_info_7006": "卖出英雄时金币收益提升{0}",
"fskill_info_7007": "战斗结束时全队恢复效果+{0}%",
"fskill_info_7008": "场上所有友方攻击力提升{0}%",
"fskill_info_7009": "场上所有友方冰冻概率提升{0}%",
"fskill_info_7010": "场上所有友方暴击率提升{0}%",
"fskill_info_7011": "场上所有友方暴击伤害提升{0}%",
"fskill_info_7012": "场上所有友方攻击速度提升{0}%",
"fskill_info_7014": "场上所有友方召唤触发技能触发次数+{0}",
"fskill_info_7015": "场上所有友方死亡触发技能触发次数+{0}",
"fskill_info_7016": "场上所有友方战斗开始触发技能触发次数+{0}",
"fskill_info_7017": "场上所有友方战斗结束触发技能触发次数+{0}",
"fskill_info_7010": "每回合结束时金币收益提升{0}",
"fskill_info_7011": "卖出英雄时金币收益提升{0}",
"fskill_info_7001": "战斗结束时全队恢复效果+{0}%",
"fskill_info_7002": "场上所有友方攻击力提升{0}%",
"fskill_info_7003": "场上所有友方冰冻概率提升{0}%",
"fskill_info_7004": "场上所有友方暴击率提升{0}%",
"fskill_info_7005": "场上所有友方暴击伤害提升{0}%",
"fskill_info_7006": "场上所有友方攻击速度提升{0}%",
"hl_title_CritMaster_1": "初级暴击者",
"hl_title_CritMaster_2": "暴击大师",

View File

@@ -15,8 +15,8 @@
* | mon_name | 怪物名称 | mon_name_6001 | 兽人战士 |
* | skill_name | 技能名称 | skill_name_6001 | 攻击 |
* | skill_info | 技能描述 | skill_info_6001 | 对单个目标攻击... |
* | fskill_name | 驻场技能名称 | fskill_name_7001 | 召唤精通 |
* | fskill_info | 驻场技能描述 | fskill_info_7001 | 召唤触发... |
* | fskill_name | 驻场技能名称 | fskill_name_7014 | 召唤精通 |
* | fskill_info | 驻场技能描述 | fskill_info_7014 | 召唤触发... |
* | scard_name | 特殊卡牌名称 | scard_name_7001 | 战术晋升 |
* | scard_info | 特殊卡牌描述 | scard_info_7001 | 升级场上随机... |
* | hl_name | 亮点成就名称 | hl_name_9001 | 暴击大师 |
@@ -30,8 +30,8 @@
* langf(LangPrefix.skill_info, 6001, 1, 100) // → "对单个目标攻击1次每次造成100%攻击的伤害"
*
* // 驻场技能
* lang(LangPrefix.fskill_name, 7001) // → "召唤精通"
* langf(LangPrefix.fskill_info, 7001, 1) // → "场上所有友方召唤触发技能触发次数+1"
* lang(LangPrefix.fskill_name, 7014) // → "召唤精通"
* langf(LangPrefix.fskill_info, 7014, 1) // → "场上所有友方召唤触发技能触发次数+1"
*
* // 亮点成就(与英雄/技能完全一致的调用方式)
* lang(LangPrefix.hl_title, 9011) // → "初级暴击者"

View File

@@ -283,13 +283,13 @@ export class SingletonModuleComp extends ecs.Comp {
* 在游戏载入早期调用,预加载常用图集
*/
preloadCommonAssets() {
resources.load("gui/uicons", SpriteAtlas, (err, atlas) => {
resources.load("gui/ui3", SpriteAtlas, (err, atlas) => {
if (!err && atlas) {
// 增加引用计数防止图集被引擎自动垃圾回收GC导致底层 spriteFrames 为 null
atlas.addRef();
this.uiconsAtlas = atlas;
} else {
mLogger.error(this.debugMode, 'SMC', "预加载 gui/uicons 图集失败:", err);
mLogger.error(this.debugMode, 'SMC', "预加载 gui/ui3 图集失败:", err);
}
});
}

View File

@@ -1,6 +1,6 @@
import * as exp from "constants"
import { HeroInfo, HeroList, HType } from "./heroSet"
import { FightSet } from "./GameSet"
import { FightSet, SKILL_CARD_WAVES } from "./GameSet"
import { oops } from "db://oops-framework/core/Oops"
import { SkillOverrides, TGroup } from "./SkillSet"
@@ -37,6 +37,16 @@ export enum CKind {
Potion = 4, //药水
}
/** 技能卡触发类型 */
export enum CardSkillType {
Interval = 1, // 间隔定时触发 (战斗中每隔N秒执行)
Field = 2, // 驻场技能 (被动光环)
BattleStart = 3, // 战斗开始时触发一次
BattleEnd = 4, // 战斗结束时触发一次
HeroDead = 5, // 场上己方英雄死亡时触发
HeroCall = 6, // 场上己方英雄召唤上场时触发
}
/** 卡池等级定义 */
export enum CardLV {
LV1 = 1,
@@ -46,6 +56,21 @@ export enum CardLV {
LV5 = 5,
}
/**
* 卡牌技能触发类型
* - 命名对齐英雄侧 SkillTriggerType便于跨模块认知统一
* - 枚举值从 1 开始,避免 0 的 falsy 坑if (trigger_type) 判断出错)
*/
export enum CardTriggerType {
Instant = 1, // 即时触发:使用后立即生效一次
Interval = 2, // 定时循环:战斗中按 t_inv 间隔重复触发
Field = 3, // 驻场光环:被动生效(仅显式分类,仍由 field 字段驱动)
FightStart = 4, // 战斗开始时触发
FightEnd = 5, // 战斗结束时触发(每波结束)
HeroDead = 6, // 场上己方英雄死亡时触发
HeroCall = 7, // 英雄上场时触发(主角召唤 + 技能召唤 + 复活)
}
/** 通用卡牌配置 */
export interface CardConfig {
uuid: number
@@ -61,6 +86,7 @@ export interface CardConfig {
// 技能卡扩展属性
skill?: number // 关联的技能 UUID
icon?: string // 图标ID可选优先使用未设置时按 trigger_type 从 SkillSet/FieldSkillSet 自动取)
name?: string // 卡牌名称
info?: string // 卡牌描述信息
is_inst?: boolean // 是否即时起效
@@ -69,6 +95,15 @@ export interface CardConfig {
keep_waves?: number // 维持的波次数(-1表示持续到战斗结束0或undefined表示仅本波次
overrides?: SkillOverrides // 技能参数覆写如自定义伤害ap、buff值、金币数等
field?: number[] // 驻场技能 UUID 数组,表示该卡牌提供驻场属性加成
/** 触发类型(必填,技能卡专用;功能卡/英雄卡可缺省) */
trigger_type?: CardTriggerType;
/**
* 事件型触发的全局次数上限(仅 FightStart/FightEnd/HeroDead/HeroCall 有效)
* 默认 Infinity达到上限后销毁节点
* 注意:与 t_times 语义不同——t_times 控制每波内 Interval 的次数
*/
trigger_limit?: number;
}
export const CardsUpSet: Record<number, number> = {
1: 50,
@@ -78,14 +113,12 @@ export const CardsUpSet: Record<number, number> = {
5: 250,
}
/**初始coin数 */
export const CardInitCoins = 4
/** 卡池升级每波减免金额 */
export const CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE = 10
/** 卡池默认初始等级 */
export const CARD_POOL_INIT_LEVEL = CardLV.LV1
/** 卡池等级上限 */
export const CARD_POOL_MAX_LEVEL = CardLV.LV5
/** 卡池等级上限(统一由 FightSet.MAX_CARD_POOL_LEVEL 设定,保持单一数据源) */
export const CARD_POOL_MAX_LEVEL = FightSet.MAX_CARD_POOL_LEVEL as unknown as CardLV
/** 英雄最高等级限制 */
export const CARD_HERO_MAX_LEVEL = 1
/** 基础卡池(英雄、技能、功能) */
@@ -140,58 +173,82 @@ HeroList.forEach(uuid => {
});
// 添加非英雄卡牌 (技能、功能卡)
const waveToPoolLv: Record<number, number> = {
1: 1,
5: 2,
10: 3,
15: 4,
20: 5
};
// 体系wave 由 SKILL_CARD_WAVES 统一配置每档强度递增Field 靠 field uuid 区分数值Interval 靠 overrides 覆写)
// wave→pool_lv 映射由 SKILL_CARD_WAVES 索引+1 自动生成wave 1→lv1, wave 5→lv2, wave 8→lv3
const waveToPoolLv: Record<number, number> = {};
SKILL_CARD_WAVES.forEach((w, i) => { waveToPoolLv[w] = i + 1; });
const SkillCardData: any[] = [
// === 1波技能 ===
{ uuid: 8301, skill: 6301, wave: 1, name: "护盾", info: "为伙伴/自己添加护盾可抵挡3次伤害", is_inst: true, keep_waves: 15 },
{ uuid: 8302, skill: 6302, wave: 1, name: "治疗", info: "治疗伙伴/自己", is_inst: true, keep_waves: 15 },
{ uuid: 8705, skill: 0, wave: 1, name: "金币收益", info: "每回合金币收益+1", is_inst: false, keep_waves: -1, field: [7005] },
{ uuid: 8706, skill: 0, wave: 1, name: "出售强化", info: "卖出英雄金币+1", is_inst: false, keep_waves: -1, field: [7006] },
{ uuid: 8707, skill: 0, wave: 1, name: "战后恢复", info: "战斗结束生命回复量+10%", is_inst: false, keep_waves: -1, field: [7007] },
// ==================== wave 1 档(基础强度) ====================
// --- 驻场卡Field ---
// === 5波技能 ===
{ uuid: 8303, skill: 6303, wave: 5, name: "获取金币", info: "增加一定数量的金币", is_inst: true, keep_waves: 15 },
{ uuid: 8401, skill: 6401, wave: 5, name: "攻击强化", info: "全体友方攻击力提升5点持续1次", is_inst: true, keep_waves: 15 },
{ uuid: 8402, skill: 6402, wave: 5, name: "生命强化", info: "全体友方最大生命值提升20点持续1次", is_inst: true, keep_waves: 15 },
{ uuid: 8403, skill: 6403, wave: 5, name: "暴击强化", info: "全体友方暴击率提升10%持续1次", is_inst: true, keep_waves: 15 },
{ uuid: 8404, skill: 6404, wave: 5, name: "暴伤强化", info: "全体友方暴击伤害提升20%持续1次", is_inst: true, keep_waves: 15 },
{ uuid: 8405, skill: 6405, wave: 5, name: "击晕强化", info: "全体友方击晕概率提升10%持续1次", is_inst: true, keep_waves: 15 },
{ uuid: 8408, skill: 6408, wave: 5, name: "穿刺强化", info: "全体友方穿透概率提升20%持续1次", is_inst: true, keep_waves: 15 },
{ uuid: 8409, skill: 6409, wave: 5, name: "风怒强化", info: "全体友方风怒次数提升1次持续1次", is_inst: true, keep_waves: 15 },
{ uuid: 8501, skill: 6501, wave: 5, name: "复活", info: "ap 代表复活的生命值百分比", is_inst: true, keep_waves: 15 },
{ uuid: 8701, skill: 0, wave: SKILL_CARD_WAVES[0], name: "战后恢复", info: "战斗结束生命回复量+10%", is_inst: false, keep_waves: -1, field: [7001], trigger_type: CardTriggerType.Field },
{ uuid: 8702, skill: 0, wave: SKILL_CARD_WAVES[0], name: "攻击加成", info: "英雄攻击力+10%", is_inst: false, keep_waves: -1, field: [7002], trigger_type: CardTriggerType.Field },
{ uuid: 8703, skill: 0, wave: SKILL_CARD_WAVES[0], name: "击晕加成", info: "英雄击晕概率+10%", is_inst: false, keep_waves: -1, field: [7003], trigger_type: CardTriggerType.Field },
{ uuid: 8704, skill: 0, wave: SKILL_CARD_WAVES[0], name: "暴击加成", info: "英雄暴击率+10%", is_inst: false, keep_waves: -1, field: [7004], trigger_type: CardTriggerType.Field },
{ uuid: 8705, skill: 0, wave: SKILL_CARD_WAVES[0], name: "暴伤加成", info: "英雄暴击伤害+20%", is_inst: false, keep_waves: -1, field: [7005], trigger_type: CardTriggerType.Field },
{ uuid: 8706, skill: 0, wave: SKILL_CARD_WAVES[0], name: "攻速加成", info: "英雄攻击速度+10%", is_inst: false, keep_waves: -1, field: [7006], trigger_type: CardTriggerType.Field },
{ uuid: 8707, skill: 0, wave: SKILL_CARD_WAVES[0], name: "生命加成", info: "英雄最大生命+10%", is_inst: false, keep_waves: -1, field: [7007], trigger_type: CardTriggerType.Field },
{ uuid: 8708, skill: 0, wave: SKILL_CARD_WAVES[0], name: "风怒加成", info: "英雄风怒概率+10%", is_inst: false, keep_waves: -1, field: [7008], trigger_type: CardTriggerType.Field },
{ uuid: 8709, skill: 0, wave: SKILL_CARD_WAVES[0], name: "穿刺加成", info: "英雄穿刺概率+10%", is_inst: false, keep_waves: -1, field: [7009], trigger_type: CardTriggerType.Field },
// === 10波技能 ===
{ uuid: 8708, skill: 0, wave: 10, name: "攻击加成", info: "英雄攻击力+10%", is_inst: false, keep_waves: -1, field: [7008] },
{ uuid: 8709, skill: 0, wave: 10, name: "击晕加成", info: "英雄击晕概率+10%", is_inst: false, keep_waves: -1, field: [7009] },
{ uuid: 8710, skill: 0, wave: 10, name: "暴击加成", info: "英雄暴击率+10%", is_inst: false, keep_waves: -1, field: [7010] },
{ uuid: 8711, skill: 0, wave: 10, name: "暴伤加成", info: "英雄暴击伤害+20%", is_inst: false, keep_waves: -1, field: [7011] },
{ uuid: 8712, skill: 0, wave: 10, name: "攻速加成", info: "英雄攻击速度+10%", is_inst: false, keep_waves: -1, field: [7012] },
{ uuid: 8713, skill: 0, wave: 10, name: "购买优惠", info: "购买卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7013] },
{ uuid: 8714, skill: 0, wave: 10, name: "刷新优惠", info: "刷新卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7014] },
{ uuid: 8716, skill: 0, wave: 10, name: "生命加成", info: "英雄最大生命+10%", is_inst: false, keep_waves: -1, field: [7016] },
{ uuid: 8717, skill: 0, wave: 10, name: "风怒加成", info: "英雄风怒概率+10%", is_inst: false, keep_waves: -1, field: [7017] },
{ uuid: 8718, skill: 0, wave: 10, name: "穿刺加成", info: "英雄穿刺概率+10%", is_inst: false, keep_waves: -1, field: [7018] },
{ uuid: 8710, skill: 0, wave: SKILL_CARD_WAVES[0], name: "金币收益", info: "每回合金币收益+1", is_inst: false, keep_waves: -1, field: [7010], trigger_type: CardTriggerType.Field },
{ uuid: 8711, skill: 0, wave: SKILL_CARD_WAVES[0], name: "出售强化", info: "卖出英雄金币+1", is_inst: false, keep_waves: -1, field: [7011], trigger_type: CardTriggerType.Field },
// === 15波技能 ===
{ uuid: 8701, skill: 0, wave: 15, name: "召唤强化", info: "召唤触发技能次数+1", is_inst: false, keep_waves: -1, field: [7001] },
{ uuid: 8702, skill: 0, wave: 15, name: "死亡强化", info: "死亡触发技能次数+1", is_inst: false, keep_waves: -1, field: [7002] },
{ uuid: 8703, skill: 0, wave: 15, name: "开场强化", info: "战斗开始触发技能次数+1", is_inst: false, keep_waves: -1, field: [7003] },
{ uuid: 8704, skill: 0, wave: 15, name: "结束强化", info: "战斗结束触发技能次数+1", is_inst: false, keep_waves: -1, field: [7004] },
// ==================== wave 5 档(强度 ×2 ====================
// --- 驻场卡field uuid +200对应 FieldSkillSet 72xx 段) ---
{ uuid: 8751, skill: 0, wave: SKILL_CARD_WAVES[1], name: "战后恢复+", info: "战斗结束生命回复量+20%", is_inst: false, keep_waves: -1, field: [7201], trigger_type: CardTriggerType.Field },
{ uuid: 8752, skill: 0, wave: SKILL_CARD_WAVES[1], name: "攻击加成+", info: "英雄攻击力+20%", is_inst: false, keep_waves: -1, field: [7202], trigger_type: CardTriggerType.Field },
{ uuid: 8753, skill: 0, wave: SKILL_CARD_WAVES[1], name: "击晕加成+", info: "英雄击晕概率+20%", is_inst: false, keep_waves: -1, field: [7203], trigger_type: CardTriggerType.Field },
{ uuid: 8754, skill: 0, wave: SKILL_CARD_WAVES[1], name: "暴击加成+", info: "英雄暴击率+20%", is_inst: false, keep_waves: -1, field: [7204], trigger_type: CardTriggerType.Field },
{ uuid: 8755, skill: 0, wave: SKILL_CARD_WAVES[1], name: "暴伤加成+", info: "英雄暴击伤害+40%", is_inst: false, keep_waves: -1, field: [7205], trigger_type: CardTriggerType.Field },
{ uuid: 8756, skill: 0, wave: SKILL_CARD_WAVES[1], name: "攻速加成+", info: "英雄攻击速度+20%", is_inst: false, keep_waves: -1, field: [7206], trigger_type: CardTriggerType.Field },
{ uuid: 8757, skill: 0, wave: SKILL_CARD_WAVES[1], name: "生命加成+", info: "英雄最大生命+20%", is_inst: false, keep_waves: -1, field: [7207], trigger_type: CardTriggerType.Field },
{ uuid: 8758, skill: 0, wave: SKILL_CARD_WAVES[1], name: "风怒加成+", info: "英雄风怒概率+20%", is_inst: false, keep_waves: -1, field: [7208], trigger_type: CardTriggerType.Field },
{ uuid: 8759, skill: 0, wave: SKILL_CARD_WAVES[1], name: "穿刺加成+", info: "英雄穿刺概率+20%", is_inst: false, keep_waves: -1, field: [7209], trigger_type: CardTriggerType.Field },
// --- 范围攻击卡ap 递增,间隔缩短) ---
{ uuid: 8261, skill: 6201, wave: SKILL_CARD_WAVES[1], name: "雷墙+", info: "召唤雷墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
{ uuid: 8262, skill: 6202, wave: SKILL_CARD_WAVES[1], name: "火墙+", info: "召唤火墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
{ uuid: 8263, skill: 6203, wave: SKILL_CARD_WAVES[1], name: "飓风+", info: "召唤飓风攻击敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
{ uuid: 8264, skill: 6204, wave: SKILL_CARD_WAVES[1], name: "水墙+", info: "召唤水墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
{ uuid: 8265, skill: 6205, wave: SKILL_CARD_WAVES[1], name: "风墙+", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
{ uuid: 8266, skill: 6206, wave: SKILL_CARD_WAVES[1], name: "陨石术+", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
// === 20波技能 ===
{ uuid: 8201, skill: 6201, wave: 20, name: "雷墙", info: "召唤雷墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
{ uuid: 8202, skill: 6202, wave: 20, name: "火墙", info: "召唤火墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
{ uuid: 8203, skill: 6203, wave: 20, name: "飓风", info: "召唤飓风攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
{ uuid: 8204, skill: 6204, wave: 20, name: "水墙", info: "召唤水墙阻挡敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
{ uuid: 8205, skill: 6205, wave: 20, name: "风墙", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
{ uuid: 8206, skill: 6206, wave: 20, name: "陨石术", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_times: 999, t_inv: 5, keep_waves: -1 },
{ uuid: 8760, skill: 0, wave: SKILL_CARD_WAVES[1], name: "金币收益+", info: "每回合金币收益+2", is_inst: false, keep_waves: -1, field: [7210], trigger_type: CardTriggerType.Field },
{ uuid: 8761, skill: 0, wave: SKILL_CARD_WAVES[1], name: "购买优惠+", info: "购买卡牌费用-2金币", is_inst: false, keep_waves: -1, field: [7212], trigger_type: CardTriggerType.Field },
{ uuid: 8762, skill: 0, wave: SKILL_CARD_WAVES[1], name: "刷新优惠", info: "刷新卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7213], trigger_type: CardTriggerType.Field },
// ==================== wave 8 档(强度 ×3 ====================
// --- 驻场卡field uuid +400对应 FieldSkillSet 74xx 段) ---
{ uuid: 8801, skill: 0, wave: SKILL_CARD_WAVES[2], name: "战后恢复++", info: "战斗结束生命回复量+30%", is_inst: false, keep_waves: -1, field: [7401], trigger_type: CardTriggerType.Field },
{ uuid: 8802, skill: 0, wave: SKILL_CARD_WAVES[2], name: "攻击加成++", info: "英雄攻击力+30%", is_inst: false, keep_waves: -1, field: [7402], trigger_type: CardTriggerType.Field },
{ uuid: 8803, skill: 0, wave: SKILL_CARD_WAVES[2], name: "击晕加成++", info: "英雄击晕概率+30%", is_inst: false, keep_waves: -1, field: [7403], trigger_type: CardTriggerType.Field },
{ uuid: 8804, skill: 0, wave: SKILL_CARD_WAVES[2], name: "暴击加成++", info: "英雄暴击率+30%", is_inst: false, keep_waves: -1, field: [7404], trigger_type: CardTriggerType.Field },
{ uuid: 8805, skill: 0, wave: SKILL_CARD_WAVES[2], name: "暴伤加成++", info: "英雄暴击伤害+60%", is_inst: false, keep_waves: -1, field: [7405], trigger_type: CardTriggerType.Field },
{ uuid: 8806, skill: 0, wave: SKILL_CARD_WAVES[2], name: "攻速加成++", info: "英雄攻击速度+30%", is_inst: false, keep_waves: -1, field: [7406], trigger_type: CardTriggerType.Field },
{ uuid: 8807, skill: 0, wave: SKILL_CARD_WAVES[2], name: "生命加成++", info: "英雄最大生命+30%", is_inst: false, keep_waves: -1, field: [7407], trigger_type: CardTriggerType.Field },
{ uuid: 8808, skill: 0, wave: SKILL_CARD_WAVES[2], name: "风怒加成++", info: "英雄风怒概率+30%", is_inst: false, keep_waves: -1, field: [7408], trigger_type: CardTriggerType.Field },
{ uuid: 8809, skill: 0, wave: SKILL_CARD_WAVES[2], name: "穿刺加成++", info: "英雄穿刺概率+30%", is_inst: false, keep_waves: -1, field: [7409], trigger_type: CardTriggerType.Field },
{ uuid: 8361, skill: 6201, wave: SKILL_CARD_WAVES[2], name: "雷墙++", info: "召唤雷墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
{ uuid: 8362, skill: 6202, wave: SKILL_CARD_WAVES[2], name: "火墙++", info: "召唤火墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
{ uuid: 8363, skill: 6203, wave: SKILL_CARD_WAVES[2], name: "飓风++", info: "召唤飓风攻击敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
{ uuid: 8364, skill: 6204, wave: SKILL_CARD_WAVES[2], name: "水墙++", info: "召唤水墙阻挡敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
{ uuid: 8365, skill: 6205, wave: SKILL_CARD_WAVES[2], name: "风墙++", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
{ uuid: 8366, skill: 6206, wave: SKILL_CARD_WAVES[2], name: "陨石术++", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
{ uuid: 8810, skill: 0, wave: SKILL_CARD_WAVES[2], name: "金币收益++", info: "每回合金币收益+3", is_inst: false, keep_waves: -1, field: [7410], trigger_type: CardTriggerType.Field },
{ uuid: 8811, skill: 0, wave: SKILL_CARD_WAVES[2], name: "召唤强化++", info: "召唤触发技能次数+1", is_inst: false, keep_waves: -1, field: [7014], trigger_type: CardTriggerType.Field },
{ uuid: 8812, skill: 0, wave: SKILL_CARD_WAVES[2], name: "死亡强化++", info: "死亡触发技能次数+1", is_inst: false, keep_waves: -1, field: [7015], trigger_type: CardTriggerType.Field },
{ uuid: 8813, skill: 0, wave: SKILL_CARD_WAVES[2], name: "开场强化++", info: "战斗开始触发技能次数+1", is_inst: false, keep_waves: -1, field: [7016], trigger_type: CardTriggerType.Field },
{ uuid: 8814, skill: 0, wave: SKILL_CARD_WAVES[2], name: "结束强化++", info: "战斗结束触发技能次数+1", is_inst: false, keep_waves: -1, field: [7017], trigger_type: CardTriggerType.Field },
{ uuid: 8815, skill: 0, wave: SKILL_CARD_WAVES[2], name: "攻击强化++", info: "攻击触发技能次数+1", is_inst: false, keep_waves: -1, field: [7018], trigger_type: CardTriggerType.Field },
{ uuid: 8816, skill: 0, wave: SKILL_CARD_WAVES[2], name: "受击强化++", info: "被攻击触发技能次数+1", is_inst: false, keep_waves: -1, field: [7019], trigger_type: CardTriggerType.Field },
// --- 范围攻击卡ap 最高,间隔最短) ---
];
SkillCardData.forEach(data => {
@@ -204,14 +261,18 @@ SkillCardData.forEach(data => {
pool_lv: waveToPoolLv[data.wave] as CardLV,
wave: data.wave,
kind: CKind.Skill,
card_lv: 1,
card_lv: waveToPoolLv[data.wave], // wave 1→1, 5→2, 8→3
name: data.name,
info: data.info,
icon: data.icon, // 【新增】透传自定义图标ID优先级最高
is_inst: data.is_inst,
t_times: data.t_times || (data.is_inst ? 1 : 999),
t_inv: data.t_inv || 0,
keep_waves: data.keep_waves,
field: data.field
field: data.field,
overrides: data.overrides, // 【修复】原遗漏
trigger_type: data.trigger_type, // 【新增】显式触发类型
trigger_limit: data.trigger_limit, // 【新增】事件型触发次数上限
});
});
@@ -290,14 +351,19 @@ const weightedPick = (cards: CardConfig[]): CardConfig | null => {
return cards[cards.length - 1]
}
/** 连续抽取 count 张卡,允许重复 */
const pickCards = (cards: CardConfig[], count: number): CardConfig[] => {
/** 连续抽取 count 张卡,允许重复或通过 unique 剔除重复 */
const pickCards = (cards: CardConfig[], count: number, unique: boolean = false): CardConfig[] => {
if (cards.length === 0 || count <= 0) return []
const selected: CardConfig[] = []
let available = [...cards]
while (selected.length < count) {
const pick = weightedPick(cards)
if (available.length === 0) break
const pick = weightedPick(available)
if (!pick) break
selected.push(pick)
if (unique) {
available = available.filter(c => c.uuid !== pick.uuid)
}
}
return selected
}
@@ -345,6 +411,7 @@ export const drawCardsByRule = (
heroLv?: number
targetPoolLv?: number
wave?: number
unique?: boolean
} = {}
): CardConfig[] => {
const count = Math.max(0, Math.floor(options.count ?? 4))
@@ -394,6 +461,6 @@ export const drawCardsByRule = (
})
}
const picked = pickCards(pool, count)
const picked = pickCards(pool, count, options.unique)
return picked
}

View File

@@ -20,6 +20,8 @@ export enum GameEvent {
CastSkill = "CastSkill",
CardsClose = "CardsClose",
CardRefresh = "CardRefresh",
/** 单张卡牌被点击选中payload 为被点击的 CardComp 实例,用于其他卡牌联动隐藏 call_btn */
CardSelected = "CardSelected",
UseHeroCard = "UseHeroCard",
UseSkillCard = "UseSkillCard",
UseSpecialCard = "UseSpecialCard",
@@ -74,6 +76,7 @@ export enum GameEvent {
UpdateMissionGet = "UpdateMissionGet",
GlobalAttrChange = "GlobalAttrChange",
CoinAdd = "CoinAdd",
ShowSmallTip = "ShowSmallTip",
CardPoolUpgrade = "CardPoolUpgrade",
TriggerSkill = "TriggerSkill", // 瞬间触发施法事件
RemoveSkillBox = "RemoveSkillBox", // 技能盒销毁事件

View File

@@ -13,7 +13,7 @@ export enum BoxSet {
LETF_END = -360,
RIGHT_END = 360,
//游戏地平线
GAME_LINE = -100,
GAME_LINE = -90,
}
export enum FacSet {
@@ -21,14 +21,14 @@ export enum FacSet {
MON = 1,
}
export enum FightSet {
WAVE_COIN_BASE = 4, // 波次金币基础奖励
WAVE_COIN_GROW = 1, // 波次金币递增值
WAVE_COIN_MAX = 10, // 波次金币最大基础奖励
WAVE_COIN_BASE = 7, // 波次金币基础奖励
WAVE_COIN_GROW = 2, // 波次金币递增值
WAVE_COIN_MAX = 17, // 波次金币最大基础奖励
CRIT_DAMAGE = 50,//暴击伤害
MORE_RC = 10,//更多次数 广告获取的次数
HEARTPOS = -320,//基地位置
HERO_MAX_NUM = 6,//英雄最大数量
MERGE_MAX = 3, //英雄最大等级
MERGE_MAX = 2, //英雄最大等级
MERGE_NEED = 3, //英雄升级需要的英雄数
// BACK_RANG=30,//后退范围
BACK_RANG = 30,//后退范围
@@ -42,11 +42,33 @@ export enum FightSet {
SHIELD_MAX = 5,
WAVE_HEAL_RATE = 0.5, // 回合结束时所有英雄恢复最大生命值的比例
PUNCTURE_DOWN = 50,
REFRESH_COST = 1,
BASE_COST=3
REFRESH_COST = 2,
BASE_COST = 5,
INIT_COIN = 7, // 初始金币数
// 刷新成本
/** 卡池等级上限(对应 CardLV 最大值) */
MAX_CARD_POOL_LEVEL = 5,
}
/**
* 卡池升级波次配置(单一数据源)。
* 索引 i 对应目标等级 = i + 2
* - 第 1 个波次 → 升至 LV2
* - 第 2 个波次 → 升至 LV3
* - 依此类推,上限为 FightSet.MAX_CARD_POOL_LEVEL
*/
export const CARD_POOL_UPGRADE_WAVES: number[] = [4, 7, 10, 13];
/**
* 技能卡牌出现的波次配置(单一数据源)。
* 数组索引 i 对应卡牌档位 card_lv = i + 1
* - 第 1 个波次 → LV1 档(基础强度)
* - 第 2 个波次 → LV2 档(强度 ×2
* - 第 3 个波次 → LV3 档(强度 ×3
* 需与 CardSet.SkillCardData 的 wave 字段严格对齐,否则抽卡时池子为空。
*/
export const SKILL_CARD_WAVES: number[] = [1, 5, 8];
export const laneIdx = {
2: [-180, 90],
1: [-180, 0],

View File

@@ -155,6 +155,7 @@ export interface SkillConfig {
bck?: number, // 额外击退概率
buff_type?: Attrs, // Buff 类型 (单一职责)
call_hero?: number, // 召唤技能召唤英雄id(可选)
is_accel?: boolean, // 是否逐渐加速飞行
info: string, // 技能描述
}
@@ -170,6 +171,7 @@ export interface SkillOverrides {
bck?: number;
buff_type?: Attrs;
call_hero?: number;
is_accel?: boolean;
}
/**
@@ -208,39 +210,33 @@ export const SkillSet: Record<number, SkillConfig> = {
* 6010 箭矢黄 击晕取向
**/
6001: {
uuid: 6001, name: "火球", sp_name: "atk_1", icon: "1026", 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,
uuid: 6001, name: "火球", sp_name: "atk_1", 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%的伤害",
},
6002: {
uuid: 6002, name: "紫烟", sp_name: "atk_2", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
uuid: 6002, name: "紫烟", sp_name: "atk_2", icon: "Stat_Attack_01", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.linear, EType: EType.collision, info: "近战普通攻击技能",
},
6003: {
uuid: 6003, name: "白球", sp_name: "atk_3", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
uuid: 6003, name: "白球", sp_name: "atk_3", icon: "Stat_Attack_01", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.linear, EType: EType.collision, info: "一定几率暴击",
},
6004: {
uuid: 6004, name: "水球", sp_name: "atk_4", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
DTType: DTType.single, ap: 100, hit_count: 1, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.linear, EType: EType.collision, info: "普通远程攻击",
},
6008: {
uuid: 6008, name: "箭矢", sp_name: "arrow", icon: "1135", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
uuid: 6008, name: "箭矢", sp_name: "arrow", icon: "Stat_RangedAttack", 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.remote,
RType: RType.bezier, EType: EType.collision, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "造成攻击力100%的伤害",
},
6009: {
uuid: 6009, name: "箭矢蓝", sp_name: "arrow_blue", icon: "1135", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
uuid: 6009, name: "箭矢蓝", sp_name: "arrow_blue", icon: "Stat_RangedAttack", 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.remote,
RType: RType.bezier, EType: EType.collision, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "造成攻击力100%的伤害",
},
6010: {
uuid: 6010, name: "箭矢红", sp_name: "arrow_red", icon: "1135", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
uuid: 6010, name: "箭矢红", sp_name: "arrow_red", icon: "Stat_RangedAttack", 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.remote,
RType: RType.bezier, EType: EType.collision, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "造成攻击力100%的伤害",
},
@@ -252,27 +248,22 @@ export const SkillSet: Record<number, SkillConfig> = {
* 6104 水球 溅射:分裂多个
**/
6101: {
uuid: 6101, name: "大火球", sp_name: "line_1", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
uuid: 6101, name: "大火球", sp_name: "line_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%的伤害,一定几率暴击,高阶技能",
},
//怪物法师统一使用 暗影球
6102: {
uuid: 6102, name: "大紫球", sp_name: "line_2", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
uuid: 6102, name: "大紫球", sp_name: "line_2", icon: "Stat_Mana", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
DTType: DTType.single, 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%的伤害,高阶技能",
},
6103: {
uuid: 6103, name: "大白球", sp_name: "line_3", icon: "1126", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
uuid: 6103, name: "大白球", sp_name: "line_3", icon: "Stat_WaterDamage", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "atk",
DTType: DTType.single, 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%的伤害,一定几率上毒(后期加入),高阶技能 ",
},
6104: {
uuid: 6104, name: "大水球", sp_name: "line_4", icon: "1135", TGroup: TGroup.Enemy, readyAnm: "yellow", endAnm: "", act: "max",
DTType: DTType.single, crt: 20, ap: 100, hit_count: 2, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.linear, EType: EType.collision, info: "射出强力箭矢最多穿透6个敌人附带20%额外暴击率",
},
/*** ======高阶范围攻击技能 ====
* 都是3*3 范围攻击 不是英雄技能是技能卡20波技能
* 6201 雷墙 击晕向
@@ -283,105 +274,105 @@ export const SkillSet: Record<number, SkillConfig> = {
* 6206 陨石术 暴击向
**/
6201: {
uuid: 6201, name: "雷墙", sp_name: "box_1", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
uuid: 6201, name: "雷墙", sp_name: "box_1", icon: "Stat_LightningDamag", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.fixed, EType: EType.animationEnd, info: "召唤雷墙阻挡敌人,有概率击晕",
},
6202: {
uuid: 6202, name: "火墙", sp_name: "box_2", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
uuid: 6202, name: "火墙", sp_name: "box_2", icon: "Stat_FireDamage", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.fixed, EType: EType.animationEnd, info: "召唤雷墙阻挡敌人,有概率击晕",
},
6203: {
uuid: 6203, name: "飓风", sp_name: "box_3", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
uuid: 6203, name: "飓风", sp_name: "box_3", icon: "Stat_Stun_01", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.fixed, EType: EType.animationEnd, info: "召唤雷墙阻挡敌人,有概率击晕",
},
6204: {
uuid: 6204, name: "水墙", sp_name: "box_4", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
uuid: 6204, name: "水墙", sp_name: "box_4", icon: "Stat_Mana", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.fixed, EType: EType.animationEnd, info: "召唤雷墙阻挡敌人,有概率击晕",
},
6205: {
uuid: 6205, name: "风墙", sp_name: "box_5", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
uuid: 6205, name: "风墙", sp_name: "box_5", icon: "Stat_Stun_01", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.fixed, EType: EType.animationEnd, info: "召唤风墙困住敌人,有概率击晕",
},
6206: {
uuid: 6206, name: "陨石术", sp_name: "box_6", icon: "1173", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
uuid: 6206, name: "陨石术", sp_name: "box_6", icon: "Stat_Tripleshot", TGroup: TGroup.Enemy, readyAnm: "blues", endAnm: "", act: "max",
DTType: DTType.aoe_grid, stun: 0, ap: 150, hit_count: 6, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.fixed, EType: EType.animationEnd, info: "召唤陨石范围攻击敌人,有概率击晕",
},
//============================= ====== 辅助技能 技能卡牌 1 波技能 ====== ==========================
6301: {
uuid: 6301, name: "护盾", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Self, readyAnm: "up_blue", endAnm: "", act: "atk",
uuid: 6301, name: "护盾", sp_name: "buff_wind", icon: "Stat_Defense", TGroup: TGroup.Self, readyAnm: "up_blue", endAnm: "", act: "atk",
DTType: DTType.single, kind: SkillKind.Shield, ap: 3, 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, info: "为伙伴/自己添加护盾可抵挡3次伤害",
},
6302: {
uuid: 6302, name: "治疗", sp_name: "buff_wind", icon: "1292", TGroup: TGroup.Team, readyAnm: "up_green", endAnm: "", act: "atk",
uuid: 6302, name: "治疗", sp_name: "buff_wind", icon: "Stat_Hp_01", TGroup: TGroup.Team, readyAnm: "up_green", endAnm: "", act: "atk",
DTType: DTType.single, kind: SkillKind.Heal, ap: 300, 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, info: "治疗伙伴/自己",
},
//==========================buff 技能 也是 技能卡牌 5 波技能
6303: {
uuid: 6303, name: "获取金币", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Self, readyAnm: "up_blue", endAnm: "gold", act: "atk",
uuid: 6303, name: "获取金币", sp_name: "buff_wind", icon: "Stat_GoldGainIncrease_01", TGroup: TGroup.Self, readyAnm: "up_blue", endAnm: "gold", act: "atk",
DTType: DTType.single, kind: SkillKind.Gold, ap: 0, gold: 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, info: "增加一定数量的金币",
},
//==========================buff 技能=====================
6401: {
uuid: 6401, name: "攻击强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
uuid: 6401, name: "攻击强化", sp_name: "buff_wind", icon: "Stat_Attack_03", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
DTType: DTType.single, kind: SkillKind.Support, ap: 5, 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.ap, info: "全体友方攻击力提升5点持续1次",
},
6402: {
uuid: 6402, name: "生命强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_hp", endAnm: "", act: "atk",
uuid: 6402, name: "生命强化", sp_name: "buff_wind", icon: "Stat_Hp_02", TGroup: TGroup.Team, readyAnm: "up_hp", endAnm: "", act: "atk",
DTType: DTType.single, kind: SkillKind.Support, ap: 20, 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.hp_max, info: "全体友方最大生命值提升20点持续1次",
},
6403: {
uuid: 6403, name: "暴击强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
uuid: 6403, name: "暴击强化", sp_name: "buff_wind", icon: "Stat_CriticalChance_02", TGroup: TGroup.Team, readyAnm: "up_ap", 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.critical, info: "全体友方暴击率提升10%持续1次",
},
6404: {
uuid: 6404, name: "暴伤强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
uuid: 6404, name: "暴伤强化", sp_name: "buff_wind", icon: "Stat_Critical_01", TGroup: TGroup.Team, readyAnm: "up_ap", 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.critical_damage, info: "全体友方暴击伤害提升20%持续1次",
},
6405: {
uuid: 6405, name: "击晕强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
uuid: 6405, 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.stun_chance, info: "全体友方击晕概率提升10%持续1次",
},
6406: {
uuid: 6406, name: "击退强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
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, info: "暂未使用",
},
6407: {
uuid: 6407, name: "距推强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_blue", endAnm: "", act: "atk",
uuid: 6407, 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, info: "暂未使用",
},
6408: {
uuid: 6408, name: "穿刺强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
uuid: 6408, name: "穿刺强化", sp_name: "buff_wind", icon: "Stat_Tripleshot", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
DTType: DTType.single, kind: SkillKind.Support, ap: 20, 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_chance, info: "全体友方穿透概率提升20%持续1次",
},
6409: {
uuid: 6409, name: "风怒强化", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Team, readyAnm: "up_ap", endAnm: "", act: "atk",
uuid: 6409, name: "风怒强化", sp_name: "buff_wind", icon: "Stat_CriticalComboChance", TGroup: TGroup.Team, readyAnm: "up_ap", 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.wfuny, info: "全体友方风怒次数提升1次持续1次",
},
6501: {
uuid: 6501, name: "复活", sp_name: "buff_wind", icon: "1255", TGroup: TGroup.Self, readyAnm: "up_ap", endAnm: "", act: "atk",
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,
RType: RType.fixed, EType: EType.animationEnd, info: "ap 代表复活的生命值百分比",
}
@@ -408,34 +399,77 @@ export enum FieldSkillType {
HeroHp = 16, // 英雄最大生命加成
HeroWindFury = 17, // 英雄风怒概率加成
HeroPuncture = 18, // 英雄穿刺概率加成
AtkCount = 19, // 攻击触发技能次数提升
BeAtkCount = 20, // 被攻击触发技能次数提升
}
export interface FieldSkillConfig {
uuid: number;
name: string;
icon: string; // 新增图标字段
type: FieldSkillType;
value: number; // 提升的数值
info: string;
}
export const FieldSkillSet: Record<number, FieldSkillConfig> = {
7001: { uuid: 7001, name: "召唤强化", type: FieldSkillType.SummonCount, value: 1, info: "召唤触发技能次数+1" }, //15 波技能
7002: { uuid: 7002, name: "死亡强化", type: FieldSkillType.DeadCount, value: 1, info: "死亡触发技能次数+1" }, //15 波技能
7003: { uuid: 7003, name: "开场强化", type: FieldSkillType.StartCount, value: 1, info: "战斗开始触发技能次数+1" }, //15 波技能
7004: { uuid: 7004, name: "结束强化", type: FieldSkillType.EndCount, value: 1, info: "战斗结束触发技能次数+1" }, //15 波技能
7005: { uuid: 7005, name: "金币收益", type: FieldSkillType.WaveGold, value: 1, info: "每回合金币收益+1" }, //1 波技能
7006: { uuid: 7006, name: "出售强化", type: FieldSkillType.SellGold, value: 1, info: "卖出英雄金币+1" }, //1 波技能
7007: { uuid: 7007, name: "战后恢复", type: FieldSkillType.WaveHeal, value: 0.1, info: "战斗结束生命回复量+10%" }, //1 波技能
7008: { uuid: 7008, name: "攻加成", type: FieldSkillType.HeroAtk, value: 0.1, info: "英雄攻击+10%" }, //10 波技能
7009: { uuid: 7009, name: "击晕加成", type: FieldSkillType.HeroStun, value: 0.1, info: "英雄击晕概率+10%" }, //10 波技能
7010: { uuid: 7010, name: "暴击加成", type: FieldSkillType.HeroCrit, value: 0.1, info: "英雄暴击率+10%" }, //10 波技能
7011: { uuid: 7011, name: "暴伤加成", type: FieldSkillType.HeroCritDamage, value: 0.2, info: "英雄暴击伤害+20%" }, //10 波技能
7012: { uuid: 7012, name: "攻速加成", type: FieldSkillType.HeroSpeed, value: 0.1, info: "英雄攻击速度+10%" }, //10 波技能
// ---- 13~18 来自原 TalentSet统一为驻场百分比 / 绝对值口径 ----
// 出售返还由原生 SellGold 承担SellBonus 不再单独配置
7013: { uuid: 7013, name: "购买优惠", type: FieldSkillType.BuyDiscount, value: 1, info: "购买卡牌费用-1金币" }, //10 波技能
7014: { uuid: 7014, name: "刷新优惠", type: FieldSkillType.RefreshDiscount, value: 1, info: "刷新卡牌费用-1金币" }, //10 波技能
7016: { uuid: 7016, name: "生命加成", type: FieldSkillType.HeroHp, value: 0.1, info: "英雄最大生命+10%" }, //10 波技能
7017: { uuid: 7017, name: "风怒加成", type: FieldSkillType.HeroWindFury, value: 0.1, info: "英雄风怒概率+10%" }, //10 波技能
7018: { uuid: 7018, name: "穿刺加成", type: FieldSkillType.HeroPuncture, value: 0.1, info: "英雄穿刺概率+10%" }, //10 波技能
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金币" },
};

View File

@@ -100,14 +100,14 @@ export enum SkillTriggerType {
}
export const SkillTriggerName = {
[SkillTriggerType.Call]: "降临",
[SkillTriggerType.Dead]: "遗志",
[SkillTriggerType.FStart]: "手",
[SkillTriggerType.FEnd]: "终战",
[SkillTriggerType.Call]: "召唤",
[SkillTriggerType.Dead]: "亡语",
[SkillTriggerType.FStart]: "手",
[SkillTriggerType.FEnd]: "生息",
[SkillTriggerType.Field]: "光环",
[SkillTriggerType.Atking]: "击",
[SkillTriggerType.Atked]: "击",
[SkillTriggerType.Revive]: "涅槃",
[SkillTriggerType.Atking]: "击",
[SkillTriggerType.Atked]: "击",
[SkillTriggerType.Revive]: "复活",
}
export const SkillTriggerDesc = {
@@ -180,63 +180,193 @@ export interface HeroEvolve {
}
/*
*=============英雄配置列表================
* 职业触发规则 (v3)
* 战士 专注 atked(受击) + dead(死亡) 触发 — 承伤坦克定位
* 刺客 专注 atking(攻击) + dead(死亡) 触发 — 高风险高回报近战
* 射手 专注 atking(攻击) + fstart(战前) 触发 — 稳定远程输出
* 法师 专注 atking(攻击) + field(驻场) 触发 — 魔法输出+被动光环
* 辅助 专注 atking(攻击) + revive(复活) 触发 — 战斗支援
* v4 触发类型底座版 — 每个英雄专注单一效果,流派协同由玩家自行发现
*
* atked 战士 受击触发,效果作用于自身,越挨打越强
* atking 刺客 攻击触发自身强化;射手 攻击触发队友强化hit_count 控制目标数 1→3→6
* dead 战士/刺客 死亡触发,一次性全队遗产
* fstart 法师 战斗开始前触发一次,必然生效的全队增益
* field 法师 驻场光环,存活期间持续生效,死亡消失
* fend 辅助 每波结束触发,跨波次累积增益
*
* 触发节奏atked 3次/5次atking 5次/7次fstart/fend 1次/波
* 废弃属性frz(冰冻)、bck(击退) — 怪物固定不移动,无效
*
* 设定中的英雄都是1级,最高可以升级到3级不在列表内提现,升级在游戏内进行)
* skills[0]是普通攻击技能
* skills[1]是等级2时的技能,skills[2]是等级3时的技能
*
* 属性基准(pool_lv:1,lv:1) SPEED:800, AP:30 | HP:300 | skills[0].cd = 1.0 (普通)
* 坦克(pool_lv:1,lv:1) SPEED:800, AP:25 | HP:450 | skills[0].cd = 2.3 或 2.8 (很慢+/很慢) - 突出沉重感与承伤定位
* 近战dps(pool_lv:1,lv:1) SPEED:800, AP:50 | HP:250 | skills[0].cd = 0.3 或 0.5 (极速/快速+) - 强化割草连击爽感
* 远程dps(pool_lv:1,lv:1) SPEED:800, AP:60 | HP:150 | skills[0].cd = 0.7 或 0.9 (快速/普通+) - 稳定持续的物理输出节奏
*远程法dps(pool_lv:1,lv:1) SPEED:800, AP:60 | HP:150 | skills[0].cd = 1.5 或 1.8 (慢+/慢) - 强调施法前摇与单发爆发
* 远程辅助(pool_lv:1,lv:1) SPEED:800, AP:20 | HP:150 | skills[0].cd = 1.1 (普通) - 贴近基准,动作不急不躁,侧重技能
*/
export const HeroInfo: Record<number, heroInfo> = {
// ========== 基础九大英雄 ==========
5001:{uuid:5001,name:"见习战士",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:30,
// ========== atked 类(战士 · 自身强化) ==========
5011:{uuid:5011,name:"小铁卫",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:28,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:4}}],
info:"每受击3次为自身添加4层护盾"},
5012:{uuid:5012,name:"不死小强",path:"hk2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[{s_uuid:6302,t_num:3,overrides:{TGroup:TGroup.Self,ap:250}}],
info:"每受击3次为自身回复攻击力250%的生命值"},
5013:{uuid:5013,name:"铁骨头",path:"hk3", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[{s_uuid:6402,t_num:5,overrides:{TGroup:TGroup.Self,ap:100}}],
info:"每受击5次永久提升自身最大生命值100点"},
5014:{uuid:5014,name:"怒火武者",path:"hk4", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
atked:[{s_uuid:6401,t_num:3,overrides:{TGroup:TGroup.Self,ap:12}}],
info:"每受击3次永久提升自身攻击力12点"},
5015:{uuid:5015,name:"血刃武者",path:"hk5", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Melee,hp:1200,ap:113,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[
{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:3}},
{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Self,ap:15}}
],
info:"每受击3次加3层护盾每受击5次永久+15攻击力"},
5016:{uuid:5016,name:"狂血战士",path:"hc1", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[
{s_uuid:6401,t_num:3,overrides:{TGroup:TGroup.Self,ap:10}},
{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:15}}
],
info:"每受击3次永久+10攻击力每受击5次永久+15%暴击率"},
// ========== atking 类 — 刺客(自身强化) ==========
5021:{uuid:5021,name:"小刺客",path:"hc1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:28,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast2].cd,ccd:0}},
atking:[{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Self,ap:8}}],
info:"每攻击5次永久提升自身攻击力8点"},
5022:{uuid:5022,name:"嗜血剑客",path:"hc2", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast2].cd,ccd:0}},
atking:[
{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:10}},
{s_uuid:6401,t_num:7,overrides:{TGroup:TGroup.Self,ap:12}}
],
info:"每攻击5次永久+10%暴击率每攻击7次永久+12攻击力"},
5023:{uuid:5023,name:"暗影杀手",path:"hc3", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast1].cd,ccd:0}},
atking:[
{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:10}},
{s_uuid:6404,t_num:7,overrides:{TGroup:TGroup.Self,ap:15}}
],
info:"每攻击5次永久+10%暴击率每攻击7次永久+15%暴伤"},
// ========== atking 类 — 射手队友强化hit_count 控制目标数) ==========
5031:{uuid:5031,name:"援护弓手",path:"ha1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:143,ap:40,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal2].cd,ccd:0}},
atking:[{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Team,hit_count:1,ap:8}}],
info:"每攻击5次为随机1名队友永久提升攻击力8点"},
5032:{uuid:5032,name:"战术弓手",path:"ha2", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal1].cd,ccd:0}},
atked:[{s_uuid:6301,t_num:3}],
info:"基础近战受击3次加护盾"},
5002:{uuid:5002,name:"见习骑士",path:"hk2", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:350,ap:25,
skills:{6003:{uuid:6003,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal2].cd,ccd:0}},
atked:[{s_uuid:6402,t_num:3}],
info:"基础近战受击3次提升生命"},
5003:{uuid:5003,name:"见习刺客",path:"hk3", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:250,ap:40,
skills:{6004:{uuid:6004,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast3].cd,ccd:0}},
atking:[{s_uuid:6401,t_num:3}],
info:"基础近战攻击3次提升攻击"},
5004:{uuid:5004,name:"见习法师",path:"hm1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Mid,hp:200,ap:45,
skills:{6101:{uuid:6101,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
atking:[{s_uuid:6403,t_num:3}],
info:"基础中程攻击3次提升暴击"},
5005:{uuid:5005,name:"见习术士",path:"hm2", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Mid,hp:200,ap:45,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow2].cd,ccd:0}},
atking:[{s_uuid:6404,t_num:3}],
info:"基础中程攻击3次提升暴伤"},
5006:{uuid:5006,name:"见习巫师",path:"hm3", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Mid,hp:200,ap:45,
skills:{6104:{uuid:6104,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
atking:[{s_uuid:6405,t_num:3}],
info:"基础中程攻击3次提升击晕"},
5007:{uuid:5007,name:"见习射手",path:"ha1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:180,ap:50,
skills:{6008:{uuid:6008,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
atking:[{s_uuid:6303,t_num:3}],
info:"基础远程攻击3次获得金币"},
5008:{uuid:5008,name:"见习游侠",path:"ha2", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:180,ap:50,
skills:{6009:{uuid:6009,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:3}],
info:"基础远程攻击3次治疗"},
5009:{uuid:5009,name:"见习弩手",path:"ha3", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:180,ap:50,
skills:{6010:{uuid:6010,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
atking:[{s_uuid:6401,t_num:3}],
info:"基础远程攻击3次提升攻击"},
atking:[{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Team,hit_count:3,ap:10}}],
info:"每攻击5次为随机3名队友永久提升暴击率10%"},
5033:{uuid:5033,name:"鹰眼弓将",path:"ha3", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Long,hp:717,ap:200,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast3].cd,ccd:0}},
atking:[
{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Team,hit_count:6,ap:8}},
{s_uuid:6404,t_num:7,overrides:{TGroup:TGroup.Team,hit_count:6,ap:12}}
],
info:"每攻击5次为随机6名队友永久+8攻击力每攻击7次永久+12%暴伤"},
// ========== dead 类(战士+刺客 · 死亡遗产) ==========
5041:{uuid:5041,name:"殉道卫士",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:28,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
dead:[{s_uuid:6301,t_num:1,overrides:{TGroup:TGroup.Team,ap:3}}],
info:"死亡时为全队添加3层护盾"},
5042:{uuid:5042,name:"遗志将军",path:"hk2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
dead:[
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}},
{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:80}}
],
revive:{s_uuid:6501,r_num:1,upr:0.3},
info:"死亡时全队永久+20攻击力、+80最大生命值死后复活一次"},
5043:{uuid:5043,name:"亡魂刺客",path:"hc1", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal1].cd,ccd:0}},
dead:[{s_uuid:6405,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}}],
info:"死亡时全队永久提升击晕概率15%"},
5044:{uuid:5044,name:"血誓剑客",path:"hc2", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Melee,hp:1200,ap:113,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast3].cd,ccd:0}},
dead:[
{s_uuid:6403,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}},
{s_uuid:6404,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}
],
info:"死亡时全队永久+15%暴击率、+20%暴伤"},
5045:{uuid:5045,name:"不灭战魂",path:"hk3", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
dead:[
{s_uuid:6301,t_num:1,overrides:{TGroup:TGroup.Team,ap:5}},
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:30}},
{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:120}}
],
revive:{s_uuid:6501,r_num:1,upr:0.5},
info:"死亡时全队获得5层护盾、永久+30攻击力、永久+120最大生命值死后复活一次"},
// ========== fstart 类(法师 · 战前增益) ==========
5051:{uuid:5051,name:"占卜师",path:"hm1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:143,ap:40,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid3].cd,ccd:0}},
fstart:[{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}}],
info:"战斗开始时为全队永久提升攻击力15点"},
5052:{uuid:5052,name:"护盾牧师",path:"hm2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Long,hp:287,ap:80,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
fstart:[{s_uuid:6301,t_num:1,overrides:{TGroup:TGroup.Team,ap:2}}],
info:"战斗开始时为全队添加2层护盾"},
5053:{uuid:5053,name:"血盟法师",path:"hm3", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Long,hp:287,ap:80,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid2].cd,ccd:0}},
fstart:[{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:100}}],
info:"战斗开始时为全队永久提升最大生命值100点"},
5054:{uuid:5054,name:"暴击法师",path:"hm4", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid3].cd,ccd:0}},
fstart:[{s_uuid:6403,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}],
info:"战斗开始时为全队永久提升暴击率20%"},
5055:{uuid:5055,name:"毁灭法师",path:"hm5", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Long,hp:573,ap:160,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid1].cd,ccd:0}},
fstart:[
{s_uuid:6404,t_num:1,overrides:{TGroup:TGroup.Team,ap:25}},
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}
],
info:"战斗开始时为全队永久+25%暴伤、+20攻击力"},
5056:{uuid:5056,name:"预言法师",path:"hm6", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Long,hp:717,ap:200,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
fstart:[
{s_uuid:6405,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}},
{s_uuid:6403,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}},
{s_uuid:6404,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}
],
info:"战斗开始时为全队永久+20%击晕概率、+15%暴击率、+20%暴伤"},
// ========== field 类(法师 · 驻场光环) ==========
5061:{uuid:5061,name:"亡语法师",path:"hm1", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid2].cd,ccd:0}},
field:[7015],
info:"驻场期间全队死亡触发技能次数+1死亡后光环消失"},
// ========== fend + atking 类(辅助 · 治疗续航 + 波次增益) ==========
5071:{uuid:5071,name:"治愈牧师",path:"hh1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:143,ap:40,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:200}}],
info:"每攻击5次治疗全队200%AP"},
5072:{uuid:5072,name:"小金库",path:"hh2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Long,hp:287,ap:80,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid1].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:200}}],
fend:[{s_uuid:6303,t_num:1,overrides:{gold:1}}],
info:"每攻击5次治疗全队每波结束获得1金币"},
5073:{uuid:5073,name:"强化牧师",path:"hh3", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:250}}],
fend:[{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:10}}],
info:"每攻击5次治疗全队每波结束全队永久+10攻击力"},
5074:{uuid:5074,name:"生命牧师",path:"hh4", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Long,hp:573,ap:160,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:250}}],
fend:[{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:80}}],
info:"每攻击5次治疗全队每波结束全队永久+80最大生命值"},
5075:{uuid:5075,name:"全能牧师",path:"hh5", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Long,hp:717,ap:200,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal2].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:300}}],
fend:[
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:12}},
{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:60}}
],
info:"每攻击5次治疗全队300%AP每波结束全队永久+12攻击力、+60最大生命值"},
@@ -251,61 +381,59 @@ export const HeroInfo: Record<number, heroInfo> = {
*/
// 基础怪物 (全部远程攻击HType仅决定站位)
// 近战位怪物 (站在前排,承受更多伤害) — v5: TD节奏CD多而弱爽感设计
// 基础怪物 (全部固定点位站桩攻击HType仅决定是前排还是后排)
// 前排怪物 (站在前排,承受更多伤害) — v5: TD节奏CD多而弱爽感设计
6001:{uuid:6001,name:"兽人战士",path:"m1", fac:FacSet.MON,lv:1,type:HType.Melee,hp:220,ap:10,speed:70,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"基础近战位怪"},
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"基础前排怪"},
6002:{uuid:6002,name:"兽人精锐战士",path:"m2", fac:FacSet.MON,lv:1,type:HType.Melee,hp:300,ap:14,speed:110,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"进阶近战位怪,更快更痛"},
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"进阶前排怪,更快更痛"},
6003:{uuid:6003,name:"兽人重装兵",path:"m3", fac:FacSet.MON,lv:1,type:HType.Melee,hp:850,ap:20,speed:50,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"重型坦克怪高HP慢攻"},
// 远程位怪物 (站在后排,输出更高)
// 后排怪物 (站在后排,输出更高)
6004:{uuid:6004,name:"兽人射手",path:"m4", fac:FacSet.MON,lv:1,type:HType.Long,hp:190,ap:35,speed:70,
skills:{6008:{uuid:6008,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"远程高DPS怪"},
skills:{6008:{uuid:6008,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"后排高DPS怪"},
6005:{uuid:6005,name:"兽人刺客",path:"m5", fac:FacSet.MON,lv:1,type:HType.Long,hp:210,ap:38,speed:130,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"高AP快速攻击刺客"},
// 特殊位怪物
6006:{uuid:6006,name:"骷髅领主",path:"m6", fac:FacSet.MON,lv:1,type:HType.Melee,hp:5000,ap:20,speed:60,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"MiniBoss级坦克"},
6007:{uuid:6007,name:"兽人术士",path:"m7", fac:FacSet.MON,lv:1,type:HType.Melee,hp:300,ap:24,speed:70,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"法师怪,远程魔法攻击"},
6008:{uuid:6008,name:"兽人火法",path:"m8", fac:FacSet.MON,lv:1,type:HType.Melee,hp:270,ap:32,speed:70,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"高输出法师怪"},
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"前排MiniBoss级坦克"},
6007:{uuid:6007,name:"兽人术士",path:"m7", fac:FacSet.MON,lv:1,type:HType.Long,hp:300,ap:24,speed:70,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"后排法师怪,魔法攻击"},
6008:{uuid:6008,name:"兽人火法",path:"m8", fac:FacSet.MON,lv:1,type:HType.Long,hp:270,ap:32,speed:70,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"后排高输出法师怪"},
// BOSS怪物 — Boss节奏1.2-1.5s删除不存在的6206技能
6101:{uuid:6101,name:"兽人首领-双刀战士",path:"mb1", fac:FacSet.MON,lv:6,type:HType.Long,hp:1900,ap:30,speed:120,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"远程Boss高攻速"},
6101:{uuid:6101,name:"兽人首领-双刀战士",path:"mb1", fac:FacSet.MON,lv:6,type:HType.Melee,hp:1900,ap:30,speed:120,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"前排Boss高攻速"},
6102:{uuid:6102,name:"兽人首领-斧头战士",path:"mb2", fac:FacSet.MON,lv:6,type:HType.Melee,hp:7500,ap:26,speed:60,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"近战Boss超高HP"},
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"前排Boss超高HP"},
6103:{uuid:6103,name:"兽人首领-魔法师",path:"mb3", fac:FacSet.MON,lv:6,type:HType.Long,hp:2250,ap:38,speed:110,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"远程法系Boss高AP"},
6104:{uuid:6104,name:"兽人首领-射手",path:"mb4", fac:FacSet.MON,lv:6,type:HType.Melee,hp:6800,ap:30,speed:70,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"近战位Boss均衡型"},
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"后排法系Boss高AP"},
6104:{uuid:6104,name:"兽人首领-射手",path:"mb4", fac:FacSet.MON,lv:6,type:HType.Long,hp:6800,ap:30,speed:70,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"后排位Boss均衡型"},
6105:{uuid:6105,name:"亡灵首领-法师",path:"mb5", fac:FacSet.MON,lv:6,type:HType.Long,hp:2600,ap:42,speed:110,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"远程高AP Boss"},
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"后排高AP Boss"},
6106:{uuid:6106,name:"亡灵首领-骑马战士",path:"mb6", fac:FacSet.MON,lv:6,type:HType.Melee,hp:9000,ap:26,speed:130,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"终极Boss最高HP+高速"},
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"前排终极Boss最高HP+高速"},
};
export const HeroList: number[] = [
// 铁壁反伤流
5001, 5002, 5007, 5008, 5009,
// 攻速叠伤流
5006, 5205, 5403,
// 冰冻控制流
5101, 5106, 5107, 5108,
// 治疗续航流
5301, 5302, 5303, 5304, 5305,
// 穿刺风怒流
5209, 5210, 5404,
// 暴击爆发流
5211, 5212, 5405,
// 经济滚雪球流
5109, 5306, 5213,
// 献祭亡语流
5010, 5110, 5111,
// atked 类(战士 · 自身强化)
5011, 5012, 5013, 5014, 5015, 5016,
// atking 刺客(自身强化)
5021, 5022, 5023,
// atking 射手(队友强化)
5031, 5032, 5033,
// dead 类(死亡遗产)
5041, 5042, 5043, 5044, 5045,
// fstart 类(法师 · 战前增益)
5051, 5052, 5053, 5054, 5055, 5056,
// field 类(法师 · 驻场光环)
5061,
// fend + atking 类(辅助 · 治疗 + 波次增益)
5071, 5072, 5073, 5074, 5075,
];

File diff suppressed because it is too large Load Diff

View File

@@ -249,7 +249,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
// 触发复活动画
if (targetView && reviveSkillConf) {
targetView.playReady(reviveSkillConf.readyAnm);
targetView.skill_name('', reviveSkillConf.uuid);
targetView.skill_name('', reviveSkillConf.uuid, SkillTriggerType.Revive);
// 延迟 0.5 秒后恢复状态,让特效有时间播放,同时也能体现“正在复活”的过程
targetView.scheduleOnce(() => {
if (targetView.node && targetView.node.isValid) {
@@ -327,6 +327,10 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpd
const view = entity.get(HeroViewComp);
if (view) {
SkillTriggerHelper.trigger(SkillTriggerType.Dead, TAttrsComp, view);
// 【新增】仅英雄阵营派发全局死亡事件(怪物死亡会误触发海量卡牌效果)
if (TAttrsComp.fac === FacSet.HERO) {
oops.message.dispatchEvent(GameEvent.HeroDead, { eid: entity.eid });
}
}
}

View File

@@ -9,6 +9,7 @@ import { SkillSet,} from "../common/config/SkillSet";
import { HeroInfo } from "../common/config/heroSet";
import { oops } from "db://oops-framework/core/Oops";
import { UIID } from "../common/config/GameUIConfig";
import { GameEvent } from "../common/config/GameEvent";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { Tooltip } from "../skill/Tooltip";
import { timedCom } from "../skill/timedCom";
@@ -321,10 +322,10 @@ export class HeroViewComp extends CCComp {
Tooltip.load(pos, type, value, s_uuid, this.node);
}
/** 技能提示 */
public skill_name(value: string = "", s_uuid: number = 1001, y: number = 50) {
public skill_name(value: string = "", s_uuid: number = 1001, triggerType: string = "", y: number = 50) {
let pos = v3(0, 60);
pos.y = pos.y + y;
Tooltip.load(pos, TooltipTypes.skill, value, s_uuid, this.node);
Tooltip.load(pos, TooltipTypes.skill, value, s_uuid, this.node, 1, this.model?.fac ?? FacSet.MON, triggerType);
}
/** 血量提示(伤害数字) */
private hp_tip(type: number = 1, value: string = "", s_uuid: number = 1001, y: number = 0) {
@@ -454,6 +455,12 @@ export class HeroViewComp extends CCComp {
this.top_node.active = true;
this.status_change("idle");
// 【新增】仅英雄阵营派发复活成功事件供卡牌技能HeroCall 类型)监听
// 统一在此派发可覆盖两条复活路径:复活技能触发 + 关卡战斗准备阶段恢复
if (this.model && this.model.fac === FacSet.HERO && this.ent) {
oops.message.dispatchEvent(GameEvent.ReviveSuccess, { eid: this.ent.eid });
}
}

View File

@@ -278,7 +278,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if(castTimes >1){
val = "*"+castTimes.toString
}
heroView.skill_name(val,s_uuid)
heroView.skill_name(val,s_uuid,triggerType)
for (let i = 0; i < castTimes; i++) {
if (!heroView.node || !heroView.node.isValid) return;
if (isFriendly) {

View File

@@ -101,10 +101,19 @@ export class SkillTriggerHelper {
*/
private static handleAtking(model: HeroAttrsComp, view: HeroViewComp) {
if (!model.atking || model.atking.length === 0) return;
let triggerCount = 1;
if (model.fac === FacSet.HERO) {
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.AtkCount);
}
triggerCount = Math.max(1, Math.floor(triggerCount));
model.atking.forEach(atkConfig => {
// atk_count 代表已进行的普攻次数。当其余数刚好整除配置阈值时触发。
if (model.atk_count > 0 && model.atk_count % atkConfig.t_num === 0) {
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atking, atkConfig.overrides);
for (let i = 0; i < triggerCount; i++) {
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atking, atkConfig.overrides);
}
}
});
}
@@ -115,10 +124,19 @@ export class SkillTriggerHelper {
*/
private static handleAtked(model: HeroAttrsComp, view: HeroViewComp) {
if (!model.atked || model.atked.length === 0) return;
let triggerCount = 1;
if (model.fac === FacSet.HERO) {
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.BeAtkCount);
}
triggerCount = Math.max(1, Math.floor(triggerCount));
model.atked.forEach(atkConfig => {
// atked_count 代表已承受的受击次数。当其余数刚好整除配置阈值时触发。
if (model.atked_count > 0 && model.atked_count % atkConfig.t_num === 0) {
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atked, atkConfig.overrides);
for (let i = 0; i < triggerCount; i++) {
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atked, atkConfig.overrides);
}
}
});
}

View File

@@ -20,7 +20,7 @@
* - smc.vmdata.mission_data —— 读写局内金币
*/
import { mLogger } from "../common/Logger";
import { _decorator, Animation, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, resources, Light, UITransform, Widget } from "cc";
import { _decorator, Animation, AnimationClip, EventTouch, Input, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, input, resources, Light, UITransform, Widget } 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 { CardConfig, CardType, SpecialRefreshCardList, SpecialUpgradeCardList, CKind, CardPoolList } from "../common/config/CardSet";
@@ -62,6 +62,9 @@ export class CardComp extends CCComp {
/** 解锁态图标节点(显示时表示本槽位未锁定,可点击上锁) */
@property(Node)
unLock: Node = null!
@property(Node)
call_btn: Node = null!
/** 英雄卡信息面板(显示 AP / HP 废弃,改用直接绑定 */
// @property(Node)
// info_node=null!
@@ -132,10 +135,8 @@ export class CardComp extends CCComp {
* 防止快速切卡时旧回调错误覆盖新图标。
*/
private iconVisualToken: number = 0;
/** 是否触发了长按 */
private isLongPressed: boolean = false;
/** 长按触发时间(秒) */
private readonly LONG_PRESS_DURATION: number = 0.5;
/** 视为"点击"的最大位移阈值(像素),小于该值视为点击而非拖拽 */
private readonly tapMoveThreshold: number = 10;
// ======================== 生命周期 ========================
@@ -151,7 +152,8 @@ export class CardComp extends CCComp {
this.opacityComp.opacity = 255;
this.updateLockUI();
this.applyEmptyUI();
// call_btn 默认隐藏,仅在点击本卡时显示
this.hideCallBtn();
}
/** 组件销毁时解绑所有事件,防止残留回调 */
@@ -310,7 +312,7 @@ export class CardComp extends CCComp {
// 使用统一经济管理入口消费金币
const success = MissionEconomy.spendCoin(cardCost);
if (!success) {
oops.gui.toast(`金币不足,召唤需要${cardCost}`);
oops.message.dispatchEvent(GameEvent.ShowSmallTip, "buy_coin");
this.playReboundAnim();
mLogger.log(this.debugMode, "CardComp", "use card coin not enough", {
uuid: this.cardData.uuid,
@@ -445,6 +447,7 @@ export class CardComp extends CCComp {
this.node.setScale(new Vec3(1, 1, 1));
this.updateLockUI();
this.applyEmptyUI();
this.hideCallBtn();
this.node.active = false;
}
@@ -458,6 +461,10 @@ export class CardComp extends CCComp {
this.node.on(NodeEventType.TOUCH_CANCEL, this.onCardTouchCancel, this);
this.Lock?.on(NodeEventType.TOUCH_END, this.onToggleLock, this);
this.unLock?.on(NodeEventType.TOUCH_END, this.onToggleLock, this);
// 召唤按钮点击 = 使用卡牌
this.call_btn?.on(NodeEventType.TOUCH_END, this.onCallBtnClick, this);
// 监听跨卡联动:其他卡牌被点击时隐藏本卡 call_btn
oops.message.on(GameEvent.CardSelected, this.onOtherCardSelected, this);
}
/** 解绑触控,防止节点销毁后残留回调 */
@@ -474,19 +481,22 @@ export class CardComp extends CCComp {
if (this.unLock && this.unLock.isValid) {
this.unLock.off(NodeEventType.TOUCH_END, this.onToggleLock, this);
}
if (this.call_btn && this.call_btn.isValid) {
this.call_btn.off(NodeEventType.TOUCH_END, this.onCallBtnClick, this);
}
oops.message.off(GameEvent.CardSelected, this.onOtherCardSelected, this);
// 兜底注销全局触摸监听showCallBtn 后组件销毁的场景)
input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
}
// ======================== 触摸交互 ========================
/** 触摸开始:记录起点 Y,进入拖拽状态 */
/** 触摸开始:记录起点坐标,进入拖拽状态 */
private onCardTouchStart(event: EventTouch) {
if (!this.cardData || this.isUsing) return;
this.touchStartY = event.getUILocation().y;
this.touchStartX = event.getUILocation().x;
this.isDragging = true;
this.isLongPressed = false;
this.unschedule(this.onLongPress);
this.scheduleOnce(this.onLongPress, this.LONG_PRESS_DURATION);
}
/**
@@ -496,25 +506,15 @@ export class CardComp extends CCComp {
if (!this.isDragging || !this.cardData || this.isUsing) return;
const currentY = event.getUILocation().y;
const deltaY = Math.max(0, currentY - this.touchStartY);
if (deltaY > 10) {
this.unschedule(this.onLongPress);
if (this.isLongPressed) {
this.isLongPressed = false;
oops.gui.remove(UIID.HInfo);
}
}
this.node.setPosition(this.restPosition.x, this.restPosition.y + deltaY, this.restPosition.z);
}
/**
* 触摸结束:
* - 技能卡:点击即可使用
* - 英雄卡/其他:上拉距离 >= dragUseThreshold → 视为"使用卡牌"
* - 否则视为"点击"或者"长按结束"
* - 上拉距离 >= dragUseThreshold → 视为"使用卡牌"
* - 位移很小(点击)→ 触发 onCardTap显示 call_btn / 打开信息面板 / 通知其他卡牌联动
*/
private onCardTouchEnd(event: EventTouch) {
this.unschedule(this.onLongPress);
if (!this.isDragging || !this.cardData || this.isUsing) return;
const endY = event.getUILocation().y;
const endX = event.getUILocation().x;
@@ -522,11 +522,6 @@ export class CardComp extends CCComp {
const deltaX = endX - this.touchStartX;
this.isDragging = false;
if (this.isLongPressed) {
this.isLongPressed = false;
oops.gui.remove(UIID.HInfo);
}
// 英雄卡保持上划使用
if (deltaY >= this.dragUseThreshold) {
const used = this.useCard();
@@ -536,31 +531,93 @@ export class CardComp extends CCComp {
return;
}
// 位移小于阈值视为"点击"
if (Math.abs(deltaY) < this.tapMoveThreshold && Math.abs(deltaX) < this.tapMoveThreshold) {
this.onCardTap();
return;
}
this.playReboundAnim();
}
/** 触摸取消:回弹至原位 */
private onCardTouchCancel() {
this.unschedule(this.onLongPress);
if (!this.isDragging || this.isUsing) return;
this.isDragging = false;
if (this.isLongPressed) {
this.isLongPressed = false;
oops.gui.remove(UIID.HInfo);
}
this.playReboundAnim();
}
/** 长按触发:英雄卡打开英雄信息预览面板 */
private onLongPress() {
if (!this.isDragging || this.isUsing) return;
if (!this.cardData || this.card_type !== CardType.Hero) return;
this.isLongPressed = true;
const heroUuid = this.card_uuid;
const heroLv = Math.max(1, this.cardData.hero_lv ?? 1);
const poolLv = Math.max(1, this.cardData.base_pool_lv ?? this.cardData.pool_lv ?? 1);
oops.gui.remove(UIID.HInfo);
oops.gui.open(UIID.HInfo, { heroUuid, heroLv, poolLv });
/**
* 卡牌被点击(非拖拽):
* 1. 显示本卡的召唤按钮 call_btn。
* 2. 派发 CardSelected 事件,其他卡牌收到后隐藏各自的 call_btn。
* 3. 英雄卡打开 HInfo 信息面板(点击查看,替代原长按)。
*/
private onCardTap() {
this.playReboundAnim();
this.showCallBtn();
oops.message.dispatchEvent(GameEvent.CardSelected, this);
// 英雄卡打开信息面板(点击替代长按)
if (this.card_type === CardType.Hero) {
const heroUuid = this.card_uuid;
const heroLv = Math.max(1, this.cardData?.hero_lv ?? 1);
const poolLv = Math.max(1, this.cardData?.base_pool_lv ?? this.cardData?.pool_lv ?? 1);
oops.gui.remove(UIID.HInfo);
oops.gui.open(UIID.HInfo, { heroUuid, heroLv, poolLv });
}
}
/**
* 其他卡牌被点击时联动隐藏本卡 call_btn。
* @param event 事件名ListenerFunc 约定的第一个参数)
* @param source 被点击的 CardComp 实例
*/
private onOtherCardSelected(event: string, source: CardComp) {
if (source === this) return;
this.hideCallBtn();
}
/** 显示召唤按钮 */
private showCallBtn() {
if (this.call_btn && this.call_btn.isValid) {
this.call_btn.active = true;
// 按需监听全局触摸:点击本体以外区域时隐藏召唤按钮
input.on(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
}
}
/** 隐藏召唤按钮 */
private hideCallBtn() {
if (this.call_btn && this.call_btn.isValid) {
this.call_btn.active = false;
}
// 注销全局监听无论是否曾注册off 均安全)
input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
}
/**
* 全局触摸结束:点击落点不在召唤按钮 / 卡牌本体包围盒内时隐藏召唤按钮。
* Why: 复用 HInfoComp 的"点击外部关闭"交互模式,避免 call_btn 长期滞留。
* 排除卡牌本体是防止与 onCardTap 的"点击卡牌显示 call_btn"交互相互冲突。
* @param event 全局触摸事件
*/
private onGlobalTouchEnd(event: EventTouch) {
if (!this.call_btn || !this.call_btn.active) return;
const uiPos = event.getUILocation();
// 命中召唤按钮本体 → 保留
const btnTrans = this.call_btn.getComponent(UITransform);
if (btnTrans && btnTrans.getBoundingBoxToWorld().contains(uiPos)) return;
// 命中卡牌本体 → 保留(由 onCardTap 自行管理显隐)
const cardTrans = this.node.getComponent(UITransform);
if (cardTrans && cardTrans.getBoundingBoxToWorld().contains(uiPos)) return;
// 其余区域 → 隐藏
this.hideCallBtn();
}
/** 召唤按钮点击回调:阻止冒泡后触发使用卡牌 */
private onCallBtnClick(event: EventTouch) {
event.propagationStopped = true;
this.useCard();
}
/**

View File

@@ -19,14 +19,14 @@
* - Hero —— 英雄 ECS 实体类(用于出售删除)
* - UIID.IBox —— 英雄详情弹窗 ID
*/
import { _decorator, Animation, AnimationClip, Button, Event, Label, Node, NodeEventType, Sprite, resources, CCInteger, SpriteFrame } from "cc";
import { _decorator, Animation, AnimationClip, Button, Event, EventTouch, Input, Label, Node, NodeEventType, Sprite, UITransform, input, resources, CCInteger, SpriteFrame } 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 { HeroInfo } from "../common/config/heroSet";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { Hero } from "../hero/Hero";
import { FieldSkillType } from "../common/config/SkillSet";
import { FieldSkillType, FieldSkillSet } from "../common/config/SkillSet";
import { buildSkillDesc } from "../common/config/HeroSkillDesc";
import { GameEvent } from "../common/config/GameEvent";
import { oops } from "db://oops-framework/core/Oops";
@@ -35,7 +35,7 @@ import { mLogger } from "../common/Logger";
import { MissionHeroComp } from "./MissionHeroComp";
import { MoveComp } from "../hero/MoveComp";
import { FacSet, getLvColor } from "../common/config/GameSet";
import { CKind, CardPoolList } from "../common/config/CardSet";
import { CKind, CardPoolList, CardConfig, CardTriggerType } from "../common/config/CardSet";
import { SkillSet } from "../common/config/SkillSet";
import { CardBgComp } from "./CardBgComp";
import { MissionEconomy } from "./MissionEconomy";
@@ -54,6 +54,9 @@ export class HInfoComp extends CCComp {
/** 英雄 idle 动画图标节点 */
@property(Node)
icon_node=null!
/** 技能图标节点 */
@property(Node)
skill_icon_node=null!
/** 出售按钮节点 */
@property(Node)
sell_node=null!
@@ -153,9 +156,15 @@ export class HInfoComp extends CCComp {
const heroUuid = this.previewUuid;
const heroLv = Math.max(1, this.previewLv);
// 技能卡无等级概念,隐藏等级节点;英雄卡显示等级
if (this.lv_node) {
this.lv_node.string = `Lv.${heroLv}`;
this.lv_node.color = getLvColor(heroLv);
if (this.isSkillCard) {
this.lv_node.node.active = false;
} else {
this.lv_node.node.active = true;
this.lv_node.string = `Lv.${heroLv}`;
this.lv_node.color = getLvColor(heroLv);
}
}
const kindName = this.isSkillCard ? CKind[CKind.Skill] : CKind[CKind.Hero];
@@ -169,6 +178,10 @@ export class HInfoComp extends CCComp {
if (this.isSkillCard) {
// ================= 技能卡预览 =================
// 互斥:技能卡只显示技能图标,隐藏英雄图标
if (this.icon_node) this.icon_node.active = false;
if (this.skill_icon_node) this.skill_icon_node.active = true;
const config = CardPoolList.find(c => c.uuid === heroUuid);
if (!config) return;
@@ -192,10 +205,14 @@ export class HInfoComp extends CCComp {
if (heroUuid !== this.iconHeroUuid) {
this.iconHeroUuid = heroUuid;
this.iconVisualToken += 1;
this.updateSkillAnimation(this.icon_node, config.skill ?? heroUuid, this.iconVisualToken);
this.updateSkillAnimation(this.skill_icon_node, config, this.iconVisualToken);
}
} else {
// ================= 英雄卡预览 =================
// 互斥:英雄卡只显示英雄图标,隐藏技能图标
if (this.icon_node) this.icon_node.active = true;
if (this.skill_icon_node) this.skill_icon_node.active = false;
const hero = HeroInfo[heroUuid];
if (!hero) return;
@@ -461,31 +478,35 @@ export class HInfoComp extends CCComp {
/**
* 为技能图标加载静态图片。
* @param node 图标节点
* @param skillUuid 技能 UUID
* @param token 视觉令牌
* 图标来源优先级config.icon > 按 trigger_type 自动取Interval→SkillSetField→FieldSkillSet
* @param node 图标节点
* @param config 卡牌配置
* @param token 视觉令牌
*/
private updateSkillAnimation(node: Node, skillUuid: number, token: number) {
private updateSkillAnimation(node: Node, config: CardConfig, token: number) {
if (!node) return;
this.clearIconAnimation(node); // 停止之前的动画
this.clearIconAnimation(node);
const sprite = node.getComponent(Sprite) || node.getComponentInChildren(Sprite);
if (!sprite) return;
const skillData = SkillSet[skillUuid];
if (!skillData || !skillData.icon) {
sprite.spriteFrame = null;
return;
}
sprite.spriteFrame = null;
if (smc.uiconsAtlas) {
const frame = smc.uiconsAtlas.getSpriteFrame(skillData.icon);
if (frame && token === this.iconVisualToken) {
sprite.spriteFrame = frame;
// 优先使用卡牌自定义 icon未定义则按 trigger_type 自动取
let iconId: string | undefined = config.icon;
if (!iconId) {
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];
iconId = fieldUuid ? FieldSkillSet[fieldUuid]?.icon : undefined;
}
} else {
const sf = oops.res.get("game/heros/cards/" + skillData.icon, SpriteFrame) as SpriteFrame;
if (sf && token === this.iconVisualToken) {
sprite.spriteFrame = sf;
}
if (!iconId) return;
// 与 CardComp / SCardComp 一致:仅信任全局缓存的 uiconsAtlas
if (smc.uiconsAtlas) {
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
if (token === this.iconVisualToken) {
sprite.spriteFrame = frame || null;
}
}
}
@@ -511,6 +532,8 @@ export class HInfoComp extends CCComp {
this.sell_node?.on(Button.EventType.CLICK, this.onSellHero, this);
this.close_node?.on(Button.EventType.CLICK, this.onClosePanel, this);
// this.node.on(NodeEventType.TOUCH_END, this.onOpenIBox, this);
// 监听全局触摸结束:点击本体节点以外区域时关闭面板
input.on(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
}
private unbindEvents() {
@@ -523,6 +546,28 @@ export class HInfoComp extends CCComp {
// if (this.node && this.node.isValid) {
// this.node.off(NodeEventType.TOUCH_END, this.onOpenIBox, this);
// }
input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
}
/**
* 全局触摸结束处理:点击落点不在本体节点包围盒内时关闭面板。
* Why: 弹窗常见的"点击空白处关闭"交互,用全局 input + 包围盒命中检测实现,
* 无需额外遮罩节点,也不会误关面板内部(含子按钮)的点击。
* @param event 全局触摸事件
*/
private onGlobalTouchEnd(event: EventTouch) {
if (this.isClosing) return;
const transform = this.node.getComponent(UITransform);
if (!transform) {
// 缺少 UITransform 时保守处理为关闭
this.onClosePanel();
return;
}
const worldRect = transform.getBoundingBoxToWorld();
const uiPos = event.getUILocation();
if (!worldRect.contains(uiPos)) {
this.onClosePanel();
}
}
/**

View File

@@ -63,16 +63,16 @@ export class MissSkillsComp extends CCComp {
* 第 2 行 y=320x = -320, -240, -160, -80, 0
*/
private slots: SkillBoxSlot[] = [
{ x: -320, y: 200, used: false, node: null },
{ x: -240, y: 200, used: false, node: null },
{ x: -160, y: 200, used: false, node: null },
{ x: -80, y: 200, used: false, node: null },
{ x: 0, y: 200, used: false, node: null },
{ x: -320, y: 300, used: false, node: null },
{ x: -240, y: 300, used: false, node: null },
{ x: -160, y: 300, used: false, node: null },
{ x: -80, y: 300, used: false, node: null },
{ x: 0, y: 300, used: false, node: null },
{ x: -310, y: 200, used: false, node: null },
{ x: -220, y: 200, used: false, node: null },
{ x: -130, y: 200, used: false, node: null },
{ x: -40, y: 200, used: false, node: null },
{ x: 50, y: 200, used: false, node: null },
{ x: -310, y: 300, used: false, node: null },
{ x: -220, y: 300, used: false, node: null },
{ x: -130, y: 300, used: false, node: null },
{ x: -40, y: 300, used: false, node: null },
{ x: 50, y: 300, used: false, node: null },
];
/** 注册事件监听 */

View File

@@ -45,10 +45,11 @@ import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { HeroInfo, HType } from "../common/config/heroSet";
import { HeroViewComp } from "../hero/HeroViewComp";
import { FacSet, FightSet } from "../common/config/GameSet";
import { FacSet, FightSet, CARD_POOL_UPGRADE_WAVES, SKILL_CARD_WAVES } from "../common/config/GameSet";
import { MoveComp } from "../hero/MoveComp";
import { MissionHeroComp } from "./MissionHeroComp";
import { MissionEconomy } from "./MissionEconomy";
import { MissionComp } from "./MissionComp";
import { UIID } from "../common/config/GameUIConfig";
const { ccclass, property } = _decorator;
@@ -234,7 +235,7 @@ export class MissionCardComp extends CCComp {
this.dispatchCardsToSlots(cards);
const wave = this.getCurrentWave();
if ([1, 5, 10, 15, 20].includes(wave)) {
if (SKILL_CARD_WAVES.includes(wave)) {
this.showSkillCardPopup();
}
@@ -292,6 +293,7 @@ export class MissionCardComp extends CCComp {
oops.message.on(GameEvent.UseSkillCard, this.onUseSkillCard, this);
oops.message.on(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
oops.message.on(GameEvent.CardPoolUpgrade, this.onCardPoolUpgrade, this);
oops.message.on(GameEvent.ShowSmallTip, this.onShowSmallTip, this);
/** 按钮触控事件:抽卡与卡池升级 */
this.cards_chou?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
@@ -358,12 +360,58 @@ export class MissionCardComp extends CCComp {
});
// 提示卡池升级
oops.gui.toast(`卡池已升至${this.poolLv}`);
this.showSmallTip("pool_upgrade");
// 更新UI
this.updatePoolLvUI();
}
private onShowSmallTip(event: string, args: any) {
const type = args as string;
this.showSmallTip(type as any);
}
public showSmallTip(type: "refresh_coin" | "pool_upgrade" | "buy_coin" | "hero_full") {
let targetNode: Node | null = null;
switch (type) {
case "refresh_coin":
targetNode = this.cards_chou;
break;
case "pool_upgrade":
targetNode = this.pool_lv_node;
break;
case "buy_coin":
targetNode = this.coins_node;
break;
case "hero_full":
targetNode = this.hero_num_node;
break;
}
if (targetNode && targetNode.isValid) {
const tipNode = targetNode.getChildByName("smalltip");
if (tipNode) {
tipNode.active = true;
Tween.stopAllByTarget(tipNode);
// 设置初始状态:缩放为 0
tipNode.setScale(new Vec3(0, 0, 1));
tween(tipNode)
// 1. 弹出动画(微放大再回弹)
.to(0.15, { scale: new Vec3(1.1, 1.1, 1) }, { easing: 'quadOut' })
.to(0.05, { scale: new Vec3(1, 1, 1) })
// 2. 停留 1 秒
.delay(1)
// 3. 缩小消失动画
.to(0.15, { scale: new Vec3(0, 0, 1) }, { easing: 'quadIn' })
.call(() => {
if (tipNode && tipNode.isValid) tipNode.active = false;
})
.start();
}
}
}
private onPhasePrepareStart() {
this.updateHeroNumUI(true, true);
}
@@ -378,7 +426,7 @@ export class MissionCardComp extends CCComp {
this.dispatchCardsToSlots(cards);
const wave = this.getCurrentWave();
if ([1, 5, 10, 15, 20].includes(wave)) {
if (SKILL_CARD_WAVES.includes(wave)) {
this.showSkillCardPopup();
}
}
@@ -457,6 +505,10 @@ export class MissionCardComp extends CCComp {
if (!smc.finish_guides.includes(3)) {
oops.gui.open(UIID.Guide3);
}
// 驻场技能可能影响刷新费用(如"刷新优惠"),延迟到下一帧刷新费用 UI
// 确保 MissSkillsComp 已创建 SkillBoxComp 并注册驻场效果
this.scheduleOnce(() => this.updateCoinAndCostUI(), 0);
}
/** 解除按钮监听,避免节点销毁后回调泄漏 */
@@ -469,6 +521,7 @@ export class MissionCardComp extends CCComp {
oops.message.off(GameEvent.UseSkillCard, this.onUseSkillCard, this);
oops.message.off(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
oops.message.off(GameEvent.CardPoolUpgrade, this.onCardPoolUpgrade, this);
oops.message.off(GameEvent.ShowSmallTip, this.onShowSmallTip, 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);
@@ -558,7 +611,7 @@ export class MissionCardComp extends CCComp {
}
payload.cancel = true;
payload.reason = "hero_limit";
oops.gui.toast(`英雄已满 (${current}/${heroMax})`);
this.showSmallTip("hero_full");
this.playHeroNumDeniedAnim();
}
}
@@ -678,7 +731,7 @@ export class MissionCardComp extends CCComp {
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
const success = MissionEconomy.executeRefresh(this.refreshCost);
if (!success) {
oops.gui.toast(`金币不足,刷新需要${cost}`);
this.showSmallTip("refresh_coin");
return;
}
const cards = this.buildSkillDrawCards();
@@ -735,7 +788,7 @@ export class MissionCardComp extends CCComp {
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
const success = MissionEconomy.executeRefresh(this.refreshCost);
if (!success) {
oops.gui.toast(`金币不足,刷新需要${cost}`);
this.showSmallTip("refresh_coin");
this.updateCoinAndCostUI();
mLogger.log(this.debugMode, "MissionCardComp", "draw coin not enough", {
currentCoin: MissionEconomy.getCoin(),
@@ -857,7 +910,8 @@ export class MissionCardComp extends CCComp {
const cards = drawCardsByRule(this.poolLv, {
count: 3,
type: targetType,
wave: currentWave
wave: currentWave,
unique: true // 保证技能牌不重复
});
if (cards.length >= 3) return cards.slice(0, 3);
@@ -866,10 +920,18 @@ export class MissionCardComp extends CCComp {
const fallback = drawCardsByRule(this.poolLv, {
count: 3,
type: targetType,
wave: currentWave
wave: currentWave,
unique: true
});
if (fallback.length === 0) break;
filled.push(fallback[filled.length % fallback.length]);
// 如果池子数量不足,只能被迫允许重复,但尽量拿没被抽到的
const fPick = fallback.find(c => !filled.some(fc => fc.uuid === c.uuid));
if (fPick) {
filled.push(fPick);
} else {
filled.push(fallback[filled.length % fallback.length]);
}
}
return filled;
}
@@ -974,23 +1036,6 @@ export class MissionCardComp extends CCComp {
}
/** 更新升级按钮上的等级文案,反馈当前卡池层级 */
private updatePoolLvUI() {
// if (this.cards_up) {
// const nobg = this.cards_up.getChildByName("nobg");
// if (nobg) {
// nobg.active = !this.canUpPool();
// }
// const coinNode = this.cards_up.getChildByName("coin");
// const label = coinNode?.getChildByName("num")?.getComponent(Label);
// if (this.poolLv >= CARD_POOL_MAX_LEVEL) {
// if (label) {
// label.string = `0`;
// }
// } else {
// if (label) {
// label.string = `${this.getUpgradeCost(this.poolLv)}`;
// }
// }
// }
if (this.pool_lv_node) {
this.pool_lv_node.active = true;
const lv = Math.max(CARD_POOL_INIT_LEVEL, Math.min(CARD_POOL_MAX_LEVEL, Math.floor(this.poolLv)));
@@ -1001,12 +1046,48 @@ export class MissionCardComp extends CCComp {
label.string = `lv.${lv}`;
}
}
// const widget = this.pool_lv_node.getComponent(Widget);
// if (widget) widget.updateAlignment();
// this.pool_lv_node.children.forEach(child => {
// const childWidget = child.getComponent(Widget);
// if (childWidget) childWidget.updateAlignment();
// });
const nextNode = this.pool_lv_node.getChildByName("next");
if (nextNode) {
const nextLabel = nextNode.getComponent(Label);
if (nextLabel) {
if (this.poolLv >= CARD_POOL_MAX_LEVEL) {
nextLabel.string = `已满级`;
} else {
// 优先取 MissionComp 运行时配置,缺失时回退到全局常量
let upgradeWaves: number[] = CARD_POOL_UPGRADE_WAVES;
ecs.query(ecs.allOf(MissionComp)).forEach((entity) => {
const mission = entity.get(MissionComp);
if (mission && mission.cardPoolUpgradeWaves && mission.cardPoolUpgradeWaves.length > 0) {
upgradeWaves = mission.cardPoolUpgradeWaves;
}
});
// 已完成的升级次数 = 当前等级 - 初始等级
// 例poolLv=2INIT=1→ 已升 1 次 → 下一升级对应 upgradeWaves[1]
const upgradedCount = Math.max(0, Math.floor(this.poolLv) - CARD_POOL_INIT_LEVEL);
const currentWave = this.getCurrentWave();
if (upgradedCount >= upgradeWaves.length) {
// 配置已耗尽但等级未到上限(配置缺陷)
nextLabel.string = `已满级`;
} else {
const nextWave = upgradeWaves[upgradedCount];
if (nextWave > currentWave) {
const remain = nextWave - currentWave;
nextLabel.string = `${remain} 回合后升级`;
} else if (nextWave === currentWave) {
// 当前波次正好是升级波次(事件可能即将触发或刚刚触发)
nextLabel.string = `本回合升级`;
} else {
// nextWave < currentWave异常状态升级事件未按时触发
nextLabel.string = `即将升级`;
}
}
}
}
}
const peak = 1.2
this.playHeroNumNodePop(this.pool_lv_node, peak);
}
@@ -1043,7 +1124,7 @@ export class MissionCardComp extends CCComp {
}
private updateCoinAndCostUI() {
// this.updatePoolLvUI();
this.updatePoolLvUI();
this.updateDrawCostUI();
}

View File

@@ -39,13 +39,12 @@ import { HeroViewComp } from "../hero/HeroViewComp";
import { SkillTriggerHelper } from "../hero/SkillTriggerHelper";
import { UIID } from "../common/config/GameUIConfig";
import { SkillView } from "../skill/SkillView";
import { FacSet, FightSet } from "../common/config/GameSet";
import { FacSet, FightSet, CARD_POOL_UPGRADE_WAVES } from "../common/config/GameSet";
import { HeroInfo } from "../common/config/heroSet";
import { mLogger } from "../common/Logger";
import { Monster } from "../hero/Mon";
import { Skill } from "../skill/Skill";
import { Tooltip } from "../skill/Tooltip";
import { CardInitCoins } from "../common/config/CardSet";
import { Timer } from "db://oops-framework/core/common/timer/Timer";
import { FieldSkillType } from "../common/config/SkillSet";
import { FieldSkillHelper } from "../hero/FieldSkillHelper";
@@ -88,9 +87,8 @@ export class MissionComp extends CCComp {
private maxMonsterCount: number = 80;
/** 怪物数量恢复阈值(降至此值以下恢复刷怪) */
private resumeMonsterCount: number = 45;
/** 卡池升级波次配置:达到对应波次时,推送卡池升级事件 */
@property({ type: [CCInteger], tooltip: "卡池升级波次配置,例如 [10, 20] 表示第10波升到2级第20波升到3级" })
cardPoolUpgradeWaves: number[] = [5, 10];
/** 卡池升级波次配置(默认值来自 GameSet.CARD_POOL_UPGRADE_WAVES保持全局统一 */
public cardPoolUpgradeWaves: number[] = CARD_POOL_UPGRADE_WAVES;
// ======================== 编辑器绑定节点 ========================
@@ -198,10 +196,12 @@ export class MissionComp extends CCComp {
this.mission_start();
}, 0);
smc.map.MapView.scene.mapLayer.stopAnimations();
smc.map.MapView.scene.mapLayer.node.getChildByName("fight").getChildByName("fbox").active = true;
}
onDestroy() {
smc.map.MapView.scene.mapLayer.playAnimations()
smc.map.MapView.scene.mapLayer.node.getChildByName("fight").getChildByName("fbox").active = false;
super.onDestroy();
if (this.start_btn && this.start_btn.isValid) {
this.start_btn.off(NodeEventType.TOUCH_END, this.onStartFightBtnClick, this);
@@ -495,6 +495,9 @@ export class MissionComp extends CCComp {
// 战斗结束阶段给予所有英雄恢复70%血量的技能效果
this.healAllHeroes();
// 【新增】派发每波战斗结束事件,供卡牌技能监听(区别于整局结束的 MissionEnd
oops.message.dispatchEvent(GameEvent.FightEnd);
break;
case MissionPhase.Settle:
@@ -732,7 +735,7 @@ export class MissionComp extends CCComp {
// 重置所有的战局得分数据,防止上一局的数据污染
smc.resetScores();
smc.vmdata.mission_data.coin = Math.max(0, Math.floor(CardInitCoins));
smc.vmdata.mission_data.coin = Math.max(0, Math.floor(FightSet.INIT_COIN));
// 【评分系统 - 效率分】记录初始获得的金币收入
smc.vmdata.scores.gold_earned += smc.vmdata.mission_data.coin;
}
@@ -853,11 +856,6 @@ export class MissionComp extends CCComp {
// 怪物全灭检测:如果战斗阶段场上没有任何活着的怪物,且待刷新的怪物队列也为空,直接结束战斗进入下一波的准备阶段
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) {
let heroesAliveRatio = heroCount / 6.0; // 假设最大 6 个站位,或者直接基于存活数算比例
// 如果能获取当前已部署英雄数最好,这里简化处理,大于 4 个就算高存活
heroesAliveRatio = Math.min(1.0, heroCount / 4.0);
spawningEngine.updateAdaptive(heroesAliveRatio, this.clearTime);
if (this.currentWave >= 15) {
// 15 波通关
this.open_Victory(null, false);

View File

@@ -55,12 +55,12 @@ export class MissionHeroComp extends CCComp {
/** 硬编码的6个英雄占位点 */
public static readonly HERO_POSITIONS: Vec3[] = [
v3(-210, BoxSet.GAME_LINE + 90, 0), // index 0 (node_index 1): Top Front
v3(-160, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front
v3(-210, BoxSet.GAME_LINE - 90, 0), // index 2 (node_index 3): Bot Front
v3(-300, BoxSet.GAME_LINE + 90, 0), // index 3 (node_index 4): Top Back
v3(-300, BoxSet.GAME_LINE, 0), // index 4 (node_index 5): Mid Back
v3(-300, BoxSet.GAME_LINE - 90, 0), // index 5 (node_index 6): Bot Back
v3(-175, BoxSet.GAME_LINE + 100, 0), // index 0 (node_index 1): Top Front
v3(-170, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front
v3(-175, BoxSet.GAME_LINE - 100, 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
];
/** 英雄出生时的掉落高度(从空中落到地面的像素差) */

View File

@@ -3,27 +3,13 @@
* @description 怪物Monster波次刷新管理组件逻辑层
*
* 职责:
* 1. 管理每一波怪物的 **生成计划**:根据 WaveSlotConfig 生成怪物。
* 2. 处理特殊插队刷怪请求MonQueue优先于常规刷新
* 3. 自动推进波次:当前波所有怪物被清除后自动进入下一波。
* 1. 管理每一波怪物的生成计划:根据 RogueConfig 生成怪物。
* 2. 自动推进波次在准备阶段结束时PhasePrepareEnd统一刷出怪物
*
* 关键设计:
* - 突破 5 槽限制,怪物按刷怪顺序依次从 X=280 开始向右(每隔 50排布。
* - 6 条刷怪线:在三路 Y 轴范围内随机偏移,实现 6 路进军
* - resetSlotSpawnData(wave) 在每波开始时读取配置,分配并立即生成所有怪物
* - 去除跨波 HP 继承,上一波残留怪在波次结束/开始时销毁。
*
* 怪物属性计算公式:
* ap = floor((base_ap + stage × grow_ap) × SpawnPowerBias)
* hp = floor((base_hp + stage × grow_hp) × SpawnPowerBias)
* 其中 stage = currentWave - 1
*
* 依赖:
* - RogueConfig —— 怪物类型、成长值、波次配置
* - Monsterhero/Mon.ts—— 怪物 ECS 实体类
* - HeroInfoheroSet—— 怪物基础属性配置(与英雄共用配置)
* - HeroAttrsComp / MonMoveComp —— 怪物属性和移动组件
* - BoxSet.GAME_LINE —— 地面基准 Y 坐标
* - 采用 12 个硬编码的网格位置点 (MON_POSITIONS3行x4列)
* - 每次生成最多 12 个怪物,固定在位置点
* - 上一波残留怪在波次结束/开始时统一清理
*/
import { _decorator, v3, Vec3 } from "cc";
import { mLogger } from "../common/Logger";
@@ -31,21 +17,15 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { Monster } from "../hero/Mon";
import { HeroInfo, HType } from "../common/config/heroSet";
import { smc } from "../common/SingletonModuleComp";
import { GameEvent } from "../common/config/GameEvent";
import {BoxSet, FacSet } from "../common/config/GameSet";
import { spawningEngine, GeneratedMonster, AffixType, MonType, MonList, TestModeConfig } from "./RogueConfig";
import { BoxSet, FacSet } from "../common/config/GameSet";
import { spawningEngine, GeneratedMonster, TestModeConfig } from "./RogueConfig";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { MonMoveComp } from "../hero/MonMoveComp";
const { ccclass, property } = _decorator;
/**
* MissionMonCompComp —— 怪物波次刷新管理器
*
* 每波开始时根据 WaveSlotConfig 配置生成怪物,
* 战斗中监控数量,所有怪物消灭后自动推进到下一波。
*/
@ccclass('MissionMonCompComp')
@ecs.register('MissionMonComp', false)
export class MissionMonCompComp extends CCComp {
@@ -59,21 +39,21 @@ export class MissionMonCompComp extends CCComp {
/** 硬编码的 12 个怪物占位点 (3行4列) */
public static readonly MON_POSITIONS: Vec3[] = [
// 第 1 列 (X=60)
v3(60, BoxSet.GAME_LINE + 90, 0), // index 0: Top
v3(60, BoxSet.GAME_LINE, 0), // index 1: Mid
v3(60, BoxSet.GAME_LINE - 90, 0), // index 2: Bot
v3(0, BoxSet.GAME_LINE + 100, 0), // index 0: Top
v3(0, BoxSet.GAME_LINE, 0), // index 1: Mid
v3(0, BoxSet.GAME_LINE - 100, 0), // index 2: Bot
// 第 2 列 (X=140)
v3(140, BoxSet.GAME_LINE + 90, 0), // index 3: Top
v3(140, BoxSet.GAME_LINE, 0), // index 4: Mid
v3(140, BoxSet.GAME_LINE - 90, 0), // index 5: Bot
v3(90, BoxSet.GAME_LINE + 100, 0), // index 3: Top
v3(90, BoxSet.GAME_LINE, 0), // index 4: Mid
v3(90, BoxSet.GAME_LINE - 100, 0), // index 5: Bot
// 第 3 列 (X=220)
v3(220, BoxSet.GAME_LINE + 90, 0), // index 6: Top
v3(220, BoxSet.GAME_LINE, 0), // index 7: Mid
v3(220, BoxSet.GAME_LINE - 90, 0), // index 8: Bot
v3(180, BoxSet.GAME_LINE + 100, 0), // index 6: Top
v3(180, BoxSet.GAME_LINE, 0), // index 7: Mid
v3(180, BoxSet.GAME_LINE - 100, 0), // index 8: Bot
// 第 4 列 (X=300)
v3(300, BoxSet.GAME_LINE + 90, 0), // index 9: Top
v3(300, BoxSet.GAME_LINE, 0), // index 10: Mid
v3(300, BoxSet.GAME_LINE - 90, 0), // index 11: Bot
v3(270, BoxSet.GAME_LINE + 100, 0), // index 9: Top
v3(270, BoxSet.GAME_LINE, 0), // index 10: Mid
v3(270, BoxSet.GAME_LINE - 100, 0), // index 11: Bot
];
// ======================== 编辑器属性 ========================
@@ -81,83 +61,32 @@ export class MissionMonCompComp extends CCComp {
@property({ tooltip: "是否启用调试日志" })
private debugMode: boolean = false;
// ======================== 插队刷怪队列 ========================
/**
* 刷怪队列(优先于常规配置处理):
* 用于插队生成(如运营活动怪、技能召唤怪、剧情强制怪)。
*/
private MonQueue: Array<{
/** 怪物 UUID */
uuid: number,
/** 怪物等级 */
level: number,
/** 飞行层 */
flyLane: number,
}> = [];
// ======================== 运行时状态 ========================
/** 全局生成顺序计数器(用于渲染层级排序) */
private globalSpawnOrder: number = 0;
/** 插队刷怪处理计时器 */
private queueTimer: number = 0;
/** 当前波数 */
private currentWave: number = 0;
/** 当前波的目标怪物总数 */
private waveTargetCount: number = 0;
/** 当前波已生成的怪物数量 */
private waveSpawnedCount: number = 0;
/** 等待生成的怪物队列(由新肉鸽引擎提供) */
/** 等待生成的怪物队列 */
private pendingMonsters: GeneratedMonster[] = [];
// ======================== 生命周期 ========================
onLoad(){
this.on(GameEvent.FightReady,this.fight_ready,this)
this.on("SpawnSpecialMonster", this.onSpawnSpecialMonster, this);
onLoad() {
this.on(GameEvent.FightReady, this.fight_ready, this);
this.on("PhasePrepareEnd", this.onPhasePrepareEnd, this);
this.on("TimeUpAdvanceWave", this.onTimeUpAdvanceWave, this);
}
/**
* 帧更新:
* 1. 检查游戏是否运行中。
* 2. 处理插队刷怪队列。
* 3. 逐步从 pendingMonsters 队列中生成怪物(受 stop_spawn_mon 限制)。
*/
protected update(dt: number): void {
smc.vmdata.mission_data.pending_mon_num = this.pendingMonsters.length;
if(!smc.mission.play) return
if(smc.mission.pause) return
if(smc.mission.stop_mon_action) return;
if(!smc.mission.in_fight) return;
this.updateSpecialQueue(dt);
}
// ======================== 事件处理 ========================
/**
* 接收特殊刷怪事件并入队。
* @param event 事件名
* @param args { uuid: number, level: number, flyLane?: number }
*/
private onSpawnSpecialMonster(event: string, args: any) {
if (!args) return;
mLogger.log(this.debugMode, 'MissionMonComp', `[MissionMonComp] 收到特殊刷怪指令:`, args);
this.MonQueue.push({
uuid: args.uuid,
level: args.level,
flyLane: args.flyLane || 0
});
// 加速队列消费
this.queueTimer = 1.0;
}
start() {
}
start() {}
private setupWaveData(monsters: GeneratedMonster[]) {
this.pendingMonsters = monsters.slice(0, MissionMonCompComp.MAX_MONSTERS);
@@ -166,9 +95,7 @@ export class MissionMonCompComp extends CCComp {
let hasBoss = monsters.some(m => m.isBoss);
console.log(`[MissionMonComp] 波次 ${this.currentWave} 生成怪物总数: ${this.waveTargetCount}`);
const uuids = monsters.map(m => m.uuid);
console.log(`[MissionMonComp] 波次 ${this.currentWave} 怪物 UUID 列表:`, uuids);
mLogger.log(this.debugMode, 'MissionMonComp', `[MissionMonComp] 波次 ${this.currentWave} 生成怪物总数: ${this.waveTargetCount}`);
oops.message.dispatchEvent(GameEvent.NewWave, {
wave: this.currentWave,
@@ -180,81 +107,41 @@ export class MissionMonCompComp extends CCComp {
/**
* 战斗准备:重置所有运行时状态并开始第一波。
*/
fight_ready(){
smc.vmdata.mission_data.mon_num=0
smc.mission.stop_spawn_mon = false
this.globalSpawnOrder = 0
this.queueTimer = 0
this.currentWave = 1
this.waveTargetCount = 0
this.waveSpawnedCount = 0
this.MonQueue = []
this.pendingMonsters = []
fight_ready() {
smc.vmdata.mission_data.mon_num = 0;
smc.mission.stop_spawn_mon = false;
this.globalSpawnOrder = 0;
this.currentWave = 1;
this.waveTargetCount = 0;
this.waveSpawnedCount = 0;
this.pendingMonsters = [];
// 预生成第一波数据以获取数量和 Boss 信息
const monsters = spawningEngine.generateWave(this.currentWave);
this.setupWaveData(monsters);
// 如果处于测试模式,英雄也需要限制为只产出一个,这部分通知可以配合使用
if (TestModeConfig.enable) {
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] 测试模式已开启每波仅生成1只基准怪物");
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] 测试模式已开启");
}
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
}
// ======================== 插队刷怪 ========================
/**
* 处理插队刷怪队列(每 0.15 秒尝试消费一个):
* 1. 找到后从队列中移除并生成怪物。
*/
private updateSpecialQueue(dt: number) {
if (this.MonQueue.length <= 0) return;
this.queueTimer += dt;
if (this.queueTimer < 0.15) return;
const item = this.MonQueue.shift()!;
this.queueTimer = 0;
const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) ||
MonList[MonType.LongBoss].includes(item.uuid);
const spawnIndex = this.waveSpawnedCount++;
const targetPosIndex = spawnIndex % MissionMonCompComp.MAX_MONSTERS;
// 构造一个模拟的 GeneratedMonster 数据传递给 addMonsterAtGrid
const base = HeroInfo[item.uuid];
const monData: GeneratedMonster = {
uuid: item.uuid,
type: MonType.Melee, // 简化的兜底,真实逻辑依赖 heroSet 配置
hp: base ? base.hp : 100,
ap: base ? base.ap : 10,
affixes: [],
isBoss: isBoss,
spawnIndex: 0
};
this.addMonsterAtGrid(targetPosIndex, monData, item.level);
}
// ======================== 波次管理 ========================
/**
* 开始下一波:
* 1. 波数 +1 并更新全局数据。
* 2. 分发 NewWave 事件(实际的生成在 resetSlotSpawnData 中触发)。
* 开始下一波:波数 +1 并预生成数据
*/
private onTimeUpAdvanceWave() {
this.currentWave += 1;
smc.vmdata.mission_data.level = this.currentWave;
// 预生成新一波数据以获取数量和 Boss 信息
const monsters = spawningEngine.generateWave(this.currentWave);
this.setupWaveData(monsters);
}
private onPhasePrepareEnd() {
this.resetSlotSpawnData(this.currentWave);
this.resetSlotSpawnData();
// 准备结束阶段,立即刷出本波所有怪物
if (this.pendingMonsters.length > 0) {
@@ -262,11 +149,9 @@ export class MissionMonCompComp extends CCComp {
for (let i = 0; i < count; i++) {
const monData = this.pendingMonsters.shift()!;
const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MAX_MONSTERS;
console.log(`[MissionMonComp] [PhasePrepareEnd] 准备生成怪物 UUID=${monData.uuid}, 当前已生成数量=${this.waveSpawnedCount}`);
this.addMonsterAtGrid(targetPosIndex, monData);
this.addMonsterAtGrid(targetPosIndex, monData, this.currentWave);
this.waveSpawnedCount++;
}
// 生成完毕后清空 pendingMonsters
this.pendingMonsters = [];
}
}
@@ -274,14 +159,9 @@ export class MissionMonCompComp extends CCComp {
// ======================== 槽位管理 ========================
/**
* 重新分配本波所有怪物状态:
* 1. 清理上一波残留怪物。
* 2. pendingMonsters 已在 onTimeUpAdvanceWave / fight_ready 中准备好。
*
* @param wave 当前波数
* 清理上一波残留怪物,并重置生成计数
*/
private resetSlotSpawnData(wave: number = 1) {
// 1. 清理上一波残留怪物
private resetSlotSpawnData() {
ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => {
const attrs = e.get(HeroAttrsComp);
if (attrs && attrs.fac === FacSet.MON && !attrs.is_dead) {
@@ -289,18 +169,13 @@ export class MissionMonCompComp extends CCComp {
}
});
// 2. 重置排号索引
this.waveSpawnedCount = 0;
}
// ======================== 怪物生成 ========================
/**
* 在指定位置索引处生成一个怪物
*
* @param posIndex 位置索引 (0-11)
* @param monData 新引擎生成的怪物数据 (含 uuid, hp, ap, affixes 等)
* @param monLv 怪物等级 (仅对旧有的 level 参数做兼容,实际属性由 monData 决定)
* 在指定位置索引处生成一个怪物
*/
private addMonsterAtGrid(
posIndex: number,
@@ -310,51 +185,32 @@ export class MissionMonCompComp extends CCComp {
let mon = ecs.getEntity<Monster>(Monster);
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 spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
// 如果存在测试技能覆盖,则传递下去(修改 mon.load 逻辑或者通过预存)
// 为了避免侵入 Mon.ts 的原有逻辑,我们先预存
(mon as any)._testSkills = monData.testSkills;
if (monData.testSkills) {
(mon as any)._testSkills = monData.testSkills;
}
mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, posIndex);
// 设置渲染排序
const move = mon.get(MonMoveComp);
if (move) {
move.spawnOrder = this.globalSpawnOrder;
}
// 应用新引擎计算好的最终属性和词缀
// 应用新引擎计算好的最终属性
const model = mon.get(HeroAttrsComp);
if (!model) return;
model.ap = monData.ap;
model.hp_max = monData.hp;
model.hp = model.hp_max;
// 将词缀记录到属性组件上,供战斗层使用
(model as any).affixes = monData.affixes || [];
// 解析特定的抗性词缀
if (monData.affixes) {
if (monData.affixes.includes(AffixType.CritRes)) {
model.critical_res = 50;
}
if (monData.affixes.includes(AffixType.FreezeRes)) {
model.freeze_res = 50;
}
if (monData.affixes.includes(AffixType.KnockbackRes)) {
model.knockback_res = 50;
}
if (model) {
model.ap = monData.ap;
model.hp_max = monData.hp;
model.hp = model.hp_max;
}
}
/** ECS 组件移除时触发(当前不销毁节点) */
reset() {
// this.node.destroy();
}
/** ECS 组件移除时触发 */
reset() {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { CardConfig, CardType, CKind, CardPoolList } from "../common/config/CardSet";
import { CardBgComp } from "./CardBgComp";
import { SkillSet } from "../common/config/SkillSet";
import { FieldSkillSet, SkillSet } from "../common/config/SkillSet";
import { GameEvent } from "../common/config/GameEvent";
import { oops } from "db://oops-framework/core/Oops";
import { smc } from "../common/SingletonModuleComp";
@@ -96,7 +96,7 @@ export class SCardComp extends CCComp {
const success = MissionEconomy.spendCoin(cardCost);
if (!success) {
oops.gui.toast(`金币不足,需要${cardCost}`);
oops.message.dispatchEvent(GameEvent.ShowSmallTip, "buy_coin");
this.playReboundAnim();
return null;
}
@@ -258,7 +258,22 @@ export class SCardComp extends CCComp {
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
this.setLabel(this.name_node, `${spSuffix}${skillCard?.name || skill?.name || ""}${spSuffix}`);
if (this.info_node) this.info_node.active = true;
if (this.info_node) {
this.info_node.active = true;
// 驻场技能卡描述取 FieldSkillSet其他卡牌取卡牌/技能配置
let desc = "";
if (this.cardData.field && this.cardData.field.length > 0) {
desc = FieldSkillSet[this.cardData.field[0]]?.info || "";
}
if (!desc) {
desc = skillCard?.info || skill?.info || this.cardData?.info || "";
}
// 与 HlistComp 保持一致:优先查找名为 "info" 的子节点上的 Label
const infoLabel = this.info_node.getChildByName("info")?.getComponent(Label)
|| this.info_node.getComponent(Label)
|| this.info_node.getComponentInChildren(Label);
if (infoLabel) infoLabel.string = desc;
}
if (this.cost_node) {
this.cost_node.active = true;
@@ -272,7 +287,14 @@ export class SCardComp extends CCComp {
if (iconNode) {
iconNode.setScale(new Vec3(1, 1, 1));
this.clearIconAnimation(iconNode);
const iconId = skill?.icon || `${s_uuid}`;
// 驻场技能卡(skill=undefined 但有 field)使用 FieldSkillSet 中的图标
let iconId: string;
if (!this.cardData.skill && this.cardData.field && this.cardData.field.length > 0) {
const fieldUuid = this.cardData.field[0];
iconId = FieldSkillSet[fieldUuid]?.icon || `${fieldUuid}`;
} else {
iconId = skill?.icon || `${s_uuid}`;
}
this.updateIcon(iconNode, iconId);
}
}

View File

@@ -4,23 +4,27 @@
*
* 职责:
* 1. 表示一张已使用的技能卡在战场上的 **可视化实体**。
* 2. 管理技能的 **触发逻辑**:即时触发 vs 定时触发(战斗中按间隔触发)。
* 2. 按 trigger_type 类型化分发触发逻辑(即时 / 定时 / 驻场 / 事件型)。
* 3. 显示技能图标和剩余触发次数。
* 4. 触发结束后自动销毁。
*
* 关键设计
* - is_instant=true即时技能init 时立即触发一次,播放后延迟销毁。
* - is_instant=false持续技能战斗中每隔 trigger_interval 秒触发一次,
* 共触发 trigger_times 次后销毁。
* - 新一波NewWave时如果持续技能的次数已用完则销毁。
* - 销毁时通过 GameEvent.RemoveSkillBox 通知 MissSkillsComp 回收槽位。
* 触发类型CardTriggerType
* - Instant (1)init 时立即触发一次(按 t_times 控制次数,跨波次 NewWave 时再次触发)
* - Interval (2):监听 FightStart → update 帧驱动按 t_inv 间隔重复触发(按 t_times 控制每波次数)
* - Field (3):被动生效,不主动施法(实际由 FieldSkillSet 处理)
* - FightStart (4):监听 FightStart 事件,按 trigger_limit 全局累计上限
* - FightEnd (5):监听 FightEnd 事件(每波结束派发),按 trigger_limit 全局累计上限
* - HeroDead (6):监听 HeroDead 事件(仅英雄阵营派发,怪物死亡不触发)
* - HeroCall (7):监听 MasterCalled主角/技能召唤)+ ReviveSuccess复活
*
* 触发技能的方式
* - 通过 GameEvent.TriggerSkill 事件,将技能 UUID、卡牌等级、
* 触发位置等信息分发给技能系统。
* 关键设计
* - 事件型4-7统一走 onEventTrigger 入口,仅作触发信号,不读取 payload
* - 触发上限Instant/Interval 按 t_times每波内事件型按 trigger_limit全局
* - 跨波次keep_waves 控制存活;事件型 trigger_count 不随波次重置
* - 销毁时通过 GameEvent.RemoveSkillBox 通知 MissSkillsComp 回收槽位
*
* 依赖:
* - CardPoolListCardSet—— 查询技能卡的触发配置t_times / t_inv / is_inst
* - CardPoolList / CardTriggerTypeCardSet—— 查询技能卡的触发配置
* - SkillSet —— 技能静态配置icon 字段)
* - GameEvent —— 各类游戏事件
* - smc.mission —— 游戏运行状态
@@ -29,8 +33,8 @@ import { mLogger } from "../common/Logger";
import { _decorator, Node, Prefab, Sprite, Label, Vec3, resources, SpriteAtlas, tween, v3, Tween, NodeEventType } 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 { CardPoolList } from "../common/config/CardSet";
import { SkillSet, SkillOverrides } from "../common/config/SkillSet";
import { CardPoolList, CardTriggerType } from "../common/config/CardSet";
import { SkillSet, SkillOverrides, FieldSkillSet } from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { smc } from "../common/SingletonModuleComp";
@@ -53,6 +57,10 @@ export class SkillBoxComp extends CCComp {
@property({ type: Node })
private icon_node: Node = null;
/** 技能图标节点:下面有 green blue purple red yellow 5个不同颜色节点*/
@property({ type: Node })
private bg_node: Node = null;
/** 剩余次数标签 */
@property(Label)
private info_label: Label = null;
@@ -78,6 +86,17 @@ export class SkillBoxComp extends CCComp {
private overrides?: SkillOverrides;
/** 驻场技能 UUID 列表 */
public field: number[] = [];
/** 卡牌自定义图标ID优先级最高未定义则按 trigger_type 自动取) */
private card_icon?: string;
// ======================== 触发类型化扩展 ========================
/** 触发类型(默认即时,保持向后兼容) */
private trigger_type: CardTriggerType = CardTriggerType.Instant;
/** 事件型触发的全局次数上限Infinity 表示无上限) */
private trigger_limit: number = Infinity;
/** 事件型已触发次数 */
private trigger_count: number = 0;
// ======================== 运行时状态 ========================
@@ -92,9 +111,13 @@ export class SkillBoxComp extends CCComp {
// ======================== 生命周期 ========================
/** 注册战斗开始、任务结束、新一波等事件 */
/**
* 注册全局事件:
* - MissionEnd所有类型都需要监听任务结束时强制销毁
* - NewWave处理 keep_waves 跨波次逻辑(所有类型统一)
* - 其它触发事件由 registerTrigger 按 trigger_type 动态注册
*/
onLoad() {
oops.message.on(GameEvent.FightStart, this.onFightStart, this);
oops.message.on(GameEvent.MissionEnd, this.onMissionEnd, this);
this.node.on(GameEvent.NewWave, this.onNewWave, this);
oops.message.on(GameEvent.NewWave, this.onNewWaveGlobal, this);
@@ -104,8 +127,15 @@ export class SkillBoxComp extends CCComp {
/** 销毁时移除所有事件监听并通知槽位管理器回收 */
onDestroy() {
super.onDestroy();
oops.message.off(GameEvent.FightStart, this.onFightStart, this);
// 统一 off 所有可能订阅的事件(即使未订阅也无副作用)
// 注意FightStart 可能由两种回调订阅Interval→onFightStart / FightStart触发型→onEventTrigger都需要 off
oops.message.off(GameEvent.MissionEnd, this.onMissionEnd, this);
oops.message.off(GameEvent.FightStart, this.onFightStart, this);
oops.message.off(GameEvent.FightStart, this.onEventTrigger, this);
oops.message.off(GameEvent.FightEnd, this.onEventTrigger, this);
oops.message.off(GameEvent.HeroDead, this.onEventTrigger, this);
oops.message.off(GameEvent.MasterCalled, this.onEventTrigger, this);
oops.message.off(GameEvent.ReviveSuccess, this.onEventTrigger, this);
if (this.node && this.node.isValid) {
this.node.off(GameEvent.NewWave, this.onNewWave, this);
this.node.off(NodeEventType.TOUCH_END, this.onNodeClicked, this);
@@ -128,9 +158,9 @@ export class SkillBoxComp extends CCComp {
/**
* 初始化技能卡效果:
* 1. 从 CardPoolList 查询技能卡的触发配置。
* 1. 从 CardPoolList 查询技能卡的触发配置(含 trigger_type
* 2. 更新 UI 显示(图标 + 次数)。
* 3. 即时技能立即触发一次;若次数已满则延迟销毁
* 3. 按 trigger_type 注册对应事件监听并执行首次触发
*
* @param uuid 卡牌 UUID
* @param card_lv 技能卡等级
@@ -148,58 +178,208 @@ export class SkillBoxComp extends CCComp {
this.keep_waves = config.keep_waves ?? 0;
this.overrides = config.overrides;
this.field = config.field || [];
this.card_icon = config.icon; // 保存卡牌自定义图标(优先级最高)
// 读取触发类型与上限(兜底默认值,避免 undefined
this.trigger_type = config.trigger_type ?? CardTriggerType.Instant;
this.trigger_limit = config.trigger_limit ?? Infinity;
} else {
this.s_uuid = uuid;
}
this.current_trigger_times = 0;
this.trigger_count = 0;
this.timer = 0;
this.initialized = true;
this.updateUI();
if (this.is_instant) {
// 即时技能:立即触发
this.triggerSkill();
this.current_trigger_times++;
if (this.keep_waves === 0 && this.current_trigger_times >= this.trigger_times) {
// 次数已满且不跨波次维持 → 延迟 1 秒后销毁(保留短暂视觉反馈)
this.scheduleOnce(() => {
if (this.ent) {
(this.ent as ecs.Entity).destroy();
} else if (this.node && this.node.isValid) {
this.node.destroy();
}
}, 1.0);
}
// 按 trigger_type 注册事件监听 + 执行首次触发
this.registerTrigger();
}
/**
* 按 trigger_type 注册对应事件监听:
* - Instant: init 时立即触发一次(保持旧行为)
* - Interval: 监听 FightStart进入战斗后由 update 帧驱动计时
* - Field: 不主动施法(实际生效由 FieldSkillSet 处理)
* - FightStart: 监听 FightStart 事件
* - FightEnd: 监听 FightEnd 事件
* - HeroDead: 监听 HeroDead 事件(已在派发处做阵营过滤)
* - HeroCall: 监听 MasterCalled主角/技能召唤)+ ReviveSuccess复活
*
* 注意MasterCalled 各派发点 payload 不一致onEventTrigger 仅作触发信号使用。
*/
private registerTrigger(): void {
switch (this.trigger_type) {
case CardTriggerType.Instant:
// 即时技能:立即触发一次
this.onEventTrigger();
break;
case CardTriggerType.Interval:
// 定时循环:监听 FightStart 进入战斗后启动计时
oops.message.on(GameEvent.FightStart, this.onFightStart, this);
break;
case CardTriggerType.Field:
// 驻场光环:不主动施法,由 FieldSkillSet 处理
break;
case CardTriggerType.FightStart:
oops.message.on(GameEvent.FightStart, this.onEventTrigger, this);
break;
case CardTriggerType.FightEnd:
oops.message.on(GameEvent.FightEnd, this.onEventTrigger, this);
break;
case CardTriggerType.HeroDead:
oops.message.on(GameEvent.HeroDead, this.onEventTrigger, this);
break;
case CardTriggerType.HeroCall:
// 同时监听召唤和复活两类英雄上场事件
oops.message.on(GameEvent.MasterCalled, this.onEventTrigger, this);
oops.message.on(GameEvent.ReviveSuccess, this.onEventTrigger, this);
break;
default:
mLogger.warn(true, 'SkillBoxComp', `[registerTrigger] unknown trigger_type: ${this.trigger_type}, fallback to Instant`);
this.onEventTrigger();
break;
}
}
/**
* 事件型触发的统一入口:
* - Instant 类型:按 trigger_times 上限判定,复用 current_trigger_times 跟踪
* (保持与原 is_instant 行为一致,且 NewWave 中也用 current_trigger_times
* - 事件型FightStart/FightEnd/HeroDead/HeroCall按 trigger_limit 上限判定,使用 trigger_count 跟踪
*
* 注意:本方法不读取事件 payload仅作触发信号使用避免 MasterCalled 不同 payload 字段引发的兼容问题)。
*/
private onEventTrigger(): void {
if (!this.initialized) return;
if (this.trigger_type === CardTriggerType.Instant) {
// 即时触发:上限由 trigger_times 控制(保持旧行为)
if (this.current_trigger_times >= this.trigger_times) {
this.destroySelf();
return;
}
this.triggerSkill();
this.current_trigger_times++;
this.updateUI();
// 单次触发 + 不跨波次维持 → 延迟销毁(保留短暂视觉反馈)
if (this.keep_waves === 0 && this.current_trigger_times >= this.trigger_times) {
this.scheduleOnce(() => this.destroySelf(), 1.0);
}
return;
}
// 事件型:上限由 trigger_limit 控制(全局累计,跨波次不重置)
if (this.trigger_count >= this.trigger_limit) {
this.destroySelf();
return;
}
this.triggerSkill();
this.trigger_count++;
this.updateUI();
}
/**
* 统一的节点销毁封装:
* 优先通过 ECS 实体销毁;否则直接销毁节点。
*/
private destroySelf(): void {
if (this.ent) {
(this.ent as ecs.Entity).destroy();
} else if (this.node && this.node.isValid) {
this.node.destroy();
}
}
/**
* 根据技能等级切换 bg_node 下的颜色子节点显示。
* 等级与颜色对应关系(技能卡 wave 档位):
* card_lv 1 (wave 1) → green
* card_lv 2 (wave 5) → blue
* card_lv 3 (wave 8) → purple
*/
private updateBgNode() {
if (!this.bg_node) return;
const lvToColor: Record<number, string> = {
1: "green",
2: "blue",
3: "purple",
};
const targetColor = lvToColor[this.card_lv];
this.bg_node.children.forEach(child => {
child.active = (child.name === targetColor);
});
}
/**
* 更新 UI
* - 图标:从 uicons 图集获取。
* - 剩余次数:持续技能显示剩余数字,即时技能不显示。
* - 背景:根据技能等级切换对应颜色子节点显示
* - 图标:从 uicons 图集获取
* - 剩余次数标签:
* * Interval / 事件型:显示剩余次数(按各自上限计算)
* * Instant / Field不显示
* - CD 遮罩:仅 Interval 类型展示冷却进度
*/
updateUI() {
// 按技能等级切换背景颜色节点(与 getLvColor 等级配色一致)
this.updateBgNode();
// 加载技能图标
if (this.icon_node) {
const iconId = SkillSet[this.s_uuid]?.icon || `${this.s_uuid}`;
if (smc.uiconsAtlas) {
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
if (frame && this.icon_node && this.icon_node.isValid) {
let sprite = this.icon_node.getComponent(Sprite) || this.icon_node.addComponent(Sprite);
sprite.spriteFrame = frame;
// 优先使用卡牌自定义 icon未定义则按 trigger_type 自动取
let iconId: string | undefined = this.card_icon;
if (!iconId) {
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];
iconId = fieldUuid ? FieldSkillSet[fieldUuid]?.icon : undefined;
}
}
if (this.icon_node && this.icon_node.isValid) {
let sprite = this.icon_node.getComponent(Sprite) || this.icon_node.addComponent(Sprite);
if (smc.uiconsAtlas && iconId) {
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
sprite.spriteFrame = frame || null; // 取不到时清空,避免残留
} else {
sprite.spriteFrame = null;
}
}
}
// 更新剩余次数标签
// 是否需要展示剩余次数
const showRemainCount =
this.trigger_type === CardTriggerType.Interval ||
this.trigger_type === CardTriggerType.FightStart ||
this.trigger_type === CardTriggerType.FightEnd ||
this.trigger_type === CardTriggerType.HeroDead ||
this.trigger_type === CardTriggerType.HeroCall;
if (this.info_label) {
if (!this.is_instant) {
if (this.trigger_interval <= 0 && this.field && this.field.length > 0) {
this.info_label.string = ""; // 纯驻场技能不显示剩余次数
if (showRemainCount) {
// 事件型按 trigger_limitInterval 按 t_times
const isEvent =
this.trigger_type === CardTriggerType.FightStart ||
this.trigger_type === CardTriggerType.FightEnd ||
this.trigger_type === CardTriggerType.HeroDead ||
this.trigger_type === CardTriggerType.HeroCall;
const used = isEvent ? this.trigger_count : this.current_trigger_times;
const total = isEvent ? this.trigger_limit : this.trigger_times;
if (isEvent && !isFinite(total)) {
// 无上限:显示已触发次数
this.info_label.string = `${used}`;
} else {
const remain = Math.max(0, this.trigger_times - this.current_trigger_times);
const remain = Math.max(0, Math.floor(total) - used);
this.info_label.string = `${remain}`;
}
} else {
@@ -207,14 +387,14 @@ export class SkillBoxComp extends CCComp {
}
}
// 初始化或重置 CD 遮罩表现
// 初始化或重置 CD 遮罩表现(仅 Interval 类型有冷却进度)
if (this.cd_mask && this.cd_mask.isValid) {
let sprite = this.cd_mask.getComponent(Sprite);
if (sprite) {
if (this.is_instant || this.trigger_interval <= 0) {
sprite.fillRange = 0; // 无需冷却(包括驻场光环卡),直接归 0
} else {
if (this.trigger_type === CardTriggerType.Interval && this.trigger_interval > 0) {
sprite.fillRange = Math.max(0, 1 - (this.timer / this.trigger_interval));
} else {
sprite.fillRange = 0; // 非冷却类型直接归 0
}
}
}
@@ -222,14 +402,17 @@ export class SkillBoxComp extends CCComp {
// ======================== 战斗状态事件 ========================
/** 战斗开始:标记进入战斗状态,持续技能开始计时 */
/**
* 战斗开始回调:
* - 仅 Interval 类型在 registerTrigger 中订阅此事件
* - 标记进入战斗状态,启动计时器(实际触发由 update 帧驱动)
*
* 注意FightStart 触发型CardTriggerType.FightStart的事件回调是 onEventTrigger不是本方法。
*/
private onFightStart() {
if (!this.initialized) return;
this.in_combat = true;
if (!this.is_instant) {
this.timer = 0; // 重置计时器
}
this.timer = 0; // 重置计时器
}
/** 节点级新一波事件处理 */
@@ -245,76 +428,79 @@ export class SkillBoxComp extends CCComp {
/**
* 新一波:退出战斗状态。
* 处理维持波次逻辑:递减剩余波次,或者重置触发次数。
*
* 各类型在新一波的行为:
* - Instant/Interval/FightStart/FightEnd按 keep_waves 决定维持/销毁,并在新一波开始时重置本地计数
* - Field被动生效跟随 keep_waves 决定存活
* - HeroDead/HeroCall跨波次触发的事件型trigger_count全局不重置仅 keep_waves 控制存活
*/
private handleNewWave() {
if (!this.initialized) return;
this.in_combat = false;
// 事件型触发HeroDead / HeroCalltrigger_count 全局累计,不随波次重置
const isGlobalEventType =
this.trigger_type === CardTriggerType.HeroDead ||
this.trigger_type === CardTriggerType.HeroCall;
if (this.keep_waves !== 0) {
if (this.keep_waves > 0) {
this.keep_waves--;
if (this.keep_waves <= 0) {
if (this.ent) {
(this.ent as ecs.Entity).destroy();
} else if (this.node && this.node.isValid) {
this.node.destroy();
}
this.destroySelf();
return;
}
}
// 能够跨波次维持重置触发次数和计时器,以便新一波继续触发
this.current_trigger_times = 0;
// 跨波次维持重置本地计数与计时器(事件型 trigger_count 不重置)
if (!isGlobalEventType) {
this.current_trigger_times = 0;
this.trigger_count = 0;
}
this.timer = 0;
// 即时技能在新一波开始立即触发一次
if (this.is_instant) {
// 即时/事件型触发一次保持旧行为Instant 在新一波开始立即触发一次
if (this.trigger_type === CardTriggerType.Instant) {
this.triggerSkill();
this.current_trigger_times++;
}
this.updateUI();
} else {
// 默认逻辑:不跨波次维持
if (!this.is_instant) {
if (this.current_trigger_times >= this.trigger_times) {
if (this.ent) {
(this.ent as ecs.Entity).destroy();
} else if (this.node && this.node.isValid) {
this.node.destroy();
}
}
// 不跨波次维持:达到上限即销毁
// - Interval / Instant按 t_times 判定
// - 事件型:按 trigger_limit 判定
const reachedLimit = isGlobalEventType
? this.trigger_count >= this.trigger_limit
: this.current_trigger_times >= this.trigger_times;
if (reachedLimit) {
this.destroySelf();
}
}
}
/** 任务结束:强制销毁 */
private onMissionEnd() {
if (this.ent) {
(this.ent as ecs.Entity).destroy();
} else if (this.node && this.node.isValid) {
this.node.destroy();
}
this.destroySelf();
}
// ======================== 帧更新 ========================
/**
* 每帧更新(仅对持续技能生效)
* - 累加计时器,达到 trigger_interval 时触发一次技能。
* - 触发后重置计时器并更新 UI。
* - 总次数用完后延迟销毁。
* 每帧更新:
* - 仅 Interval 类型走帧驱动计时逻辑(其它类型提前 return
* - 累加计时器,达到 trigger_interval 时触发一次技能
* - 触发后重置计时器并更新 UI
* - 总次数用完后延迟销毁
*/
update(dt: number) {
if (!this.initialized || !this.in_combat || this.is_instant) return;
// 收窄:仅 Interval 类型走帧驱动
if (this.trigger_type !== CardTriggerType.Interval) return;
if (!this.initialized || !this.in_combat) return;
if (!smc.mission.play || smc.mission.pause) return;
// 如果是纯驻场光环技能且无触发间隔,则不执行定期触发逻辑
if (this.trigger_interval <= 0 && this.field && this.field.length > 0) {
return;
}
if (this.current_trigger_times < this.trigger_times) {
this.timer += dt;
// 更新 CD 遮罩 (fillRange 从 1 降到 0)
if (this.cd_mask && this.cd_mask.isValid && this.trigger_interval > 0) {
let sprite = this.cd_mask.getComponent(Sprite);
@@ -331,13 +517,7 @@ export class SkillBoxComp extends CCComp {
// 次数用完且不跨波次维持 → 延迟销毁
if (this.keep_waves === 0 && this.current_trigger_times >= this.trigger_times) {
this.scheduleOnce(() => {
if (this.ent) {
(this.ent as ecs.Entity).destroy();
} else if (this.node && this.node.isValid) {
this.node.destroy();
}
}, 0.5);
this.scheduleOnce(() => this.destroySelf(), 0.5);
}
}
}

View File

@@ -15,7 +15,7 @@ import { _decorator, Label, Sprite, SpriteFrame } 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 { CardConfig } from "../common/config/CardSet";
import { SkillSet } from "../common/config/SkillSet";
import { FieldSkillSet, SkillSet } from "../common/config/SkillSet";
import { CardBgComp } from "./CardBgComp";
import { smc } from "../common/SingletonModuleComp";
import { oops } from "db://oops-framework/core/Oops";
@@ -45,38 +45,35 @@ export class TalentItemComp extends CCComp {
*/
public updateItem(config: CardConfig): void {
if (!config) return;
if (this.lbl_name) this.lbl_name.string = config.name ?? "";
if (this.lbl_info) this.lbl_info.string = config.info ?? "";
// 根据 wave 映射背景颜色
// 1=绿色(poolLv=1) 5=蓝色(poolLv=2) 10=紫色(poolLv=3) 15=黄色(poolLv=4) 20=红色(poolLv=5)
// 直接复用 CardSet 中已映射好的 pool_lv保证与技能卡牌背景一致
// CardSet 通过 waveToPoolLv[wave] 由 SKILL_CARD_WAVES 索引推导wave 1→1, 5→2, 8→3
if (this.bg) {
let poolLv = 1;
const wave = config.wave || 1;
if (wave >= 20) poolLv = 5;
else if (wave >= 15) poolLv = 4;
else if (wave >= 10) poolLv = 3;
else if (wave >= 5) poolLv = 2;
else poolLv = 1;
this.bg.apply(poolLv);
this.bg.apply(config.pool_lv || 1);
}
// 设置图标
if (this.icon && config.skill) {
const skillData = SkillSet[config.skill];
if (skillData && skillData.icon) {
// 设置图标:驻场卡(skill=undefined 但有 field)走 FieldSkillSet否则走 SkillSet
// 与 SCardComp 保持一致,避免驻场卡无 icon 显示
if (this.icon) {
let iconId: string | undefined;
if (!config.skill && config.field && config.field.length > 0) {
// 驻场卡:用 FieldSkillSet[field[0]].icon
const fieldUuid = config.field[0];
iconId = FieldSkillSet[fieldUuid]?.icon || `${fieldUuid}`;
} else if (config.skill) {
// 技能卡:用 SkillSet[skill].icon
iconId = SkillSet[config.skill]?.icon;
}
if (iconId) {
if (smc.uiconsAtlas) {
const frame = smc.uiconsAtlas.getSpriteFrame(skillData.icon);
if (frame) {
this.icon.spriteFrame = frame;
}
const frame = smc.uiconsAtlas.getSpriteFrame(iconId);
if (frame) this.icon.spriteFrame = frame;
} else {
const sf = oops.res.get("game/heros/cards/" + skillData.icon, SpriteFrame) as SpriteFrame;
if (sf) {
this.icon.spriteFrame = sf;
}
const sf = oops.res.get("game/heros/cards/" + iconId, SpriteFrame) as SpriteFrame;
if (sf) this.icon.spriteFrame = sf;
}
}
}

View File

@@ -69,6 +69,9 @@ export class SMoveDataComp extends ecs.Comp {
bezierMidHeight: number = 200;
bezierArc: number = 1;
/** 是否逐渐加速 (ease-in) */
isAccelerate: boolean = false;
/** 是否自动销毁(到达目标后) */
autoDestroy: boolean = true;
@@ -80,6 +83,7 @@ export class SMoveDataComp extends ecs.Comp {
this.bezierStartHeight = 30;
this.bezierMidHeight = 200;
this.bezierArc = 1;
this.isAccelerate = false;
this.speed = 500;
this.progress = 0;
this.scale = 1;
@@ -181,14 +185,20 @@ export class SMoveDataComp extends ecs.Comp {
* 根据移动类型计算当前位置
*/
private calculateCurrentPosition() {
// 如果开启了逐渐加速,混合线性与二次方曲线 (如 0.7 * t^2 + 0.3 * t)
// 这样起步拥有 30% 的基础速度,不会显得完全静止,随后逐渐加速
const t = this.isAccelerate ?
(this.progress * this.progress * 0.7 + this.progress * 0.3) :
this.progress;
switch (this.runType) {
case RType.linear:
// 直线运动
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, this.progress);
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, t);
break;
case RType.bezier:
this.calculateBezierPosition(this.progress);
this.calculateBezierPosition(t);
break;
case RType.fixed:
@@ -198,7 +208,7 @@ export class SMoveDataComp extends ecs.Comp {
break;
default:
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, this.progress);
Vec3.lerp(this.currentPos, this.startPos, this.targetPos, t);
break;
}
}

View File

@@ -40,10 +40,13 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return;
}
// 根据配置设置移动速度
// 根据配置设置移动速度与加速度
if (skillConfig.speed > 0) {
moveComp.speed = skillConfig.speed;
}
if (skillConfig.is_accel) {
moveComp.isAccelerate = true;
}
// 根据runType设置初始位置
this.initializePosition(moveComp, skillView);
@@ -190,10 +193,12 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (moveComp.progress < 1) {
// 计算下一帧的位置来确定方向
const nextProgress = Math.min(moveComp.progress + 0.01, 1);
const t = moveComp.isAccelerate ?
(nextProgress * nextProgress * 0.7 + nextProgress * 0.3) :
nextProgress;
const nextPos = v3(0, 0, 0);
// 计算下一个位置
const t = nextProgress;
const oneMinusT = 1 - t;
const oneMinusTSquared = oneMinusT * oneMinusT;
const tSquared = t * t;
@@ -245,6 +250,7 @@ export class SMoveHelper {
if (skillConfig) {
moveComp.runType = skillConfig.RType || RType.linear;
moveComp.speed = skillConfig.speed || 500;
if (skillConfig.is_accel) moveComp.isAccelerate = true;
}
}

View File

@@ -60,7 +60,7 @@ export class Tooltip extends ecs.Entity {
this.remove(TooltipCom);
super.destroy();
}
static load(pos: Vec3 = Vec3.ZERO,type:number=1,vaule:string="",s_uuid:number=1001,parent:any=null,cd:number=1,fac:number=FacSet.MON) {
static load(pos: Vec3 = Vec3.ZERO,type:number=1,vaule:string="",s_uuid:number=1001,parent:any=null,cd:number=1,fac:number=FacSet.MON,triggerType:string="") {
let node: Node;
if (Tooltip.pool.size() > 0) {
node = Tooltip.pool.get()!;
@@ -75,7 +75,7 @@ export class Tooltip extends ecs.Entity {
node.active = true;
var sv = node.getComponent(TooltipCom)!;
sv.init(type, vaule, s_uuid, fac);
sv.init(type, vaule, s_uuid, fac, triggerType);
// this.add(sv); // 不要添加到单例实体上,否则会覆盖或导致单例被销毁
}

View File

@@ -1,8 +1,9 @@
import { _decorator, Collider2D, Contact2DType, v3, IPhysics2DContact, Vec3, tween, Label, resources, SpriteFrame, Sprite, UIOpacity, Color, math, Tween } from "cc";
import { _decorator, Collider2D, Contact2DType, v3, IPhysics2DContact, Vec3, tween, Label, resources, SpriteFrame, Sprite, UIOpacity, Color, math, Tween,Node } 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 { SkillSet } from "../common/config/SkillSet";
import { FacSet, TooltipTypes } from "../common/config/GameSet";
import { SkillTriggerType } from "../common/config/heroSet";
import { Tooltip } from "./Tooltip";
const { ccclass, property } = _decorator;
@@ -11,11 +12,26 @@ const { ccclass, property } = _decorator;
@ccclass('TooltipCom')
@ecs.register('TooltipView', false)
export class TooltipCom extends CCComp {
@property(Node)
atking_bg: Node = null;
@property(Node)
atked_bg: Node = null;
@property(Node)
fstart_bg: Node = null;
@property(Node)
fend_bg: Node = null;
@property(Node)
dead_bg: Node = null;
@property(Node)
revive_bg: Node = null;
stype: number = 1; // 1:减少生命值2增加生命值3技能图标
value: string = "";
s_uuid: number = 1001;
fac: number = FacSet.MON;
/** 当前技能喊话对应的触发类型(空字符串表示普通主动技能) */
triggerType: string = "";
// 动画参数配置
private readonly popDuration = 0.15;
private readonly driftDuration = 0.5;
@@ -29,11 +45,12 @@ export class TooltipCom extends CCComp {
}
/** 初始化并播放动画 */
init(type: number, value: string, uuid: number, fac: number = FacSet.MON) {
init(type: number, value: string, uuid: number, fac: number = FacSet.MON, triggerType: string = "") {
this.stype = type;
this.value = value;
this.s_uuid = uuid;
this.fac = fac;
this.triggerType = triggerType;
// 初始化或获取 UIOpacity 组件
this._uiOpacity = this.node.getComponent(UIOpacity);
@@ -106,6 +123,8 @@ export class TooltipCom extends CCComp {
this.setupLabel("skill", "name", skillName+this.value);
// this.node.setPosition(v3(this.node.position.x, currentY));
this.node.setSiblingIndex(topSiblingIndex);
// 根据触发类型激活对应的背景标识(追击/反击/起手/生息/亡语/复活)
this.setupTriggerBg(this.triggerType);
break;
case TooltipTypes.uskill:
this.setupLabel("uskill", "name", this.value);
@@ -146,6 +165,34 @@ export class TooltipCom extends CCComp {
}
}
/**
* 根据技能触发类型激活对应的背景标识节点
* 仅 TooltipTypes.skill 走此分支,其他飘字类型不受影响
* 对象池复用场景下先统一关闭所有 _bg避免上一次的状态残留
*/
private setupTriggerBg(triggerType: string) {
// 先关闭所有触发类型背景,防止节点池复用时残留
this.atking_bg && (this.atking_bg.active = false);
this.atked_bg && (this.atked_bg.active = false);
this.fstart_bg && (this.fstart_bg.active = false);
this.fend_bg && (this.fend_bg.active = false);
this.dead_bg && (this.dead_bg.active = false);
this.revive_bg && (this.revive_bg.active = false);
if (!triggerType) return;
const bgMap: Record<string, Node | null> = {
[SkillTriggerType.Atking]: this.atking_bg,
[SkillTriggerType.Atked]: this.atked_bg,
[SkillTriggerType.FStart]: this.fstart_bg,
[SkillTriggerType.FEnd]: this.fend_bg,
[SkillTriggerType.Dead]: this.dead_bg,
[SkillTriggerType.Revive]: this.revive_bg,
};
const bg = bgMap[triggerType];
if (bg) bg.active = true;
}
playAnimation(scaleMax: number, isCrit: boolean, isHeal: boolean, sx: number = 1) {
// 随机 X 轴偏移 (防止重叠)
const randomX = (Math.random() - 0.5) * 60;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
# 技能卡触发机制重构方案(已废弃)
> ⚠️ **本文档已废弃**,被以下执行计划取代:
> [`2026-06-19-card-skill-trigger-type-refactor-plan.md`](./2026-06-19-card-skill-trigger-type-refactor-plan.md)
>
> 废弃原因:本草案存在 3 处关键错误,已在新计划中修正:
> 1. 缺少 `Instant` 类型,导致现有即时卡牌无法归类
> 2. `BattleEnd` 错误映射到 `MissionEnd`(整局结束),应为 `FightEnd`(每波结束)
> 3. `HeroDead` 未提阵营过滤,会导致怪物死亡误触发
>
> 保留本文档仅作历史记录,请勿参考。
---
## 旧草案内容(仅供参考)
### 原始需求背景
当前技能卡SkillCardData`CardSet.ts` 中仅通过 `is_inst`(是否即时)和 `t_inv`(触发间隔)隐式区分类型。随着技能丰富,需要:
1. 明确定义卡牌技能的触发类型(如:驻场、定时)。
2. 新增类似于英雄生命周期的触发时机:战斗开始、战斗结束、场上英雄死亡、英雄召唤上场。
### 原始修改方案
#### 1. 明确技能触发类型 (`CardSet.ts`)
新增枚举 `CardSkillType`,用于明确区分卡牌技能的触发时机:
```typescript
export enum CardSkillType {
Interval = 1, // 间隔定时触发 (战斗中每隔N秒执行)
Field = 2, // 驻场技能 (被动光环)
BattleStart = 3, // 战斗开始时触发一次
BattleEnd = 4, // 战斗结束时触发一次
HeroDead = 5, // 场上己方英雄死亡时触发
HeroCall = 6, // 场上己方英雄召唤上场时触发
}
```
> ❌ **错误 1**:缺少 `Instant` 类型,现有 `is_inst: true` 的卡牌8301护盾、8302治疗等无法归类。
#### 2. 完善事件派发机制
为支持新的触发类型,确保相关事件被正确派发:
- **英雄召唤上场 (`GameEvent.MasterCalled`)**:已在 `Hero.ts` 中实现。
- **英雄死亡 (`GameEvent.HeroDead`)**:需在 `HeroAtkSystem.ts` 中的英雄死亡逻辑里,补充派发 `GameEvent.HeroDead` 事件,供技能盒子监听。
- **战斗开始/结束 (`GameEvent.FightStart` / `GameEvent.MissionEnd`)**:已支持。
> ❌ **错误 2**`BattleEnd` 映射到 `MissionEnd` 是错的——MissionEnd 是整局任务结束,不是每波战斗结束。
> ❌ **错误 3**HeroDead 未提阵营过滤,怪物死亡会误触发。
#### 3. 重构技能盒子逻辑 (`SkillBoxComp.ts`)
修改 `SkillBoxComp`,使其根据 `trigger_type` 进行不同的监听与触发:
- **属性定义**:新增解析并保存 `trigger_type`
- **事件监听**:在 `onLoad``init` 后根据 `trigger_type` 注册相应的监听:
- `CardSkillType.BattleStart`: 监听 `GameEvent.FightStart`
- `CardSkillType.BattleEnd`: 监听 `GameEvent.MissionEnd`
- `CardSkillType.HeroDead`: 监听 `GameEvent.HeroDead`
- `CardSkillType.HeroCall`: 监听 `GameEvent.MasterCalled`
- **触发处理**
- 每当监听到对应事件,调用 `triggerSkill()` 释放技能,并累加触发次数。
- 若已达最大触发次数,则销毁节点。

View File

@@ -0,0 +1,332 @@
# 卡牌技能触发类型化改造执行计划
> 状态Accepted
> 日期2026-06-19
> 关联文档:`skill_card_trigger_refactor.md`(旧草案,已废弃,被本计划取代)
> 关联设计:`2026-05-22-skill-template-refactor-design.md`(技能 overrides 机制,本计划复用)
---
## 一、背景与目标
### 1.1 现状问题
当前技能卡([SkillCardData](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts#L151))通过 `is_inst` / `t_inv` / `field` 三个字段**隐式组合**推断触发模式:
| 隐式模式 | 判定条件 | 痛点 |
|---------|---------|------|
| 即时一次性 | `is_inst: true` | 类型不直观,新人需交叉对比 3 个字段 |
| 战斗中定时 | `is_inst: false && t_inv > 0` | 同上 |
| 纯驻场光环 | `field.length > 0 && t_inv <= 0` | 同上 |
且**无法表达**事件驱动型触发(战斗开始/结束、英雄死亡/召唤)。
### 1.2 改造目标
1. **显式类型化**:新增 `trigger_type` 字段,一张卡一个类型,强制必填
2. **事件驱动扩展**:新增 4 种事件触发类型,对齐英雄侧 [SkillTriggerType](file:///d:/game/pixelheros/assets/script/game/common/config/heroSet.ts#L91-L101)
3. **复用现有事件**:直接监听 `GameEvent.FightStart` / `FightEnd` / `HeroDead` / `MasterCalled` / `ReviveSuccess`
4. **零破坏迁移**:一次性批改所有 SkillCardData 配置,不保留向后兼容推断逻辑
### 1.3 关键决策(已确认)
| 决策点 | 选择 | 理由 |
|--------|------|------|
| 向后兼容策略 | **强制显式声明** | 一次性迁移到位,避免推断逻辑长期残留 |
| FightEnd 事件 | **新增 FightEnd 派发** | MissionEnd 是整局结束语义不符FightEnd 才是每波战斗结束 |
| HeroCall 覆盖范围 | **所有英雄上场** | MasterCalled主角+技能召唤)+ ReviveSuccess复活 |
| Field 类型改造 | **仅显式分类** | 实际生效仍由 FieldSkillSet 处理SkillBoxComp 不主动施法 |
---
## 二、最终设计(融合修正版)
### 2.1 CardTriggerType 枚举定义
> 融合说明吸收旧草案的命名规范Field/Interval/从1开始规避其事件映射错误
```typescript
/** 卡牌技能触发类型 */
export enum CardTriggerType {
Instant = 1, // 即时触发:使用后立即生效一次
Interval = 2, // 定时循环:战斗中按 t_inv 间隔重复触发
Field = 3, // 驻场光环:被动生效(仅显式分类,仍由 field 字段驱动)
FightStart = 4, // 战斗开始时触发
FightEnd = 5, // 战斗结束时触发(每波结束)
HeroDead = 6, // 场上己方英雄死亡时触发
HeroCall = 7, // 英雄上场时触发(主角召唤 + 技能召唤 + 复活)
}
```
**命名对齐说明**
- `Field` 对齐英雄侧 [SkillTriggerType.Field](file:///d:/game/pixelheros/assets/script/game/common/config/heroSet.ts#L96)
- `Interval` 对齐现有 `t_inv`interval命名
- 枚举值从 1 开始,避免 `0` 的 falsy 坑(`if (trigger_type)` 判断出错)
### 2.2 事件映射表(核心设计)
| trigger_type | 监听事件 | 派发点现状 | 需补派发 |
|--------------|---------|-----------|---------|
| `Instant` | 无init 时立即触发) | — | — |
| `Interval` | `FightStart`(启动计时) | ✅ [MissionComp.ts:458](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts#L458) | — |
| `Field` | 无(不主动施法) | — | — |
| `FightStart` | `FightStart` | ✅ 已派发 | — |
| `FightEnd` | `FightEnd` | ❌ **未派发** | ✅ [MissionComp.ts:494](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts#L494) 之后 |
| `HeroDead` | `HeroDead` | ❌ **未派发**(死代码) | ✅ [HeroAtkSystem.ts:329](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts#L329) 内(带阵营过滤) |
| `HeroCall` | `MasterCalled` + `ReviveSuccess` | MasterCalled ✅ 已派发ReviveSuccess ❌ 未派发 | ✅ 复活成功处补 ReviveSuccess |
### 2.3 CardConfig 接口扩展
```typescript
export interface CardConfig {
// ... 既有字段 ...
/** 触发类型(必填) */
trigger_type: CardTriggerType;
/** 事件型触发的全局次数上限(仅 FightStart/FightEnd/HeroDead/HeroCall 有效)
* 默认 Infinity达到上限后销毁节点
* 注意:与 t_times 语义不同——t_times 控制每波内 Interval 的次数 */
trigger_limit?: number;
}
```
### 2.4 t_times vs trigger_limit 语义区分
| 字段 | 适用类型 | 含义 | 重置时机 |
|------|---------|------|---------|
| `t_times` | `Interval` | 每波内的触发次数上限 | 每波 NewWave 时重置 |
| `trigger_limit` | `FightStart/FightEnd/HeroDead/HeroCall` | 整局全局触发总次数 | 不重置,达上限销毁 |
---
## 三、分阶段执行计划
### 阶段 1补齐事件派发缺口基础设施
**目标**:确保所有新触发类型依赖的事件都能正确派发
#### 任务 1.1MissionComp 补派发 FightEnd
- **文件**[MissionComp.ts](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts)
- **位置**`BattleEnd` case`triggerHeroBattleSkills(false)` + `healAllHeroes()` 之后
- **改动**
```typescript
case MissionPhase.BattleEnd:
// ... 既有评分逻辑 ...
this.triggerHeroBattleSkills(false);
this.healAllHeroes();
// 【新增】派发战斗结束事件,供卡牌技能监听
oops.message.dispatchEvent(GameEvent.FightEnd);
break;
```
#### 任务 1.2HeroAtkSystem 补派发 HeroDead带阵营过滤
- **文件**[HeroAtkSystem.ts](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts)
- **位置**`triggerDeadSkills` 方法L329 附近)
- **改动**
```typescript
private triggerDeadSkills(entity: ecs.Entity): void {
const TAttrsComp = entity.get(HeroAttrsComp);
if (!TAttrsComp) return;
const view = entity.get(HeroViewComp);
if (view) {
SkillTriggerHelper.trigger(SkillTriggerType.Dead, TAttrsComp, view);
// 【新增】仅英雄阵营派发全局死亡事件(怪物死亡不触发卡牌效果)
if (TAttrsComp.fac === FacSet.HERO) {
oops.message.dispatchEvent(GameEvent.HeroDead, { eid: entity.eid });
}
}
}
```
#### 任务 1.3:复活逻辑补派发 ReviveSuccess
- **文件**:需先定位复活成功处理点(搜索 `is_reviving` 置 false 的位置)
- **改动**:复活成功时派发 `oops.message.dispatchEvent(GameEvent.ReviveSuccess, { eid })`
- **注意**:需先执行任务:全局搜索复活成功逻辑位置
---
### 阶段 2CardSet 配置层改造
**目标**:定义枚举 + 扩展接口 + 修复字段透传 + 批量迁移配置
#### 任务 2.1:新增 CardTriggerType 枚举
- **文件**[CardSet.ts](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts)
- **位置**`CardLV` 枚举之后
- **内容**:见 [2.1 节](#21-cardtriggertype-枚举定义)
#### 任务 2.2CardConfig 接口扩展
- **文件**:同上
- **位置**`CardConfig` 接口
- **内容**:见 [2.3 节](#23-cardconfig-接口扩展)
#### 任务 2.3:修复 SkillCardData.forEach 字段透传断点
- **文件**:同上
- **位置**[L220-L240](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts#L220-L240) `SkillCardData.forEach`
- **改动**:补充 `overrides``trigger_type` 透传:
```typescript
SkillCardData.forEach(data => {
CardPoolList.push({
// ... 既有字段 ...
keep_waves: data.keep_waves,
field: data.field,
overrides: data.overrides, // 【修复】原遗漏
trigger_type: data.trigger_type, // 【新增】
trigger_limit: data.trigger_limit, // 【新增】
});
});
```
#### 任务 2.4SkillCardData 批量补 trigger_type30 张卡牌)
- **文件**:同上
- **迁移对照表**
| 卡牌区间 | 旧字段特征 | 新增 trigger_type |
|---------|-----------|------------------|
| 8301, 8302, 8303, 8401-8409, 8501`is_inst: true` | 即时技能 | `CardTriggerType.Instant` |
| 8705, 8706, 8707, 8708-8718, 8701-8704`field` | 驻场光环 | `CardTriggerType.Field` |
| 8201-8206`is_inst: false, t_inv: 5` | 定时循环 | `CardTriggerType.Interval` |
---
### 阶段 3SkillBoxComp 核心重构
**目标**:按 trigger_type 分发事件监听与触发
#### 任务 3.1:新增成员变量
- **文件**[SkillBoxComp.ts](file:///d:/game/pixelheros/assets/script/game/map/SkillBoxComp.ts)
- **位置**`// ======================== 技能配置 ========================` 区块
```typescript
/** 触发类型 */
private trigger_type: CardTriggerType = CardTriggerType.Instant;
/** 事件型触发次数上限 */
private trigger_limit: number = Infinity;
/** 事件型已触发次数 */
private trigger_count: number = 0;
```
#### 任务 3.2init 读取 trigger_type
- **位置**`init()` 方法内,读取 config 之后
```typescript
this.trigger_type = config.trigger_type ?? CardTriggerType.Instant;
this.trigger_limit = config.trigger_limit ?? Infinity;
```
#### 任务 3.3:新增 registerTrigger 方法
按 trigger_type 注册对应事件监听,见下方完整代码。
#### 任务 3.4:新增 onEventTrigger 统一入口
事件型触发的统一处理:检查 trigger_limit → triggerSkill → 累加计数 → 检查销毁。
#### 任务 3.5onLoad / onDestroy 调整
- `onLoad`:移除原 FightStart / NewWave 硬编码监听,改为 `init` 后调用 `registerTrigger` 动态注册
- `onDestroy`:统一 off 所有可能订阅的事件(即使没订阅也无副作用)
#### 任务 3.6update 方法收窄
`Interval` 类型走帧驱动计时逻辑,其他类型提前 return。
---
### 阶段 4验证与回归
#### 任务 4.1:编译检查
- 确认所有 CardTriggerType 引用正确
- 确认无 TS 类型错误
#### 任务 4.2:功能验证清单
- [ ] 即时卡8301 护盾):使用后立即触发,每波重置
- [ ] 定时卡8201 雷墙):战斗中每 5 秒触发,跨波次维持
- [ ] 驻场卡8705 金币收益):被动生效,不主动施法
- [ ] 新增 FightStart 卡:每波战斗开始时触发
- [ ] 新增 FightEnd 卡:每波战斗结束时触发
- [ ] 新增 HeroDead 卡:英雄死亡时触发,怪物死亡不触发
- [ ] 新增 HeroCall 卡:主角召唤/技能召唤/复活都触发
#### 任务 4.3:边界场景
- [ ] trigger_limit 达上限后节点正确销毁
- [ ] keep_waves 与 trigger_type 的组合行为正确
- [ ] 节点销毁时所有事件监听正确注销(无内存泄漏)
---
## 四、风险与注意事项
### 4.1 高风险点
1. **HeroDead 必须加阵营过滤**
- [HeroAtkSystem.triggerDeadSkills](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts#L319) 是英雄和怪物共用
- 不加 `fac === FacSet.HERO` 过滤 → 每波几百只怪物死亡 = 海量误触发
2. **FightEnd vs MissionEnd 不可混淆**
- MissionEnd = 整局任务结束(通关/失败)
- FightEnd = 每波战斗结束BattleEnd 阶段)
- 文档草案错误地把 BattleEnd 映射到 MissionEnd本计划已修正
3. **MasterCalled 携带数据不一致**
- 3 个派发点([Hero.ts:206](file:///d:/game/pixelheros/assets/script/game/hero/Hero.ts#L206)、[MissionHeroComp.ts:223](file:///d:/game/pixelheros/assets/script/game/map/MissionHeroComp.ts#L223)、[273](file:///d:/game/pixelheros/assets/script/game/map/MissionHeroComp.ts#L273)payload 字段不同
- SkillBoxComp 的 `onEventTrigger` **不要读 payload 字段**,仅作触发信号
### 4.2 不破坏的现有逻辑
- ✅ Field 类型完全复用现有 [FieldSkillSet](file:///d:/game/pixelheros/assets/script/game/common/config/SkillSet.ts#L414-L421) 机制
- ✅ Interval 类型完全复用现有 `update` 帧驱动 + cd_mask 表现
- ✅ [forceCastCardSkill](file:///d:/game/pixelheros/assets/script/game/hero/SCastSystem.ts#L75) 施法入口零改动
- ✅ [SBox.ts](file:///d:/game/pixelheros/assets/script/game/map/SBox.ts) 节点工厂零改动
### 4.3 keep_waves 跨类型语义
| trigger_type | keep_waves 默认值 | 行为 |
|--------------|-----------------|------|
| `Instant` | 0 = 用完即销 | `-1` = 每波重置再触发一次 |
| `Interval` | -1 = 跨波次维持 | 每波重置 timer 和 trigger_count |
| `Field` | -1 = 全程存活 | 不主动触发 |
| 事件型 | 由 `trigger_limit` 控制 | 达上限销毁 |
---
## 五、文件改动清单
| 文件 | 改动类型 | 阶段 |
|------|---------|------|
| [MissionComp.ts](file:///d:/game/pixelheros/assets/script/game/map/MissionComp.ts) | 补 FightEnd 派发 | 1 |
| [HeroAtkSystem.ts](file:///d:/game/pixelheros/assets/script/game/hero/HeroAtkSystem.ts) | 补 HeroDead 派发(带过滤) | 1 |
| 复活逻辑文件(待定位) | 补 ReviveSuccess 派发 | 1 |
| [CardSet.ts](file:///d:/game/pixelheros/assets/script/game/common/config/CardSet.ts) | 枚举+接口+透传+30张卡迁移 | 2 |
| [SkillBoxComp.ts](file:///d:/game/pixelheros/assets/script/game/map/SkillBoxComp.ts) | 核心重构 | 3 |
| [SBox.ts](file:///d:/game/pixelheros/assets/script/game/map/SBox.ts) | **零改动** | — |
---
## 六、新增卡牌配置示例
```typescript
// 战斗开始护盾(整局每波开始都给全队加盾)
{ uuid: 8310, skill: 6301, wave: 5, name: "起手护盾",
trigger_type: CardTriggerType.FightStart, keep_waves: -1,
overrides: { TGroup: TGroup.Team, ap: 3 },
info: "每波战斗开始时为全体友方添加护盾", is_inst: false }
// 英雄死亡治疗(整局最多触发 3 次)
{ uuid: 8311, skill: 6302, wave: 10, name: "亡语治疗",
trigger_type: CardTriggerType.HeroDead, trigger_limit: 3, keep_waves: -1,
overrides: { TGroup: TGroup.Team, ap: 200 },
info: "己方英雄死亡时治疗全体友方整局最多触发3次", is_inst: false }
// 英雄上场攻击强化(每次有新英雄上场都触发,最多 5 次)
{ uuid: 8312, skill: 6401, wave: 15, name: "召唤强化",
trigger_type: CardTriggerType.HeroCall, trigger_limit: 5, keep_waves: -1,
info: "有英雄上场时触发攻击强化整局最多触发5次", is_inst: false }
```

View File

@@ -0,0 +1,351 @@
# 通用英雄/技能配置编辑器Cocos Creator 扩展)— 设计规格
- 日期2026-06-20
- 作者brainstorm 协作产出
- 目标引擎Cocos Creator **3.8.6**
- 部署位置:`extensions/pixelhero-config-editor/`
- 关联配置源:`assets/script/game/common/config/{heroSet,SkillSet,HeroAttrs,HeroSkillDesc}.ts`
---
## 1. 概述Overview
构建一个 Cocos Creator 编辑器扩展,提供**可视化**的英雄与技能配置编辑能力。扩展以 **schema 驱动 + TypeScript AST 往返**为核心:直接读写现有 `Record<number,X>` 形态的 `.ts` 配置文件,**不改动任何游戏运行时代码**。通过声明式 schema 描述每张表的字段、枚举、引用关系UI 由 schema 自动生成,从而实现"通用"——未来新增驻场技能表、卡牌表等只需追加 schema无需编写新 UI。
## 2. 目标与非目标
### 目标
- 可视化编辑 `HeroInfo`(英雄 50xx + 怪物 60xx/61xx`SkillSet`(技能 60xx/63xx/64xx/65xx`FieldSkillSet`(驻场 70xx/72xx/74xx三张表。
- 原地回写对应 `.ts` 文件,**保留符号表达式**(如 `AtkSpeedSet[AtkSpeedLv.Slow3].cd`)与手工注释/分节标题。
- 异构触发槽、技能引用覆盖(`SkillOverrides`)、进化配置的可视化编辑。
- 实时校验 + 实时描述预览(与游戏内 `buildSkillDesc` 一致)。
- 新建/复制/删除/保存/还原,并保持 `HeroList``HeroInfo` 一致。
- 纯逻辑层schema/IO/校验)有自动化单元测试,作为 BLOCKING 证据。
### 非目标v1
- 不重构游戏代码、不把数据迁移到 JSON。
- 不做运行时热重载游戏逻辑(仅通过 asset-db 刷新让编辑器与编辑器内预览生效)。
- 撤销/重做Undo/Redo列为 v1.1。
- 不编辑 `CardSet / HighlightSet / GameSet / ScoreSet`(架构允许后续以新增 schema 方式扩展,但不在本次范围)。
- 不做多人协作/版本对比。
## 3. 背景事实(已核实)
| 事实 | 影响 |
|---|---|
| 引擎 Cocos Creator 3.8.6 | 使用 `package_version:2` 扩展格式;面板经 `Editor.Panel.define({...})`;消息经 `contributions.messages` |
| 配置运行时只读、仅按 key 访问、无动态加载 | 回写安全;输出只需是合法 TS 且 `HeroList` 一致 |
| `skills[n].cd` 为符号表达式 | IO 必须基于 TS Compiler API识别并保号往返 |
| 现有 `oops-plugin-framework` 仅运行时框架 | 无面板示例可抄;需自建打包 |
| `HeroList` 被运行时迭代(`CardSet.ts` | 写英雄表后必须同步 `HeroList = 排序后的英雄(HERO) uuid` |
| `HeroSkillDesc.buildSkillDesc` 生成游戏内描述 | 移植为 JS 用于面板实时预览 |
## 4. 架构(五层)
```
┌────────────────────────────────────────────────────────────┐
│ UI 层 Vue3 面板 (dist/panels/default.js) │
│ master-detail + 嵌套编辑器 + 校验面板 + 描述预览 │
└───────────────────────────────┬────────────────────────────┘
Editor.Message.request │ broadcast 'record-changed'
┌───────────────────────────────┴────────────────────────────┐
│ 主进程 dist/main.js 内存真理源 + 消息处理 + 广播 │
└──────┬──────────────────────┬──────────────────────┬───────┘
│ │ │
┌──────▼───────┐ ┌───────────▼──────────┐ ┌─────────▼────────┐
│ 校验层 │ │ Schema 注册表 │ │ IO 层 (TS 往返) │
│ validate() │ │ tables/fields/enums │ │ TsConfigFile │
│ → Issue[] │ │ → 驱动 UI 生成 │ │ load/patch/save │
└──────────────┘ └──────────────────────┘ └──────────┬────────┘
│ 写回 .ts
Editor.Message.request('asset-db','refresh-asset')
```
层次依赖单向UI → 主进程 → {校验, schema, IO}。校验与 schema 为纯逻辑可独立测试。IO 依赖 `typescript` 包。
## 5. 组件详设
### 5.1 Schema 注册表(`src/shared/schema/`
"通用"的核心。每个数据表用一份 `TableSchema` 描述:
```ts
interface TableSchema {
id: 'hero' | 'skill' | 'field'; // 表标识
label: string; // "英雄/怪物"
sourceFile: string; // 相对 assets 配置目录,如 'heroSet.ts'
exportName: string; // 'HeroInfo' | 'SkillSet' | 'FieldSkillSet'
keyType: 'number';
idSegments: { label: string; min: number; max: number; note?: string }[];
listExportName?: string; // 仅 hero'HeroList'(需同步)
fields: FieldSchema[]; // 记录字段(顺序即 UI 顺序)
}
interface FieldSchema {
key: string; // 字段名(对应 .ts 对象键)
label: string; // 中文标签
type: FieldType; // 见下
required?: boolean;
default?: unknown;
group?: string; // UI 分组("基础"/"触发技能"/...
help?: string;
// type 相关的可选元数据:
enumRef?: string; // type=enum → enums.ts 中的枚举键
refTable?: TableId; // type=ref → 引用哪张表
overlayKeys?: string[]; // type=overrides → 可覆盖键集合
showIf?: { field: string; in: unknown[] };// 条件显示
}
type FieldType =
| 'number' | 'string' | 'boolean'
| 'enum' // 下拉,选项来自 enumRef
| 'ref' // 引用另一表的 uuid下拉显示 目标.name
| 'speedExpr' // 攻速符号表达式,下拉=AtkSpeedLv 档位
| 'skillMap' // Record<number,HSkillInfo>(英雄专用)
| 'triggerSlots' // 6 种数组触发槽(英雄专用)
| 'fieldList' // number[] 驻场技能列表(英雄专用)
| 'reviveSlot' // 单对象 {s_uuid,r_num,upr}(英雄专用)
| 'overrides'; // SkillOverrides 覆盖层(出现在 triggerSlots 内部)
```
**枚举源 `src/shared/schema/enums.ts`**:镜像游戏枚举为 `{label,value}[]` 字典——`HType, FacSet, TGroup, DTType, SkillKind, DType, IType, RType, EType, FieldSkillType, Attrs(buff_type), AtkSpeedLv`。此文件为编辑器侧单一事实源;并提供 `assertEnumsMatchGame()` 调试期检查(读取游戏 `.ts` 枚举定义比对,不一致则告警),避免漂移。
**三张表的字段清单v1**
- **hero (`HeroInfo`)** — 分组:
- 基础:`uuid`(number,必填), `name`(string,必填), `path`(string,必填), `icon`?(string), `fac`(enum FacSet,必填), `pool_lv`?(number), `lv`(number,必填,默认1), `type`(enum HType,必填), `hp`(number,必填), `ap`(number,必填), `dis`?(number), `speed`?(number), `info`(string,必填)
- 技能:`skills`(skillMap,必填), 触发槽组 `call/dead/fstart/fend/atking/atked`(triggerSlots), `field`(fieldList), `revive`(reviveSlot), `evolve`(evolveMap — v1 只读展示,标注"v1.1 编辑")
- ID 段:英雄 [5000,5999];怪物 [6000,6999]6101-6106 为 Boss
- **skill (`SkillSet``SkillConfig`)**
- 基础:`uuid`(number,必填), `name`(string,必填), `sp_name`(string,必填), `icon`(string,必填), `act`(string,必填), `info`(string,必填)
- 目标/类型:`TGroup`(enum,必填), `DTType`(enum,必填), `IType`(enum,必填), `RType`(enum,必填), `EType`(enum,必填), `kind`?(enum SkillKind), `DType`?(enum,默认 ATK)
- 数值:`ap`(number,必填), `gold`?(number), `hit_count`(number,必填), `hitcd`(number,必填), `speed`(number,必填), `ready`(number,必填), `with`(number,必填,默认0)
- 动画/特效:`readyAnm`,`endAnm`,`DAnm`(string), `EAnm`(number)
- 高级:`crt?`,`stun?`,`frz?`,`bck?`(number), `buff_type`?(enum Attrs), `call_hero`?(ref hero), `time`?, `bezier_start_y?`,`bezier_mid_y?`,`bezier_arc?`(number)
- ID 段6001-6999
- **field (`FieldSkillSet``FieldSkillConfig`)**
- `uuid`(number,必填), `name`(string,必填), `icon`(string,必填), `type`(enum FieldSkillType,必填), `value`(number,必填), `info`(string,必填)
- ID 段7001-7999
> 字段清单来源 = 直接对照 `heroSet.ts`/`SkillSet.ts` 的 interface 定义,已逐字段核对类型与必填性。
### 5.2 IO 层(`src/main/io/TsConfigFile.ts`
基于 `typescript`npm在扩展主进程 Node 上下文中 `require`)。
```ts
class TsConfigFile {
load(file, exportName): void; // 解析并缓存 SourceFile + 目标 VariableDeclaration
getKeys(): number[]; // 该 const 的所有键
read(key): RecordValue; // AST → 结构化值
patch(key, value: RecordValue): void; // AST 区间替换该条目
add(key, value): void; // 在 const 末尾插入新条目
delete(key): void; // 删除条目区间
serialize(value): string; // 单条目确定性序列化
save(): { ok: boolean; error?: string }; // 写 .bak → 校验可解析 → 落盘 → 触发 asset-db refresh
reload(): void;
}
```
**值模型 `RecordValue`**
- 标量:`{kind:'num',value}` / `{kind:'str',value}` / `{kind:'bool',value}`
- 符号表达式:`{kind:'speed', level:'Slow3'}` ⇄ 源 `AtkSpeedSet[AtkSpeedLv.Slow3].cd`AST 形态:`PropertyAccessExpression(ElementAccessExpression(Ident,ElementAccessExpression(Ident,Ident)),Ident)`,固定匹配该模式;不匹配则降级为 `{kind:'num',value:<节点文本>}` 并标记需人工确认)
- 对象:`{kind:'obj', props: {key: RecordValue}}`
- 数组:`{kind:'arr', items: RecordValue[]}`
**回写策略 = 条目级 AST 区间替换**
1. 解析文件 → 定位目标 `exportName``ObjectLiteralExpression`
2. 在其中按 key 定位单个 `PropertyAssignment` 的完整文本区间(含其尾随逗号)。
3. 对该条目调用 `serialize()` 生成新文本。**确定性格式(固定,无歧义)**受控多行对象字面量每字段一行、4 空格缩进、字段顺序按 schema `fields` 顺序、尾随逗号;`cd` 用符号形式 `AtkSpeedSet[AtkSpeedLv.X].cd``info` 非空时在条目上方输出一行 `// {info}` 注释。示例:
```ts
// 每受击3次为自身添加4层护盾
5011:{uuid:5011,name:"小铁卫",path:"hk1",fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:400,ap:20,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:4}}],
info:"每受击3次为自身添加4层护盾"},
```
4. 用新文本替换该区间;其余条目、分节注释、其他 const 原样不动 → **diff 最小**。
5. 新增:在 `}` 前插入;删除:移除区间。
6. 落盘前:`createProgram` 对改动文件做语法检查;失败则回滚 `.bak` 并报错。
7. 落盘后:`await Editor.Message.request('asset-db','refresh-asset', url)`,使编辑器重新导入该脚本。
**降级与安全**
- 任一步骤异常 → 不写文件,返回结构化错误,面板展示。
- 始终先写 `*.bak`;校验通过后再覆盖原文件。
- 若条目内出现未识别的初始化表达式(非上述 RecordValue 形态),该条目标记为"只读(含未支持表达式)",可编辑其他条目但不改动它,避免破坏。
### 5.3 校验层(`src/shared/validation/`
纯函数 `validate(tableId, allRecords): Issue[]`,每次改动后对受影响表运行。规则:
| 规则 | 级别 |
|---|---|
| uuid 在表内唯一 | error |
| uuid 落在该实体声明的 idSegments 内 | error |
| 必填字段非空 | error |
| enum 字段值 ∈ 枚举集合 | error |
| ref 字段目标在引用表中存在(英雄触发槽/技能图引用 → SkillSetfield 列表 → FieldSkillSetcall_hero → HeroInfo | error |
| overrides 仅含 `SkillOverrides` 允许键TGroup,ap,gold,hit_count,hitcd,crt,frz,stun,bck,buff_type,call_hero | error |
| 英雄表:`HeroList` 与 `HeroInfo` 一致(每 HeroList 项存在且 fac=HERO每 fac=HERO 条目都在 HeroList | error |
**`HeroList` 同步策略(最小 diff**:保留现有数组顺序与分节注释,仅在新增英雄时把新 uuid 追加到数组末尾、删除英雄时移除其 uuid。**不重排、不重生成**,以避免破坏手工分节注释。同步后再次运行一致性校验。
| 怪物无 `pool_lv`/`evolve`(语义警告) | warn |
| `info` 文案长度 >0 | warn |
`Issue = {tableId, key, fieldPath, severity:'error'|'warn', code, message}`。面板据此在列表行显红点、字段内联报错;存在 error 时禁用"保存"。
### 5.4 主进程(`src/main/index.ts`
扩展入口。在内存持有三张表的 `TsConfigFile` 实例与 schema 注册表,作为唯一真理源。处理消息(见 §7。任何写入先校验error 则拒绝并返回 Issue 列表;成功后广播 `record-changed {tableId, key}`,所有打开面板据此刷新。
### 5.5 UI 层(`src/panels/default/`Vue 3
- **打包**`esbuild` 将 `src/panels/default/index.ts`(含 Vue 3 runtime+compiler打成单文件 `dist/panels/default.js`。Vue 用**字符串模板**`compiler` 在线编译),避免 SFC 工具链。
- **面板入口**`Editor.Panel.define``template`=`<div id="app"></div>``ready()` 中 `createApp({…}).mount(this.$.app)``close()` 卸载。
- **布局**:左 master表切换 + 搜索 + 列表),右 detailschema 驱动表单 + 嵌套编辑器 + 预览),底部校验条。
- **控件映射**number→`<ui-num-input>`string→`<ui-input>`boolean→`<ui-checkbox>`enum→`<ui-select>`ref→`<ui-select>`(选项=目标表 `{uuid:name}`,空选项=清除speedExpr→`<ui-select>`(选项=AtkSpeedLv 档位,含"自定义数值"兜底)。
- **嵌套编辑器**
- `TriggerSlotsEditor`:对 6 种触发类型,每组可增删行 `{s_uuid(技能 ref 选择器), t_num(number), overrides(可折叠)}`行内显示该技能基础信息name/kind
- `SkillMapEditor`:英雄 `skills`;每项 `uuid + lv + cd(speedExpr)`;至少 1 项index 0=普攻)。
- `FieldListEditor``field:number[]` 多选驻场技能。
- `ReviveEditor``revive` 单对象 `{s_uuid,r_num,upr}` 或空。
- `OverrideEditor`:依据所选基础技能 `kind`,仅渲染相关覆盖键(如 Damage→ap/hit_count/crt/stun…Support→TGroup/ap/buff_type…
- **实时预览**`PreviewPane` 调主进程 `query-preview-desc`(移植 `buildSkillDesc`),随编辑即时刷新。
- **图标/特效预览**`icon`、`sp_name` 变更时 `query-asset` 取贴图,旁置缩略图。
- **操作栏**:新建(自动取 idSegment 内下一个可用 uuid、复制uuid+2 或手动指定、删除、还原reload、保存并刷新触发 §5.2 save。未保存改动用 `*` 标记;切换记录前若有未保存改动,弹确认。
- **原生观感**:尽量用 Cocos `<ui-*>` 元素Vue 仅做状态与组合。
## 6. 内存与持久化
- 真理源 = 磁盘 `.ts` 文件。主进程首次 `query-*` 时懒加载并缓存 `TsConfigFile`。
- 编辑改动先作用于内存 AST"保存"才落盘。还原=丢弃内存改动重载。
- 多面板实例:主进程单例,广播保证一致。
## 7. 消息协议(`contributions.messages`
| 消息 | 方向 | 载荷 | 返回 |
|---|---|---|---|
| `query-schema` | panel→main | `tableId?` | schema全部或单表 |
| `query-enums` | panel→main | — | 枚举字典 |
| `query-keys` | panel→main | `tableId` | `number[]` |
| `query-record` | panel→main | `tableId,key` | `RecordValue` |
| `query-preview-desc` | panel→main | `hero RecordValue` | `string`(描述) |
| `query-asset` | panel→main | `name, type` | 贴图 url 或 null |
| `validate` | panel→main | `tableId` | `Issue[]` |
| `save-record` | panel→main | `tableId,key,RecordValue` | `{ok, issues?}` |
| `add-record` | panel→main | `tableId,key,RecordValue` | `{ok, issues?}` |
| `delete-record` | panel→main | `tableId,key` | `{ok, issues?}` |
| `revert-record` | panel→main | `tableId,key` | `RecordValue`(重载后值) |
| `record-changed` | main→broadcast | `tableId,key` | — |
`save/add/delete` 成功后主进程自动广播 `record-changed`。
## 8. 数据流(保存一条英雄)
```
面板改字段 → save-record(hero,5011,value)
→ 主进程 validate(hero)error? 返回 {ok:false,issues}
→ TsConfigFile.patch(5011, value) // AST 区间替换
→ TsConfigFile.save():写 .bak → createProgram 语法校验 → 覆盖原 .ts → asset-db refresh
→ 广播 record-changed(hero,5011)
→ 面板重查 → UI 刷新(含 HeroList 若变动)
```
## 9. 模块/文件布局
```
extensions/pixelhero-config-editor/
├── package.json # package_version:2, panels, contributions.messages/menu, deps: typescript, vue, esbuild
├── tsconfig.json
├── esbuild.config.mjs # 打包 main + 面板
├── i18n/{en,zh}.js
├── static/
│ ├── template/default/index.html
│ └── style/default/index.css
├── src/
│ ├── main/
│ │ ├── index.ts # 扩展入口 + onMessage 注册
│ │ └── store.ts # 三张表 TsConfigFile 单例 + 广播
│ ├── io/
│ │ ├── TsConfigFile.ts # 解析/序列化/patch/save
│ │ ├── recordValue.ts # RecordValue 类型与归一化(含 speedExpr
│ │ └── serializer.ts # serialize(entry) 确定性文本生成
│ ├── shared/ # 纯逻辑(无 Cocos/Editor 依赖,可独立测试)
│ │ ├── schema/
│ │ │ ├── types.ts # TableSchema/FieldSchema 定义
│ │ │ ├── registry.ts # 三张表 schema 注册
│ │ │ ├── hero.ts
│ │ │ ├── skill.ts
│ │ │ ├── field.ts
│ │ │ └── enums.ts # 枚举镜像 + assertEnumsMatchGame
│ │ ├── validation/
│ │ │ └── index.ts # validate() 规则
│ │ └── desc/
│ │ └── buildSkillDesc.ts # HeroSkillDesc 的 JS 移植(预览用)
│ └── panels/default/
│ ├── index.ts # Editor.Panel.define + Vue mount
│ └── app/ # Vue 组件App, MasterList, DetailForm, TriggerSlots, SkillMap, FieldList, Revive, Override, PreviewPane, ValidationBar
├── dist/ # 打包产物main.js, panels/default.js
└── __tests__/ # node:test 纯逻辑测试
├── tsConfigFile.roundtrip.test.ts
├── serializer.test.ts
├── validation.test.ts
├── speedExpr.test.ts
├── schema.test.ts
└── fixtures/ # 真实配置副本
```
## 10. 构建与开发
- `npm run build``esbuild` 同时打包 `main`platform=node, format=cjs与 `panels/default`platform=browser, format=iife, bundle vue输出到 `dist/`。
- 开发:改完 `build` → 在 Cocos"扩展管理器"重载扩展。
- 依赖:`typescript`IO 用)、`vue`(面板用)、`esbuild`(打包)、`fs-extra`(可选,读模板)。`typescript` 体积大但必要;打 main 时 bundle 进 dist。
## 11. 测试策略
遵循项目 `coding-standards.md`
- **逻辑层BLOCKING自动化`node:test`**
- `tsConfigFile.roundtrip`:用真实 `heroSet.ts`/`SkillSet.ts` 副本作 fixture → load → 读全部 → 不改动 → save → 与原文件逐字节相等(验证保号保注释)。
- 改动往返patch 一条英雄 → save → 重新 load → 读回值 == 改入值;且产物经 `createProgram` 语法合法。
- `serializer`:给定结构化值 → 序列化文本 → 再解析 → 等价。
- `speedExpr``AtkSpeedSet[AtkSpeedLv.Slow3].cd` ⇄ `{kind:'speed',level:'Slow3'}` 双向。
- `validation`:构造各类非法数据 → 断言 Issue 正确。
- `schema`:所有表 schema 字段 key 与对应 interface 一致;枚举镜像与游戏 `.ts` 枚举一致(`assertEnumsMatchGame`)。
- 命名/隔离/无外部依赖遵循测试规范fixture 为常量文件副本,不内联魔法数。
- **UIADVISORY**`production/qa/evidence/` 下手动走查文档(覆盖三表增删改查、校验阻断、预览一致性)+ 编辑器内截图。
## 12. 分阶段实施(供 writing-plans 细化)
| 阶段 | 产出 | 验证 |
|---|---|---|
| P0 脚手架 | package.json/panels/menu/build面板可从菜单打开显示 "hello" | 截图 |
| P1 IO 层 | TsConfigFile + RecordValue + serializer + roundtrip 测试 | 测试通过 |
| P2 schema+枚举+校验 | 三表 schema、enums、validate + 测试 | 测试通过 |
| P3 主进程 | store + 全部消息处理(先只读类) | 手动 query 验证 |
| P4 面板基础 | master 列表 + 标量/枚举/ref 字段编辑 + 保存往返 | 端到端改一个英雄保存 |
| P5 嵌套与预览 | 触发槽/技能图/驻场/复活/覆盖 + 描述预览 + 缩略图 | 编辑复杂英雄保存往返一致 |
| P6 收尾 | 新建/复制/删除/HeroList 同步/还原/打磨/QA 文档 | QA 走查通过 |
## 13. 风险与缓解
| 风险 | 缓解 |
|---|---|
| TS AST 往返破坏文件 | 条目级区间替换 + `.bak` + `createProgram` 校验 + 未识别表达式标记只读 |
| 符号表达式形态多样 | 固定匹配 `AtkSpeedSet[AtkSpeedLv.X].cd` 模式;其余降级为只读数值并提示 |
| 枚举镜像与游戏漂移 | `assertEnumsMatchGame` 调试期比对告警 |
| `typescript` 打包体积 | 仅 bundle 进 mainnode面板不包含可接受 |
| esbuild/Vue 在扩展环境运行 | P0 先打通最小面板Vue mount 成功)再扩展 |
| 多面板状态不一致 | 主进程单例 + 广播 record-changed |
| `HeroList` 失同步 | 英雄表写入后由 store 强制同步并校验 |
## 14. 验收标准
1. 扩展可从"面板"菜单打开,三张表可切换浏览、搜索。
2. 对三张表任一记录:可视化查看/编辑所有字段(含触发槽、技能图、覆盖、驻场、复活),保存后磁盘 `.ts` 被更新且为合法 TSasset-db 已刷新。
3. **保号往返**:仅查看后保存,文件零字节变化(测试断言)。
4. 实时校验:违反任一 error 规则时"保存"被阻断并列出问题;行/字段级可见。
5. 描述预览与游戏内 `buildSkillDesc` 输出一致。
6. 新建/复制/删除可用;`HeroList` 与 `HeroInfo` 始终一致。
7. 逻辑层单元测试全部通过BLOCKING
8. 未改动 `assets/script/game/**` 任何游戏运行时代码git diff 可证)。

View File

@@ -0,0 +1,3 @@
node_modules/
dist/
*.bak

View File

@@ -0,0 +1 @@
.tmp/

View File

@@ -0,0 +1,25 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { buildSkillDesc } from '../src/shared/desc/buildSkillDesc';
import { RecordValue } from '../src/io/recordValue';
const skillSet: Record<number, RecordValue> = {
6301: { kind: 'obj', props: { name: { kind: 'str', value: '护盾' }, kind: { kind: 'enumRef', qualifier: 'SkillKind', member: 'Shield' }, ap: { kind: 'num', value: 4 } } },
};
const fieldSet: Record<number, RecordValue> = {
7015: { kind: 'obj', props: { name: { kind: 'str', value: '死亡强化' }, info: { kind: 'str', value: '死亡触发技能次数+1' } } },
};
const hero: RecordValue = { kind: 'obj', props: {
atked: { kind: 'arr', items: [{ kind: 'obj', props: { s_uuid: { kind: 'num', value: 6301 }, t_num: { kind: 'num', value: 3 }, overrides: { kind: 'obj', props: { ap: { kind: 'num', value: 4 } } } } }] },
field: { kind: 'arr', items: [{ kind: 'num', value: 7015 }] },
} };
test('renders atked trigger with shield effect', () => {
const out = buildSkillDesc(hero, skillSet, fieldSet);
assert.match(out, /受击3次:护盾/);
assert.match(out, /护盾4次/);
});
test('renders field aura', () => {
const out = buildSkillDesc(hero, skillSet, fieldSet);
assert.match(out, /场上存活:死亡强化 死亡触发技能次数\+1/);
});

View File

@@ -0,0 +1,13 @@
// 测试夹具:镜像真实 heroSet.ts 片段speed 表达式 + 枚举引用 + 触发槽 + 驻场 + 复活)
export const HeroInfo: Record<number, any> = {
5011:{uuid:5011,name:"小铁卫",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:400,ap:20,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:4}}],
field:[7015],
revive:{s_uuid:6501,r_num:1,upr:0.3},
info:"每受击3次为自身添加4层护盾"},
6001:{uuid:6001,name:"兽人战士",path:"m1", fac:FacSet.MON,lv:1,type:HType.Melee,hp:220,ap:10,speed:70,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"基础近战位怪"},
};
export const HeroList: number[] = [5011];

View File

@@ -0,0 +1,16 @@
// 测试夹具:镜像真实 SkillSet.ts 片段(含 SkillSet + FieldSkillSet
export const SkillSet: Record<number, any> = {
6001:{uuid:6001,name:"火球",sp_name:"atk_1",icon:"Stat_Attack_01",TGroup:TGroup.Enemy,act:"atk",
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.2,speed:720,with:0,ready:0.2,
IType:IType.Melee,RType:RType.bezier,EType:EType.collision,info:"造成攻击力100%的伤害"},
6301:{uuid:6301,name:"护盾",sp_name:"buff_wind",icon:"Stat_Defense",TGroup:TGroup.Self,act:"atk",
DTType:DTType.single,kind:SkillKind.Shield,ap:3,hit_count:1,hitcd:0.2,speed:720,with:0,ready:0.2,
IType:IType.support,RType:RType.fixed,EType:EType.animationEnd,info:"添加护盾"},
6501:{uuid:6501,name:"复活",sp_name:"buff_wind",icon:"Stat_HolyDamage",TGroup:TGroup.Self,act:"atk",
DTType:DTType.single,kind:SkillKind.Support,ap:50,hit_count:3,hitcd:0.2,speed:720,with:0,ready:0.2,
IType:IType.support,RType:RType.fixed,EType:EType.animationEnd,info:"复活百分比"},
};
export const FieldSkillSet: Record<number, any> = {
7015:{uuid:7015,name:"死亡强化",icon:"Stat_PoisonChanceIncrease",type:FieldSkillType.DeadCount,value:1,info:"死亡触发技能次数+1"},
};

View File

@@ -0,0 +1,47 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import * as ts from 'typescript';
import { parseExpression, findExportObjectLiteral } from '../src/io/parser';
function parse(text: string): ts.Expression {
const src = ts.createSourceFile('x.ts', `const _ = ${text};`, ts.ScriptTarget.Latest, true);
const decl = src.statements[0] as ts.VariableStatement;
return decl.declarationList.declarations[0].initializer!;
}
test('num / str / bool', () => {
assert.deepEqual(parseExpression(parse('400')), { kind: 'num', value: 400 });
assert.deepEqual(parseExpression(parse('"小铁卫"')), { kind: 'str', value: '小铁卫' });
assert.deepEqual(parseExpression(parse('true')), { kind: 'bool', value: true });
});
test('enumRef: FacSet.HERO', () => {
assert.deepEqual(parseExpression(parse('FacSet.HERO')), { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' });
});
test('speed: AtkSpeedSet[AtkSpeedLv.Slow3].cd', () => {
assert.deepEqual(parseExpression(parse('AtkSpeedSet[AtkSpeedLv.Slow3].cd')), { kind: 'speed', level: 'Slow3' });
});
test('non-Speed two-segment access falls back to enumRef', () => {
assert.deepEqual(parseExpression(parse('Foo.bar')), { kind: 'enumRef', qualifier: 'Foo', member: 'bar' });
});
test('arr', () => {
assert.deepEqual(parseExpression(parse('[1,2,3]')), { kind: 'arr', items: [
{ kind: 'num', value: 1 }, { kind: 'num', value: 2 }, { kind: 'num', value: 3 }] });
});
test('obj with numeric + identifier keys', () => {
const v = parseExpression(parse('{6001:{uuid:6001},name:"x"}'));
assert.equal(v.kind, 'obj');
assert.deepEqual(v.props['6001'], { kind: 'obj', props: { uuid: { kind: 'num', value: 6001 } } });
assert.deepEqual(v.props['name'], { kind: 'str', value: 'x' });
});
test('raw: unsupported expression preserved verbatim', () => {
const v = parseExpression(parse('a + b'));
assert.equal(v.kind, 'raw');
assert.equal((v as any).text, 'a + b');
});
test('findExportObjectLiteral locates HeroInfo', () => {
const src = ts.createSourceFile('x.ts',
`export const HeroInfo: Record<number, any> = { 5011:{uuid:5011} };`, ts.ScriptTarget.Latest, true);
const node = findExportObjectLiteral(src, 'HeroInfo');
assert.ok(node);
assert.equal(node!.properties.length, 1);
});

View File

@@ -0,0 +1,44 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { serializeValue, serializeEntry } from '../src/io/serializer';
import { RecordValue } from '../src/io/recordValue';
test('serializeValue: num/str/bool', () => {
assert.equal(serializeValue({ kind: 'num', value: 400 }), '400');
assert.equal(serializeValue({ kind: 'str', value: '小铁卫' }), '"小铁卫"');
assert.equal(serializeValue({ kind: 'bool', value: true }), 'true');
});
test('serializeValue: enumRef', () => {
const v: RecordValue = { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' };
assert.equal(serializeValue(v), 'FacSet.HERO');
});
test('serializeValue: speed', () => {
const v: RecordValue = { kind: 'speed', level: 'Slow3' };
assert.equal(serializeValue(v), 'AtkSpeedSet[AtkSpeedLv.Slow3].cd');
});
test('serializeValue: arr', () => {
const v: RecordValue = { kind: 'arr', items: [{ kind: 'num', value: 1 }, { kind: 'num', value: 2 }] };
assert.equal(serializeValue(v), '[1,2]');
});
test('serializeValue: obj', () => {
const v: RecordValue = { kind: 'obj', props: { a: { kind: 'num', value: 1 }, b: { kind: 'str', value: 'x' } } };
assert.equal(serializeValue(v), '{a:1,b:"x"}');
});
test('serializeValue: raw passthrough', () => {
const v: RecordValue = { kind: 'raw', text: 'a + b' };
assert.equal(serializeValue(v), 'a + b');
});
test('serializeEntry: all-scalar one line with trailing comma', () => {
const v: RecordValue = { kind: 'obj', props: { uuid: { kind: 'num', value: 1 }, name: { kind: 'str', value: 'x' } } };
assert.equal(serializeEntry('1', v), '1:{uuid:1,name:"x"},');
});
test('serializeEntry: nested on continuation lines', () => {
const v: RecordValue = { kind: 'obj', props: {
uuid: { kind: 'num', value: 5011 },
skills: { kind: 'obj', props: { 6001: { kind: 'obj', props: { uuid: { kind: 'num', value: 6001 } } } } },
} };
const out = serializeEntry('5011', v);
assert.match(out, /^5011:\{uuid:5011,$/m); // 首行:键 + 标量
assert.match(out, / skills:\{6001:\{uuid:6001\}\},$/m); // 续行 8 空格
assert.match(out, /^ \},$/m); // 闭合 4 空格
});

View File

@@ -0,0 +1,101 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, copyFileSync, mkdtempSync, readdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { TsConfigFile } from '../src/io/TsConfigFile';
const here = dirname(fileURLToPath(import.meta.url));
const fixture = join(here, 'fixtures', 'heroSet.sample.ts');
test('load + getKeys', () => {
const f = new TsConfigFile(fixture, 'HeroInfo');
f.load();
assert.deepEqual(f.getKeys(), ['5011', '6001']);
});
test('read returns structured value with speed/enumRef preserved', () => {
const f = new TsConfigFile(fixture, 'HeroInfo');
f.load();
const v = f.read('5011')!;
assert.equal(v.kind, 'obj');
assert.deepEqual(v.props['fac'], { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' });
const skill = v.props['skills'].props['6001'];
assert.deepEqual(skill.props['cd'], { kind: 'speed', level: 'Slow3' });
});
test('getText after load equals original file (no mutation on load)', () => {
const f = new TsConfigFile(fixture, 'HeroInfo');
f.load();
assert.equal(f.getText(), readFileSync(fixture, 'utf8'));
});
test('read missing key returns null', () => {
const f = new TsConfigFile(fixture, 'HeroInfo');
f.load();
assert.equal(f.read('9999'), null);
});
function withTempFixture(): { dir: string; file: string } {
const dir = mkdtempSync(join(tmpdir(), 'phcfg-'));
const file = join(dir, 'heroSet.sample.ts');
copyFileSync(fixture, file);
return { dir, file };
}
test('patch updates one entry; reload reads new value; other entries intact', () => {
const { file } = withTempFixture();
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
const v = f.read('5011')!;
v.props['ap'] = { kind: 'num', value: 99 };
f.patch('5011', v);
assert.equal(f.isDirty(), true);
assert.equal(f.save().ok, true);
const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load();
assert.deepEqual(f2.read('5011')!.props['ap'], { kind: 'num', value: 99 });
assert.equal(f2.read('6001')!.props['name'].value, '兽人战士'); // 另一条未破坏
});
test('patch preserves speed + enumRef on reload', () => {
const { file } = withTempFixture();
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
const v = f.read('5011')!;
v.props['hp'] = { kind: 'num', value: 500 };
f.patch('5011', v); f.save();
const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load();
const again = f2.read('5011')!;
assert.deepEqual(again.props['fac'], { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' });
assert.deepEqual(again.props['skills'].props['6001'].props['cd'], { kind: 'speed', level: 'Slow3' });
});
test('add appends entry readable after save+reload', () => {
const { file } = withTempFixture();
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
f.add('5099', { kind: 'obj', props: { uuid: { kind: 'num', value: 5099 }, name: { kind: 'str', value: '新英雄' } } });
f.save();
const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load();
assert.ok(f2.getKeys().includes('5099'));
assert.equal(f2.read('5099')!.props['name'].value, '新英雄');
});
test('delete removes entry', () => {
const { file } = withTempFixture();
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
f.delete('6001'); f.save();
const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load();
assert.ok(!f2.getKeys().includes('6001'));
assert.ok(f2.getKeys().includes('5011'));
});
test('save writes a .bak backup of the pre-save file', () => {
const { file } = withTempFixture();
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
const v = f.read('5011')!; v.props['hp'] = { kind: 'num', value: 1 }; f.patch('5011', v);
f.save();
assert.equal(readFileSync(file + '.bak', 'utf8'), readFileSync(fixture, 'utf8'));
});
test('save with no edits is a no-op (ok, no .bak written)', () => {
const { file, dir } = withTempFixture();
const f = new TsConfigFile(file, 'HeroInfo'); f.load();
assert.equal(f.save().ok, true);
assert.equal(readdirSync(dir).some(p => p.endsWith('.bak')), false);
});

View File

@@ -0,0 +1,67 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { validate } from '../src/shared/validation';
import { RecordValue } from '../src/io/recordValue';
const ctx = {
hasSkill: (u: number) => [6001, 6301, 6501].includes(u),
hasField: (u: number) => [7015].includes(u),
hasHero: (u: number) => [5011, 6001].includes(u),
heroListKeys: new Set<string>(['5011']),
};
function heroObj(extra: Record<string, RecordValue> = {}): RecordValue {
return { kind: 'obj', props: {
uuid: { kind: 'num', value: 5011 }, name: { kind: 'str', value: 'x' }, path: { kind: 'str', value: 'p' },
fac: { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' }, lv: { kind: 'num', value: 1 },
type: { kind: 'enumRef', qualifier: 'HType', member: 'Melee' }, hp: { kind: 'num', value: 400 },
ap: { kind: 'num', value: 20 },
skills: { kind: 'obj', props: { 6001: { kind: 'obj', props: { uuid: { kind: 'num', value: 6001 }, lv: { kind: 'num', value: 1 }, cd: { kind: 'speed', level: 'Slow3' }, ccd: { kind: 'num', value: 0 } } } } },
info: { kind: 'str', value: 'ok' },
...extra,
} };
}
test('valid hero → no errors', () => {
const issues = validate('hero', new Map([['5011', heroObj()]]), ctx);
assert.equal(issues.filter(i => i.severity === 'error').length, 0);
});
test('duplicate uuid → error', () => {
// 两条记录携带相同的 uuid 字段值Map 键不同uuid 字段相同)
const dup = heroObj(); // uuid=5011
const issues = validate('hero', new Map([['5011', heroObj()], ['5012', dup]]), ctx);
assert.ok(issues.some(i => i.severity === 'error' && i.code === 'dup-uuid'));
});
test('missing required field → error', () => {
const h = heroObj(); delete h.props['name'];
const issues = validate('hero', new Map([['5011', h]]), ctx);
assert.ok(issues.some(i => i.code === 'missing-required' && i.fieldPath === 'name'));
});
test('trigger slot references missing skill → error', () => {
const h = heroObj({ atked: { kind: 'arr', items: [{ kind: 'obj', props: {
s_uuid: { kind: 'num', value: 9999 }, t_num: { kind: 'num', value: 3 } } }] } });
const issues = validate('hero', new Map([['5011', h]]), ctx);
assert.ok(issues.some(i => i.code === 'dangling-ref'));
});
test('field list references missing field skill → error', () => {
const h = heroObj({ field: { kind: 'arr', items: [{ kind: 'num', value: 8888 }] } });
const issues = validate('hero', new Map([['5011', h]]), ctx);
assert.ok(issues.some(i => i.code === 'dangling-ref'));
});
test('HeroList/hero consistency: fac=HERO entry missing from HeroList → error', () => {
const ctx2 = { ...ctx, heroListKeys: new Set<string>([]) };
const issues = validate('hero', new Map([['5011', heroObj()]]), ctx2);
assert.ok(issues.some(i => i.code === 'herolist-inconsistent'));
});
test('invalid enum value → error', () => {
const h = heroObj({ fac: { kind: 'enumRef', qualifier: 'FacSet', member: 'NOPE' } });
const issues = validate('hero', new Map([['5011', h]]), ctx);
assert.ok(issues.some(i => i.code === 'bad-enum'));
});
test('unknown overrides key → error', () => {
const h = heroObj({ atked: { kind: 'arr', items: [{ kind: 'obj', props: {
s_uuid: { kind: 'num', value: 6301 }, t_num: { kind: 'num', value: 3 },
overrides: { kind: 'obj', props: { bogus: { kind: 'num', value: 1 } } } } }] } });
const issues = validate('hero', new Map([['5011', h]]), ctx);
assert.ok(issues.some(i => i.code === 'bad-override-key'));
});

View File

@@ -0,0 +1,29 @@
import { build, context } from 'esbuild';
import { readFileSync } from 'fs';
import { join } from 'path';
const watch = process.argv.includes('--watch');
const common = {
bundle: true,
sourcemap: false,
logLevel: 'info',
alias: { 'vue': 'vue/dist/vue.esm-bundler.js' },
};
// 面板进程在 Cocos 的 Electron 渲染层运行,可访问 Node 内建fs/path但 vue 必须
// 打进浏览器侧 IIFE。因此面板项用 platform:'browser' + 把 node: 内建标为 external
// 交由运行时解析;这样 esbuild 既不抱怨,又保持 plan 的"运行时读 static 文件"语义。
const entries = [
{ entryPoints: ['src/main/index.ts'], outfile: 'dist/main.js', platform: 'node', format: 'cjs', external: [] },
{ entryPoints: ['src/panels/default/index.ts'], outfile: 'dist/panels/default.js', platform: 'browser', format: 'iife', external: ['node:fs', 'node:path'] },
];
if (watch) {
for (const e of entries) {
const ctx = await context({ ...common, ...e });
await ctx.watch();
}
} else {
for (const e of entries) await build({ ...common, ...e });
}

View File

@@ -0,0 +1,4 @@
exports.en = {
'pixelhero-config-editor': { description: 'Hero/Skill Config Editor', title: 'Hero/Skill Config' },
'menu': { 'panel/英雄技能配置': 'Hero/Skill Config' },
};

View File

@@ -0,0 +1,4 @@
exports.zh = {
'pixelhero-config-editor': { description: '英雄/技能配置编辑器', title: '英雄/技能配置' },
'menu': { 'panel/英雄技能配置': '英雄技能配置' },
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
{
"package_version": 2,
"name": "pixelhero-config-editor",
"version": "0.1.0",
"description": "英雄/技能配置编辑器",
"main": "./dist/main.js",
"author": "pixelhero",
"editor": ">=3.8.0",
"scripts": {
"build": "node esbuild.config.mjs",
"watch": "node esbuild.config.mjs --watch",
"test": "tsx --test __tests__/*.test.ts"
},
"panels": {
"default": {
"title": "英雄/技能配置",
"type": "dockable",
"main": "./dist/panels/default.js",
"size": { "width": 960, "height": 640, "min-width": 640, "min-height": 480 }
}
},
"contributions": {
"menu": [
{ "path": "PixelHero/英雄技能配置", "message": "open-panel" }
],
"messages": {
"open-panel": { "methods": ["open-panel"] },
"query-schema": { "methods": ["query-schema"] },
"query-enums": { "methods": ["query-enums"] },
"query-keys": { "methods": ["query-keys"] },
"query-record": { "methods": ["query-record"] },
"query-preview-desc": { "methods": ["query-preview-desc"] },
"validate": { "methods": ["validate"] },
"save-record": { "methods": ["save-record"] },
"revert-record": { "methods": ["revert-record"] }
}
},
"dependencies": {
"typescript": "^5.4.0",
"vue": "^3.4.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"esbuild": "^0.20.0",
"fs-extra": "^11.0.0",
"tsx": "^4.7.0"
}
}

View File

@@ -0,0 +1,110 @@
import * as fs from 'node:fs';
import * as ts from 'typescript';
import { RecordValue } from './recordValue';
import { findExportObjectLiteral, parseExpression } from './parser';
import { serializeEntry } from './serializer';
/**
* 以"条目级 AST 区间替换"方式读写一个 `export const <name>: Record<number,X> = {...}` 配置文件。
* 保留符号表达式speed与枚举引用enumRef未识别表达式原样保留raw
* save() 纯逻辑:仅做 fs 读写 + 语法校验(写 .bakasset-db 刷新由主进程在 save 成功后调用。
*/
export class TsConfigFile {
private sourceText = '';
private sourceFile!: ts.SourceFile;
private targetNode!: ts.ObjectLiteralExpression;
private dirty = false;
constructor(public readonly filePath: string, public readonly exportName: string) {}
load(): void {
this.sourceText = fs.readFileSync(this.filePath, 'utf8');
this.reparse();
this.dirty = false;
}
private reparse(): void {
// 第 4 参数 setParentNodes 必须为 true否则 node.getStart()/getEnd() 不可用。
this.sourceFile = ts.createSourceFile(this.filePath, this.sourceText, ts.ScriptTarget.Latest, true);
const node = findExportObjectLiteral(this.sourceFile, this.exportName);
if (!node) throw new Error(`export const ${this.exportName} not found or not an object literal in ${this.filePath}`);
this.targetNode = node;
}
getKeys(): string[] {
return this.targetNode.properties.map(p => (p.name as ts.PropertyName).getText());
}
read(key: string): RecordValue | null {
const entry = this.findEntry(key);
if (!entry) return null;
return parseExpression(entry.initializer);
}
private findEntry(key: string): ts.PropertyAssignment | undefined {
return this.targetNode.properties.find(p => (p.name as ts.PropertyName).getText() === key) as ts.PropertyAssignment | undefined;
}
getText(): string { return this.sourceText; }
isDirty(): boolean { return this.dirty; }
patch(key: string, value: RecordValue): void {
const entry = this.findEntry(key);
const newText = serializeEntry(key, value);
if (entry) {
const { start, end } = this.entrySpan(entry);
this.sourceText = this.sourceText.slice(0, start) + newText + this.sourceText.slice(end);
} else {
this.insertEntry(newText);
}
this.reparse();
this.dirty = true;
}
add(key: string, value: RecordValue): void {
if (this.findEntry(key)) throw new Error(`key ${key} already exists`);
this.insertEntry(serializeEntry(key, value));
this.reparse();
this.dirty = true;
}
delete(key: string): void {
const entry = this.findEntry(key);
if (!entry) return;
const { start, end } = this.entrySpan(entry);
this.sourceText = this.sourceText.slice(0, start) + this.sourceText.slice(end);
this.reparse();
this.dirty = true;
}
save(): { ok: true } | { ok: false; error: string } {
if (!this.dirty) return { ok: true };
const check = ts.createSourceFile(this.filePath, this.sourceText, ts.ScriptTarget.Latest, true);
if (check.parseDiagnostics.length > 0) {
const msg = check.parseDiagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n')).join('; ');
return { ok: false, error: `syntax error after edit: ${msg}` };
}
// .bak 取自磁盘原始内容(落盘前的当前文件),保证可回滚。
fs.writeFileSync(this.filePath + '.bak', fs.readFileSync(this.filePath));
fs.writeFileSync(this.filePath, this.sourceText);
this.dirty = false;
return { ok: true };
}
/** 条目文本区间:从 key 词法起始(不含前导缩进 trivia避免吞掉上一行换行到尾随逗号止。 */
private entrySpan(entry: ts.PropertyAssignment): { start: number; end: number } {
const start = entry.getStart();
let end = entry.getEnd();
const comma = /^\s*,/.exec(this.sourceText.slice(end));
if (comma) end += comma[0].length;
return { start, end };
}
/** 在闭合 `}` 前追加一条新记录4 空格缩进)。 */
private insertEntry(entryText: string): void {
const closeBrace = this.targetNode.getLastToken();
if (!closeBrace) throw new Error('object literal has no closing brace');
const pos = closeBrace.getStart();
this.sourceText = this.sourceText.slice(0, pos) + ` ${entryText}\n ` + this.sourceText.slice(pos);
}
}

View File

@@ -0,0 +1,65 @@
import * as ts from 'typescript';
import { RecordValue } from './recordValue';
/** 在 SourceFile 中查找 `export const <name> = {...}`,返回其对象字面量节点。 */
export function findExportObjectLiteral(src: ts.SourceFile, name: string): ts.ObjectLiteralExpression | null {
let result: ts.ObjectLiteralExpression | null = null;
const visit = (node: ts.Node) => {
if (result) return;
if (ts.isVariableStatement(node)) {
for (const d of node.declarationList.declarations) {
if (d.name.getText() === name && d.initializer && ts.isObjectLiteralExpression(d.initializer)) {
result = d.initializer;
return;
}
}
}
ts.forEachChild(node, visit);
};
visit(src);
return result;
}
/** 识别 `AtkSpeedSet[AtkSpeedLv.<level>].cd`,否则 null。 */
function tryParseSpeedExpr(node: ts.PropertyAccessExpression): RecordValue | null {
if (node.name.text !== 'cd') return null;
const inner = node.expression;
if (!ts.isElementAccessExpression(inner)) return null;
if (!ts.isIdentifier(inner.expression) || inner.expression.text !== 'AtkSpeedSet') return null;
const arg = inner.argumentExpression;
if (!ts.isPropertyAccessExpression(arg)) return null;
if (!ts.isIdentifier(arg.expression) || arg.expression.text !== 'AtkSpeedLv') return null;
return { kind: 'speed', level: arg.name.text };
}
export function parseExpression(node: ts.Expression): RecordValue {
if (ts.isNumericLiteral(node)) return { kind: 'num', value: Number(node.text) };
if (ts.isStringLiteral(node)) return { kind: 'str', value: node.text };
if (node.kind === ts.SyntaxKind.TrueKeyword) return { kind: 'bool', value: true };
if (node.kind === ts.SyntaxKind.FalseKeyword) return { kind: 'bool', value: false };
if (ts.isPropertyAccessExpression(node)) {
const speed = tryParseSpeedExpr(node);
if (speed) return speed;
if (ts.isIdentifier(node.expression)) {
return { kind: 'enumRef', qualifier: node.expression.text, member: node.name.text };
}
return { kind: 'raw', text: node.getText() }; // 多级 a.b.c 等
}
if (ts.isArrayLiteralExpression(node)) {
return { kind: 'arr', items: node.elements.map(e => parseExpression(e)) };
}
if (ts.isObjectLiteralExpression(node)) {
const props: Record<string, RecordValue> = {};
for (const m of node.properties) {
if (ts.isPropertyAssignment(m)) {
props[m.name.getText()] = parseExpression(m.initializer);
}
}
return { kind: 'obj', props };
}
return { kind: 'raw', text: node.getText() }; // 二元/模板/调用/其他元素访问
}

View File

@@ -0,0 +1,15 @@
/** IO 层对配置对象字面量值的结构化表示。零依赖。 */
export type RecordValue =
| { kind: 'num'; value: number }
| { kind: 'str'; value: string }
| { kind: 'bool'; value: boolean }
| { kind: 'enumRef'; qualifier: string; member: string } // 如 FacSet.HERO
| { kind: 'speed'; level: string } // AtkSpeedSet[AtkSpeedLv.X].cd
| { kind: 'obj'; props: Record<string, RecordValue> }
| { kind: 'arr'; items: RecordValue[] }
| { kind: 'raw'; text: string }; // 未识别表达式,原样保留
export function isScalar(v: RecordValue): boolean {
return v.kind === 'num' || v.kind === 'str' || v.kind === 'bool'
|| v.kind === 'enumRef' || v.kind === 'speed' || v.kind === 'raw';
}

View File

@@ -0,0 +1,32 @@
import { RecordValue, isScalar } from './recordValue';
export function serializeValue(v: RecordValue): string {
switch (v.kind) {
case 'num': return String(v.value);
case 'str': return JSON.stringify(v.value);
case 'bool': return String(v.value);
case 'enumRef': return `${v.qualifier}.${v.member}`;
case 'speed': return `AtkSpeedSet[AtkSpeedLv.${v.level}].cd`;
case 'arr': return `[${v.items.map(serializeValue).join(',')}]`;
case 'obj': {
const body = Object.entries(v.props).map(([k, val]) => `${k}:${serializeValue(val)}`).join(',');
return `{${body}}`;
}
case 'raw': return v.text;
}
}
/** 序列化一条顶层记录。首行无缩进(缩进由调用处保留的 trivia 提供);嵌套字段 8 空格续行;闭合 4 空格。 */
export function serializeEntry(key: string, obj: RecordValue): string {
if (obj.kind !== 'obj') return `${key}:${serializeValue(obj)},`;
const props = obj.props;
const keys = Object.keys(props);
const scalars = keys.filter(k => isScalar(props[k]));
const nested = keys.filter(k => !isScalar(props[k]));
const scalarTxt = scalars.map(k => `${k}:${serializeValue(props[k])}`).join(',');
if (nested.length === 0) return `${key}:{${scalarTxt}},`;
const lines = [`${key}:{${scalarTxt},`];
for (const k of nested) lines.push(` ${k}:${serializeValue(props[k])},`);
lines.push(' },');
return lines.join('\n');
}

View File

@@ -0,0 +1,48 @@
import { store } from './store';
/**
* Cocos Creator 3.8 扩展主进程入口。
* 关键约定(与官方 first/message 文档一致):
* - 处理函数必须挂在 `methods` 对象里Cocos 读取 module.exports.methods
* methods 的 key 必须与 package.json 中 contributions.messages[*].methods 引用的名字一致。
* - 生命周期钩子为 `load`/`unload`(不是 onLoad
* - 处理函数直接接收消息参数(无 event返回值即 Editor.Message.request 的 resolve 结果。
*/
function load() {
store.reloadAll();
}
function unload() {
// 进程级单例随扩展卸载自然销毁,无需显式清理。
}
const methods: Record<string, (...args: any[]) => any> = {
'open-panel'() {
Editor.Panel.open('pixelhero-config-editor');
},
'query-schema'(id?: string) {
return store.querySchema(id as any);
},
'query-enums'() {
return store.queryEnums();
},
'query-keys'(id: string) {
return store.queryKeys(id as any);
},
'query-record'(id: string, key: string) {
return store.queryRecord(id as any, key);
},
'query-preview-desc'(hero: any) {
return store.queryPreviewDesc(hero);
},
'validate'(id: string) {
return store.validate(id as any);
},
'save-record'(id: string, key: string, value: any) {
return store.saveRecord(id as any, key, value);
},
'revert-record'(id: string, key: string) {
return store.revertRecord(id as any, key);
},
};
module.exports = { methods, load, unload };

View File

@@ -0,0 +1,85 @@
import { join } from 'node:path';
import { TsConfigFile } from '../io/TsConfigFile';
import { allSchemas } from '../shared/schema/registry';
import { ENUMS } from '../shared/schema/enums';
import { validate, ValidationContext } from '../shared/validation';
import { buildSkillDesc } from '../shared/desc/buildSkillDesc';
import { RecordValue } from '../io/recordValue';
import { TableId } from '../shared/schema/types';
/** 游戏配置目录(相对项目根)。主进程用 Editor.Project.path 解析到项目根的 assets 配置目录。 */
const CONFIG_DIR = join(Editor.Project.path, 'assets/script/game/common/config');
interface Entry { file: TsConfigFile; }
const tables: Partial<Record<TableId, Entry>> = {};
// skill 与 field 共享 SkillSet.ts但用不同 exportName 的 TsConfigFile 实例
const fileCache: Record<string, TsConfigFile> = {};
function getTable(id: TableId): Entry {
if (tables[id]) return tables[id]!;
const schema = allSchemas().find(s => s.id === id)!;
const cacheKey = `${schema.sourceFile}:${schema.exportName}`;
let file = fileCache[cacheKey];
if (!file) { file = new TsConfigFile(join(CONFIG_DIR, schema.sourceFile), schema.exportName); file.load(); fileCache[cacheKey] = file; }
const entry = { file };
tables[id] = entry;
return entry;
}
function recordsOf(id: TableId): Map<string, RecordValue> {
const { file } = getTable(id);
const m = new Map<string, RecordValue>();
for (const k of file.getKeys()) { const v = file.read(k); if (v) m.set(k, v); }
return m;
}
function buildContext(): ValidationContext {
const hero = recordsOf('hero');
const skill = recordsOf('skill');
const field = recordsOf('field');
const heroKeys = Array.from(hero.keys()).map(Number);
const skillKeys = Array.from(skill.keys()).map(Number);
const fieldKeys = Array.from(field.keys()).map(Number);
// HeroList 读取hero 表的 listExportName
const heroList = new TsConfigFile(join(CONFIG_DIR, 'heroSet.ts'), 'HeroList');
heroList.load();
const listRaw = heroList.getText();
const heroListKeys = new Set<string>(
(listRaw.match(/HeroList[\s\S]*?=\s*\[([^\]]*)\]/)?.[1].match(/-?\d+/g) ?? []).map(String)
);
return {
hasHero: (u) => heroKeys.includes(u),
hasSkill: (u) => skillKeys.includes(u),
hasField: (u) => fieldKeys.includes(u),
heroListKeys,
};
}
export const store = {
queryEnums: () => ENUMS,
querySchema: (id?: TableId) => id ? allSchemas().find(s => s.id === id) : allSchemas(),
queryKeys: (id: TableId) => getTable(id).file.getKeys(),
queryRecord: (id: TableId, key: string) => getTable(id).file.read(key),
queryPreviewDesc: (hero: RecordValue) => {
const skill = recordsOf('skill'); const field = recordsOf('field');
const skillSet: Record<number, RecordValue> = {};
for (const [k, v] of skill) skillSet[Number(k)] = v;
const fieldSet: Record<number, RecordValue> = {};
for (const [k, v] of field) fieldSet[Number(k)] = v;
return buildSkillDesc(hero, skillSet, fieldSet);
},
validate: (id: TableId) => validate(id, recordsOf(id), buildContext()),
saveRecord: (id: TableId, key: string, value: RecordValue) => {
// 先在副本上试写并校验
const { file } = getTable(id);
file.patch(key, value);
const issues = validate(id, recordsOf(id), buildContext()).filter(i => i.key === key && i.severity === 'error');
if (issues.length) { file.load(); return { ok: false as const, issues }; } // 回滚内存
const r = file.save();
if (!r.ok) { file.load(); return { ok: false as const, issues: [] }; }
void Editor.Message.request('asset-db', 'refresh-asset', `db://assets/script/game/common/config/${allSchemas().find(s => s.id === id)!.sourceFile}`);
return { ok: true as const, issues: [] };
},
revertRecord: (id: TableId, key: string) => { getTable(id).file.load(); return getTable(id).file.read(key); },
reloadAll: () => { for (const k of Object.keys(fileCache)) fileCache[k].load(); },
};

View File

@@ -0,0 +1,38 @@
import { createApp, defineComponent, reactive } from 'vue';
export const App = defineComponent({
setup() {
const state = reactive({ table: 'hero' as string, keys: [] as string[], picked: '' as string, detail: '' as string });
async function load() {
const keys = await Editor.Message.request('pixelhero-config-editor', 'query-keys', state.table);
state.keys = keys || [];
state.picked = ''; state.detail = '';
}
async function pick(k: string) {
state.picked = k;
const v = await Editor.Message.request('pixelhero-config-editor', 'query-record', state.table, k);
state.detail = JSON.stringify(v, null, 2);
}
load();
return { state, load, pick };
},
template: `
<div>
<div class="row">
<label>表:</label>
<select v-model="state.table" @change="load">
<option value="hero">英雄/怪物</option>
<option value="skill">技能</option>
<option value="field">驻场技能</option>
</select>
<span style="margin-left:12px;color:#888">共 {{ state.keys.length }} 条(端到端 IPC 已打通)</span>
</div>
<ul>
<li v-for="k in state.keys" :key="k" @click="pick(k)" :style="state.picked===k ? 'font-weight:bold' : ''">{{ k }}</li>
</ul>
<pre v-if="state.detail" style="white-space:pre-wrap;background:var(--color-normal-fill);padding:8px">{{ state.detail }}</pre>
</div>
`,
});
export function mount(el: HTMLElement) { createApp(App).mount(el); }

View File

@@ -0,0 +1,14 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { mount } from './app';
const template = readFileSync(join(__dirname, '../../../static/template/default/index.html'), 'utf-8');
const style = readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8');
module.exports = Editor.Panel.define({
template,
style,
$: { app: '#app' },
ready() { mount(this.$.app); },
close() { /* Vue 卸载随面板进程退出自动清理 */ },
});

View File

@@ -0,0 +1,63 @@
import { RecordValue } from '../../io/recordValue';
const TRIGGER_KEYS = ['call', 'dead', 'fstart', 'fend', 'atking', 'atked'] as const;
const TRIGGER_DESC: Record<string, string> = {
call: '召唤时', dead: '死亡时', fstart: '战斗开始时', fend: '战斗结束时',
field: '场上存活', atking: '攻击n次', atked: '受击n次', revive: '复活时',
};
function num(v: RecordValue | undefined): number | undefined { return v && v.kind === 'num' ? v.value : undefined; }
function str(v: RecordValue | undefined): string | undefined { return v && v.kind === 'str' ? v.value : undefined; }
function member(v: RecordValue | undefined): string | undefined { return v && v.kind === 'enumRef' ? v.member : undefined; }
function buildEffect(merged: RecordValue): string {
if (merged.kind !== 'obj') return '';
const kind = member(merged.props['kind']);
const ap = num(merged.props['ap']) ?? 0;
const parts: string[] = [];
if (kind === 'Heal') parts.push(`治疗伙伴${ap}`);
else if (kind === 'Shield') parts.push(`护盾${ap}`);
else if (kind === 'Gold') parts.push(`金币+${num(merged.props['gold']) ?? 0}`);
else if (kind === 'Support') parts.push(String(str(merged.props['info']) ?? ''));
else { // Damage / undefined
parts.push(`伤害${ap}%`);
if ((num(merged.props['hit_count']) ?? 1) > 1) parts.push(`${num(merged.props['hit_count'])}`);
if (num(merged.props['crt'])) parts.push(`暴击+${num(merged.props['crt'])}%`);
if (num(merged.props['stun'])) parts.push(`击晕+${num(merged.props['stun'])}%`);
}
return parts.join(' ');
}
/** 合并 overrides 到 base浅合并对象 props */
function merge(base: RecordValue, overrides: RecordValue | undefined): RecordValue {
if (!overrides || overrides.kind !== 'obj' || base.kind !== 'obj') return base;
return { kind: 'obj', props: { ...base.props, ...overrides.props } };
}
export function buildSkillDesc(hero: RecordValue, skillSet: Record<number, RecordValue>, fieldSet: Record<number, RecordValue>): string {
if (hero.kind !== 'obj') return '';
const lines: string[] = [];
for (const key of TRIGGER_KEYS) {
const arr = hero.props[key];
if (!arr || arr.kind !== 'arr') continue;
const tpl = TRIGGER_DESC[key] ?? key;
for (const it of arr.items) {
if (it.kind !== 'obj') continue;
const su = num(it.props['s_uuid']); if (su === undefined) continue;
const base = skillSet[su]; if (!base) continue;
const merged = merge(base, it.props['overrides']);
const trigger = tpl.includes('n') ? tpl.replace('n', String(num(it.props['t_num']) ?? '')) : tpl;
lines.push(`${trigger}:${str(base.props['name'])} ${buildEffect(merged)}`);
}
}
const fl = hero.props['field'];
if (fl && fl.kind === 'arr') {
for (const it of fl.items) { const u = num(it); if (u === undefined) continue; const fs = fieldSet[u]; if (fs) lines.push(`${TRIGGER_DESC.field}:${str(fs.props['name'])} ${str(fs.props['info'])}`); }
}
const rv = hero.props['revive'];
if (rv && rv.kind === 'obj') {
const su = num(rv.props['s_uuid']); const base = su !== undefined ? skillSet[su] : undefined;
if (base) lines.push(`${TRIGGER_DESC.revive} : ${str(base.props['name'])} ${buildEffect(merge(base, undefined))}`);
}
return lines.join('\n');
}

View File

@@ -0,0 +1,55 @@
export interface EnumMember { label: string; value: number | string; }
export interface EnumDef { qualifier: string; members: EnumMember[]; }
export const ENUMS: Record<string, EnumDef> = {
HType: { qualifier: 'HType', members: [
{ label: '近战 Melee', value: 0 }, { label: '中程 Mid', value: 1 }, { label: '远程 Long', value: 2 } ] },
FacSet: { qualifier: 'FacSet', members: [
{ label: '英雄 HERO', value: 0 }, { label: '怪物 MON', value: 1 } ] },
TGroup: { qualifier: 'TGroup', members: [
{ label: '自身 Self', value: 0 }, { label: '友方含己 Ally', value: 1 },
{ label: '友方 Team', value: 2 }, { label: '敌方 Enemy', value: 3 }, { label: '全体 All', value: 4 } ] },
DTType: { qualifier: 'DTType', members: [
{ label: '单体 single', value: 0 }, { label: '范围 range', value: 1 }, { label: '3x3 aoe_grid', value: 2 } ] },
SkillKind: { qualifier: 'SkillKind', members: [
{ label: '伤害 Damage', value: 0 }, { label: '治疗 Heal', value: 1 }, { label: '护盾 Shield', value: 2 },
{ label: '辅助 Support', value: 3 }, { label: '金币 Gold', value: 4 } ] },
DType: { qualifier: 'DType', members: [
{ label: '物理 ATK', value: 0 }, { label: '冰 ICE', value: 1 }, { label: '火 FIRE', value: 2 }, { label: '风 WIND', value: 3 } ] },
IType: { qualifier: 'IType', members: [
{ label: '近战 Melee', value: 0 }, { label: '远程 remote', value: 1 }, { label: '辅助 support', value: 2 } ] },
RType: { qualifier: 'RType', members: [
{ label: '直线 linear', value: 0 }, { label: '贝塞尔 bezier', value: 1 },
{ label: '固定起点 fixed', value: 2 }, { label: '固定终点 fixedEnd', value: 3 } ] },
EType: { qualifier: 'EType', members: [
{ label: '动画结束 animationEnd', value: 0 }, { label: '时间结束 timeEnd', value: 1 }, { label: '碰撞 collision', value: 2 } ] },
FieldSkillType: { qualifier: 'FieldSkillType', members: [
{ label: '召唤次数 SummonCount', value: 1 }, { label: '死亡次数 DeadCount', value: 2 },
{ label: '开场次数 StartCount', value: 3 }, { label: '结束次数 EndCount', value: 4 },
{ label: '每波金币 WaveGold', value: 5 }, { label: '卖出金币 SellGold', value: 6 },
{ label: '战后回复 WaveHeal', value: 7 }, { label: '英雄攻击 HeroAtk', value: 8 },
{ label: '英雄击晕 HeroStun', value: 9 }, { label: '英雄暴击 HeroCrit', value: 10 },
{ label: '英雄暴伤 HeroCritDamage', value: 11 }, { label: '英雄攻速 HeroSpeed', value: 12 },
{ label: '购买优惠 BuyDiscount', value: 13 }, { label: '刷新优惠 RefreshDiscount', value: 14 },
{ label: '英雄生命 HeroHp', value: 16 }, { label: '英雄风怒 HeroWindFury', value: 17 },
{ label: '英雄穿刺 HeroPuncture', value: 18 }, { label: '攻击次数 AtkCount', value: 19 },
{ label: '受击次数 BeAtkCount', value: 20 } ] },
AtkSpeedLv: { qualifier: 'AtkSpeedLv', members: [
{ label: '极速++ VeryFast1', value: 1 }, { label: '极速+ VeryFast2', value: 2 }, { label: '极速 VeryFast3', value: 3 },
{ label: '快速++ Fast1', value: 4 }, { label: '快速+ Fast2', value: 5 }, { label: '快速 Fast3', value: 6 },
{ label: '中速++ Normal1', value: 7 }, { label: '中速+ Normal2', value: 8 }, { label: '中速 Normal3', value: 9 },
{ label: '一般+ Mid1', value: 10 }, { label: '一般 Mid2', value: 11 }, { label: '一般- Mid3', value: 12 },
{ label: '慢 Slow1', value: 13 }, { label: '慢+ Slow2', value: 14 }, { label: '慢++ Slow3', value: 15 },
{ label: '很慢 VerySlow1', value: 16 }, { label: '很慢+ VerySlow2', value: 17 }, { label: '很慢++ VerySlow3', value: 18 } ] },
Attrs: { qualifier: 'Attrs', members: [
{ label: 'ap', value: 'ap' }, { label: 'hp', value: 'hp' }, { label: 'hp_max', value: 'hp_max' },
{ label: 'critical', value: 'critical' }, { label: 'critical_damage', value: 'critical_damage' },
{ label: 'stun_chance', value: 'stun_chance' }, { label: 'puncture_chance', value: 'puncture_chance' },
{ label: 'wfuny', value: 'wfuny' }, { label: 'freeze_chance', value: 'freeze_chance' },
{ label: 'knockback_chance', value: 'knockback_chance' } ] },
};
/** qualifier如 'FacSet')→ 对应 enumId如 'FacSet')。当前两者同名。 */
export const QUALIFIER_TO_ID: Record<string, string> = Object.fromEntries(
Object.entries(ENUMS).map(([id, def]) => [def.qualifier, id])
);

View File

@@ -0,0 +1,14 @@
import { TableSchema } from './types';
export const fieldSchema: TableSchema = {
id: 'field', label: '驻场技能',
sourceFile: 'SkillSet.ts', exportName: 'FieldSkillSet',
idSegments: [{ label: '驻场技能', min: 7000, max: 7999 }],
fields: [
{ key: 'uuid', label: 'UUID', type: 'number', required: true, group: '基础' },
{ key: 'name', label: '名称', type: 'string', required: true, group: '基础' },
{ key: 'icon', label: '图标', type: 'string', required: true, group: '基础' },
{ key: 'type', label: '类型', type: 'enum', enumRef: 'FieldSkillType', required: true, group: '效果' },
{ key: 'value', label: '数值', type: 'number', required: true, group: '效果' },
{ key: 'info', label: '描述', type: 'string', required: true, group: '基础' },
],
};

View File

@@ -0,0 +1,33 @@
import { TableSchema } from './types';
export const heroSchema: TableSchema = {
id: 'hero', label: '英雄/怪物',
sourceFile: 'heroSet.ts', exportName: 'HeroInfo', listExportName: 'HeroList',
idSegments: [
{ label: '英雄', min: 5000, max: 5999 },
{ label: '怪物', min: 6000, max: 6999 },
],
fields: [
{ key: 'uuid', label: 'UUID', type: 'number', required: true, group: '基础' },
{ key: 'name', label: '名称', type: 'string', required: true, group: '基础' },
{ key: 'path', label: '资源路径', type: 'string', required: true, group: '基础' },
{ key: 'icon', label: '图标', type: 'string', group: '基础' },
{ key: 'fac', label: '阵营', type: 'enum', enumRef: 'FacSet', required: true, group: '基础' },
{ key: 'pool_lv', label: '卡片等级', type: 'number', group: '基础' },
{ key: 'lv', label: '英雄等级', type: 'number', required: true, default: 1, group: '基础' },
{ key: 'type', label: '攻击定位', type: 'enum', enumRef: 'HType', required: true, group: '基础' },
{ key: 'hp', label: '生命上限', type: 'number', required: true, group: '基础' },
{ key: 'ap', label: '攻击力', type: 'number', required: true, group: '基础' },
{ key: 'dis', label: '攻击距离', type: 'number', group: '基础' },
{ key: 'speed', label: '移动速度', type: 'number', group: '基础' },
{ key: 'skills', label: '携带技能', type: 'skillMap', required: true, group: '技能' },
{ key: 'call', label: '召唤触发', type: 'triggerSlots', group: '触发技能' },
{ key: 'dead', label: '死亡触发', type: 'triggerSlots', group: '触发技能' },
{ key: 'fstart', label: '战斗开始触发', type: 'triggerSlots', group: '触发技能' },
{ key: 'fend', label: '战斗结束触发', type: 'triggerSlots', group: '触发技能' },
{ key: 'atking', label: '攻击触发', type: 'triggerSlots', group: '触发技能' },
{ key: 'atked', label: '受击触发', type: 'triggerSlots', group: '触发技能' },
{ key: 'field', label: '驻场技能', type: 'fieldList', group: '触发技能' },
{ key: 'revive', label: '复活触发', type: 'reviveSlot', group: '触发技能' },
{ key: 'info', label: '描述文案', type: 'string', required: true, group: '基础' },
],
};

View File

@@ -0,0 +1,10 @@
import { TableId, TableSchema } from './types';
import { heroSchema } from './hero';
import { skillSchema } from './skill';
import { fieldSchema } from './field';
export const REGISTRY: Record<TableId, TableSchema> = {
hero: heroSchema, skill: skillSchema, field: fieldSchema,
};
export function getSchema(id: TableId): TableSchema { return REGISTRY[id]; }
export function allSchemas(): TableSchema[] { return Object.values(REGISTRY); }

View File

@@ -0,0 +1,41 @@
import { TableSchema } from './types';
export const skillSchema: TableSchema = {
id: 'skill', label: '技能',
sourceFile: 'SkillSet.ts', exportName: 'SkillSet',
idSegments: [{ label: '技能', min: 6000, max: 6999 }],
fields: [
{ key: 'uuid', label: 'UUID', type: 'number', required: true, group: '基础' },
{ key: 'name', label: '名称', type: 'string', required: true, group: '基础' },
{ key: 'sp_name', label: '特效名', type: 'string', required: true, group: '基础' },
{ key: 'icon', label: '图标ID', type: 'string', required: true, group: '基础' },
{ key: 'act', label: '执行动画', type: 'string', required: true, group: '基础' },
{ key: 'TGroup', label: '目标群体', type: 'enum', enumRef: 'TGroup', required: true, group: '目标' },
{ key: 'DTType', label: '伤害类型', type: 'enum', enumRef: 'DTType', required: true, group: '目标' },
{ key: 'IType', label: '技能类型', type: 'enum', enumRef: 'IType', required: true, group: '目标' },
{ key: 'RType', label: '运行类型', type: 'enum', enumRef: 'RType', required: true, group: '目标' },
{ key: 'EType', label: '结束条件', type: 'enum', enumRef: 'EType', required: true, group: '目标' },
{ key: 'kind', label: '主效果', type: 'enum', enumRef: 'SkillKind', group: '目标' },
{ key: 'ap', label: 'ap(伤害%/护盾次)', type: 'number', required: true, group: '数值' },
{ key: 'gold', label: '金币值', type: 'number', group: '数值' },
{ key: 'hit_count', label: '可命中次数', type: 'number', required: true, group: '数值' },
{ key: 'hitcd', label: '伤害间隔', type: 'number', required: true, group: '数值' },
{ key: 'speed', label: '移动速度', type: 'number', required: true, group: '数值' },
{ key: 'ready', label: '前摇时间', type: 'number', required: true, group: '数值' },
{ key: 'with', label: '宽度', type: 'number', default: 0, group: '数值' },
{ key: 'readyAnm', label: '前摇动画', type: 'string', group: '动画' },
{ key: 'endAnm', label: '结束动画', type: 'string', group: '动画' },
{ key: 'DAnm', label: '命中动画ID', type: 'string', group: '动画' },
{ key: 'EAnm', label: '结束动画ID', type: 'number', group: '动画' },
{ key: 'crt', label: '额外暴击率', type: 'number', group: '高级' },
{ key: 'stun', label: '额外击晕概率', type: 'number', group: '高级' },
{ key: 'frz', label: '额外冰冻概率', type: 'number', group: '高级' },
{ key: 'bck', label: '额外击退概率', type: 'number', group: '高级' },
{ key: 'buff_type', label: 'Buff类型', type: 'enum', enumRef: 'Attrs', group: '高级' },
{ key: 'call_hero', label: '召唤英雄', type: 'ref', refTable: 'hero', group: '高级' },
{ key: 'time', label: '持续时间', type: 'number', group: '高级' },
{ key: 'bezier_start_y', label: '贝塞尔起始Y', type: 'number', group: '高级' },
{ key: 'bezier_mid_y', label: '贝塞尔中点Y', type: 'number', group: '高级' },
{ key: 'bezier_arc', label: '贝塞尔弧度', type: 'number', group: '高级' },
{ key: 'info', label: '描述', type: 'string', required: true, group: '基础' },
],
};

View File

@@ -0,0 +1,31 @@
export type TableId = 'hero' | 'skill' | 'field';
export type FieldType =
| 'number' | 'string' | 'boolean'
| 'enum' // 下拉,选项来自 enumRef
| 'ref' // 引用另一表 uuid
| 'speedExpr' // 攻速符号表达式
| 'skillMap' | 'triggerSlots' | 'fieldList' | 'reviveSlot' | 'overrides';
export interface FieldSchema {
key: string;
label: string;
type: FieldType;
required?: boolean;
default?: unknown;
group?: string;
enumRef?: string; // type='enum'
refTable?: TableId; // type='ref'
}
export interface IdSegment { label: string; min: number; max: number; }
export interface TableSchema {
id: TableId;
label: string;
sourceFile: string; // 相对 assets/script/game/common/config/
exportName: string; // 'HeroInfo' | 'SkillSet' | 'FieldSkillSet'
listExportName?: string; // 'HeroList'(仅 hero
idSegments: IdSegment[];
fields: FieldSchema[];
}

View File

@@ -0,0 +1,107 @@
import { RecordValue } from '../../io/recordValue';
import { TableId } from '../schema/types';
import { ENUMS } from '../schema/enums';
export type Severity = 'error' | 'warn';
export interface Issue {
tableId: TableId; key: string; fieldPath: string;
severity: Severity; code: string; message: string;
}
export interface ValidationContext {
hasSkill: (u: number) => boolean;
hasField: (u: number) => boolean;
hasHero: (u: number) => boolean;
heroListKeys: Set<string>;
}
const OVERRIDE_KEYS = new Set(['TGroup', 'ap', 'gold', 'hit_count', 'hitcd', 'crt', 'frz', 'stun', 'bck', 'buff_type', 'call_hero']);
const TRIGGER_ARRAY_KEYS = ['call', 'dead', 'fstart', 'fend', 'atking', 'atked'];
function num(v: RecordValue | undefined): number | undefined {
return v && v.kind === 'num' ? v.value : undefined;
}
function isKnownEnum(qualifier: string, member: string): boolean {
const def = ENUMS[qualifier] ?? Object.values(ENUMS).find(e => e.qualifier === qualifier);
return !!def && def.members.some(m => String(m.value) === member || m.label.split(' ').pop() === member);
}
export function validate(tableId: TableId, records: Map<string, RecordValue>, ctx: ValidationContext): Issue[] {
const issues: Issue[] = [];
// 跟踪 uuid 字段值(而非 Map 键)—— JS Map 会折叠重复键,但两条记录可能携带相同的 uuid 字段值。
const seenUuids = new Set<string>();
for (const [key, rec] of records) {
const push = (fieldPath: string, severity: Severity, code: string, message: string) =>
issues.push({ tableId, key, fieldPath, severity, code, message });
if (rec.kind !== 'obj') { push('', 'error', 'not-object', '记录不是对象'); continue; }
const p = rec.props;
const uVal = num(p['uuid']);
if (uVal !== undefined) {
const uKey = String(uVal);
if (seenUuids.has(uKey)) push('uuid', 'error', 'dup-uuid', `重复 uuid: ${uKey}`);
seenUuids.add(uKey);
}
// 必填(按表最小集合)
const required: Record<TableId, string[]> = {
hero: ['uuid', 'name', 'path', 'fac', 'lv', 'type', 'hp', 'ap', 'skills', 'info'],
skill: ['uuid', 'name', 'sp_name', 'icon', 'act', 'TGroup', 'DTType', 'IType', 'RType', 'EType', 'ap', 'hit_count', 'hitcd', 'speed', 'ready', 'info'],
field: ['uuid', 'name', 'icon', 'type', 'value', 'info'],
};
for (const f of required[tableId]) {
if (p[f] === undefined) push(f, 'error', 'missing-required', `缺少必填字段 ${f}`);
}
// 枚举值合法
for (const [f, v] of Object.entries(p)) {
if (v.kind === 'enumRef' && !isKnownEnum(v.qualifier, v.member)) {
push(f, 'error', 'bad-enum', `非法枚举值 ${v.qualifier}.${v.member}`);
}
}
if (tableId === 'hero') {
// 触发槽数组引用 + overrides 键
for (const tk of TRIGGER_ARRAY_KEYS) {
const arr = p[tk];
if (!arr || arr.kind !== 'arr') continue;
for (const it of arr.items) {
if (it.kind !== 'obj') continue;
const su = num(it.props['s_uuid']);
if (su !== undefined && !ctx.hasSkill(su)) push(tk, 'error', 'dangling-ref', `引用了不存在的技能 ${su}`);
const ov = it.props['overrides'];
if (ov && ov.kind === 'obj') {
for (const k of Object.keys(ov.props)) {
if (!OVERRIDE_KEYS.has(k)) push(`${tk}.overrides`, 'error', 'bad-override-key', `非法覆盖键 ${k}`);
}
}
}
}
// field 列表
const fl = p['field'];
if (fl && fl.kind === 'arr') {
for (const it of fl.items) { const u = num(it); if (u !== undefined && !ctx.hasField(u)) push('field', 'error', 'dangling-ref', `引用了不存在的驻场技能 ${u}`); }
}
// revive
const rv = p['revive'];
if (rv && rv.kind === 'obj') {
const su = num(rv.props['s_uuid']); if (su !== undefined && !ctx.hasSkill(su)) push('revive', 'error', 'dangling-ref', `引用了不存在的技能 ${su}`);
}
// call_hero技能表里的 ref 也用 hasHero
}
if (tableId === 'skill') {
const ch = num(p['call_hero']); if (ch !== undefined && !ctx.hasHero(ch)) push('call_hero', 'error', 'dangling-ref', `引用了不存在的英雄 ${ch}`);
}
// HeroList 一致性(仅 hero 表)
if (tableId === 'hero') {
const u = num(p['uuid']); const fac = p['fac'];
const isHero = fac && fac.kind === 'enumRef' && fac.member === 'HERO';
if (isHero && u !== undefined && !ctx.heroListKeys.has(String(u))) {
push('uuid', 'error', 'herolist-inconsistent', `英雄 ${u} 不在 HeroList 中`);
}
}
}
return issues;
}

View File

@@ -0,0 +1,7 @@
#app { color: var(--color-font-normal); font-size: 12px; }
.row { margin: 6px 0; }
button { margin-right: 8px; }
ul { list-style: none; padding: 0; max-height: 360px; overflow:auto; }
li { padding: 3px 6px; cursor: pointer; }
li:hover { background: var(--color-hover-bg); }
.err { color: var(--color-warn); }

View File

@@ -0,0 +1 @@
<div id="app" style="padding:12px;"></div>

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"experimentalDecorators": true,
"types": ["node"],
"lib": ["ES2020", "DOM"]
},
"include": ["src/**/*", "__tests__/**/*"]
}

View File

@@ -0,0 +1,93 @@
# 配置编辑器 Plan A — 验证证据
- **Plan**: `docs/superpowers/plans/2026-06-20-config-editor-foundation.md`
- **Branch**: `card0614`
- **Date**: 2026-06-21
- **Tasks implemented**: 113 (full plan)
---
## 自动化测试BLOCKING 门槛)
- **命令**: `npx tsx --test __tests__/*.test.ts`(在 `extensions/pixelhero-config-editor/` 下运行)
- **结果**: **36 / 36 PASS**0 fail0 skip
- **耗时**: ~560600ms
覆盖范围:
| 测试文件 | 用例数 | 覆盖点 |
|---|---|---|
| `recordValue.serializer.test.ts` | 8 | num/str/bool/enumRef/speed/arr/obj/raw 序列化;`serializeEntry` 单行与嵌套续行 |
| `parser.test.ts` | 8 | AST → RecordValuespeed 表达式识别enumRef fallbackraw 保留;`findExportObjectLiteral` |
| `tsConfigFile.test.ts` | 10 | load/getKeys/readpatch/add/delete`.bak` 备份语法校验阻断dirty/no-op savespeed+enumRef 往返不丢 |
| `validation.test.ts` | 8 | dup-uuid、missing-required、bad-enum、dangling-ref触发槽/field/revive、bad-override-key、herolist-inconsistent |
| `buildSkillDesc.test.ts` | 2 | 受击触发盾效、驻场光环描述 |
> **说明**Task 11`store.ts`)与 Task 12`index.ts`/面板)依赖 Cocos `Editor` 全局,无法在 Node 中 import/运行,故无单元测试——按计划设计。自动化门槛以纯逻辑层 36 用例为准。
---
## 构建BLOCKING 门槛)
- **命令**: `npm run build`(在 `extensions/pixelhero-config-editor/` 下运行)
- **结果**: 成功两个产物均生成esbuild 无错误。
| 产物 | 大小(字节) | 说明 |
|---|---|---|
| `dist/main.js` | 9,982,310 (~9.5 MB) | node/cjs内联 `typescript` 编译器 API`Editor` 为运行时全局(无 esbuild 报错) |
| `dist/panels/default.js` | 642,825 (~628 KB) | browser/iife内联 `vue/dist/vue.esm-bundler.js`(含运行时模板编译器) |
`dist/` 已被 `extensions/pixelhero-config-editor/.gitignore` 忽略,未提交。
### 构建过程中的必要修正(已记入 Task 12 commit message
Plan 给出的 `esbuild.config.mjs` 将面板入口设为 `platform: 'browser'``external: []`。但 Plan 的面板 `src/panels/default/index.ts` 在运行时用 `node:fs`/`node:path` 读取 `static/template/default/index.html``static/style/default/index.css`。esbuild 在 browser 平台下拒绝打包 `node:` 内建,报错:
```
X [ERROR] Could not resolve "node:fs"
X [ERROR] Could not resolve "node:path"
```
**修正**:将面板入口的 `external` 改为 `['node:fs', 'node:path']`。理由Cocos 面板进程在 Electron 渲染层运行,能访问 Node 内建vue 仍按 plan 打进 IIFE。`dist/main.js` 不受影响platform:'node' 本就允许 `node:` 内建)。
---
## 编辑器内集成ADVISORY — 待人工完成)
> 以下为人工在 Cocos Creator 3.8.6 中执行的验证清单。完成后请勾选并补截图路径。
- [ ] 1. 打开 Cocos Creator 3.8.6 项目 `d:\game\pixelheros`
- [ ] 2. 扩展管理器Extension Manager→ 项目扩展 → 启用/重载 `pixelhero-config-editor`;控制台无报错。
- [ ] 3. 主菜单 → 面板Panel→ 英雄技能配置Hero/Skill Config面板打开。
- [ ] 4. 表下拉切换 `英雄/怪物` → 列表显示真实 uuid应含 5011/5012/.../6106 等英雄条目)。
- [ ] 5. 表下拉切换 `技能` → 列表显示 6xxx 系列技能 uuid`驻场技能` → 7xxx 系列。
- [ ] 6. 点选 hero 表任一条 → 右侧 `<pre>` 显示结构化 JSON
- [ ] 6a. `fac` 字段为 `{ "kind": "enumRef", "qualifier": "FacSet", "member": "HERO" }`(或 `MON`)。
- [ ] 6b. 技能 `skills.<uuid>.cd``{ "kind": "speed", "level": "Slow3" }`(或其它 AtkSpeedLv 成员)。
- [ ] 6c. 数值/字符串字段为 `{ "kind": "num"|"str", "value": ... }`
- [ ] 7. 点选 skill 表任一条 → JSON 中 `TGroup`/`DTType`/`IType`/`RType`/`EType` 等字段均为 `enumRef``call_hero`(若有)为 `num``enumRef`
- [ ] 8. 结论:端到端 IPC 打通IO 层正确解析真实 `assets/script/game/common/config/heroSet.ts``SkillSet.ts`
- [ ] 9. 截图存档路径(填写):`production/qa/evidence/screenshots/config-editor-plan-a-YYYYMMDD.png`
---
## 提交记录
| Task | Commit SHA | 标题 |
|---|---|---|
| 11 | `e3102c63` | feat(config-editor): main-process store (in-memory truth + message impls + asset-db refresh) |
| 12 | `24b5c498` | feat(config-editor): extension entry + minimal Vue panel proving end-to-end IPC |
| 13 | (本提交) | test(config-editor): record Plan A verification evidence |
Tasks 110 由前序批次完成并提交SHAs 详见 `git log --oneline extensions/pixelhero-config-editor/`
---
## Definition of Done 对照
1. **扩展可被 Cocos 3.8.6 加载,菜单可打开面板** — 待人工确认(见上节步骤 23
2. **IO 层对真实配置正确解析(含 speed 表达式与枚举引用)** — 单元测试覆盖fixtures 镜像真实形态),人工确认真实文件见步骤 6。
3. **patch 一条英雄保存后:磁盘文件更新且为合法 TS其他条目与符号表达式原样保留`.bak` 已生成asset-db 已刷新** — TsConfigFile 单元测试覆盖文件行为patch/add/delete/save/.bak/语法校验asset-db refresh 在 `store.saveRecord` 中调用,待人工在编辑器内验证一次保存。
4. **校验层对各类非法数据正确报错error 级阻断保存)** — 8 个 validation 单元测试覆盖;`saveRecord` 在 error 时回滚不落盘。
5. **全部单元测试 PASSBLOCKING****36/36 PASS**
6. **未改动 `assets/script/game/**` 任何游戏运行时代码** — 本批次仅改动 `extensions/pixelhero-config-editor/**` 与本证据文件。