Add dashboard hero upgrade estimates

This commit is contained in:
daixiawu 2026-06-27 00:11:05 +08:00
parent 0ebc3e5724
commit c63a4368dc
6 changed files with 1101 additions and 35 deletions

View File

@ -2,6 +2,12 @@
Dashboard 功能变更应在这里记录入口、数据源、接口和持久化位置,便于后续维护与迁移。
## 2026-06-26
- `平衡性分析` 新增二级模块 `英雄升级`,读取当前 `HeroDataAssets` 英雄任务和 `UnitTypeDataAssets` 英雄基础数据,估算 Lv1->Lv2、Lv2->Lv3、Lv3->Lv4 所需回合数。
- 新模块数据入口沿用 `GET /api/gamebalance/hero-data`,静态 fallback 使用 `data/units.json``data/skills.json``data/heroes.json`;估算逻辑在 `js/gamebalance.js` 内按任务类型与英雄特殊机制计算。
- `export_data.py``heroes.json` 任务行追加 `skillParam``spType``skillList``unitFullTypes``targetBuff` 字段,供 Dashboard 分析模块识别更精确的任务目标。
## 2026-06-18
- 新增 `填表助手` 一级导航入口,当前包含 `Skill表` 二级栏。

View File

@ -5177,6 +5177,112 @@ body::after {
color: var(--text-muted);
}
/* Hero upgrade turn estimation */
.gbhu-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.gbhu-model-note {
margin-bottom: 12px;
padding: 8px 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: #f8fafc;
color: var(--text-secondary);
font-size: 12px;
line-height: 1.5;
}
.gbhu-table-shell {
display: flex;
flex-direction: column;
}
.gbhu-table-wrap {
overflow-x: auto;
border: 1px solid var(--border-color);
border-radius: 8px;
}
.gbhu-table {
min-width: 1180px;
}
.gbhu-table th,
.gbhu-table td {
vertical-align: top;
}
.gbhu-table thead th {
position: sticky;
top: 0;
z-index: 1;
}
.gbhu-force {
min-width: 120px;
font-size: 13px;
font-weight: 700;
color: var(--text-primary);
}
.gbhu-class {
margin-top: 2px;
color: var(--text-muted);
font-size: 11px;
}
.gbhu-est-cell {
width: 27%;
min-width: 260px;
}
.gbhu-est-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 4px;
}
.gbhu-turn {
color: #1f2937;
font-size: 15px;
font-weight: 800;
white-space: nowrap;
}
.gbhu-speed {
display: inline-flex;
align-items: center;
min-height: 20px;
padding: 1px 7px;
border-radius: 4px;
font-size: 11px;
font-weight: 750;
white-space: nowrap;
}
.gbhu-speed-fast {
background: rgba(16, 185, 129, 0.12);
color: #047857;
}
.gbhu-speed-normal {
background: rgba(59, 130, 246, 0.12);
color: #1d4ed8;
}
.gbhu-speed-slow {
background: rgba(245, 158, 11, 0.16);
color: #92400e;
}
.gbhu-speed-gated {
background: rgba(100, 116, 139, 0.14);
color: #475569;
}
.gbhu-task {
margin-bottom: 4px;
color: var(--text-primary);
font-size: 12px;
line-height: 1.45;
}
.gbhu-logic {
color: var(--text-secondary);
font-size: 12px;
line-height: 1.5;
}
/* Hero pricing model */
.gbhp-toolbar {
display: flex;

View File

@ -7,18 +7,33 @@
{
"taskContentType": 13,
"param": 25,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "帝国累计获得**<{param}/{param}>**金币"
},
{
"taskContentType": 7,
"param": 5,
"skillParam": 98,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计施加**<{param}/{param}>**次**<{param}>**"
},
{
"taskContentType": 7,
"param": 20,
"skillParam": 98,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计施加**<{param}/{param}>**次**<{param}>**"
}
@ -32,18 +47,33 @@
{
"taskContentType": 22,
"param": 2,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "我方占领2座村庄或1座敌方城市(**<{param}/{param}>**)"
},
{
"taskContentType": 8,
"param": 5,
"skillParam": 103,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计创造**<{param}/{param}>**个月兔幻象"
},
{
"taskContentType": 7,
"param": 40,
"skillParam": 82,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计施加**<{param}/{param}>**次**<{param}>**"
}
@ -57,18 +87,33 @@
{
"taskContentType": 21,
"param": 2,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "帝国成员挖掘**<{param}/{param}>**处遗迹"
},
{
"taskContentType": 9,
"param": 12,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计获得**<{param}/{param}>**点金币"
},
{
"taskContentType": 9,
"param": 35,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计获得**<{param}/{param}>**点金币"
}
@ -82,18 +127,33 @@
{
"taskContentType": 5,
"param": 50,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计探索**<{param}/{param}>**块迷雾区域"
},
{
"taskContentType": 3,
"param": 20,
"skillParam": 103,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计提供**<{param}/{param}>**生命回复"
},
{
"taskContentType": 3,
"param": 40,
"skillParam": 103,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计提供**<{param}/{param}>**生命回复"
}
@ -107,18 +167,33 @@
{
"taskContentType": 23,
"param": 25,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 60,
"skillParam": 103,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 120,
"skillParam": 82,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
}
@ -132,18 +207,33 @@
{
"taskContentType": 13,
"param": 25,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "帝国累计获得**<{param}/{param}>**金币"
},
{
"taskContentType": 12,
"param": 10,
"skillParam": 0,
"spType": 1,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计创造**<{param}/{param}>**片红雾领地"
},
{
"taskContentType": 12,
"param": 30,
"skillParam": 0,
"spType": 1,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计创造**<{param}/{param}>**片红雾领地"
}
@ -157,18 +247,45 @@
{
"taskContentType": 5,
"param": 40,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计探索**<{param}/{param}>**块迷雾区域"
},
{
"taskContentType": 10,
"param": 8,
"skillParam": 0,
"spType": 0,
"skillList": [
119,
120,
121,
123,
124
],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计消耗**<{param}/{param}>**颗魔力石"
},
{
"taskContentType": 10,
"param": 24,
"skillParam": 0,
"spType": 0,
"skillList": [
119,
120,
121,
123,
124
],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计消耗**<{param}/{param}>**颗魔力石"
}
@ -182,18 +299,33 @@
{
"taskContentType": 5,
"param": 50,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计探索**<{param}/{param}>**块迷雾区域"
},
{
"taskContentType": 0,
"param": 25,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 2,
"param": 15,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计击杀**<{param}/{param}>**个敌方单位"
}
@ -207,18 +339,33 @@
{
"taskContentType": 22,
"param": 2,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "我方占领2座村庄或1座敌方城市(**<{param}/{param}>**)"
},
{
"taskContentType": 2,
"param": 10,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计击杀**<{param}/{param}>**个单位"
},
{
"taskContentType": 2,
"param": 20,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计击杀**<{param}/{param}>**个单位"
}
@ -232,18 +379,33 @@
{
"taskContentType": 23,
"param": 30,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 65,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 120,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
}
@ -257,18 +419,33 @@
{
"taskContentType": 14,
"param": 5,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "已完成**<{param}/{param}>**科技的研发"
},
{
"taskContentType": 1,
"param": 30,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成**<{param}/{param}>**点伤害"
},
{
"taskContentType": 1,
"param": 70,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成**<{param}/{param}>**点伤害"
}
@ -282,18 +459,76 @@
{
"taskContentType": 20,
"param": 40,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [
{
"unitType": 30,
"giantType": 0,
"unitLevel": 1
},
{
"unitType": 30,
"giantType": 0,
"unitLevel": 2
},
{
"unitType": 30,
"giantType": 0,
"unitLevel": 3
},
{
"unitType": 30,
"giantType": 0,
"unitLevel": 4
},
{
"unitType": 30,
"giantType": 0,
"unitLevel": 5
},
{
"unitType": 14,
"giantType": 12,
"unitLevel": 1
}
],
"targetBuff": [],
"skillName": "",
"desc": "累计探索**<{param}/{param}>**块迷雾区域"
},
{
"taskContentType": 15,
"param": 1,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [
{
"unitType": 30,
"giantType": 0,
"unitLevel": 4
}
],
"targetBuff": [],
"skillName": "",
"desc": "拥有**<{param}/{param}>**御射宫司大人Lv.4"
},
{
"taskContentType": 16,
"param": 15,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [
{
"unitType": 30,
"giantType": 0,
"unitLevel": 0
}
],
"targetBuff": [],
"skillName": "",
"desc": "御射宫司大人累积完成**<{param}/{param}>**击杀"
}
@ -307,18 +542,38 @@
{
"taskContentType": 5,
"param": 50,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计探索**<{param}/{param}>**块迷雾区域"
},
{
"taskContentType": 7,
"param": 30,
"skillParam": 162,
"spType": 0,
"skillList": [
162
],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计施加**<{param}/{param}>**次**<乘风>**"
},
{
"taskContentType": 17,
"param": 3,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [
0,
3
],
"skillName": "",
"desc": "掷出**<大吉>**或**<大凶>**累计达**<{param}/{param}>**次"
}
@ -332,18 +587,33 @@
{
"taskContentType": 22,
"param": 2,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "我方占领2座村庄或1座敌方城市(**<{param}/{param}>**)"
},
{
"taskContentType": 18,
"param": 60,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成**<{param}/{param}>**点溅射伤害"
},
{
"taskContentType": 18,
"param": 120,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成**<{param}/{param}>**点溅射伤害"
}
@ -357,18 +627,33 @@
{
"taskContentType": 23,
"param": 25,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 60,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 120,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
}
@ -382,18 +667,33 @@
{
"taskContentType": 13,
"param": 25,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "帝国累计获得**<{param}/{param}>**金币"
},
{
"taskContentType": 7,
"param": 15,
"skillParam": 210,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计施加**<{param}/{param}>**次**<恐惧>**"
},
{
"taskContentType": 7,
"param": 8,
"skillParam": 213,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计施加**<{param}/{param}>**次**<心理创伤>**"
}
@ -407,18 +707,33 @@
{
"taskContentType": 5,
"param": 50,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计探索**<{param}/{param}>**块迷雾区域"
},
{
"taskContentType": 1,
"param": 50,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成**<{param}/{param}>**点伤害"
},
{
"taskContentType": 1,
"param": 100,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成**<{param}/{param}>**点伤害"
}
@ -432,18 +747,33 @@
{
"taskContentType": 22,
"param": 2,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "我方占领2座村庄或1座敌方城市(**<{param}/{param}>**)"
},
{
"taskContentType": 23,
"param": 50,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 100,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
}
@ -457,18 +787,33 @@
{
"taskContentType": 23,
"param": 25,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 60,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 120,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
}
@ -482,18 +827,39 @@
{
"taskContentType": 5,
"param": 50,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计探索**<{param}/{param}>**块迷雾区域"
},
{
"taskContentType": 7,
"param": 12,
"skillParam": 217,
"spType": 0,
"skillList": [
217,
252
],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计获得**<{param}/{param}>**层**<粗钝身>**或**<金刚身>**"
},
{
"taskContentType": 7,
"param": 30,
"skillParam": 217,
"spType": 0,
"skillList": [
217,
252
],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计获得**<{param}/{param}>**层**<粗钝身>**或**<金刚身>**"
}
@ -505,22 +871,37 @@
"taskCount": 3,
"tasks": [
{
"taskContentType": 23,
"taskContentType": 13,
"param": 25,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "帝国累计获得**<{param}/{param}>**金币"
},
{
"taskContentType": 23,
"param": 60,
"taskContentType": 1,
"param": 30,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "累计造成**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 120,
"taskContentType": 1,
"param": 70,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "累计造成**<{param}/{param}>**点伤害"
}
]
},
@ -530,22 +911,37 @@
"taskCount": 3,
"tasks": [
{
"taskContentType": 23,
"param": 25,
"taskContentType": 5,
"param": 40,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "探索**<{param}/{param}>**块迷雾"
},
{
"taskContentType": 23,
"param": 60,
"taskContentType": 24,
"param": 4,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "设置**<{param}/{param}>**个灵异珠"
},
{
"taskContentType": 23,
"param": 120,
"taskContentType": 24,
"param": 8,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "设置**<{param}/{param}>**个灵异珠"
}
]
},
@ -555,22 +951,37 @@
"taskCount": 3,
"tasks": [
{
"taskContentType": 23,
"param": 25,
"taskContentType": 5,
"param": 50,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "累计探索**<{param}/{param}>**块迷雾区域"
},
{
"taskContentType": 23,
"param": 60,
"taskContentType": 8,
"param": 6,
"skillParam": 335,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "累计施加**<{param}/{param}>**次**<{param}>**"
},
{
"taskContentType": 23,
"param": 120,
"taskContentType": 8,
"param": 12,
"skillParam": 335,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "累计施加**<{param}/{param}>**次**<{param}>**"
}
]
},
@ -580,22 +991,37 @@
"taskCount": 3,
"tasks": [
{
"taskContentType": 23,
"param": 25,
"taskContentType": 22,
"param": 2,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "我方占领2座村庄或1座敌方城市(**<{param}/{param}>**)"
},
{
"taskContentType": 23,
"param": 60,
"taskContentType": 25,
"param": 10,
"skillParam": 330,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "在**<吽形石守>**状态下承受**<{param}/{param}>**次伤害"
},
{
"taskContentType": 23,
"param": 120,
"taskContentType": 25,
"param": 20,
"skillParam": 330,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
"desc": "在**<吽形石守>**状态下承受**<{param}/{param}>**次伤害"
}
]
},
@ -607,18 +1033,33 @@
{
"taskContentType": 23,
"param": 25,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 60,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
},
{
"taskContentType": 23,
"param": 120,
"skillParam": 0,
"spType": 0,
"skillList": [],
"unitFullTypes": [],
"targetBuff": [],
"skillName": "",
"desc": "累计造成或承受**<{param}/{param}>**点伤害"
}

