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,393 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
|
||||
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
func parseSkillSwitchDoc(t *testing.T, text string) *dnfpvf.Document {
|
||||
t.Helper()
|
||||
doc, err := dnfpvf.Parse("test.equ", text)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
func TestSkillSwitchDocumentBuffLevelBonusExact(t *testing.T) {
|
||||
doc := parseSkillSwitchDoc(t, `
|
||||
[skill levelup]
|
||||
`+"`[swordman]`"+` 38 1 `+"`[at swordman]`"+` 72 2
|
||||
[/skill levelup]
|
||||
`)
|
||||
got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 38, 25, true, 0)
|
||||
if got != 1 {
|
||||
t.Fatalf("exact bonus=%d want=1", got)
|
||||
}
|
||||
// different skill id
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 72, 25, true, 0); got != 0 {
|
||||
t.Fatalf("skill 72 for swordman bonus=%d want=0", got)
|
||||
}
|
||||
// different job
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "at swordman", 72, 25, true, 0); got != 2 {
|
||||
t.Fatalf("at swordman skill 72 bonus=%d want=2", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchDocumentBuffLevelBonusNegative(t *testing.T) {
|
||||
doc := parseSkillSwitchDoc(t, `
|
||||
[skill levelup]
|
||||
`+"`[knight]`"+` 87 -1
|
||||
[/skill levelup]
|
||||
`)
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "knight", 87, 25, true, 0); got != -1 {
|
||||
t.Fatalf("negative bonus=%d want=-1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchDocumentBuffLevelBonusRange(t *testing.T) {
|
||||
doc := parseSkillSwitchDoc(t, `
|
||||
[all skill item container]
|
||||
[all skill item]
|
||||
[item growtype]
|
||||
`+"`[swordman]`"+` 0
|
||||
[skill apply condition]
|
||||
[skill group]
|
||||
`+"`[all]`"+`
|
||||
[lower bound level]
|
||||
1
|
||||
[upper bound level]
|
||||
45
|
||||
[value]
|
||||
1
|
||||
[extra condition]
|
||||
`+"`[except for ex skill]`"+`
|
||||
[/extra condition]
|
||||
[/skill apply condition]
|
||||
[skill phase]
|
||||
`+"`[all]`"+`
|
||||
[/all skill item]
|
||||
[/all skill item container]
|
||||
`)
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 38, 25, true, 0); got != 1 {
|
||||
t.Fatalf("range bonus=%d want=1", got)
|
||||
}
|
||||
// required level outside the range
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 38, 60, true, 0); got != 0 {
|
||||
t.Fatalf("out-of-range bonus=%d want=0", got)
|
||||
}
|
||||
// wrong job tag
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "at swordman", 38, 25, true, 0); got != 0 {
|
||||
t.Fatalf("wrong-job range bonus=%d want=0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchDocumentBuffLevelBonusRangeActiveOnly(t *testing.T) {
|
||||
doc := parseSkillSwitchDoc(t, `
|
||||
[all skill item]
|
||||
[item growtype]
|
||||
`+"`[swordman]`"+` 0
|
||||
[skill apply condition]
|
||||
[skill group]
|
||||
`+"`[active]`"+`
|
||||
[lower bound level]
|
||||
1
|
||||
[upper bound level]
|
||||
45
|
||||
[value]
|
||||
1
|
||||
[/skill apply condition]
|
||||
[/all skill item]
|
||||
`)
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 38, 25, true, 0); got != 1 {
|
||||
t.Fatalf("active skill bonus=%d want=1", got)
|
||||
}
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 38, 25, false, 0); got != 0 {
|
||||
t.Fatalf("passive skill active-only bonus=%d want=0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchDocumentBuffLevelBonusSkipsEnchant(t *testing.T) {
|
||||
doc := parseSkillSwitchDoc(t, `
|
||||
[enchant]
|
||||
[skill levelup]
|
||||
`+"`[swordman]`"+` 38 1
|
||||
[/skill levelup]
|
||||
[/enchant]
|
||||
`)
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 38, 25, true, 0); got != 0 {
|
||||
t.Fatalf("enchant-nested bonus=%d want=0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchDocumentBuffLevelBonusStageGate(t *testing.T) {
|
||||
doc := parseSkillSwitchDoc(t, `
|
||||
[all skill item]
|
||||
[item growtype]
|
||||
`+"`[swordman]`"+` 2
|
||||
[skill apply condition]
|
||||
[skill group]
|
||||
`+"`[all]`"+`
|
||||
[lower bound level]
|
||||
1
|
||||
[upper bound level]
|
||||
45
|
||||
[value]
|
||||
1
|
||||
[/skill apply condition]
|
||||
[/all skill item]
|
||||
`)
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 38, 25, true, 2); got != 1 {
|
||||
t.Fatalf("stage 2 character bonus=%d want=1", got)
|
||||
}
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 38, 25, true, 1); got != 0 {
|
||||
t.Fatalf("stage 1 character bonus=%d want=0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchDocumentBuffLevelBonusGroupScopedLevelup(t *testing.T) {
|
||||
doc := parseSkillSwitchDoc(t, `
|
||||
[all skill item]
|
||||
[item growtype]
|
||||
`+"`[at swordman]`"+` 0
|
||||
[skill levelup]
|
||||
`+"`[at swordman]`"+` 72 1
|
||||
[/skill levelup]
|
||||
[/all skill item]
|
||||
`)
|
||||
// Group is scoped to [at swordman]; a [swordman] character must not gain it.
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "swordman", 72, 25, true, 0); got != 0 {
|
||||
t.Fatalf("wrong-group levelup bonus=%d want=0", got)
|
||||
}
|
||||
if got := skillSwitchDocumentBuffLevelBonus(doc, "at swordman", 72, 25, true, 0); got != 1 {
|
||||
t.Fatalf("matching-group levelup bonus=%d want=1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func skillSwitchEmbeddedTestCatalog(t *testing.T) *pvfDungeonDropCatalog {
|
||||
t.Helper()
|
||||
source := dungeonDropCatalogTestSource{
|
||||
"monster/monster.lst": "",
|
||||
"stackable/stackable.lst": "10001 `card/test_card.stk`\n10002 `emblem/test_emblem.stk`\n10003 `card/test_range_card.stk`\n",
|
||||
"equipment/equipment.lst": "",
|
||||
"stackable/card/test_card.stk": "[name] `Test Card`\n[stackable type] `[material expert job]`\n[item category] `monster card`\n[/item category]\n" +
|
||||
"[enchant table]\n[enchant index]\n0\n[skill levelup]\n`[all]` 38 1\n[/skill levelup]\n[/enchant index]\n" +
|
||||
"[enchant index]\n1\n[skill levelup]\n`[all]` 38 2\n[/skill levelup]\n[/enchant index]\n[/enchant table]\n",
|
||||
"stackable/card/test_range_card.stk": "[name] `Test Range Card`\n[stackable type] `[material expert job]`\n[item category] `monster card`\n[/item category]\n" +
|
||||
"[enchant table]\n[enchant index]\n0\n[all skill item container]\n[all skill item]\n[item growtype]\n`[all]` 0\n" +
|
||||
"[skill apply condition]\n[skill group]\n`[active]`\n[lower bound level]\n1\n[upper bound level]\n50\n[value]\n1\n" +
|
||||
"[extra condition]\n`[except for ex skill]`\n[/extra condition]\n[/skill apply condition]\n[skill phase]\n`[all]`\n" +
|
||||
"[/all skill item]\n[/all skill item container]\n[/enchant index]\n[/enchant table]\n",
|
||||
"stackable/emblem/test_emblem.stk": "[name] `Test Emblem`\n[stackable type] `[avatar emblem]`\n[enchant]\n[skill levelup]\n`[at swordman]` 38 1\n[/skill levelup]\n[/enchant]\n",
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return catalog
|
||||
}
|
||||
|
||||
func TestSkillSwitchEnchantCardBuffLevelBonus(t *testing.T) {
|
||||
catalog := skillSwitchEmbeddedTestCatalog(t)
|
||||
if got := skillSwitchEnchantCardBuffLevelBonus(catalog, 10001, 0, "swordman", 38, 25, true, 0); got != 1 {
|
||||
t.Fatalf("upgrade 0 bonus=%d want=1", got)
|
||||
}
|
||||
if got := skillSwitchEnchantCardBuffLevelBonus(catalog, 10001, 1, "swordman", 38, 25, true, 0); got != 2 {
|
||||
t.Fatalf("upgrade 1 bonus=%d want=2", got)
|
||||
}
|
||||
if got := skillSwitchEnchantCardBuffLevelBonus(catalog, 10001, 9, "swordman", 38, 25, true, 0); got != 1 {
|
||||
t.Fatalf("missing upgrade fallback bonus=%d want=1", got)
|
||||
}
|
||||
if got := skillSwitchEnchantCardBuffLevelBonus(catalog, 10001, 0, "swordman", 72, 25, true, 0); got != 0 {
|
||||
t.Fatalf("different skill bonus=%d want=0", got)
|
||||
}
|
||||
// Range form inside the enchant index (the "1..50 skills +1" card).
|
||||
if got := skillSwitchEnchantCardBuffLevelBonus(catalog, 10003, 0, "swordman", 38, 25, true, 0); got != 1 {
|
||||
t.Fatalf("range card bonus=%d want=1", got)
|
||||
}
|
||||
if got := skillSwitchEnchantCardBuffLevelBonus(catalog, 10003, 0, "swordman", 38, 60, true, 0); got != 0 {
|
||||
t.Fatalf("range card out-of-range bonus=%d want=0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchEmblemBuffLevelBonus(t *testing.T) {
|
||||
catalog := skillSwitchEmbeddedTestCatalog(t)
|
||||
if got := skillSwitchEmblemBuffLevelBonus(catalog, 10002, "at swordman", 38, 25, true, 0); got != 1 {
|
||||
t.Fatalf("at swordman emblem bonus=%d want=1", got)
|
||||
}
|
||||
if got := skillSwitchEmblemBuffLevelBonus(catalog, 10002, "swordman", 38, 25, true, 0); got != 0 {
|
||||
t.Fatalf("wrong job emblem bonus=%d want=0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchEnchantCardIDAndUpgrade(t *testing.T) {
|
||||
if id, ok := skillSwitchEnchantCardID(map[string]string{"enchant_card_id": "10008713"}); !ok || id != 10008713 {
|
||||
t.Fatalf("enchant card id=%d ok=%t", id, ok)
|
||||
}
|
||||
if id, ok := skillSwitchEnchantCardID(map[string]string{"value_a": "10008713"}); ok && id != 0 {
|
||||
t.Fatalf("value_a must not be treated as enchant card id: %d", id)
|
||||
}
|
||||
if got := skillSwitchEnchantUpgradeCount(map[string]string{"enchant_upgrade_count": "2"}); got != 2 {
|
||||
t.Fatalf("upgrade=%d want=2", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchEmblemItemIDs(t *testing.T) {
|
||||
raw := make([]byte, currentItemListEntryWireSize)
|
||||
raw[currentEquipmentVectorOffset] = 2
|
||||
binary.LittleEndian.PutUint32(raw[currentEquipmentVectorOffset+1:currentEquipmentVectorOffset+5], 10002)
|
||||
binary.LittleEndian.PutUint32(raw[currentEquipmentVectorOffset+5:currentEquipmentVectorOffset+9], 10003)
|
||||
ids := skillSwitchEmblemItemIDs(nil, raw)
|
||||
if len(ids) != 2 || ids[0] != 10002 || ids[1] != 10003 {
|
||||
t.Fatalf("emblem ids=%v", ids)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchRegisteredItemInstance(t *testing.T) {
|
||||
inventory := dnfrepo.InventoryRecord{
|
||||
Slots: map[string]dnfrepo.ItemStack{
|
||||
"0:17": {ItemID: 600, Extra: map[string]string{"item_serial": "20"}},
|
||||
},
|
||||
Warehouse: map[string]dnfrepo.ItemStack{},
|
||||
}
|
||||
equipment := dnfrepo.EquipmentRecord{Entries: map[string]dnfrepo.EquipmentEntry{}}
|
||||
extra, raw, found := skillSwitchRegisteredItemInstance(
|
||||
skillSwitchBinding{EquipmentPart: 12, ItemSerial: 20, ItemID: 600},
|
||||
inventory,
|
||||
equipment,
|
||||
)
|
||||
if !found || extra["item_serial"] != "20" {
|
||||
t.Fatalf("instance found=%t extra=%v raw=%v", found, extra, raw)
|
||||
}
|
||||
if _, _, found := skillSwitchRegisteredItemInstance(
|
||||
skillSwitchBinding{EquipmentPart: 12, ItemSerial: 21, ItemID: 600},
|
||||
inventory,
|
||||
equipment,
|
||||
); found {
|
||||
t.Fatalf("missing serial must not resolve")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchLegacyEnchantCardID(t *testing.T) {
|
||||
catalog := skillSwitchEmbeddedTestCatalog(t)
|
||||
raw := make([]byte, currentItemListEntryWireSize)
|
||||
binary.LittleEndian.PutUint32(raw[0x0E:0x12], 10001)
|
||||
if id, ok := skillSwitchLegacyEnchantCardID(catalog, raw); !ok || id != 10001 {
|
||||
t.Fatalf("legacy card id=%d ok=%t", id, ok)
|
||||
}
|
||||
binary.LittleEndian.PutUint32(raw[0x0E:0x12], 99999999)
|
||||
if _, ok := skillSwitchLegacyEnchantCardID(catalog, raw); ok {
|
||||
t.Fatalf("unresolvable seed must not resolve as card")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSkillSwitchRealPVFBuffBonusSmoke exercises the bonus parsers against the
|
||||
// shipped Script.pvf. Set DNFBRIDGE_REAL_PVF_SMOKE to the Script.pvf path to
|
||||
// enable it.
|
||||
func TestSkillSwitchRealPVFBuffBonusSmoke(t *testing.T) {
|
||||
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
|
||||
if pvfPath == "" {
|
||||
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to verify against the shipped Script.pvf")
|
||||
}
|
||||
archive, err := platformpvf.Open(pvfPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(archive)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 圣物 : 暗影精灵之哨兵侍剑 carries 1..45 skill +1 ranges plus a
|
||||
// [knight] 87 -1 exact row.
|
||||
if _, err := catalog.ResolveItem(101000607); err == nil {
|
||||
bonus, _ := skillSwitchItemBuffLevelBonus(catalog, 101000607, "swordman", 38, 25, true, 0)
|
||||
if bonus < 0 {
|
||||
t.Fatalf("real sword weapon bonus=%d want >= 0", bonus)
|
||||
}
|
||||
} else {
|
||||
t.Logf("skip real sword weapon: %v", err)
|
||||
}
|
||||
|
||||
// 白金徽章 [魔镜幻影阵] embeds [at swordman] 69 +1.
|
||||
if _, err := catalog.ResolveItem(10088976); err == nil {
|
||||
bonus := skillSwitchEmblemBuffLevelBonus(catalog, 10088976, "at swordman", 69, 25, true, 0)
|
||||
if bonus != 1 {
|
||||
t.Fatalf("real platinum emblem bonus=%d want=1", bonus)
|
||||
}
|
||||
} else {
|
||||
t.Logf("skip real platinum emblem: %v", err)
|
||||
}
|
||||
|
||||
// A monster card with `[all] 188 1 / [all] 189 1` at enchant index 0.
|
||||
if _, err := catalog.ResolveItem(10006481); err == nil {
|
||||
bonus := skillSwitchEnchantCardBuffLevelBonus(catalog, 10006481, 0, "swordman", 188, 25, true, 0)
|
||||
if bonus != 1 {
|
||||
t.Fatalf("real monster card bonus=%d want=1", bonus)
|
||||
}
|
||||
} else {
|
||||
t.Logf("skip real monster card: %v", err)
|
||||
}
|
||||
|
||||
// A range-form monster card: [enchant index] carries a 15..15 active
|
||||
// skill +1 [all skill item] block.
|
||||
if _, err := catalog.ResolveItem(490005834); err == nil {
|
||||
bonus := skillSwitchEnchantCardBuffLevelBonus(catalog, 490005834, 0, "swordman", 38, 15, true, 0)
|
||||
if bonus != 1 {
|
||||
t.Fatalf("real range card bonus=%d want=1", bonus)
|
||||
}
|
||||
bonus = skillSwitchEnchantCardBuffLevelBonus(catalog, 490005834, 0, "swordman", 38, 25, true, 0)
|
||||
if bonus != 0 {
|
||||
t.Fatalf("real range card out-of-range bonus=%d want=0", bonus)
|
||||
}
|
||||
} else {
|
||||
t.Logf("skip real range card: %v", err)
|
||||
}
|
||||
|
||||
// 上衣装扮 415500013: [ability case index] 1450, ability 0 = [at priest] 14 +1.
|
||||
if _, err := catalog.ResolveItem(415500013); err == nil {
|
||||
bonus := skillSwitchAvatarAbilitySkillBonus(catalog, 415500013, 0, "at priest", 14)
|
||||
if bonus != 1 {
|
||||
t.Fatalf("real avatar ability bonus=%d want=1", bonus)
|
||||
}
|
||||
} else {
|
||||
t.Logf("skip real avatar: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchAvatarAbilitySkillBonus(t *testing.T) {
|
||||
source := dungeonDropCatalogTestSource{
|
||||
"monster/monster.lst": "",
|
||||
"stackable/stackable.lst": "",
|
||||
"equipment/equipment.lst": "415500013 `avatar/coat.equ`\n",
|
||||
"equipment/avatar/coat.equ": "[name] `Test Avatar`\n[equipment type] `[coat avatar]`\n[ability case index]\n1450\n",
|
||||
"skill/abilitydatas.dat": "[ability case]\n1450 0 `[SKILL_LEVEL]` `[at priest]` 14 1 1 `[SKILL_LEVEL]` `[at priest]` 15 1\n[/ability case]\n",
|
||||
}
|
||||
catalog, err := newPVFDungeonDropCatalog(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := skillSwitchAvatarAbilitySkillBonus(catalog, 415500013, 0, "at priest", 14); got != 1 {
|
||||
t.Fatalf("ability 0 buff skill bonus=%d want=1", got)
|
||||
}
|
||||
if got := skillSwitchAvatarAbilitySkillBonus(catalog, 415500013, 1, "at priest", 14); got != 0 {
|
||||
t.Fatalf("ability 1 non-buff skill bonus=%d want=0", got)
|
||||
}
|
||||
if got := skillSwitchAvatarAbilitySkillBonus(catalog, 415500013, 0, "priest", 14); got != 0 {
|
||||
t.Fatalf("wrong job ability bonus=%d want=0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkillSwitchAvatarAbilityNo(t *testing.T) {
|
||||
if no, ok := skillSwitchAvatarAbilityNo(map[string]string{"avatar_ability_no": "7"}); !ok || no != 7 {
|
||||
t.Fatalf("ability no=%d ok=%t", no, ok)
|
||||
}
|
||||
if _, ok := skillSwitchAvatarAbilityNo(map[string]string{}); ok {
|
||||
t.Fatalf("missing ability must not resolve")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user