和绝大多数游戏一样,鸣潮并未提供官方 API 用于导出抽卡数据。其抽卡历史的展示方式是:当用户点击“抽卡历史”按钮时,游戏会生成一个临时链接,并通过游戏内的浏览器打开该链接,以此来呈现抽卡历史内容。我们可以通过抓包或者从日志文件中获取到这个以 aki-gm-resources.aki-game.com/aki/gacha/index.html 开头的链接。
该页面会通过 POST 的方式向 API gmserver-api.aki-game2.com/gacha/record/query 获取每个卡池的抽取历史。这个请求中包含了玩家 ID(playerId)、服务器 ID(serverId)、卡池 ID(gachaId)等关键信息,这些都可以从 URL 中的参数提取:
继脱坑了原神、星穹铁道以及绝区零后,最近沉迷上了鸣潮 (没错就是 「xxx 只要 xxx 就好了,而 xxx 要考虑的事情就很多了」的那个游戏)。聊到这类二次元游戏,万恶的抽卡便是不得不品的一环;有抽卡就必定会有角色武器的歪与不歪,从而催生出了一堆例如「xxx 小助手」、「xxx 工坊」之类的第三方小程序,它们的其中一个重要的功能便是抽卡分析。
事情的起因是,某位朋友想看看我的星穹铁道抽卡记录,但是打开之前使用的小程序发现以前存进去的抽卡记录全都不见了。尝试下载星铁云游戏重新导入抽卡链接恢复数据,但是折腾了半天,小程序始终提示抽卡地址错误,最终没能找回历史记录。后来一直对这件事情耿耿于怀,便想自己做一个抽卡记录分析的工具,将数据保存在自己手上,于是便写了这个抽卡分析工具。
Astrionyx 是一个基于 Next.js 的 Web 应用,Astrionyx 目前是一个前端 Next.js 后端 Go 项目,支持分析不同类型的卡池记录,可以手动导入或者更新数据,也可以通过 API 导入数据。除了鸣潮,现在还顺带支持了原神、星穹铁道、绝区零、明日方舟:终末地、异环等游戏。项目前端可以直接丢到 Vercel / EdgeOne Pages上托管,后端则是用 Docker 部署到自己服务器,部署起来非常非常的方便。
在开发 Astrionyx 的过程中,碰到了不少有意思的问题,如果你也想自己写一个这样的应用,希望对你有所帮助。
数据的导入与更新
数据导入
和绝大多数游戏一样,鸣潮并未提供官方 API 用于导出抽卡数据。其抽卡历史的展示方式是:当用户点击“抽卡历史”按钮时,游戏会生成一个临时链接,并通过游戏内的浏览器打开该链接,以此来呈现抽卡历史内容。我们可以通过抓包或者从日志文件中获取到这个以 aki-gm-resources.aki-game.com/aki/gacha/index.html 开头的链接。
该页面会通过 POST 的方式向 API gmserver-api.aki-game2.com/gacha/record/query 获取每个卡池的抽取历史。这个请求中包含了玩家 ID(playerId)、服务器 ID(serverId)、卡池 ID(gachaId)等关键信息,这些都可以从 URL 中的参数提取:
其中,卡池类型参数 cardPoolType 取值为 [1, 13],具体映射如下:
创建一个后端 API ,转发到官方 API 获取每个卡池的抽卡历史即可。
数据更新
在鸣潮等游戏中,抽卡会存在「十连抽」的情况。
由图可以看到,「十连抽」会出现获取到两件相同物品的情况,即两个记录具有包括抽取时间(十连抽中,所有物品都是在同一个时间点生成)在内的完全相同的属性,通过 Json 获取到的结果就像这样:
这会导致我们在后续更新抽卡记录的时候无法确定哪些记录已经导入过,从而影响统计数据的准确性。所以我们需要构建一个即使在相同时间下依然能区分两件相同物品的编号。
我们可以通过 时间戳 + 卡池类型 ID + 抽卡序号 来确定这个唯一编号。其中,抽卡序号表示:从 1 开始计数,在相同时间戳下的第几次抽卡。由此,可以将上面的两个相同的武器设置 uniqueId,分别为 1748454456000100001 和 1748454456000100005。
这样即使导入的数据与原有的数据存在重合等情况,依然可以正常识别新增的抽卡记录并更新数据库。
概率数据计算
页面的统计概览中,使用 ECharts 库实现了抽卡概率分析折线图作为组件背景,用来展示抽卡系统的概率分布。折线图的数据通过两个主要函数计算:
理论概率计算
根据 B 站 UP 主一棵平衡树撰写的 鸣潮抽卡系统简析,鸣潮抽卡概率符合下面模型,其中
i为抽卡次数:P_5[i] = \begin{cases} 0.008 & (i < 66) \\ 0.008 + 0.04 \cdot (i - 65) & (66 \leq i < 71) \\ 0.208 + 0.08 \cdot (i - 70) & (71 \leq i < 76) \\ 0.608 + 0.1 \cdot (i - 75) & (76 \leq i < 79) \\ 1 & (i = 79) \end{cases}以此构建一个理论概率函数 calculateTheoreticalProbability:
实际概率计算
由于工具主要是个人使用,抽卡数据样本量较小,某些抽数位置可能没有任何数据。如果直接使用频率估计,会导致概率曲线波动极大,出现 0% 或者 100% 这样的极端值。
为了避免出现这种情况,我们可以引入 贝叶斯平滑 来实现更好的效果。
P_{\text{posterior}} = \frac{k_i + S \cdot P_{\text{prior}}}{n_i + S}其中,
k_i:在第i抽位置观察到的 5 星数量、n_i:在第i抽位置的总抽数、P_{\text{prior}}:理论概率、S:平滑因子其行为如下:
如果
n_i较小(即数据量较小时),P_{\text{posterior}}会更趋近于理论概率P_{\text{prior}}如果
n_i较大(即数据量较大时),P_{\text{posterior}}会更趋近于实际频率\frac{k_i}{n_i}优化后的效果如下:
实现代码如下:
自动部署
目前该项目通过工作流+阿里云容器镜像服务来实现自动部署
以下自动部署方式已过时仅保留用作记录:
上文提到过,Astrionyx 同时部署在 vercel 以及自己的服务器上,所以每次修改完项目 push 到仓库的时候,都需要在服务器上 pull 以及 build,非常麻烦。这个主题的作者写过一个将主题自动构建并且部署到远程服务器的工作流,将其稍作修改后用到了 Astrionyx 上。
但是使用过程中发现,GitHub 到服务器之间的网络连接质量非常差,导致每次将构建好的包传输到服务器都需要花费大量时间(平均 20 min+)。恰好的是,Cloudflare 家的 R2 对象存储有每月 10 GB的免费存储额度以及免下载费用,境内的下载质量也算不错,可以作为「中转站」来解决这个问题。
结合对象存储的主要工作流如下,给上传和下载都加上了 5 次重试(单次下载仍然可能因为网络波动导致下载失败,实测 5 次重试能解决绝大部分问题)
通过这套工作流,每次推送代码到主分支时,Astrionyx 都会自动构建并部署到服务器。而通过 R2 对象存储作为中转站,能极大缩短部署时间(整套工作流从 20min 降低到 4 min 内),节省生命。
That's all🎉.