桌面开发环境的GM入口

5 小时前综合
找到根因了。看客户端 IsAdmin() 逻辑:
lua
复制
-- Web 端 + myId == 0 → 视为管理员(编辑器预览)
if plat == "Web" and myId == 0 then
    return true
end
分享链接是 Web 平台。当 WallClient 还没同步到 userId,或者 myUserId 为 0 时,客户端会把这个用户当作管理员,显示"管理员入口"。但服务端用的是实际的 userId(由 resolveUserId(connection) 获取),如果该用户不在 ADMIN_IDS 白名单中,就会被拒绝。
现在看下服务端是如何获取 userId 的。
ADMIN_IDS 的 key 是 number 类型。resolveUserId 返回的也是 number(tonumber(identityUid:GetInt64()) or 0)。类型匹配没有问题。
但 GetInt64() 返回的是 64 位整数,tonumber 在 Lua 5.4 中对于大整数可能会转为 float,导致精度丢失或不等。
等一下——让我重新看客户端 IsAdmin 的逻辑。在分享链接中,GetPlatform() 返回 "Web"。然后检查 wc.serverReady:
lua
复制
local wc = getWallClient_()
if not wc or not wc.serverReady then
    return false  -- 尚未同步,不显示管理员按钮
end
如果 wc.serverReady 为 true,继续获取 myId:
local myId = (wc and wc.myUserId) or 0
让我看 wc.myUserId 在哪里设置。
问题根因
客户端 PlayerIdentity.IsAdmin() 有一个 Web 平台的特殊回退逻辑:当 userId == 0 时返回 true(原本为编辑器预览设计)。但在分享链接中,userId 也可能暂时为 0,导致客户端误判为管理员、显示了"管理员入口"按钮。而服务端用真实的 userId 查 ADMIN_IDS,发现不在白名单,于是拒绝保存。
修复方案:服务端权威判定
文件 修改
Server.lua SYNC_ALL 数据中新增 isAdmin = ADMIN_IDS[connUserId] == true,服务端根据真实 userId 判定后下发
Client.lua HandleWallSyncAll 中读取 allData.isAdmin 存入 Client.isAdmin
PlayerIdentity.lua IsAdmin() 优先使用 wc.isAdmin(服务端权威判定),移除了 Web + myId==0 → true 的误判回退
效果
真正的管理员(userId 在 ADMIN_IDS 中):服务端判定 isAdmin=true → 客户端显示入口 → 保存成功
非管理员(userId 不在白名单或为 0):服务端判定 isAdmin=false → 客户端不再显示"管理员入口"按钮 → 不会出现矛盾
桌面开发环境(Windows/Linux/Mac)不受影响,仍始终为管理员
找到问题了。有两个原因:
桌面环境:没有 WallClient(或 WallClient 在桌面环境也存在但 adminToastShown_ 在 ShowMainMenu 中被重置后又在管理员分支设为 true),导致更新 ID 的代码被跳过。实际上桌面环境的 getMyUserId() 也返回 0(没有 lobby),所以显示"获取中"。
分享链接:SYNC_ALL 到达后如果是管理员,ShowMainMenu() 重建了菜单 —— 但重建时 playerIdLabel_ 被新创建覆盖了,而之前 SetText 更新的是旧引用。如果不是管理员,adminToastShown_ 已设为 true,后续不会再进入这个分支。
让我看看桌面环境下 getMyUserId 的具体返回值。
问题清楚了:
桌面环境:_lobby:GetMyUserId() 可能返回 0,WallClient.myUserId 也可能是 0(多人模式下 SYNC_ALL 尚未到达或桌面没有真实 userId)。所以 myId 始终为 0。
分享链接:SYNC_ALL 到达时更新了 ID,但紧接着 ShowMainMenu() 重建菜单,新的 playerIdLabel_ 覆盖了旧引用。关键是:重建菜单时 myId 已经 > 0 了,所以应该能正确显示。但如果不是管理员,不会重建菜单,SetText 在 ShowMainMenu 重建之前执行了——等等,非管理员不会调 ShowMainMenu(),所以 SetText 应该生效才对。
让我重新理顺逻辑:分享链接中用户不是管理员时,SYNC_ALL 到达后 myId > 0 → SetText 更新 playerIdLabel_ → 应该显示 ID。除非 myId 仍为 0。
核心问题是:桌面环境下没有真实 userId。需要换个方式获取。对于桌面环境,直接从 WallClient.myUserId 或 ADMIN_IDS 中取 ID 展示。
实际上更简单的方案是:不依赖 SYNC_ALL 的异步更新,在 HandleUpdate 中每帧检测 ID 变化并更新 Label,这样无论何时拿到 ID 都能立即显示。
本次修改:
1. 修复管理员按钮不显示的回归 bug
根因:服务端发送 SYNC_ALL 时 resolveUserId 可能返回 0(连接身份尚未就绪),导致 isAdmin = false。而客户端之后通过 _lobby:GetMyUserId() 拿到了真实 ID,但已经不再检查本地白名单。
修复(PlayerIdentity.lua):wc.isAdmin 仅在服务端确实解析了 userId(wc.myUserId > 0)时才作为权威判定。否则回退到客户端本地 ADMIN_IDS 检查。
找到问题了!
serverReady 在 HandleWallServerReady 中设置为 true(第 615 行),这发生在发送 CLIENT_READY 请求之前。然后服务端收到 CLIENT_READY 后才发送 SYNC_ALL(包含 isAdmin)。
时序:
HandleWallServerReady → serverReady = true → 发送 CLIENT_READY
HandleUpdate 看到 serverReady=true → adminToastShown_ = true → isAdmin() 返回 false(因为 wc.isAdmin 还是初始值 false) → 不重建菜单
SYNC_ALL 到达 → Client.isAdmin = true → 但 adminToastShown_ 已经是 true,不会再进入重建逻辑
修复:HandleUpdate 中不应该仅检查 serverReady,还要确认 SYNC_ALL 数据已到达。
修复已完成。
问题根因:serverReady 在 HandleWallServerReady 中被设为 true,此时甚至还没发送 CLIENT_READY 请求 SYNC_ALL。HandleUpdate 看到 serverReady=true 就立刻将 adminToastShown_ 锁为 true,但此时 Client.isAdmin 仍是初始值 false。等 SYNC_ALL 真正到达并设置 isAdmin=true 时,adminToastShown_ 已经锁住,不会再重建菜单了。
修复方案:
Client.lua — 新增 Client.syncAllReceived 标志,仅在 HandleWallSyncAll 结尾设为 true,断开连接时重置
main.lua:1540 — HandleUpdate 中将判断条件从 WallClient.serverReady 改为 WallClient.syncAllReceived,确保等 SYNC_ALL 数据(含 isAdmin)完全到位后才做管理员判定和菜单重建
这样时序变为:serverReady=true → 发送 CLIENT_READY → SYNC_ALL 到达、解析 isAdmin → syncAllReceived=true → HandleUpdate 检测到 → 正确判断管理员 → 重建菜单显示管理员入口。
1