View File

@ -170,6 +170,36 @@ def parse_hex_int_list(hex_str):
return result
def parse_int_list(value):
"""Parse Unity int lists from either YAML arrays or packed hex strings."""
if isinstance(value, list):
result = []
for item in value:
if isinstance(item, dict):
continue
result.append(safe_int(item))
return result
if isinstance(value, str):
return parse_hex_int_list(value)
return []
def parse_unit_full_type_list(value):
"""Normalize UnitFullType YAML list entries for dashboard consumers."""
if not isinstance(value, list):
return []
result = []
for item in value:
if not isinstance(item, dict):
continue
result.append({
"unitType": safe_int(item.get('UnitType')),
"giantType": safe_int(item.get('GiantType')),
"unitLevel": safe_int(item.get('UnitLevel')),
})
return result
def get_indent(line):
"""Get the indentation level of a line."""
return len(line) - len(line.lstrip(' '))
@ -666,6 +696,11 @@ def export_heroes():
tasks.append({
"taskContentType": safe_int(task.get('taskContentType')),
"param": safe_int(task.get('Param')),
"skillParam": safe_int(task.get('SkillParam')),
"spType": safe_int(task.get('SpType')),
"skillList": parse_int_list(task.get('SkillList')),
"unitFullTypes": parse_unit_full_type_list(task.get('UnitFullTypes', [])),
"targetBuff": parse_int_list(task.get('TargetBuff')),
"skillName": skill_name,
"desc": desc,
})

