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,214 @@
|
||||
package pet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
type petCatalogTestSource map[string]string
|
||||
|
||||
func (s petCatalogTestSource) ReadText(path string) (string, error) {
|
||||
if text, found := s[path]; found {
|
||||
return text, nil
|
||||
}
|
||||
return "", fmt.Errorf("missing %s", path)
|
||||
}
|
||||
|
||||
func TestPVFCatalogResolvesHatchAndCreatureExperience(t *testing.T) {
|
||||
source := petCatalogTestSource{
|
||||
petEquipmentListPath: "63006 `creature/egg_faras.equ`\n63000 `creature/faras.equ`\n",
|
||||
"equipment/creature/egg_faras.equ": "[equipment type] `[creature]`\n[output index] 63000\n",
|
||||
"equipment/creature/faras.equ": "[name] `Faras`\n[equipment type] `[creature]`\n[minimum level] 3\n",
|
||||
petCreatureExperiencePath: petCatalogTestExperienceText(),
|
||||
}
|
||||
catalog, err := NewPVFCatalog(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var resolver PetHatchResolver = catalog
|
||||
definition, err := resolver.ResolveHatch(63006)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if definition != (PetHatchDefinition{
|
||||
EggItemID: 63006,
|
||||
HatchedItemID: 63000,
|
||||
EggPVFPath: "equipment/creature/egg_faras.equ",
|
||||
HatchedPVFPath: "equipment/creature/faras.equ",
|
||||
MinimumLevel: 3,
|
||||
}) {
|
||||
t.Fatalf("definition=%+v", definition)
|
||||
}
|
||||
creature, err := catalog.ResolveCreature(63000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if creature.Name != "Faras" || creature.PVFPath != "equipment/creature/faras.equ" {
|
||||
t.Fatalf("creature=%+v", creature)
|
||||
}
|
||||
table := catalog.ExperienceTable()
|
||||
if got := table.LevelForExperience(0); got != 1 {
|
||||
t.Fatalf("level(exp=0)=%d", got)
|
||||
}
|
||||
if got := table.LevelForExperience(2); got != 2 {
|
||||
t.Fatalf("level(exp=2)=%d", got)
|
||||
}
|
||||
if got := table.LevelForExperience(4); got != 2 {
|
||||
t.Fatalf("level(exp=4)=%d", got)
|
||||
}
|
||||
if got := table.LevelForExperience(5); got != 3 {
|
||||
t.Fatalf("level(exp=5)=%d", got)
|
||||
}
|
||||
if got := table.LevelForExperience(1 << 60); got != MaxCreatureLevel {
|
||||
t.Fatalf("level(max exp)=%d", got)
|
||||
}
|
||||
values := table.Thresholds()
|
||||
values[0] = 999999
|
||||
if catalog.ExperienceTable().Thresholds()[0] != 2 {
|
||||
t.Fatal("experience thresholds leaked mutable catalog state")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPVFCatalogDefaultsMissingCreatureMinimumLevelToOne(t *testing.T) {
|
||||
source := petCatalogTestSource{
|
||||
petEquipmentListPath: "10 `egg.equ`\n11 `pet.equ`\n",
|
||||
"equipment/egg.equ": "[equipment type] `[creature]`\n[output index] 11\n",
|
||||
"equipment/pet.equ": "[equipment type] `[creature]`\n",
|
||||
petCreatureExperiencePath: petCatalogTestExperienceText(),
|
||||
}
|
||||
catalog, err := NewPVFCatalog(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
definition, err := catalog.ResolveHatch(10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if definition.MinimumLevel != 1 {
|
||||
t.Fatalf("minimum level=%d", definition.MinimumLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPVFCatalogRejectsUnprovedHatchMappings(t *testing.T) {
|
||||
base := petCatalogTestSource{
|
||||
petEquipmentListPath: "10 `egg.equ`\n11 `pet.equ`\n",
|
||||
"equipment/egg.equ": "[equipment type] `[creature]`\n[output index] 11\n",
|
||||
"equipment/pet.equ": "[equipment type] `[creature]`\n",
|
||||
petCreatureExperiencePath: petCatalogTestExperienceText(),
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
mutate func(petCatalogTestSource)
|
||||
want error
|
||||
}{
|
||||
{
|
||||
name: "source is not creature",
|
||||
mutate: func(source petCatalogTestSource) {
|
||||
source["equipment/egg.equ"] = "[equipment type] `[weapon]`\n[output index] 11\n"
|
||||
},
|
||||
want: ErrPetPVFNotCreature,
|
||||
},
|
||||
{
|
||||
name: "missing output",
|
||||
mutate: func(source petCatalogTestSource) {
|
||||
source["equipment/egg.equ"] = "[equipment type] `[creature]`\n"
|
||||
},
|
||||
want: ErrPetPVFHatchOutputInvalid,
|
||||
},
|
||||
{
|
||||
name: "same output",
|
||||
mutate: func(source petCatalogTestSource) {
|
||||
source["equipment/egg.equ"] = "[equipment type] `[creature]`\n[output index] 10\n"
|
||||
},
|
||||
want: ErrPetPVFHatchOutputInvalid,
|
||||
},
|
||||
{
|
||||
name: "output absent from equipment list",
|
||||
mutate: func(source petCatalogTestSource) {
|
||||
source["equipment/egg.equ"] = "[equipment type] `[creature]`\n[output index] 12\n"
|
||||
},
|
||||
want: ErrPetPVFEquipmentUnresolved,
|
||||
},
|
||||
{
|
||||
name: "output is not creature",
|
||||
mutate: func(source petCatalogTestSource) {
|
||||
source["equipment/pet.equ"] = "[equipment type] `[artifact red]`\n"
|
||||
},
|
||||
want: ErrPetPVFNotCreature,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
source := make(petCatalogTestSource, len(base))
|
||||
for key, value := range base {
|
||||
source[key] = value
|
||||
}
|
||||
test.mutate(source)
|
||||
catalog, err := NewPVFCatalog(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = catalog.ResolveHatch(10)
|
||||
if !errors.Is(err, test.want) {
|
||||
t.Fatalf("error=%v want=%v", err, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPVFCatalogRejectsMalformedExperienceTable(t *testing.T) {
|
||||
base := petCatalogTestSource{
|
||||
petEquipmentListPath: "10 `egg.equ`\n",
|
||||
petCreatureExperiencePath: petCatalogTestExperienceText(),
|
||||
}
|
||||
for _, text := range []string{
|
||||
"2 5 9",
|
||||
strings.Replace(petCatalogTestExperienceText(), " 5 ", " 2 ", 1),
|
||||
} {
|
||||
source := make(petCatalogTestSource, len(base))
|
||||
for key, value := range base {
|
||||
source[key] = value
|
||||
}
|
||||
source[petCreatureExperiencePath] = text
|
||||
if _, err := NewPVFCatalog(source); !errors.Is(err, ErrPetPVFExperienceTableShape) {
|
||||
t.Fatalf("error=%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealScriptPVFPetCatalogHatchAndExperience(t *testing.T) {
|
||||
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
|
||||
if pvfPath == "" {
|
||||
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to verify the runtime pet catalog")
|
||||
}
|
||||
archive, err := platformpvf.LoadArchive(platformpvf.Options{Path: pvfPath})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
catalog, err := NewPVFCatalog(archive)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
definition, err := catalog.ResolveHatch(63006)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if definition.HatchedItemID != 63000 || definition.MinimumLevel != 1 {
|
||||
t.Fatalf("real hatch definition=%+v", definition)
|
||||
}
|
||||
thresholds := catalog.ExperienceTable().Thresholds()
|
||||
if len(thresholds) != MaxCreatureLevel || thresholds[0] != 2 || thresholds[len(thresholds)-1] != 3564 {
|
||||
t.Fatalf("real experience thresholds count=%d first=%d last=%d", len(thresholds), thresholds[0], thresholds[len(thresholds)-1])
|
||||
}
|
||||
}
|
||||
|
||||
func petCatalogTestExperienceText() string {
|
||||
return "2 5 9 15 23 33 44 57 71 88 106 126 148 171 196 223 252 283 315 350 " +
|
||||
"388 429 472 517 565 615 668 725 785 849 918 993 1073 1158 1245 1335 1430 1532 1642 1759 " +
|
||||
"1879 2011 2151 2301 2464 2639 2829 3049 3294 5579\n"
|
||||
}
|
||||
Reference in new issue
Block a user