29 Commits

Author SHA1 Message Date
pan
1bb275607a refactor: 修改英雄图鉴和详情页的排序规则为按职业升序
调整英雄列表排序逻辑,从原有的按品质降序改为按职业升序,保持与养成进度解耦的稳定展示。
2026-09-24 15:45:46 +08:00
pan
95bf2a4e17 feat: 添加回合进度指示 UI 组件及刷新逻辑 2026-09-24 15:44:06 +08:00
pan
3f2989ab55 refactor: 移除性能监控面板相关代码
本次提交移除了 MissionComp 组件中所有性能监控相关的代码,包括:
- 性能监控面板的属性、初始化和更新逻辑
- 相关的导入依赖
- 未使用的变量和方法

这些代码主要用于显示内存、帧率等性能数据,移除后不会影响游戏核心功能,有助于简化代码结构和减少不必要的性能开销。
2026-09-24 14:04:46 +08:00
pan
577a62193c refactor: 重构无限关模式,移除章节概念,统一使用关卡-回合结构 2026-09-23 17:18:05 +08:00
pan
8f89dd11fa feat: 实现无限关卡 Boss 大招系统并重构关卡配置 2026-09-23 10:06:00 +08:00
pan
c10e24709d feat(skill): 新增闪电斩技能6103的动画与配置
1.  更新技能配置表中闪电斩的sp名称为atk6
2.  为atk2/atk3/atk5/atk6添加完整的动画帧与预制体
3.  调整各技能动画时长与攻击帧触发时机
2026-09-22 16:10:40 +08:00
pan
f92502f4f5 feat(skill): 新增召唤目标选择模式与技能优化
1. 新增SummonTMode枚举,支持同一敌人/随机敌人/去重三种召唤目标模式
2. 为多个技能prefab新增召唤参数配置字段
3. 重构索敌逻辑,支持按配置模式选择召唤目标
4. 优化技能伤害与动画配置,新增maxm7技能与动画资源
5. 修复部分技能的碰撞次数与偏移参数
2026-09-22 15:56:27 +08:00
pan
fa58f699d9 fix(hero): 修正技能debuff数值与技能描述
1. 更新多个技能的debuff_value数值为正向加成值
2. 将技能描述中的"前方达人"改为"前方敌人",并补充攻击力占位符
3. 同步更新代码配置中的技能参数与json配置一致
2026-09-22 11:23:55 +08:00
pan
90e8a4a284 feat: 添加三个新技能并更新英雄配置
1. 新增6013闪电、6014小火球、6015小飓风三个技能及对应预制件
2. 更新职业技能列表,为法师和辅助职业新增技能
3. 调整部分英雄技能配置、属性描述与英雄列表
4. 优化部分技能的伤害系数与效果参数
2026-09-22 11:15:49 +08:00
pan
39de30b9b4 refactor(hero config): 批量调整英雄技能与配置参数
1.  统一替换击退、debuff相关参数为暴击属性
2.  更新技能描述与英雄简介文本
3.  调整召唤物、buff数值与触发概率
4.  重构部分技能的生效逻辑与战场技能配置
5.  修正中毒效果的数值符号与参数配置
2026-09-22 09:33:47 +08:00
pan
8707316032 feat(hero-data): 批量更新多名英雄的配置数据
本次提交对多个英雄的角色路径、技能参数、攻击效果、被动光环等配置进行了更新:
1.  修改了易伤剑仙、甲虫领主、控血大师的角色资源路径
2.  调整了多名英雄的技能伤害倍率、控制效果参数
3.  新增/更新了多个英雄的普攻、大招效果与描述
4.  调整了部分英雄的光环配置与团队增益效果
5.  修正了技能的debuff/buff参数、触发概率与持续时长
2026-09-21 17:01:35 +08:00
pan
fdcd81c083 docs(server/data,script): 更新技能触发组文档与文本渲染逻辑
1. 补充技能触发组desc占位符使用说明
2. 新增触发概率与保底叠加值的参数传递
3. 优化技能文本渲染,避免概率描述重复展示
2026-09-21 16:03:10 +08:00
pan
cd9e7296a1 feat(trigger-editor): 添加触发组概率与保底配置功能
1. 新增触发组条目字段t_chance(触发概率)和pity_add(保底叠加),仅攻击/受击组生效
2. 为TriggerGroupEditor传入分组标识,动态显示概率配置表单项
3. 新增条目创建时继承同组末档概率参数的逻辑
4. 更新常量定义与文档说明,补充概率参数使用规则
5. 在前端展示和服务端导出中添加概率相关信息展示
2026-09-21 16:01:41 +08:00
pan
be6d941b90 feat(skill): add trigger probability and pity system for attack/attacked triggers
1. 新增技能触发概率配置与保底叠加机制,仅对攻击/受击触发生效
2. 前端编辑器新增t_chance概率与pity_add保底增量配置项
3. 技能描述文案支持展示触发概率
4. 运行时新增概率判定与保底缓存逻辑,兼容存量配置
2026-09-21 15:25:02 +08:00
pan
51256a2813 chore(hero-config): set all hero rarity to 1
Unify hero rarity to 1 across heroes.json and heroSet.ts.
Deactivate an element in heros.prefab.
2026-09-21 11:34:01 +08:00
panFD
0ea0434087 fix(talents prefab): adjust ui element position and scale
修正天赋界面预制体的元素位置与缩放参数,修复界面显示偏移问题
2026-09-20 22:09:43 +08:00
pan
1634f6a528 feat(talent-ui): 优化天赋界面展示逻辑与数据显示
1.  天赋总览页新增各层已投入点数展示
2.  天赋详情弹窗重构前置与层位校验UI:
    - 拆分层位需求与软前置展示模块
    - 优化进度显示逻辑,避免数值歧义
    - 修正前置等级展示为当前/需求格式
3.  更新相关注释与文档说明
2026-09-20 16:04:09 +08:00
pan
c183ae464a fix(HerosListComp): 补刷英雄升级红点在页面重新显示时
页面隐藏期间会错过HERO_GROWTH_UPDATE事件,重新显示时补刷所有卡片的可升级状态,首次onEnable早于start时遍历空数组无风险
2026-09-20 14:15:19 +08:00
pan
d140645aab refactor(talent): 重构天赋道具UI状态显示逻辑
1. 移除原MAX满级角标,替换为两种状态边框
2. 新增满级边框和已加点边框逻辑
3. 调整UI3布局参数优化显示
4. 更新预制件和注释文档
2026-09-20 10:55:27 +08:00
panFD
c610829661 chore(resources): 更新ui3.png资源文件
替换了assets/resources/gui目录下的ui3.png图片资源
2026-09-20 10:04:47 +08:00
pan
580a5ad131 style(ui5.plist): adjust button border top and bottom value
修正ui5.plist里的按钮上下边框数值,将22.5调整为22以统一显示样式
2026-09-20 09:14:33 +08:00
panFD
c282adcf3b style(talents prefab): adjust ui element position offsets
fine-tune the y-axis position and top layout offset of the talent panel prefab
2026-09-20 09:10:11 +08:00
panFD
4900911cfc refactor(TIBoxComp): 重构天赋文本渲染逻辑
1. 拆分天赋文本构建为精准渲染和详情组装两个方法
2. 改用天赋配置对象直接替换占位符,移除对getTalentDisplayText的依赖
3. 更新注释说明和导入依赖,修正文档中遗漏的TalentConfig配置项
4. 修复Lv0等级的天赋预览逻辑,改为按1级数值展示引导
2026-09-19 22:19:22 +08:00
panFD
45e3e8712b feat(ui): 新增天赋详情弹窗功能
重构天赋加点逻辑:将卡片点击校验与加点弹窗逻辑内聚到TIBoxComp中,TalentsComp仅负责打开弹窗。新增天赋详情弹窗,支持展示天赋信息、前置条件校验,并根据玩家状态动态控制加点按钮显隐。
2026-09-19 22:19:12 +08:00
panFD
f4150bd33c fix(talent): 调整天赋项UI显示逻辑与样式
1.  修改天赋项锁定状态判断逻辑,不再因Lv0显示锁遮罩
2.  更新节点注释与tooltip描述
3.  调整预制体布局与精灵资源配置
2026-09-19 15:34:35 +08:00
panFD
707e25e73f refactor: 重构天赋系统与技能触发逻辑
1.  重构天赋系统架构:将天赋加成与触发修正统一收敛到 HeroAttrsComp.applyTalent
2.  新增天赋触发修正字段:talent_t_num_reduce 与 talent_extra_trigger_chance
3.  重构天赋加点逻辑:统一 1 点/级,新增软前置校验
4.  优化天赋UI组件:改为编辑器静态配置驱动,重构层位展示逻辑
5.  修复技能触发判定:在 SkillTriggerHelper 中应用天赋触发修正
6.  调整天赋配置结构:改为 5 层递进解锁,新增 L4/L5 专属触发修正配置
2026-09-19 11:20:55 +08:00
panFD
f53c4293df fix(talents UI): 修复天赋界面预制件配置异常
更新了天赋界面prefab的节点ID映射、位置参数,新增了大量组件属性覆盖配置,修复了重置按钮绑定缺失和界面元素显示异常问题
2026-09-18 23:12:53 +08:00
panFD
f67f8a4aa0 refactor(gui, talent): 调整天赋卡片UI布局与边框参数
feat(talent): 新增天赋面板经验数值与点数统计显示

refactor(hero): 重构英雄列表与详情弹窗排序逻辑

fix(window): 修复弹窗默认激活状态与位置偏移问题
2026-09-18 23:11:42 +08:00
panFD
16854ab1b8 refactor(heroInfo): 重构英雄信息UI展示逻辑
1. 移除废弃的fragment_node节点相关代码与引用
2. 调整英雄列表和卡片的布局尺寸与间距
3. 优化碎片数量显示的绑定方式
4. 删除无用的bak备份文件
2026-09-18 11:53:15 +08:00
74 changed files with 38743 additions and 27272 deletions

View File

@@ -1781,7 +1781,7 @@
"propertyPath": [
"_active"
],
"value": true
"value": false
},
{
"__type__": "cc.TargetInfo",
@@ -1800,7 +1800,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": -30,
"y": 0,
"z": 0
}
},
@@ -1821,7 +1821,7 @@
"value": {
"__type__": "cc.Size",
"width": 150,
"height": 95.5
"height": 35.5
}
},
{
@@ -1838,7 +1838,7 @@
"propertyPath": [
"_active"
],
"value": true
"value": false
},
{
"__type__": "cc.TargetInfo",
@@ -1869,7 +1869,7 @@
"propertyPath": [
"_top"
],
"value": -8.2625
"value": 6.737500000000001
},
{
"__type__": "cc.TargetInfo",
@@ -1885,7 +1885,7 @@
"propertyPath": [
"_bottom"
],
"value": -7.237500000000001
"value": 7.762499999999999
},
{
"__type__": "CCPropertyOverrideInfo",
@@ -1933,7 +1933,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 2.75,
"y": 17.75,
"z": 0
}
},
@@ -3659,7 +3659,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -30,
"y": 0,
"z": 0
},
"_lrot": {
@@ -3766,7 +3766,7 @@
"__id__": 202
}
],
"_active": true,
"_active": false,
"_components": [
{
"__id__": 208
@@ -4202,7 +4202,7 @@
"__id__": 220
}
],
"_active": true,
"_active": false,
"_components": [
{
"__id__": 226
@@ -4217,7 +4217,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 2.75,
"y": 17.75,
"z": 0
},
"_lrot": {
@@ -4635,7 +4635,7 @@
"_contentSize": {
"__type__": "cc.Size",
"width": 150,
"height": 95.5
"height": 35.5
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -4664,8 +4664,8 @@
"_target": null,
"_left": 0,
"_right": 0,
"_top": -8.2625,
"_bottom": -7.237500000000001,
"_top": 21.7375,
"_bottom": 22.7625,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,

View File

@@ -11,29 +11,32 @@
"speed": 1,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 0.6,
"_duration": 0.65,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
},
{
"__id__": 7
}
],
"_exoticAnimation": null,
"_events": [
{
"frame": 0.2,
"frame": 0.3,
"func": "",
"params": []
},
{
"frame": 0.2,
"frame": 0.3,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 7
"__id__": 13
},
"_auxiliaryCurveEntries": []
},
@@ -90,7 +93,8 @@
0.4,
0.45,
0.5,
0.55
0.55,
0.6
],
"_values": [
{
@@ -140,7 +144,98 @@
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@5d13a",
"__expectedType__": "cc.SpriteFrame"
}
},
null
]
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 8
},
"proxy": null
},
"_channel": {
"__id__": 11
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 9
},
{
"__id__": 10
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "slash-horizontal1"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 12
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0.15,
0.2,
0.25,
0.3,
0.35,
0.4,
0.45,
0.5,
0.55,
0.6
],
"_values": [
null,
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d5755",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d5755",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@34609",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@35b2b",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e377",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@cb59a",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@5adf0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6c6f6",
"__expectedType__": "cc.SpriteFrame"
},
null
]
},
{

View File

@@ -11,29 +11,32 @@
"speed": 1,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 0.4583333333333333,
"_duration": 0.5833333333333334,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
},
{
"__id__": 7
}
],
"_exoticAnimation": null,
"_events": [
{
"frame": 0.08333333333333333,
"frame": 0.16666666666666666,
"func": "",
"params": []
},
{
"frame": 0.08333333333333333,
"frame": 0.16666666666666666,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 7
"__id__": 13
},
"_auxiliaryCurveEntries": []
},
@@ -89,7 +92,8 @@
0.2916666666666667,
0.3333333333333333,
0.375,
0.4166666666666667
0.4166666666666667,
0.4583333333333333
],
"_values": [
{
@@ -135,7 +139,95 @@
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@21b99",
"__expectedType__": "cc.SpriteFrame"
}
},
null
]
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 8
},
"proxy": null
},
"_channel": {
"__id__": 11
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 9
},
{
"__id__": 10
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "slash-horizontal1"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 12
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0,
0.20833333333333334,
0.25,
0.2916666666666667,
0.3333333333333333,
0.375,
0.4166666666666667,
0.4583333333333333,
0.5,
0.5416666666666666
],
"_values": [
null,
null,
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d5755",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@34609",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@35b2b",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0e377",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@cb59a",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@5adf0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6c6f6",
"__expectedType__": "cc.SpriteFrame"
},
null
]
},
{

View File

@@ -11,29 +11,32 @@
"speed": 1,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 0.4166666666666667,
"_duration": 0.7083333333333334,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
},
{
"__id__": 7
}
],
"_exoticAnimation": null,
"_events": [
{
"frame": 0.08333333333333333,
"frame": 0.16666666666666666,
"func": "",
"params": []
},
{
"frame": 0.08333333333333333,
"frame": 0.16666666666666666,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 7
"__id__": 13
},
"_auxiliaryCurveEntries": []
},
@@ -88,7 +91,8 @@
0.25,
0.2916666666666667,
0.3333333333333333,
0.375
0.375,
0.4166666666666667
],
"_values": [
{
@@ -130,7 +134,115 @@
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@daa57",
"__expectedType__": "cc.SpriteFrame"
}
},
null
]
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 8
},
"proxy": null
},
"_channel": {
"__id__": 11
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 9
},
{
"__id__": 10
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "fire-slash-horizontal1"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 12
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0,
0.16666666666666666,
0.20833333333333334,
0.25,
0.2916666666666667,
0.3333333333333333,
0.375,
0.4166666666666667,
0.4583333333333333,
0.5,
0.5416666666666666,
0.5833333333333334,
0.625,
0.6666666666666666
],
"_values": [
null,
null,
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@38937",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@31859",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@33f97",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@0ff1b",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@4953c",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@cb80d",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d0b5c",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@4ce52",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@90cfe",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@17d94",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@64baf",
"__expectedType__": "cc.SpriteFrame"
},
null
]
},
{

View File

@@ -11,39 +11,32 @@
"speed": 1,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 0.625,
"_duration": 0.75,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
},
{
"__id__": 7
}
],
"_exoticAnimation": null,
"_events": [
{
"frame": 0.20833333333333334,
"frame": 0.375,
"func": "",
"params": []
},
{
"frame": 0.20833333333333334,
"func": "atk",
"params": []
},
{
"frame": 0.4583333333333333,
"func": "",
"params": []
},
{
"frame": 0.4583333333333333,
"frame": 0.375,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 7
"__id__": 13
},
"_auxiliaryCurveEntries": []
},
@@ -103,7 +96,8 @@
0.4583333333333333,
0.5,
0.5416666666666666,
0.5833333333333334
0.5833333333333334,
0.625
],
"_values": [
{
@@ -165,7 +159,115 @@
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@1b470",
"__expectedType__": "cc.SpriteFrame"
}
},
null
]
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 8
},
"proxy": null
},
"_channel": {
"__id__": 11
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 9
},
{
"__id__": 10
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "electric-slash-horizontal1"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 12
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0,
0.20833333333333334,
0.25,
0.2916666666666667,
0.3333333333333333,
0.375,
0.4166666666666667,
0.4583333333333333,
0.5,
0.5416666666666666,
0.5833333333333334,
0.625,
0.6666666666666666,
0.7083333333333334
],
"_values": [
null,
null,
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@f6490",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6c874",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@5b5e5",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@5cc03",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@161b8",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@01fef",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@70d19",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@34499",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@9ec10",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@9e7f6",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@99406",
"__expectedType__": "cc.SpriteFrame"
},
null
]
},
{

View File

@@ -0,0 +1,921 @@
[
{
"__type__": "cc.AnimationClip",
"_name": "fb-8",
"_objFlags": 0,
"__editorExtras__": {
"embeddedPlayerGroups": []
},
"_native": "",
"sample": 20,
"speed": 1,
"wrapMode": 1,
"enableTrsBlending": false,
"_duration": 1.4,
"_hash": 500763545,
"_tracks": [
{
"__id__": 1
},
{
"__id__": 7
},
{
"__id__": 13
},
{
"__id__": 24
}
],
"_exoticAnimation": null,
"_events": [
{
"frame": 0,
"func": "",
"params": []
},
{
"frame": 0,
"func": "atk",
"params": []
},
{
"frame": 0.6,
"func": "",
"params": []
},
{
"frame": 0.6,
"func": "atk",
"params": []
},
{
"frame": 1.2,
"func": "",
"params": []
},
{
"frame": 1.2,
"func": "atk",
"params": []
}
],
"_embeddedPlayers": [],
"_additiveSettings": {
"__id__": 35
},
"_auxiliaryCurveEntries": []
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 2
},
"proxy": null
},
"_channel": {
"__id__": 5
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 3
},
{
"__id__": 4
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "Node"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 6
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0,
0.05,
0.1,
0.15,
0.2,
0.25,
0.3,
0.35,
0.4,
0.45,
0.5,
0.55,
0.6,
0.65,
0.7,
0.75,
0.8,
0.85,
0.9,
0.95,
1,
1.05,
1.1,
1.15,
1.2,
1.25,
1.3,
1.35
],
"_values": [
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@2ac32",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@a38c9",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@005f7",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d585f",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@740a0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6b34e",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@2ac32",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@a38c9",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@005f7",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d585f",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@740a0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6b34e",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@2ac32",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@a38c9",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@005f7",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d585f",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@740a0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6b34e",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@2ac32",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@a38c9",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@005f7",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d585f",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@740a0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6b34e",
"__expectedType__": "cc.SpriteFrame"
}
]
},
{
"__type__": "cc.animation.ObjectTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 8
},
"proxy": null
},
"_channel": {
"__id__": 11
}
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 9
},
{
"__id__": 10
},
"spriteFrame"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "Node-001"
},
{
"__type__": "cc.animation.ComponentPath",
"component": "cc.Sprite"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 12
}
},
{
"__type__": "cc.ObjectCurve",
"_times": [
0,
0.05,
0.1,
0.15,
0.2,
0.25,
0.3,
0.35,
0.4,
0.45,
0.5,
0.55,
0.6,
0.65,
0.7,
0.75,
0.8,
0.85,
0.9,
0.95,
1,
1.05,
1.1,
1.15,
1.2,
1.25,
1.3,
1.35
],
"_values": [
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@2ac32",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@a38c9",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@005f7",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d585f",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@740a0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6b34e",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@2ac32",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@a38c9",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@005f7",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d585f",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@740a0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6b34e",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@2ac32",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@a38c9",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@005f7",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d585f",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@740a0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6b34e",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@2ac32",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@a38c9",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@005f7",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@d585f",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@740a0",
"__expectedType__": "cc.SpriteFrame"
},
{
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@6b34e",
"__expectedType__": "cc.SpriteFrame"
}
]
},
{
"__type__": "cc.animation.VectorTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 14
},
"proxy": null
},
"_channels": [
{
"__id__": 16
},
{
"__id__": 18
},
{
"__id__": 20
},
{
"__id__": 22
}
],
"_nComponents": 3
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 15
},
"position"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "Node"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 17
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.65,
1.35
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -70,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 70,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -70,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 19
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.3,
0.65,
1,
1.35
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -60,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -70,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -60,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -50,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -60,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 21
}
},
{
"__type__": "cc.RealCurve",
"_times": [],
"_values": [],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 23
}
},
{
"__type__": "cc.RealCurve",
"_times": [],
"_values": [],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.VectorTrack",
"_binding": {
"__type__": "cc.animation.TrackBinding",
"path": {
"__id__": 25
},
"proxy": null
},
"_channels": [
{
"__id__": 27
},
{
"__id__": 29
},
{
"__id__": 31
},
{
"__id__": 33
}
],
"_nComponents": 3
},
{
"__type__": "cc.animation.TrackPath",
"_paths": [
{
"__id__": 26
},
"position"
]
},
{
"__type__": "cc.animation.HierarchyPath",
"path": "Node-001"
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 28
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.65,
1.35
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 70,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -70,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 70,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 30
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.3,
0.65,
1,
1.35
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -60,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -50,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -60,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -70,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": -60,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 32
}
},
{
"__type__": "cc.RealCurve",
"_times": [
0,
0.65,
1.35
],
"_values": [
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
},
{
"__type__": "cc.RealKeyframeValue",
"interpolationMode": 0,
"tangentWeightMode": 0,
"value": 0,
"rightTangent": 0,
"rightTangentWeight": 1,
"leftTangent": 0,
"leftTangentWeight": 1,
"easingMethod": 0,
"__editorExtras__": {
"broken": null
}
}
],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.animation.Channel",
"_curve": {
"__id__": 34
}
},
{
"__type__": "cc.RealCurve",
"_times": [],
"_values": [],
"preExtrapolation": 1,
"postExtrapolation": 1
},
{
"__type__": "cc.AnimationClipAdditiveSettings",
"enabled": false,
"refClip": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "2.0.3",
"importer": "animation-clip",
"imported": true,
"uuid": "b0bec9a1-c3c7-412f-b6de-e27ec78dcc86",
"files": [
".cconb"
],
"subMetas": {},
"userData": {
"name": "fb-8"
}
}

View File

@@ -20,28 +20,31 @@
"_children": [
{
"__id__": 2
},
{
"__id__": 8
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
},
{
"__id__": 18
},
{
"__id__": 20
},
{
"__id__": 22
}
],
"_prefab": {
"__id__": 18
"__id__": 24
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -211,6 +214,139 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "slash-horizontal1",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 9
},
{
"__id__": 11
}
],
"_prefab": {
"__id__": 13
},
"_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": 2,
"y": 2,
"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__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 10
},
"_contentSize": {
"__type__": "cc.Size",
"width": 95,
"height": 48
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "0aybWnCBJDBb3WOXwo/YxC"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 12
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": null,
"_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": "45iYWOPyBCNKkmXE+dmADF"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "bdI0RFo3VCNKhgFojxldzA",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -221,7 +357,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 9
"__id__": 15
},
"_contentSize": {
"__type__": "cc.Size",
@@ -249,7 +385,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 11
"__id__": 17
},
"atk_x": 20,
"atk_y": 40,
@@ -261,6 +397,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 1,
"summon_target_mode": 0,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 0,
"_id": ""
},
@@ -278,7 +418,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 13
"__id__": 19
},
"enabledContactListener": true,
"bullet": false,
@@ -312,7 +452,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 15
"__id__": 21
},
"tag": 0,
"_group": 1,
@@ -346,7 +486,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 17
"__id__": 23
},
"playOnLoad": true,
"_clips": [

View File

@@ -20,28 +20,31 @@
"_children": [
{
"__id__": 2
},
{
"__id__": 8
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
},
{
"__id__": 18
},
{
"__id__": 20
},
{
"__id__": 22
}
],
"_prefab": {
"__id__": 18
"__id__": 24
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -211,6 +214,139 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "slash-horizontal1",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 9
},
{
"__id__": 11
}
],
"_prefab": {
"__id__": 13
},
"_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": 2,
"y": 2,
"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__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 10
},
"_contentSize": {
"__type__": "cc.Size",
"width": 95,
"height": 48
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "82TGDP+iVBv4P1whT99OBW"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 12
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": null,
"_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": "daC3XwzZFG4JHd5pqsR70r"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "e8yfxZXCJCU6wnX0/ttDjH",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -221,7 +357,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 9
"__id__": 15
},
"_contentSize": {
"__type__": "cc.Size",
@@ -249,7 +385,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 11
"__id__": 17
},
"atk_x": 20,
"atk_y": 40,
@@ -261,6 +397,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 1,
"summon_target_mode": 0,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 0,
"_id": ""
},
@@ -278,7 +418,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 13
"__id__": 19
},
"enabledContactListener": true,
"bullet": false,
@@ -312,7 +452,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 15
"__id__": 21
},
"tag": 0,
"_group": 1,
@@ -346,7 +486,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 17
"__id__": 23
},
"playOnLoad": true,
"_clips": [

View File

@@ -20,28 +20,31 @@
"_children": [
{
"__id__": 2
},
{
"__id__": 8
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
},
{
"__id__": 18
},
{
"__id__": 20
},
{
"__id__": 22
}
],
"_prefab": {
"__id__": 18
"__id__": 24
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -173,7 +176,7 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@13b06",
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@7e6d7",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
@@ -211,6 +214,139 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "fire-slash-horizontal1",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 9
},
{
"__id__": 11
}
],
"_prefab": {
"__id__": 13
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 29.777,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 2,
"y": 2,
"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__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 10
},
"_contentSize": {
"__type__": "cc.Size",
"width": 95,
"height": 48
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "61GNVJPatIJoTFQpCuL1tc"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 12
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": null,
"_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": "64tuHxFsxBMK3c6Rl+GQll"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "05Mo/yQRtK5YAX1chbWL76",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -221,7 +357,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 9
"__id__": 15
},
"_contentSize": {
"__type__": "cc.Size",
@@ -249,7 +385,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 11
"__id__": 17
},
"atk_x": 20,
"atk_y": 40,
@@ -261,6 +397,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 1,
"summon_target_mode": 0,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 0,
"_id": ""
},
@@ -278,7 +418,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 13
"__id__": 19
},
"enabledContactListener": true,
"bullet": false,
@@ -312,7 +452,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 15
"__id__": 21
},
"tag": 0,
"_group": 1,
@@ -346,7 +486,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 17
"__id__": 23
},
"playOnLoad": true,
"_clips": [

View File

@@ -20,28 +20,31 @@
"_children": [
{
"__id__": 2
},
{
"__id__": 8
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
},
{
"__id__": 18
},
{
"__id__": 20
},
{
"__id__": 22
}
],
"_prefab": {
"__id__": 18
"__id__": 24
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -211,6 +214,142 @@
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "electric-slash-horizontal1",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 9
},
{
"__id__": 11
}
],
"_prefab": {
"__id__": 13
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 25.352,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 2,
"y": 2,
"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__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 10
},
"_contentSize": {
"__type__": "cc.Size",
"width": 95,
"height": 48
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "598QTUIYhAD5NxyPBcSrlV"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 12
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@f6490",
"__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": "79xdyqEqhFdZmXNHgUvUaA"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "d1hgL10YVE8a0CvDCyOdTd",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
@@ -221,7 +360,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 9
"__id__": 15
},
"_contentSize": {
"__type__": "cc.Size",
@@ -249,7 +388,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 11
"__id__": 17
},
"atk_x": 20,
"atk_y": 40,
@@ -261,6 +400,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 1,
"summon_target_mode": 0,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 0,
"_id": ""
},
@@ -278,7 +421,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 13
"__id__": 19
},
"enabledContactListener": true,
"bullet": false,
@@ -312,7 +455,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 15
"__id__": 21
},
"tag": 0,
"_group": 1,
@@ -346,7 +489,7 @@
},
"_enabled": true,
"__prefab": {
"__id__": 17
"__id__": 23
},
"playOnLoad": true,
"_clips": [

View File

@@ -35,10 +35,13 @@
},
{
"__id__": 14
},
{
"__id__": 16
}
],
"_prefab": {
"__id__": 16
"__id__": 18
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -92,8 +95,8 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 6.77,
"y": 0,
"x": 0,
"y": -60,
"z": 0
},
"_lrot": {
@@ -105,8 +108,8 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.3,
"y": 0.3,
"x": 1.4,
"y": 2,
"z": 1
},
"_mobility": 0,
@@ -133,13 +136,13 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 90,
"height": 90
"width": 128,
"height": 128
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
"y": 0
},
"_id": ""
},
@@ -170,7 +173,7 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@4d4df",
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
@@ -222,13 +225,13 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 30,
"height": 40
"width": 20,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
"y": 0
},
"_id": ""
},
@@ -248,13 +251,26 @@
"__prefab": {
"__id__": 11
},
"atk_x": 10,
"atk_y": 15,
"atk_x": 0,
"atk_y": 0,
"runType": 3,
"endType": 0,
"speed": 720,
"time": 0,
"distance": 0,
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 1,
"summon_target_mode": 0,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "60LInmZXxDtKu79AshRG9j"
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
},
{
"__type__": "cc.RigidBody2D",
@@ -288,7 +304,7 @@
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "e1yBA625RLwLhzo6bLYW7j"
"fileId": "57cr7S0YpK4oEZCavwXtIX"
},
{
"__type__": "cc.BoxCollider2D",
@@ -315,14 +331,43 @@
},
"_size": {
"__type__": "cc.Size",
"width": 30,
"height": 40
"width": 20,
"height": 100
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "43ZDJfXX9AX73gyytKQZWm"
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"playOnLoad": true,
"_clips": [
{
"__uuid__": "45605d53-4dca-4529-819d-4a0d98c3a47c",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "45605d53-4dca-4529-819d-4a0d98c3a47c",
"__expectedType__": "cc.AnimationClip"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "20U0hP/NZErJJXRCEib1lq"
},
{
"__type__": "cc.PrefabInfo",

View File

@@ -2,7 +2,7 @@
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "fc32700f-bf4a-47ce-b619-6497b7280fde",
"uuid": "7edb8215-1344-4215-8172-7a50a72da8bb",
"files": [
".json"
],

View File

@@ -35,10 +35,13 @@
},
{
"__id__": 14
},
{
"__id__": 16
}
],
"_prefab": {
"__id__": 16
"__id__": 18
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -92,7 +95,7 @@
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 6.77,
"x": 0,
"y": 0,
"z": 0
},
@@ -100,13 +103,13 @@
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
"z": 0.706489444943837,
"w": 0.7077235789366842
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.3,
"y": 0.3,
"x": -1,
"y": 1,
"z": 1
},
"_mobility": 0,
@@ -115,7 +118,7 @@
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
"z": 89.9
},
"_id": ""
},
@@ -133,8 +136,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 90,
"height": 90
"width": 85,
"height": 119
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -170,7 +173,7 @@
"a": 255
},
"_spriteFrame": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@179ff",
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@59ec8",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
@@ -222,8 +225,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 30,
"height": 40
"width": 50,
"height": 50
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -248,13 +251,25 @@
"__prefab": {
"__id__": 11
},
"atk_x": 10,
"atk_y": 15,
"atk_x": 20,
"atk_y": 20,
"runType": 0,
"endType": 2,
"speed": 720,
"time": 0,
"distance": 0,
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 1,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 100,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "60LInmZXxDtKu79AshRG9j"
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
},
{
"__type__": "cc.RigidBody2D",
@@ -288,7 +303,7 @@
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "e1yBA625RLwLhzo6bLYW7j"
"fileId": "57cr7S0YpK4oEZCavwXtIX"
},
{
"__type__": "cc.BoxCollider2D",
@@ -315,14 +330,43 @@
},
"_size": {
"__type__": "cc.Size",
"width": 30,
"height": 40
"width": 50,
"height": 150
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "43ZDJfXX9AX73gyytKQZWm"
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"playOnLoad": true,
"_clips": [
{
"__uuid__": "45385cba-7334-4ff4-a548-578262417f58",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "45385cba-7334-4ff4-a548-578262417f58",
"__expectedType__": "cc.AnimationClip"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "20U0hP/NZErJJXRCEib1lq"
},
{
"__type__": "cc.PrefabInfo",

View File

@@ -2,7 +2,7 @@
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "5a0c6727-c741-4a33-b6f2-b2c56e3fc540",
"uuid": "7c7ff7a4-17d5-4c4a-8fee-68b73af250e5",
"files": [
".json"
],

View File

@@ -0,0 +1,519 @@
[
{
"__type__": "cc.Prefab",
"_name": "m5",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "m5",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
},
{
"__id__": 8
}
],
"_active": true,
"_components": [
{
"__id__": 14
},
{
"__id__": 16
},
{
"__id__": 18
},
{
"__id__": 20
},
{
"__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": "Node",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 7
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": -0.5,
"y": 0.5,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 277,
"height": 250
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "93pNmvtQlLSqtTgIepyEmA"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 6
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@260b5",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "12eKc8gltBz50frJCS5+ww"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "Node-001",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 9
},
{
"__id__": 11
}
],
"_prefab": {
"__id__": 13
},
"_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.5,
"y": 0.5,
"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__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 10
},
"_contentSize": {
"__type__": "cc.Size",
"width": 122,
"height": 68
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "4blcTDlixIRI3qtyERLs2E"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 12
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": null,
"_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": "e6QtagQwZA/50lsdWvOjb0"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "ed54OCubBD853t6+5pUPLQ",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 15
},
"_contentSize": {
"__type__": "cc.Size",
"width": 50,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
},
{
"__type__": "57aabs7TE1J5obTAZczc+64",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"atk_x": 0,
"atk_y": 0,
"runType": 0,
"endType": 2,
"speed": 720,
"time": 0,
"distance": 0,
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 1,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
},
{
"__type__": "cc.RigidBody2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 19
},
"enabledContactListener": true,
"bullet": false,
"awakeOnLoad": true,
"_group": 1,
"_type": 1,
"_allowSleep": false,
"_gravityScale": 1,
"_linearDamping": 0,
"_angularDamping": 0,
"_linearVelocity": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_angularVelocity": 0,
"_fixedRotation": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "57cr7S0YpK4oEZCavwXtIX"
},
{
"__type__": "cc.BoxCollider2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 21
},
"tag": 0,
"_group": 1,
"_density": 1,
"_sensor": true,
"_friction": 0.2,
"_restitution": 0,
"_offset": {
"__type__": "cc.Vec2",
"x": 0,
"y": 50
},
"_size": {
"__type__": "cc.Size",
"width": 50,
"height": 100
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 23
},
"playOnLoad": true,
"_clips": [
{
"__uuid__": "a71379d4-b14d-4070-96ee-e7c889f25bbd",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "a71379d4-b14d-4070-96ee-e7c889f25bbd",
"__expectedType__": "cc.AnimationClip"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "20U0hP/NZErJJXRCEib1lq"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "8dc65122-222f-4bd3-b89c-62e7f8eefe78",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "m5"
}
}

View File

@@ -293,6 +293,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 4,
"summon_target_mode": 2,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 100,
"_id": ""
},

View File

@@ -293,6 +293,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 6,
"summon_target_mode": 2,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 100,
"_id": ""
},

View File

@@ -261,6 +261,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 3,
"summon_target_mode": 2,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 100,
"_id": ""
},

View File

@@ -397,6 +397,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 3,
"summon_target_mode": 0,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0.2,
"height_offset": 0,
"_id": ""
},

View File

@@ -96,7 +96,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -30,
"y": -90,
"z": 0
},
"_lrot": {
@@ -108,7 +108,7 @@
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"x": 1.5,
"y": 2,
"z": 1
},
@@ -261,6 +261,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 5,
"summon_target_mode": 2,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 0,
"_id": ""
},

View File

@@ -261,6 +261,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 3,
"summon_target_mode": 2,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0.5,
"height_offset": 100,
"_id": ""
},

View File

@@ -261,6 +261,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 3,
"summon_target_mode": 2,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 100,
"_id": ""
},

View File

@@ -261,6 +261,10 @@
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 3,
"summon_target_mode": 2,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0,
"height_offset": 100,
"_id": ""
},

View File

@@ -0,0 +1,526 @@
[
{
"__type__": "cc.Prefab",
"_name": "maxm7",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "maxm7",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
},
{
"__id__": 8
}
],
"_active": true,
"_components": [
{
"__id__": 14
},
{
"__id__": 16
},
{
"__id__": 18
},
{
"__id__": 20
},
{
"__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": "Node",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 7
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -30,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 2,
"y": 2,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 128,
"height": 128
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "93pNmvtQlLSqtTgIepyEmA"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 6
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "12eKc8gltBz50frJCS5+ww"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "3arqAMBz1MvoXBzeDaL5M/",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "Node-001",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 9
},
{
"__id__": 11
}
],
"_prefab": {
"__id__": 13
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": -30,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 2,
"y": 2,
"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__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 10
},
"_contentSize": {
"__type__": "cc.Size",
"width": 128,
"height": 128
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "cbS/mBx7hFKK1RlGcpOgD1"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 8
},
"_enabled": true,
"__prefab": {
"__id__": 12
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20",
"__expectedType__": "cc.SpriteAtlas"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "eah7cf5S5FCrmP7CHX5e38"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "f9cYdo2HdAp7KWposjmIcg",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 15
},
"_contentSize": {
"__type__": "cc.Size",
"width": 200,
"height": 100
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.3
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "63NP9yq3hEUKD/OZZZ5t7x"
},
{
"__type__": "57aabs7TE1J5obTAZczc+64",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"atk_x": 0,
"atk_y": 0,
"runType": 3,
"endType": 0,
"speed": 720,
"time": 0,
"distance": 0,
"forceStartX": -1,
"forceStartY": -1,
"summon_count": 1,
"summon_target_mode": 2,
"summon_x_offset": 0,
"summon_y_offset": 0,
"summon_interval": 0.5,
"height_offset": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "35nW0iQNBH2bqkWAr3MVkQ"
},
{
"__type__": "cc.RigidBody2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 19
},
"enabledContactListener": true,
"bullet": false,
"awakeOnLoad": true,
"_group": 1,
"_type": 1,
"_allowSleep": false,
"_gravityScale": 1,
"_linearDamping": 0,
"_angularDamping": 0,
"_linearVelocity": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_angularVelocity": 0,
"_fixedRotation": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "57cr7S0YpK4oEZCavwXtIX"
},
{
"__type__": "cc.BoxCollider2D",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 21
},
"tag": 0,
"_group": 1,
"_density": 1,
"_sensor": true,
"_friction": 0.2,
"_restitution": 0,
"_offset": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_size": {
"__type__": "cc.Size",
"width": 200,
"height": 100
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "ebw5mObxNGGKxnJXDgX5gl"
},
{
"__type__": "cc.Animation",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 23
},
"playOnLoad": true,
"_clips": [
{
"__uuid__": "b0bec9a1-c3c7-412f-b6de-e27ec78dcc86",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "b0bec9a1-c3c7-412f-b6de-e27ec78dcc86",
"__expectedType__": "cc.AnimationClip"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "20U0hP/NZErJJXRCEib1lq"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "2316d214-bb61-434e-a056-48793e72dcce",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "maxm7"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "e2394eb1-27f0-4b7c-ace3-cfb3453eef99",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "TIBox"
}
}

View File

@@ -257,7 +257,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 75,
"y": 80,
"z": 0
}
},
@@ -338,8 +338,8 @@
],
"value": {
"__type__": "cc.Size",
"width": 160.26666666666668,
"height": 200
"width": 200.26666666666665,
"height": 213.33333333333334
}
},
{
@@ -358,8 +358,8 @@
],
"value": {
"__type__": "cc.Size",
"width": 160.26666666666668,
"height": 200
"width": 200.26666666666665,
"height": 213.33333333333334
}
},
{
@@ -379,8 +379,8 @@
],
"value": {
"__type__": "cc.Size",
"width": 160.26666666666668,
"height": 200
"width": 200.26666666666665,
"height": 213.33333333333334
}
},
{
@@ -400,7 +400,7 @@
],
"value": {
"__type__": "cc.Size",
"width": 152.26666666666668,
"width": 192.26666666666665,
"height": 150
}
},
@@ -422,7 +422,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 21,
"y": 27.66666666666667,
"z": 0
}
},
@@ -443,7 +443,7 @@
],
"value": {
"__type__": "cc.Size",
"width": 152.26666666666668,
"width": 192.26666666666665,
"height": 24
}
},
@@ -465,7 +465,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 84,
"y": 90.66666666666667,
"z": 0
}
},
@@ -486,7 +486,7 @@
],
"value": {
"__type__": "cc.Size",
"width": 140.26666666666668,
"width": 180.26666666666665,
"height": 150
}
},
@@ -508,7 +508,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 21,
"y": 27.66666666666667,
"z": 0
}
},
@@ -529,8 +529,8 @@
],
"value": {
"__type__": "cc.Size",
"width": 160.26666666666668,
"height": 200
"width": 200.26666666666665,
"height": 213.33333333333334
}
},
{
@@ -550,8 +550,8 @@
],
"value": {
"__type__": "cc.Size",
"width": 160.26666666666668,
"height": 200
"width": 200.26666666666665,
"height": 213.33333333333334
}
},
{
@@ -571,7 +571,7 @@
],
"value": {
"__type__": "cc.Size",
"width": 152.26666666666668,
"width": 192.26666666666665,
"height": 150
}
},
@@ -593,7 +593,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 21,
"y": 27.66666666666667,
"z": 0
}
},
@@ -614,7 +614,7 @@
],
"value": {
"__type__": "cc.Size",
"width": 152.26666666666668,
"width": 192.26666666666665,
"height": 24
}
},
@@ -636,7 +636,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 84,
"y": 90.66666666666667,
"z": 0
}
},
@@ -657,7 +657,7 @@
],
"value": {
"__type__": "cc.Size",
"width": 140.26666666666668,
"width": 180.26666666666665,
"height": 150
}
},
@@ -679,7 +679,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 21,
"y": 27.66666666666667,
"z": 0
}
},
@@ -752,8 +752,8 @@
],
"value": {
"__type__": "cc.Size",
"width": 160.26666666666668,
"height": 200
"width": 200.26666666666665,
"height": 213.33333333333334
}
},
{
@@ -773,8 +773,8 @@
],
"value": {
"__type__": "cc.Size",
"width": 160.26666666666668,
"height": 200
"width": 200.26666666666665,
"height": 213.33333333333334
}
},
{
@@ -794,7 +794,7 @@
],
"value": {
"__type__": "cc.Size",
"width": 152.26666666666668,
"width": 192.26666666666665,
"height": 150
}
},
@@ -816,7 +816,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 21,
"y": 27.66666666666667,
"z": 0
}
},
@@ -837,7 +837,7 @@
],
"value": {
"__type__": "cc.Size",
"width": 152.26666666666668,
"width": 192.26666666666665,
"height": 24
}
},
@@ -859,7 +859,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 84,
"y": 90.66666666666667,
"z": 0
}
},
@@ -880,7 +880,7 @@
],
"value": {
"__type__": "cc.Size",
"width": 140.26666666666668,
"width": 180.26666666666665,
"height": 150
}
},
@@ -902,7 +902,7 @@
"value": {
"__type__": "cc.Vec3",
"x": 0,
"y": 21,
"y": 27.66666666666667,
"z": 0
}
},
@@ -937,7 +937,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 50.892,
"y": 59.957,
"z": 0
},
"_lrot": {
@@ -1083,7 +1083,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 89.47899999999998,
"y": 96.661,
"z": 0
},
"_lrot": {
@@ -1244,6 +1244,8 @@
"__id__": 0
},
"fileId": "08p7XmUnpCJKHgFSs+XBKi",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
@@ -1260,8 +1262,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 90,
"height": 79.99999999999997
"width": 120,
"height": 90
},
"_anchorPoint": {
"__type__": "cc.Vec2",
@@ -1312,8 +1314,8 @@
"_target": null,
"_left": 15,
"_right": 15,
"_top": 20.521,
"_bottom": 49.479000000000006,
"_top": 18.339,
"_bottom": 51.66100000000001,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
@@ -1418,7 +1420,7 @@
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 131.674,
"y": 141.674,
"z": 0
},
"_lrot": {
@@ -3037,8 +3039,8 @@
},
"_contentSize": {
"__type__": "cc.Size",
"width": 120,
"height": 150
"width": 150,
"height": 160
},
"_anchorPoint": {
"__type__": "cc.Vec2",

View File

@@ -50,7 +50,7 @@
}
],
"_prefab": {
"__id__": 771
"__id__": 777
},
"_lpos": {
"__type__": "cc.Vec3",
@@ -15419,9 +15419,7 @@
"upgrade_btn": {
"__id__": 636
},
"fragment_node": {
"__id__": 315
},
"fragment_node": null,
"chip_node": {
"__id__": 605
},
@@ -15439,7 +15437,7 @@
"__id__": 72
},
"fragment_label": {
"__id__": 590
"__id__": 771
},
"upgrade_info_label": {
"__id__": 596
@@ -15460,6 +15458,165 @@
"__type__": "cc.CompPrefabInfo",
"fileId": "74nSIqyBJJwaVyNs9jfBQP"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 772
},
"_enabled": true,
"__prefab": {
"__id__": 776
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_string": "99/99",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 17,
"_fontSize": 17,
"_fontFamily": "Arial",
"_lineHeight": 20,
"_overflow": 0,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_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.Node",
"_name": "num",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 605
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 773
},
{
"__id__": 771
}
],
"_prefab": {
"__id__": 775
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 1,
"y": -23.919,
"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__": 772
},
"_enabled": true,
"__prefab": {
"__id__": 774
},
"_contentSize": {
"__type__": "cc.Size",
"width": 46.54150390625,
"height": 29.2
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "99GXVOOhxLv6HYFzXD8qI7"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "840izZ1o5N84e/O1jFl7Gl",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "bckAjBBO9Ob5COKSUTDEcI"
},
{
"__type__": "cc.PrefabInfo",
"root": {
@@ -15470,7 +15627,11 @@
},
"fileId": "ee/zS1qmRJKKqnSQsZ3i2e",
"instance": null,
"targetOverrides": [],
"targetOverrides": [
{
"__id__": 778
}
],
"nestedPrefabInstanceRoots": [
{
"__id__": 636
@@ -15506,5 +15667,27 @@
"__id__": 2
}
]
},
{
"__type__": "cc.TargetOverrideInfo",
"source": {
"__id__": 769
},
"sourceInfo": null,
"propertyPath": [
"fragment_label"
],
"target": {
"__id__": 605
},
"targetInfo": {
"__id__": 779
}
},
{
"__type__": "cc.TargetInfo",
"localID": [
"bckAjBBO9Ob5COKSUTDEcI"
]
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +0,0 @@
{
"ver": "1.0.0",
"importer": "*",
"imported": true,
"uuid": "2baaf870-8fbf-41a4-adeb-475c12130c38",
"files": [
".bak",
".json"
],
"subMetas": {},
"userData": {}
}

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

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: 962 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -116,8 +116,8 @@
"height": 54,
"rawWidth": 89,
"rawHeight": 54,
"borderTop": 0,
"borderBottom": 0,
"borderTop": 26,
"borderBottom": 26,
"borderLeft": 44,
"borderRight": 44,
"packable": true,
@@ -576,8 +576,8 @@
"height": 45,
"rawWidth": 52,
"rawHeight": 45,
"borderTop": 0,
"borderBottom": 0,
"borderTop": 22,
"borderBottom": 23,
"borderLeft": 26,
"borderRight": 26,
"packable": true,
@@ -4992,8 +4992,8 @@
"height": 45,
"rawWidth": 81,
"rawHeight": 45,
"borderTop": 0,
"borderBottom": 0,
"borderTop": 22,
"borderBottom": 22,
"borderLeft": 40,
"borderRight": 40,
"packable": true,
@@ -6096,10 +6096,10 @@
"height": 39,
"rawWidth": 37,
"rawHeight": 39,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"borderTop": 19,
"borderBottom": 18,
"borderLeft": 19,
"borderRight": 18,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,

View File

@@ -81,11 +81,13 @@ const PLACEHOLDER_PATTERN = /\{(\w+)\}/g;
* atking/atked 为周期循环触发(运行时按次数取模判定),拼为"每攻击N次"/"每受击N次"
* 表达循环语义(N=1 时退化为"每次攻击"/"每次受击");其余类型直接用触发名(召唤/死亡后/回合开始)。
*/
function buildTriggerLabel(type: SkillTriggerType, tNum: number): string {
function buildTriggerLabel(type: SkillTriggerType, tNum: number, chance?: number): string {
if (type === SkillTriggerType.Atking || type === SkillTriggerType.Atked) {
// 配置了 t_chance 且 < 100 时追加概率说明(保底叠加为隐藏机制,不在文案展示)
const chanceSeg = chance !== undefined && chance < 100 ? `(${valueRich(Math.round(chance))}%概率)` : "";
return tNum <= 1
? `每次${SkillTriggerName[type]}`
: `每${SkillTriggerName[type]}${valueRich(tNum)}次`;
? `每次${SkillTriggerName[type]}${chanceSeg}`
: `每${SkillTriggerName[type]}${valueRich(tNum)}次${chanceSeg}`;
}
return SkillTriggerName[type];
}
@@ -137,18 +139,25 @@ function getBuffValue(skill: SkillConfig): number {
* Why: 数值不写死在模板里,overrides 调整档位数值时文案自动同步。
* @param tmpl 描述模板(含占位符)
* @param skill mergeSkillParams 合并后的技能配置
* @param extra 条目级参数占位符(触发技能的 t_chance/pity_add 不在 SkillConfig,由调用方透传;优先于技能字段)
*/
function renderDesc(tmpl: string, skill: SkillConfig): string {
function renderDesc(tmpl: string, skill: SkillConfig, extra?: Record<string, number>): string {
return tmpl.replace(PLACEHOLDER_PATTERN, (raw, key: string) => {
if (key === "target") return buildTargetDesc(skill);
if (key === "dur") return valueRich(getBuffDuration(skill));
if (key === "buff_value") return valueRich(getBuffValue(skill));
if (extra && key in extra) return valueRich(String(extra[key]));
const v = (skill as unknown as Record<string, unknown>)[key];
// 未定义数值字段按 0 渲染,避免面板出现 undefined(模板与配置不匹配的兜底)
return v === undefined ? valueRich(0) : valueRich(String(v));
});
}
/** 触发档位的条目级占位符上下文(普攻/大招档位无此层参数,返回 undefined 走技能字段) */
function tierExtras(t: ISkillTierInfo): Record<string, number> | undefined {
return t.tChance === undefined ? undefined : { t_chance: t.tChance, pity_add: t.pityAdd ?? 0 };
}
/** 无模板时兜底:回退底座技能的 info 备注 */
function fallbackDesc(skill: SkillConfig): string {
return String(skill.info ?? "");
@@ -224,9 +233,12 @@ export function buildHeroSkillDesc(heroUuid: number, showName: boolean = true, s
if (e.lv <= lv && e.desc) tmpl = e.desc;
}
tmpl = tmpl ?? entries[0]?.desc;
const text = tmpl ? renderDesc(tmpl, skill) : fallbackDesc(skill);
const text = tmpl
? renderDesc(tmpl, skill, { t_chance: item.t_chance ?? 100, pity_add: item.pity_add ?? 0 })
: fallbackDesc(skill);
// 触发语境:"每攻击N次"循环语义(buildTriggerLabel),逗号衔接效果文本
const trig = buildTriggerLabel(type, item.t_num);
// 模板已显式使用 {t_chance} 时不再自动追加概率后缀,避免同一行重复展示
const trig = buildTriggerLabel(type, item.t_num, tmpl?.includes("{t_chance}") ? undefined : item.t_chance);
parts.push(`${trig}, ${text}`);
}
// dead 组内的复活效果(revive_hp / revive_count)并入同一行
@@ -279,12 +291,17 @@ interface ISkillTierInfo {
ownDesc?: string;
/** 触发技能档位的触发次数(普攻无) */
tNum: number;
/** 触发技能档位的触发概率百分点(普攻无;缺省 100 必触发) */
tChance?: number;
/** 触发技能档位的保底叠加百分点(普攻无;缺省 0) */
pityAdd?: number;
}
/** 相邻档 diff 参与展示的字段(排除动画/弹道/特效等玩家无感字段),t_num 为触发技能专有 */
const DIFF_KEYS: string[] = [
"ap", "hit_count", "num", "summon_count", "crt", "frz", "stun", "para",
"bck", "bck_chance", "buff_duration", "buff_value", "t_num", "timed_buff_id",
"t_chance", "pity_add",
];
/** diff 字段的称谓与单位("新增/漂移"描述共用) */
@@ -302,11 +319,15 @@ const DIFF_META: Record<string, { label: string; unit: string }> = {
buff_duration: { label: "持续", unit: "秒" },
buff_value: { label: "增益强度", unit: "" },
t_num: { label: "触发次数", unit: "次" },
t_chance: { label: "触发概率", unit: "%" },
pity_add: { label: "保底叠加", unit: "%" },
};
/** 读取档位某展示字段的实时值(t_num/buff_value 走专用取值) */
/** 读取档位某展示字段的实时值(t_num/t_chance/pity_add/buff_value 走专用取值) */
function getTierValue(tier: ISkillTierInfo, key: string): number {
if (key === "t_num") return tier.tNum;
if (key === "t_chance") return tier.tChance ?? 100;
if (key === "pity_add") return tier.pityAdd ?? 0;
if (key === "buff_value") return getBuffValue(tier.skill);
const v = (tier.skill as unknown as Record<string, unknown>)[key];
return typeof v === "number" ? v : 0;
@@ -497,9 +518,13 @@ function collectTriggerTiers(hero: heroInfo, type: SkillTriggerType, skillUuid:
desc: e.desc ?? inherit,
ownDesc: e.desc,
tNum: e.t_num,
tChance: e.t_chance ?? 100,
pityAdd: e.pity_add ?? 0,
};
});
const label = buildTriggerLabel(type, sorted[0].t_num);
// slv1 模板已显式使用 {t_chance} 时不再自动追加概率后缀,避免同一行重复展示
const firstTmpl = tiers[0]?.desc;
const label = buildTriggerLabel(type, sorted[0].t_num, firstTmpl?.includes("{t_chance}") ? undefined : sorted[0].t_chance);
return { tiers, label };
}
@@ -510,11 +535,11 @@ function buildTierLines(tiers: ISkillTierInfo[], triggerLabel: string): HeroSkil
const t = tiers[i];
if (i === 0) {
// 技能 1 级:完整能力 + 数值
const text = t.desc ? renderDesc(t.desc, t.skill) : fallbackDesc(t.skill);
const text = t.desc ? renderDesc(t.desc, t.skill, tierExtras(t)) : fallbackDesc(t.skill);
lines.push({ sLv: t.sLv, heroLv: t.heroLv, text: triggerLabel ? `${triggerLabel}, ${text}` : text });
} else if (t.ownDesc) {
// 提升档:该档显式配置了模板(如护盾/治疗等 diff 标签语义不达的技能),策划表述优先
lines.push({ sLv: t.sLv, heroLv: t.heroLv, text: renderDesc(t.ownDesc, t.skill) });
lines.push({ sLv: t.sLv, heroLv: t.heroLv, text: renderDesc(t.ownDesc, t.skill, tierExtras(t)) });
} else {
// 提升档:未配置模板,给相对上一级的提升
const diff = buildTierDiff(tiers[i - 1], t);

View File

@@ -9,7 +9,7 @@ import { GameScoreStats } from "./config/HeroAttrs";
import { mLogger } from "./Logger";
import { gameDataSync } from "./GameDataSync";
import { HERO_UPGRADE_NEED_FRAGMENTS } from "./config/GameSet";
import { TalentSet, TALENT_LEVEL_MAX, getTalentLevelExp, getTalentUpgradeCost, isTalentTierUnlocked } from "./config/TalentSet";
import { TalentSet, TALENT_LEVEL_MAX, getTalentLevelExp, isTalentPrereqMet, isTalentTierUnlocked } from "./config/TalentSet";
import { ScoreRankList, ScoreTier, getRankByScore } from "./config/ScoreRankSet";
import { RANK_REWARDS, STAGE_REWARDS, RewardItem, pickRandomHeroUuid } from "./config/RewardSet";
@@ -48,11 +48,9 @@ export interface GameDate {
talent_lv?: number,
/** 天赋当前等级已累计的经验值 */
talent_exp?: number,
/** 关卡进度存档:当前章节-关-节 */
/** 关卡进度存档:当前关卡号(无限关模式只记关卡号,回合恒从 1 开始) */
stage_progress?: {
chapter: number,
stage: number,
section: number,
},
/** 奖励领取状态:段位/闯关首达奖励的已领取标记(玩家点击领取) */
reward_claimed?: {
@@ -61,7 +59,7 @@ export interface GameDate {
/** 已领取的大关号 */
stage: number[],
},
/** 已通关大关的显式标记(覆盖存档停留场景,如第 10 关通关后进度停留 1-10-10) */
/** 已通关大关的显式标记(历史遗留字段,无限关模式不再写入;仅用于兼容读取旧存档) */
passed_stages?: number[],
}
export interface CloudData {
@@ -152,11 +150,9 @@ export class SingletonModuleComp extends ecs.Comp {
talent_lv: 0,
/** 当前等级已累计经验 */
talent_exp: 0,
/** 关卡进度存档:默认从 1-1-1 开始 */
/** 关卡进度存档:默认从第 1 关开始 */
stage_progress: {
chapter: 1,
stage: 1,
section: 1,
},
/** 奖励领取状态(玩家点击领取,跨局保留) */
reward_claimed: {
@@ -238,9 +234,7 @@ export class SingletonModuleComp extends ecs.Comp {
switch_music: true, // 音乐开关(MVVM绑定字段)
/** 关卡进度(MVVM绑定字段,镜像 data.stage_progress,供 HUD 实时显示) */
stage_progress: {
chapter: 1,
stage: 1,
section: 1,
},
};
@@ -352,26 +346,26 @@ export class SingletonModuleComp extends ecs.Comp {
* 将 data 层的关卡进度镜像到 vmdata 视图层(MVVM 绑定字段)。
* Why: data.stage_progress 为云端持久权威源,vmdata.stage_progress 仅供 HUD 绑定展示;
* 登录覆盖、关卡推进后都必须调用本方法保持镜像一致。
* 旧版三段存档(chapter/stage/section)在同步时归一化为 stage 单字段。
*/
public syncStageProgressToView(): void {
const p = this.data.stage_progress;
// 就地更新字段而非整体替换对象:oops VM(JsonOb)只通知被赋值属性的精确路径,
// 整体替换仅派发 VC:data.stage_progress,不会触发 HUD 绑定的
// data.stage_progress.section 等嵌套末端路径刷新,导致进度显示停留在老节
const view = this.vmdata.stage_progress;
view.chapter = p?.chapter ?? 1;
view.stage = p?.stage ?? 1;
view.section = p?.section ?? 1;
// 旧存档归一化:兼容历史 chapter/stage/section 三段结构,仅提取 stage
const stage = Math.max(1, Math.floor(p?.stage ?? 1));
if (p && (p.stage !== stage || (p as any).chapter !== undefined || (p as any).section !== undefined)) {
this.data.stage_progress = { stage };
}
this.vmdata.stage_progress.stage = stage;
}
/**
* 更新关卡进度(权威源 data + 视图镜像 vmdata),并派发事件刷新 UI。
* Why: 替代直接赋值 smc.data.stage_progress,保证视图层实时同步。
* @param progress 新的关卡进度(章节/大关/小节均从 1 开始)
* @param progress 新的关卡进度(仅关卡号,从 1 开始)
* @param is_sync 是否标记脏并触发云端/本地同步
*/
public updateStageProgress(progress: { chapter: number; stage: number; section: number }, is_sync: boolean = true): void {
this.data.stage_progress = { ...progress };
public updateStageProgress(progress: { stage: number }, is_sync: boolean = true): void {
this.data.stage_progress = { stage: Math.max(1, Math.floor(progress.stage)) };
this.syncStageProgressToView();
if (is_sync) {
gameDataSync.markDataDirty();
@@ -452,20 +446,18 @@ export class SingletonModuleComp extends ecs.Comp {
}
/**
* 标记大关已通关(通关点调用,覆盖存档停留场景)
* Why: 第 10 关通关后存档停留 1-10-10,无法从 stage_progress 推导已通状态,
* 需在通关时显式写入。
* @param stage 大关号
* 标记大关已通关(历史遗留入口)
* Why: 旧版本第 10 关通关后存档停留,无法从 stage_progress 推导已通状态,需显式标记;
* 无限关模式存档永远推进,判定已通可直接由 stage_progress 推导,无需再写入。
* 保留空实现以避免破坏外部调用方(若有旧存档读取场景走 passed_stages 兼容)。
* @param _stage 大关号(未使用,保留签名兼容)
* @deprecated 无限关模式无需调用,保留仅为兼容旧代码引用
*/
public markStagePassed(stage: number): void {
if (!this.data.passed_stages) this.data.passed_stages = [];
if (!this.data.passed_stages.includes(stage)) {
this.data.passed_stages.push(stage);
gameDataSync.markDataDirty();
}
public markStagePassed(_stage: number): void {
// 无限关模式不再写入 passed_stages
}
/** 判定大关是否已通关:显式标记优先,存档推进兜底(已进入后续关即已通关) */
/** 判定大关是否已通关:旧存档显式标记兼容,无限关以存档关卡号推导(已进入后续关即已通关) */
private isStagePassed(stage: number): boolean {
if ((this.data.passed_stages ?? []).includes(stage)) return true;
return (this.data.stage_progress?.stage ?? 1) > stage;
@@ -694,7 +686,7 @@ export class SingletonModuleComp extends ecs.Comp {
/**
* 消耗天赋点升级天赋(升级即激活,下一局生效)
* @param uuid 天赋 uuid
* @returns 是否升级成功(天赋不存在/已满级/阶位未解锁/点数不足时返回 false)
* @returns 是否升级成功(天赋不存在/已满级/层位未解锁/前置未满足/点数不足时返回 false)
*/
upgradeTalent(uuid: number): boolean {
const cfg = TalentSet[uuid];
@@ -702,11 +694,13 @@ export class SingletonModuleComp extends ecs.Comp {
if (!this.data.talents) this.data.talents = {};
const curLv = this.getTalentLv(uuid);
if (curLv >= cfg.max_lv) return false;
// 阶位解锁:二/三阶天赋要求前一阶累计投入点数达标(一阶恒解锁)
// 层位解锁:第 N 层天赋要求第 N-1 层累计投入点数达标(第 1 层恒解锁)
if (!isTalentTierUnlocked(this.data.talents, cfg.tier)) return false;
const cost = getTalentUpgradeCost(cfg.tier);
if (cost < 0 || this.getTalentPoints() < cost) return false;
this.data.talent_points -= cost;
// 软前置:前置天赋等级需达标(仅限制加点)
if (!isTalentPrereqMet(this.data.talents, cfg)) return false;
// 统一 1 点/级
if (this.getTalentPoints() < 1) return false;
this.data.talent_points -= 1;
this.data.talents[uuid] = curLv + 1;
gameDataSync.markDataDirty();
oops.message.dispatchEvent(GameEvent.TALENT_UPDATE);
@@ -715,17 +709,13 @@ export class SingletonModuleComp extends ecs.Comp {
/**
* 重置所有天赋等级并退还全部已消耗天赋点(看广告入口)
* Why: 退还点数按"升到当前等级的累计消耗"计算,与升级消耗表严格对账,避免点数膨胀/缩水。
* Why: 统一 1 点/级后退还点数 = 等级总和,与升级消耗严格对账,避免点数膨胀/缩水。
*/
resetTalents(): void {
if (!this.data.talents) this.data.talents = {};
let refund = 0;
for (const key in this.data.talents) {
const lv = this.data.talents[key];
for (let i = 0; i < lv; i++) {
const c = getTalentUpgradeCost(i);
if (c > 0) refund += c;
}
refund += this.data.talents[key];
}
this.data.talents = {};
this.data.talent_points = (this.data.talent_points ?? 0) + refund;

View File

@@ -104,15 +104,15 @@ export enum GameEvent {
STAGE_PROGRESS_UPDATE = "STAGE_PROGRESS_UPDATE",
/** 奖励领取完成(payload 为空,UI 刷新待领取列表/红点) */
REWARD_CLAIMED = "REWARD_CLAIMED",
/** 小节开始(payload: { chapter, stage, section },MissionMonComp 据此生成怪物) */
/** 回合开始(payload: { stage, section },MissionMonComp 据此生成怪物) */
SectionStart = "SectionStart",
/** 小节结束(payload: { chapter, stage, section },玩家点击"下一节"后推进) */
/** @deprecated 已废弃:无限关模式无节间结算,回合自动推进,勿再监听 */
SectionEnd = "SectionEnd",
/** 大关结束(payload: { chapter, stage },10 节打完) */
/** @deprecated 已废弃:无限关模式无大关结算,勿再监听 */
StageEnd = "StageEnd",
/** 章节结束(payload: { chapter },10 关打完) */
/** @deprecated 已废弃:无限关模式无章节概念,勿再监听 */
ChapterEnd = "ChapterEnd",
/** 结算弹窗点击"下一节"(payload 为空,MissionComp 据此推进到下一小节) */
/** @deprecated 已废弃:无限关模式无"下一节"手动推进,勿再监听 */
NextSection = "NextSection",
/** 结算弹窗点击"再来一次"(payload 为空,MissionComp 据此重开本关) */
RestartMission = "RestartMission",

View File

@@ -27,6 +27,8 @@ export enum UIID {
SInfo,
/** 英雄详情弹窗(图鉴卡片点击弹出:含属性/技能/升级/前后切换) */
HeroInfo,
/** 天赋详情弹窗(天赋迷你卡点击弹出:图标/等级/前置/条幅说明/加点按钮) */
TIBox,
}
/** 打开界面方式的配置数据 */
@@ -44,5 +46,6 @@ export var UIConfigData: { [key: number]: UIConfig } = {
[UIID.Mission]: { layer: LayerType.UI, prefab: "gui/element/mission" },
[UIID.SInfo]: { layer: LayerType.PopUp, prefab: "gui/element/SInfoBox" },
[UIID.HeroInfo]: { layer: LayerType.PopUp, prefab: "gui/element/heroInfo", bundle: "resources" },
[UIID.TIBox]: { layer: LayerType.PopUp, prefab: "gui/element/TIBox", bundle: "resources" },
}

View File

@@ -65,6 +65,16 @@ export enum EType {
timeEnd = 1, //碰撞够也不消失,时间到才消失
collision = 2, //碰撞次数够就消失
}
/**
* 召唤类技能(summon_count>1)多次施放的目标选择模式(由技能 prefab 上 SkillView 配置)。
* 仅对敌方目标技能生效;友方/自身技能不走该逻辑。
*/
export enum SummonTMode {
same = 0, // 同一敌人:所有召唤物锁定施法时选定的目标(默认,原行为)
random = 1, // 随机敌人:每次出手时从射程内敌人中随机锁定一个
unique = 2, // 避免相同敌人:优先锁定本轮尚未选过的敌人,一轮选完后重置再选
}
//debuff类型
@@ -318,6 +328,21 @@ export const SkillSet: Record<number, SkillConfig> = {
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.bezier, EType: EType.collision, info: "红瓶抛射物",
},
6013: {
uuid: 6013, name: "闪电", sp_name: "m3", icon: "109-Red-Potion", 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.fixedEnd, EType: EType.collision, info: "闪电",
},
6014: {
uuid: 6014, name: "小火球", sp_name: "m4", icon: "109-Red-Potion", 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.fixedEnd, EType: EType.collision, info: "小火球",
},
6015: {
uuid: 6015, name: "小飓风", sp_name: "m5", icon: "109-Red-Potion", 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.fixedEnd, EType: EType.collision, info: "小飓风抛",
},
// ==================== 群体大招(按职业分段:6100-6149 坦克/战士、6150-6199 刺客/射手、6200-6249 法师/辅助) ====================
// 坦克/战士群体攻击大招
6100: {
@@ -336,8 +361,8 @@ export const SkillSet: Record<number, SkillConfig> = {
RType: RType.bezier, EType: EType.collision, info: "火焰斩动画",//英雄配置技能的时候,可以叠加暴击debuff
},
6103: {
uuid: 6103, name: "闪电斩", sp_name: "atk5", icon: "100-Enchant-Magic", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "max0",
DTType: DTType.single, para: 30, ap: 150, hit_count: 5, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
uuid: 6103, name: "闪电斩", sp_name: "atk6", icon: "100-Enchant-Magic", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "max0",
DTType: DTType.single, ap: 150, hit_count: 5, hitcd: 0.2, speed: 720, with: 0, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.Melee, is_accel: true,
RType: RType.bezier, EType: EType.collision, info: "闪电斩动画",//英雄配置技能的时候,可以疲惫触发闪电系追加技能
},
6104: {
@@ -364,22 +389,22 @@ export const SkillSet: Record<number, SkillConfig> = {
},
6201: {
uuid: 6201, name: "飓风", sp_name: "maxm2", icon: "98-Enchant-Wind", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "max1",
DTType: DTType.single, ap: 100, hit_count: 2, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
DTType: DTType.single, ap: 100, hit_count: 99, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.linear, EType: EType.collision, info: "一个飓风",
},
6202: {
uuid: 6202, name: "毒气弹", sp_name: "maxm4", icon: "o_warlock_hyzy_skill", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "max1",
DTType: DTType.single, ap: 100, hit_count: 2, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
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, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "一个大毒气弹",
},
6203: {
uuid: 6203, name: "能量波", sp_name: "maxm5", icon: "b_priest_kx_skill", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "max1",
DTType: DTType.single, ap: 100, hit_count: 2, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
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, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "一个能量波",
},
6204: {
uuid: 6204, name: "多毒气弹", sp_name: "maxm6", icon: "o_warlock_hyzy_skill", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "max1",
DTType: DTType.single, ap: 100, hit_count: 2, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
uuid: 6204, name: "雷暴", sp_name: "maxm7", icon: "o_warlock_hyzy_skill", TGroup: TGroup.Enemy, readyAnm: "", endAnm: "", act: "max1",
DTType: DTType.single, ap: 100, hit_count: 99, hitcd: 0.3, speed: 720, with: 90, ready: 0.2, EAnm: 0, DAnm: "", IType: IType.remote,
RType: RType.linear, EType: EType.collision, bezier_start_y: 20, bezier_mid_y: 140, bezier_arc: 1.05, info: "一个大毒气弹",
},

View File

@@ -1,17 +1,22 @@
/**
* @file StageSet.ts
* @description 关卡系统配置(章节-关-节三级结构)
* @description 关卡系统配置(无限关卡·单线推进)
*
* 设计要点:
* - 1 章 = 10 关,每关 = 5 小节(一关一局,节间自动推进),共 50 个小节
* - 每关节点固定:1~3 节普通(3 小怪)、第 4 节精英(3 精英)、第 5 节 Boss(1 Boss + 2 小怪)
* - 怪物等级 = 关卡号(1-1 的怪为 Lv1,1-5 的怪为 Lv5)
* - 怪物属性 = 基础 × 开局系数 × (1 + 成长斜率 × (lv-1)) × 类型倍率
* - 无限关卡:从第 1 关起无上限推进,每关 = 5 回合(一关一局,回合间自动推进)
* - 第 1~4 回合为普通怪,属性逐回合轻量增强(SECTION_RAMP_STEP)
* - 第 5 回合为单 Boss 回合(无护卫),Boss 采用超厚血量(BOSS_HP_MUL)
* - 怪物等级 = 关卡号(第 N 关的怪为 LvN,随关卡无限成长)
* - 小节配置按需动态生成,不再依赖章节静态表
*
* 数值基准:
* - 开局系数 0.8(1 级小怪略弱于英雄)
* - 成长斜率 +5%/级(10 级 ≈ 1.45 倍)
* - 精英倍率 × 1.5,Boss 倍率 × 3.0(精英 × 2)
* - 成长斜率 +5%/级(无限线性成长,曲线平缓可控)
* - 回合增强 +12%/回合(第 4 回合 ≈ 1.36 倍)
* - Boss:HP × 12(超厚血量)/ AP × 2.0(攻击克制,避免 Boss 秒杀英雄)
*
* 核心公式:
* 怪物属性 = 基础 × MON_START_COEF × (1 + MON_GROW_SLOPE×(lv-1)) × 回合增强 × 类型倍率
*/
import { MonType } from "./heroSet";
@@ -20,82 +25,54 @@ import { MonType } from "./heroSet";
/** 怪物开局系数:1 级小怪略弱于英雄(0.8 倍) */
export const MON_START_COEF = 0.8;
/** 怪物成长斜率:每级 +5% 基础属性 */
/** 怪物成长斜率:每级 +5% 基础属性(无限线性成长) */
export const MON_GROW_SLOPE = 0.05;
/** 精英属性倍率:同级普通怪的 1.5 倍 */
export const ELITE_MUL = 1.5;
/** Boss 属性倍率:同级普通怪的 3.0 倍(精英 × 2) */
export const BOSS_MUL = 3.0;
/** 每关小节总数(一关一局,节间自动推进) */
/** 回合内轻量增强步长:第 1→4 回合每回合 +12% 属性 */
export const SECTION_RAMP_STEP = 0.12;
/** Boss 血量倍率:同级普通怪的 12 倍(超厚血量,拖长 Boss 战节奏) */
export const BOSS_HP_MUL = 12;
/** Boss 攻击倍率:同级普通怪的 2.0 倍(显著低于血量倍率,避免 Boss 秒杀英雄) */
export const BOSS_AP_MUL = 2.0;
/** 每关回合总数(一关一局,回合间自动推进) */
export const SECTIONS_PER_STAGE = 5;
/** 精英节号(每关第 4 节为精英节) */
export const ELITE_SECTION = 4;
/** Boss 节号(每关第 5 节为 Boss 节,即末节) */
/** Boss 回合号(每关第 5 回合,即末回合) */
export const BOSS_SECTION = 5;
/** 普通节小怪数量(一关一局压缩后单节 3 只) */
/** 普通回合小怪数量 */
export const NORMAL_MON_COUNT = 3;
/** 普通节开局立即刷出数量(3 只全部立即刷出) */
/** 普通回合开局立即刷出数量(3 只全部立即刷出) */
export const NORMAL_FIRST_SPAWN = 3;
/** 普通节后续刷怪间隔(秒):保留配置位(3 只全立即刷,当前无后续批次) */
/** 普通回合后续刷怪间隔(秒):保留配置位(3 只全立即刷,当前无后续批次) */
export const NORMAL_SPAWN_INTERVAL = 2;
/** 精英节精英数量 */
export const ELITE_COUNT = 3;
/** Boss 节护卫小怪数量 */
export const BOSS_ELITE_COUNT = 2;
/** Boss 节 Boss 延迟刷出时间(秒) */
/** Boss 回合 Boss 延迟刷出时间(秒) */
export const BOSS_DELAY = 1;
// ======================== 类型定义 ========================
/** 小节类型 */
/** 回合类型 */
export enum SectionType {
Normal = 0, // 普通节:3 小怪
Elite = 1, // 精英节:3 精英
Boss = 2, // Boss 节:1 Boss + 2 小怪
Normal = 0, // 普通回合:3 小怪(逐回合轻量增强)
Boss = 2, // Boss 回合:1 Boss(超厚血量,无护卫)
}
/** 小节配置 */
/** 回合配置 */
export interface SectionConfig {
/** 小节类型 */
/** 回合类型 */
type: SectionType;
/** 怪物等级(驱动线性成长) */
/** 怪物等级(= 关卡号,驱动线性成长) */
monLv: number;
/** 精英数量(精英关/Boss 关用) */
eliteCount?: number;
/** Boss UUID(Boss 关用,可选,缺省随机) */
bossUuid?: number;
/** 怪物类型池(普通关用,缺省随机) */
/** 回合增强系数(第 n 回合 = 1 + SECTION_RAMP_STEP×(n-1),仅普通回合生效) */
ramp: number;
/** 怪物类型池(普通回合用,缺省随机) */
monTypes?: MonType[];
}
/** 大关配置(1-1 ~ 1-10),每关 SECTIONS_PER_STAGE 个小节 */
export interface StageConfig {
/** 章节号 */
chapter: number;
/** 关卡号 1~10 */
stage: number;
/** 小节列表(长度 = SECTIONS_PER_STAGE) */
sections: SectionConfig[];
}
/** 章节配置 */
export interface ChapterConfig {
/** 章节号 */
chapter: number;
/** 章节名称 */
name: string;
/** 10 个大关 */
stages: StageConfig[];
}
// ======================== 工具函数 ========================
/**
* 计算怪物最终属性
* 计算怪物最终属性(Boss 血/攻倍率分离:超厚血 + 克制攻)
* @param baseHp 基础 HP
* @param baseAp 基础 AP
* @param lv 怪物等级
* @param isElite 是否精英
* @param isBoss 是否 Boss
* @returns 最终 HP/AP
*/
@@ -103,159 +80,70 @@ export function calcMonsterAttrs(
baseHp: number,
baseAp: number,
lv: number,
isElite: boolean = false,
isBoss: boolean = false
): { hp: number; ap: number } {
const growCoef = 1 + MON_GROW_SLOPE * (lv - 1);
let typeMul = 1;
if (isBoss) {
typeMul = BOSS_MUL;
} else if (isElite) {
typeMul = ELITE_MUL;
}
const hp = Math.max(1, Math.round(baseHp * MON_START_COEF * growCoef * typeMul));
const ap = Math.max(1, Math.round(baseAp * MON_START_COEF * growCoef * typeMul));
const hp = Math.max(1, Math.round(baseHp * MON_START_COEF * growCoef * (isBoss ? BOSS_HP_MUL : 1)));
const ap = Math.max(1, Math.round(baseAp * MON_START_COEF * growCoef * (isBoss ? BOSS_AP_MUL : 1)));
return { hp, ap };
}
/**
* 获取指定关卡的怪物等级
* @param stage 关卡号(1~10)
* 获取指定关卡的怪物等级(无限关:等级 = 关卡号,无上限)
* @param stage 关卡号(1~∞)
* @returns 怪物等级
*/
export function getMonsterLv(stage: number): number {
return Math.max(1, Math.min(10, stage));
return Math.max(1, Math.floor(stage));
}
/**
* 判断是否为精英节(每关第 ELITE_SECTION 节)
* @param section 小节号(1~SECTIONS_PER_STAGE)
*/
export function isEliteSection(section: number): boolean {
return section === ELITE_SECTION;
}
/**
* 判断是否为 Boss 节(每关第 BOSS_SECTION 节,即末节)
* @param section 小节号(1~SECTIONS_PER_STAGE)
* 判断是否为 Boss 回合(每关第 BOSS_SECTION 回合,即末回合)
* @param section 回合号(1~SECTIONS_PER_STAGE)
*/
export function isBossSection(section: number): boolean {
return section === BOSS_SECTION;
}
/**
* 获取小节类型(按节号区分:1~3 普通 / 4 精英 / 5 Boss)
* @param section 小节号(1~SECTIONS_PER_STAGE)
* 获取回合类型(按回合号区分:1~4 普通 / 5 Boss)
* @param section 回合号(1~SECTIONS_PER_STAGE)
*/
export function getSectionType(section: number): SectionType {
if (isBossSection(section)) return SectionType.Boss;
if (isEliteSection(section)) return SectionType.Elite;
return SectionType.Normal;
return isBossSection(section) ? SectionType.Boss : SectionType.Normal;
}
// ======================== 第 1 章配置 ========================
/** 生成单个大关的 SECTIONS_PER_STAGE 个小节配置(1~3 普通 / 4 精英 / 5 Boss) */
function makeStageSections(stage: number): SectionConfig[] {
const monLv = getMonsterLv(stage);
const sections: SectionConfig[] = [];
for (let i = 1; i <= SECTIONS_PER_STAGE; i++) {
const type = getSectionType(i);
const section: SectionConfig = { type, monLv };
if (type === SectionType.Elite) {
section.eliteCount = ELITE_COUNT;
} else if (type === SectionType.Boss) {
section.eliteCount = BOSS_ELITE_COUNT;
// bossUuid 缺省随机,由生成引擎从 Boss 池抽取
} else {
// 普通节:近战为主,穿插远程/重型
section.monTypes = [MonType.Melee, MonType.Melee, MonType.Melee, MonType.Heavy, MonType.Long];
}
sections.push(section);
}
return sections;
}
/** 第 1 章配置 */
export const Chapter1: ChapterConfig = {
chapter: 1,
name: "兽人入侵",
stages: [],
};
// 初始化第 1 章 10 个大关
for (let stage = 1; stage <= 10; stage++) {
Chapter1.stages.push({
chapter: 1,
stage,
sections: makeStageSections(stage),
});
}
/** 所有章节配置表(当前仅第 1 章,后续扩展) */
export const ChapterSet: Record<number, ChapterConfig> = {
1: Chapter1,
};
// ======================== 查询接口 ========================
/**
* 获取小节配置
* @param chapter 章节号
* @param stage 关卡号
* @param section 小节号(1~SECTIONS_PER_STAGE)
* @returns 小节配置,不存在时返回 null
* 获取回合配置(无限关:按需动态生成,不再依赖章节静态表)
* @param stage 关卡号(1~∞)
* @param section 回合号(1~SECTIONS_PER_STAGE)
* @returns 回合配置,回合号越界时返回 null
*/
export function getSectionConfig(chapter: number, stage: number, section: number): SectionConfig | null {
const ch = ChapterSet[chapter];
if (!ch) return null;
const st = ch.stages.find(s => s.stage === stage);
if (!st) return null;
if (section < 1 || section > st.sections.length) return null;
return st.sections[section - 1];
export function getSectionConfig(stage: number, section: number): SectionConfig | null {
if (section < 1 || section > SECTIONS_PER_STAGE) return null;
const type = getSectionType(section);
const cfg: SectionConfig = {
type,
monLv: getMonsterLv(stage),
ramp: 1 + SECTION_RAMP_STEP * (section - 1),
};
if (type !== SectionType.Boss) {
// 普通回合:近战为主,穿插远程/重型
cfg.monTypes = [MonType.Melee, MonType.Melee, MonType.Melee, MonType.Heavy, MonType.Long];
}
return cfg;
}
/**
* 获取大关配置
* @param chapter 章节号
* 获取当前进度显示文本(无限关:只展示 关-回合)
* @param stage 关卡号
* @returns 大关配置,不存在时返回 null
* @param section 回合号
* @returns 格式化文本,如 "5-3"
*/
export function getStageConfig(chapter: number, stage: number): StageConfig | null {
const ch = ChapterSet[chapter];
if (!ch) return null;
return ch.stages.find(s => s.stage === stage) ?? null;
}
/**
* 获取章节配置
* @param chapter 章节号
* @returns 章节配置,不存在时返回 null
*/
export function getChapterConfig(chapter: number): ChapterConfig | null {
return ChapterSet[chapter] ?? null;
}
/**
* 获取当前进度显示文本
* @param chapter 章节号
* @param stage 关卡号
* @param section 小节号
* @returns 格式化文本,如 "1-5-3"
*/
export function getProgressText(chapter: number, stage: number, section: number): string {
return `${chapter}-${stage}-${section}`;
}
/**
* 获取大关显示文本
* @param chapter 章节号
* @param stage 关卡号
* @returns 格式化文本,如 "1-5"
*/
export function getStageText(chapter: number, stage: number): string {
return `${chapter}-${stage}`;
export function getProgressText(stage: number, section: number): string {
return `${stage}-${section}`;
}

View File

@@ -4,13 +4,15 @@ import { HRole } from "./heroSet";
/**
* 天赋系统配置(局外成长:升级即激活,战斗中对英雄属性常驻加成)。
*
* 三阶结构(阶位逐级解锁,门槛见 TALENT_TIER_UNLOCK_THRESHOLD):
* - 一阶·基础天赋(9001-9099):全局生效,对所有英雄叠加;
* - 二阶·专精天赋(9101-9199):按 HRole 职业过滤,仅对匹配职业的英雄生效;
* - 三阶·超强天赋(9201-9219):全局(9201-9209)+ 职业(9211-9219)混合,数值幅度约为前两阶的 2 倍。
* 五层递进结构(逐层解锁,门槛见 TALENT_TIER_UNLOCK_THRESHOLD):
* - 第 1 层(9001-9099):恒解锁,建议放置全局基础属性;
* - 第 2 层(9101-9199):第 1 层累计投入 ≥5 点解锁;
* - 第 3 层(9201-9299):第 2 层累计投入 ≥5 点解锁;
* - 第 4 层(9301-9399):第 3 层累计投入 ≥5 点解锁;
* - 第 5 层(9401-9499):第 4 层累计投入 ≥5 点解锁。
*
* 解锁规则:一阶恒可升级;二阶需一阶累计投入 ≥10 点;三阶需二阶累计投入 ≥10 点。
* 专精/超强职业天赋不再有独立前置天赋,仅受阶位解锁约束。
* 解锁规则:第 1 层恒可升级;第 N 层需第 N-1 层累计投入 ≥5 点。
* 前置天赋(prereq_uuid/prereq_lv)为软前置:仅限制加点,不影响已有点数的战斗结算。
*
* 结算口径(与项目 buff 规范一致):
* - percent 模式:按英雄基础值(base_ap/base_hp/hero.def)相对放大;
@@ -23,14 +25,18 @@ export enum TalentValueMode {
Flat = 1,
}
/** 天赋阶位:一阶基础 / 二阶专精 / 三阶超强 */
/** 天赋层位:第 1 层 ~ 第 5 层递进解锁 */
export enum TalentTier {
/** 一阶:基础天赋,全局生效 */
Basic = 1,
/** 二阶:专精天赋,按职业过滤 */
Specialized = 2,
/** 三阶:超强天赋,全局 + 职业混合 */
Super = 3,
/** 第 1 层:恒解锁 */
L1 = 1,
/** 第 2 层:第 1 层投入 5 点解锁 */
L2 = 2,
/** 第 3 层:第 2 层投入 5 点解锁 */
L3 = 3,
/** 第 4 层:第 3 层投入 5 点解锁 */
L4 = 4,
/** 第 5 层:第 4 层投入 5 点解锁 */
L5 = 5,
}
/** 单条属性加成词条 */
@@ -47,7 +53,7 @@ export interface TalentAttrBonus {
export interface TalentConfig {
/** 天赋 uuid(段位见 TalentSet 注释) */
uuid: number;
/** 所属阶位(升级校验:升下一阶天赋需前一阶累计投入点数达标) */
/** 所属层位(升级校验:升下一层天赋需前一层累计投入点数达标) */
tier: TalentTier;
/** 天赋名称 */
name: string;
@@ -59,8 +65,16 @@ export interface TalentConfig {
bonuses: TalentAttrBonus[];
/** 最大等级 */
max_lv: number;
/** 专精/超强职业天赋:仅对该职业英雄生效;全局天赋省略此字段 */
/** 专精/职业天赋:仅对该职业英雄生效;全局天赋省略此字段 */
role?: HRole;
/** 软前置:前置天赋 uuid(与 prereq_lv 成对出现;缺省视为无前置) */
prereq_uuid?: number;
/** 软前置:前置天赋要求达到的等级(仅限制加点,不影响已有点数的结算) */
prereq_lv?: number;
/** L4 触发修正:触发类技能计数阈值减少量(每级,下限 1;与 bonuses 互斥) */
t_num_reduce_per_lv?: number;
/** L5 触发修正:触发类技能额外触发 1 次的概率(每级百分点;与 bonuses 互斥) */
extra_trigger_chance_per_lv?: number;
}
/** 单条已解析的加成(等级已乘算) */
@@ -84,208 +98,201 @@ export interface ITalentTarget {
base_ap: number;
/** 基础血量快照(percent 模式的基数) */
base_hp: number;
/** L4 写入:触发类技能计数阈值减少量 */
talent_t_num_reduce: number;
/** L5 写入:触发类技能额外触发 1 次的概率(百分点) */
talent_extra_trigger_chance: number;
/** 防御基础值(percent 模式的基数,等于 hero.def) */
[key: string]: any;
}
/**
* 天赋配置表。
* uuid 段位:9001-9099 一阶基础(全局);9101-9199 二阶专精(按职业);
* 9201-9209 三阶超强·全局;9211-9219 三阶超强·职业。
* uuid 段位:9001-9099 第 1 层;9101-9199 第 2 层;9201-9299 第 3 层;
* 9301-9399 第 4 层;9401-9499 第 5 层。
* 每层建议放置 4-6 个天赋;前置天赋用 prereq_uuid + prereq_lv 声明(软前置,仅限制加点)。
* icon 复用 SkillSet/uiconsAtlas 既有帧名,后续可在编辑器侧替换为专属图标。
*/
export const TalentSet: Record<number, TalentConfig> = {
// ==================== 一阶·基础天赋(9001-9099,全局生效) ====================
// ==================== 第 1 层(9001-9099,恒解锁,基础属性) ====================
9001: {
uuid: 9001, tier: TalentTier.Basic, name: "攻击强化", info: "所有英雄攻击 +{value}%", icon: "3-Aggression",
uuid: 9001, tier: TalentTier.L1, name: "攻击强化", info: "所有英雄攻击 +{value}%", icon: "3-Aggression",
bonuses: [{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 3 }],
max_lv: 5,
},
9002: {
uuid: 9002, tier: TalentTier.Basic, name: "生命强化", info: "所有英雄生命 +{value}%", icon: "29-Endure",
uuid: 9002, tier: TalentTier.L1, name: "生命强化", info: "所有英雄生命 +{value}%", icon: "29-Endure",
bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 }],
max_lv: 5,
},
9003: {
uuid: 9003, tier: TalentTier.Basic, name: "防御强化", info: "所有英雄防御 +{value} 点", icon: "46-Iron-Clad",
uuid: 9003, tier: TalentTier.L1, name: "防御强化", info: "所有英雄防御 +{value} 点", icon: "46-Iron-Clad",
bonuses: [{ attr: Attrs.defense, mode: TalentValueMode.Flat, per_lv: 8 }],
max_lv: 5,
},
9004: {
uuid: 9004, tier: TalentTier.Basic, name: "暴击强化", info: "所有英雄暴击率 +{value}%", icon: "27-Hawkeye",
uuid: 9004, tier: TalentTier.L1, name: "反伤荆棘", info: "所有英雄反伤 +{value} 点", icon: "45-Induration",
bonuses: [{ attr: Attrs.thorns_dmg, mode: TalentValueMode.Flat, per_lv: 5 }],
max_lv: 5,
},
// ==================== 第 2 层(9101-9199,第 1 层投入 5 点解锁,概率属性) ====================
9101: {
uuid: 9101, tier: TalentTier.L2, name: "暴击强化", info: "所有英雄暴击率 +{value}%", icon: "27-Hawkeye",
bonuses: [{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
9005: {
uuid: 9005, tier: TalentTier.Basic, name: "击晕强化", info: "所有英雄击晕率 +{value}%", icon: "13-Concussion",
bonuses: [{ attr: Attrs.stun_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
9006: {
uuid: 9006, tier: TalentTier.Basic, name: "冰冻强化", info: "所有英雄冰冻率 +{value}%", icon: "92-Frost-Bite",
bonuses: [{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
9007: {
uuid: 9007, tier: TalentTier.Basic, name: "击退强化", info: "所有英雄击退距离 +{value}", icon: "34-Force-Push",
9102: {
uuid: 9102, tier: TalentTier.L2, name: "击退强化", info: "所有英雄击退距离 +{value}", icon: "34-Force-Push",
bonuses: [{ attr: Attrs.knockback_distance, mode: TalentValueMode.Flat, per_lv: 10 }],
max_lv: 5,
},
// 风怒机制已停用: 9008 风怒强化天赋已移除
/*
9008: {
uuid: 9008, tier: TalentTier.Basic, name: "风怒强化", info: "所有英雄风怒率 +{value}%", icon: "98-Enchant-Wind",
bonuses: [{ attr: Attrs.wfuny, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
*/
// ==================== 二阶·专精天赋(9101-9199,按职业过滤;一阶投入 10 点解锁) ====================
9101: {
uuid: 9101, tier: TalentTier.Specialized, name: "坦克专精", info: "坦克防御 +{value}%、生命 +{value2}%", icon: "36-Guardian-Stance",
bonuses: [
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 30 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 },
],
max_lv: 5, role: HRole.Tank,
},
9102: {
uuid: 9102, tier: TalentTier.Specialized, name: "战士专精", info: "战士生命 +{value}%", icon: "1-Battle-Roar",
bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 12 }],
max_lv: 5, role: HRole.Warrior,
},
9103: {
uuid: 9103, tier: TalentTier.Specialized, name: "刺客专精", info: "刺客攻击 +{value}%、暴击率 +{value2}%", icon: "127-Hide",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 },
],
max_lv: 5, role: HRole.Assassin,
uuid: 9103, tier: TalentTier.L2, name: "冰冻强化", info: "所有英雄冰冻率 +{value}%", icon: "92-Frost-Bite",
bonuses: [{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
9104: {
uuid: 9104, tier: TalentTier.Specialized, name: "射手专精", info: "射手攻击 +{value}%、穿透率 +{value2}%", icon: "56-Pierce-Shot",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 2 },
],
max_lv: 5, role: HRole.Archer,
uuid: 9104, tier: TalentTier.L2, name: "击晕强化", info: "所有英雄击晕率 +{value}%", icon: "13-Concussion",
bonuses: [{ attr: Attrs.stun_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
9105: {
uuid: 9105, tier: TalentTier.Specialized, name: "法师专精", info: "法师攻击 +{value}%、冰冻率 +{value2}%", icon: "145-Electromancy",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 },
],
max_lv: 5, role: HRole.Mage,
uuid: 9105, tier: TalentTier.L2, name: "穿透强化", info: "所有英雄穿透率 +{value}%", icon: "56-Pierce-Shot",
bonuses: [{ attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
9106: {
uuid: 9106, tier: TalentTier.Specialized, name: "辅助专精", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%", icon: "38-Healing-Light",
bonuses: [
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 10 },
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 8 },
],
max_lv: 5, role: HRole.Support,
uuid: 9106, tier: TalentTier.L2, name: "暴伤强化", info: "所有英雄暴击伤害 +{value}%", icon: "37-Head-Shot",
bonuses: [{ attr: Attrs.critical_damage, mode: TalentValueMode.Flat, per_lv: 5 }],
max_lv: 5,
},
// ==================== 三阶·超强天赋(9201-9219,二阶投入 10 点解锁;幅度约为前两阶 2 倍) ====================
// ---- 全局超强(9201-9209) ----
// ==================== 第 3 层(9201-9299,第 2 层投入 5 点解锁,职业加成) ====================
9201: {
uuid: 9201, tier: TalentTier.Super, name: "神威降临", info: "所有英雄攻击 +{value}%、生命 +{value2}%", icon: "52-Might-of-Heaven",
uuid: 9201, tier: TalentTier.L3, name: "坦克专精", info: "坦克生命 +{value}%、防御 +{value2} 点", icon: "36-Guardian-Stance",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
],
max_lv: 5,
},
9202: {
uuid: 9202, tier: TalentTier.Super, name: "致命终结", info: "所有英雄暴击率 +{value}%、暴击伤害 +{value2}%", icon: "37-Head-Shot",
bonuses: [
{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 3 },
{ attr: Attrs.critical_damage, mode: TalentValueMode.Flat, per_lv: 15 },
],
max_lv: 5,
},
9203: {
uuid: 9203, tier: TalentTier.Super, name: "破甲重创", info: "所有英雄穿透率 +{value}%、穿透伤害 +{value2}%", icon: "76-Shove-Stab",
bonuses: [
{ attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 3 },
{ attr: Attrs.puncture_dmg_bonus, mode: TalentValueMode.Flat, per_lv: 10 },
],
max_lv: 5,
},
9204: {
uuid: 9204, tier: TalentTier.Super, name: "钢铁之躯", info: "所有英雄防御 +{value}%、生命 +{value2}%", icon: "45-Induration",
bonuses: [
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 20 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 6 },
],
max_lv: 5,
},
// ---- 职业超强(9211-9219,按 HRole 过滤) ----
9211: {
uuid: 9211, tier: TalentTier.Super, name: "不朽壁垒", info: "坦克防御 +{value}%、生命 +{value2}%", icon: "36-Guardian-Stance",
bonuses: [
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 40 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.defense, mode: TalentValueMode.Flat, per_lv: 10 },
],
max_lv: 5, role: HRole.Tank,
},
9212: {
uuid: 9212, tier: TalentTier.Super, name: "狂血战魂", info: "战士攻击 +{value}%、生命 +{value2}%", icon: "113-Rage",
9202: {
uuid: 9202, tier: TalentTier.L3, name: "战士专精", info: "战士攻击 +{value}%、生命 +{value2}%", icon: "1-Battle-Roar",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 10 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 6 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 6 },
],
max_lv: 5, role: HRole.Warrior,
},
9213: {
uuid: 9213, tier: TalentTier.Super, name: "影刃绝杀", info: "刺客攻击 +{value}%、暴击伤害 +{value2}%", icon: "136-Heart-Rip",
9203: {
uuid: 9203, tier: TalentTier.L3, name: "刺客专精", info: "刺客攻击 +{value}%、攻速 +{value2}%", icon: "127-Hide",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
{ attr: Attrs.critical_damage, mode: TalentValueMode.Flat, per_lv: 12 },
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.speed, mode: TalentValueMode.Flat, per_lv: 3 },
],
max_lv: 5, role: HRole.Assassin,
},
9214: {
uuid: 9214, tier: TalentTier.Super, name: "穿云贯日", info: "射手攻击 +{value}%、穿透伤害 +{value2}%", icon: "37-Head-Shot",
9204: {
uuid: 9204, tier: TalentTier.L3, name: "射手专精", info: "射手攻击 +{value}%、攻速 +{value2}%", icon: "56-Pierce-Shot",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
{ attr: Attrs.puncture_dmg_bonus, mode: TalentValueMode.Flat, per_lv: 8 },
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.speed, mode: TalentValueMode.Flat, per_lv: 3 },
],
max_lv: 5, role: HRole.Archer,
},
9215: {
uuid: 9215, tier: TalentTier.Super, name: "元素主宰", info: "法师攻击 +{value}%、冰冻率 +{value2}%", icon: "144-Cryomancy",
9205: {
uuid: 9205, tier: TalentTier.L3, name: "法师专精", info: "法师攻击 +{value}%、技能增益效果 +{value2}%", icon: "145-Electromancy",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 3 },
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 6 },
],
max_lv: 5, role: HRole.Mage,
},
9216: {
uuid: 9216, tier: TalentTier.Super, name: "圣光普照", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%", icon: "40-Holy",
9206: {
uuid: 9206, tier: TalentTier.L3, name: "辅助专精", info: "辅助治疗量 +{value}%、增益效果 +{value2}%", icon: "38-Healing-Light",
bonuses: [
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 15 },
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 12 },
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 10 },
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 6 },
],
max_lv: 5, role: HRole.Support,
},
// ==================== 第 4 层(9301-9399,第 3 层投入 5 点解锁,触发条件减少,按职业) ====================
9301: {
uuid: 9301, tier: TalentTier.L4, name: "坦克·蓄势", info: "坦克触发类技能所需次数 -{value}", icon: "36-Guardian-Stance",
bonuses: [], t_num_reduce_per_lv: 1,
max_lv: 2, role: HRole.Tank,
},
9302: {
uuid: 9302, tier: TalentTier.L4, name: "战士·蓄势", info: "战士触发类技能所需次数 -{value}", icon: "113-Rage",
bonuses: [], t_num_reduce_per_lv: 1,
max_lv: 2, role: HRole.Warrior,
},
9303: {
uuid: 9303, tier: TalentTier.L4, name: "刺客·蓄势", info: "刺客触发类技能所需次数 -{value}", icon: "136-Heart-Rip",
bonuses: [], t_num_reduce_per_lv: 1,
max_lv: 2, role: HRole.Assassin,
},
9304: {
uuid: 9304, tier: TalentTier.L4, name: "射手·蓄势", info: "射手触发类技能所需次数 -{value}", icon: "74-Satisfaction",
bonuses: [], t_num_reduce_per_lv: 1,
max_lv: 2, role: HRole.Archer,
},
9305: {
uuid: 9305, tier: TalentTier.L4, name: "法师·蓄势", info: "法师触发类技能所需次数 -{value}", icon: "144-Cryomancy",
bonuses: [], t_num_reduce_per_lv: 1,
max_lv: 2, role: HRole.Mage,
},
9306: {
uuid: 9306, tier: TalentTier.L4, name: "辅助·蓄势", info: "辅助触发类技能所需次数 -{value}", icon: "40-Holy",
bonuses: [], t_num_reduce_per_lv: 1,
max_lv: 2, role: HRole.Support,
},
// ==================== 第 5 层(9401-9499,第 4 层投入 5 点解锁,概率额外触发,按职业) ====================
9401: {
uuid: 9401, tier: TalentTier.L5, name: "坦克·余震", info: "坦克触发类技能 +{value}% 概率额外触发 1 次", icon: "36-Guardian-Stance",
bonuses: [], extra_trigger_chance_per_lv: 10,
max_lv: 5, role: HRole.Tank, prereq_uuid: 9301, prereq_lv: 1,
},
9402: {
uuid: 9402, tier: TalentTier.L5, name: "战士·余威", info: "战士触发类技能 +{value}% 概率额外触发 1 次", icon: "113-Rage",
bonuses: [], extra_trigger_chance_per_lv: 10,
max_lv: 5, role: HRole.Warrior, prereq_uuid: 9302, prereq_lv: 1,
},
9403: {
uuid: 9403, tier: TalentTier.L5, name: "刺客·残影", info: "刺客触发类技能 +{value}% 概率额外触发 1 次", icon: "136-Heart-Rip",
bonuses: [], extra_trigger_chance_per_lv: 10,
max_lv: 5, role: HRole.Assassin, prereq_uuid: 9303, prereq_lv: 1,
},
9404: {
uuid: 9404, tier: TalentTier.L5, name: "射手·连珠", info: "射手触发类技能 +{value}% 概率额外触发 1 次", icon: "74-Satisfaction",
bonuses: [], extra_trigger_chance_per_lv: 10,
max_lv: 5, role: HRole.Archer, prereq_uuid: 9304, prereq_lv: 1,
},
9405: {
uuid: 9405, tier: TalentTier.L5, name: "法师·回响", info: "法师触发类技能 +{value}% 概率额外触发 1 次", icon: "144-Cryomancy",
bonuses: [], extra_trigger_chance_per_lv: 10,
max_lv: 5, role: HRole.Mage, prereq_uuid: 9305, prereq_lv: 1,
},
9406: {
uuid: 9406, tier: TalentTier.L5, name: "辅助·福音", info: "辅助触发类技能 +{value}% 概率额外触发 1 次", icon: "40-Holy",
bonuses: [], extra_trigger_chance_per_lv: 10,
max_lv: 5, role: HRole.Support, prereq_uuid: 9306, prereq_lv: 1,
},
};
/** 天赋升级消耗:按阶位统一消耗,同一阶内每级消耗相同 */
export const TALENT_UPGRADE_COST: Record<number, number> = {
[TalentTier.Basic]: 1, // 基础天赋:1 点升 1 级
[TalentTier.Specialized]: 2, // 专精天赋:2 点升 1 级
[TalentTier.Super]: 2, // 传说天赋:2 点升 1 级
};
/** 天赋升级消耗:全部天赋统一 1 点/级 */
export const TALENT_UPGRADE_COST = 1;
/**
* 查询升级所需天赋点数
* @param tier 天赋所属阶位
* @returns 升 1 级所需点数;非法阶位返回 -1
* @param _tier 天赋所属层位(保留参数以兼容旧调用;统一点价后不再按层区分)
* @returns 升 1 级所需点数(恒为 1)
*/
export function getTalentUpgradeCost(tier: TalentTier): number {
return TALENT_UPGRADE_COST[tier] ?? -1;
export function getTalentUpgradeCost(_tier: TalentTier): number {
return TALENT_UPGRADE_COST;
}
/** 天赋等级上限(不含):等级从 0 起步,区间 0~29,共 30 级;每升 1 级产出 1 点天赋点 */
@@ -308,42 +315,51 @@ export function getTalentLevelExp(curLv: number): number {
return TALENT_LEVEL_EXP[curLv];
}
/** 阶位解锁门槛:前一阶累计投入 ≥ 该点数才能升级下一阶天赋 */
export const TALENT_TIER_UNLOCK_THRESHOLD = 10;
/** 层位解锁门槛:前一层累计投入 ≥ 该点数才能升级下一层天赋 */
export const TALENT_TIER_UNLOCK_THRESHOLD = 5;
/**
* 计算指定阶位已投入的天赋点数(按该阶统一消耗 × 等级累加)。
* Why: 阶位解锁按"实际消耗点数"而非"等级总和"口径——与玩家直觉(投入了多少点)
* 计算指定层位已投入的天赋点数(统一点价 × 等级累加)。
* Why: 层位解锁按"实际消耗点数"而非"等级总和"口径——与玩家直觉(投入了多少点)
* 及重置退款口径(resetTalents 同表对账)保持一致。
* @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级)
* @param tier 目标阶位
* @returns 该阶位全部天赋已投入的点数总和
* @param tier 目标层位
* @returns 该层位全部天赋已投入的点数总和
*/
export function getTalentTierInvestedPoints(levels: Record<number, number>, tier: TalentTier): number {
if (!levels) return 0;
const cost = getTalentUpgradeCost(tier);
if (cost <= 0) return 0;
let invested = 0;
for (const key in levels) {
const cfg = TalentSet[key];
if (!cfg || cfg.tier !== tier) continue;
invested += levels[key] * cost;
invested += levels[key] * TALENT_UPGRADE_COST;
}
return invested;
}
/**
* 判断指定阶位是否已解锁(加点入口校验用;战斗结算无需关心阶位——已投入的等级恒生效)。
* 规则:一阶恒解锁;二阶要求一阶累计投入 ≥ TALENT_TIER_UNLOCK_THRESHOLD;三阶要求二阶累计投入达标。
* 判断指定层位是否已解锁(加点入口校验用;战斗结算无需关心层位——已投入的等级恒生效)。
* 规则:第 1 层恒解锁;第 N 层要求第 N-1 层累计投入 ≥ TALENT_TIER_UNLOCK_THRESHOLD。
* @param levels 天赋等级表
* @param tier 目标阶位
* @param tier 目标层位
*/
export function isTalentTierUnlocked(levels: Record<number, number>, tier: TalentTier): boolean {
if (tier === TalentTier.Basic) return true;
if (tier === TalentTier.L1) return true;
const prevTier = (tier - 1) as TalentTier;
return getTalentTierInvestedPoints(levels, prevTier) >= TALENT_TIER_UNLOCK_THRESHOLD;
}
/**
* 判断天赋的软前置是否已满足(加点入口校验用;未满足仅限制加点,不影响已有点数的结算)。
* @param levels 天赋等级表
* @param cfg 天赋配置
* @returns 无前置声明或前置天赋等级达标时返回 true
*/
export function isTalentPrereqMet(levels: Record<number, number>, cfg: TalentConfig): boolean {
if (!cfg.prereq_uuid || !cfg.prereq_lv) return true;
return (levels?.[cfg.prereq_uuid] ?? 0) >= cfg.prereq_lv;
}
/** 天赋属性的卡面显示名(迷你卡空间有限,用短标签) */
const TALENT_ATTR_LABELS: Partial<Record<Attrs, string>> = {
[Attrs.ap]: "攻击",
@@ -376,6 +392,15 @@ const TALENT_ATTR_SUFFIX: Partial<Record<Attrs, string>> = {
*/
export function getTalentDisplayText(cfg: TalentConfig, lv: number): string {
const mult = lv > 0 ? lv : 1;
// L4/L5 效果词条:不走 bonuses,按专属字段生成文案
if (cfg.t_num_reduce_per_lv) {
const text = `触发次数 -${cfg.t_num_reduce_per_lv * mult}`;
return lv > 0 ? text : `${text}/级`;
}
if (cfg.extra_trigger_chance_per_lv) {
const text = `额外触发 +${cfg.extra_trigger_chance_per_lv * mult}%`;
return lv > 0 ? text : `${text}/级`;
}
const parts: string[] = [];
for (const b of cfg.bonuses) {
const label = TALENT_ATTR_LABELS[b.attr] ?? String(b.attr);
@@ -388,8 +413,8 @@ export function getTalentDisplayText(cfg: TalentConfig, lv: number): string {
/**
* 聚合指定职业英雄生效的全部天赋加成。
* Why: 基础/超强全局天赋全局生效,专精与超强职业天赋按 role 过滤;
* 阶位解锁只约束加点阶段,战斗加载时(Hero.load / applyHeroLevel)按当前等级直接取加成。
* Why: 全局天赋全局生效,职业天赋按 role 过滤;
* 层位解锁与软前置只约束加点阶段,战斗加载时(Hero.load / applyHeroLevel)按当前等级直接取加成。
* @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级)
* @param role 英雄职业(职业过滤依据;无 role 时只应用全局天赋)
* @returns 已解析的加成列表(等级已乘算进 value)
@@ -412,7 +437,47 @@ export function getTalentBonusesForRole(levels: Record<number, number>, role?: H
}
/**
* 将天赋加成写入英雄属性模型(在 Hero.load / MissionCardComp.applyHeroLevel 的属性组装段调用)。
* 聚合指定职业英雄的 L4 触发阈值修正量。
* @param levels 天赋等级表
* @param role 英雄职业
* @returns 触发计数阈值的总减少量(SkillTriggerHelper 端再 clamp 下限 1)
*/
export function getTalentTriggerNumReduce(levels: Record<number, number>, role?: HRole): number {
let total = 0;
if (!levels) return total;
for (const key in levels) {
const lv = levels[key];
if (lv <= 0) continue;
const cfg = TalentSet[key];
if (!cfg || !cfg.t_num_reduce_per_lv) continue;
if (cfg.role !== undefined && cfg.role !== role) continue;
total += cfg.t_num_reduce_per_lv * lv;
}
return total;
}
/**
* 聚合指定职业英雄的 L5 额外触发概率(百分点)。
* @param levels 天赋等级表
* @param role 英雄职业
* @returns 额外触发 1 次的概率(百分点,可超过 100 由消费端自行 clamp)
*/
export function getTalentExtraTriggerChance(levels: Record<number, number>, role?: HRole): number {
let total = 0;
if (!levels) return total;
for (const key in levels) {
const lv = levels[key];
if (lv <= 0) continue;
const cfg = TalentSet[key];
if (!cfg || !cfg.extra_trigger_chance_per_lv) continue;
if (cfg.role !== undefined && cfg.role !== role) continue;
total += cfg.extra_trigger_chance_per_lv * lv;
}
return total;
}
/**
* 将天赋加成写入英雄属性模型(统一由 HeroAttrsComp.applyTalent 调用)。
*
* 结算规则:
* - Percent 模式:以基础快照为基数(ap→base_ap,hp_max→base_hp,defense→def_base),按百分比放大后累加;

View File

@@ -1,6 +1,6 @@
import { v3 } from "cc"
import { v3 } from "cc"
import { BoxSet, FacSet, FightSet } from "./GameSet"
import { SkillOverrides, TGroup, SkillSet, FieldSkillSet, SkillConfig, SkillKind, FieldSkillConfig, FieldSkillType, mergeSkillParams } from "./SkillSet"
import { SkillOverrides, TGroup, SkillSet, FieldSkillSet, SkillConfig, SkillKind, FieldSkillConfig, FieldSkillType, mergeSkillParams, BuffTarget } from "./SkillSet"
import { BuffList, BuffConfig, ModOp } from "./BuffSet"
import { Attrs } from "./HeroAttrs"
@@ -424,8 +424,8 @@ export interface heroInfo {
[SkillTriggerType.FStart]?: TriggerGrouped; // 战斗开始时释放的技能配置
[SkillTriggerType.FEnd]?: TriggerGrouped; // 回合结束时释放的技能配置
[SkillTriggerType.Field]?: LvFieldEntry[]; // 驻场光环(按 lv 升序,运行时取 ≤英雄等级 的最大档)
[SkillTriggerType.Atking]?: TriggerGrouped; // 普通攻击后触发的技能配置,s_uuid: 技能id, t_num: 触发所需的普攻次数
[SkillTriggerType.Atked]?: TriggerGrouped; // 受击后触发的技能配置,s_uuid: 技能id, t_num: 触发所需的受击次数
[SkillTriggerType.Atking]?: TriggerGrouped; // 普通攻击后触发的技能配置,s_uuid: 技能id, t_num: 触发所需的普攻次数, t_chance: 触发概率(%),pity_add: 未触发时下次概率增量
[SkillTriggerType.Atked]?: TriggerGrouped; // 受击后触发的技能配置,s_uuid: 技能id, t_num: 触发所需的受击次数, t_chance: 触发概率(%),pity_add: 未触发时下次概率增量
dis?: number; // 攻击距离(像素)
speed?: number; // 移动速度(像素/秒)
dodge?: number; // 闪避率(百分点,缺省由初始化处兜底为全局默认值)
@@ -504,6 +504,11 @@ export interface LvTriggerEntry {
s_lv?: SkillLv; // 此档对应的技能等级(标注用,便于面板显示"技能Lv.x")
name?: string; // 该 s_uuid 触发技能组的对外展示名(写在第 1 档,作为组名)
t_num: number;
/** 触发概率(百分点 0~100,缺省 100 必触发;仅 Atking/Atked 攻受击触发生效) */
t_chance?: number;
/** 保底叠加:判定未触发时,为该 s_uuid 累积下次概率增量(百分点)。
* 缺省/0 = 不叠加;触发成功后清零,最终概率封顶 100 */
pity_add?: number;
overrides?: SkillOverrides;
/** 此档描述模板:缺省回退该组第 1 档 desc;由 HeroSkillText 渲染合并后实时数值。
* 显式配置后弹窗提升档(slv2+)直接展示该模板渲染结果,优先于自动 diff */
@@ -516,6 +521,20 @@ export interface LvTriggerEntry {
*/
export type TriggerGrouped = Record<number /*s_uuid*/, LvTriggerEntry[]>;
/**
* 运行时生效的触发条目(resolveTriggerByLv 产出):
* 每个 s_uuid 取 ≤lv 最大档的扁平结果,供触发器/评分/文案消费。
*/
export interface ResolvedTriggerEntry {
s_uuid: number;
t_num: number;
/** 触发概率(百分点,缺省 100 必触发;仅 Atking/Atked 生效) */
t_chance?: number;
/** 保底叠加:未触发时累积的下次概率增量(百分点,触发成功后清零) */
pity_add?: number;
overrides?: SkillOverrides;
}
/**
* 驻场光环词条:uuid 指向 FieldSkillSet 底座(类型/图标/元数据),
* value 为该词条实际生效数值(由配置处按档位指定,不再随 uuid 分档)。
@@ -585,40 +604,40 @@ export const HeroInfo: Record<number, heroInfo> = {
},
},
atked: {
6301: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "全体圣盾", t_num: 3, overrides: {"TGroup":null,"ap":10,"icon":"Stat_UnitSummonIncrease_01"}, desc: "全队获得本英雄生命最大值{ap}%的护盾" },
6412: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, t_chance: 50, pity_add: 20, overrides: {"ap":1} },
],
},
bonus: [
{ lv: 12, attrs: {"def":50}, name: "钢铁壁垒", icon: "46-Iron-Clad", },
],
info: "自身强护盾+群体小护盾,强生存",
info: "自身强护盾+防御强化",
},
// 5002 易伤剑仙 — CSV导入:击退压场+易伤放大;复刻酒仙
5002: {
uuid: 5002, name: "易伤剑仙", path: "hk3", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: HERO_ROLE_BASE[HRole.Warrior].hp + 5, ap: HERO_ROLE_BASE[HRole.Warrior].ap + 20, def: HERO_ROLE_BASE[HRole.Warrior].def + 5,
uuid: 5002, name: "易伤剑仙", path: "hk5", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: HERO_ROLE_BASE[HRole.Warrior].hp + 5, ap: HERO_ROLE_BASE[HRole.Warrior].ap + 20, def: HERO_ROLE_BASE[HRole.Warrior].def + 5,
skills: {
6004: { uuid: 6004, name: "强化普攻", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: {"ap":100}, desc: "对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"ap":120}, desc: "普通攻击造成 120% 攻击伤害" },
],
},
6101: { uuid: 6101, name: "气斩", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":150,"timed_debuff_id":7002,"icon":"Stat_AttackSpeed_02"}, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)",
6101: { uuid: 6101, name: "气斩", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":150,"timed_debuff_id":7002,"debuff_value":30,"debuff_chance":100,"debuff_duration":5}, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"buff_value":20} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":180,"bck":60,"buff_value":40}, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,击退{bck},并使目标易伤(持续{dur}秒)" },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"timed_debuff_id":7002,"debuff_value":70,"debuff_chance":100,"debuff_duration":5} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"timed_debuff_id":7002,"debuff_value":150,"debuff_chance":100,"debuff_duration":7} },
],
},
},
atking: {
6004: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "风暴打击", t_num: 3, overrides: {"ap":300,"icon":"Stat_AllUnitAttack"}, desc: "立刻打击{target},造成攻击力{ap}%伤害" },
6001: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, t_chance: 50, pity_add: 20, overrides: {"timed_debuff_id":7002,"debuff_value":10,"debuff_chance":100,"debuff_duration":5} },
],
},
bonus: [
{ lv: 12, attrs: {"hp":50}, name: "不屈战意", icon: "29-Endure", },
],
info: "易伤+风怒,近战 dps",
info: "易伤,近战 dps",
},
// 5003 眩晕暴击哥 — CSV导入:高爆发+击晕收割;复刻齐天大圣
5003: {
@@ -629,16 +648,18 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"stun":10}, desc: "普通攻击有{stun}%概率击晕敌人" },
],
},
6104: { uuid: 6104, name: "开山斧", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":300,"stun":100}, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,必定击晕",
6104: { uuid: 6104, name: "开山斧", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":250,"stun":100}, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,必定击晕",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":450,"stun":100} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":450,"stun":100} },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":300,"stun":100} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":350,"stun":100} },
],
},
},
field: [
{ lv: 4, uuids: [{ uuid: 7005, value: 0.25, name: "大圣神威" }] },
],
atking: {
6504: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, t_chance: 50, pity_add: 20, overrides: {"buff_value":30}, desc: "{t_chance}% 概率提升普通攻击 30%暴击率5秒" },
],
},
bonus: [
{ lv: 12, attrs: {"hp":50}, name: "不屈战意", icon: "29-Endure", },
],
@@ -653,16 +674,18 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"crt":10}, desc: "普通攻击有{crt}%概率暴击" },
],
},
6100: { uuid: 6100, name: "光闪", lv: 1, cd: UltCdSet[UltCdLv.VeryFast].cd, ccd: 0, overrides: {"ap":150}, desc: "斩出光闪,对{target}造成攻击力{ap}%伤害",
6150: { uuid: 6150, name: "剑刃风暴", lv: 1, cd: UltCdSet[UltCdLv.VeryFast].cd, ccd: 0, overrides: {"ap":150,"crt":50}, desc: "对{target}造成攻击力{ap}%伤害,有50% 概率暴击",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":250} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":350} },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"crt":60} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"crt":70} },
],
},
},
field: [
{ lv: 4, uuids: [{ uuid: 7004, value: 0.12, name: "剑刃风暴" }, { uuid: 7005, value: 0.2, name: "斩魂利刃" }] },
],
atking: {
6504: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, t_chance: 50, pity_add: 20, overrides: {"timed_buff_id":7012,"buff_value":10,"buff_chance":100,"buff_duration":5}, desc: "{t_chance}% 概率,普通攻击获得 10% 的暴击率" },
],
},
bonus: [
{ lv: 12, attrs: {"ap":50}, name: "致命本能", icon: "36-Guardian-Stance", },
],
@@ -674,19 +697,21 @@ export const HeroInfo: Record<number, heroInfo> = {
skills: {
6002: { uuid: 6002, name: "强化普攻", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast].cd, ccd: 0, overrides: {"ap":100}, desc: "挥刀对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_debuff_id":7007,"debuff_value":30,"debuff_chance":30}, desc: "普通攻击有{debuff_chance}%概率使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_debuff_id":7007,"debuff_value":-30,"debuff_chance":50}, desc: "普通攻击有{debuff_chance}%概率使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
],
},
6103: { uuid: 6103, name: "闪电斩", lv: 1, cd: UltCdSet[UltCdLv.VeryFast].cd, ccd: 0, overrides: {"ap":150}, desc: "高速斩击{target},造成攻击力{ap}%伤害",
6150: { uuid: 6150, name: "血刃风暴", lv: 1, cd: UltCdSet[UltCdLv.VeryFast].cd, ccd: 0, overrides: {"ap":200,"timed_debuff_id":7007,"debuff_value":-20,"debuff_chance":100,"debuff_duration":5}, desc: "高速斩击{target},造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":250,"timed_debuff_id":7007,"debuff_value":40}, desc: "高速斩击{target},造成攻击力{ap}%,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":350,"timed_debuff_id":7007,"debuff_value":60}, desc: "高速斩击{target},造成攻击力{ap}%,附带 50% 概率麻痹,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":250,"timed_debuff_id":7007,"debuff_value":-30,"debuff_chance":100,"debuff_duration":7}, desc: "高速斩击{target},造成攻击力{ap}%,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":300,"timed_debuff_id":7007,"debuff_value":-50,"debuff_chance":100,"debuff_duration":9}, desc: "高速斩击{target},造成攻击力{ap}%,附带 50% 概率麻痹,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
],
},
},
field: [
{ lv: 4, uuids: [{ uuid: 7002, value: 10, name: "攻击光环" }] },
],
atking: {
6401: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, t_chance: 50, pity_add: 20, overrides: {"buff_value":2} },
],
},
bonus: [
{ lv: 12, attrs: {"ap":50}, name: "致命本能", icon: "36-Guardian-Stance", },
],
@@ -701,16 +726,18 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"frz":10}, desc: "普通攻击有{frz}%概率 冰冻敌人" },
],
},
6150: { uuid: 6150, name: "万箭齐发·冰", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":150,"frz":30,"icon":"Stat_AttackRangeIncrease_02"}, desc: "万箭齐发,对{target}造成攻击力{ap}%伤害,{frz}%概率冰冻",
6150: { uuid: 6150, name: "寒冰箭", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":150,"frz":30,"icon":"Stat_AttackRangeIncrease_02"}, desc: "射出 6 支寒冰箭,对{target}造成攻击力{ap}%伤害,每支箭有{frz}%概率冰冻敌人",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"frz":50} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"frz":70} },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"frz":30} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"frz":30} },
],
},
},
field: [
{ lv: 4, uuids: [{ uuid: 7006, value: 0.15 }] },
],
atking: {
6401: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, t_chance: 50, pity_add: 20, overrides: {"buff_value":2,"TGroup": TGroup.Ally}, desc: "{t_chance}%概率,永久提升全队 2% 攻击力" },
],
},
bonus: [
{ lv: 12, attrs: {"ap":50}, name: "鹰眼锁定", icon: "27-Hawkeye", },
],
@@ -725,15 +752,15 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"bck_chance":20}, desc: "普通攻击有{bck_chance}%概率击退敌人" },
],
},
6200: { uuid: 6200, name: "火焰喷射", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":250,"bck":100}, desc: "喷出烈焰风暴,对{target}造成攻击力{ap}%伤害,击退{bck}",
6200: { uuid: 6200, name: "火焰喷射", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":250,"crt":50}, desc: "喷出烈焰风暴,对{target}造成攻击力{ap}%伤害,击退{bck}",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":350,"bck":100} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":450,"bck":100,"summon_count":2} },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":350,"crt":0} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":450,"crt":0,"summon_count":4} },
],
},
},
field: [
{ lv: 4, uuids: [{ uuid: 7002, value: 0.15, name: "深渊威压" }] },
{ lv: 4, uuids: [{ uuid: 7002, value: 0.15, name: "攻击光环" }] },
],
bonus: [
{ lv: 12, attrs: {"ap":50}, name: "奥术共鸣", icon: "100-Enchant-Magic", },
@@ -749,18 +776,16 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"ap":120}, desc: "普通攻击造成 120% 攻击伤害" },
],
},
6302: { uuid: 6302, name: "治疗", lv: 1, cd: UltCdSet[UltCdLv.VerySlow].cd, ccd: 0, overrides: {"ap":30}, desc: "为{target}回复攻击力{ap}%生命",
6302: { uuid: 6302, name: "治疗", lv: 1, cd: UltCdSet[UltCdLv.VerySlow].cd, ccd: 0, overrides: {"ap":30,"TGroup": TGroup.Ally}, desc: "为{target}回复攻击力{ap}%生命",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":40} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":50} },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":40,"TGroup": TGroup.Ally} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":50,"TGroup": TGroup.Ally,"timed_buff_id":7024,"buff_value":20,"buff_chance":100,"buff_duration":5} },
],
},
},
atking: {
6301: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "圣愈之盾", t_num: 3, overrides: {"ap":30}, desc: "{target}获得生命最大值{ap}%的护盾" },
],
},
field: [
{ lv: 4, uuids: [{ uuid: 7001, value: 20 }], desc: "每回合结束 休整恢复的血量值提升 20%" },
],
bonus: [
{ lv: 12, attrs: {"hp":50}, name: "守护之心", icon: "29-Endure", },
],
@@ -768,23 +793,23 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5009 甲虫领主
5009: {
uuid: 5009, name: "甲虫领主", path: "hk2", fac: FacSet.HERO, rarity: 2, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: HERO_ROLE_BASE[HRole.Tank].hp, ap: HERO_ROLE_BASE[HRole.Tank].ap, def: HERO_ROLE_BASE[HRole.Tank].def,
uuid: 5009, name: "甲虫领主", path: "hk3", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: HERO_ROLE_BASE[HRole.Tank].hp, ap: HERO_ROLE_BASE[HRole.Tank].ap, def: HERO_ROLE_BASE[HRole.Tank].def,
skills: {
6004: { uuid: 6004, name: "普攻强化", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0,
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"proc_skill":{"s_uuid":6502,"chance":10,"overrides":{"ap":5,"TGroup": TGroup.Self}}}, desc: "普通攻击有 10% 的概率为自己释放回春(5 秒恢复生命值的{ap}%)" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"ap":120}, desc: "普通攻击造成的伤害提升 20%" },
],
},
6102: { uuid: 6102, lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":250,"bck":30},
6102: { uuid: 6102, lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":250,"proc_skill":{"s_uuid":6302,"chance":100,"overrides":{"TGroup": TGroup.Self,"buff_value":10}}},
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":300,"bck":50} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":350,"bck":70} },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":300,"proc_skill":{"s_uuid":6302,"chance":100,"overrides":{"TGroup": TGroup.Self,"buff_value":15}}} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":350,"proc_skill":{"s_uuid":6302,"chance":100,"overrides":{"TGroup": TGroup.Self,"buff_value":20}}} },
],
},
},
atked: {
6302: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, overrides: {"ap":5}, desc: "为自己释放治疗,恢复生命值的{ap}%" },
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, t_chance: 30, pity_add: 20, overrides: {"ap":5}, desc: "{t_chance}%概率,恢复{ap}%生命值" },
],
},
bonus: [
@@ -794,49 +819,49 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5010 火箭哥
5010: {
uuid: 5010, name: "火箭哥", path: "ha2", fac: FacSet.HERO, rarity: 3, lv: 1, type: HType.Mid, grow_type: HGrowType.Archer, role: HRole.Archer, hp: HERO_ROLE_BASE[HRole.Archer].hp, ap: HERO_ROLE_BASE[HRole.Archer].ap, def: HERO_ROLE_BASE[HRole.Archer].def,
uuid: 5010, name: "火箭哥", path: "ha2", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Mid, grow_type: HGrowType.Archer, role: HRole.Archer, hp: HERO_ROLE_BASE[HRole.Archer].hp, ap: HERO_ROLE_BASE[HRole.Archer].ap, def: HERO_ROLE_BASE[HRole.Archer].def,
skills: {
6006: { uuid: 6006, name: "强化普攻", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast].cd, ccd: 0,
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"ap":100,"bck_chance":10} },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"crt":10} },
],
},
6151: { uuid: 6151, lv: 1, cd: UltCdSet[UltCdLv.VeryFast].cd, ccd: 0, overrides: {"ap":70,"summon_count":5,"bck_chance":100},
6151: { uuid: 6151, lv: 1, cd: UltCdSet[UltCdLv.VeryFast].cd, ccd: 0, overrides: {"ap":70,"summon_count":5,"timed_debuff_id":7006,"debuff_value":-10,"debuff_chance":100,"debuff_duration":5},
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":80,"summon_count":5,"bck_chance":100} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"bck_chance":100,"ap":100,"summon_count":6} },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":80,"timed_debuff_id":7006,"debuff_value":-20,"debuff_chance":100,"debuff_duration":5} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":100,"timed_debuff_id":7006,"debuff_value":-30,"debuff_chance":100,"debuff_duration":5} },
],
},
},
atking: {
6008: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, overrides: {"ap":120,"bck_chance":50} },
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, t_chance: 50, pity_add: 20, overrides: {"ap":120} },
],
},
bonus: [
{ lv: 12, attrs: {"ap":50}, name: "鹰眼锁定", icon: "27-Hawkeye", },
],
info: "万箭齐射+高频击退,远程压制",
info: "暴击",
},
// 5011 控血大师
5011: {
uuid: 5011, name: "控血大师", path: "hk3", fac: FacSet.HERO, rarity: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: HERO_ROLE_BASE[HRole.Tank].hp, ap: HERO_ROLE_BASE[HRole.Tank].ap, def: HERO_ROLE_BASE[HRole.Tank].def,
uuid: 5011, name: "控血大师", path: "hk2", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: HERO_ROLE_BASE[HRole.Tank].hp, ap: HERO_ROLE_BASE[HRole.Tank].ap, def: HERO_ROLE_BASE[HRole.Tank].def,
skills: {
6004: { uuid: 6004, name: "普攻强化", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0,
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"proc_skill":{"s_uuid":6502,"chance":10,"overrides":{"ap":5}}}, desc: "普攻有 10% 概率,对自己释放回春(5 秒回复5% 生命)" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"proc_skill":{"s_uuid":6502,"chance":10,"overrides":{"ap":5,"TGroup": TGroup.Self}}}, desc: "普攻有 10% 概率,对自己释放回春(5 秒回复5% 生命)" },
],
},
6517: { uuid: 6517, name: "生命爆发", lv: 1, cd: UltCdSet[UltCdLv.VerySlow].cd, ccd: 0, overrides: {"buff_value":30,"buff_duration":10}, desc: "全体友方最大生命值提升{buff_value}%,持续{dur}秒",
6103: { uuid: 6103, name: "闪电战", lv: 1, cd: UltCdSet[UltCdLv.VerySlow].cd, ccd: 0, overrides: {"ap":200,"timed_buff_id":7029,"buff_value":30,"buff_chance":100,"buff_duration":5,"buff_target": BuffTarget.Self}, desc: "对前方敌人造成攻击力{ap}%伤害,并提升自己最大生命值{buff_value}%,持续{dur}秒",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"buff_value":40,"buff_duration":10}, desc: "全体友方最大生命值提升{buff_value}%,持续{dur}秒" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"buff_value":50,"buff_duration":15}, desc: "全体友方最大生命值提升{buff_value}%,持续{dur}秒" },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":250,"timed_buff_id":7029,"buff_value":40,"buff_chance":100,"buff_duration":5,"buff_target": BuffTarget.Self} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":300,"timed_buff_id":7029,"buff_value":50,"buff_chance":100,"buff_duration":5,"buff_target": BuffTarget.Self} },
],
},
},
atked: {
6402: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 5, overrides: {"ap":1,"TGroup": TGroup.Ally,"hit_count":3}, desc: "全队永久增加 1% 生命最大值" },
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 5, t_chance: 30, pity_add: 15, overrides: {"ap":1,"TGroup": TGroup.Ally,"hit_count":3}, desc: "全队永久增加 1% 生命最大值" },
],
},
bonus: [
@@ -846,17 +871,17 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5012 疾风法师
5012: {
uuid: 5012, name: "疾风法师", path: "hm2", fac: FacSet.HERO, rarity: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: HERO_ROLE_BASE[HRole.Mage].hp, ap: HERO_ROLE_BASE[HRole.Mage].ap, def: HERO_ROLE_BASE[HRole.Mage].def,
uuid: 5012, name: "疾风法师", path: "hm2", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: HERO_ROLE_BASE[HRole.Mage].hp, ap: HERO_ROLE_BASE[HRole.Mage].ap, def: HERO_ROLE_BASE[HRole.Mage].def,
skills: {
6009: { uuid: 6009, name: "普攻强化", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0,
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"proc_skill":{"s_uuid":6507,"chance":10,"overrides":{"buff_value":30,"TGroup": TGroup.Self}}} },
],
},
6201: { uuid: 6201, name: "飓风", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":100,"summon_count":2,"proc_skill":{"s_uuid":6507,"chance":20,"overrides":{"buff_value":20}}}, desc: "召唤 {summon_count} 道飓风攻击前方敌人,每道飓风造成 {ap}%伤害,每道飓风释放时有<color=#3CE66E>20</color>% 概率增加全体队友<color=#3CE66E>20</color>% 攻速",
6201: { uuid: 6201, name: "飓风", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":100,"summon_count":2,"proc_skill":{"s_uuid":6507,"chance":20,"overrides":{"buff_value":20,"TGroup": TGroup.Ally}}}, desc: "召唤 {summon_count} 道飓风攻击前方敌人,每道飓风造成 {ap}%伤害,每道飓风释放时有<color=#3CE66E>20</color>% 概率增加全体队友<color=#3CE66E>20</color>% 攻速",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":100,"summon_count":3,"proc_skill":{"s_uuid":6507,"chance":20,"overrides":{"buff_value":20}}}, desc: "召唤 {summon_count} 道飓风攻击前方敌人,每道飓风造成 {ap}%伤害,每道飓风释放时有<color=#3CE66E>20</color>% 概率增加全体队友<color=#3CE66E>20</color>% 攻速" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":100,"summon_count":4,"proc_skill":{"s_uuid":6507,"chance":20,"overrides":{"buff_value":20}}}, desc: "召唤 {summon_count} 道飓风攻击前方敌人,每道飓风造成 {ap}%伤害,每道飓风释放时有<color=#3CE66E>20</color>% 概率增加全体队友<color=#3CE66E>20</color>% 攻速" },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":100,"summon_count":3,"proc_skill":{"s_uuid":6507,"chance":25,"overrides":{"buff_value":25,"TGroup": TGroup.Ally}}}, desc: "召唤 {summon_count} 道飓风攻击前方敌人,每道飓风造成 {ap}%伤害,每道飓风释放时有<color=#3CE66E>20</color>% 概率增加全体队友<color=#3CE66E>20</color>% 攻速" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":100,"summon_count":4,"proc_skill":{"s_uuid":6507,"chance":30,"overrides":{"buff_value":30,"TGroup": TGroup.Ally}}}, desc: "召唤 {summon_count} 道飓风攻击前方敌人,每道飓风造成 {ap}%伤害,每道飓风释放时有<color=#3CE66E>20</color>% 概率增加全体队友<color=#3CE66E>20</color>% 攻速" },
],
},
},
@@ -868,39 +893,39 @@ export const HeroInfo: Record<number, heroInfo> = {
],
info: "普攻 大招加速 buff+加速光环,加速大师",
},
// 5013 反伤哥
// 5013 生命哥
5013: {
uuid: 5013, name: "反伤哥", path: "hh2", fac: FacSet.HERO, rarity: 4, lv: 1, type: HType.Long, grow_type: HGrowType.Support, role: HRole.Support, hp: HERO_ROLE_BASE[HRole.Support].hp, ap: HERO_ROLE_BASE[HRole.Support].ap, def: HERO_ROLE_BASE[HRole.Support].def,
uuid: 5013, name: "生命哥", path: "hh2", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Support, role: HRole.Support, hp: HERO_ROLE_BASE[HRole.Support].hp, ap: HERO_ROLE_BASE[HRole.Support].ap, def: HERO_ROLE_BASE[HRole.Support].def,
skills: {
6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0,
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_buff_id":7029,"buff_value":10,"buff_chance":20} },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"ap":120} },
],
},
6515: { uuid: 6515, name: "荆棘护盾", lv: 1, cd: UltCdSet[UltCdLv.VerySlow].cd, ccd: 0, overrides: {"buff_value":20,"TGroup": TGroup.Ally,"hit_count":3,"buff_duration":5},
6517: { uuid: 6517, name: "生命护盾", lv: 1, cd: UltCdSet[UltCdLv.VerySlow].cd, ccd: 0, overrides: {"buff_value":20,"TGroup": TGroup.Ally,"hit_count":3,"buff_duration":5},
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"buff_value":25,"TGroup": TGroup.Ally,"hit_count":3,"buff_duration":10} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"buff_value":30,"buff_duration":10,"hit_count":3,"TGroup": TGroup.Ally} },
],
},
},
fstart: {
6301: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 1, overrides: {"ap":20} },
atking: {
6414: [
{ lv: 4, s_lv: SkillLv.Lv1, t_num: 3, t_chance: 30, pity_add: 15, overrides: {"buff_value":10,"TGroup": TGroup.Ally} },
],
},
bonus: [
{ lv: 12, attrs: {"hp":50}, name: "守护之心", icon: "29-Endure", },
],
info: "大招全体反伤 普攻暴击 暴击反伤辅助",
info: "大招全体生命最大值+概率全体+反伤护盾",
},
// 5014 闪避哥
5014: {
uuid: 5014, name: "闪避哥", path: "hk4", fac: FacSet.HERO, rarity: 4, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: HERO_ROLE_BASE[HRole.Warrior].hp, ap: HERO_ROLE_BASE[HRole.Warrior].ap, def: HERO_ROLE_BASE[HRole.Warrior].def,
uuid: 5014, name: "闪避哥", path: "hk4", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Warrior, role: HRole.Warrior, hp: HERO_ROLE_BASE[HRole.Warrior].hp, ap: HERO_ROLE_BASE[HRole.Warrior].ap, def: HERO_ROLE_BASE[HRole.Warrior].def,
skills: {
6004: { uuid: 6004, name: "普攻强化", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0,
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_buff_id":7030,"buff_value":10,"buff_chance":20} },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"crt":10} },
],
},
6100: { uuid: 6100, name: "寒冰斩", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":200,"timed_buff_id":7030,"buff_value":50},
@@ -916,32 +941,29 @@ export const HeroInfo: Record<number, heroInfo> = {
bonus: [
{ lv: 12, attrs: {"hp":50}, name: "不屈战意", icon: "29-Endure", },
],
info: "",
info: "强闪避",
},
// 5015 毒箭猎手 — 毒伤持续流:普攻淬毒叠层+毒雨铺DoT+攻击光环喂养毒伤
5015: {
uuid: 5015, name: "毒箭猎手", path: "ha3", fac: FacSet.HERO, rarity: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Archer, role: HRole.Archer, hp: HERO_ROLE_BASE[HRole.Archer].hp + 10, ap: HERO_ROLE_BASE[HRole.Archer].ap, def: HERO_ROLE_BASE[HRole.Archer].def,
uuid: 5015, name: "毒箭猎手", path: "ha3", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Archer, role: HRole.Archer, hp: HERO_ROLE_BASE[HRole.Archer].hp + 10, ap: HERO_ROLE_BASE[HRole.Archer].ap, def: HERO_ROLE_BASE[HRole.Archer].def,
skills: {
6007: { uuid: 6007, name: "淬毒箭", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0, overrides: {"ap":100}, desc: "射出箭矢,对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_debuff_id":7005,"debuff_value":30,"debuff_chance":30}, desc: "普通攻击有{debuff_chance}%概率使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"bck_chance":10}, desc: "普通攻击有{bck_chance}%概率击退敌人" },
],
},
6150: { uuid: 6150, name: "万箭齐发·毒", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":150,"timed_debuff_id":7005,"debuff_value":30,"debuff_chance":50,"icon":"4-Apply-Poison"}, desc: "毒箭如雨,对{target}造成攻击力{ap}%伤害,{debuff_chance}%概率使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)",
6150: { uuid: 6150, name: "万箭齐发·毒", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":150,"timed_debuff_id":7005,"debuff_value":-30,"debuff_chance":50,"icon":"4-Apply-Poison"}, desc: "毒箭如雨,对{target}造成攻击力{ap}%伤害,{debuff_chance}%概率使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"buff_value":40,"buff_chance":60} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"buff_value":60,"buff_chance":80,"hit_count":2}, desc: "毒箭如雨,对{target}造成攻击力{ap}%伤害,{buff_chance}%概率使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒),每支箭可命中{hit_count}个目标" },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"timed_debuff_id":7005,"debuff_value":-60,"debuff_chance":100,"debuff_duration":5} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"timed_debuff_id":7005,"debuff_value":-100,"debuff_chance":100,"debuff_duration":7} },
],
},
},
atking: {
6007: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "蚀骨毒箭", t_num: 3, overrides: {"ap":80,"timed_debuff_id":7005,"debuff_value":40,"debuff_chance":100}, desc: "追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
{ lv: 4, s_lv: SkillLv.Lv1, name: "蚀骨毒箭", t_num: 3, t_chance: 50, pity_add: 20, overrides: {"ap":80,"timed_debuff_id":7005,"debuff_value":40,"debuff_chance":100}, desc: "追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
],
},
field: [
{ lv: 4, uuids: [{ uuid: 7002, value: 0.1, name: "淬毒磨砺" }] },
],
bonus: [
{ lv: 12, attrs: {"ap":50}, name: "剧毒淬炼", icon: "27-Hawkeye", },
],
@@ -949,27 +971,22 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5016 连珠神射 — 暴击连射爆发流:VeryFast极速普攻+概率追射必暴箭+开局团队暴击爆发
5016: {
uuid: 5016, name: "连珠神射", path: "ha4", fac: FacSet.HERO, rarity: 4, lv: 1, type: HType.Mid, grow_type: HGrowType.Archer, role: HRole.Archer, hp: HERO_ROLE_BASE[HRole.Archer].hp, ap: HERO_ROLE_BASE[HRole.Archer].ap + 10, def: HERO_ROLE_BASE[HRole.Archer].def,
uuid: 5016, name: "连珠神射", path: "ha4", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Mid, grow_type: HGrowType.Archer, role: HRole.Archer, hp: HERO_ROLE_BASE[HRole.Archer].hp, ap: HERO_ROLE_BASE[HRole.Archer].ap + 10, def: HERO_ROLE_BASE[HRole.Archer].def,
skills: {
6008: { uuid: 6008, name: "连珠箭", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VeryFast].cd, ccd: 0, overrides: {"ap":100}, desc: "极速连射,对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"crt":20,"proc_skill":{"s_uuid":6007,"chance":20,"overrides":{"crt":100}}}, desc: "普通攻击暴击率提升{crt}%,并有概率触发连射追箭(必定暴击)" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"proc_skill":{"s_uuid":6007,"chance":20,"overrides":{"TGroup": TGroup.Enemy,"ap":80}}}, desc: "有 20%额外射击,造成 80% 伤害" },
],
},
6151: { uuid: 6151, name: "裂空箭雨", lv: 1, cd: UltCdSet[UltCdLv.VeryFast].cd, ccd: 0, overrides: {"ap":150,"crt":30,"summon_count":5}, desc: "箭雨倾泻,对{target}造成攻击力{ap}%伤害,{crt}%概率暴击",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"crt":50} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"crt":70,"summon_count":6,"hit_count":2}, desc: "箭雨倾泻,对{target}造成攻击力{ap}%伤害,{crt}%概率暴击,每支箭可命中{hit_count}个目标" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"crt":70,"summon_count":6}, desc: "箭雨倾泻,对{target}造成攻击力{ap}%伤害,{crt}%概率暴击,每支箭可命中{hit_count}个目标" },
],
},
},
fstart: {
6504: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "猎杀时刻", t_num: 1, overrides: {"buff_value":30}, desc: "战斗开始时释放暴击爆发,全体友方暴击率提升{buff_value}%,持续{dur}秒" },
],
},
field: [
{ lv: 4, uuids: [{ uuid: 7004, value: 0.15, name: "猎手集结" }] },
{ lv: 4, uuids: [{ uuid: 7004, value: 0.1, name: "猎手集结" }] },
],
bonus: [
{ lv: 12, attrs: {"ap":50}, name: "猎杀直觉", icon: "27-Hawkeye", },
@@ -978,25 +995,20 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5017 碎甲术士 — 破甲减防法师:fstart先手/普攻概率/大招必定三通道施加破甲7008+穿透光环,团队物理增伤位
5017: {
uuid: 5017, name: "碎甲术士", path: "hm3", fac: FacSet.HERO, rarity: 3, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: HERO_ROLE_BASE[HRole.Mage].hp, ap: HERO_ROLE_BASE[HRole.Mage].ap + 10, def: HERO_ROLE_BASE[HRole.Mage].def,
uuid: 5017, name: "碎甲术士", path: "hm3", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: HERO_ROLE_BASE[HRole.Mage].hp, ap: HERO_ROLE_BASE[HRole.Mage].ap + 10, def: HERO_ROLE_BASE[HRole.Mage].def,
skills: {
6012: { uuid: 6012, name: "蚀甲酸瓶", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0, overrides: {"ap":100}, desc: "掷出酸瓶,对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_debuff_id":7008,"debuff_chance":30}, desc: "普通攻击有{debuff_chance}%概率使目标破甲(防御降低,持续{dur}秒)" },
],
},
6203: { uuid: 6203, name: "碎甲能量波", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":150,"timed_debuff_id":7008}, desc: "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲(防御降低,持续{dur}秒)",
6203: { uuid: 6203, name: "碎甲能量波", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":150,"timed_debuff_id":7008,"debuff_value":-30,"debuff_chance":100,"debuff_duration":5}, desc: "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲(防御降低,持续{dur}秒)",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"buff_value":-70}, desc: "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲加深(防御大幅降低,持续{dur}秒)" },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"timed_debuff_id":7008,"debuff_value":-50,"debuff_chance":100,"debuff_duration":5} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"timed_debuff_id":7008,"debuff_value":-70,"debuff_chance":100,"debuff_duration":7}, desc: "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲加深(防御大幅降低,持续{dur}秒)" },
],
},
},
fstart: {
6012: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "先手蚀甲", t_num: 1, overrides: {"ap":150,"timed_debuff_id":7008}, desc: "战斗开始时掷出蚀甲酸瓶,对{target}造成攻击力{ap}%伤害,并必定使其破甲(防御降低,持续{dur}秒)" },
],
},
field: [
{ lv: 4, uuids: [{ uuid: 7009, value: 0.1, name: "锐矛之赐" }] },
],
@@ -1007,62 +1019,28 @@ export const HeroInfo: Record<number, heroInfo> = {
},
// 5018 雷暴领主 — 雷暴AOE爆发+麻痹控场:先手雷击+高倍率能量波
5018: {
uuid: 5018, name: "雷暴领主", path: "hm4", fac: FacSet.HERO, rarity: 5, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: HERO_ROLE_BASE[HRole.Mage].hp - 10, ap: HERO_ROLE_BASE[HRole.Mage].ap + 20, def: HERO_ROLE_BASE[HRole.Mage].def,
uuid: 5018, name: "雷暴领主", path: "hm4", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: HERO_ROLE_BASE[HRole.Mage].hp - 10, ap: HERO_ROLE_BASE[HRole.Mage].ap + 20, def: HERO_ROLE_BASE[HRole.Mage].def,
skills: {
6009: { uuid: 6009, name: "雷霆弹", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast].cd, ccd: 0, overrides: {"ap":100}, desc: "掷出雷霆弹,对{target}造成攻击力{ap}%伤害",
6013: { uuid: 6013, name: "召唤闪电", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast].cd, ccd: 0, overrides: {"ap":100}, desc: "召唤闪电,对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"para":15}, desc: "普通攻击有{para}%概率麻痹敌人,减缓其技能冷却恢复" },
],
},
6203: { uuid: 6203, name: "雷霆裂空", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":200,"para":40}, desc: "释放雷霆裂空波,对{target}造成攻击力{ap}%伤害,{para}%概率麻痹敌人",
6204: { uuid: 6204, name: "雷霆裂空", lv: 1, cd: UltCdSet[UltCdLv.Normal].cd, ccd: 0, overrides: {"ap":150,"para":50}, desc: "释放多束闪电,对{target}造成攻击力{ap}%伤害,{para}%概率麻痹敌人",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":250,"para":60}, desc: "释放雷霆裂空波,对{target}造成攻击力{ap}%伤害,{para}%概率麻痹敌人" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":300,"para":80,"hit_count":3}, desc: "释放雷霆裂空波,对{target}造成攻击力{ap}%伤害,可多次命中,{para}%概率麻痹敌人" },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"para":70}, desc: "释放雷霆裂空波,对{target}造成攻击力{ap}%伤害,{para}%概率麻痹敌人" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"para":100}, desc: "释放雷霆裂空波,对{target}造成攻击力{ap}%伤害,可多次命中,{para}%概率麻痹敌人" },
],
},
},
fstart: {
6203: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "雷击先手", t_num: 1, overrides: {"ap":150,"para":30,"icon":"100-Enchant-Magic"}, desc: "战斗开始释放雷击,对{target}造成攻击力{ap}%伤害,{para}%概率麻痹敌人" },
],
},
field: [
{ lv: 4, uuids: [{ uuid: 7003, value: 10, name: "雷云领域" }] },
{ lv: 4, uuids: [{ uuid: 7024, value: 10, name: "雷电精准" }] },
],
bonus: [
{ lv: 12, attrs: {"ap":50}, name: "奥术共鸣", icon: "100-Enchant-Magic", },
],
info: "雷暴爆发+麻痹控场,法系主C",
},
// 5019 圣盾贤者 — 护盾反伤生存法:全队圣盾+荆棘反伤+受击荆棘
5019: {
uuid: 5019, name: "圣盾贤者", path: "hm5", fac: FacSet.HERO, rarity: 2, lv: 1, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, hp: HERO_ROLE_BASE[HRole.Mage].hp + 20, ap: HERO_ROLE_BASE[HRole.Mage].ap - 5, def: HERO_ROLE_BASE[HRole.Mage].def + 5,
skills: {
6011: { uuid: 6011, name: "圣光飞瓶", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0, overrides: {"ap":100}, desc: "掷出圣光飞瓶,对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"proc_skill":{"s_uuid":6516,"chance":20,"overrides":{"timed_buff_id":7028,"buff_value":10}}}, desc: "普通攻击概率凝聚荆棘护体(受击时反弹{buff_value}%所受伤害,持续{dur}秒)" },
],
},
6301: { uuid: 6301, name: "奥术圣壁", lv: 1, cd: UltCdSet[UltCdLv.VerySlow].cd, ccd: 0, overrides: {"TGroup": TGroup.Ally,"ap":20,"timed_buff_id":7027,"buff_value":10}, desc: "为全体友方施加生命最大值{ap}%的护盾,并附加荆棘(受击时反弹{buff_value}点伤害,持续{dur}秒)",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":30,"buff_value":15}, desc: "为全体友方施加生命最大值{ap}%的护盾,并附加荆棘(受击时反弹{buff_value}点伤害,持续{dur}秒)" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":40,"buff_value":20}, desc: "为全体友方施加生命最大值{ap}%的护盾,并附加荆棘(受击时反弹{buff_value}点伤害,持续{dur}秒)" },
],
},
},
atked: {
6516: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "荆棘回应", t_num: 3, overrides: {"timed_buff_id":7028,"buff_value":10,"icon":"75-Shield-Stun"}, desc: "凝聚荆棘护体,受击时反弹{buff_value}%所受伤害,持续{dur}秒" },
],
},
field: [
{ lv: 4, uuids: [{ uuid: 7022, value: 8, name: "荆棘领域" }] },
],
bonus: [
{ lv: 12, attrs: {"hp":50}, name: "守护之心", icon: "29-Endure", },
],
info: "全队护盾+荆棘反伤,生存反击法",
},
// @hero-setup:heroes-end
/*
@@ -1142,9 +1120,9 @@ export const HeroInfo: Record<number, heroInfo> = {
};
export const HeroList: number[] = [
// 编辑器导出自动同步(共 19 个)
// 编辑器导出自动同步(共 18 个)
5001, 5002, 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010,
5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019,
5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018,
];
// @hero-setup:region-end
@@ -1164,48 +1142,48 @@ export interface HeroComboInfo {
}
/**
* 推荐阵容表(几何王国复刻版 5001~5009)。
* 推荐阵容表(基于现役 5001~5018 英雄实际技能组)。
* 设计原则:
* 1. 铁三角结构:1 前排(坦克)+ 1 增益(光环/先手)+ 1 主C
* 2. 乘区分散:攻击/暴击/暴伤各占独立乘区,组合优先覆盖不同乘区避免稀释
* 3. 发动机冗余:前排挨打/死亡触发增益,叠甲骑士(5005)死亡缠绕负责献祭点火
* 1. 铁三角结构:heroes[0] 固定为近战前排(坦克/战士),其余按站位从近到远
* 2. 乘区分散:攻击/攻速/暴击/穿透各占独立乘区,组合优先覆盖不同乘区避免稀释
* 3. 主题唯一:每套阵容围绕一个流派关键词(暴击/控制/续航/破甲/光环/消耗)组队
*/
export const HeroComboSet: Record<number, HeroComboInfo> = {
// 8001 暴击爆发流 — 暴击率与暴伤双乘区互不稀释,成型最强
// 8001 暴击爆发流 — 连珠神射暴击光环喂养双物理C,成型最强
8001: {
uuid: 8001, name: "暴击爆发流",
heroes: [5005, 5008, 5002],
desc: "叠甲骑士前排抗伤叠甲,刀刀暴击的暴击光环与眩晕暴击哥的暴伤光环组成双乘区,双近战暴击爆发收割。",
heroes: [5001, 5016, 5004],
desc: "叠甲骑士前排抗伤+受击叠防,连珠神射暴击光环覆盖全队并以极速箭雨压场,刀刀暴击近战收割残局。",
},
// 8002 冰雷控制流 — 冰冻+击晕双控场,敌人出不了手
// 8002 冰雷控制流 — 击晕+冰冻+麻痹三控链,敌人出不了手
8002: {
uuid: 8002, name: "冰雷控制流",
heroes: [5007, 5004, 5001],
desc: "微醺战神前排击退压场,冻住不谢冰冻链配合紫电魔女能量波击退击晕,击晕光环叠加,全场控制。",
heroes: [5003, 5006, 5018],
desc: "眩晕暴击哥前排开山斧必晕压阵,冻箭哥寒冰箭冰冻链,雷暴领主雷霆麻痹迟滞技能,三控交替敌人难以出手。",
},
// 8003 铁桶续航流 — 双防御前排+双治疗保血,最稳
// 8003 铁桶续航流 — 双坦克承伤+治疗保血,最稳
8003: {
uuid: 8003, name: "铁桶续航流",
heroes: [5005, 5009, 5006],
desc: "叠甲骑士与铁壳斧弟双前排叠甲叠防,光明奶爸治疗+生命光环抬全队血线,专打持久消耗战。",
heroes: [5009, 5011, 5008],
desc: "甲虫领主受击自疗顶前排,控血大师受击全队永久生命成长+大招生命爆发,团疗奶爸全体治疗+战后回复光环,专打持久战。",
},
// 8004 先手爆发流 — 入场剑气先手+能量波跟进,短回合最强
// 8004 破甲压制流 — 破甲+减攻双减益,克制高防高攻阵容
8004: {
uuid: 8004, name: "先手爆发流",
heroes: [5003, 5001, 5008],
desc: "格挡反杀入场500%剑气先手压血线,紫电魔女能量波击退跟进,眩晕暴击哥开山斧收割残局。",
uuid: 8004, name: "破甲压制流",
heroes: [5001, 5017, 5010],
desc: "碎甲术士普攻/大招双通道破甲+穿透光环放大物理伤害,火箭哥五箭弹幕必挂减攻削弱敌方输出,叠甲骑士前排兜底。",
},
// 8005 破甲推进流 — 易伤放大全队伤害,克制高血Boss
// 8005 光环覆盖流 — 闪避/攻击/攻速三光环全覆盖
8005: {
uuid: 8005, name: "破甲推进流",
heroes: [5007, 5006, 5002],
desc: "微醺战神气斩易伤放大全队伤害,光明奶爸治疗保前排,刀刀暴击吃满易伤与自身暴击完成推进。",
uuid: 8005, name: "光环覆盖流",
heroes: [5014, 5007, 5012],
desc: "闪避哥前排+闪避光环提升全队生存,击退火女攻击光环、疾风法师攻速光环持续放大全队输出,三光环增益拉满。",
},
// 8006 光环覆盖流 — 攻击/暴击/暴伤/生命光环全覆盖
// 8006 易伤消耗流 — 易伤放大出血DoT,磨血收割
8006: {
uuid: 8006, name: "光环覆盖流",
heroes: [5002, 5006, 5001],
desc: "刀刀暴击暴击+暴伤双光环、紫电魔女攻击光环、光明奶爸生命光环,四光环全覆盖持续放大全队输出。",
uuid: 8006, name: "易伤消耗流",
heroes: [5002, 5005, 5008],
desc: "易伤剑仙气斩+普攻双通道挂易伤放大全队伤害,出血贼普攻+大招叠出血磨血,团疗奶爸治疗保前排打消耗战。",
},
};
@@ -1280,9 +1258,9 @@ export const HERO_POWER_WEIGHTS = {
* 解析触发技能在目标等级下的有效条目列表。
* 每个 s_uuid 取 ≤lv 的最大档;返回扁平数组供评分/触发器消费。
*/
export function resolveTriggerByLv(grouped: TriggerGrouped | undefined, lv: number): { s_uuid: number; t_num: number; overrides?: SkillOverrides }[] {
export function resolveTriggerByLv(grouped: TriggerGrouped | undefined, lv: number): ResolvedTriggerEntry[] {
if (!grouped) return [];
const result: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[] = [];
const result: ResolvedTriggerEntry[] = [];
for (const key in grouped) {
const entries = grouped[key];
if (!entries || entries.length === 0) continue;
@@ -1290,7 +1268,7 @@ export function resolveTriggerByLv(grouped: TriggerGrouped | undefined, lv: numb
for (const e of entries) {
if (e.lv <= lv && (!pick || e.lv > pick.lv)) pick = e;
}
if (pick) result.push({ s_uuid: Number(key), t_num: pick.t_num, overrides: pick.overrides });
if (pick) result.push({ s_uuid: Number(key), t_num: pick.t_num, t_chance: pick.t_chance, pity_add: pick.pity_add, overrides: pick.overrides });
}
return result;
}
@@ -1595,7 +1573,7 @@ export interface HeroPowerDetailed {
/** 计算普通触发类技能(atking/atked/fstart/dead)的详细价值 */
function calcTriggerPowerDetailed(
triggerConfigs: { s_uuid: number, t_num?: number, overrides?: SkillOverrides }[],
triggerConfigs: ResolvedTriggerEntry[],
hero: heroInfo,
targetLv: number,
type: SkillTriggerType,
@@ -1636,7 +1614,9 @@ function calcTriggerPowerDetailed(
}
const triggerCount = getTriggerCount(type, tNum, hero, seconds);
const value = effectValue * triggerCount * scopeMul * weight;
// 期望折算:按基础触发概率缩放(保底叠加提升的是方差体验,不影响长期期望)
const chanceMul = (trig.t_chance ?? 100) / 100;
const value = effectValue * triggerCount * scopeMul * weight * chanceMul;
total += value;
items.push({ type, s_uuid: trig.s_uuid, name: skill.name, t_num: tNum, value });
}

View File

@@ -1,9 +1,23 @@
uuid,英雄名称,简化说明,rarity,类型,形态,ap,hp,def,skill[0]_lv1,skill[0]_lv2,skill[1]_lv1,skill[1]_lv8,skill[1]_lv16,atking[0]_lv4,atked[0]_lv4,fstart[0]_lv4,dead[0]_lv4,field[0]_lv4
5001,叠甲骑士,CSV导入:自身护盾+全体护盾,1,tank,近战,tank,tank+20,tank+10,6004 强化普攻(ap100;慢速1.20s;触发飞剑6001(100%)),ap120,6301 魔法盾(ap30),ap60,ap120,无,6301 全体圣盾(t_num3;ap10):全队获得本英雄生命最大值{ap}%的护盾,无,无,无
5002,易伤剑仙,CSV导入:击退压场+易伤放大;复刻酒仙,1,warrior,近战,warrior+20,warrior+5,warrior+5,6004 强化普攻(ap100;慢速1.20s),ap120,6101 气斩(ap150;附加易伤7002),,ap180;bck60,6004 风暴打击(t_num3;ap300):立刻打击{target},造成攻击力{ap}%伤害,无,无,无,无
5003,眩晕暴击哥,CSV导入:高爆发+击晕收割;复刻齐天大圣,1,warrior,近战,warrior+20,warrior+5,warrior+5,6004 强化普攻(ap100;慢速1.20s),stun10,6104 开山斧(ap300;stun100),ap450;stun100,ap450;stun100,无,无,无,无,7005 大圣神威0.25
5004,刀刀暴击,CSV导入:暴击/暴伤双光环乘区;复刻剑圣,1,assassin,中程,assassin+30,assassin,assassin,6002 强化普攻(ap100;快速0.90s),crt10,6100 光闪(ap150),ap250,ap350,无,无,无,无,7004 剑刃风暴0.12+7005 斩魂利刃0.2
5005,出血贼,CSV导入:入场先手+受击格挡;复刻剑豪,1,assassin,中程,assassin+30,assassin,assassin,6002 强化普攻(ap100;快速0.90s),附加30出血7007buff_chance30,6103 闪电斩(ap150),ap250;附加出血7007,ap350;para50;附加60出血7007,无,无,无,无,7018 连击光环1
5006,冻箭哥,CSV导入:多重单体箭,带冰冻,1,archer,远程,archer+30,archer,archer,6006 强化普攻(ap100;中速1.05s),bck10,6150 万箭齐发·冰(ap150;frz30),ap200;frz50,ap250;frz70,无,无,无,无,7006 攻速光环0.15
5007,击退火女,CSV导入:群体击退/麻痹压场;复刻深渊女王,1,mage,远程,mage+10,mage-5,mage,6010 强化普攻(ap100;慢速1.20s;bck30),bck40,6200 火焰喷射(ap250;bck100),ap350;bck100,ap450;bck100;summon2,无,无,无,无,7002 深渊威压0.15
5008,团疗奶爸,CSV导入:全体治疗续航+自身护盾,1,support,远程,support+20,support+10,support,6011 强化普攻(ap100;中速1.05s),bck10,6302 治疗(ap30),ap40,ap50,6301 圣愈之盾(t_num3;ap30):{target}获得生命最大值{ap}%的护盾,无,无,无,无
字段,5001,5002,5003,5004,5005,5006,5007,5008,5009,5010,5011,5012,5013,5014,5015,5016,5017,5018,5019
英雄名称,叠甲骑士,易伤剑仙,眩晕暴击哥,刀刀暴击,出血贼,冻箭哥,击退火女,团疗奶爸,甲虫领主,火箭哥,控血大师,疾风法师,反伤哥,闪避哥,毒箭猎手,连珠神射,碎甲术士,雷暴领主,圣盾贤者
简化说明,CSV导入:自身护盾+全体护盾,CSV导入:击退压场+易伤放大;复刻酒仙,CSV导入:高爆发+击晕收割;复刻齐天大圣,CSV导入:暴击/暴伤双光环乘区;复刻剑圣,CSV导入:入场先手+受击格挡;复刻剑豪,CSV导入:多重单体箭,带冰冻,CSV导入:群体击退/麻痹压场;复刻深渊女王,CSV导入:全体治疗续航+自身护盾,,,,,,,毒伤持续流:普攻淬毒叠层+毒雨铺DoT+攻击光环喂养毒伤,暴击连射爆发流:VeryFast极速普攻+概率追射必暴箭+开局团队暴击爆发,破甲减防法师:fstart先手/普攻概率/大招必定三通道施加破甲7008+穿透光环,团队物理增伤位,雷暴AOE爆发+麻痹控场:先手雷击+高倍率能量波,护盾反伤生存法:全队圣盾+荆棘反伤+受击荆棘
rarity,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
类型,tank,warrior,warrior,assassin,assassin,archer,mage,support,tank,archer,tank,mage,support,warrior,archer,archer,mage,mage,mage
形态,近战,近战,近战,中程,中程,远程,远程,远程,近战,中程,近战,远程,远程,近战,远程,中程,远程,远程,远程
ap,tank,warrior+20,warrior+20,assassin+30,assassin+30,archer+30,mage+10,support+20,tank,archer,tank,mage,support,warrior,archer,archer+10,mage+10,mage+20,mage-5
hp,tank+20,warrior+5,warrior+5,assassin,assassin,archer,mage-5,support+10,tank,archer,tank,mage,support,warrior,archer+10,archer,mage,mage-10,mage+20
def,tank+10,warrior+5,warrior+5,assassin,assassin,archer,mage,support,tank,archer,tank,mage,support,warrior,archer,archer,mage,mage,mage+5
dodge,,,,,,,,,,,,,,,,,,,
accuracy,,,,,,,,,,,,,,,,,,,
skill[0]_lv1,6004 强化普攻(ap100;慢速1.20s),6004 强化普攻(ap100;慢速1.20s),6004 强化普攻(ap100;慢速1.20s),6002 强化普攻(ap100;快速0.90s),6002 强化普攻(ap100;快速0.90s),6006 强化普攻(ap100;中速1.05s),6010 强化普攻(ap100;慢速1.20s),6011 强化普攻(ap100;中速1.05s),6004 普攻强化(慢速1.20s),6006 强化普攻(快速0.90s),6004 普攻强化(中速1.05s),6009 普攻强化(中速1.05s),6004 攻击(中速1.05s),6004 普攻强化(中速1.05s),6007 淬毒箭(ap100;中速1.05s),6008 连珠箭(ap100;极速0.75s),6012 蚀甲酸瓶(ap100;中速1.05s),6009 雷霆弹(ap100;快速0.90s),6011 圣光飞瓶(ap100;中速1.05s)
skill[0]_lv2,ap120,ap120,stun10,crt10,命中附加30出血7007debuff_chance30,frz10,,ap120,施法触发计时回血6502(10%;ap5),ap100,施法触发计时回血6502(10%;ap5),施法触发攻速爆发6507(10%),附加10生命爆发7029buff_chance20,附加10闪避爆发7030buff_chance20,命中附加30中毒7005debuff_chance30,crt20;施法触发蓝箭6007(20%),命中附加破甲7008debuff_chance30,para15,施法触发次级反伤6516(20%)
skill[1]_lv1,6301 魔法盾(ap30),6101 气斩(ap150;命中附加易伤7002),6104 开山斧(ap300;stun100),6100 光闪(ap150),6103 闪电斩(ap150),6150 万箭齐发·冰(ap150;frz30),6200 火焰喷射(ap250;bck100),6302 治疗(ap30),6102 火焰斩(ap250;bck30),6151 万箭齐发.火(ap70;summon5),6517 生命爆发(),6201 飓风(ap100;summon2;施法触发攻速爆发6507(20%)),6515 荆棘护盾(),6100 寒冰斩(ap200;附加50闪避爆发7030),6150 万箭齐发·毒(ap150;命中附加30中毒7005debuff_chance50),6151 裂空箭雨(ap150;crt30;summon5),6203 碎甲能量波(ap150;命中附加破甲7008),6203 雷霆裂空(ap200;para40),6301 奥术圣壁(ap20;附加10荆棘7027)
skill[1]_lv8,ap60,,ap450;stun100,ap250,ap250;命中附加40出血7007,ap200;frz50,ap350;bck100,ap40,ap300;bck50,ap80;summon5,,ap100;summon3;施法触发攻速爆发6507(20%),,ap250;附加50闪避爆发7030,ap200,ap200;crt50,ap200,ap250;para60,ap30
skill[1]_lv16,ap120,ap180;bck60,ap450;stun100,ap350,ap350;命中附加60出血7007,ap250;frz70,ap450;bck100;summon2,ap50,ap350;bck70,ap100;summon6,,ap100;summon4;施法触发攻速爆发6507(20%),,ap350;附加80闪避爆发7030,ap250,ap250;crt70;summon6,ap250,ap300;para80,ap40
atking[0]_lv4,无,6004 风暴打击(t_num3;ap300):立刻打击{target},造成攻击力{ap}%伤害,无,无,无,无,无,6301 圣愈之盾(t_num3;ap30):{target}获得生命最大值{ap}%的护盾,无,6008 红箭(t_num3;ap120):,无,无,无,无,6007 蚀骨毒箭(t_num3;ap80):追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒),无,无,无,无
atked[0]_lv4,6302 治疗(t_num3;概率50%;ap10):有 {t_chance}% 的概率,治疗自己 {ap}%生命最大值,无,无,无,无,无,无,无,6302 治疗(t_num3;ap5):为自己释放治疗,恢复生命值的{ap}%,无,6402 生命强化(t_num5;ap1):全队永久增加 1% 生命最大值,无,无,无,无,无,无,无,6516 荆棘回应(t_num3):凝聚荆棘护体,受击时反弹{buff_value}%所受伤害,持续{dur}秒
fstart[0]_lv4,无,无,无,无,无,无,无,无,无,无,无,无,6301 护盾(t_num1;ap20):,无,无,6504 猎杀时刻(t_num1):战斗开始时释放暴击爆发,全体友方暴击率提升{buff_value}%,持续{dur}秒,6012 先手蚀甲(t_num1;ap150):战斗开始时掷出蚀甲酸瓶,对{target}造成攻击力{ap}%伤害,并必定使其破甲(防御降低,持续{dur}秒),6203 雷击先手(t_num1;ap150):战斗开始释放雷击,对{target}造成攻击力{ap}%伤害,{para}%概率麻痹敌人,无
fend[0]_lv4,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无
dead[0]_lv4,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无,无
field[0]_lv4,无,无,7005 大圣神威0.25,7004 剑刃风暴0.12+7005 斩魂利刃0.2,7002 攻击光环10,7006 攻速光环0.15,7002 深渊威压0.15,无,无,无,无,7006 攻速光环10,无,7023 闪避光环10,7002 淬毒磨砺0.1,7004 猎手集结0.15,7009 锐矛之赐0.1,7003 雷云领域10,7022 荆棘领域8
bonus,lv12 def+50%,lv12 hp+50%,lv12 hp+50%,lv12 ap+50%,lv12 ap+50%,lv12 ap+50%,lv12 ap+50%,lv12 hp+50%,lv12 def+50%,lv12 ap+50%,lv12 def+50%,lv12 ap+50%,lv12 hp+50%,lv12 hp+50%,lv12 ap+50%,lv12 ap+50%,lv12 ap+50%,lv12 ap+50%,lv12 hp+50%
1 uuid 字段 英雄名称 5001 简化说明 5002 rarity 5003 类型 5004 形态 5005 ap 5006 hp 5007 def 5008 skill[0]_lv1 5009 skill[0]_lv2 5010 skill[1]_lv1 5011 skill[1]_lv8 5012 skill[1]_lv16 5013 atking[0]_lv4 5014 atked[0]_lv4 5015 fstart[0]_lv4 5016 dead[0]_lv4 5017 field[0]_lv4 5018 5019
2 5001 英雄名称 叠甲骑士 叠甲骑士 CSV导入:自身护盾+全体护盾 易伤剑仙 1 眩晕暴击哥 tank 刀刀暴击 近战 出血贼 tank 冻箭哥 tank+20 击退火女 tank+10 团疗奶爸 6004 强化普攻(ap100;慢速1.20s;触发飞剑6001(100%)) 甲虫领主 ap120 火箭哥 6301 魔法盾(ap30) 控血大师 ap60 疾风法师 ap120 反伤哥 无 闪避哥 6301 全体圣盾(t_num3;ap10):全队获得本英雄生命最大值{ap}%的护盾 毒箭猎手 无 连珠神射 无 碎甲术士 无 雷暴领主 圣盾贤者
3 5002 简化说明 易伤剑仙 CSV导入:自身护盾+全体护盾 CSV导入:击退压场+易伤放大;复刻酒仙 CSV导入:击退压场+易伤放大;复刻酒仙 1 CSV导入:高爆发+击晕收割;复刻齐天大圣 warrior CSV导入:暴击/暴伤双光环乘区;复刻剑圣 近战 CSV导入:入场先手+受击格挡;复刻剑豪 warrior+20 CSV导入:多重单体箭,带冰冻 warrior+5 CSV导入:群体击退/麻痹压场;复刻深渊女王 warrior+5 CSV导入:全体治疗续航+自身护盾 6004 强化普攻(ap100;慢速1.20s) ap120 6101 气斩(ap150;附加易伤7002) ap180;bck60 6004 风暴打击(t_num3;ap300):立刻打击{target},造成攻击力{ap}%伤害 无 毒伤持续流:普攻淬毒叠层+毒雨铺DoT+攻击光环喂养毒伤 无 暴击连射爆发流:VeryFast极速普攻+概率追射必暴箭+开局团队暴击爆发 无 破甲减防法师:fstart先手/普攻概率/大招必定三通道施加破甲7008+穿透光环,团队物理增伤位 无 雷暴AOE爆发+麻痹控场:先手雷击+高倍率能量波 护盾反伤生存法:全队圣盾+荆棘反伤+受击荆棘
4 5003 rarity 眩晕暴击哥 1 CSV导入:高爆发+击晕收割;复刻齐天大圣 1 1 1 warrior 1 近战 1 warrior+20 1 warrior+5 1 warrior+5 1 6004 强化普攻(ap100;慢速1.20s) 1 stun10 1 6104 开山斧(ap300;stun100) 1 ap450;stun100 1 ap450;stun100 1 无 1 无 1 无 1 无 1 7005 大圣神威0.25 1 1
5 5004 类型 刀刀暴击 tank CSV导入:暴击/暴伤双光环乘区;复刻剑圣 warrior 1 warrior assassin assassin 中程 assassin assassin+30 archer assassin mage assassin support 6002 强化普攻(ap100;快速0.90s) tank crt10 archer 6100 光闪(ap150) tank ap250 mage ap350 support 无 warrior 无 archer 无 archer 无 mage 7004 剑刃风暴0.12+7005 斩魂利刃0.2 mage mage
6 5005 形态 出血贼 近战 CSV导入:入场先手+受击格挡;复刻剑豪 近战 1 近战 assassin 中程 中程 中程 assassin+30 远程 assassin 远程 assassin 远程 6002 强化普攻(ap100;快速0.90s) 近战 附加30出血7007buff_chance30 中程 6103 闪电斩(ap150) 近战 ap250;附加出血7007 远程 ap350;para50;附加60出血7007 远程 无 近战 无 远程 无 中程 无 远程 7018 连击光环1 远程 远程
7 5006 ap 冻箭哥 tank CSV导入:多重单体箭,带冰冻 warrior+20 1 warrior+20 archer assassin+30 远程 assassin+30 archer+30 archer+30 archer mage+10 archer support+20 6006 强化普攻(ap100;中速1.05s) tank bck10 archer 6150 万箭齐发·冰(ap150;frz30) tank ap200;frz50 mage ap250;frz70 support 无 warrior 无 archer 无 archer+10 无 mage+10 7006 攻速光环0.15 mage+20 mage-5
8 5007 hp 击退火女 tank+20 CSV导入:群体击退/麻痹压场;复刻深渊女王 warrior+5 1 warrior+5 mage assassin 远程 assassin mage+10 archer mage-5 mage-5 mage support+10 6010 强化普攻(ap100;慢速1.20s;bck30) tank bck40 archer 6200 火焰喷射(ap250;bck100) tank ap350;bck100 mage ap450;bck100;summon2 support 无 warrior 无 archer+10 无 archer 无 mage 7002 深渊威压0.15 mage-10 mage+20
9 5008 def 团疗奶爸 tank+10 CSV导入:全体治疗续航+自身护盾 warrior+5 1 warrior+5 support assassin 远程 assassin support+20 archer support+10 mage support support 6011 强化普攻(ap100;中速1.05s) tank bck10 archer 6302 治疗(ap30) tank ap40 mage ap50 support 6301 圣愈之盾(t_num3;ap30):{target}获得生命最大值{ap}%的护盾 warrior 无 archer 无 archer 无 mage 无 mage mage+5
10 dodge
11 accuracy
12 skill[0]_lv1 6004 强化普攻(ap100;慢速1.20s) 6004 强化普攻(ap100;慢速1.20s) 6004 强化普攻(ap100;慢速1.20s) 6002 强化普攻(ap100;快速0.90s) 6002 强化普攻(ap100;快速0.90s) 6006 强化普攻(ap100;中速1.05s) 6010 强化普攻(ap100;慢速1.20s) 6011 强化普攻(ap100;中速1.05s) 6004 普攻强化(慢速1.20s) 6006 强化普攻(快速0.90s) 6004 普攻强化(中速1.05s) 6009 普攻强化(中速1.05s) 6004 攻击(中速1.05s) 6004 普攻强化(中速1.05s) 6007 淬毒箭(ap100;中速1.05s) 6008 连珠箭(ap100;极速0.75s) 6012 蚀甲酸瓶(ap100;中速1.05s) 6009 雷霆弹(ap100;快速0.90s) 6011 圣光飞瓶(ap100;中速1.05s)
13 skill[0]_lv2 ap120 ap120 stun10 crt10 命中附加30出血7007debuff_chance30 frz10 ap120 施法触发计时回血6502(10%;ap5) ap100 施法触发计时回血6502(10%;ap5) 施法触发攻速爆发6507(10%) 附加10生命爆发7029buff_chance20 附加10闪避爆发7030buff_chance20 命中附加30中毒7005debuff_chance30 crt20;施法触发蓝箭6007(20%) 命中附加破甲7008debuff_chance30 para15 施法触发次级反伤6516(20%)
14 skill[1]_lv1 6301 魔法盾(ap30) 6101 气斩(ap150;命中附加易伤7002) 6104 开山斧(ap300;stun100) 6100 光闪(ap150) 6103 闪电斩(ap150) 6150 万箭齐发·冰(ap150;frz30) 6200 火焰喷射(ap250;bck100) 6302 治疗(ap30) 6102 火焰斩(ap250;bck30) 6151 万箭齐发.火(ap70;summon5) 6517 生命爆发() 6201 飓风(ap100;summon2;施法触发攻速爆发6507(20%)) 6515 荆棘护盾() 6100 寒冰斩(ap200;附加50闪避爆发7030) 6150 万箭齐发·毒(ap150;命中附加30中毒7005debuff_chance50) 6151 裂空箭雨(ap150;crt30;summon5) 6203 碎甲能量波(ap150;命中附加破甲7008) 6203 雷霆裂空(ap200;para40) 6301 奥术圣壁(ap20;附加10荆棘7027)
15 skill[1]_lv8 ap60 ap450;stun100 ap250 ap250;命中附加40出血7007 ap200;frz50 ap350;bck100 ap40 ap300;bck50 ap80;summon5 ap100;summon3;施法触发攻速爆发6507(20%) ap250;附加50闪避爆发7030 ap200 ap200;crt50 ap200 ap250;para60 ap30
16 skill[1]_lv16 ap120 ap180;bck60 ap450;stun100 ap350 ap350;命中附加60出血7007 ap250;frz70 ap450;bck100;summon2 ap50 ap350;bck70 ap100;summon6 ap100;summon4;施法触发攻速爆发6507(20%) ap350;附加80闪避爆发7030 ap250 ap250;crt70;summon6 ap250 ap300;para80 ap40
17 atking[0]_lv4 无 6004 风暴打击(t_num3;ap300):立刻打击{target},造成攻击力{ap}%伤害 无 无 无 无 无 6301 圣愈之盾(t_num3;ap30):{target}获得生命最大值{ap}%的护盾 无 6008 红箭(t_num3;ap120): 无 无 无 无 6007 蚀骨毒箭(t_num3;ap80):追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒) 无 无 无 无
18 atked[0]_lv4 6302 治疗(t_num3;概率50%;ap10):有 {t_chance}% 的概率,治疗自己 {ap}%生命最大值 无 无 无 无 无 无 无 6302 治疗(t_num3;ap5):为自己释放治疗,恢复生命值的{ap}% 无 6402 生命强化(t_num5;ap1):全队永久增加 1% 生命最大值 无 无 无 无 无 无 无 6516 荆棘回应(t_num3):凝聚荆棘护体,受击时反弹{buff_value}%所受伤害,持续{dur}秒
19 fstart[0]_lv4 无 无 无 无 无 无 无 无 无 无 无 无 6301 护盾(t_num1;ap20): 无 无 6504 猎杀时刻(t_num1):战斗开始时释放暴击爆发,全体友方暴击率提升{buff_value}%,持续{dur}秒 6012 先手蚀甲(t_num1;ap150):战斗开始时掷出蚀甲酸瓶,对{target}造成攻击力{ap}%伤害,并必定使其破甲(防御降低,持续{dur}秒) 6203 雷击先手(t_num1;ap150):战斗开始释放雷击,对{target}造成攻击力{ap}%伤害,{para}%概率麻痹敌人 无
20 fend[0]_lv4 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无
21 dead[0]_lv4 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无 无
22 field[0]_lv4 无 无 7005 大圣神威0.25 7004 剑刃风暴0.12+7005 斩魂利刃0.2 7002 攻击光环10 7006 攻速光环0.15 7002 深渊威压0.15 无 无 无 无 7006 攻速光环10 无 7023 闪避光环10 7002 淬毒磨砺0.1 7004 猎手集结0.15 7009 锐矛之赐0.1 7003 雷云领域10 7022 荆棘领域8
23 bonus lv12 def+50% lv12 hp+50% lv12 hp+50% lv12 ap+50% lv12 ap+50% lv12 ap+50% lv12 ap+50% lv12 hp+50% lv12 def+50% lv12 ap+50% lv12 def+50% lv12 ap+50% lv12 hp+50% lv12 hp+50% lv12 ap+50% lv12 ap+50% lv12 ap+50% lv12 ap+50% lv12 hp+50%

View File

@@ -13,7 +13,6 @@ import { Attrs } from "../common/config/HeroAttrs";
import { MoveComp } from "./MoveComp";
import { mLogger } from "../common/Logger";
import { FieldSkillType } from "../common/config/SkillSet";
import { applyTalentBonuses } from "../common/config/TalentSet";
/** 英雄实体:负责英雄节点创建、属性初始化、入场动画与销毁流程 */
@ecs.register(`Hero`)
@@ -173,16 +172,6 @@ export class Hero extends ecs.Entity {
model.skills[skill.uuid] = { ...skill, lv: Math.min(FightSet.HERO_SKILL_MAX_LV, Math.max(0, skill.lv + hero_lv - 2)), ccd: 0, overrides: resolveSkillOverrides(skill, hero_lv) };
}
// 按当前英雄等级 resolve 触发技能/光环到运行时缓存,供 SkillTriggerHelper 消费
model.runtime_call = resolveTriggerByLv(model.call, hero_lv);
model.runtime_dead = resolveTriggerByLv(model.dead, hero_lv);
model.runtime_fstart = resolveTriggerByLv(model.fstart, hero_lv);
model.runtime_fend = resolveTriggerByLv(model.fend, hero_lv);
model.runtime_atking = resolveTriggerByLv(model.atking, hero_lv);
model.runtime_atked = resolveTriggerByLv(model.atked, hero_lv);
model.runtime_field = resolveFieldByLv(model.field, hero_lv);
// 复活已纳入 dead 触发组,无需单独解析 revive
// 应用额外属性加成(成长类型每级递增 + bonus 档位累加)
// 成长增量:仅英雄生效(model.fac 已为 HERO),按 grow_type 递增曲线累计
const grow = calcGrowBonus(hero, hero_lv);
@@ -211,12 +200,23 @@ export class Hero extends ecs.Entity {
// lv12 职业专项已移除:单项属性强化统一由 bonus 配置(如 attrs: { ap: 50 })按等级注入
// 天赋加成层:全局天赋(一阶基础/三阶超强全局)全局生效,职业天赋(二阶专精/三阶超强职业)按 role 过滤(仅英雄生效,怪物跳过)
// Why: 放在成长/bonus 之后、光环乘区之后,以基础快照为基数叠加,避免与乘区叠加成乘法膨胀
// 天赋加成层:全局天赋(L1/L2)全局生效,职业天赋(L3-L5)按 role 过滤(仅英雄生效,怪物跳过)
// Why: 放在成长/bonus 之后、runtime_* resolve 之前——触发修正需先写入模型,L4/L5 才能影响阈值判定;
// 属性加成以基础快照为基数叠加,避免与光环乘区叠加成乘法膨胀
if (model.fac === FacSet.HERO) {
applyTalentBonuses(model, smc.getTalentLevels(), model.role);
model.applyTalent();
}
// 按当前英雄等级 resolve 触发技能/光环到运行时缓存,供 SkillTriggerHelper 消费
model.runtime_call = resolveTriggerByLv(model.call, hero_lv);
model.runtime_dead = resolveTriggerByLv(model.dead, hero_lv);
model.runtime_fstart = resolveTriggerByLv(model.fstart, hero_lv);
model.runtime_fend = resolveTriggerByLv(model.fend, hero_lv);
model.runtime_atking = resolveTriggerByLv(model.atking, hero_lv);
model.runtime_atked = resolveTriggerByLv(model.atked, hero_lv);
model.runtime_field = resolveFieldByLv(model.field, hero_lv);
// 复活已纳入 dead 触发组,无需单独解析 revive
// 缓存技能射程等派生数据,减少战斗帧内重复计算
model.updateSkillDistanceCache();

View File

@@ -1,10 +1,11 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType, TriggerGrouped, LvFieldEntry, LvBonusAttrsEntry, FieldEntry, HRole } from "../common/config/heroSet";
import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType, TriggerGrouped, LvFieldEntry, LvBonusAttrsEntry, FieldEntry, HRole, ResolvedTriggerEntry } from "../common/config/heroSet";
import { mLogger } from "../common/Logger";
import { FacSet, FightSet } from "../common/config/GameSet";
import { FieldSkillSet, FieldSkillType, SkillOverrides } from "../common/config/SkillSet";
import { smc } from "../common/SingletonModuleComp";
import { Attrs } from "../common/config/HeroAttrs";
import { applyTalentBonuses, getTalentExtraTriggerChance, getTalentTriggerNumReduce } from "../common/config/TalentSet";
import { FieldSkillHelper } from "./FieldSkillHelper";
import { BuffComp } from "./BuffComp";
import { BuffList, ModOp } from "../common/config/BuffSet";
@@ -63,20 +64,26 @@ export class HeroAttrsComp extends ecs.Comp {
// ==================== 运行时解析缓存(按当前等级 resolve 后的有效列表) ====================
/** 当前等级下生效的攻击触发条目(供 SkillTriggerHelper 消费) */
runtime_atking?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
runtime_atking?: ResolvedTriggerEntry[];
/** 当前等级下生效的受击触发条目 */
runtime_atked?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
runtime_atked?: ResolvedTriggerEntry[];
/** 当前等级下生效的召唤触发条目 */
runtime_call?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
runtime_call?: ResolvedTriggerEntry[];
/** 当前等级下生效的死亡触发条目 */
runtime_dead?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
runtime_dead?: ResolvedTriggerEntry[];
/** 当前等级下生效的战前触发条目 */
runtime_fstart?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
runtime_fstart?: ResolvedTriggerEntry[];
/** 当前等级下生效的回合结束触发条目 */
runtime_fend?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
runtime_fend?: ResolvedTriggerEntry[];
/** 当前等级下生效的驻场光环词条列表 */
runtime_field?: FieldEntry[];
// ==================== 触发概率保底缓存(key: s_uuid, value: 已累积的概率增量百分点) ====================
/** 攻击触发未命中时累积的下次概率增量(pity_add 驱动,触发成功后清零) */
pity_atking: Record<number, number> = {};
/** 受击触发未命中时累积的下次概率增量(pity_add 驱动,触发成功后清零) */
pity_atked: Record<number, number> = {};
// ==================== 特殊属性 ====================
critical: number = 0; // 暴击率
critical_res: number = 0; // 暴击抗性
@@ -101,6 +108,10 @@ export class HeroAttrsComp extends ecs.Comp {
thorns_dmg: number = 0; // 反伤伤害(固定值,受到攻击时对攻击者造成等量伤害)
thorns_rate: number = 0; // 反伤比例(百分点;反伤值 = 实际受到伤害 * thorns_rate / 100 + thorns_dmg)
// ==================== 天赋触发修正(L4/L5 写入,SkillTriggerHelper 消费) ====================
talent_t_num_reduce: number = 0; // L4:触发类技能计数阈值减少量(下限 1 由消费端 clamp)
talent_extra_trigger_chance: number = 0; // L5:触发类技能额外触发 1 次的概率(百分点)
revived_count: number = 0; // 已复活次数(从 dead 触发配置的 revive_count 限制)
death_count: number = 0; // 本回合死亡次数(用于 dead 触发统计)
invincible_time: number = 0;// 无敌时间
@@ -154,6 +165,20 @@ export class HeroAttrsComp extends ecs.Comp {
*/
initAttrs() {
}
/**
* 天赋统一应用入口:写入属性加成(L1-L3)与触发修正(L4/L5)。
* Why: 收敛到属性组件单点调用,Hero.load 与局内升级重算(applyHeroLevel)不再各自 import TalentSet;
* 触发修正必须在 runtime_* 触发缓存 resolve 之前写入,才能影响 SkillTriggerHelper 的阈值判定。
* 调用前请确保触发修正字段已归零(升级重算路径由调用方归零,保证幂等)。
*/
applyTalent(): void {
if (this.fac !== FacSet.HERO) return;
const levels = smc.getTalentLevels();
this.talent_t_num_reduce = getTalentTriggerNumReduce(levels, this.role);
this.talent_extra_trigger_chance = getTalentExtraTriggerChance(levels, this.role);
applyTalentBonuses(this, levels, this.role);
}
/*******************基础属性管理********************/
add_hp(value: number) {
@@ -740,6 +765,9 @@ export class HeroAttrsComp extends ecs.Comp {
this.runtime_fstart = undefined;
this.runtime_fend = undefined;
this.runtime_field = undefined;
// 清空触发概率保底叠加缓存
this.pity_atking = {};
this.pity_atked = {};
this.critical = 0;
this.critical_res = 0;
this.dodge = 0;
@@ -765,6 +793,8 @@ export class HeroAttrsComp extends ecs.Comp {
this.buff_power = 0;
this.thorns_dmg = 0;
this.thorns_rate = 0;
this.talent_t_num_reduce = 0;
this.talent_extra_trigger_chance = 0;
this.boom = false;
// 重置技能距离缓存

View File

@@ -236,6 +236,21 @@ export class Monster extends ecs.Entity {
if (testSkills.fstart !== undefined) model.fstart = testSkills.fstart;
if (testSkills.call !== undefined) model.call = testSkills.call;
}
// Boss 大招注入:在保留普攻的基础上追加(多个大招共存,由 SCastSystem 按 CD 轮转施放,
// 含镜头聚焦 + 技能名横幅表现,与英雄大招同一条施法链路)
const ults = (this as any)._testSkills?.ults;
if (ults && ults.length > 0) {
for (const ult of ults) {
if (!ult || !ult.s_uuid) continue;
model.skills[ult.s_uuid] = {
uuid: ult.s_uuid,
cd: ult.cd ?? 8,
lv: 1,
ccd: 0,
overrides: ult.overrides
};
}
}
// 按怪物等级 resolve 触发技能到运行时缓存(怪物无 lv 成长,恒用 mon_lv)
model.runtime_call = resolveTriggerByLv(model.call, mon_lv);

View File

@@ -1,8 +1,8 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3, Prefab, instantiate, tween, Node } from "cc";
import { Vec3, Prefab, instantiate, tween, Node, UITransform } from "cc";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { DTType, RType, SkillConfig, SkillKind, SkillSet, TGroup, SkillOverrides, mergeSkillParams } from "../common/config/SkillSet";
import { DTType, RType, SkillConfig, SkillKind, SkillSet, SummonTMode, TGroup, SkillOverrides, mergeSkillParams } from "../common/config/SkillSet";
import { Skill } from "../skill/Skill";
import { SkillView } from "../skill/SkillView";
import { smc } from "../common/SingletonModuleComp";
@@ -20,12 +20,14 @@ import { BuffList } from "../common/config/BuffSet";
import { CameraFX } from "../common/CameraFX";
import { ScreenShake } from "../common/ScreenShake";
/** 召唤参数:数量 / XY 步进偏移 / 生成间隔,均由技能 prefab 上的 SkillView 配置 */
/** 召唤参数:数量 / XY 步进偏移 / 生成间隔 / 目标选择模式,均由技能 prefab 上的 SkillView 配置 */
export interface SummonParams {
count: number;
x_offset: number;
y_offset: number;
interval: number;
/** 多次施放的目标选择模式(同一敌人/随机敌人/避免相同敌人) */
target_mode: SummonTMode;
}
/**
@@ -137,12 +139,12 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
mockAttrs.type = HType.Long; // 假定为远程,拥有较长索敌范围
mockAttrs.dis = 2000; // 给予全屏以上的索敌范围
// 伪造一个 view 供找敌逻辑使用,位置为 spawnPos(random/unique 模式实时索敌也复用)
const mockView = {
node: { position: spawnPos }
} as any;
let targetPos: Vec3 | null = null;
if (!isFriendly) {
// 伪造一个 view 供找敌逻辑使用,位置为 spawnPos
const mockView = {
node: { position: spawnPos }
} as any;
// 获取全屏索敌范围
const maxRange = this.resolveMaxCastRange(mockAttrs, mockAttrs.type as HType);
const target = this.findNearestEnemyInRange(mockAttrs, mockView, maxRange);
@@ -157,6 +159,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
}
mLogger.log(this.debugMode, "SCastSystem", `[卡牌施法] 释放技能「${config.name}」(s_uuid:${s_uuid}) rarity:${rarity} castTimes:${castTimes} targetPos:${targetPos ? `(${targetPos.x.toFixed(0)},${targetPos.y.toFixed(0)})` : "none"} isFriendly:${isFriendly}`);
// unique 模式去重记录:本次施法序列内已锁定过的敌人 eid
const usedTargetEids = new Set<number>();
for (let i = 0; i < castTimes; i++) {
// 多发/召唤按间隔错峰执行:i=0 立即,其余延迟 i*castInterval(召唤间隔由 prefab 配置)
const exec = () => {
@@ -167,7 +171,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (friendlyTargets.length === 0) return;
this.applyFriendlySkillEffects(s_uuid, rarity, config, null as any, mockAttrs, friendlyTargets, spawnPos);
} else {
const enemyTargetPos = this.resolveRepeatCastTargetPos(targetPos, i, summon);
const enemyTargetPos = this.resolveSummonCastTargetPos(config, mockAttrs, mockView, targetPos, i, summon, usedTargetEids);
this.createSkillEntityForCard(s_uuid, rarity, mockAttrs, spawnPos, enemyTargetPos, i, overrides, summon);
}
};
@@ -214,14 +218,14 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
/**
* 解析单次施法的召唤参数。
* 数量优先级:合并后配置上的 summon_count(heroSet overrides 可覆写) > prefab 上 SkillView.summon_count;
* XY 偏移与间隔仅由 prefab 配置。
* XY 偏移、间隔与目标选择模式仅由 prefab 配置。
*/
private resolveSummonParams(config: SkillConfig): SummonParams {
const fromPrefab = this.readSummonParamsFromPrefab(config.sp_name);
const count = typeof config.summon_count === "number"
? Math.max(1, Math.floor(config.summon_count))
: fromPrefab.count;
return { count, x_offset: fromPrefab.x_offset, y_offset: fromPrefab.y_offset, interval: fromPrefab.interval };
return { count, x_offset: fromPrefab.x_offset, y_offset: fromPrefab.y_offset, interval: fromPrefab.interval, target_mode: fromPrefab.target_mode };
}
/**
@@ -232,7 +236,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
const cached = this.summonParamsCache.get(sp_name);
if (cached) return cached;
const path = `game/skill/atk/${sp_name}`;
let params: SummonParams = { count: 1, x_offset: 0, y_offset: 0, interval: 0 };
let params: SummonParams = { count: 1, x_offset: 0, y_offset: 0, interval: 0, target_mode: SummonTMode.same };
const prefab = oops.res.get(path, Prefab) as Prefab | null;
if (prefab) {
const view = prefab.data?.getComponent(SkillView);
@@ -242,6 +246,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
x_offset: view.summon_x_offset,
y_offset: view.summon_y_offset,
interval: view.summon_interval,
target_mode: view.summon_target_mode ?? SummonTMode.same,
};
}
this.summonParamsCache.set(sp_name, params);
@@ -355,6 +360,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
}
heroView.skill_name(val, s_uuid)
mLogger.log(this.debugMode, "SCastSystem", `[触发施法] ${heroAttrs.hero_name}(uuid:${heroAttrs.hero_uuid}) 触发类型:${triggerType ?? "none"} 释放技能「${config.name}」(s_uuid:${s_uuid}) lv:${skillLv} ${isFriendly ? "友方" : "对敌"} castTimes:${castTimes}`);
// unique 模式去重记录:本次施法序列内已锁定过的敌人 eid
const usedTargetEids = new Set<number>();
for (let i = 0; i < castTimes; i++) {
if (!heroView.node || !heroView.node.isValid) return;
// 多发/召唤按间隔错峰执行:i=0 立即,其余延迟 i*castInterval(召唤间隔由 prefab 配置)
@@ -367,7 +374,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
this.applyFriendlySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, friendlyTargets, null);
return;
}
const enemyTargetPos = this.resolveRepeatCastTargetPos(targetPos, i, summon);
const enemyTargetPos = this.resolveSummonCastTargetPos(config, heroAttrs, heroView, targetPos, i, summon, usedTargetEids);
this.applyEnemySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, enemyTargetPos, i, overrides, summon);
}, i * castInterval);
}
@@ -469,6 +476,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
// 注意:这里仍然是基于时间的延迟,受帧率波动影响。
// 若需精确同步,建议在动画中添加帧事件并在 HeroViewComp 中监听。
const delay = config.ready > 0 ? config.ready : FightSet.SKILL_CAST_DELAY;
// unique 模式去重记录:本次施法序列内已锁定过的敌人 eid
const usedTargetEids = new Set<number>();
heroView.scheduleOnce(() => {
if (!smc.mission.play || smc.mission.pause || !smc.mission.in_fight) return;
@@ -484,7 +493,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
this.applyFriendlySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, friendlyTargets, null);
return;
}
const enemyTargetPos = this.resolveRepeatCastTargetPos(castPlan.targetPos, i, summon);
const enemyTargetPos = this.resolveSummonCastTargetPos(config, heroAttrs, heroView, castPlan.targetPos, i, summon, usedTargetEids);
this.applyEnemySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, enemyTargetPos, i, overrides, summon);
}, i * castInterval);
}
@@ -537,6 +546,71 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return new Vec3(targetPos.x + offset.x, targetPos.y + offset.y, targetPos.z);
}
/**
* 解析第 castIndex 次施放的对敌目标点(召唤目标选择模式入口)。
* - same(默认):沿用施法时锁定的目标点并按召唤偏移对称铺开(原行为);
* - random:每次出手时从射程内敌人中随机锁定一个(错峰施放时实时索敌,目标状态最新鲜);
* - unique:优先锁定本轮未选过的敌人,敌人不足一轮时重置去重记录再来一轮;
* 实时索敌失败(敌人死亡/移动出射程)时回退施法时锁定的目标点,保证技能不落空。
*/
private resolveSummonCastTargetPos(config: SkillConfig, heroAttrs: HeroAttrsComp, heroView: HeroViewComp | null, baseTargetPos: Vec3 | null, castIndex: number, summon: SummonParams | undefined, usedEids: Set<number>): Vec3 | null {
if (!summon || summon.count <= 1 || summon.target_mode === SummonTMode.same) {
return this.resolveRepeatCastTargetPos(baseTargetPos, castIndex, summon);
}
const maxRange = this.resolveMaxCastRange(heroAttrs, heroAttrs.type as HType);
const enemies = this.collectEnemiesInRange(heroAttrs, heroView, maxRange);
const picked = enemies.length > 0 ? this.pickEnemyByTargetMode(enemies, summon.target_mode, usedEids) : null;
if (picked?.node) {
mLogger.log(this.debugMode, "SCastSystem", `[召唤索敌] 「${config.name}」第${castIndex + 1}个实体锁定目标 eid:${picked.ent?.eid} 模式:${SummonTMode[summon.target_mode]}`);
// 与 resolveEnemyCastTargetPos 保持一致:贝塞尔取脚底原始坐标,其余补半身高命中身体中心
const pos = config.RType === RType.bezier ? picked.node.position.clone() : this.buildEnemyCastTargetPos(picked);
// 召唤偏移铺开仍生效:random 模式重复锁定同一敌人时,避免实体完全重叠
const offset = this.resolveCastOffset(castIndex, summon);
pos.x += offset.x;
pos.y += offset.y;
return pos;
}
// 实时索敌失败:回退施法时锁定的目标点
return this.resolveRepeatCastTargetPos(baseTargetPos, castIndex, summon);
}
/** 按目标模式从射程内候选敌人中挑选本次施放的锁定目标,并写入去重记录 */
private pickEnemyByTargetMode(enemies: HeroViewComp[], mode: SummonTMode, usedEids: Set<number>): HeroViewComp | null {
let pool = enemies;
if (mode === SummonTMode.unique) {
const unused = enemies.filter(view => !view.ent || !usedEids.has(view.ent.eid));
if (unused.length > 0) {
pool = unused;
} else {
// 敌人不足一轮:重置已选记录,重新开始去重,保证尽量分散
usedEids.clear();
}
}
const picked = pool[Math.floor(Math.random() * pool.length)];
if (picked?.ent) usedEids.add(picked.ent.eid);
return picked ?? null;
}
/** 收集施法者射程内的全部敌方目标(随机/去重目标模式实时索敌用),网格遍历与 findNearestEnemyByGrid 同源 */
private collectEnemiesInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp | null, maxRange: number): HeroViewComp[] {
const result: HeroViewComp[] = [];
if (!heroView?.node) return result;
const grid = heroAttrs.fac === FacSet.HERO ? smc.mission.monGrid : smc.mission.heroGrid;
const currentX = heroView.node.position.x;
for (const eid of grid) {
if (eid < 0) continue;
const entity = ecs.getEntityByEid(eid);
if (!entity) continue;
const model = entity.get(HeroAttrsComp);
const view = entity.get(HeroViewComp);
if (!model || model.is_dead || model.is_reviving || !view?.node) continue;
if (Math.abs(currentX - view.node.position.x) <= maxRange) {
result.push(view);
}
}
return result;
}
/** 解析第 castIndex 个实体的偏移:召唤技能按 prefab 配置对称交替铺开(0, +1, -1, +2, -2... 档 × 步进值);多发回落纵向扇形档位 */
private resolveCastOffset(castIndex: number, summon?: SummonParams): { x: number; y: number } {
if (summon && summon.count > 1) {
@@ -883,7 +957,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return nearestEnemy.node?.position.clone() ?? null;
}
if (config.DTType !== DTType.range) {
return this.buildEnemyCastTargetPos(heroView, nearestEnemy, maxRange);
return this.buildEnemyCastTargetPos(nearestEnemy);
}
const frontEnemy = this.findFrontEnemyInRange(heroAttrs, heroView, maxRange, nearestEnemy);
if (!frontEnemy?.node) return null;
@@ -897,19 +971,10 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return heroAttrs.dis;
}
/** 生成沿目标方向的施法目标坐标 */
private buildEnemyCastTargetPos(caster: HeroViewComp, target: HeroViewComp, castRange: number): Vec3 {
// 直接返回目标的真实坐标,保留其 Y 轴信息,确保能向目标真实所在位置发射
// 考虑到目前角色的 y 坐标都是脚底(碰撞体底部),为了命中身体中心,给目标 y 加上高度的一半
let halfHeight = 0;
if (target.node) {
const transform = target.node.getComponent('cc.UITransform') as any;
if (transform) {
halfHeight = transform.height / 2;
} else {
halfHeight = 40; // 如果没有 UITransform,给一个默认高度偏移
}
}
/** 生成对敌施法目标坐标:直接返回目标的真实坐标;y 为脚底(碰撞体底部),补半身高以命中身体中心 */
private buildEnemyCastTargetPos(target: HeroViewComp): Vec3 {
const transform = target.node?.getComponent(UITransform);
const halfHeight = transform ? transform.height / 2 : 40; // 无 UITransform 时给默认高度偏移
const pos = target.node.position.clone();
pos.y += halfHeight;

View File

@@ -1,6 +1,6 @@
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { SkillTriggerType } from "../common/config/heroSet";
import { ResolvedTriggerEntry, SkillTriggerType } from "../common/config/heroSet";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { FacSet } from "../common/config/GameSet";
@@ -98,6 +98,7 @@ export class SkillTriggerHelper {
/**
* 处理攻击附加技能
* 判定逻辑:依据配置中的 t_num(触发所需普攻次数),对比当前的普攻累计次数取模判定。
* 天赋修正:L4 减少 t_num 阈值(下限 1);L5 按概率额外触发 1 次。
*/
private static handleAtking(model: HeroAttrsComp, view: HeroViewComp) {
if (!model.runtime_atking || model.runtime_atking.length === 0) return;
@@ -107,10 +108,18 @@ export class SkillTriggerHelper {
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.AtkCount);
}
triggerCount = Math.max(1, Math.floor(triggerCount));
// L5 天赋:概率额外触发 1 次(与羁绊次数加成独立乘算)
if (model.talent_extra_trigger_chance > 0 && Math.random() * 100 < model.talent_extra_trigger_chance) {
triggerCount += 1;
}
// L4 天赋:减少触发所需次数(下限 1)
const tNumReduce = model.talent_t_num_reduce;
model.runtime_atking.forEach(atkConfig => {
// atk_count 代表已进行的普攻次数。当其余数刚好整除配置阈值时触发。
if (model.atk_count > 0 && model.atk_count % atkConfig.t_num === 0) {
const tNum = Math.max(1, atkConfig.t_num - tNumReduce);
if (model.atk_count > 0 && model.atk_count % tNum === 0
&& this.rollTriggerChance(model, SkillTriggerType.Atking, atkConfig)) {
for (let i = 0; i < triggerCount; i++) {
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atking, atkConfig.overrides);
}
@@ -121,6 +130,7 @@ export class SkillTriggerHelper {
/**
* 处理受击附加技能
* 判定逻辑:依据配置中的 t_num(触发所需受击次数),对比当前的受击累计次数取模判定。
* 天赋修正:L4 减少 t_num 阈值(下限 1);L5 按概率额外触发 1 次。
*/
private static handleAtked(model: HeroAttrsComp, view: HeroViewComp) {
if (!model.runtime_atked || model.runtime_atked.length === 0) return;
@@ -130,10 +140,18 @@ export class SkillTriggerHelper {
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.BeAtkCount);
}
triggerCount = Math.max(1, Math.floor(triggerCount));
// L5 天赋:概率额外触发 1 次(与羁绊次数加成独立乘算)
if (model.talent_extra_trigger_chance > 0 && Math.random() * 100 < model.talent_extra_trigger_chance) {
triggerCount += 1;
}
// L4 天赋:减少触发所需次数(下限 1)
const tNumReduce = model.talent_t_num_reduce;
model.runtime_atked.forEach(atkConfig => {
// atked_count 代表已承受的受击次数。当其余数刚好整除配置阈值时触发。
if (model.atked_count > 0 && model.atked_count % atkConfig.t_num === 0) {
const tNum = Math.max(1, atkConfig.t_num - tNumReduce);
if (model.atked_count > 0 && model.atked_count % tNum === 0
&& this.rollTriggerChance(model, SkillTriggerType.Atked, atkConfig)) {
for (let i = 0; i < triggerCount; i++) {
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atked, atkConfig.overrides);
}
@@ -141,6 +159,31 @@ export class SkillTriggerHelper {
});
}
/**
* 触发概率判定 + 保底叠加(仅 Atking/Atked 消费)
* 次数条件达成后按 t_chance(缺省 100 必触发,兼容存量配置)掷骰;
* 未命中且配置 pity_add > 0 时,为该 s_uuid 累积下次概率增量,形成"越不触发越容易触发"的保底体验;
* 触发成功后清空叠加,最终概率封顶 100。
*
* @param model 施法者属性组件(保底缓存挂载于此)
* @param type 触发类型(决定读/写哪份保底缓存)
* @param config 当前生效的触发条目
* @returns 是否通过概率判定
*/
private static rollTriggerChance(model: HeroAttrsComp, type: SkillTriggerType, config: ResolvedTriggerEntry): boolean {
const pityMap = type === SkillTriggerType.Atking ? model.pity_atking : model.pity_atked;
const chance = Math.min(100, (config.t_chance ?? 100) + (pityMap[config.s_uuid] ?? 0));
if (chance >= 100 || Math.random() * 100 < chance) {
pityMap[config.s_uuid] = 0; // 触发成功:清空保底叠加
return true;
}
const pityAdd = config.pity_add ?? 0;
if (pityAdd > 0) {
pityMap[config.s_uuid] = (pityMap[config.s_uuid] ?? 0) + pityAdd;
}
return false;
}
/**
* 通用的数组型触发器处理(适用于 FStart 等无需额外判定的简单列表触发)
*/

View File

@@ -6,7 +6,7 @@
* 1. 展示单个英雄的详情:idle 动画、名称、AP / DEF / HP / CD、技能描述、
* 稀有度底框、局外等级、职业、碎片数量、升级按钮、技能图标列表。
* 2. 提供升级操作(消耗碎片提升局外等级,成功后播放升级特效)。
* 3. 提供前后英雄切换按钮,按图鉴排序顺序循环切换(getSortedHeroList)。
* 3. 提供前后英雄切换按钮,按图鉴排序顺序循环切换(品质降序 → uuid 升序)。
* 4. 提供关闭按钮 / 遮罩点击关闭弹窗。
*
* 使用(oops.gui 模块):
@@ -15,12 +15,11 @@
* 关键设计:
* - 详情渲染逻辑与原 HerosListComp 详情面板同源(已迁移至此),
* HerosListComp 仅保留卡片列表,点击卡片改为打开本弹窗。
* - 前后切换按 getSortedHeroList 计算的顺序循环(与图鉴页面卡片顺序一致)。
* - 前后切换按品质降序 → uuid 升序循环(与图鉴页面卡片顺序一致)。
* - hero_icon 使用 Animation + iconVisualToken 机制防止异步加载竞态。
*
* 依赖:
* - HeroInfo / heroSet —— 英雄静态配置
* - getSortedHeroList(heroUpSet)—— 图鉴排序
* - HeroInfo / HeroList(heroSet)—— 英雄静态配置与全量 UUID 列表
* - smc.getHeroGrowth / smc.getHeroOuterLv / smc.upgradeHero —— 成长数据
* - buildHeroSkillDesc(HeroSkillText)—— 技能描述生成器
* - SSkillBoxComp / ChipComp / SmallTipComp —— 子组件
@@ -30,12 +29,11 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { oops } from "db://oops-framework/core/Oops";
import { mLogger } from "../common/Logger";
import { HeroInfo, SkillTriggerType, LvFieldEntry, LvTriggerEntry, TriggerGrouped, HSkillInfo, getHeroDisplayStats, LvBonusAttrsEntry } from "../common/config/heroSet";
import { HeroInfo, HeroList, SkillTriggerType, LvFieldEntry, LvTriggerEntry, TriggerGrouped, HSkillInfo, getHeroDisplayStats, LvBonusAttrsEntry } from "../common/config/heroSet";
import { buildHeroSkillDesc } from "../common/HeroSkillText";
import { outlineRich } from "../common/RichOutline";
import { GameEvent } from "../common/config/GameEvent";
import { HERO_UPGRADE_NEED_FRAGMENTS, RARITY_BG_COLORS } from "../common/config/GameSet";
import { getSortedHeroList } from "../common/config/heroUpSet";
import { ChipComp } from "./ChipComp";
import { SmallTipComp } from "./SmallTipComp";
import { SSkillBoxComp } from "./SSkillBoxComp";
@@ -91,10 +89,6 @@ export class HeroInfoComp extends CCComp {
@property(Node)
upgrade_btn = null!
/** 碎片数量显示节点(Label) */
@property(Node)
fragment_node = null!
/** 碎片图标节点(挂载 ChipComp 用于渲染碎片图标) */
@property(Node)
chip_node = null!
@@ -253,7 +247,15 @@ export class HeroInfoComp extends CCComp {
* @param delta 1 下一个,-1 上一个
*/
private switchHero(delta: 1 | -1) {
const sorted = getSortedHeroList(uuid => smc.getHeroGrowth(uuid));
// 与图鉴页面排序同源:按职业(HRole)升序 → uuid 升序
const sorted = [...HeroList].sort((a, b) => {
const heroA = HeroInfo[a];
const heroB = HeroInfo[b];
const roleA = heroA?.role ?? 0;
const roleB = heroB?.role ?? 0;
if (roleA !== roleB) return roleA - roleB;
return a - b;
});
if (sorted.length === 0) return;
const idx = sorted.indexOf(this.huuid);
// 当前 uuid 不在列表中(异常)时回退到第一个
@@ -454,9 +456,6 @@ export class HeroInfoComp extends CCComp {
const fragmentText = isMaxLv
? `${growth.fragments} / MAX`
: `${growth.fragments} / ${needFragments}`;
if (this.fragment_node) {
this.setLabelText(this.fragment_node, fragmentText);
}
if (this.fragment_label) {
this.fragment_label.string = fragmentText;
}

View File

@@ -5,15 +5,16 @@
* 职责:
* 1. 以卡片列表形式展示所有可用英雄(使用 CardLiteComp 预制体)。
* 2. 点击卡片打开英雄详情弹窗(UIID.HeroInfo),详情/升级在 HeroInfoComp 中处理。
* 3. 卡片排序:可升级优先 → 现有等级降序 → 品质降序(getSortedHeroList)。
* 3. 卡片排序:仅按品质降序 → uuid 升序(与养成进度解耦,保持展示稳定)。
*
* 关键设计:
* - cards_node 为卡片容器,通过 instantiate(card_lite_prefab) 动态生成卡片。
* - 升级/碎片变动后通过 HERO_GROWTH_UPDATE 事件统一刷新所有卡片的可升级标识。
* - 详情面板已从本组件剥离,由 HeroInfoComp 承载。
* - 排序不依赖 smc,仅读取静态配置(HeroInfo.rarity)。
*
* 依赖:
* - getSortedHeroList(heroUpSet)—— 图鉴排序
* - HeroInfo / HeroList(heroSet)—— 英雄静态配置与全量 UUID 列表
* - CardLiteComp —— 轻量卡片组件
* - UIID.HeroInfo(oops.gui)—— 英雄详情弹窗
*/
@@ -24,10 +25,8 @@ import { oops } from "db://oops-framework/core/Oops";
import { mLogger } from "../common/Logger";
import { HeroInfo, HeroList } from "../common/config/heroSet";
import { GameEvent } from "../common/config/GameEvent";
import { getSortedHeroList } from "../common/config/heroUpSet";
import { UIID } from "../common/config/GameUIConfig";
import { CardLiteComp } from "./CardLiteComp";
import { smc } from "../common/SingletonModuleComp";
const { property, ccclass } = _decorator;
@@ -46,14 +45,30 @@ export class HerosListComp extends CCComp {
@property
card_w = 120
/** 卡片高度 */
@property
card_h = 200
/** 卡片横向间隔 */
@property
spacing_x = 10
/** 卡片纵向间隔 */
@property
spacing_y = 10
/** 每行卡片数(竖向滚动时按列排列) */
@property
cols = 4
/** 卡片左右边距 */
@property
margin_x = 15
/** 卡片上下边距 */
@property
margin_y = 50
// ======================== 运行时状态 ========================
debugMode: boolean = false
@@ -64,6 +79,9 @@ export class HerosListComp extends CCComp {
protected onEnable(): void {
oops.message.on(GameEvent.HERO_GROWTH_UPDATE, this.onGrowthUpdate, this)
// 页面隐藏期间会错过事件(战斗结算发碎片时本节点 active=false 已注销监听),
// 重新显示时必须补刷;首次 onEnable 早于 start,卡片未创建时 forEach 空数组安全
this.refreshAllCardsCanUp()
}
protected onDisable(): void {
@@ -95,8 +113,15 @@ export class HerosListComp extends CCComp {
this.cards_node.removeAllChildren()
// 排序与详情面板"前后切换"同源(getSortedHeroList),保证两个入口顺序一致
const sorted = getSortedHeroList(uuid => smc.getHeroGrowth(uuid))
// 图鉴排序:按职业(HRole)升序 → uuid 升序(与养成进度解耦,保持展示稳定)
const sorted = [...HeroList].sort((a, b) => {
const heroA = HeroInfo[a];
const heroB = HeroInfo[b];
const roleA = heroA?.role ?? 0;
const roleB = heroB?.role ?? 0;
if (roleA !== roleB) return roleA - roleB;
return a - b;
})
for (const uuid of sorted) {
const hero = HeroInfo[uuid]
@@ -127,16 +152,18 @@ export class HerosListComp extends CCComp {
const total = this.cards_node.children.length
if (total === 0) return
// 宽度 = 卡片总宽 + 卡片间隔 + 左右边距
const contentW = total * this.card_w
+ (total - 1) * this.spacing_x
// 竖向滚动:宽度固定(每行 cols 列),高度按行数累计
const rows = Math.ceil(total / this.cols)
// 宽度 = 每行卡片总宽 + 横向间隔 + 左右边距
const contentW = this.cols * this.card_w
+ (this.cols - 1) * this.spacing_x
+ this.margin_x * 2
const cols = 4
const rows = Math.ceil(total / cols)
const cardH = 200
const spacingY = 10
const contentH = Math.max(1000, rows * (cardH + spacingY) + 50)
// 高度 = 行数 × 卡片高 + 纵向间隔 + 上下边距
const contentH = rows * this.card_h
+ (rows - 1) * this.spacing_y
+ this.margin_y * 2
const uiTrans = this.cards_node.getComponent(UITransform)
if (uiTrans) {

View File

@@ -54,7 +54,6 @@ import { oops } from "db://oops-framework/core/Oops";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { HeroInfo, HType, resolveTriggerByLv, resolveFieldByLv, resolveSkillOverrides, calcGrowBonus, getHeroQualityCoef } from "../common/config/heroSet";
import { applyTalentBonuses } from "../common/config/TalentSet";
import { HeroViewComp } from "../hero/HeroViewComp";
import { FacSet, FightSet } from "../common/config/GameSet";
import { MissionEconomy } from "./MissionEconomy";
@@ -2094,13 +2093,16 @@ export class MissionCardComp extends CCComp {
model.knockback_distance = 0;
model.heal_power = 0;
model.buff_power = 0;
applyTalentBonuses(model, smc.getTalentLevels(), model.role);
// 天赋触发修正归零(applyTalent 内部重算,保证升级重算幂等)
model.talent_t_num_reduce = 0;
model.talent_extra_trigger_chance = 0;
model.applyTalent();
}
// 血量按当前血线比例映射到新上限,保留升级前战斗状态
model.hp = Math.max(1, Math.floor(model.hp_max * Math.max(0, Math.min(1, hpRate))));
// 重新解析触发技能/驻场光环/复活到运行时缓存(与 Hero.load 对齐)
// 重新解析触发技能/驻场光环/复活到运行时缓存(在 applyTalent 之后,确保 L4/L5 触发修正已写入模型)
model.runtime_call = resolveTriggerByLv(model.call, nextLv);
model.runtime_dead = resolveTriggerByLv(model.dead, nextLv);
model.runtime_fstart = resolveTriggerByLv(model.fstart, nextLv);

View File

@@ -17,7 +17,7 @@
* - 第 5 节胜利 → open_Victory(final) 弹结算,确认退出并存档进下一关。
* - cleanComponents() / clearBattlePools() 仅在整关开始/结束时执行,节间不调用。
*/
import { _decorator, Vec3, Animation, instantiate, Prefab, Node, NodeEventType, ProgressBar, Label, CCInteger, tween, v3, Tween, Widget, UIOpacity } from "cc";
import { _decorator, Node, NodeEventType, Label, tween, v3, Tween, Widget, Color, Sprite, ProgressBar } 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 { smc } from "../common/SingletonModuleComp";
@@ -31,7 +31,6 @@ import { SkillTriggerHelper } from "../hero/SkillTriggerHelper";
import { UIID } from "../common/config/GameUIConfig";
import { SkillView } from "../skill/SkillView";
import { FacSet, FightSet } 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";
@@ -80,8 +79,6 @@ type SettleType = "defeat" | "final";
export class MissionComp extends CCComp {
@property({ tooltip: "是否启用调试日志" })
private debugMode: boolean = true;
@property({ tooltip: "是否显示战斗内存观测面板" })
private showMemoryPanel: boolean = false;
// ======================== 配置参数 ========================
@@ -97,6 +94,21 @@ export class MissionComp extends CCComp {
@property(Node)
tooltip: Node = null!
/** 回合指示节点(5 个,索引 0~4 对应回合 1~5),按回合进度染色 */
@property({ type: [Node], tooltip: "回合指示节点(按回合 1~5 顺序绑定)" })
round_nodes: Node[] = [];
/** 回合进度条:第 1 回合 0%,每推进一回合 +25%(第 5 回合满格) */
@property({ type: ProgressBar, tooltip: "回合进度条(第 1 回合 0%,每回合 +25%)" })
round_progress: ProgressBar = null!;
/** 回合指示:未激活(白色) */
private static readonly ROUND_INACTIVE = new Color(255, 255, 255, 255);
/** 回合指示:已激活(橙黄 #FFA300) */
private static readonly ROUND_ACTIVE = new Color(255, 163, 0, 255);
/** 回合指示:Boss 当前回合(红色) */
private static readonly ROUND_BOSS = new Color(230, 60, 60, 255);
/** 阶段名称映射表(用于 UI 显示) */
private static readonly PhaseNameMap: Record<MissionPhase, string> = {
[MissionPhase.None]: "未开始",
@@ -133,26 +145,6 @@ export class MissionComp extends CCComp {
private lastTimeStr: string = "";
/** 上一次显示的秒数(避免重复计算) */
private lastTimeSecond: number = -1;
/** 性能监控面板 Label 引用 */
private memoryLabel: Label | null = null;
/** 性能监控刷新计时器 */
private memoryRefreshTimer: number = 0;
/** 上一次性能文本(避免重复渲染) */
private lastMemoryText: string = "";
/** 帧间隔累加(用于计算平均 FPS) */
private perfDtAcc: number = 0;
/** 帧数计数 */
private perfFrameCount: number = 0;
/** 初始堆内存基准值(MB) */
private heapBaseMB: number = -1;
/** 堆内存峰值(MB) */
private heapPeakMB: number = 0;
/** 堆内存增长趋势(MB/分钟) */
private heapTrendPerMinMB: number = 0;
/** 趋势计算计时器 */
private heapTrendTimer: number = 0;
/** 趋势计算基准(MB) */
private heapTrendBaseMB: number = -1;
/** 怪物数量同步计时器(降低同步频率) */
private monsterCountSyncTimer: number = 0;
/** 当前回合数(= 当前小节号,驱动 HUD 回合/进度显示) */
@@ -161,8 +153,6 @@ export class MissionComp extends CCComp {
private isBossWave: boolean = false;
/** 超时回合已在 onBattleTimeout 预扣留存分,BattleEnd 需跳过重复累加 */
private skipRemainScoreOnBattleEnd: boolean = false;
/** 上节清场时间快照(含清场加速还原,供评分统计) */
private lastWaveClearTime: number = 0;
/** 当前任务阶段 */
public currentPhase: MissionPhase = MissionPhase.None;
/** 是否处于回合间倒计时状态 */
@@ -180,11 +170,9 @@ export class MissionComp extends CCComp {
// ======================== 关卡进度状态 ========================
/** 当前章节号 */
public currentChapter: number = 1;
/** 当前关卡号(1~10) */
/** 当前关卡号(无限关,1~∞) */
public currentStage: number = 1;
/** 当前小节号(1~SECTIONS_PER_STAGE) */
/** 当前回合号(1~SECTIONS_PER_STAGE) */
public currentSection: number = 1;
// ======================== ECS 查询匹配器(预缓存) ========================
@@ -202,7 +190,6 @@ export class MissionComp extends CCComp {
this.heroViewMatcher = ecs.allOf(HeroViewComp);
this.skillViewMatcher = ecs.allOf(SkillView);
this.heroAttrsMatcher = ecs.allOf(HeroAttrsComp);
this.showMemoryPanel = false
// 注册生命周期事件
this.on(GameEvent.MissionEnd, this.mission_end, this)
this.on(GameEvent.NewWave, this.onNewWave, this)
@@ -212,10 +199,9 @@ export class MissionComp extends CCComp {
this.start_btn?.on(NodeEventType.TOUCH_END, this.onStartFightBtnClick, this)
// 开局免费三选一选完后自动进入战斗
oops.message.on("OpeningPicksDone", this.onOpeningPicksDone, this);
this.removeMemoryPanel()
}
onAdded(args: any) {
onAdded() {
// 使用 scheduleOnce 将事件推迟到下一帧执行,
// 确保所有关联组件(如 MissionCardComp 等)都已经完成其 onLoad 生命周期。
this.scheduleOnce(() => {
@@ -370,7 +356,6 @@ export class MissionComp extends CCComp {
this.changePhase(MissionPhase.Prepare);
// 派发首个 SectionStart,MissionMonComp 据此生成开局怪物
oops.message.dispatchEvent(GameEvent.SectionStart, {
chapter: this.currentChapter,
stage: this.currentStage,
section: this.currentSection,
});
@@ -455,7 +440,6 @@ export class MissionComp extends CCComp {
*/
private changePhase(targetPhase: MissionPhase) {
if (this.currentPhase === targetPhase) return;
const oldPhase = this.currentPhase;
this.currentPhase = targetPhase;
const phaseName = MissionComp.PhaseNameMap[targetPhase] || "未知";
@@ -556,8 +540,6 @@ export class MissionComp extends CCComp {
// 英雄为真实死亡(实体已销毁),本节死亡数取死亡记录长度
const heroDeathCount = smc.mission.dead_heroes.length;
// 快照清场时间供评分统计(onNewWave 会清零 clearTime,必须提前快照)
this.lastWaveClearTime = this.clearTime;
// 【评分系统 - 战绩分】记录全员存活的胜利节数(额外加分)
if (hasHero && heroDeathCount === 0) {
smc.vmdata.scores.wave_all_alive_count++;
@@ -751,11 +733,11 @@ export class MissionComp extends CCComp {
* - 胜利即 final(通关),存档推进到下一关;失败即 defeat(进度不变,可复活/再来一次)
* - 切入 BattleEnd,2s 后由 autoNextPhase 自动弹出 VictoryComp
*
* @param e 事件对象(未使用)
* @param is_hero_dead 是否因英雄全灭触发
* @param _e 事件对象(未使用)
* @param _is_hero_dead 是否因英雄全灭触发(当前未使用,保留签名以统一事件调用口径)
* @param victory 是否胜利(默认 false)
*/
open_Victory(e: any, is_hero_dead: boolean = false, victory: boolean = false) {
open_Victory(_e: any, _is_hero_dead: boolean = false, victory: boolean = false) {
// 战斗结束(通关/失败),标记暂停状态以切断节间流转逻辑
smc.mission.pause = true;
this._victory = victory;
@@ -765,23 +747,18 @@ export class MissionComp extends CCComp {
// 失败:进度不变,结算窗走复活/再来一次/退出流程
this._settleType = "defeat";
} else {
// 通关(Boss 节胜利):结算窗显示通关标题,确认退出并存档进下一关
// 通关(Boss 回合胜利):无限关卡模式,存档永远推进到下一关第 1 节
// (仅写存档,内存进度保留用于本局结算显示)
this._settleType = "final";
this._settleTitle = this.currentStage >= 10 ? "本章通关" : "本关通关";
// 存档推进到下一关第 1 节(仅写存档,内存进度保留用于本局结算显示);
// 第 10 关通关即本章全通,存档停留等待后续章节扩展
if (this.currentStage < 10) {
smc.updateStageProgress({
chapter: this.currentChapter,
stage: this.currentStage + 1,
section: 1,
});
if ((smc as any).updateCloudData) {
(smc as any).updateCloudData();
}
mLogger.log(this.debugMode, 'MissionComp',
`[StageProgress] 本关通关,存档推进: ${this.currentChapter}-${this.currentStage + 1}-1`);
this._settleTitle = "本关通关";
smc.updateStageProgress({
stage: this.currentStage + 1,
});
if ((smc as any).updateCloudData) {
(smc as any).updateCloudData();
}
mLogger.log(this.debugMode, 'MissionComp',
`[StageProgress] 本关通关,存档推进: ${this.currentStage + 1}-1`);
}
// 直接切入 BattleEnd,2s 后由 autoNextPhase 自动流转弹窗结算
this.changePhase(MissionPhase.BattleEnd);
@@ -836,20 +813,11 @@ export class MissionComp extends CCComp {
this.currentWave = 1;
this.isBossWave = false;
this.skipRemainScoreOnBattleEnd = false;
this.lastWaveClearTime = 0;
this.updateRoundNodes();
this.rewards = []
this.revive_times = 1;
this.lastTimeStr = "";
this.lastTimeSecond = -1;
this.memoryRefreshTimer = 0;
this.lastMemoryText = "";
this.perfDtAcc = 0;
this.perfFrameCount = 0;
this.heapBaseMB = -1;
this.heapPeakMB = 0;
this.heapTrendPerMinMB = 0;
this.heapTrendTimer = 0;
this.heapTrendBaseMB = -1;
this.monsterCountSyncTimer = 0;
spawningEngine.reset();
@@ -868,57 +836,48 @@ export class MissionComp extends CCComp {
// ======================== 关卡进度管理 ========================
/**
* 加载关卡进度存档(一关一局:只恢复大关,节恒从 1 开始)
* 从 smc.data.stage_progress 恢复章节/大关,缺省从 1-1-1 开始
* 加载关卡进度存档(一关一局:只恢复大关,回合恒从 1 开始)
* 从 smc.data.stage_progress 恢复关卡号,缺省从第 1 关开始
*/
private loadStageProgress() {
const progress = smc.data.stage_progress;
if (progress && progress.chapter >= 1 && progress.stage >= 1) {
this.currentChapter = progress.chapter;
this.currentStage = progress.stage;
} else {
this.currentChapter = 1;
this.currentStage = 1;
}
// 一关一局:进入关卡始终从第 1 节开打
this.currentStage = Math.max(1, Math.floor(progress?.stage ?? 1));
// 一关一局:进入关卡始终从第 1 回合开打
this.currentSection = 1;
// 加载完成后将权威进度镜像到 vmdata,驱动 HUD 实时显示
smc.syncStageProgressToView();
mLogger.log(this.debugMode, 'MissionComp',
`[StageProgress] 加载进度: ${getProgressText(this.currentChapter, this.currentStage, this.currentSection)}`);
`[StageProgress] 加载进度: ${getProgressText(this.currentStage, this.currentSection)}`);
}
/**
* 推进到下一小节(节间自动推进):
* 1. 节号 +1,派发 SectionStart 触发 MissionMonComp 生成下一节怪物(从右侧跑入)。
* 推进到下一回合(回合间自动推进):
* 1. 回合号 +1,派发 SectionStart 触发 MissionMonComp 生成下一回合怪物(从右侧跑入)。
* 2. 不清理英雄/技能实体、不重置局内数据、不重新三选一——英雄保留并自动归位(编队排队)。
* 3. 进度实时镜像到 HUD,但不写持久存档(存档只在整关通关时推进到下一关)。
*
* 调用时机:PrepareStart 过场倒计时结束(autoNextPhase)。
* 保证:仅 1~4 节清场后触发,末节(BOSS_SECTION)已走 open_Victory 结算,不会推进。
* 保证:仅 1~4 回合清场后触发,末回合(BOSS_SECTION)已走 open_Victory 结算,不会推进。
*/
private advanceToNextSection() {
this.currentSection++;
// 节号实时同步到 HUD(内存进度,不持久化;整关通关时才统一写大关存档)。
// 回合号实时同步到 HUD(内存进度,不持久化;整关通关时才统一写大关存档)。
// 直接写 vmdata 视图层而非走 syncStageProgressToView(后者从 data 读,而 data 仍停留在大关起点)
const view = smc.vmdata.stage_progress;
view.chapter = this.currentChapter;
view.stage = this.currentStage;
view.section = this.currentSection;
oops.message.dispatchEvent(GameEvent.STAGE_PROGRESS_UPDATE);
// 触发 SectionStart 事件,MissionMonComp 据此生成下一节怪物(从右侧跑入)
// 触发 SectionStart 事件,MissionMonComp 据此生成下一回合怪物(从右侧跑入)
oops.message.dispatchEvent(GameEvent.SectionStart, {
chapter: this.currentChapter,
stage: this.currentStage,
section: this.currentSection,
});
mLogger.log(this.debugMode, 'MissionComp',
`[StageProgress] 进入小节: ${getProgressText(this.currentChapter, this.currentStage, this.currentSection)}`);
`[StageProgress] 进入回合: ${getProgressText(this.currentStage, this.currentSection)}`);
}
/**
* 重开本关:清理战场并重置数据,从本关第 1 节重新开始
* Why: 一关一局,失败"再来一次"即整关重打(节恒从 1 开始)
* 重开本关:清理战场并重置数据,从本关第 1 回合重新开始
* Why: 一关一局,失败"再来一次"即整关重打(回合恒从 1 开始)
*/
private restartCurrentMission() {
// 清理战场与局内数据
@@ -929,21 +888,20 @@ export class MissionComp extends CCComp {
this._victory = false;
this._settleType = "defeat";
this._settleTitle = "";
// 一关一局:重开从第 1 节开始
// 一关一局:重开从第 1 回合开始
this.currentSection = 1;
// 进入准备阶段
this.changePhase(MissionPhase.Prepare);
// 触发 MissionStart 重置开局免费抽卡
oops.message.dispatchEvent(GameEvent.MissionStart, {});
oops.message.dispatchEvent(GameEvent.FightReady);
// 触发 SectionStart 重新生成本关第 1 节怪物
// 触发 SectionStart 重新生成本关第 1 回合怪物
oops.message.dispatchEvent(GameEvent.SectionStart, {
chapter: this.currentChapter,
stage: this.currentStage,
section: this.currentSection,
});
mLogger.log(this.debugMode, 'MissionComp',
`[StageProgress] 重开本关: ${getProgressText(this.currentChapter, this.currentStage, this.currentSection)}`);
`[StageProgress] 重开本关: ${getProgressText(this.currentStage, this.currentSection)}`);
}
// ======================== 回合管理 ========================
@@ -959,7 +917,7 @@ export class MissionComp extends CCComp {
* @param event 事件名
* @param data { wave: number, bossWave?: boolean }
*/
private onNewWave(event: string, data: any) {
private onNewWave(_event: string, data: any) {
const wave = Number(data?.wave ?? 0);
if (wave <= 0) return;
@@ -968,12 +926,46 @@ export class MissionComp extends CCComp {
this.currentWave = wave;
smc.vmdata.mission_data.level = wave;
this.updateRoundNodes();
// 金币改为怪物死亡掉落(见 HeroAtkSystem),不再每回合固定发放
this.lastTimeSecond = -1;
this.clearTime = 0;
this.update_time();
}
/**
* 刷新回合指示节点颜色:
* - 已激活(已完成 + 当前回合)→ 黄色;当前回合且为 Boss 节 → 红色;未激活 → 白色。
* Why: 回合推进时(onNewWave)与开局重置(data_init)调用,驱动 HUD 回合指示实时反馈。
*/
private updateRoundNodes() {
const len = Math.min(this.round_nodes.length, SECTIONS_PER_STAGE);
for (let i = 0; i < len; i++) {
const node = this.round_nodes[i];
if (!node || !node.isValid) continue;
const round = i + 1;
const color = round > this.currentWave
? MissionComp.ROUND_INACTIVE
: (round === this.currentWave && this.isBossWave)
? MissionComp.ROUND_BOSS
: MissionComp.ROUND_ACTIVE;
// 优先染色 Sprite(图标配色),无 Sprite 时退化到 Label,均无则跳过
const sprite = node.getComponent(Sprite);
if (sprite) {
sprite.color = color;
} else {
const label = node.getComponent(Label);
if (label) label.color = color;
}
}
// 进度条:第 1 回合 0%,每推进一回合 +25%(第 5 回合满格)
if (this.round_progress && this.round_progress.isValid) {
this.round_progress.progress =
Math.max(0, Math.min(SECTIONS_PER_STAGE, this.currentWave) - 1) / (SECTIONS_PER_STAGE - 1);
}
}
// ======================== 怪物数量管理 ========================
/**
@@ -1182,115 +1174,6 @@ export class MissionComp extends CCComp {
}
}
/** 获取战斗层的英雄和技能节点数量(用于性能监控) */
private getBattleLayerNodeCount() {
const scene = smc.map?.MapView?.scene;
const layer = scene?.entityLayer?.node;
if (!layer) return { heroNodes: 0, skillNodes: 0 };
const heroRoot = layer.getChildByName("HERO");
const skillRoot = layer.getChildByName("SKILL");
return {
heroNodes: heroRoot?.children.length || 0,
skillNodes: skillRoot?.children.length || 0
};
}
// ======================== 性能监控面板 ========================
/** 性能监控相关代码 */
/** 初始化性能监控面板:在本组件节点下创建 Label */
private initMemoryPanel() {
if (!this.showMemoryPanel) return;
let panel = this.node.getChildByName("mem_panel");
if (!panel) {
panel = new Node("mem_panel");
panel.parent = this.node;
panel.setPosition(0, -32, 0);
}
let label = panel.getComponent(Label);
if (!label) {
label = panel.addComponent(Label);
}
label.fontSize = 16;
label.lineHeight = 20;
this.memoryLabel = label;
}
/** 移除性能监控面板 */
private removeMemoryPanel() {
const panel = this.node?.getChildByName("mem_panel");
if (panel) {
panel.destroy();
}
this.memoryLabel = null;
this.lastMemoryText = "";
}
/**
* 更新性能监控面板内容(每 0.5 秒一次):
* 显示 堆内存 / 增长趋势 / 帧率 / 实体数量 / 对象池状态 等信息。
*/
private updateMemoryPanel(dt: number) {
if (!this.showMemoryPanel || !this.memoryLabel) return;
this.perfDtAcc += dt;
this.perfFrameCount += 1;
this.memoryRefreshTimer += dt;
if (this.memoryRefreshTimer < 0.5) return;
this.memoryRefreshTimer = 0;
let heroCount = 0;
ecs.query(this.heroViewMatcher).forEach(() => {
heroCount++;
});
let skillCount = 0;
ecs.query(this.skillViewMatcher).forEach(() => {
skillCount++;
});
const monPool = Monster.getPoolStats();
const skillPool = Skill.getPoolStats();
const tooltipPool = Tooltip.getPoolStats();
const layerNodes = this.getBattleLayerNodeCount();
const perf = (globalThis as any).performance;
const heapBytes = perf && perf.memory ? perf.memory.usedJSHeapSize : 0;
let heapMB = heapBytes > 0 ? heapBytes / 1024 / 1024 : -1;
if (heapMB > 0 && this.heapBaseMB < 0) {
this.heapBaseMB = heapMB;
this.heapPeakMB = heapMB;
this.heapTrendBaseMB = heapMB;
this.heapTrendTimer = 0;
}
if (heapMB > this.heapPeakMB) {
this.heapPeakMB = heapMB;
}
this.heapTrendTimer += 0.5;
if (heapMB > 0 && this.heapTrendBaseMB > 0 && this.heapTrendTimer >= 10) {
const deltaMB = heapMB - this.heapTrendBaseMB;
this.heapTrendPerMinMB = (deltaMB / this.heapTrendTimer) * 60;
this.heapTrendBaseMB = heapMB;
this.heapTrendTimer = 0;
}
const heapText = heapMB > 0 ? heapMB.toFixed(1) : "N/A";
const heapDeltaText = this.heapBaseMB > 0 && heapMB > 0 ? (heapMB - this.heapBaseMB).toFixed(1) : "N/A";
const heapPeakText = this.heapPeakMB > 0 ? this.heapPeakMB.toFixed(1) : "N/A";
const avgDt = this.perfFrameCount > 0 ? this.perfDtAcc / this.perfFrameCount : 0;
const fps = avgDt > 0 ? 1 / avgDt : 0;
this.perfDtAcc = 0;
this.perfFrameCount = 0;
const text =
`Heap:${heapText}MB Δ:${heapDeltaText} Peak:${heapPeakText}\n` +
`Trend:${this.heapTrendPerMinMB.toFixed(2)}MB/min\n` +
`Perf dt:${(avgDt * 1000).toFixed(1)}ms fps:${fps.toFixed(1)}\n` +
`Ent H:${heroCount} S:${skillCount} N:${layerNodes.heroNodes}/${layerNodes.skillNodes}\n` +
`Pool M:${monPool.total}(${monPool.paths}) K:${skillPool.total}(${skillPool.paths}) T:${tooltipPool.total}`;
if (text === this.lastMemoryText) return;
this.lastMemoryText = text;
this.memoryLabel.string = text;
}
/** ECS 组件移除时销毁节点 */
reset() {
this.PhaseTime = null as any;

View File

@@ -124,12 +124,11 @@ export class MissionMonCompComp extends CCComp {
start() { }
/**
* 小节开始事件回调:生成怪物列表并按节奏刷出
* 回合开始事件回调:生成怪物列表并按节奏刷出
* @param event 事件名
* @param data { chapter, stage, section }
* @param data { stage, section }
*/
private onSectionStart(event: string, data: any) {
const chapter = Number(data?.chapter ?? 1);
const stage = Number(data?.stage ?? 1);
const section = Number(data?.section ?? 1);
@@ -142,15 +141,15 @@ export class MissionMonCompComp extends CCComp {
smc.vmdata.mission_data.mon_num = 0;
smc.vmdata.mission_data.pending_mon_num = 0;
// 从生成引擎获取本小节怪物列表
const monsters = spawningEngine.generateSectionMonsters(chapter, stage, section);
// 从生成引擎获取本回合怪物列表
const monsters = spawningEngine.generateSectionMonsters(stage, section);
this.sectionTargetCount = monsters.length;
if (TestModeConfig.enable) {
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] 测试模式已开启");
}
mLogger.log(this.debugMode, 'MissionMonComp',
`[MissionMonComp] 小节 ${chapter}-${stage}-${section} 生成完成,怪物总数: ${this.sectionTargetCount}`);
`[MissionMonComp] 回合 ${stage}-${section} 生成完成,怪物总数: ${this.sectionTargetCount}`);
// 按批次拆分:batch=0 立即刷,batch=1 进入待刷队列
const immediate: GeneratedMonster[] = [];

View File

@@ -1,30 +1,32 @@
/**
* @file RogueConfig.ts
* @description 关卡刷怪系统(章节-关-节三级推进)
* @description 无限关卡刷怪系统(关-回合两级推进)
*
* 游戏机制:
* - 玩家线性推进 1 章 10 关 × 每关 5 小节(一关一局,节间自动推进)。
* - 每关节点固定:1~3 节普通(3 小怪,立即全刷)、第 4 节精英(3 精英同刷)、
* 第 5 节 Boss(2 小怪先刷,Boss 延迟 1s 刷出)。
* - 怪物属性 = 基础 × 0.8 × (1 + 0.05×(lv-1)) × 类型倍率(精英1.5 / Boss3.0)。
* - 玩家单线无限推进:从第 1 关起无上限,每关 5 回合(一关一局,回合间自动推进)。
* - 第 1~4 回合普通怪(3 小怪立即全刷),属性逐回合轻量增强(SECTION_RAMP_STEP)。
* - 第 5 回合单 Boss 登场(无护卫,延迟 1s 刷出),超厚血量(BOSS_HP_MUL)。
* - Boss 加载时按攻击定位(近战/中程/远程)随机取型,并随机携带大招:
* 前期 1 个,随关卡解锁扩展至多个(BOSS_ULT_UNLOCK_EVERY / BOSS_ULT_MAX_COUNT)。
*
* 设计层次:
* 1. StageSet - 关卡配置(章节/关/节 + 数值公式)
* 2. MonSkillPool/BossSkillPool - 怪物技能池(atking / atked / dead 等全触发类型)
* 3. StageSpawningEngine - 生成引擎(按小节配置生成怪物列表)
* 1. StageSet - 关卡数值公式(等级/成长/回合增强/Boss 血攻倍率)
* 2. BossPools - Boss 池(按攻击定位分组,动态从 HeroInfo 提取)
* 3. BossUltPools - Boss 大招池(按定位分组,引用 SkillSet 6100~6204 群体大招段)
* 4. StageSpawningEngine - 生成引擎(按回合配置生成怪物列表)
*
* 核心公式:
* 最终属性 = HeroInfo 基础属性 × MON_START_COEF × (1 + MON_GROW_SLOPE×(lv-1)) × 类型倍率
* 1 级小怪 ≈ 英雄 × 0.8,10 级小怪 ≈ 英雄 × 1.16,保持平缓成长。
* 小怪属性 = HeroInfo 基础属性 × MON_START_COEF × (1 + MON_GROW_SLOPE×(lv-1)) × 回合增强
* Boss 属性 = 同上 × BOSS_HP_MUL(血)/ BOSS_AP_MUL(攻),等级 = 关卡号无限成长
*/
import { HeroInfo, MonType, MonTypeName, TriggerGrouped } from "../common/config/heroSet";
import { SkillOverrides } from "../common/config/SkillSet";
import { HeroInfo, HType, MonType, TriggerGrouped, UltCdLv, UltCdSet } from "../common/config/heroSet";
import { SkillOverrides, SkillSet } from "../common/config/SkillSet";
import { FacSet } from "../common/config/GameSet";
import {
SectionType, SectionConfig, getSectionConfig, calcMonsterAttrs,
NORMAL_MON_COUNT, NORMAL_FIRST_SPAWN, ELITE_COUNT, BOSS_ELITE_COUNT, BOSS_DELAY,
MON_START_COEF, MON_GROW_SLOPE, ELITE_MUL, BOSS_MUL,
NORMAL_MON_COUNT, NORMAL_FIRST_SPAWN, BOSS_DELAY,
MON_START_COEF,
} from "../common/config/StageSet";
// ======================== 1. 战斗常量 ========================
@@ -79,126 +81,114 @@ export function getMonsterGoldDrop(monType: number, isBoss: boolean): number {
return Math.max(0, Math.floor(isBoss ? cfg.boss : cfg.base));
}
// ======================== 3. 怪物技能池 ========================
// ======================== 3. Boss 池(按攻击定位分组) ========================
/**
* 怪物技能套装(一套完整的触发技能配置)
* 覆盖触发时机:call / atking / atked / dead / fstart / revive
* Boss 池:按攻击定位(近战/中程/远程)分组,动态从 HeroInfo 提取。
* 加载时随机一个定位 → 从对应池随机 Boss;定位池为空时自动回退(如暂无中程 Boss 配置)。
*/
export interface MonSkillSet {
id: string;
export const BossPools: Record<HType, number[]> = {
[HType.Melee]: [],
[HType.Mid]: [],
[HType.Long]: [],
};
for (const key in HeroInfo) {
const info = HeroInfo[key];
if (info.fac !== FacSet.MON) continue;
if (info.monType !== MonType.MeleeBoss && info.monType !== MonType.LongBoss) continue;
const pool = BossPools[info.type as HType];
if (pool) pool.push(info.uuid);
}
// ======================== 4. Boss 大招池 ========================
/** 每解锁 1 个额外大招收紧的关卡间隔(每 5 关 +1 个大招位) */
export const BOSS_ULT_UNLOCK_EVERY = 5;
/** Boss 同时携带大招数量上限 */
export const BOSS_ULT_MAX_COUNT = 3;
/**
* 获取指定关卡的 Boss 携带大招数量(1 个起步,随关卡解锁扩展)
* @param stage 关卡号(1~∞)
* @returns 大招数量(1~BOSS_ULT_MAX_COUNT)
*/
export function getBossUltCount(stage: number): number {
const st = Math.max(1, Math.floor(stage));
return Math.min(BOSS_ULT_MAX_COUNT, 1 + Math.floor((st - 1) / BOSS_ULT_UNLOCK_EVERY));
}
/** Boss 大招条目(s_uuid 引用 SkillSet 6100~6204 群体大招段,按定位分组) */
export interface BossUltEntry {
s_uuid: number;
name: string;
/** 普攻技能覆盖(可选,不填则使用怪物默认普攻) */
skill?: { s_uuid: number; cd?: number; overrides?: SkillOverrides };
/** 召唤触发 */
call?: TriggerGrouped;
/** 攻击触发 */
atking?: TriggerGrouped;
/** 受击触发 */
atked?: TriggerGrouped;
/** 死亡触发 */
dead?: TriggerGrouped;
/** 战斗开始触发 */
fstart?: TriggerGrouped;
/** 复活触发 */
revive?: TriggerGrouped;
/** 在技能池中的抽取权重 */
/** 抽取权重 */
weight: number;
/** 技能参数覆盖(伤害系数等) */
overrides?: SkillOverrides;
}
/**
* 怪物技能池(硬编码)
* 技能 uuid 引用 SkillSet 中的 6000~6500 段触发技能。
* Boss 大招池(按攻击定位分组)
* 大招注入 Boss 的 skills 表后由 SCastSystem 按 CD 自动施放(含镜头聚焦 + 技能名横幅)。
*/
export const MonSkillPool: Record<string, MonSkillSet> = {
/** 狂暴:攻击触发自身攻击提升 */
berserk: {
id: "berserk", name: "狂暴", weight: 10,
atking: {
6401: [{ lv: 1, t_num: 5, overrides: { ap: 1 } }],
},
},
/** 坚韧:受击获得护盾 */
tough: {
id: "tough", name: "坚韧", weight: 8,
atked: {
6301: [{ lv: 1, t_num: 3, overrides: { ap: 2 } }],
},
},
/** 遗志:死亡时全队攻击提升 */
legacy: {
id: "legacy", name: "遗志", weight: 6,
dead: {
6401: [{ lv: 1, t_num: 1, overrides: { ap: 3 } }],
},
},
/** 战吼:战斗开始时全队攻击提升 */
warcry: {
id: "warcry", name: "战吼", weight: 5,
fstart: {
6401: [{ lv: 1, t_num: 1, overrides: { ap: 2 } }],
},
},
/** 吸血:攻击恢复生命 */
leech: {
id: "leech", name: "吸血", weight: 7,
atking: {
6302: [{ lv: 1, t_num: 4, overrides: { ap: 150 } }],
},
},
export const BossUltPools: Record<HType, BossUltEntry[]> = {
// 近战系:战士群体斩击(6100~6104 段)
[HType.Melee]: [
{ s_uuid: 6100, name: "寒冰斩", weight: 10, overrides: { ap: 180 } },
{ s_uuid: 6101, name: "裂气斩", weight: 10, overrides: { ap: 180 } },
{ s_uuid: 6102, name: "火焰斩", weight: 10, overrides: { ap: 180 } },
{ s_uuid: 6103, name: "闪电斩", weight: 8, overrides: { ap: 180 } },
{ s_uuid: 6104, name: "开天斩", weight: 6, overrides: { ap: 220 } },
],
// 中程系:射手箭雨 + 混合法术(6150~6151 / 6203~6204 段)
[HType.Mid]: [
{ s_uuid: 6150, name: "万箭齐发", weight: 10, overrides: { ap: 150 } },
{ s_uuid: 6151, name: "火箭齐发", weight: 10, overrides: { ap: 150 } },
{ s_uuid: 6203, name: "能量波", weight: 8, overrides: { ap: 150 } },
{ s_uuid: 6204, name: "雷暴", weight: 8, overrides: { ap: 150 } },
],
// 远程系:法师群体法术(6200~6204 段)
[HType.Long]: [
{ s_uuid: 6200, name: "火球术", weight: 10, overrides: { ap: 160 } },
{ s_uuid: 6201, name: "飓风", weight: 10, overrides: { ap: 140 } },
{ s_uuid: 6202, name: "毒气弹", weight: 8, overrides: { ap: 150 } },
{ s_uuid: 6203, name: "能量波", weight: 8, overrides: { ap: 150 } },
{ s_uuid: 6204, name: "雷暴", weight: 8, overrides: { ap: 150 } },
],
};
/** Boss 专属技能池 */
export const BossSkillPool: Record<string, MonSkillSet> = {
/** 狂暴领主:攻击触发全队攻击提升 */
boss_rage: {
id: "boss_rage", name: "狂暴领主", weight: 10,
atking: {
6401: [{ lv: 1, t_num: 3, overrides: { ap: 3 } }],
},
dead: {
6401: [{ lv: 1, t_num: 1, overrides: { ap: 5 } }],
},
},
/** 铁壁领主:受击获得高额护盾 */
boss_iron: {
id: "boss_iron", name: "铁壁领主", weight: 8,
atked: {
6301: [{ lv: 1, t_num: 2, overrides: { ap: 5 } }],
},
},
/** 毁灭领主:战斗开始时全队攻击大幅提升 */
boss_doom: {
id: "boss_doom", name: "毁灭领主", weight: 6,
fstart: {
6401: [{ lv: 1, t_num: 1, overrides: { ap: 8 } }],
},
atking: {
6401: [{ lv: 1, t_num: 5, overrides: { ap: 2 } }],
},
},
};
// ======================== 4. 配置校验 ========================
// ======================== 5. 配置校验 ========================
/**
* 校验 MonSkillPool / BossSkillPool 引用一致性
* 校验 Boss 池 / Boss 大招池引用一致性
* 建议在游戏启动时调用一次,便于发现配置错误
* @returns 错误信息数组,空数组表示校验通过
*/
export function validateRogueConfig(): string[] {
const errors: string[] = [];
// 校验 Boss 类型在 MonList 中有怪
if ((!MonList[MonType.MeleeBoss] || MonList[MonType.MeleeBoss].length === 0)
&& (!MonList[MonType.LongBoss] || MonList[MonType.LongBoss].length === 0)) {
errors.push("MonList 中无任何 Boss 类型(MeleeBoss/LongBoss)可用 uuid");
// 校验至少一个定位有可用 Boss
const hasBoss = (Object.keys(BossPools) as unknown as HType[]).some(t => BossPools[t].length > 0);
if (!hasBoss) {
errors.push("BossPools 中无任何可用 Boss uuid(MeleeBoss/LongBoss 均缺失)");
}
// 校验大招池条目均存在于 SkillSet
for (const key in BossUltPools) {
const pool = BossPools[key as unknown as HType];
const ults = BossUltPools[key as unknown as HType];
if (!pool || pool.length === 0) continue; // 空定位池跳过(大招池无消费方)
for (const ult of ults) {
if (!SkillSet[ult.s_uuid]) {
errors.push(`BossUltPools[${key}] 大招 ${ult.s_uuid}(${ult.name}) 未在 SkillSet 中定义`);
}
}
}
return errors;
}
// ======================== 5. 生成结果接口 ========================
// ======================== 6. 生成结果接口 ========================
/** 生成结果(传递给 MissionMonComp 用于实例化怪物) */
export interface GeneratedMonster {
@@ -207,10 +197,8 @@ export interface GeneratedMonster {
hp: number;
ap: number;
isBoss: boolean;
/** 是否为精英(精英关/Boss 关的精英) */
/** 是否为精英(对象池复用实体需显式重置的标记位) */
isElite?: boolean;
/** 是否为 Boss 护卫队成员(供 UI/统计识别) */
isBossGuard?: boolean;
/** 怪物等级(驱动技能等级增量,Mon.load 第 6 参) */
lv: number;
spawnIndex: number;
@@ -220,11 +208,15 @@ export interface GeneratedMonster {
delay?: number;
/**
* 怪物技能套装(覆盖全部触发时机)
* 注入方式与 _testSkills 相同,在 Mon.load() 中写入 HeroAttrsComp
* 怪物技能套装(技能注入通道,与 _testSkills 相同,在 Mon.load() 中写入 HeroAttrsComp)
* - skill:替换普攻(会清空原技能表)
* - ults:追加 Boss 大招(保留普攻,多个大招共存,SCastSystem 按 CD 轮转施放)
* - 其余为触发技能组覆盖
*/
skills?: {
skill?: { s_uuid: number; cd?: number; overrides?: SkillOverrides };
/** Boss 大招列表(追加注入,不替换普攻) */
ults?: { s_uuid: number; cd?: number; overrides?: SkillOverrides }[];
call?: TriggerGrouped;
atking?: TriggerGrouped;
atked?: TriggerGrouped;
@@ -234,7 +226,7 @@ export interface GeneratedMonster {
};
}
// ======================== 6. 测试模式配置 ========================
// ======================== 7. 测试模式配置 ========================
export const TestModeConfig = {
enable: false, // 默认关闭测试模式
@@ -245,48 +237,43 @@ export const TestModeConfig = {
spawnCount: 1,
skill: undefined as { s_uuid: number; cd?: number; overrides?: SkillOverrides } | undefined,
/** 测试模式注入的大招列表(追加,不替换普攻) */
ults: undefined as { s_uuid: number; cd?: number; overrides?: SkillOverrides }[] | undefined,
atking: undefined as TriggerGrouped | undefined,
atked: undefined as TriggerGrouped | undefined,
dead: undefined as TriggerGrouped | undefined,
fstart: undefined as TriggerGrouped | undefined,
};
// ======================== 7. 生成引擎 ========================
// ======================== 8. 生成引擎 ========================
/**
* 关卡刷怪生成引擎
* 按章节-关-节配置生成怪物列表,属性走 StageSet 数值公式
* 无限关卡刷怪生成引擎
* 按关-回合配置生成怪物列表,属性走 StageSet 数值公式
*/
export class StageSpawningEngine {
/**
* 生成指定小节的怪物列表
* @param chapter 章节号
* @param stage 关卡号
* @param section 小节号(1~10)
* 生成指定回合的怪物列表
* @param stage 关卡号(1~∞)
* @param section 回合号(1~SECTIONS_PER_STAGE)
* @returns 怪物列表(按刷出顺序排列)
*/
generateSectionMonsters(chapter: number, stage: number, section: number): GeneratedMonster[] {
generateSectionMonsters(stage: number, section: number): GeneratedMonster[] {
// 测试模式拦截:完全绕过生成引擎,直接返回硬编码测试怪
if (TestModeConfig.enable) {
return this.generateTestWave();
}
const cfg = getSectionConfig(chapter, stage, section);
const cfg = getSectionConfig(stage, section);
if (!cfg) {
// 配置缺失兜底:生成 3 只 Lv1 近战小怪
return this.makeFallbackMonsters();
}
switch (cfg.type) {
case SectionType.Elite:
return this.generateEliteSection(cfg);
case SectionType.Boss:
return this.generateBossSection(cfg);
case SectionType.Normal:
default:
return this.generateNormalSection(cfg);
}
return cfg.type === SectionType.Boss
? this.generateBossSection(cfg, stage)
: this.generateNormalSection(cfg);
}
/** 重置引擎状态(每局开始时调用,预留) */
@@ -294,7 +281,8 @@ export class StageSpawningEngine {
}
/**
* 普通节:NORMAL_MON_COUNT 只小怪全部立即刷出(batch=0)
* 普通回合(1~4):NORMAL_MON_COUNT 只小怪全部立即刷出(batch=0),
* 属性乘回合增强系数(cfg.ramp)实现逐回合轻量增强
*/
private generateNormalSection(cfg: SectionConfig): GeneratedMonster[] {
const monsters: GeneratedMonster[] = [];
@@ -304,64 +292,49 @@ export class StageSpawningEngine {
const type = types[i % types.length];
const mon = this.makeMonster(type, cfg.monLv, i);
mon.batch = i < NORMAL_FIRST_SPAWN ? 0 : 1;
// 回合内轻量增强:第 n 回合 × (1 + SECTION_RAMP_STEP×(n-1))
mon.hp = Math.max(1, Math.round(mon.hp * cfg.ramp));
mon.ap = Math.max(1, Math.round(mon.ap * cfg.ramp));
monsters.push(mon);
}
return monsters;
}
/**
* 精英节:3 只精英同时刷出
* Boss 回合(5):单 Boss 登场(无护卫)
* Boss 延迟 BOSS_DELAY 秒刷出,属性由 makeBoss 按血/攻分离倍率计算(超厚血量)
*/
private generateEliteSection(cfg: SectionConfig): GeneratedMonster[] {
const monsters: GeneratedMonster[] = [];
const count = cfg.eliteCount ?? ELITE_COUNT;
// 精英池:近战/重型/远程/刺客
const eliteTypes = [MonType.Melee, MonType.Heavy, MonType.Long, MonType.Assassin];
for (let i = 0; i < count; i++) {
const type = eliteTypes[i % eliteTypes.length];
const mon = this.makeMonster(type, cfg.monLv, i);
mon.isElite = true;
mon.batch = 0;
// 精英属性 = 普通 × ELITE_MUL
mon.hp = Math.max(1, Math.round(mon.hp * ELITE_MUL));
mon.ap = Math.max(1, Math.round(mon.ap * ELITE_MUL));
monsters.push(mon);
}
return monsters;
}
/**
* Boss 节:2 小怪护卫 + 1 Boss
* 小怪先刷(batch=0),Boss 延迟刷出(batch=0, delay=BOSS_DELAY)
*/
private generateBossSection(cfg: SectionConfig): GeneratedMonster[] {
const monsters: GeneratedMonster[] = [];
const eliteCount = cfg.eliteCount ?? BOSS_ELITE_COUNT;
const eliteTypes = [MonType.Melee, MonType.Heavy];
// 先刷精英
for (let i = 0; i < eliteCount; i++) {
const type = eliteTypes[i % eliteTypes.length];
const mon = this.makeMonster(type, cfg.monLv, i);
mon.isElite = true;
mon.isBossGuard = true;
mon.batch = 0;
mon.hp = Math.max(1, Math.round(mon.hp * ELITE_MUL));
mon.ap = Math.max(1, Math.round(mon.ap * ELITE_MUL));
monsters.push(mon);
}
// Boss 延迟刷出
const boss = this.makeBoss(cfg.monLv, monsters.length);
private generateBossSection(cfg: SectionConfig, stage: number): GeneratedMonster[] {
const boss = this.makeBoss(cfg.monLv, stage, 0);
boss.batch = 0;
boss.delay = BOSS_DELAY;
// Boss 属性 = 普通 × BOSS_MUL
boss.hp = Math.max(1, Math.round(boss.hp * BOSS_MUL));
boss.ap = Math.max(1, Math.round(boss.ap * BOSS_MUL));
monsters.push(boss);
return [boss];
}
return monsters;
/** 生成普通怪物(属性按 StageSet 公式计算) */
private makeMonster(type: MonType, monLv: number, spawnIndex: number): GeneratedMonster {
let uuids = MonList[type];
if (!uuids || uuids.length === 0) {
// 兜底 Melee
uuids = MonList[MonType.Melee] || [6001];
}
const uuid = uuids[Math.floor(Math.random() * uuids.length)];
const baseInfo = HeroInfo[uuid];
const baseHp = baseInfo ? baseInfo.hp : 100;
const baseAp = baseInfo ? baseInfo.ap : 10;
const attrs = calcMonsterAttrs(baseHp, baseAp, monLv, false);
return {
uuid,
type,
hp: attrs.hp,
ap: attrs.ap,
isBoss: false,
lv: monLv,
spawnIndex,
batch: 0,
};
}
/** 配置缺失兜底:3 只 Lv1 近战小怪 */
@@ -399,6 +372,7 @@ export class StageSpawningEngine {
batch: 0,
skills: {
skill: TestModeConfig.skill,
ults: TestModeConfig.ults,
atking: TestModeConfig.atking,
atked: TestModeConfig.atked,
dead: TestModeConfig.dead,
@@ -409,77 +383,74 @@ export class StageSpawningEngine {
return monsters;
}
/** 生成 Boss(基础面板按 StageSet 公式计算) */
private makeBoss(monLv: number, spawnIndex: number): GeneratedMonster {
const isMeleeBoss = Math.random() < 0.5;
const type = isMeleeBoss ? MonType.MeleeBoss : MonType.LongBoss;
/**
* 生成 Boss:加载时随机攻击定位 → 从对应池随机 Boss 个体 → 随机携带大招
* 属性走 calcMonsterAttrs(isBoss=true):HP × BOSS_HP_MUL(超厚血)/ AP × BOSS_AP_MUL
*/
private makeBoss(monLv: number, stage: number, spawnIndex: number): GeneratedMonster {
const range = this.pickBossRange();
const uuid = this.pickBossUuid(range);
// Boss 类型兜底:若 MonList 缺失则退回 MeleeBoss
let uuids = MonList[type];
if (!uuids || uuids.length === 0) {
uuids = MonList[MonType.MeleeBoss] || MonList[MonType.Melee] || [6001];
}
const uuid = uuids[Math.floor(Math.random() * uuids.length)];
// Boss 基础面板取 HeroInfo 基础值,属性由调用方乘 BOSS_MUL
const baseInfo = HeroInfo[uuid];
const baseHp = baseInfo ? baseInfo.hp : 600;
const baseAp = baseInfo ? baseInfo.ap : 30;
const attrs = calcMonsterAttrs(baseHp, baseAp, monLv, false, false);
const attrs = calcMonsterAttrs(baseHp, baseAp, monLv, true);
return {
uuid,
type,
type: baseInfo?.monType ?? MonType.MeleeBoss,
hp: attrs.hp,
ap: attrs.ap,
isBoss: true,
lv: monLv,
spawnIndex,
batch: 0,
skills: {
ults: this.pickBossUlts(range, stage),
},
};
}
/** 生成普通怪物(属性按 StageSet 公式计算) */
private makeMonster(type: MonType, monLv: number, spawnIndex: number): GeneratedMonster {
let uuids = MonList[type];
/**
* 随机 Boss 攻击定位(近战/中程/远程)
* 空池自动回退:中程暂无 Boss 配置时落到远程/近战,后续补配即自动生效
*/
private pickBossRange(): HType {
const all: HType[] = [HType.Melee, HType.Mid, HType.Long];
const first = all[Math.floor(Math.random() * all.length)];
if (BossPools[first].length > 0) return first;
// 回退链:随机定位空池时按剩余定位找非空池
const fallback = all.find(t => BossPools[t].length > 0);
return fallback ?? HType.Melee;
}
/** 从指定定位池随机取 Boss uuid(含全局兜底) */
private pickBossUuid(range: HType): number {
let uuids = BossPools[range];
if (!uuids || uuids.length === 0) {
// 兜底 Melee
uuids = MonList[MonType.Melee] || [6001];
uuids = MonList[MonType.MeleeBoss] || MonList[MonType.Melee] || [6001];
}
const uuid = uuids[Math.floor(Math.random() * uuids.length)];
const baseInfo = HeroInfo[uuid];
const baseHp = baseInfo ? baseInfo.hp : 100;
const baseAp = baseInfo ? baseInfo.ap : 10;
const attrs = calcMonsterAttrs(baseHp, baseAp, monLv, false, false);
return {
uuid,
type,
hp: attrs.hp,
ap: attrs.ap,
isBoss: false,
lv: monLv,
spawnIndex,
batch: 0,
};
return uuids[Math.floor(Math.random() * uuids.length)];
}
/** 从技能池中按权重抽取一套技能 */
private pickSkillSet(pool: string[], library: Record<string, MonSkillSet>): GeneratedMonster["skills"] | undefined {
const valid = pool.map(id => library[id]).filter(s => s);
if (valid.length === 0) return undefined;
const picked = this.pickWeighted(valid);
if (!picked) return undefined;
return {
skill: picked.skill,
call: picked.call,
atking: picked.atking,
atked: picked.atked,
dead: picked.dead,
fstart: picked.fstart,
revive: picked.revive,
};
/**
* 按定位抽取 Boss 大招(不重复),数量由关卡进度决定(getBossUltCount)
* @returns 大招配置列表;池空时返回 undefined
*/
private pickBossUlts(range: HType, stage: number): GeneratedMonster["skills"]["ults"] {
const pool = [...(BossUltPools[range] ?? [])];
const count = Math.min(getBossUltCount(stage), pool.length);
if (count <= 0) return undefined;
const ults: { s_uuid: number; cd?: number; overrides?: SkillOverrides }[] = [];
for (let i = 0; i < count; i++) {
const picked = this.pickWeighted(pool);
if (!picked) break;
pool.splice(pool.indexOf(picked), 1);
// 大招 CD 取英雄同档标准档(8s),与英雄大招节奏对齐
ults.push({ s_uuid: picked.s_uuid, cd: UltCdSet[UltCdLv.Normal].cd, overrides: picked.overrides });
}
return ults.length > 0 ? ults : undefined;
}
/** 通用加权随机抽取 */
@@ -496,18 +467,6 @@ export class StageSpawningEngine {
}
}
// ======================== 8. 全局单例 ========================
// ======================== 9. 全局单例 ========================
export const spawningEngine = new StageSpawningEngine();
// 保留旧接口兼容(逐步迁移期使用)
/** @deprecated 请使用 StageSpawningEngine.generateSectionMonsters */
export class RogueSpawningEngine extends StageSpawningEngine {
/** @deprecated 请使用 generateSectionMonsters */
generateBossCombo(): GeneratedMonster[] {
return this.generateSectionMonsters(1, 1, 1);
}
}
/** @deprecated 请使用 spawningEngine */
export const rogueSpawningEngine = new RogueSpawningEngine();

View File

@@ -0,0 +1,247 @@
/**
* @file TIBoxComp.ts
* @description 天赋详情弹窗组件(UI 视图层,TIBox.prefab 载体)
*
* 职责:
* 1. 展示天赋图标 / 名称 / 当前等级 / 条幅说明(getTalentDisplayText)。
* 2. 展示层位需求行:前置层位名称(如:1 级天赋)、投入进度(如:5/5)、解锁勾图标(解锁亮 On,未解锁亮 Off)。
* 3. 展示软前置行:前置天赋名称、等级进度(当前/需求,如:0/5)、前置已达成勾图标(达成亮 On,未达成亮 Off)。
* 4. 加点按钮:仅在可加点(层位已解锁 + 前置满足 + 未满级 + 点数足够)时显示;
* 不满足条件或已满级时隐藏,弹窗仍可正常打开查看。
* 5. 加点成功后通过 TALENT_UPDATE 事件驱动 TalentsComp 全量刷新,并同步刷新本弹窗。
*
* 使用(oops.gui 模块):
* oops.gui.open(UIID.TIBox, { uuid: 9001 });
*
* 依赖:
* - TalentSet(TalentConfig 配置与校验:isTalentTierUnlocked/isTalentPrereqMet/getTalentUpgradeCost)
* - smc —— 天赋等级表 / 点数 / upgradeTalent / 图标图集
* - outlineRich —— RichText 条幅描边
*/
import { _decorator, EventTouch, Label, Node, NodeEventType, RichText, 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 { TalentSet, TalentConfig, TalentTier, TALENT_TIER_UNLOCK_THRESHOLD, getTalentTierInvestedPoints, getTalentUpgradeCost, isTalentPrereqMet, isTalentTierUnlocked } from "../common/config/TalentSet";
import { GameEvent } from "../common/config/GameEvent";
import { HRoleName } from "../common/config/heroSet";
import { outlineRich } from "../common/RichOutline";
import { smc } from "../common/SingletonModuleComp";
import { oops } from "db://oops-framework/core/Oops";
const { ccclass, property } = _decorator;
/** TIBox 弹窗初始化参数 */
export interface TIBoxParams {
/** 天赋 uuid(TalentSet key) */
uuid: number;
}
/** TIBoxComp —— 天赋详情弹窗(任何状态均可点开,不可加点时隐藏加点按钮) */
@ccclass('TIBoxComp')
@ecs.register('TIBoxComp', false)
export class TIBoxComp extends CCComp {
@property({ type: Sprite, tooltip: "天赋图标" })
icon: Sprite = null!;
@property({ type: Label, tooltip: "天赋名称" })
lbl_name: Label = null!;
@property({ type: Label, tooltip: "当前天赋等级(当前/满级)" })
lbl_level: Label = null!;
@property({ type: Label, tooltip: "前置天赋名称(无前置时隐藏前置行)" })
lbl_prereq_name: Label = null!;
@property({ type: Label, tooltip: "前置天赋等级进度(当前/需求,如:0/5)" })
lbl_prereq_lv: Label = null!;
@property({ type: Node, tooltip: "前置已达成勾图标(On)" })
node_prereq_check_on: Node = null!;
@property({ type: Node, tooltip: "前置未达成勾图标(Off)" })
node_prereq_check_off: Node = null!;
@property({ type: Node, tooltip: "前置行容器(无前置天赋时整体隐藏)" })
node_prereq: Node = null!;
@property({ type: Node, tooltip: "层位需求行容器(第 1 层天赋无层位需求,整体隐藏)" })
node_tier: Node = null!;
@property({ type: Label, tooltip: "前置层位名称(如:1 级天赋)" })
lbl_tier_name: Label = null!;
@property({ type: Label, tooltip: "前一层投入进度(已投入/门槛,如:5/5)" })
lbl_tier_progress: Label = null!;
@property({ type: Node, tooltip: "层位已解锁勾图标(On)" })
node_tier_check_on: Node = null!;
@property({ type: Node, tooltip: "层位未解锁勾图标(Off)" })
node_tier_check_off: Node = null!;
@property({ type: RichText, tooltip: "条幅说明(当前已生效数值 / 每级引导)" })
richtext_info: RichText = null!;
@property({ type: Node, tooltip: "加点按钮(不可加点或满级时隐藏)" })
node_add: Node = null!;
@property({ type: Node, tooltip: "关闭遮罩/关闭按钮(点击关闭弹窗)" })
node_close: Node = null!;
/** 当前展示的天赋 uuid */
private talent_uuid: number = 0;
protected onLoad(): void {
this.node_add?.on(NodeEventType.TOUCH_END, this.onAddTap, this);
this.node_close?.on(NodeEventType.TOUCH_END, this.onCloseTap, this);
}
protected onDestroy(): void {
this.node_add?.targetOff(this);
this.node_close?.targetOff(this);
oops.message.off(GameEvent.TALENT_UPDATE, this.refresh, this);
}
/**
* oops.gui 添加到舞台回调(框架约定入口)
* @param params 见 TIBoxParams
*/
onAdded(params: TIBoxParams): void {
this.talent_uuid = params?.uuid ?? 0;
// 加点成功后同步刷新本弹窗(等级/按钮状态),与 TalentsComp 同一事件源
oops.message.on(GameEvent.TALENT_UPDATE, this.refresh, this);
this.refresh();
}
/** 全量刷新弹窗内容 */
private refresh(): void {
const cfg = TalentSet[this.talent_uuid];
if (!cfg) return;
const levels = smc.getTalentLevels();
const lv = levels[this.talent_uuid] ?? 0;
// 图标:技能图标图集(gui/ui7)优先,资源目录回退(与 TalentItemComp 口径一致)
if (this.icon && cfg.icon) {
let frame: SpriteFrame | null = null;
if (smc.skillIconsAtlas) {
frame = smc.skillIconsAtlas.getSpriteFrame(cfg.icon);
}
if (!frame) {
frame = oops.res.get("game/heros/cards/" + cfg.icon, SpriteFrame) as SpriteFrame;
}
if (frame) this.icon.spriteFrame = frame;
}
if (this.lbl_name) this.lbl_name.string = cfg.name;
if (this.lbl_level) this.lbl_level.string = `${lv}/${cfg.max_lv}`;
// 条幅说明:已加点显示当前生效数值,未加点显示每级引导
if (this.richtext_info) this.richtext_info.string = outlineRich(this.buildDetailText(cfg, lv));
// 层位需求行:第 1 层恒解锁无需求,整体隐藏;其余层展示前一层名称与投入进度(如:1 级天赋 5/5)
const hasTierNeed = cfg.tier > TalentTier.L1;
if (this.node_tier) this.node_tier.active = hasTierNeed;
if (hasTierNeed) {
const prevTier = (cfg.tier - 1) as TalentTier;
const invested = getTalentTierInvestedPoints(levels, prevTier);
const tierMet = isTalentTierUnlocked(levels, cfg.tier);
if (this.lbl_tier_name) this.lbl_tier_name.string = `${prevTier} 级天赋`;
// 进度超门槛时截断显示(如已投入 7 点仍显示 5/5,避免 7/5 的歧义观感)
const shown = Math.min(invested, TALENT_TIER_UNLOCK_THRESHOLD);
if (this.lbl_tier_progress) this.lbl_tier_progress.string = `${shown}/${TALENT_TIER_UNLOCK_THRESHOLD}`;
if (this.node_tier_check_on) this.node_tier_check_on.active = tierMet;
if (this.node_tier_check_off) this.node_tier_check_off.active = !tierMet;
}
// 软前置行:无前置整体隐藏;有前置展示名称/需求等级/达成勾
const hasPrereq = !!(cfg.prereq_uuid && cfg.prereq_lv);
if (this.node_prereq) this.node_prereq.active = hasPrereq;
if (hasPrereq) {
const preCfg = TalentSet[cfg.prereq_uuid!];
const preMet = isTalentPrereqMet(levels, cfg);
if (this.lbl_prereq_name) this.lbl_prereq_name.string = preCfg ? preCfg.name : `天赋${cfg.prereq_uuid}`;
// 前置等级进度(当前/需求,如:0/5);超需求时截断显示,避免 7/5 的歧义观感
const preLv = Math.min(levels?.[cfg.prereq_uuid!] ?? 0, cfg.prereq_lv!);
if (this.lbl_prereq_lv) this.lbl_prereq_lv.string = `${preLv}/${cfg.prereq_lv}`;
if (this.node_prereq_check_on) this.node_prereq_check_on.active = preMet;
if (this.node_prereq_check_off) this.node_prereq_check_off.active = !preMet;
}
// 加点按钮:层位解锁 + 前置满足 + 未满级 + 点数足够时才显示
if (this.node_add) this.node_add.active = this.canUpgrade(lv, cfg.tier, levels);
}
/**
* 按等级精准渲染天赋 info 模板({value}/{value2} 占位替换为实际数值)。
* Lv0 时按 1 级数值预览(引导加点)。
* @param cfg 天赋配置
* @param lv 当前等级
* @returns 替换占位后的完整描述文本
*/
private buildPreciseText(cfg: TalentConfig, lv: number): string {
const mult = lv > 0 ? lv : 1;
const v1 = cfg.bonuses[0]?.per_lv ?? cfg.t_num_reduce_per_lv ?? cfg.extra_trigger_chance_per_lv ?? 0;
const v2 = cfg.bonuses[1]?.per_lv ?? 0;
return cfg.info
.replace("{value}", `${v1 * mult}`)
.replace("{value2}", `${v2 * mult}`);
}
/**
* 构建天赋详情条幅文本:精准描述 + 下一级预览 + 满级提示
* @param cfg 天赋配置
* @param lv 当前等级
*/
private buildDetailText(cfg: TalentConfig, lv: number): string {
const lines: string[] = [];
// 精准效果描述:使用 cfg.info 模板按当前等级替换数值占位
lines.push(this.buildPreciseText(cfg, lv));
// 未满级:追加下一级预览
if (lv < cfg.max_lv) {
lines.push(`<color=#E0A93D>下一级:${this.buildPreciseText(cfg, lv + 1)}</color>`);
} else {
lines.push(`<color=#FFD700>已满级</color>`);
}
// 职业限定提示
if (cfg.role !== undefined) {
lines.push(`<color=#AAAAAA>仅${HRoleName[cfg.role]}生效</color>`);
}
return lines.join("\n");
}
/**
* 当前是否可加点(与 smc.upgradeTalent 校验口径一致,仅做 UI 预判定)
* @param lv 当前等级
* @param tier 所属层位
* @param levels 天赋等级表
*/
private canUpgrade(lv: number, tier: number, levels: Record<number, number>): boolean {
const cfg = TalentSet[this.talent_uuid];
if (!cfg) return false;
if (lv >= cfg.max_lv) return false;
if (!isTalentTierUnlocked(levels, tier)) return false;
if (!isTalentPrereqMet(levels, cfg)) return false;
return smc.getTalentPoints() >= getTalentUpgradeCost(tier);
}
/** 加点按钮点击:升级成功后由 TALENT_UPDATE 事件驱动刷新(含本弹窗) */
private onAddTap(event?: EventTouch): void {
if (event) event.propagationStopped = true;
oops.audio.playEffect("music/button");
smc.upgradeTalent(this.talent_uuid);
}
/** 点击关闭 */
private onCloseTap(event?: EventTouch): void {
if (event) event.propagationStopped = true;
oops.audio.playEffect("music/button");
oops.gui.removeByNode(this.node);
}
/** ECS 组件移除时销毁节点(CCComp 抽象方法实现) */
reset(): void {
if (this.node && this.node.isValid) {
this.node.destroy();
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "33249e41-30b1-42e3-a12e-f525b5580c37",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -1,40 +1,45 @@
/**
* @file TalentItemComp.ts
* @description 天赋迷你升级卡组件(UI 视图层)
* @description 天赋迷你升级卡组件(UI 视图层,编辑器静态摆放)
*
* 职责:
* 1. 接收 TalentsComp 下发的天赋配置 + 当前等级 + 阶位解锁态,渲染迷你点选卡。
* 1. 通过编辑器配置的 uuid 自查询天赋配置 + 当前等级 + 层位解锁态,渲染迷你点选卡。
* 2. 卡面自包含:图标 / 名称 / 当前已生效数值(Lv0 显示每级数值引导)/ 右下「当前/满级」角标。
* 3. 阶位背景:普通背景节点下挂 blue / red / yellow 三个颜色子节点,按阶位激活其一(Ⅰ蓝 / Ⅱ红 / Ⅲ金)。
* 4. 状态机:满级(MAX 金角标)/ 未激活(左上锁角标,不再做灰阶处理)。
* 3. 层位背景:普通背景节点下挂 blue / red / yellow 三个颜色子节点,按层位映射激活其一(L1蓝 / L2红 / L3金,L4/L5 复用金)。
* 4. 状态机:锁定(锁遮罩:层位未解锁或前置未满足;可加点时不显示,不做灰阶处理)/ 状态边框(满级 max 边框、已加点未满 active 边框,未加点均隐藏)。
* 5. 点击回调上抛卡体点击,加点确认弹窗与升级校验统一由 TalentsComp 处理(单一决策点)。
*
* 依赖:
* - TalentSet.TalentConfig / TalentTier / getTalentDisplayText —— 配置与卡面文案
* - TalentSet(TalentConfig/TalentTier/getTalentDisplayText/isTalentTierUnlocked/isTalentPrereqMet)—— 配置与校验
* - 背景三颜色子节点(blue / red / yellow,编辑器侧搭建)
* - smc.uiconsAtlas —— 图标解析(缺省回退资源目录)
* - smc —— 天赋等级表与图标图集(缺省回退资源目录)
*/
import { _decorator, Label, Node, NodeEventType, Sprite, SpriteFrame } from "cc";
import { _decorator, CCInteger, Label, Node, NodeEventType, 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 { TalentConfig, TalentTier, getTalentDisplayText } from "../common/config/TalentSet";
import { TalentSet, TalentTier, getTalentDisplayText, isTalentPrereqMet, isTalentTierUnlocked } from "../common/config/TalentSet";
import { smc } from "../common/SingletonModuleComp";
import { oops } from "db://oops-framework/core/Oops";
const { ccclass, property } = _decorator;
/** 阶位 → 背景节点下的颜色子节点名(Ⅰ蓝 / Ⅱ红 / Ⅲ金) */
/** 层位 → 背景节点下的颜色子节点名(L1蓝 / L2红 / L3金;L4/L5 复用金) */
const TIER_BG_CHILD_NAMES: Record<number, string> = {
[TalentTier.Basic]: "blue",
[TalentTier.Specialized]: "red",
[TalentTier.Super]: "yellow",
[TalentTier.L1]: "blue",
[TalentTier.L2]: "red",
[TalentTier.L3]: "yellow",
[TalentTier.L4]: "yellow",
[TalentTier.L5]: "yellow",
};
/** TalentItemComp —— 天赋迷你升级卡组件 */
/** TalentItemComp —— 天赋迷你升级卡组件(编辑器静态摆放,按 uuid 自驱动渲染) */
@ccclass('TalentItemComp')
@ecs.register('TalentItem', false)
export class TalentItemComp extends CCComp {
@property({ type: CCInteger, tooltip: "天赋 uuid(TalentSet key,编辑器侧配置)" })
talent_uuid: number = 0;
@property({ type: Label, tooltip: "天赋名称" })
lbl_name: Label = null!;
@@ -47,20 +52,21 @@ export class TalentItemComp extends CCComp {
@property({ type: Sprite, tooltip: "天赋图标" })
icon: Sprite = null!;
@property({ type: Node, tooltip: "卡牌背景节点(下挂 blue / red / yellow 三颜色子节点,按阶位激活其一)" })
@property({ type: Node, tooltip: "卡牌背景节点(下挂 blue / red / yellow 三颜色子节点,按层位激活其一)" })
bg: Node = null!;
@property({ type: Node, tooltip: "MAX 满级角标" })
node_max: Node = null!;
@property({ type: Node, tooltip: "未激活锁遮罩/锁图标(未加点或阶位未解锁)" })
@property({ type: Node, tooltip: "锁遮罩/锁图标(层位未解锁或前置未满足时显示;可加点时隐藏)" })
node_lock: Node = null!;
/** 点击回调(卡体与 + 徽标同源上抛),由父级统一校验升级 */
public onTalentTap: ((uuid: number) => void) | null = null;
@property({ type: Node, tooltip: "满级边框(lv 达到 max_lv 时显示)" })
node_border_max: Node = null!;
/** 当前展示的天赋 uuid(回调携带) */
private curUuid: number = 0;
@property({ type: Node, tooltip: "已加点边框(0 < lv < max_lv 时显示)" })
node_border_active: Node = null!;
/** 点击回调(卡体点击上抛),由 TalentsComp 统一校验升级 */
public onTalentTap: ((uuid: number) => void) | null = null;
protected onLoad(): void {
this.node.on(NodeEventType.TOUCH_END, this.onTap, this);
@@ -70,25 +76,24 @@ export class TalentItemComp extends CCComp {
this.node.off(NodeEventType.TOUCH_END, this.onTap, this);
}
/**
* 刷新迷你卡展示
* @param cfg 天赋配置(TalentSet 条目)
* @param lv 当前等级
* @param tierUnlocked 所属阶位是否已解锁
*/
public updateItem(cfg: TalentConfig, lv: number, tierUnlocked: boolean): void {
/** 刷新迷你卡展示:按编辑器配置的 uuid 自查询配置与等级,层位/前置校验内部完成 */
public updateItem(): void {
const cfg = TalentSet[this.talent_uuid];
if (!cfg) return;
this.curUuid = cfg.uuid;
const levels = smc.getTalentLevels();
const lv = levels[this.talent_uuid] ?? 0;
const tierUnlocked = isTalentTierUnlocked(levels, cfg.tier);
const prereqMet = isTalentPrereqMet(levels, cfg);
if (this.lbl_name) this.lbl_name.string = cfg.name;
// 卡面自包含:已加点显示当前生效数值,未加点显示每级数值引导
if (this.lbl_info) this.lbl_info.string = getTalentDisplayText(cfg, lv);
if (this.lbl_lv) this.lbl_lv.string = `${lv}/${cfg.max_lv}`;
// 未激活(Lv0 或阶位未解锁):仅显示 node_lock 锁遮罩,不做灰阶处理
const inactive = lv <= 0 || !tierUnlocked;
// 锁遮罩:仅在层位未解锁或前置未满足时显示;已解锁且前置满足即可加点,不再因 Lv0 挂锁
const locked = !tierUnlocked || !prereqMet;
// 阶位背景:激活对应颜色子节点(Ⅰ蓝 / Ⅱ红 / Ⅲ金)
// 层位背景:激活对应颜色子节点(L1蓝 / L2红 / L3金)
if (this.bg) this.applyTierBg(cfg.tier);
// 图标:技能图标图集(gui/ui7)优先,资源目录回退(与 SCardComp 口径一致)
@@ -103,12 +108,15 @@ export class TalentItemComp extends CCComp {
if (frame) this.icon.spriteFrame = frame;
}
// 状态角标:满级 MAX / 未激活锁角标(未加点或阶位未解锁,置于左上角)
if (this.node_max) this.node_max.active = lv >= cfg.max_lv;
if (this.node_lock) this.node_lock.active = inactive;
// 锁遮罩:层位未解锁或前置未满足时显示
if (this.node_lock) this.node_lock.active = locked;
// 状态边框:满级显示 max 边框;已加点未满显示 active 边框;未加点均隐藏
if (this.node_border_max) this.node_border_max.active = lv >= cfg.max_lv;
if (this.node_border_active) this.node_border_active.active = lv > 0 && lv < cfg.max_lv;
}
/** 按阶位激活 bg 下对应颜色子节点,其余隐藏 */
/** 按层位激活 bg 下对应颜色子节点,其余隐藏 */
private applyTierBg(tier: TalentTier): void {
const curName = TIER_BG_CHILD_NAMES[tier];
for (const [, name] of Object.entries(TIER_BG_CHILD_NAMES)) {
@@ -120,7 +128,7 @@ export class TalentItemComp extends CCComp {
/** 卡体点击上抛 uuid,加点确认与升级校验由 TalentsComp 处理 */
private onTap(): void {
this.onTalentTap?.(this.curUuid);
this.onTalentTap?.(this.talent_uuid);
}
/** ECS 组件移除时销毁节点(CCComp 抽象方法实现) */

View File

@@ -1,65 +1,39 @@
/**
* @file TalentsComp.ts
* @description 天赋加点页组件(UI 视图层)
* @description 天赋加点页组件(UI 视图层,最小容器)
*
* 职责:
* 1. 三阶页签(Ⅰ 基础 / Ⅱ 专精 / Ⅲ 超强)状态机:单屏聚焦当前阶位的迷你天赋卡。
* 2. 页签常显解锁进度(前阶投入 x/10),点击天赋卡弹出 CommonPrompt 确认加点(smc.upgradeTalent)。
* 3. 监听 GameEvent.TALENT_UPDATE 全量重渲染(23 条卡片,无需虚拟列表)。
* 1. 页头展示:可用天赋点 / 天赋等级(Lv.x)/ 当前等级经验进度 / 各层已投入点数。
* 2. 收集子树内全部 TalentItemComp(编辑器静态摆放),TALENT_UPDATE 事件驱动全量刷新。
* 3. 卡片点击统一打开 TIBox 天赋详情弹窗;加点校验(层位/前置/满级/点数)由 TIBoxComp 内聚,
* 不满足条件时弹窗隐藏加点按钮,仅作详情查看。
* 4. 看广告重置入口:CommonPrompt 二次确认后调 smc.resetTalents() 退还全部点数。
*
* 依赖:
* - TalentSet(TalentSet/TalentTier/isTalentTierUnlocked/getTalentTierInvestedPoints)—— 配置与阶位校验
* - TalentItemComp —— 单张迷你升级卡
* - TalentSet(getTalentTierInvestedPoints/getTalentLevelExp)—— 配置与统计
* - TalentItemComp —— 单张迷你升级卡(编辑器静态摆放,按 uuid 自驱动渲染)
* - TIBoxComp —— 天赋详情弹窗(UIID.TIBox)
* - smc —— 天赋等级表/点数/升级/重置
*/
import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, ProgressBar, UITransform } from "cc";
import { _decorator, Label, Node, NodeEventType, ProgressBar } 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 {
TalentSet, TalentTier,
TALENT_TIER_UNLOCK_THRESHOLD,
getTalentDisplayText, getTalentLevelExp, getTalentTierInvestedPoints, getTalentUpgradeCost, isTalentTierUnlocked,
getTalentLevelExp, getTalentTierInvestedPoints,
} from "../common/config/TalentSet";
import { GameEvent } from "../common/config/GameEvent";
import { UIID } from "../common/config/GameUIConfig";
import { SmallTipComp } from "./SmallTipComp";
import { TalentItemComp } from "./TalentItemComp";
import { smc } from "../common/SingletonModuleComp";
import { oops } from "db://oops-framework/core/Oops";
const { ccclass, property } = _decorator;
/** 阶位页签展示文案(lockName 用于未解锁提示) */
const TIER_TAB_TEXT: Record<number, { title: string; lockName: string }> = {
[TalentTier.Basic]: { title: "基础天赋", lockName: "基础天赋" },
[TalentTier.Specialized]: { title: "专精天赋", lockName: "基础天赋" },
[TalentTier.Super]: { title: "传说天赋", lockName: "专精天赋" },
};
/** 阶位页签选中高亮子节点名(可选,编辑器侧搭建) */
const TAB_SELECTED_CHILD = "sp_on";
/** 阶位页签锁定图标子节点名(可选;阶位未解锁时显示) */
const TAB_LOCKED_CHILD = "locked";
/** 阶位页签未选中态子节点名(可选;非当前选中页签时显示) */
const TAB_INACTIVE_CHILD = "noactive";
/** 页签/卡片上的 smalltip 气泡节点名 */
const SMALL_TIP_CHILD = "smalltip";
/** TalentsComp —— 天赋加点页组件(阶位 Tab 单屏聚焦) */
/** TalentsComp —— 天赋加点页组件(最小容器:页头 + 编辑器静态卡片收集) */
@ccclass('TalentsComp')
@ecs.register('Talents', false)
export class TalentsComp extends CCComp {
@property({ type: Node, tooltip: "当前阶位卡片列表容器(ScrollView content,Grid 三列)" })
talents_content: Node = null!;
@property({ type: Prefab, tooltip: "迷你天赋卡预制(TalentItemComp)" })
prefab_talent_item: Prefab = null!;
@property({ type: Label, tooltip: "页头可用天赋点" })
lbl_points: Label = null!;
@@ -69,52 +43,22 @@ export class TalentsComp extends CCComp {
@property({ type: ProgressBar, tooltip: "当前等级经验进度条" })
progress_exp: ProgressBar = null!;
@property({ type: Node, tooltip: "一阶页签节点" })
tab_basic: Node = null!;
@property({ type: Label, tooltip: "当前等级经验数值(当前经验/升级所需经验)" })
lbl_exp: Label = null!;
@property({ type: Node, tooltip: "二阶页签节点" })
tab_special: Node = null!;
@property({ type: Node, tooltip: "三阶页签节点" })
tab_super: Node = null!;
@property({ type: Label, tooltip: "一阶页签文案" })
lbl_tab_basic: Label = null!;
@property({ type: Label, tooltip: "二阶页签文案" })
lbl_tab_special: Label = null!;
@property({ type: Label, tooltip: "三阶页签文案" })
lbl_tab_super: Label = null!;
@property({ type: Label, tooltip: "一阶已投入点数" })
lbl_invest_basic: Label = null!;
@property({ type: Label, tooltip: "二阶已投入点数" })
lbl_invest_special: Label = null!;
@property({ type: Label, tooltip: "三阶已投入点数" })
lbl_invest_super: Label = null!;
@property({ type: [Label], tooltip: "各层已投入点数 Label(数组索引对应第 1~5 层)" })
lbl_tier_points: Label[] = [];
@property({ type: Node, tooltip: "看广告重置按钮" })
btn_reset: Node = null!;
/** 当前选中的阶位(首次进入默认为已解锁的最高阶,之后由玩家切换) */
private curTier: TalentTier = TalentTier.Basic;
/** 是否已完成首次默认阶位选择 */
private inited: boolean = false;
/** 阶位 → 页签节点/文案/投入点数/气泡 映射(onLoad 时装配) */
private tabNodes: Map<TalentTier, { node: Node | null; label: Label | null; invest: Label | null; tip: SmallTipComp | null }> = new Map();
/** 页内全部天赋卡(onLoad 时自动收集子树内所有 TalentItemComp,无需编辑器拖引) */
private talent_items: TalentItemComp[] = [];
protected onLoad(): void {
this.tabNodes.set(TalentTier.Basic, { node: this.tab_basic, label: this.lbl_tab_basic, invest: this.lbl_invest_basic, tip: this.getSmallTip(this.tab_basic) });
this.tabNodes.set(TalentTier.Specialized, { node: this.tab_special, label: this.lbl_tab_special, invest: this.lbl_invest_special, tip: this.getSmallTip(this.tab_special) });
this.tabNodes.set(TalentTier.Super, { node: this.tab_super, label: this.lbl_tab_super, invest: this.lbl_invest_super, tip: this.getSmallTip(this.tab_super) });
for (const [tier, tab] of this.tabNodes) {
tab.node?.on(NodeEventType.TOUCH_END, () => this.selectTier(tier), this);
this.talent_items = this.node.getComponentsInChildren(TalentItemComp);
for (const item of this.talent_items) {
item.onTalentTap = (uuid: number) => this.onTalentTap(uuid);
}
this.btn_reset?.on(NodeEventType.TOUCH_END, this.onResetTap, this);
}
@@ -130,25 +74,30 @@ export class TalentsComp extends CCComp {
}
protected onDestroy(): void {
for (const [, tab] of this.tabNodes) {
tab.node?.targetOff(this);
}
this.btn_reset?.targetOff(this);
}
/** 切换阶位页签:锁定阶允许查看(卡片呈锁定态),不弹解锁提示 */
private selectTier(tier: TalentTier): void {
if (tier === this.curTier) return;
this.curTier = tier;
this.refreshUI();
}
/** 全量刷新:页头点数 + 页签解锁进度 + 当前阶位卡片列表 */
/** 全量刷新:页头点数 + 天赋等级/经验 + 全部静态卡片 */
public refreshUI(): void {
if (!this.talents_content || !this.prefab_talent_item) return;
const levels = smc.getTalentLevels();
if (this.lbl_points) this.lbl_points.string = `${smc.getTalentPoints()}`;
if (this.lbl_points) {
// 剩余/总点数:总点数 = 剩余 + 各层已投入
const remaining = smc.getTalentPoints();
let invested = 0;
for (let tier = 1; tier <= 5; tier++) {
invested += getTalentTierInvestedPoints(levels, tier);
}
this.lbl_points.string = `${remaining}/${remaining + invested}`;
}
// 各层已投入点数(数组索引对应层 1~5)
for (let tier = 1; tier <= this.lbl_tier_points.length; tier++) {
const lbl = this.lbl_tier_points[tier - 1];
if (lbl) {
lbl.string = `${getTalentTierInvestedPoints(levels, tier)}`;
}
}
// 天赋等级(Lv.x)与当前等级经验进度
const talentLv = smc.getTalentLevel();
@@ -160,127 +109,25 @@ export class TalentsComp extends CCComp {
? Math.min(1, smc.getTalentExp() / needExp)
: 1;
}
// 仅首次进入时默认选中已解锁的最高阶,后续刷新保持玩家当前选择
if (!this.inited) {
this.inited = true;
if (isTalentTierUnlocked(levels, TalentTier.Super)) this.curTier = TalentTier.Super;
else if (isTalentTierUnlocked(levels, TalentTier.Specialized)) this.curTier = TalentTier.Specialized;
if (this.lbl_exp) {
const needExp = getTalentLevelExp(talentLv);
// 满级后显示为 0/0,避免分母为 0
this.lbl_exp.string = needExp > 0
? `${smc.getTalentExp()}/${needExp}`
: `0/0`;
}
this.refreshTabs(levels);
this.refreshGrid(levels);
}
/** 页签文案 + 各阶已投入点数 + 选中高亮(sp_on)+ 锁定图标(locked)+ 未选中态(noactive) */
private refreshTabs(levels: Record<number, number>): void {
for (const [tier, tab] of this.tabNodes) {
const text = TIER_TAB_TEXT[tier];
const unlocked = isTalentTierUnlocked(levels, tier);
if (tab.label) tab.label.string = text.title;
// 各阶已投入点数(该阶所有天赋等级之和)
if (tab.invest) tab.invest.string = `${getTalentTierInvestedPoints(levels, tier)}`;
// 锁定图标:该阶未解锁时显示,解锁后隐藏(一阶恒解锁,节点可缺省)
const locked = tab.node?.getChildByName(TAB_LOCKED_CHILD);
if (locked) locked.active = !unlocked;
const selected = tier === this.curTier;
// 未选中态节点:非当前激活页签时显示(与 sp_on 选中高亮互斥)
const inactive = tab.node?.getChildByName(TAB_INACTIVE_CHILD);
if (inactive) inactive.active = !selected;
tab.node?.getChildByName(TAB_SELECTED_CHILD)?.setPosition(0, 0);
tab.node?.getChildByName(TAB_SELECTED_CHILD) && (tab.node.getChildByName(TAB_SELECTED_CHILD)!.active = selected);
}
}
/** 重建当前阶位的卡片列表(每阶 ≤10 条,直接全量实例化) */
private refreshGrid(levels: Record<number, number>): void {
this.talents_content.removeAllChildren();
const unlocked = isTalentTierUnlocked(levels, this.curTier);
const cfgs = Object.values(TalentSet)
.filter(cfg => cfg.tier === this.curTier)
.sort((a, b) => a.uuid - b.uuid);
let cardHeight = 0;
for (const cfg of cfgs) {
const lv = levels[cfg.uuid] ?? 0;
const itemNode = instantiate(this.prefab_talent_item);
this.talents_content.addChild(itemNode);
const comp = itemNode.getComponent(TalentItemComp);
if (!comp) continue;
comp.updateItem(cfg, lv, unlocked);
comp.onTalentTap = (uuid: number) => this.onTalentTap(uuid);
if (!cardHeight) cardHeight = itemNode.getComponent(UITransform)?.height ?? 0;
}
// Grid 三列手算容器高度(与旧版图鉴页口径一致,spacing 15 / 上下 padding 15)
const rows = Math.ceil(cfgs.length / 3);
if (cardHeight > 0 && rows > 0) {
const contentHeight = rows * cardHeight + (rows - 1) * 15 + 30;
const uiTransform = this.talents_content.getComponent(UITransform);
if (uiTransform) uiTransform.height = contentHeight;
for (const item of this.talent_items) {
item.updateItem();
}
}
/**
* 获取挂载点下的 smalltip 气泡组件(节点缺失或组件未挂时返回 null 并静默跳过提示)
* @param parent 气泡挂载的父节点
* 卡片点击统一入口:任何状态均打开 TIBox 天赋详情弹窗,加点按钮显隐由 TIBoxComp 校验
* @param uuid 天赋 uuid
*/
private getSmallTip(parent: Node | null): SmallTipComp | null {
const tipNode = parent?.getChildByName(SMALL_TIP_CHILD);
return tipNode ? (tipNode.getComponent(SmallTipComp) ?? tipNode.addComponent(SmallTipComp)) : null;
}
/**
* 在指定节点下弹出 smalltip 气泡提示
* @param anchor 气泡挂载的父节点(优先页签,卡片点击时传卡片节点)
* @param text 提示文案
*/
private showSmallTip(anchor: Node | null, text: string): void {
const tip = this.getSmallTip(anchor);
tip?.show(text);
}
/** 卡片点击统一入口:未解锁/已满级/点数不足走 smalltip 气泡;其余弹出 CommonPrompt 确认加点,成功后事件驱动刷新 */
private onTalentTap(uuid: number): void {
const cfg = TalentSet[uuid];
if (!cfg) return;
const levels = smc.getTalentLevels();
const tab = this.tabNodes.get(cfg.tier);
if (!isTalentTierUnlocked(levels, cfg.tier)) {
const invested = getTalentTierInvestedPoints(levels, (cfg.tier - 1) as TalentTier);
this.showSmallTip(tab?.node ?? null, `${TIER_TAB_TEXT[cfg.tier].lockName}投入${TALENT_TIER_UNLOCK_THRESHOLD} 点解锁`);
return;
}
const lv = smc.getTalentLv(uuid);
if (lv >= cfg.max_lv) {
this.showSmallTip(tab?.node ?? null, "该天赋已满级");
return;
}
const cost = getTalentUpgradeCost(cfg.tier);
if (cost <= 0) return;
if (smc.getTalentPoints() < cost) {
this.showSmallTip(tab?.node ?? null, "天赋点不足");
return;
}
// CommonPrompt 确认加点(UIID.Window)
oops.gui.open(UIID.Window, {
title: "天赋加点",
content: `${cfg.name} Lv.${lv} → Lv.${lv + 1}\n消耗 <color=#E0A93D>${cost}</color> 点天赋点,获得 ${getTalentDisplayText(cfg, 1)}。是否加点?`,
okWord: "加点",
cancelWord: "取消",
needCancel: true,
maskClose: true,
okFunc: () => {
if (!smc.upgradeTalent(uuid)) {
this.showSmallTip(this.tabNodes.get(cfg.tier)?.node ?? null, "天赋点不足");
}
},
});
oops.gui.open(UIID.TIBox, { uuid });
}
/** 看广告重置入口:CommonPrompt 二次确认(广告接入前先直接确认重置) */

View File

@@ -2,7 +2,7 @@ import { _decorator, Animation, CCFloat, CCInteger, Collider2D, Contact2DType, E
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { HeroViewComp } from "../hero/HeroViewComp";
import { DTType, EType, RType, SkillConfig, SkillSet } from "../common/config/SkillSet";
import { DTType, EType, RType, SkillConfig, SkillSet, SummonTMode } from "../common/config/SkillSet";
import { SDataCom } from "./SDataCom";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { DamageQueueHelper } from "../hero/DamageQueueComp";
@@ -54,6 +54,10 @@ export class SkillView extends CCComp {
@property({ type: CCInteger, slide: true, range: [1, 8, 1], tooltip: "单次施法召唤的技能实体个数" })
summon_count: number = 1;
/** 召唤目标选择模式:same=锁定同一敌人(默认);random=每次出手随机敌人;unique=避免重复敌人(一轮选完重置) */
@property({ type: Enum(SummonTMode), tooltip: "召唤目标选择:同一敌人/随机敌人/避免相同敌人" })
summon_target_mode: SummonTMode = SummonTMode.same;
/** 召唤物 X 轴步进偏移(像素):summon_count>1 时以出生点/目标点为中心对称交替铺开(第1个=0,之后按 +1,-1,+2,-2... 档 × 该值,正=右) */
@property({ type: CCFloat, slide: true, range: [-200, 200, 5], tooltip: "召唤物X轴步进偏移:以中心对称交替铺开(0,+1,-1,+2,-2...档×该值)" })
summon_x_offset: number = 0;

File diff suppressed because it is too large Load Diff

View File

@@ -70,9 +70,12 @@
## 4. 触发组规范(atking / atked / fstart / fend / dead)
- 结构:`{ "<技能uuid字符串>": [条目, ...] }`,一个组可挂多个技能 uuid;
- 条目:`{lv, s_lv, name, t_num, overrides, desc}`;
- 条目:`{lv, s_lv, name, t_num, t_chance?, pity_add?, overrides, desc}`;
- 解锁等级惯例 **lv4**(`lv:4, s_lv:1`;heroSet.ts 头部注释写的 lv6 已过时,现有英雄与编辑器默认值均为 lv4);
- `t_num`:每 n 次普攻(atking)/受击(atked)触发一次;fstart/fend 为每回合开始/结束固定触发 1 次,`t_num` 恒填 1;
- `t_chance`:触发概率(百分点 0~100,**仅 atking/atked 生效**,其余组忽略);缺省/100 = 必触发。示例 30 表示次数条件达成后掷骰 30%;
- `pity_add`:保底叠加(百分点,仅 atking/atked 生效);未触发时为该技能累积下次概率增量,触发成功后清零、封顶 100。示例 `t_chance:30, pity_add:20` → 30/50/70/90/100 递进;
- 触发组 `desc` 模板可额外使用 `{t_chance}`(触发概率,缺省渲染 100)与 `{pity_add}`(保底叠加,缺省渲染 0)占位符;模板显式使用 `{t_chance}` 时触发语境不再自动追加"(N%概率)"后缀,避免重复;
- `name` 为组对外展示名(写在该组首档),留空回退底座名;
- 复活:在 `dead` 某条目 `overrides` 配 `revive_hp`(回血%)+ `revive_count`(次数上限);
- 无触发组时写空对象 `{}`,**不要省略字段**。

View File

@@ -215,6 +215,9 @@ function renderSingleHero(h) {
if (e.s_lv) eparts.push(`s_lv: SkillLv.Lv${e.s_lv}`)
if (e.name) eparts.push(`name: ${JSON.stringify(e.name)}`)
eparts.push(`t_num: ${e.t_num}`)
// 概率参数:仅攻/受击触发消费;缺省(100/0)不输出,保持生成 TS 干净
if (e.t_chance != null && e.t_chance < 100) eparts.push(`t_chance: ${e.t_chance}`)
if (e.pity_add != null && e.pity_add > 0) eparts.push(`pity_add: ${e.pity_add}`)
if (e.overrides && Object.keys(e.overrides).length) eparts.push(`overrides: ${serializeOverrides(e.overrides)}`)
if (e.desc) eparts.push(`desc: ${JSON.stringify(e.desc)}`)
push(` { ${eparts.join(', ')} },`)
@@ -351,6 +354,8 @@ function csvTrigger(grp) {
const name = e.name || base?.name || ''
const ov = e.overrides || {}
const parts = [`t_num${e.t_num}`]
// 攻/受击组附带概率信息(缺省 100 不展示)
if (e.t_chance != null && e.t_chance < 100) parts.push(`概率${e.t_chance}%`)
if (ov.ap != null) parts.push(`ap${ov.ap}`)
const desc = e.desc || ''
return `${sid} ${name}(${parts.join(';')}):${desc}`

View File

@@ -165,11 +165,11 @@
<div class="panel">
<h3>触发技能(lv4 激活)</h3>
<el-tabs>
<el-tab-pane label="atking 攻击后"><TriggerGroupEditor :model="current.atking" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
<el-tab-pane label="atked 受击后"><TriggerGroupEditor :model="current.atked" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
<el-tab-pane label="fstart 战斗开始"><TriggerGroupEditor :model="current.fstart" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
<el-tab-pane label="fend 回合结束"><TriggerGroupEditor :model="current.fend" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
<el-tab-pane label="dead 死亡后"><TriggerGroupEditor :model="current.dead" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
<el-tab-pane label="atking 攻击后"><TriggerGroupEditor group="atking" :model="current.atking" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
<el-tab-pane label="atked 受击后"><TriggerGroupEditor group="atked" :model="current.atked" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
<el-tab-pane label="fstart 战斗开始"><TriggerGroupEditor group="fstart" :model="current.fstart" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
<el-tab-pane label="fend 回合结束"><TriggerGroupEditor group="fend" :model="current.fend" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
<el-tab-pane label="dead 死亡后"><TriggerGroupEditor group="dead" :model="current.dead" :skill-options="allSkills" :skill-map="skillMap" :buff-options="buffOptions" /></el-tab-pane>
</el-tabs>
</div>
@@ -380,7 +380,15 @@ function triggerSummary(h) {
const name = entries[0]?.name || skillMap.value[sid]?.name || ''
const e = entries[0]
const ov = e?.overrides || {}
const parts = [`t_num${e?.t_num}`, ...overrideParts(ov)]
const parts = [`t_num${e?.t_num}`]
// 攻/受击组附带概率与保底信息(缺省 100%/0 不展示)
if (tk === 'atking' || tk === 'atked') {
if (e?.t_chance != null && e.t_chance < 100) {
parts.push(`概率${e.t_chance}%`)
if (e.pity_add > 0) parts.push(`保底+${e.pity_add}%`)
}
}
parts.push(...overrideParts(ov))
lines.push(`${tk}: ${sid} ${name} lv${minLv} ${parts.join(' ')}`)
}
}

View File

@@ -32,6 +32,8 @@
<span class="tpl-meta-item">{{ p.uuid }} {{ skillName(p.uuid) }}</span>
<span class="tpl-meta-sep">·</span>
<span class="tpl-meta-item">t_num {{ p.entry.t_num }}</span>
<span class="tpl-meta-sep" v-if="p.entry.t_chance != null && p.entry.t_chance < 100">·</span>
<span class="tpl-meta-item" v-if="p.entry.t_chance != null && p.entry.t_chance < 100">概率 {{ p.entry.t_chance }}%</span>
<span class="tpl-meta-sep">·</span>
<span class="tpl-meta-item">lv{{ p.entry.lv }}</span>
</div>
@@ -129,6 +131,14 @@
<el-form-item label="t_num">
<el-input-number v-model="currentTrigger.entry.t_num" :min="1" :controls="false" style="width: 100%" />
</el-form-item>
<template v-if="currentTrigger.group === 'atking' || currentTrigger.group === 'atked'">
<el-form-item label="t_chance 触发概率%(空=100)">
<el-input-number v-model="currentTrigger.entry.t_chance" :min="0" :max="100" :controls="false" style="width: 100%" />
</el-form-item>
<el-form-item label="pity_add 保底叠加%(空=0)">
<el-input-number v-model="currentTrigger.entry.pity_add" :min="0" :max="100" :controls="false" style="width: 100%" />
</el-form-item>
</template>
<el-form-item label="组名 name">
<el-input v-model="currentTrigger.entry.name" placeholder="留空回退底座名" />
</el-form-item>

View File

@@ -15,9 +15,17 @@
<el-option v-for="n in 5" :key="n" :label="`Lv${n}`" :value="n" />
</el-select>
</el-form-item>
<el-form-item label="t_num (1=每次,3≈33%,4≈25%)">
<el-form-item label="t_num 次数阈值(1=每次)">
<el-input-number v-model="entry.t_num" :min="1" :controls="false" style="width: 100%" />
</el-form-item>
<template v-if="chanceVisible">
<el-form-item label="t_chance 触发概率%(空=100)">
<el-input-number v-model="entry.t_chance" :min="0" :max="100" :controls="false" style="width: 100%" placeholder="缺省必触发" />
</el-form-item>
<el-form-item label="pity_add 保底叠加%(空=0)">
<el-input-number v-model="entry.pity_add" :min="0" :max="100" :controls="false" style="width: 100%" placeholder="未触发时下次概率增量" />
</el-form-item>
</template>
<el-form-item label="组名(仅第1档)" v-if="idx === 0">
<el-input v-model="entry.name" />
</el-form-item>
@@ -42,18 +50,22 @@
</template>
<script setup>
import { ref } from 'vue'
import { computed, ref } from 'vue'
import OverrideEditor from './OverrideEditor.vue'
import { defaultTriggerEntry } from '../constants'
const props = defineProps({
model: { type: Object, required: true }, // TriggerGrouped
// 触发组 key(atking/atked/fstart/fend/dead),决定是否展示概率参数
group: { type: String, default: '' },
skillOptions: { type: Array, default: () => [] },
skillMap: { type: Object, default: () => ({}) },
// 全部 buff 候选(timed_buff_id 下拉用),由 App 透传
buffOptions: { type: Array, default: () => [] },
})
const newSid = ref(null)
// 概率参数仅攻/受击触发消费(fstart/fend/dead 固定触发)
const chanceVisible = computed(() => props.group === 'atking' || props.group === 'atked')
function skillName(sid) {
return props.skillMap[sid]?.name || ''
@@ -69,6 +81,13 @@ function removeGroup(sid) {
}
function addEntry(group) {
const lastLv = group.length ? group[group.length - 1].lv : 4
group.push({ lv: lastLv + 3, s_lv: 2, name: '', t_num: 3, overrides: {}, desc: '' })
const entry = { lv: lastLv + 3, s_lv: 2, name: '', t_num: 3, overrides: {}, desc: '' }
// 概率参数沿袭组内末档(攻/受击触发才生效)
if (chanceVisible.value && group.length) {
const last = group[group.length - 1]
entry.t_chance = last.t_chance ?? null
entry.pity_add = last.pity_add ?? null
}
group.push(entry)
}
</script>

View File

@@ -46,8 +46,8 @@ export const ROLE_ATK_SKILL = {
Warrior: [6004],
Assassin: [6001, 6002, 6003, 6004],
Archer: [6004, 6006, 6007, 6008],
Mage: [6009, 6010, 6011, 6012],
Support: [6004, 6009, 6010, 6011, 6012],
Mage: [6009, 6010, 6011, 6012, 6013, 6014, 6015],
Support: [6004, 6009, 6010, 6011, 6012, 6013, 6014, 6015],
}
// 按职业映射大招底座
@@ -117,7 +117,7 @@ export function defaultHero() {
dodge: null, // 闪避率(null = 全局默认 15,填值覆盖)
accuracy: null, // 命中率(null = 全局默认 10,填值覆盖)
skills: [], // [{uuid,name,cd,cdExpr,overrides,desc,lv_entries:[{lv,s_lv,overrides,desc}]}]
atking: {}, // {uuid:[{lv,s_lv,name,t_num,overrides,desc}]}
atking: {}, // {uuid:[{lv,s_lv,name,t_num,t_chance?,pity_add?,overrides,desc}]} t_chance/pity_add 仅攻/受击组生效
atked: {},
fstart: {},
fend: {},