服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
54 lines
2.3 KiB
Go
54 lines
2.3 KiB
Go
package mail
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
// 这组用例钉住"锁定"这条拒绝在 op95 上到底回什么码,以及它**不能**被算成别的语义。
|
|
//
|
|
// 现场证据(2026-09-15,线上包 packet log,玩家在游戏里点领取那一刻):
|
|
//
|
|
// SEND kind=game-upper msg_id=95 body=0004
|
|
//
|
|
// 服务端确实回了 ackType 0x005F 的码 4,而客户端**没有画任何东西**。所以:
|
|
// - 码 4 在 op95 表里是可达的(老注释若说它渲染"背包已满"就与本机客户端不符),
|
|
// - 无论回哪个码,都不能再假设"客户端一定会自己画提示"。
|
|
//
|
|
// 这个用例只保证服务端这一侧稳定:锁定必须走锁定那条错误,且码值集中在一个 switch 里,
|
|
// 将来客户端码表反查清楚后只改一处。
|
|
func TestMailboxClaimErrorCodeKeepsLockedDistinct(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
err error
|
|
want byte
|
|
why string
|
|
}{
|
|
{"nil", nil, 0, "成功不回失败码"},
|
|
{"locked", ErrMailboxItemLocked, 4, "锁定走'放不进去'这一格(当前客户端不渲染,安全性不受影响)"},
|
|
{"locked wrapped", fmt.Errorf("%w: list=0 slot=65 item=2001", ErrMailboxItemLocked), 4, "带上文包装后仍要认出锁定"},
|
|
{"inventory full", ErrMailboxInventoryFull, 4, "背包放不下与锁定共用同一格"},
|
|
{"mail gone", ErrMailboxMailNotFound, 21, "信件不存在"},
|
|
{"already claimed", ErrMailboxAlreadyClaimed, 21, "已领取"},
|
|
{"expired", ErrMailboxExpired, 21, "已过期"},
|
|
{"gold", ErrMailboxGoldInsufficient, 10, "金币携带上限"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := mailboxClaimErrorCode(tc.err); got != tc.want {
|
|
t.Fatalf("code=%d want=%d (%s)", got, tc.want, tc.why)
|
|
}
|
|
})
|
|
}
|
|
// 锁定永远不能被折叠成"成功"或"0":静默成功会把锁定附件真的发出去。
|
|
body := buildErrorAck(mailboxClaimErrorCode(fmt.Errorf("%w: x", ErrMailboxItemLocked)))
|
|
if len(body) != 2 || body[0] != 0x00 || body[1] == 0 {
|
|
t.Fatalf("锁定拒绝帧必须是非零失败码,实际 % X", body)
|
|
}
|
|
// 而且 must 不等于"没有错误"这条路径。
|
|
if errors.Is(ErrMailboxItemLocked, ErrMailboxInventoryFull) {
|
|
t.Fatal("锁定与背包已满是两个不同原因,不能互为别名")
|
|
}
|
|
}
|