DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,119 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user