怪物赠礼(Grant ID)
当 NPC 把一只怪物交给玩家 —— 实验室里的初始伙伴、路上的赠礼、复活的化石 —— 单人游戏只需把它加入队伍。在线游戏不能这样做:能给自己任意怪物的客户端,也能给自己一只传说怪物。在 MCE Online 中,由服务器铸造赠礼,客户端只是请求它。
适用于 Pro 和 Source 层级。
赠礼在线上如何运作
每个赠礼背后的节点都是 GiveMonsters(CommandGraph,Monsters 类别)。初始伙伴和“NPC 赠礼”是同一个节点,所以两者只有一套机制。
- 玩家与 NPC 交谈,图到达
GiveMonsters节点。 - 在线时,该节点不会添加怪物。它通过
OnlineCommandBridge.RequestGiveMonsterAsync(grantId, ...)向服务器请求,并用GrantId指明站点。 - 服务器在自己的数据(
monster_grant_data)中查找该站点,铸造它记录在案的物种、等级和数量。客户端在请求中发送的任何物种都会被忽略并记入日志。 - 服务器记录该角色已领取过这个站点(
monster_grant_claims,以角色和赠礼 ID 为键),因此赠礼每个角色只能领取一次:一个可以反复交谈的 NPC 不是怪物工厂。 - 客户端收到铸造出的怪物,并使用离线路径所用的同样对话框来显示它。
如果服务器不批准,该节点不会在本地授予任何东西 —— 那样会重新打开自我赠礼的漏洞 —— 并显示“grant failed”对话框。离线时则完全没有变化。
GrantId
每个赠礼站点都需要自己的 GrantId:一个存储在 GiveMonsters 节点上的不透明字符串(它在 Inspector 中是隐藏的,你不需要输入)。服务器会点名拒绝空 ID(grantId is required),因此没有 ID 的站点在线上不会给出赠礼 —— 它是失败时关闭的。
这些 ID 具有以下特性:
- 按站点,而非按节点类型。 两个互相复制的节点共享节点的创作 GUID,但给出不同的东西;盖章的 ID 是节点的 GUID 加上节点所在位置(资产的 GUID 和属性路径)的短哈希,因此每个 站点都是独一无二的。
- 稳定。 哈希使用资产的 GUID 而不是它的路径,所以移动或重命名文件不会改变它。
- 不透明。 ID 不含
#、/或文件扩展名,因此节点在运行时总能处理它。 - 绝不覆盖。 盖章只会填充空的 ID。已经存在的 ID,以及服务器针对它的种子数据,永远不会改变。
盖章与导出(编辑器侧)
MonsterGrantSeedExporter(位于 MCE.Online.Editor 程序集)完成两项工作:
- 它扫描项目的预制体和场景中的
GiveMonsters节点。场景会先按文本过滤(只有当场景的 YAML 包含节点的类标记时才会打开它),所以扫描不会打开每一个场景。 - 开启盖章后,它填充缺失的
GrantId,并写出种子:SeedData/monster-grant-seed.json(每个站点给出什么)和SeedData/monster-grant-seed.report.txt(看到的站点和跳过的站点,并附原因)。
导出器的入口:
| 入口 | 用途 |
|---|---|
MonsterGrantSeedExporter.Export(folder) | 为缺失的 ID 盖章,然后写出种子和报告 |
MonsterGrantSeedExporter.Scan(stampMissingIds, persist) | 扫描;可选择盖章,可选择保存到磁盘。单独的 Scan() 不读取也不写入任何内容 |
BatchExporters.ExportMonsterGrants | 批处理模式:-executeMethod OpenMon.MCE.Online.Editor.BatchExporters.ExportMonsterGrants |
把种子发送到服务器是服务器对服务器的,与其他种子相同:mce_monster_grant_seed RPC 会拒绝任何携带用户会话的调用,并需要运行时 HTTP 密钥(关于该密钥为何绝不进入 Unity,参见物种播种)。
GiveMonsters 发放一个固定的赠礼(或一个随机的)。提供选择的节点 —— GiveMonstersBasedOnChosenItem,就像化石复活师那样 —— 会以 choice: true 导出:服务器根据玩家选择的索引推导出所选选项需要的道具,因此客户端无法通过声称花掉了便宜的那个来复活昂贵的化石。
导入的游戏
从卡带或 Essentials 项目导入的游戏充满了赠礼节点:实验室的三 只初始伙伴、NPC 给的宝可梦、化石。导入器会为你完成盖章。
NPC 导入器(HeartGold / SoulSilver、FireRed)完成之后,GrantsEnLoImportado.Estampar() 会在开启盖章和持久化的情况下运行导出器的扫描 —— 前提是在线层是项目的一部分;没有它就无章可盖。随后它用独立的计数来检查自己的工作:读取 Assets/ImportedGames 下的每个场景,并统计空的 GrantId 值,包括场景以预制体实例的覆盖形式存储的那些(实验室的初始伙伴正是如此)。如果还剩下任何一个,它就会抛出异常,因此导入绝不会在留下服务器会拒绝的赠礼时“成功”结束:
[Importador] GrantId de los regalos: estampados=3 yaEstaban=0 quedanSinId=0
(estampados:本次盖章的数量;yaEstaban:已有 ID 的数量;quedanSinId:仍为空的数量,必须为 0。)
之后,导出赠礼种子(见上文)并发送到服务器。与其他种子 RPC 一样,mce_monster_grant_seed 会拒绝任何携带用户会话的调用:只有带运行时 HTTP 密钥的服务器对服务器调用才能写入它。
参考
| 类型 | 位置 | 作用 |
|---|---|---|
GiveMonsters | OpenMon.MCE.Actors.CommandGraph.Monsters | 节点;携带 GrantId,在线时向服务器请求 |
OnlineCommandBridge.RequestGiveMonsterAsync | MCE.Online.Runtime | 请求(grantId,可选的选择索引) |
MonsterGrantSeedExporter | OpenMon.MCE.Online.Editor | 扫描、盖章并导出种子 |
GrantsEnLoImportado | OpenMon.MCE.EssentialsImporter.Editor | 导入后为 ID 盖章并验证 |
mce_monster_grant_seed | Nakama RPC | 服务器对服务器:存储站点 |