21 KiB
游戏服协议
生产
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。
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 联调通过,未部署生产服。
{"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 覆盖)
{ "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}账号