Files
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

127 lines
4.7 KiB
Go

package cerashop
import (
"errors"
"math"
"testing"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
)
// 代币券 / 欢乐代币券必须能落成钱包余额,而不是背包里的一件"材料"。
// 这里守住券的道具号识别与入账语义(溢出、全有或全无)。
func TestVoucherKindOfRecognizesBothVouchers(t *testing.T) {
cases := []struct {
itemID int64
want VoucherKind
}{
{2681762, VoucherTokenCera},
{2681917, VoucherHappyTokenCera},
{0, VoucherNone},
{2681762 - 1, VoucherNone},
{2681762 + 1, VoucherNone},
{2681917 + 1, VoucherNone},
{100050, VoucherNone},
}
for _, testCase := range cases {
if got := VoucherKindOf(testCase.itemID); got != testCase.want {
t.Fatalf("VoucherKindOf(%d)=%v want=%v", testCase.itemID, got, testCase.want)
}
if IsVoucher(testCase.itemID) != (testCase.want != VoucherNone) {
t.Fatalf("IsVoucher(%d) 与 VoucherKindOf 不一致", testCase.itemID)
}
}
if VoucherItemID(VoucherTokenCera) != 2681762 || VoucherItemID(VoucherHappyTokenCera) != 2681917 ||
VoucherItemID(VoucherNone) != 0 {
t.Fatal("VoucherItemID 与 VoucherKindOf 不是同一套映射")
}
if ids := VoucherItemIDs(); len(ids) != 2 || !IsVoucher(ids[0]) || !IsVoucher(ids[1]) {
t.Fatalf("VoucherItemIDs=%v", ids)
}
}
func TestCreditVoucherCountsAddsBothWallets(t *testing.T) {
account := &dnfrepo.AccountRecord{
AccountID: "account-1",
Metadata: map[string]string{"account_cera": "700", "account_token_cera": "5"},
}
wallet, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: 100, HappyTokenCera: 250})
if err != nil {
t.Fatal(err)
}
if wallet.Cera != 700 || wallet.TokenCera != 105 || wallet.HappyTokenCera != 250 {
t.Fatalf("钱包=%+v want cera=700 token=105 happy=250", wallet)
}
// 点券字段绝不能被券的入账碰到。
if got := Balance(*account); got != 700 {
t.Fatalf("点券被改动了: %d", got)
}
}
// 数量为 0 时是空操作,不该写任何键(否则会给账号留下无意义的新元数据)。
func TestCreditVoucherCountsIgnoresZeroAndNegative(t *testing.T) {
account := &dnfrepo.AccountRecord{AccountID: "account-1"}
wallet, err := CreditVoucherCounts(account, VoucherCounts{})
if err != nil || wallet.TokenCera != 0 || wallet.HappyTokenCera != 0 {
t.Fatalf("空入账 err=%v wallet=%+v", err, wallet)
}
if len(account.Metadata) != 0 {
t.Fatalf("空入账不该写元数据: %v", account.Metadata)
}
// 负数只可能来自上游 bug,按 0 处理而不是把余额扣下去。
if _, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: -50, HappyTokenCera: -1}); err != nil {
t.Fatal(err)
}
if wallet := WalletBalance(*account); wallet.TokenCera != 0 || wallet.HappyTokenCera != 0 {
t.Fatalf("负数不该扣余额: %+v", wallet)
}
}
// 溢出必须整笔拒绝,且**一个字段都不写**(全有或全无)——否则会出现
// "代币券溢出失败、欢乐代币券却已经进账"的半截状态。
func TestCreditVoucherCountsRefusesOverflowAtomically(t *testing.T) {
account := &dnfrepo.AccountRecord{
AccountID: "account-1",
Metadata: map[string]string{"account_token_cera": "2147483647"},
}
_, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: 1, HappyTokenCera: 100})
if !errors.Is(err, ErrWalletOverflow) {
t.Fatalf("代币券溢出应返回 ErrWalletOverflow,实际=%v", err)
}
if wallet := WalletBalance(*account); wallet.TokenCera != math.MaxInt32 || wallet.HappyTokenCera != 0 {
t.Fatalf("溢出时不该写任何字段: %+v", wallet)
}
// 边界:刚好填满到上限是允许的(不是溢出)。
account = &dnfrepo.AccountRecord{AccountID: "account-1"}
wallet, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: math.MaxInt32})
if err != nil || wallet.TokenCera != math.MaxInt32 {
t.Fatalf("刚好到上限应当允许: err=%v wallet=%+v", err, wallet)
}
// 再 +1 就溢出。
if _, err := CreditVoucherCounts(account, VoucherCounts{TokenCera: 1}); !errors.Is(err, ErrWalletOverflow) {
t.Fatalf("超过上限应溢出,实际=%v", err)
}
}
// nil 账号必须报错而不是 panic:调用方多为事务回调,panic 会连累整条连接。
func TestCreditVoucherCountsRejectsNilAccount(t *testing.T) {
if _, err := CreditVoucherCounts(nil, VoucherCounts{TokenCera: 1}); err == nil {
t.Fatal("nil 账号应报错")
}
}
func TestVoucherCountsAddAndIsZero(t *testing.T) {
if !(VoucherCounts{}).IsZero() {
t.Fatal("空 counts 应当是 zero")
}
if (VoucherCounts{TokenCera: 1}).IsZero() {
t.Fatal("非空 counts 不该是 zero")
}
sum := VoucherCounts{TokenCera: 3}.Add(VoucherCounts{TokenCera: 4, HappyTokenCera: 5})
if sum.TokenCera != 7 || sum.HappyTokenCera != 5 {
t.Fatalf("Add=%+v", sum)
}
}