feat: 新增英雄单查、新增、更新、删除接口及API文档

新增了英雄相关的四个核心API接口:单英雄查询、新增/覆盖英雄、局部更新英雄、删除英雄,并补充了详细的API调用规范文档,方便智能体配置英雄。
This commit is contained in:
panFD
2026-10-05 15:10:39 +08:00
parent 8c7748436b
commit 0db3404481
2 changed files with 349 additions and 0 deletions

View File

@@ -0,0 +1,277 @@
# 英雄配置 API 智能体调用规范
> 本文档面向 **Hermes / OpenClaw** 等智能体 Skill,说明如何通过 HTTP API 配置与修改英雄。
> 数据源头为 `heroes.json` / MongoDB,技能/光环/buff 底座定义请阅读 `heroes.json编辑指引.md`。
---
## 1. 服务信息
| 项 | 值 |
|---|---|
| 开发版地址 | `http://localhost:3001`(`MODE=local`) |
| 线上设计版地址 | 部署地址(`MODE=remote`,静态托管 `web/dist`) |
| 内容类型 | `application/json` |
| 认证 | 无(内网/本地环境) |
启动服务(在 `put` 目录):
```bash
npm run dev # 开发版
npm run dev:remote # 线上设计版(本地模拟 remote)
```
---
## 2. 核心接口一览
| 方法 | 路径 | 说明 |
|---|---|---|
| `GET` | `/api/meta` | 获取底座快照(技能/光环/buff/职业模板/版本) |
| `GET` | `/api/heroes` | 获取全部英雄列表 |
| `GET` | `/api/heroes/:uuid` | 获取单个英雄完整配置 |
| `POST` | `/api/heroes/:uuid` | **新增或覆盖**单个英雄(body 为完整英雄对象) |
| `PUT` | `/api/heroes/:uuid` | **局部更新**单个英雄(body 为需修改的字段) |
| `DELETE` | `/api/heroes/:uuid` | 删除单个英雄 |
| `POST` | `/api/refresh` | 刷新底座缓存(修改 SkillSet.ts 等后调用) |
> **版本闸门**:线上设计版(remote)在库中 `schemaVersion` 高于服务内置版本时,写操作返回 `409`,需更新部署。
---
## 3. 英雄对象结构(Hero Schema)
智能体在构造或修改英雄时,必须遵循以下字段规范。
### 3.1 顶层字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `uuid` | number | 是 | 英雄唯一 ID,5000 段,新增从 5011 起(先 `GET /api/heroes` 查重) |
| `name` | string | 是 | 显示名 |
| `comment` | string | 否 | 设计说明/机制主题 |
| `path` | string | 是 | 美术资源名,需存在 `assets/resources/game/heros/{path}.prefab` |
| `rarity` | 1~5 | 是 | 稀有度,影响 lv1 基础属性缩放与卡池费用 |
| `roleKey` | string | 是 | 职业:`Tank`/`Warrior`/`Assassin`/`Archer`/`Mage`/`Support` |
| `growKey` | string | 是 | 成长曲线,通常与 `roleKey` 一致 |
| `typeKey` | string | 是 | 攻击定位:`Melee`/`Mid`/`Long` |
| `statBase` | `{hp,ap,def}` | 是 | 相对职业模板的偏移量,惯例 ±5/10/20/30 |
| `dis` / `speed` / `dodge` / `accuracy` | number\|null | 否 | 英雄一般 `null`,走默认 |
| `skills` | array | 是 | **恰好 2 个**:`[普攻, 大招]`,见 §3.2 |
| `atking`/`atked`/`fstart`/`fend`/`dead` | object | 是 | 触发组,见 §3.3;无则写 `{}` |
| `field` | array | 是 | 驻场光环档位,见 §3.4;无则写 `[]` |
| `bonus` | array | 是 | 额外属性加成,见 §3.5;常规英雄写 `[]` |
| `info` | string | 否 | 描述文案 |
### 3.2 skills 数组(恰好 2 项)
每项结构:`{uuid, name, cd, cdExpr, overrides, desc, lv_entries}`
| 字段 | 说明 |
|---|---|
| `uuid` | 技能底座 uuid,普攻必须 ∈ `ROLE_ATK_SKILL[roleKey]`,大招 ∈ `ROLE_ULT_SKILL[roleKey]` 或 `SUPPORT_ULT` |
| `name` | 留空回退底座名;建议起特色名 |
| `cd` | 数值冷却(普攻 0.75/0.9/1.05/1.2/1.5;大招 6/7/8/9/10) |
| `cdExpr` | 表达式,普攻=`AtkSpeedSet[AtkSpeedLv.X].cd`,大招=`UltCdSet[UltCdLv.X].cd` |
| `overrides` | 覆写键值对,见 §3.6 |
| `desc` | 描述模板,支持 `{target}` `{ap}` `{dur}` 等占位符 |
| `lv_entries` | 升级档位:普攻 1 档 `{lv:2,s_lv:2,...}`;大招 2 档 `{lv:8,s_lv:2,...}`、`{lv:16,s_lv:3,...}` |
### 3.3 触发组(atking / atked / fstart / fend / dead)
结构:`{ "<技能uuid字符串>": [条目, ...] }`
条目:`{lv, s_lv, name, t_num, t_chance?, pity_add?, overrides, desc}`
| 字段 | 说明 |
|---|---|
| `lv` | 解锁等级,惯例 **4** |
| `s_lv` | 技能等级,惯例 **1** |
| `t_num` | 触发间隔:atking/atked 为每 n 次;fstart/fend 固定 **1** |
| `t_chance` | 触发概率(0~100),仅 atking/atked 生效,缺省=100 |
| `pity_add` | 保底叠加,仅 atking/atked 生效,触发后清零 |
| `overrides` | 同技能覆写 |
| `desc` | 描述模板,支持 `{t_chance}` `{pity_add}` 占位符 |
**复活**:在 `dead` 组条目 `overrides` 中配置 `revive_hp`(回血%)+ `revive_count`(次数上限)。
### 3.4 field 光环
结构:`[{lv:4, uuids:[{uuid, value, name}], desc?}]`
- `uuid` 必须 ∈ `FieldSkillSet`(7001~7022 段)
- `value` 按光环类型:比例类为小数(如 0.15),次数/固定值类为整数
- 解锁等级惯例 **lv4**
### 3.5 bonus 额外属性
结构:`[{lv, attrs:{...}, name?, icon?, info?}]`
- `attrs` 键:`hp`/`ap`/`def`(百分比,基数=基础×品质+成长)、`crt`/`frz`/`stun`/`para`(固定百分点)
- 运行时累加所有 `lv ≤ 英雄等级` 的档位
- 配 `icon` 的档位在图鉴技能列表最末展示
### 3.6 overrides 白名单
可用键:
`ap, hit_count, hitcd, crt, frz, stun, para, bck, bck_chance, TGroup, buff_type, timed_buff_id, buff_value, buff_duration, buff_chance, buff_target, timed_debuff_id, debuff_value, debuff_duration, debuff_chance, call_hero, summon_count, is_accel, num, revive_hp, revive_count, proc_skill, icon`
特殊格式:
- `TGroup`:字符串 `"Self"`/`"Ally"`/`"Team"`/`"Enemy"`,导出还原枚举
- `buff_target`:数字 0~3(Hit/Self/Ally/Enemy)
- `proc_skill`:对象 `{s_uuid, chance?, overrides?}`
- `icon`:字符串图标帧名
**buff/debuff 语义**:
- `timed_buff_id`:增益,缺省挂施法者自身(伤害技能)或友方目标(辅助技能)
- `timed_debuff_id`:减益,命中后挂被命中者,目标不可覆写
- 对敌挂减益必须用 `timed_debuff_id`,误用 `timed_buff_id` 会把减益挂到自己身上
---
## 4. 调用示例
### 4.1 获取底座数据(修改前必读)
```http
GET /api/meta
```
返回包含 `SkillSet`、`FieldSkillSet`、`BuffList`、`HERO_ROLE_BASE`、`HERO_GROW_COEF`、`AtkSpeedSet`、`HeroDisVal` 等。
### 4.2 查询全部英雄
```http
GET /api/heroes
```
### 4.3 查询单个英雄
```http
GET /api/heroes/5001
```
### 4.4 新增英雄(完整对象)
```http
POST /api/heroes/5011
Content-Type: application/json
{
"uuid": 5011,
"name": "烈焰法师",
"comment": "群体火伤+燃烧",
"path": "mage_fire",
"rarity": 4,
"roleKey": "Mage",
"growKey": "Mage",
"typeKey": "Long",
"statBase": { "hp": -5, "ap": 30, "def": 0 },
"dis": null,
"speed": null,
"dodge": null,
"accuracy": null,
"skills": [
{
"uuid": 6009,
"name": "火球术",
"cd": 1.2,
"cdExpr": "AtkSpeedSet[AtkSpeedLv.Slow].cd",
"overrides": { "ap": 120 },
"desc": "对{target}造成{ap}%伤害",
"lv_entries": [
{ "lv": 2, "s_lv": 2, "overrides": { "ap": 150 }, "desc": "造成{ap}%伤害" }
]
},
{
"uuid": 6200,
"name": "烈焰风暴",
"cd": 8,
"cdExpr": "UltCdSet[UltCdLv.Normal].cd",
"overrides": { "ap": 200, "timed_debuff_id": 7002, "debuff_value": 30, "debuff_chance": 50 },
"desc": "对全体敌人造成{ap}%伤害,{debuff_chance}%附加燃烧",
"lv_entries": [
{ "lv": 8, "s_lv": 2, "overrides": { "ap": 250 }, "desc": "造成{ap}%伤害" },
{ "lv": 16, "s_lv": 3, "overrides": { "ap": 350 }, "desc": "造成{ap}%伤害" }
]
}
],
"atking": {},
"atked": {},
"fstart": {},
"fend": {},
"dead": {},
"field": [],
"bonus": [],
"info": "掌控火焰的法师"
}
```
### 4.5 局部修改英雄(如只改大招数值)
```http
PUT /api/heroes/5011
Content-Type: application/json
{
"skills": [
{ "uuid": 6009, "name": "火球术", "cd": 1.2, "cdExpr": "AtkSpeedSet[AtkSpeedLv.Slow].cd", "overrides": { "ap": 120 }, "desc": "对{target}造成{ap}%伤害", "lv_entries": [{ "lv": 2, "s_lv": 2, "overrides": { "ap": 150 }, "desc": "造成{ap}%伤害" }] },
{ "uuid": 6200, "name": "烈焰风暴", "cd": 8, "cdExpr": "UltCdSet[UltCdLv.Normal].cd", "overrides": { "ap": 250, "timed_debuff_id": 7002, "debuff_value": 50, "debuff_chance": 80 }, "desc": "对全体敌人造成{ap}%伤害,{debuff_chance}%附加燃烧", "lv_entries": [{ "lv": 8, "s_lv": 2, "overrides": { "ap": 300 }, "desc": "造成{ap}%伤害" }, { "lv": 16, "s_lv": 3, "overrides": { "ap": 400 }, "desc": "造成{ap}%伤害" }] }
]
}
```
> **注意**:`PUT` 是浅合并。若修改 `skills` 数组,必须传入**完整的新数组**(不能只传被修改的技能对象)。
### 4.6 删除英雄
```http
DELETE /api/heroes/5011
```
---
## 5. 智能体操作流程(推荐)
1. **读取底座**:`GET /api/meta`,确认技能/光环/buff 可用值与职业模板
2. **查询现有英雄**:`GET /api/heroes`,确定新 uuid 不冲突(当前已用 5001~5010)
3. **构造英雄对象**:严格遵循 §3 结构,空组写 `{}`,空数组写 `[]`
4. **提交**:
- 新增/覆盖:`POST /api/heroes/:uuid`
- 局部修改:`PUT /api/heroes/:uuid`(传完整修改字段)
5. **验证**:`GET /api/heroes/:uuid` 确认数据已写入
6. **(开发版)应用**:`POST /api/apply` 将 heroes.json 渲染进 `heroSet.ts`(线上设计版无此权限)
---
## 6. 错误码与处理
| 状态码 | 场景 | 处理 |
|---|---|---|
| 400 | 参数错误(uuid 非数字、body 非对象/数组、heroes 为空等) | 检查请求格式 |
| 403 | 线上设计版调用本地专属接口(如 `/api/apply`) | 切换至开发版或放弃该操作 |
| 404 | 英雄不存在 | 先 `GET /api/heroes` 确认 uuid |
| 409 | 线上设计版结构版本落后 | 更新部署后再试 |
| 500 | 服务端错误(MongoDB 连接失败、文件读写失败等) | 查看服务日志 |
---
## 7. 常见陷阱(智能体必读)
- **不要手改 `heroSet.ts` 英雄区**:`POST /api/apply` 会整体覆盖标记区内容
- **新增英雄后必须手动把 uuid 加入 `HeroList`**(`heroSet.ts` 标记区之外),否则英雄不进卡池
- **触发组解锁等级是 lv4,大招档位是 lv8+lv16**,不要写 lv6/lv9
- **普攻 cdExpr 用 `AtkSpeedSet`,大招 cdExpr 用 `UltCdSet`**,两套体系不可混用
- **档位 overrides 与基础 overrides 浅合并**,档位只写变化键,不要全量复制
- **desc 用占位符 `{ap}`**,不要硬编码数字,避免与 overrides 数值脱节
- **对敌 debuff 用 `timed_debuff_id`,吸血/自强化用 `timed_buff_id`**,两者目标语义相反
- **复活已废弃独立 `revive` 字段**,改在 `dead` 组 overrides 配 `revive_hp`/`revive_count`
---
## 8. 参考文档
- 详细字段规则与数据源:`put/server/data/heroes.json编辑指引.md`
- 编辑器枚举与默认值:`put/web/src/constants.js`
- 技能底座定义:`assets/script/game/common/config/SkillSet.ts`
- Buff 底座定义:`assets/script/game/common/config/BuffSet.ts`
- 职业模板与成长:`assets/script/game/common/config/heroSet.ts`

