feat: 新增英雄单查、新增、更新、删除接口及API文档
新增了英雄相关的四个核心API接口:单英雄查询、新增/覆盖英雄、局部更新英雄、删除英雄,并补充了详细的API调用规范文档,方便智能体配置英雄。
This commit is contained in:
277
put/server/data/api-skill-guide.md
Normal file
277
put/server/data/api-skill-guide.md
Normal 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`
|
||||
@@ -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 从线上库读取 */
|
||||
app.get('/api/templates', async (_req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user