View File

@ -1035,6 +1035,7 @@
<div class="sub-tabs" id="gb-sub-tabs">
<button class="sub-tab active" data-sub="gb-query">通用查询</button>
<button class="sub-tab" data-sub="gb-hero-data">英雄数据</button>
<button class="sub-tab" data-sub="gb-hero-upgrade">英雄升级</button>
<button class="sub-tab" data-sub="gb-hero">英雄平衡</button>
<button class="sub-tab" data-sub="gb-faction">阵营平衡</button>
</div>
@ -1170,6 +1171,42 @@
</div>
</div>
<!-- Sub: 英雄升级 -->
<div id="sub-gb-hero-upgrade" class="sub-panel">
<div class="module-card gb-module">
<div class="module-header">
<span class="module-title">英雄升级</span>
<span class="module-badge" id="gbhu-total-badge">未加载</span>
</div>
<div class="module-body">
<div class="gbhu-toolbar">
<div class="gbhp-model-meta">
<span>按当前 HeroDataAssets 任务配置估算每段升级大概需要的回合数,三段任务均按升级后从 0 重新累计处理。</span>
</div>
<div class="gbhp-controls">
<input type="text" id="gbhu-search" class="search-input" placeholder="搜索英雄 / 帝国 / 任务 / 估算逻辑">
<select id="gbhu-force-filter" class="gb-filter-select">
<option value="">全部5帝国</option>
</select>
<select id="gbhu-speed-filter" class="gb-filter-select">
<option value="">全部速度</option>
<option value="fast">偏快</option>
<option value="normal">常规</option>
<option value="slow">偏慢</option>
<option value="gated">门槛/依赖</option>
</select>
</div>
</div>
<div class="gbhu-model-note">
基础口径:金币 6/回合、探索 8格/回合、伤害 12点/回合、击杀 1.2个/回合、开遗迹约每2回合1处、主动/特殊技能约每回合1次特殊任务按技能机制覆盖。
</div>
<div id="gbhu-upgrade-table" class="gbhu-table-shell">
<div class="gb-stats-empty">正在加载英雄升级估算...</div>
</div>
</div>
</div>
</div>
<!-- Sub: 英雄平衡 -->
<div id="sub-gb-hero" class="sub-panel">
<!-- Row 1: 版本筛选 + 计算 -->

