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

120 lines
4.3 KiB
Go

package itemdailylimit
import (
"errors"
"testing"
"time"
dnffatigue "longheng.io/server/internal/modules/dnf/fatigue"
"longheng.io/server/internal/modules/dnf/premium"
)
func TestConsumeWithoutDeclaredLimitTouchesNothing(t *testing.T) {
stats := map[string]int64{}
for _, limit := range []int64{0, -1} {
used, err := Consume(stats, 490003149, limit, time.Now().UTC(), 90)
if err != nil || used != 0 {
t.Fatalf("limit=%d used=%d err=%v", limit, used, err)
}
}
if len(stats) != 0 {
t.Fatalf("没有 [total usable count] 的道具不应该写入任何东西: %+v", stats)
}
}
func TestConsumeEnforcesTheDeclaredCap(t *testing.T) {
now := time.Date(2026, 9, 21, 4, 0, 0, 0, time.UTC)
stats := map[string]int64{dnffatigue.ItemUsageDayStatKey: premium.ServiceDayID(now)}
const itemID, limit = int64(10153501), int64(3)
for want := int64(1); want <= limit; want++ {
used, err := Consume(stats, itemID, limit, now, 90)
if err != nil || used != want {
t.Fatalf("第 %d 次 used=%d err=%v", want, used, err)
}
}
used, err := Consume(stats, itemID, limit, now, 90)
var reached LimitReachedError
if !errors.Is(err, ErrLimitReached) || !errors.As(err, &reached) {
t.Fatalf("超过上限应返回可识别的拒绝: %v", err)
}
if reached.ItemID != itemID || reached.Used != limit || reached.Limit != limit {
t.Fatalf("拒绝里带的数字不对: %+v", reached)
}
if used != limit || stats[Key(itemID)] != limit {
t.Fatalf("被拒的一次不得改动计数: used=%d stats=%+v", used, stats)
}
}
func TestConsumeAppliesTheServiceDayBoundary(t *testing.T) {
now := time.Date(2026, 9, 21, 4, 0, 0, 0, time.UTC)
const itemID, limit = int64(490003149), int64(2)
stats := map[string]int64{
// 昨天的计数已经打满,今天必须从 0 开始。
dnffatigue.ItemUsageDayStatKey: premium.ServiceDayID(now.Add(-24 * time.Hour)),
Key(itemID): limit,
Key(490003150): 5,
}
used, err := Consume(stats, itemID, limit, now, 90)
if err != nil || used != 1 {
t.Fatalf("跨 06:00 后第一次应该从 0 开始: used=%d err=%v stats=%+v", used, err, stats)
}
if stats[Key(490003150)] != 0 {
t.Fatalf("同一天边界必须清掉其它道具的计数: %+v", stats)
}
if stats[dnffatigue.ItemUsageDayStatKey] != premium.ServiceDayID(now) {
t.Fatalf("服务日没有推进: %+v", stats)
}
}
func TestConsumeReadsTheLegacyFatiguePotionSpelling(t *testing.T) {
now := time.Date(2026, 9, 21, 4, 0, 0, 0, time.UTC)
const itemID, limit = int64(490702338), int64(3)
stats := map[string]int64{
dnffatigue.ItemUsageDayStatKey: premium.ServiceDayID(now),
// 旧键属于疲劳药那一天的服务日,两个日键都要在,否则 ReconcileDailyStats
// 会先把它当成"没初始化过的一天"清掉(既有语义,不是本模块引入的)。
dnffatigue.PotionUsageDayStatKey: premium.ServiceDayID(now),
// 通用键出现之前写下的旧计数不能被当成 0。
LegacyKey(itemID): 2,
}
if got := Used(stats, itemID); got != 2 {
t.Fatalf("旧键读数=%d want 2", got)
}
used, err := Consume(stats, itemID, limit, now, 90)
if err != nil || used != 3 {
t.Fatalf("应从旧计数继续: used=%d err=%v", used, err)
}
if _, err := Consume(stats, itemID, limit, now, 90); !errors.Is(err, ErrLimitReached) {
t.Fatalf("旧键 + 新键合计到上限后必须拒绝: %v", err)
}
}
func TestReleaseGivesBackASingleReservedUse(t *testing.T) {
now := time.Date(2026, 9, 21, 4, 0, 0, 0, time.UTC)
const itemID, limit = int64(490003149), int64(2)
stats := map[string]int64{dnffatigue.ItemUsageDayStatKey: premium.ServiceDayID(now)}
if _, err := Consume(stats, itemID, limit, now, 90); err != nil {
t.Fatal(err)
}
if got := Release(stats, itemID); got != 0 {
t.Fatalf("回滚后计数=%d want 0", got)
}
// 没有可回滚的次数时不得把它做成负数。
if got := Release(stats, itemID); got != 0 || stats[Key(itemID)] != 0 {
t.Fatalf("重复回滚破坏了计数: got=%d stats=%+v", got, stats)
}
}
func TestUsedToleratesHostileStats(t *testing.T) {
if got := Used(nil, 1001); got != 0 {
t.Fatalf("nil stats=%d", got)
}
if got := Used(map[string]int64{Key(1001): -5}, 1001); got != 0 {
t.Fatalf("负数计数=%d", got)
}
if got := Used(map[string]int64{}, 0); got != 0 {
t.Fatalf("零道具 id=%d", got)
}
}