View File

@@ -142,6 +142,78 @@ app.post('/api/heroes', async (req, res) => {
} }
}) })
/** 单英雄读取:GET /api/heroes/:uuid */
app.get('/api/heroes/:uuid', async (req, res) => {
try {
const uuid = Number(req.params.uuid)
if (Number.isNaN(uuid)) return res.status(400).json({ error: 'uuid 必须为数字' })
const heroes = await store.getHeroes()
const hero = heroes.find((h) => h.uuid === uuid)
if (!hero) return res.status(404).json({ error: `英雄 ${uuid} 不存在` })
res.json(hero)
} catch (e) {
res.status(500).json({ error: String(e?.message || e) })
}
})
/** 单英雄新增/覆盖:POST /api/heroes/:uuid(body 为完整英雄对象,uuid 以 URL 为准) */
app.post('/api/heroes/:uuid', async (req, res) => {
try {
if (!(await guardVersion(res))) return
const uuid = Number(req.params.uuid)
if (Number.isNaN(uuid)) return res.status(400).json({ error: 'uuid 必须为数字' })
const hero = req.body
if (!hero || typeof hero !== 'object') return res.status(400).json({ error: 'body 必须为英雄对象' })
hero.uuid = uuid
const heroes = await store.getHeroes()
const idx = heroes.findIndex((h) => h.uuid === uuid)
if (idx >= 0) heroes[idx] = hero
else heroes.push(hero)
await store.saveHeroes(heroes)
res.json({ ok: true, uuid, action: idx >= 0 ? 'updated' : 'created' })
} catch (e) {
res.status(500).json({ error: String(e?.message || e) })
}
})
/** 单英雄局部更新:PUT /api/heroes/:uuid(body 为部分字段,与现有英雄浅合并) */
app.put('/api/heroes/:uuid', async (req, res) => {
try {
if (!(await guardVersion(res))) return
const uuid = Number(req.params.uuid)
if (Number.isNaN(uuid)) return res.status(400).json({ error: 'uuid 必须为数字' })
const patch = req.body
if (!patch || typeof patch !== 'object') return res.status(400).json({ error: 'body 必须为部分字段对象' })
const heroes = await store.getHeroes()
const idx = heroes.findIndex((h) => h.uuid === uuid)
if (idx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在` })
// uuid 不可变,防止 patch 覆盖
delete patch.uuid
heroes[idx] = { ...heroes[idx], ...patch }
await store.saveHeroes(heroes)
res.json({ ok: true, uuid, action: 'patched' })
} catch (e) {
res.status(500).json({ error: String(e?.message || e) })
}
})
/** 单英雄删除:DELETE /api/heroes/:uuid */
app.delete('/api/heroes/:uuid', async (req, res) => {
try {
if (!(await guardVersion(res))) return
const uuid = Number(req.params.uuid)
if (Number.isNaN(uuid)) return res.status(400).json({ error: 'uuid 必须为数字' })
const heroes = await store.getHeroes()
const idx = heroes.findIndex((h) => h.uuid === uuid)
if (idx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在` })
heroes.splice(idx, 1)
await store.saveHeroes(heroes)
res.json({ ok: true, uuid, action: 'deleted' })
} catch (e) {
res.status(500).json({ error: String(e?.message || e) })
}
})
/** 预设库:GET 从线上库读取 */ /** 预设库:GET 从线上库读取 */
app.get('/api/templates', async (_req, res) => { app.get('/api/templates', async (_req, res) => {
try { try {