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

158 lines
5.5 KiB
Go

package pet
import (
"context"
"errors"
"testing"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
dnfrepomemory "longheng.io/server/internal/modules/dnf/repository/memory"
)
type fakeArtifactResolver map[int64]PetArtifactKind
func (resolver fakeArtifactResolver) ResolveArtifact(itemID int64) (PetArtifactDefinition, error) {
kind, ok := resolver[itemID]
if !ok {
return PetArtifactDefinition{}, ErrPetPVFArtifactTypeInvalid
}
return PetArtifactDefinition{ItemID: itemID, Kind: kind}, nil
}
func TestArtifactOwnerEquipSwapAndUnequipAreAtomic(t *testing.T) {
ctx := context.Background()
repos := dnfrepomemory.NewMemoryGroup()
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{
CharacterID: "71",
Slots: map[string]dnfrepo.ItemStack{
"7:140": {ItemID: 63500, Count: 1, RawEntry: []byte{1}, Extra: map[string]string{"serial": "100"}},
"7:141": {ItemID: 63501, Count: 1, RawEntry: []byte{2}, Extra: map[string]string{"serial": "101"}},
"7:142": {ItemID: 64000, Count: 1, RawEntry: []byte{3}, Extra: map[string]string{"serial": "102"}},
},
}); err != nil {
t.Fatalf("save inventory: %v", err)
}
resolver := fakeArtifactResolver{
63500: PetArtifactKindRed,
63501: PetArtifactKindRed,
64000: PetArtifactKindBlue,
}
owner, err := NewArtifactOwner(repos, resolver)
if err != nil {
t.Fatalf("NewArtifactOwner: %v", err)
}
first, err := owner.Equip(ctx, ArtifactEquipCommand{SelectedCharacterID: 71, ListType: listTypePet, SlotIndex: 140})
if err != nil {
t.Fatalf("first Equip: %v", err)
}
if !first.Changed || first.Kind != PetArtifactKindRed || first.ItemID != 63500 || first.SwappedItem != 0 {
t.Fatalf("first result = %+v", first)
}
swap, err := owner.Equip(ctx, ArtifactEquipCommand{SelectedCharacterID: 71, ListType: listTypePet, SlotIndex: 141})
if err != nil {
t.Fatalf("swap Equip: %v", err)
}
if swap.ItemID != 63501 || swap.SwappedItem != 63500 {
t.Fatalf("swap result = %+v", swap)
}
if stack := swap.PetInventory["7:141"]; stack.ItemID != 63500 || stack.Extra["serial"] != "100" || string(stack.RawEntry) != string([]byte{1}) {
t.Fatalf("swapped source = %+v", stack)
}
if _, err := owner.Equip(ctx, ArtifactEquipCommand{SelectedCharacterID: 71, ListType: listTypePet, SlotIndex: 142}); err != nil {
t.Fatalf("blue Equip: %v", err)
}
record, ok, err := repos.Pet.Load(ctx, "71")
if err != nil || !ok {
t.Fatalf("load pets ok=%v err=%v", ok, err)
}
ids, err := EquippedArtifactItemIDs(record)
if err != nil || len(ids) != 2 || ids[0] != 63501 || ids[1] != 64000 {
t.Fatalf("artifact ids=%v err=%v record=%+v", ids, err, record.Artifacts)
}
unequipped, err := owner.Unequip(ctx, ArtifactUnequipCommand{
SelectedCharacterID: 71,
ListType: listTypePet,
SlotIndex: 150,
Kind: PetArtifactKindRed,
})
if err != nil {
t.Fatalf("Unequip: %v", err)
}
if unequipped.ItemID != 63501 || unequipped.PetInventory["7:150"].Extra["serial"] != "101" {
t.Fatalf("unequip result = %+v", unequipped)
}
record, _, _ = repos.Pet.Load(ctx, "71")
if _, exists := record.Artifacts["red"]; exists || record.Artifacts["blue"].ItemID != 64000 {
t.Fatalf("artifacts after unequip = %+v", record.Artifacts)
}
}
func TestArtifactOwnerRejectsWrongRangeAndCorruptStateWithoutMutation(t *testing.T) {
ctx := context.Background()
repos := dnfrepomemory.NewMemoryGroup()
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{
CharacterID: "72",
Slots: map[string]dnfrepo.ItemStack{
"7:140": {ItemID: 63500, Count: 1},
},
}); err != nil {
t.Fatalf("save inventory: %v", err)
}
resolver := fakeArtifactResolver{63500: PetArtifactKindRed, 64000: PetArtifactKindBlue}
owner, _ := NewArtifactOwner(repos, resolver)
if _, err := owner.Equip(ctx, ArtifactEquipCommand{SelectedCharacterID: 72, ListType: listTypePet, SlotIndex: 139}); !errors.Is(err, ErrSlotNotFound) {
t.Fatalf("wrong range error = %v", err)
}
if err := repos.Pet.Save(ctx, dnfrepo.PetRecord{
CharacterID: "72",
Artifacts: map[string]dnfrepo.ItemStack{
"red": {ItemID: 64000, Count: 1},
},
}); err != nil {
t.Fatalf("save corrupt pet state: %v", err)
}
if _, err := owner.Equip(ctx, ArtifactEquipCommand{SelectedCharacterID: 72, ListType: listTypePet, SlotIndex: 140}); !errors.Is(err, ErrPetArtifactStateInvalid) {
t.Fatalf("corrupt state error = %v", err)
}
inventory, _, _ := repos.Inventory.Load(ctx, "72")
if inventory.Slots["7:140"].ItemID != 63500 {
t.Fatalf("source mutated after failure: %+v", inventory.Slots)
}
}
func TestArtifactOwnerRejectsOccupiedUnequipTarget(t *testing.T) {
ctx := context.Background()
repos := dnfrepomemory.NewMemoryGroup()
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{
CharacterID: "73",
Slots: map[string]dnfrepo.ItemStack{"7:150": {ItemID: 999, Count: 1}},
}); err != nil {
t.Fatalf("save inventory: %v", err)
}
if err := repos.Pet.Save(ctx, dnfrepo.PetRecord{
CharacterID: "73",
Artifacts: map[string]dnfrepo.ItemStack{"green": {ItemID: 64500, Count: 1}},
}); err != nil {
t.Fatalf("save pets: %v", err)
}
owner, _ := NewArtifactOwner(repos, fakeArtifactResolver{64500: PetArtifactKindGreen})
_, err := owner.Unequip(ctx, ArtifactUnequipCommand{
SelectedCharacterID: 73,
ListType: listTypePet,
SlotIndex: 150,
Kind: PetArtifactKindGreen,
})
if !errors.Is(err, ErrPetArtifactTargetOccupied) {
t.Fatalf("Unequip error = %v", err)
}
record, _, _ := repos.Pet.Load(ctx, "73")
if record.Artifacts["green"].ItemID != 64500 {
t.Fatalf("artifact mutated after failure: %+v", record.Artifacts)
}
}