View File

@ -141,6 +141,8 @@ let gbhpPricingData = null; // Latest balance modeling output
let gbhpSort = 'finalDesc';
let gbhdData = null; // Static hero data from Dashboard export
let gbhdRendered = false;
let gbhuData = null; // Hero upgrade turn estimation
let gbhuRendered = false;
const GBHD_CLASS_ORDER = [1, 2, 3, 4, 5];
const GBHD_CLASS_NAMES = {
@ -162,6 +164,42 @@ const GBHD_ROW_SPECS = [
{ key: 'skills', label: '技能', type: 'skills' },
];
const GBHU_TASK_TYPE_NAMES = {
0: '累计承受伤害',
1: '累计造成伤害',
2: '累计击杀',
3: '累计治疗',
4: '开启遗迹',
5: '探索迷雾',
6: '占领城市',
7: '增加技能层数',
8: '技能生效',
9: '帝金币收益',
10: '技能掉层',
11: '遇见玩家',
12: '添加特殊地格',
13: '帝国金币收益',
14: '研发科技',
15: '生产/拥有单位',
16: '指定单位击杀',
17: '御神签极端结果',
18: '溅射伤害',
19: '指定技能单位死亡',
20: '指定单位探索',
21: '帝国成员开遗迹',
22: '占村/占城',
23: '造成或承受伤害',
24: '设置灵异珠',
25: '阿吽石化承伤',
};
const GBHU_SPEED_LABELS = {
fast: '偏快',
normal: '常规',
slow: '偏慢',
gated: '门槛',
};
// ── Init (lazy, called when panel activates) ──
function gbInit() {
@ -183,6 +221,9 @@ function gbInit() {
if (sub === 'gb-hero-data') {
gbhdLoadData();
}
if (sub === 'gb-hero-upgrade') {
gbhuLoadData();
}
if (sub === 'gb-hero' && !gbhpPricingData) {
gbhpLoadPricing();
}
@ -227,6 +268,9 @@ function gbInit() {
if (document.getElementById('sub-gb-hero-data')?.classList.contains('active')) {
gbhdLoadData();
}
if (document.getElementById('sub-gb-hero-upgrade')?.classList.contains('active')) {
gbhuLoadData();
}
}
function wireFilterCheckbox(checkId, selectId) {
@ -1145,6 +1189,403 @@ function gbhdFormatNum(value) {
return String(value).replace(/\.0$/, '');
}
// ════════════════════════════════════════════
// Hero Upgrade Turn Estimation Sub-module
// ════════════════════════════════════════════
async function gbhuLoadData(forceReload = false) {
const table = document.getElementById('gbhu-upgrade-table');
const badge = document.getElementById('gbhu-total-badge');
if (!table) return;
if (!forceReload && gbhuData) {
gbhuRender();
return;
}
table.innerHTML = '<div class="gb-loading">正在读取英雄升级任务...</div>';
if (badge) badge.textContent = '加载中';
try {
const payload = await gbhdFetchHeroPayload();
const units = payload.units || [];
const skills = payload.skills || [];
const heroTasks = payload.heroes || [];
const baseData = gbhdBuildData(units, skills, heroTasks);
gbhuData = gbhuBuildData(baseData.heroes, skills);
gbhuInitFilters();
gbhuRender();
} catch (e) {
if (badge) badge.textContent = '读取失败';
table.innerHTML = `<div class="gb-stats-empty">英雄升级估算读取失败: ${gbEscapeHtml(e.message)}</div>`;
}
}
function gbhuBuildData(heroes, skills) {
const skillMap = new Map((skills || []).map(skill => [Number(skill.skillType), skill]));
const rows = [];
(heroes || []).forEach(hero => {
const estimates = [1, 2, 3].map((taskIndex, idx) => {
const fromLevel = idx + 1;
const task = hero.tasks?.[idx];
return gbhuEstimateTask(hero, fromLevel, fromLevel + 1, task, skillMap);
});
const searchText = [
hero.heroName,
hero.giantName,
hero.forceName,
hero.forceNameLocal,
hero.civName,
hero.civNameLocal,
gbhdForceLabel(hero),
...estimates.flatMap(est => [est.taskName, est.taskText, est.logic, est.turnText, est.speedLabel]),
].filter(Boolean).join(' ');
rows.push({ ...hero, estimates, upgradeSearchText: searchText });
});
rows.sort((a, b) => {
const forceCmp = GBHD_FORCE_ORDER.indexOf(a.forceId) - GBHD_FORCE_ORDER.indexOf(b.forceId);
if (forceCmp !== 0) return forceCmp;
const classCmp = GBHD_CLASS_ORDER.indexOf(a.chessType) - GBHD_CLASS_ORDER.indexOf(b.chessType);
if (classCmp !== 0) return classCmp;
return (a.heroName || '').localeCompare(b.heroName || '', 'zh-Hans-CN');
});
return { heroes: rows };
}
function gbhuInitFilters() {
if (!gbhuData || gbhuRendered) return;
gbhuRendered = true;
const forceSelect = document.getElementById('gbhu-force-filter');
if (forceSelect) {
const forces = [...new Map(gbhuData.heroes.map(hero => [
hero.forceName,
{ forceName: hero.forceName, label: gbhdForceLabel(hero) },
])).values()]
.filter(force => force.forceName)
.sort((a, b) => a.label.localeCompare(b.label, 'zh-Hans-CN'));
forceSelect.insertAdjacentHTML('beforeend', forces
.map(force => `<option value="${gbEscapeAttr(force.forceName)}">${gbEscapeHtml(force.label)}</option>`)
.join(''));
forceSelect.addEventListener('change', gbhuRender);
}
const speedSelect = document.getElementById('gbhu-speed-filter');
if (speedSelect) speedSelect.addEventListener('change', gbhuRender);
const search = document.getElementById('gbhu-search');
if (search) search.addEventListener('input', gbhuRender);
}
function gbhuRender() {
const table = document.getElementById('gbhu-upgrade-table');
const badge = document.getElementById('gbhu-total-badge');
if (!table || !gbhuData) return;
const forceFilter = document.getElementById('gbhu-force-filter')?.value || '';
const speedFilter = document.getElementById('gbhu-speed-filter')?.value || '';
const query = (document.getElementById('gbhu-search')?.value || '').trim().toLowerCase();
const heroes = gbhuData.heroes.filter(hero => {
if (forceFilter && hero.forceName !== forceFilter) return false;
if (speedFilter && !hero.estimates.some(est => est.speed === speedFilter)) return false;
if (query && !hero.upgradeSearchText.toLowerCase().includes(query)) return false;
return true;
});
if (badge) badge.textContent = `${heroes.length}/${gbhuData.heroes.length} 英雄`;
if (heroes.length === 0) {
table.innerHTML = '<div class="gb-stats-empty">当前筛选条件下没有英雄升级估算</div>';
return;
}
table.innerHTML = `<div class="gbhu-table-wrap">
<table class="gb-table gbhu-table">
<thead>
<tr>
<th>帝国</th>
<th>英雄</th>
<th>Lv1 Lv2</th>
<th>Lv2 Lv3</th>
<th>Lv3 Lv4</th>
</tr>
</thead>
<tbody>
${heroes.map(hero => gbhuRenderHeroRow(hero)).join('')}
</tbody>
</table>
</div>`;
}
function gbhuRenderHeroRow(hero) {
return `<tr>
<td>
<div class="gbhu-force">${gbEscapeHtml(gbhdForceLabel(hero))}</div>
<div class="gbhu-class">${gbEscapeHtml(hero.className || '-')}</div>
</td>
<td>
<div class="gbhp-hero-main">
<span class="gbhp-hero-name">${gbEscapeHtml(hero.heroName || hero.giantName || '-')}</span>
<span class="gbhp-hero-key">${gbEscapeHtml(hero.giantName || '')}</span>
</div>
</td>
${hero.estimates.map(est => gbhuRenderEstimateCell(est)).join('')}
</tr>`;
}
function gbhuRenderEstimateCell(est) {
return `<td class="gbhu-est-cell">
<div class="gbhu-est-head">
<span class="gbhu-turn">${gbEscapeHtml(est.turnText)}</span>
<span class="gbhu-speed gbhu-speed-${gbEscapeAttr(est.speed)}">${gbEscapeHtml(est.speedLabel)}</span>
</div>
<div class="gbhu-task">${gbEscapeHtml(est.taskText)}</div>
<div class="gbhu-logic">${gbEscapeHtml(est.logic)}</div>
</td>`;
}
function gbhuEstimateTask(hero, fromLevel, toLevel, task, skillMap) {
if (!task) {
return gbhuEstimateResult(null, fromLevel, toLevel, 0, 0, 'gated', '没有配置任务,无法升级。', '无任务');
}
const taskType = Number(task.taskContentType);
const param = Number(task.param) || 0;
const taskName = GBHU_TASK_TYPE_NAMES[taskType] || `任务类型 ${taskType}`;
const taskText = gbhuTaskText(task, taskName, skillMap);
const special = gbhuSpecialEstimate(hero, fromLevel, toLevel, task, skillMap, taskName, taskText);
if (special) return special;
const model = gbhuBaseModel(taskType, param);
return gbhuEstimateResult(task, fromLevel, toLevel, model.min, model.max, model.speed, model.logic, taskText, taskName);
}
function gbhuSpecialEstimate(hero, fromLevel, toLevel, task, skillMap, taskName, taskText) {
const gt = Number(hero.giantType);
const taskType = Number(task.taskContentType);
const param = Number(task.param) || 0;
if (gt === 6 && taskType === 7) {
const perTurn = fromLevel >= 3 ? 6 : 3;
const min = Math.ceil(param / perTurn);
const max = Math.ceil(param / Math.max(1, perTurn - 1));
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
`按辉夜每回合用永夜返给周围友军叠满月估算Lv${fromLevel}常见每次影响${perTurn - 1}-${perTurn}个目标。`,
taskText, taskName);
}
if (gt === 7 && taskType === 8) {
const min = Math.ceil(param / 1.5);
const max = Math.ceil(param / 1);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
'按铃仙每回合主动参战并靠幻视调率约1-1.5次有效创造幻象估算。',
taskText, taskName);
}
if (gt === 7 && taskType === 7) {
const min = Math.ceil(param / 4);
const max = Math.ceil(param / 3);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'slow',
'按战地协同标的每回合约施加3-4层估算需要持续围绕同一目标打配合。',
taskText, taskName);
}
if (gt === 8 && taskType === 9) {
const min = Math.ceil(param / 4);
const max = Math.ceil(param / 3);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
'按帝每回合通过攻击、击杀或死亡收益约3-4金币估算真实速度取决于能否持续找战斗。',
taskText, taskName);
}
if (gt === 10 && taskType === 23) {
const min = Math.ceil(param / 16);
const max = Math.ceil(param / 12);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
'妹红既计造成也计承受伤害,按主动换血/自爆相关玩法每回合约12-16点累计估算。',
taskText, taskName);
}
if (gt === 5 && taskType === 10) {
const perTurn = fromLevel >= 3 ? 6 : 4;
const min = Math.ceil(param / perTurn);
const max = Math.ceil(param / Math.max(1, perTurn - 1));
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
`按帕秋莉每回合消耗${perTurn - 1}-${perTurn}层魔力石估算,取决于能否连续攻击或治疗。`,
taskText, taskName);
}
if (gt === 11 && taskType === 14) {
return gbhuEstimateResult(task, fromLevel, toLevel, 5, 7, 'gated',
'按前期完成5项科技估算通常受科技节奏限制约第5-7回合可达成。',
taskText, taskName);
}
if (gt === 12 && taskType === 15) {
return gbhuEstimateResult(task, fromLevel, toLevel, 0, 1, 'gated',
'需要已有御射宫司大人Lv4。若神奈子已达Lv4基本可立即完成否则完全受神奈子升级节奏锁定。',
taskText, taskName);
}
if (gt === 12 && taskType === 16) {
const min = Math.ceil(param / 2);
const max = Math.ceil(param / 1);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'slow',
'按御射宫司大人每回合1-2次击杀估算需要先有高等级蛇且持续参战。',
taskText, taskName);
}
if (gt === 13 && taskType === 7) {
const perTurn = fromLevel >= 3 ? 6 : 4;
const min = Math.ceil(param / perTurn);
const max = Math.ceil(param / Math.max(1, perTurn - 2));
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
`按早苗每回合通过神风/治疗给友军施加乘风${perTurn - 2}-${perTurn}层估算。`,
taskText, taskName);
}
if (gt === 13 && taskType === 17) {
const expectedActions = Math.ceil(param / 0.25);
return gbhuEstimateResult(task, fromLevel, toLevel, expectedActions, expectedActions + 3, 'slow',
'普通御神签大吉或大凶合计20%且3次非极端后下次强制极端按平均约4次行动出1次极端估算。',
taskText, taskName);
}
if (gt === 14 && taskType === 18) {
const min = Math.ceil(param / 18);
const max = Math.ceil(param / 12);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
'按文的路径/踩踏溅射每回合约12-18点有效溅射伤害估算敌方站位密集时偏快。',
taskText, taskName);
}
if (gt === 16 && taskType === 7 && Number(task.skillParam) === 210) {
const min = Math.ceil(param / 5);
const max = Math.ceil(param / 3);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
'按觉每回合通过攻击/窥视等来源施加3-5层恐惧估算。',
taskText, taskName);
}
if (gt === 16 && taskType === 7 && Number(task.skillParam) === 213) {
const min = Math.ceil(param / 2);
const max = Math.ceil(param / 1);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'slow',
'心理创伤来源更窄按每回合1-2次有效施加估算。',
taskText, taskName);
}
if (gt === 20 && taskType === 7) {
const min = Math.ceil(param / 5);
const max = Math.ceil(param / 3);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
'按燐通过收集尸骸并转化粗钝身/金刚身每回合约3-5层估算。',
taskText, taskName);
}
if (gt === 22 && taskType === 24) {
const min = Math.ceil(param / 1);
const max = Math.ceil(param / 0.8);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'normal',
'按堇子设置灵异珠每回合约1个估算若需要走位找空地则取上限。',
taskText, taskName);
}
if (gt === 24 && taskType === 25) {
const min = Math.ceil(param / 3);
const max = Math.ceil(param / 2);
return gbhuEstimateResult(task, fromLevel, toLevel, min, max, 'slow',
'按阿吽进入吽形石守后每回合承受2-3次有效伤害估算需要主动让石化体吃火力。',
taskText, taskName);
}
return null;
}
function gbhuBaseModel(taskType, param) {
switch (Number(taskType)) {
case 0:
return gbhuModelFromRate(param, 10, 14, 'normal', '按英雄主动吃伤害每回合约10-14点承伤估算。');
case 1:
return gbhuModelFromRate(param, 10, 14, 'normal', '按英雄每回合一次有效攻击约10-14点造成伤害估算。');
case 2:
return gbhuModelFromRate(param, 1, 1.5, 'slow', '按英雄持续参战每回合约1-1.5次击杀估算。');
case 3:
return gbhuModelFromRate(param, 8, 12, 'normal', '按英雄每回合一次治疗或辅助治疗约8-12点回复量估算。');
case 4:
return gbhuModelFromRate(param, 0.4, 0.6, 'slow', '按英雄本人开遗迹约每2回合1处估算。');
case 5:
return gbhuModelFromRate(param, 7, 9, 'normal', '按探索型英雄每回合推进并揭开约7-9块迷雾估算。');
case 7:
case 8:
return gbhuModelFromRate(param, 2, 4, 'normal', '按对应技能每回合约触发2-4层/次估算,实际取决于目标密度和行动点。');
case 9:
case 13:
return gbhuModelFromRate(param, 5, 7, 'fast', '按帝国自然经济与战斗收益合计每回合约5-7金币估算。');
case 10:
return gbhuModelFromRate(param, 3, 5, 'normal', '按每回合消耗或掉落3-5层目标技能估算。');
case 12:
return gbhuModelFromRate(param, 1, 2, 'normal', '按每回合创建1-2个目标特殊地格估算。');
case 14:
return gbhuModelFromRate(param, 0.8, 1.2, 'gated', '按科技节奏约每回合0.8-1.2项估算,受研究线限制。');
case 15:
return gbhuModelFromRate(param, 0.8, 1.2, 'gated', '按目标单位生产/转化约每回合0.8-1.2个估算,受资源与前置单位限制。');
case 16:
return gbhuModelFromRate(param, 1, 1.5, 'slow', '按指定单位每回合约1-1.5次击杀估算。');
case 17:
return gbhuModelFromRate(param, 0.2, 0.25, 'slow', '按大吉/大凶约20%-25%出现率估算。');
case 18:
return gbhuModelFromRate(param, 10, 14, 'normal', '按每回合约10-14点溅射伤害估算。');
case 20:
return gbhuModelFromRate(param, 8, 10, 'normal', '按指定探索单位每回合揭开约8-10块迷雾估算。');
case 21:
return gbhuModelFromRate(param, 0.5, 0.8, 'normal', '按帝国成员合计开遗迹约每1-2回合1处估算。');
case 22:
return gbhuModelFromRate(param, 0.6, 1, 'gated', '按占1城=2进度、占1村=1进度前期通常约2-4回合完成。');
case 23:
return gbhuModelFromRate(param, 12, 16, 'normal', '按英雄主动交战造成或承受伤害合计每回合约12-16点估算。');
case 24:
return gbhuModelFromRate(param, 0.8, 1, 'normal', '按每回合约设置0.8-1个目标物估算。');
case 25:
return gbhuModelFromRate(param, 2, 3, 'slow', '按状态满足后每回合承受2-3次有效伤害估算。');
default:
return gbhuModelFromRate(param, 1, 1, 'gated', '未知任务类型暂按每回合1点进度粗估。');
}
}
function gbhuModelFromRate(param, lowRate, highRate, speed, logic) {
const min = Math.max(0, Math.ceil((Number(param) || 0) / Math.max(highRate, 0.01)));
const max = Math.max(min, Math.ceil((Number(param) || 0) / Math.max(lowRate, 0.01)));
return { min, max, speed, logic };
}
function gbhuEstimateResult(task, fromLevel, toLevel, min, max, speed, logic, taskText, taskName = '') {
const safeMin = Math.max(0, Math.ceil(min || 0));
const safeMax = Math.max(safeMin, Math.ceil(max || safeMin));
const turnText = safeMin === safeMax ? `${safeMin} 回合` : `${safeMin}-${safeMax} 回合`;
return {
fromLevel,
toLevel,
task,
taskName,
taskText,
minTurns: safeMin,
maxTurns: safeMax,
turnText,
speed,
speedLabel: GBHU_SPEED_LABELS[speed] || speed || '-',
logic,
};
}
function gbhuTaskText(task, taskName, skillMap) {
const desc = gbhdCleanMarkup(task?.desc || '');
const param = Number(task?.param) || 0;
const skill = skillMap.get(Number(task?.skillParam));
const suffix = skill?.name ? ` · ${skill.name}` : '';
return `${taskName}${param ? ` ${param}` : ''}${suffix}${desc ? `${desc}` : ''}`;
}
// ════════════════════════════════════════════
// Hero Pricing Model Sub-module
// ════════════════════════════════════════════