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,132 @@
|
||||
package creaturestate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
||||
dnfrepomemory "longheng.io/server/internal/modules/dnf/repository/memory"
|
||||
)
|
||||
|
||||
// 线上 Script.pvf 里 2681466 = `equipment/creature/egg_SDCharacterCreature_ft.equ`
|
||||
// (宠物蛋 (迷你气功师),带 [output index] 2681467);
|
||||
// 400990167 / 400990168 是直接发放的真宠物(无 [output index])。
|
||||
const (
|
||||
testPetEggItemID int64 = 2681466
|
||||
testRealPetItemID int64 = 400990167
|
||||
testPetEggSerial uint32 = 2
|
||||
)
|
||||
|
||||
// TestReconcileHealsPetEggBinding 覆盖现场:蛋被当成"直接发放的宠物",拿到了
|
||||
// serial 并建出了宠物记录,于是丢弃侧按"已绑定宠物"拒绝(「宠物蛋无法丢弃」)。
|
||||
// 对账必须自愈:清掉行上的绑定 extra、删掉误建的宠物记录。
|
||||
func TestReconcileHealsPetEggBinding(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const characterID = "77"
|
||||
|
||||
SetPetEggProbe(func(itemID int64) bool { return itemID == testPetEggItemID })
|
||||
defer SetPetEggProbe(nil)
|
||||
|
||||
repos := dnfrepomemory.NewMemoryGroup()
|
||||
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: characterID, Slots: map[string]dnfrepo.ItemStack{
|
||||
"7:0": {ItemID: testPetEggItemID, Count: 1, Extra: map[string]string{
|
||||
"creature_serial_or_handle": "2",
|
||||
"creature_key": "2",
|
||||
"item_kind": "equipment",
|
||||
"equipment_type": "[creature]",
|
||||
"pvf_path": "equipment/creature/egg_SDCharacterCreature_ft.equ",
|
||||
}},
|
||||
"7:1": {ItemID: testRealPetItemID, Count: 1, Extra: map[string]string{
|
||||
"creature_serial_or_handle": "1",
|
||||
"item_kind": "equipment",
|
||||
"equipment_type": "[creature]",
|
||||
"pvf_path": "equipment/creature/chn_2018_SpringFestival/chn_400990167.equ",
|
||||
}},
|
||||
}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Equipment.Save(ctx, dnfrepo.EquipmentRecord{CharacterID: characterID, Entries: map[string]dnfrepo.EquipmentEntry{}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Pet.Save(ctx, dnfrepo.PetRecord{CharacterID: characterID, Entries: map[string]dnfrepo.PetEntry{
|
||||
"2": {PetKey: "2", CreatureKey: 2, ItemID: testPetEggItemID, SourceListType: 7, SourceSlotIndex: 0},
|
||||
"1": {PetKey: "1", CreatureKey: 1, ItemID: testRealPetItemID, SourceListType: 7, SourceSlotIndex: 1},
|
||||
}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ReconcileInventory(ctx, characterID, repos.Inventory, repos.Equipment, repos.Pet); err != nil {
|
||||
t.Fatalf("ReconcileInventory: %v", err)
|
||||
}
|
||||
|
||||
inventory, found, err := repos.Inventory.Load(ctx, characterID)
|
||||
if err != nil || !found {
|
||||
t.Fatalf("load inventory found=%t err=%v", found, err)
|
||||
}
|
||||
egg := inventory.Slots["7:0"]
|
||||
if !stackLacksPetBinding(egg) {
|
||||
t.Fatalf("蛋行仍带宠物绑定 extra: %+v", egg.Extra)
|
||||
}
|
||||
|
||||
petRecord, found, err := repos.Pet.Load(ctx, characterID)
|
||||
if err != nil || !found {
|
||||
t.Fatalf("load pets found=%t err=%v", found, err)
|
||||
}
|
||||
if _, still := petRecord.Entries["2"]; still {
|
||||
t.Fatalf("蛋的宠物记录没有被清理: %+v", petRecord.Entries)
|
||||
}
|
||||
// 真宠物必须不受影响。
|
||||
if _, ok := petRecord.Entries["1"]; !ok {
|
||||
t.Fatalf("真宠物的记录被误删: %+v", petRecord.Entries)
|
||||
}
|
||||
if got := inventory.Slots["7:1"].Extra["creature_serial_or_handle"]; got != "1" {
|
||||
t.Fatalf("真宠物的 serial 被误改: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReconcileKeepsEggBehaviourWithoutProbe 钉住回退路径:判据未装配(隔离测试、
|
||||
// 未接线的调用方)时必须完全等于改动前的行为。
|
||||
func TestReconcileKeepsEggBehaviourWithoutProbe(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const characterID = "78"
|
||||
|
||||
SetPetEggProbe(nil)
|
||||
defer SetPetEggProbe(nil)
|
||||
|
||||
repos := dnfrepomemory.NewMemoryGroup()
|
||||
if err := repos.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: characterID, Slots: map[string]dnfrepo.ItemStack{
|
||||
"7:0": {ItemID: testPetEggItemID, Count: 1, Extra: map[string]string{
|
||||
"item_kind": "equipment",
|
||||
"equipment_type": "[creature]",
|
||||
"pvf_path": "equipment/creature/egg_SDCharacterCreature_ft.equ",
|
||||
}},
|
||||
}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Equipment.Save(ctx, dnfrepo.EquipmentRecord{CharacterID: characterID, Entries: map[string]dnfrepo.EquipmentEntry{}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.Pet.Save(ctx, dnfrepo.PetRecord{CharacterID: characterID, Entries: map[string]dnfrepo.PetEntry{}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := ReconcileInventory(ctx, characterID, repos.Inventory, repos.Equipment, repos.Pet); err != nil {
|
||||
t.Fatalf("ReconcileInventory: %v", err)
|
||||
}
|
||||
inventory, _, _ := repos.Inventory.Load(ctx, characterID)
|
||||
if got := inventory.Slots["7:0"].Extra["creature_serial_or_handle"]; got == "" {
|
||||
t.Fatal("未装配判据时不应改变行为(旧行为会给蛋分配 serial)")
|
||||
}
|
||||
}
|
||||
|
||||
func stackLacksPetBinding(stack dnfrepo.ItemStack) bool {
|
||||
if _, ok := stackSerial(stack.Extra); ok {
|
||||
return false
|
||||
}
|
||||
for _, key := range []string{"creature_serial_or_handle", "creature_key", "creature_serial", "pet_serial"} {
|
||||
if stack.Extra[key] != "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in new issue
Block a user