163 lines
21 KiB
Markdown
163 lines
21 KiB
Markdown
# 游戏服协议
|
||
|
||
## 生产
|
||
|
||
Base: `https://BuddyDefense.xworld.ren:15888`(Caddy → 后端)
|
||
|
||
## 本地 AIServer(推荐开发)
|
||
|
||
路径:`AIServer/`(C++ + SQLite)
|
||
|
||
| Base | `http://127.0.0.1:8080` |
|
||
| WS | `ws://127.0.0.1:8080/ws?pid={pid}&token={token}` |
|
||
|
||
构建与运行见 [`AIServer/README.md`](../AIServer/README.md)。
|
||
|
||
## HTTP
|
||
|
||
| 方法 | 路径 | Body | 成功 | 失败 |
|
||
| --- | --- | --- | --- | --- |
|
||
| POST | `/register` | `{"username","password"}` | `{token,pid}` | `invalid_input` / `user_exists` |
|
||
| POST | `/login` | `{"username","password"}` | `{token,pid}` | `invalid_credentials` |
|
||
| GET | `/health` | — | `{ok,service,maintenance,gameProtocol:1}` | — |
|
||
| GET | `/maintenance` | — | `{enabled,message}` | — |
|
||
| GET | `/game/state` | — | 游戏快照、服务端时间、待完成活动 | 401 / 503 / 500 |
|
||
| POST | `/game/command` | 见下文 | 幂等操作结果和最新快照 | 400 / 401 / 409 / 503 / 500 |
|
||
|
||
### G1 教学玩法 API(本地已实现)
|
||
|
||
玩法接口统一使用 `Authorization: Bearer {token}`。玩家身份从 token 解析,Body 内的 `pid` 不参与寻址。维护期间读写玩法均返回 HTTP 503。此版本仅在本地 AIServer 联调通过,未部署生产服。
|
||
|
||
```json
|
||
{"requestId":"32位唯一请求号","action":"shot.begin","revision":0,"aim":5}
|
||
```
|
||
|
||
`requestId` 为 16~64 位字母、数字、下划线或短横线;客户端使用 GUID。`revision` 是最近一次快照的修订号。同一玩家、同一请求号、相同完整 JSON 内容再次提交,返回原操作结果及当前最新快照,并标记 `replayed:true`;对象字段顺序不影响比较。同一请求号更换内容返回 HTTP 409 `request_id_conflict`。已记录的业务拒绝也保持原结果,修正条件后必须创建新请求号。
|
||
|
||
| action | 额外字段 | 行为 |
|
||
| --- | --- | --- |
|
||
| `shot.begin` | `aim`:整数 -35~35 | 扣1弹珠,保存待结算活动,返回完整服务端轨迹与奖励 |
|
||
| `shot.finish` | `activityId`:begin 请求号 | 服务端到期后结算一次,首次及每十次赠券 |
|
||
| `capture` | 无 | 消耗1券;服务端随机并推进保底,返回 `result.id/pet/duplicate/tutorial/pity/shards/poolVersion` |
|
||
| `train` | 无 | 消耗 `30×level` 糖果;共享训练最高20级 |
|
||
| `evolve` | `pet`:0~4 | T1、Lv.5、碎片40、金币500;成长到T2 |
|
||
| `deploy` | `pet`:0~4,`slot`:0~1 | 唯一伙伴、占位交换,只开放中央路线 |
|
||
| `autoDeploy` | 无 | 恢复岩团团前排、火喵后排 |
|
||
| `stage.select` | `stageId`:1~5 | 保存选中关卡,只允许已解锁关卡 |
|
||
| `battle.begin` | 可选 `stageId`,省略/0使用已选关卡 | 检查解锁,冻结编队、成长和完整配置,服务端计算并保存战斗结果 |
|
||
| `battle.finish` | `activityId`:begin 请求号 | 校验活动与最短时间;服务端胜利才发一次首通奖 |
|
||
| `battle.abandon` | `activityId` | 立即结束,不发胜利奖励 |
|
||
| `goal.claim` | 无 | 通关后只发一次5弹珠 |
|
||
| `camp.place` | `item`:0~2,`slot`:0~2 | 通过1-5后购买并放置;已拥有则免费移动,目标被其他装饰占用时拒绝 |
|
||
| `camp.store` | `slot`:0~2 | 通过1-5后收回装饰,保留拥有记录,无扣费/退款 |
|
||
| `camp.interact` | `pet`:0~4 | 通过1-3后与已拥有伙伴互动,保存伙伴和次数,不发资源 |
|
||
| `daily.claim` | `item`:0~2,`day`:最近快照的 `dailyDay` | 领取已完成当日目标;跨日旧请求的新提交拒绝,已提交成功的原请求可重放 |
|
||
| `signin.claim` | `item`:0~6 | 领取累计登录已解锁项,逐项只发一次,已解锁未领奖励保留 |
|
||
| `shop.buy` | `item`:0~2,`day`:最近快照的 `dailyDay` | 按服务器价格领取/购买一份补给,校验解锁、余额和当日限购 |
|
||
| `chest.claimDaily` | 无 | 领取当日每日宝箱,只增加1个箱库存,不发箱内容 |
|
||
| `chest.open` | `chest`:0~2;自选箱另带 `choice`:0~4 | 扣除1个箱并在同一事务发放固定/自选内容 |
|
||
|
||
回应公共字段:`ok/error/replayed/action/state/serverTime/configVersion/config/result/capture/rewards`;读取时 `action` 为空,命令与重放响应保留动作名。`serverTime` 是 UTC Unix 秒。`config` 是唯一配置源 `Config/Gameplay/greenfield.json` 的内容,包含经济、弹珠、五只伙伴、捕捉池、敌人、五关波次与首通奖励、五元素关系;服务端 CMake 编译时嵌入,版本为 `greenfield-1-{源文件SHA256前12位}`,修改 JSON 后须重新构建。客户端使用返回配置,战斗使用开始时冻结配置,不能混用新旧数值。
|
||
|
||
`state` 对应客户端 `PrototypeSave`:当前schema=9,保留钱包、阵容、恢复锚点、修订号、首通/目标标志、`pendingShot/pendingBattle/lastShot/lastBattle`、`selectedStage` 和长度5的0/1数组 `clearedStages`。伙伴 `stages/shards` 长度为5,顺序为岩团团、火喵、芽芽、闪闪雀、泡泡龟;保留 `captureTutorialDone/captureCount/duplicateStreak`、营地及任务/签到字段。按顺序首次通过才解锁下一关,每关首通独立只发一次,重复战斗不发经济奖励;`cleared` 保留为1-1完成标志,用于原教学目标。
|
||
|
||
商店新增 `shopBought:[0,0,0]`,为当前 `dailyDay` 各商品已领取/购买次数,和每日任务在同一次日切事务清零。`config.shop.goods` 定义商品ID、名称/说明、金币价、每日上限、解锁关卡与 `{coins,candy,balls,tickets}` 奖励。当前ID0为免费5弹珠/每日1次;ID1为100金币换20糖果/1-1解锁/每日3次;ID2为300金币换1券/1-3解锁/每日1次。装饰仍使用`camp.place/store`,不从商店重复购买。
|
||
|
||
`shop.buy` 不接受客户端价格、奖励、解锁或库存数值,回执为 `{id,item,day,chargedCoins,reward:{coins,candy,balls,tickets}}`。金币扣除、原钱包到账、限购次数与请求日志同事务提交;新请求须携带当前日键,过期确认拒绝。原成功请求跨日重放保留原费用/奖励,不重新发放或消耗新日库存;客户端恢复“补给已到账”弹窗,标明原费用。打开确认、取消、分类切换均不提交消费请求。限购不按离线天数累积,时钟回退不刷新。免费补给可令弹珠超过自然恢复上限,但不改变原恢复规则。
|
||
|
||
背包没有新增库存接口或镜像余额,读取同一快照的`tickets/shards/decorations/campLayout`,钱包只展示原`coins/candy/balls`。当前物品均永久有效,捕捉、训练和装饰操作直接影响这些原字段;通用物品/批量使用/活动道具/宝箱协议尚未接入。
|
||
|
||
任务/签到新增 `dailyDay`、`dailyProgress:[0,0,0]`、`dailyClaimed:[0,0,0]`、`signinDays:1`、`signinClaimed:[0,0,0,0,0,0,0]`。公共响应附 `daily:{day,resetAt}`,`resetAt` 为下次重置的UTC Unix秒;`config.daily` 含 `utcOffsetSeconds:28800`、三项任务目标/奖励与七项签到奖励。服务器日键为 `floor((now+28800)/86400)`;首次进入当天的玩法读写事务会清空上日每日进度/领取标志,并将累计登录增加1(最多7),跳过多天仍只增加1。时钟回退时保留已记录的较大日键,不回滚标志或多计登录。
|
||
|
||
只有首次执行成功的 `shot.finish`、正常 `battle.finish`(胜/负均可)、`camp.interact` 分别增加三项进度,封顶3/1/1。`shot.begin/battle.begin`、主动放弃、页面打开、重放和客户端自报进度不计入;跨日活动按实际结算日计数。每日奖励未领取则在重置后失效,`daily.claim` 的 `day` 必须匹配当前日键;旧日已成功请求重放仍返回原回执,不改写新日进度/领取标志。签到按累计登录日解锁,不要求连续登录;已解锁项任意顺序领取,无付费补签和七日循环。
|
||
|
||
`daily.claim/signin.claim` 回执为 `{id,item,reward:{coins,candy,balls}}`,每日任务另含 `day`;`goal.claim` 新回执也携带 `id/reward`。领取标志、钱包和请求日志同事务提交;写入失败整体回滚,同一请求重试只返回原奖励,客户端恢复“奖励已领取”弹窗并清理已确认队列。奖励允许弹珠超过恢复上限,恢复规则不另改。旧版本已保存的教学回执可没有reward,客户端按原教学奖励兼容展示。
|
||
|
||
营地新增 `decorations:[0,0,0]` 拥有标记、`campLayout:[-1,-1,-1]` 固定位置(值为装饰ID,-1为空)、`restPet:0` 和 `campInteractions:0`。同一装饰最多放置一次,收回后进入仓库。`config.camp` 定义解锁关卡、槽数、三件装饰的名称/说明/价格;以服务端价格扣费。`camp.place/store` 回执为 `{id,item,slot,chargedCoins}`,购买/移动/收回都与钱包和日志同事务提交;重放返回原费用和原结果。预览与撤回仅改变界面,不发消费请求。确认后丢失响应时保留请求重试,客户端展示“营地已更新”回执。营地操作沿用修订号、鉴权与在途活动互斥规则。
|
||
|
||
弹珠树共用 `state.balls/ballRecoveryUnix` 与原 `recover` 规则,没有额外生产/领取接口;显示的倒计时用最近服务端时间加客户端单调时间推算,余额只在服务端快照确认后更新。伙伴互动只更新选择和累计次数,客户端驱动已制作肖像节点的缩放反馈;无资源产出。
|
||
|
||
`capture` 为当前快照的池展示:`tutorial/completed/pityReady/count/duplicateStreak/entries`,每项为 `{pet,baseProbability,probability}`,概率是0~1的小数。当前普通池五种等权,各20%;首次教学固定芽芽,不推进普通次数或保底。普通捕捉连续4次重复后,下次仅在未拥有伙伴中按权重抽取;获得新伙伴清零重复计数,集齐后保底暂停、重复计数归零。重复发20对应物种碎片。扣券、随机结果、次数、保底、碎片和操作日志同事务提交,客户端不能指定结果或随机种子。
|
||
|
||
最近一次已提交捕捉附 `lastCaptureResult`,结构与捕捉 `result` 相同;不存在时省略,不把默认pet=0视为历史结果。重放返回原回执及最新快照,不重新抽取;客户端恢复待确认的 `capture` 后展示原结果。`poolVersion` 是抽取时的配置版本,`id` 是原请求号。
|
||
|
||
尚未完成的弹珠附 `shot:{id,readyAt,path:[{x,y}],bin,candy,coins}`;挑战附 `battle:{id,readyAt,snapshot:{level,stages,formation,stageId,config,legacy}}`。最后一次战斗结果附 `lastBattleResult:{id,stageId,won,firstClear,duration,durability,defeated,reward:{coins,candy,balls}}`。`reward` 是该关首通基准,只有 `firstClear=true` 时实际发放。
|
||
|
||
schema=1/2服务端存档在首次读写事务内迁移到3。schema=1旧 `cleared=true` 只映射为1-1已通关;schema=2既有关卡进度不变。保留余额、目标、前三伙伴的拥有/进化/碎片和阵容,新增两种伙伴初始未拥有;已拥有芽芽视为完成教学,普通捕捉/保底从0开始,不补发伙伴或奖励。旧未结算弹珠继续用已保存轨迹/奖励;G1旧挑战标为 `legacy:true`,按原G1表现与原80/40/3奖励结算;G2三伙伴挑战保留冻结的三伙伴配置和原关卡奖励,新五伙伴配置不改写在途活动。未知未来schema拒绝处理,不重置账号。客户端PlayerPrefs的G0余额仍不迁入服务端。
|
||
|
||
上述schema1/2迁移后继续执行3→4迁移;既有schema3保留钱包、捕捉次数/保底/回执、伙伴、关卡和在途活动,初始化空装饰库存/布局、默认休息伙伴0和互动次数0,不扣费也不补发奖励。该阶段得到schema4,随后继续下述任务迁移。旧冻结战斗配置允许没有 `camp` 字段,新实时配置必须带营地数据。
|
||
|
||
4→5迁移保留全部钱包、教学/关卡、捕捉、营地、回执和在途活动;初始化新的每日进度/领取标志及七日领取标志,当次访问登记为首个累计登录日,不追溯旧发射/战斗/互动总数,不自动发签到奖励。该阶段得到schema5,随后继续下述商店迁移。旧冻结战斗配置允许没有 `daily` 字段,按保存的战斗数值与奖励继续结算。
|
||
|
||
商店阶段迁移执行5→6:只新增空的`shopBought`,不自动领补给、不清空每日/签到/捕捉/营地或钱包;旧未完成活动与回执保留。旧冻结战斗配置允许缺少`shop`字段,战斗仍按原配置与奖励结算。随后执行下述邮件迁移与宝箱迁移,再执行阵容扩展,当前服务端实时快照为schema9;客户端兼容schema6~9服务滚动更新,低于对应版本时邮件/宝箱入口提示稍后重进。
|
||
|
||
邮件扩展(2026-10-18):6→7只新增`mails`,按`config.mail.initial`保存初始通知/附件,`sentAt=now`、`expiresAt=now+lifetimeSeconds`。新旧账号首次迁移均只建记录,不自动发附件;schema7后读取不补发、不重建邮件。原钱包、伙伴、营地、捕捉、日常、购买次数、回执与在途活动保留。旧冻结战斗无需mail字段,仍按保存数值与奖励结算;实时schema7要求完整mail配置。
|
||
|
||
`state.mails`最多100封,每封为`{mailId,title,body,reward:{coins,candy,balls,tickets},sentAt,expiresAt,read,claimed,expired}`。ID在账号内唯一,服务端时间达到`expiresAt`即过期,已记录的`expired`不因时钟回退复活。到期和已领取记录仍保留供阅读;首页红点仅表示有效未读邮件或有效未领附件,客户端倒计时使用服务端时间加单调时钟,实际可领性仍以服务端为准。
|
||
|
||
- `mail.read`携带`mailId`:只标已读,不发附件,回执`{id,mailId}`。
|
||
- `mail.claim`携带`mailId`:领取保存的附件,忽略客户端奖励;同时标记已读/已领取,回执`{id,mailId,claimedCount:1,reward}`。
|
||
- `mail.claimAll`携带1~20个不重复的`mailIds`:逐封处理,回执`{id,claimedCount,reward,entries:[{mailId,status}]}`。`status`为`claimed/expired/already_claimed/no_attachment/not_found`,失效邮件不阻止有效附件到账;参数重复、空数组、超容量或错误类型整单拒绝为`invalid_input`。
|
||
|
||
邮件操作继续使用鉴权、revision和在途活动互斥。单封业务错误为`mail_not_found/mail_expired/mail_already_claimed/mail_no_attachment`;拒绝也持久化,条件变化须新请求。钱包、标志、批量结果及操作日志在同一事务提交;存储失败全部回滚。原成功请求在重启、跨日或邮件到期后重放仍返回原结果,不二次发附件。客户端仅在完整有效快照和匹配的邮件回执确认后清理请求,并恢复领取结果弹窗。
|
||
|
||
当前初始邮件为7天有效的欢迎补给(糖果20、弹珠5)和无附件营地通知,属于本地开发基线;尚无运营投递/群发/删除邮件接口,修改initial不会向已有schema7账号自动追加新邮件。
|
||
|
||
宝箱扩展(2026-10-19):7→8只新增`chests:[0,0,0]`与`chestDailyClaimed:0`,保留原钱包、伙伴、关卡、捕捉、营地、日常、购买次数、邮件、回执与在途活动;迁移不发放任何免费宝箱。`config.rewards`定义三种箱(`id/name/kind/description/contents/choices`)、每日箱ID与首通掉落表。当前ID0「绿野补给箱」固定内容金币60/糖果30/弹珠2,同时作为每日宝箱;ID1「伙伴碎片自选箱」为自选箱,五个选项各对应一种伙伴碎片×10;ID2「溪口大礼箱」固定内容金币150/糖果80/弹珠5/捕捉券1。首通掉落:1-1→箱0、1-3→箱1、1-5→箱2,每次首通只掉一个,重复挑战不再掉落。
|
||
|
||
箱内容使用`{coins,candy,balls,tickets,shardsPet,shards}`:`shards>0`时`shardsPet`为伙伴ID并把碎片计入`state.shards`,否则`shardsPet=-1`且`shards=0`。公共响应附`rewards:{dailyClaimed}`,与`state.chestDailyClaimed`一致。
|
||
|
||
- `chest.claimDaily`只把每日箱加入库存(回执`{id,chest,day,count:1}`),不发放箱内容;每服务器日一次,与商店限购共用UTC+8日切,时钟回退不刷新。
|
||
- `chest.open`回执`{id,chest,kind,choice?,remaining,chestReward}`,`remaining`为同类箱剩余数量;固定箱忽略`choice`,自选箱缺少或越界的选择返回`invalid_choice`。扣箱、发奖(含碎片)与操作日志同事务提交;失败整体回滚,同一请求重放返回原回执,不重复发放。
|
||
|
||
宝箱业务错误为`invalid_chest/chest_missing/chest_claimed/invalid_choice`,沿用鉴权、revision与在途活动互斥。`battle.finish`首通回执在掉落时新增`chest`字段表示获得的箱ID。客户端校验回执ID、箱ID与箱内容;JsonUtility会把缺失的嵌套奖励物化为全零对象,因此打开的箱必须包含至少一项非零奖励,每日箱回执则不得携带非零奖励。
|
||
|
||
阵容与关卡扩展(2026-10-20):8→9只把`stages/shards`扩到10项、`clearedStages`扩到20项,新伙伴全部未拥有、新关卡全部未通关,不发放伙伴、关卡首通奖或宝箱。`config.pets`现为10只:岩团团、火喵、芽芽、闪闪雀、泡泡龟、岩甲犀(岩·控制·减速)、烈焰狮(火·输出·双目标)、藤蔓灵(草·支援·治疗)、雷鸣豹(电·输出·双目标)、潮汐鲸(水·守护·护盾)。捕捉池权重十项等权10%,教学仍固定芽芽;自选箱十个选项各对应一种伙伴碎片×10。
|
||
|
||
`config.stages`现为20关,每关新增`chapter`:1-1~1-10为「绿野初程」,2-1~2-10为「溪谷守望」。每关仍为三波九个敌人、固定五类敌人;`hpScale`由1.0递增到4.0、`attackScale`由1.0递增到2.35,1-5起三路线。首通掉落扩展为1-1→箱0、1-3→箱1、1-5→箱2、1-10→箱1、1-15→箱0、1-20→箱2,仍为每关首通只掉一次。
|
||
|
||
`autoDeploy`改为按配置选择:前排优先射程最短的已拥有伙伴,后排优先射程最长的,按路线依次填充,不再依赖固定的五个伙伴ID。`camp.interact`与`evolve`的伙伴边界改为`config.pets`长度。
|
||
|
||
成功与业务拒绝均返回 HTTP 200,业务拒绝 `ok:false`,同时给出最新快照。常见错误:`stale_revision`、`pending_activity`、`insufficient_balls/candy/tickets`、`evolve_requirements`、`invalid_formation`、`activity_missing/not_ready`、`goal_incomplete/claimed`、`level_cap`、`stage_locked`、`invalid_stage`。格式错误400、鉴权失败401、请求号冲突409、维护503和存储失败500不确认业务操作;客户端保留未确认请求重试。
|
||
|
||
SQLite `game_states` 与 `game_operations` 在同一 `BEGIN IMMEDIATE` 事务中写入。请求号按玩家唯一,扣费与日志同时提交;写入失败整体回滚。客户端请求队列只保存原始操作、不保存 token,按端点与玩家隔离;401 会重新登录同一游客身份,再提交同一请求。客户端收到完整有效快照才清理请求队列,失败时禁用业务操作并提供重试。禁止回退到 G0 本地钱包。
|
||
|
||
营地业务错误包括 `camp_build_locked/camp_rest_locked`、`invalid_decoration/invalid_camp_slot/invalid_pet`、`camp_slot_occupied/camp_slot_empty`、`insufficient_coins`;缺失/错误类型字段返回已记录的 `invalid_input` 业务拒绝。
|
||
|
||
任务/签到业务错误包括 `daily_expired`、`task_incomplete`、`reward_claimed`、`signin_locked`、`invalid_task/invalid_signin`;同样持久化业务拒绝,条件变化后须新建请求号。领取沿用在途活动互斥、鉴权与修订号规则。
|
||
|
||
商店业务错误包括 `shop_expired`、`shop_limit`、`goods_locked`、`invalid_goods`、`insufficient_coins`,同样沿用修订号、在途活动互斥与持久业务拒绝;参数类型错误返回`invalid_input`业务拒绝。条件变化后重新确认并使用新请求号。
|
||
|
||
弹珠只能按服务端轨迹播放;奖励不接受客户端数值。战斗在开始时由服务端按冻结快照计算结果,完成请求不接受客户端 `won`;最早结算时间为模拟时长的一半(最高2倍速)。暂停或重进时可以重新播放已授权挑战,不再创建新挑战。此时间门槛不验证逐帧操作,当前仅适用没有战中决策的教学自动战斗;加入技能输入或新战斗规则时须扩展验证协议。
|
||
|
||
## WebSocket
|
||
|
||
```
|
||
/ws?pid={pid}&token={token}
|
||
```
|
||
|
||
- 无 token / token 无效 → HTTP 401
|
||
- 应用层:`{"op":"hello"}` → `{"op":"hello_ack","ok":true}`;`{"op":"ping"}` → `{"op":"pong"}`
|
||
- 维护:若 `data/maintenance.json` 中 `enabled=true`,升级后立刻下发 `{"op":"maintenance","message":"..."}` 并断开
|
||
- 协议层 Ping/Pong 由服务端应答
|
||
|
||
## 维护开关
|
||
|
||
文件:`AIServer/data/maintenance.json`(与 `--db` 同目录,可用 `--maintenance` 覆盖)
|
||
|
||
```json
|
||
{ "enabled": true, "message": "今晚 2:00-4:00 维护" }
|
||
```
|
||
|
||
| GET | `/maintenance` | — | `{enabled,message}` |
|
||
| GET | `/health` | — | 增加 `maintenance` 字段 |
|
||
|
||
CDN `version.json.notice` 也会在客户端启动时弹公告(不依赖游戏服)。
|
||
|
||
## 客户端
|
||
|
||
- `HotUpdate.Net.AuthApi` / `GameNet` / `WsClient`
|
||
- Boot:`HttpBase` + `WsUrlTemplate`
|
||
- 菜单:**AIGame/Boot/Server/Local AIServer :8080** 或 Production
|
||
- 编辑器自动 `guest_{deviceId}` 账号
|