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

112 lines
4.1 KiB
Go

package dnfbridge
import (
"context"
"testing"
"time"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
dnfweaknesspotion "longheng.io/server/internal/modules/dnf/weaknesspotion"
)
// 防虚弱药剂的效果是客户端本地的:活体抓包(2026-09-15 00:43)显示使用只以普通
// class-1/op44 上行到达,之后没有任何包携带该状态。所以服务端按道具家族记下持久
// 免疫窗口,放弃副本时据此豁免 —— 这条测试钉住"窗口内不惩罚、窗口外照常惩罚"。
func TestForcedReturnIsExemptedByAntiWeaknessPotionWindow(t *testing.T) {
ctx := context.Background()
service, session, repositories, _ := prepareCurrentWeaknessTest(t, 40, 0, 0, nil)
characterID := "5"
arm := func(offset time.Duration) {
t.Helper()
character, found, err := repositories.Character.Load(ctx, characterID)
if err != nil || !found {
t.Fatalf("load character err=%v found=%t", err, found)
}
character = dnfrepo.CloneCharacter(character)
if character.Stats == nil {
character.Stats = make(map[string]int64, 2)
}
character.Stats[dnfweaknesspotion.ImmuneUntilStatKey] = service.gameplayNow().Add(offset).Unix()
if err := repositories.Character.Save(ctx, character); err != nil {
t.Fatal(err)
}
}
// 生效中:不给惩罚,也不写任何东西。
arm(30 * time.Minute)
level, exemption, err := service.commitCurrentForcedReturnWeakness(session)
if err != nil {
t.Fatal(err)
}
if exemption != currentWeaknessExemptionPotItem {
t.Fatalf("exemption=%q want %q", exemption, currentWeaknessExemptionPotItem)
}
if level != 0 || currentWeaknessStoredLevel(t, repositories) != 0 {
t.Fatalf("生效期内不应写入惩罚: level=%d stored=%d", level, currentWeaknessStoredLevel(t, repositories))
}
// 已过期:照常按满额惩罚。
arm(-time.Minute)
level, exemption, err = service.commitCurrentForcedReturnWeakness(session)
if err != nil {
t.Fatal(err)
}
if exemption != currentWeaknessExemptionNone {
t.Fatalf("过期窗口不应豁免: exemption=%q", exemption)
}
if level != currentWeaknessForcedReturnLevel ||
currentWeaknessStoredLevel(t, repositories) != currentWeaknessForcedReturnLevel {
t.Fatalf("过期后应写入满额惩罚: level=%d stored=%d", level, currentWeaknessStoredLevel(t, repositories))
}
}
// 持有型(十周年防虚弱青石):材料栏里带着就已经生效,不需要使用,也没有窗口。
func TestForcedReturnIsExemptedWhileHoldingPassiveAntiWeaknessItem(t *testing.T) {
ctx := context.Background()
service, session, repositories, _ := prepareCurrentWeaknessTest(t, 40, 0, 0, nil)
const heldItemID = int64(490008656)
if !dnfweaknesspotion.HeldExempts(heldItemID) {
t.Fatalf("持有人型清单缺少 %d", heldItemID)
}
if err := repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{
CharacterID: "5",
Slots: map[string]dnfrepo.ItemStack{
"0:3": {ItemID: heldItemID, Count: 1},
},
}); err != nil {
t.Fatal(err)
}
level, exemption, err := service.commitCurrentForcedReturnWeakness(session)
if err != nil {
t.Fatal(err)
}
if exemption != currentWeaknessExemptionPotHeld {
t.Fatalf("exemption=%q want %q", exemption, currentWeaknessExemptionPotHeld)
}
if level != 0 || currentWeaknessStoredLevel(t, repositories) != 0 {
t.Fatalf("持有期内不应写入惩罚: level=%d stored=%d", level, currentWeaknessStoredLevel(t, repositories))
}
}
// 同一张表必须覆盖用户报告过的那些版本,并且时长各不相同。
func TestAntiWeaknessPotionFamilyCarriesPerItemDurations(t *testing.T) {
for itemID, wantSeconds := range map[int64]int64{
10087825: 1800, // 防虚弱药剂(本次报告的那一件)
490003621: 600,
490008213: 900,
490007908: 3600,
10005909: 1800, // 说明未写时长,取 [cool time]
} {
seconds, ok := dnfweaknesspotion.ImmunitySeconds(itemID)
if !ok || seconds != wantSeconds {
t.Fatalf("item=%d seconds=%d ok=%t want=%d", itemID, seconds, ok, wantSeconds)
}
}
if seconds, ok := dnfweaknesspotion.ImmunitySeconds(1001); ok || seconds != 0 {
t.Fatalf("家族外道具不应有时长: seconds=%d ok=%t", seconds, ok)
}
}