Files
90CNServer/server/internal/services/dnfbridge/dungeon_monster_reward_sources_test.go
T
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

168 lines
6.8 KiB
Go

package dnfbridge
import (
"errors"
"fmt"
"reflect"
"testing"
"longheng.io/server/internal/modules/dnf/dungeoncmd"
dnfmonster "longheng.io/server/internal/modules/dnf/monster"
"longheng.io/server/internal/modules/dnf/progression"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
"longheng.io/server/internal/modules/dnf/worldmap"
)
func TestCurrentDungeonMonsterRewardSourcesPreserveEvidenceAndRemainFailClosed(t *testing.T) {
sources, err := progression.LoadMonsterExperienceSources(monsterRewardEvidenceTestSource())
if err != nil {
t.Fatal(err)
}
runtime := &runtimeDungeonState{
Request: dungeoncmd.SelectDungeonRequest{DungeonID: 7146, Difficulty: 2},
Dungeon: worldmap.Dungeon{
ID: 7146,
Metadata: worldmap.DungeonMetadata{
BasisLevel: worldmap.OptionalInt{Value: 2, Set: true},
ExperienceIncreasingPoint: worldmap.OptionalNumber{Value: 1.75, Set: true},
},
},
Character: dnfrepo.CharacterRecord{CharacterID: "99", AccountID: "account-1", Level: 10},
}
monster := runtimeDungeonMonster{
ObjectKey: 501,
Spawn: worldmap.MonsterSpawn{
MonsterID: 77, Level: 1, AutoLevel: 1, Rank: "[boss]",
RandomDropCount: 3, FixedDropCount: 1,
},
Definition: dnfmonster.Monster{ID: 77, Rank: "[boss]", Kind: "[beast]", Exp: 456},
}
pool := []dungeonMonsterDropPoolEntry{{ItemID: 1001, Weight: 70}, {ItemID: 1002, Weight: 30}}
plan, err := currentDungeonMonsterRewardSources(runtime, monster, sources, pool)
if err != nil {
t.Fatal(err)
}
if plan.MonsterID != 77 || plan.SpawnRank != "[boss]" || plan.DefinitionRank != "[boss]" ||
plan.DefinitionKind != "[beast]" || plan.WireActorType != 3 {
t.Fatalf("monster evidence=%+v", plan)
}
experience := plan.Experience
if experience.CharacterLevel != 10 || experience.MonsterLevel != 3 || experience.MonsterTable != 72 ||
experience.DefinitionEXP != 456 || !reflect.DeepEqual(experience.MonsterGlobalRates, []float64{1}) ||
experience.PartyRate != 1 || experience.StarterPartyRate != 1 || experience.DifficultyIndex != 2 ||
experience.DifficultyRate != 2.5 || experience.DungeonID != 7146 || !experience.DungeonRateFound ||
experience.DungeonRate != 2.3 || !experience.DungeonLocalRateSpecified || experience.DungeonLocalRate != 1.75 {
t.Fatalf("experience evidence=%+v", experience)
}
if experience.MonsterMinusCharacter != (currentDungeonMonsterPenaltySourceCandidate{Key: -7, Rate: 0.8, Found: true}) ||
experience.CharacterMinusMonster != (currentDungeonMonsterPenaltySourceCandidate{Key: 7, Rate: 0.9, Found: true}) {
t.Fatalf("penalty candidates=%+v/%+v", experience.MonsterMinusCharacter, experience.CharacterMinusMonster)
}
wantExperienceBlockers := []string{
"modifier_combination_unproved",
"level_difference_direction_unproved",
"actor_type_rate_mapping_unproved",
"monster_definition_exp_semantics_unproved",
"dungeon_local_experience_rate_composition_unproved",
"op38_op37_natural_order_unproved",
}
if experience.AwardReady || plan.FullAwardReady || !reflect.DeepEqual(experience.Blockers, wantExperienceBlockers) {
t.Fatalf("unproved experience was enabled: %+v", plan)
}
if plan.Drop.RandomDropCount != 3 || plan.Drop.FixedDropCount != 1 || plan.Drop.ExplicitWeight != 100 ||
!plan.Drop.ExplicitPoolCompatibilityReady || plan.Drop.GenericDropAwardReady ||
!reflect.DeepEqual(plan.Drop.ExplicitPool, pool) {
t.Fatalf("drop evidence=%+v", plan.Drop)
}
pool[0].ItemID = 9999
if plan.Drop.ExplicitPool[0].ItemID != 1001 {
t.Fatal("source plan retained caller-owned pool slice")
}
}
func TestCurrentDungeonMonsterRewardSourcesRejectInvalidOwnerAndKeepUnprovedDropBranchesClosed(t *testing.T) {
sources, err := progression.LoadMonsterExperienceSources(monsterRewardEvidenceTestSource())
if err != nil {
t.Fatal(err)
}
runtime := &runtimeDungeonState{
Request: dungeoncmd.SelectDungeonRequest{DungeonID: 3, Difficulty: 0},
Dungeon: worldmap.Dungeon{ID: 3, Metadata: worldmap.DungeonMetadata{BasisLevel: worldmap.OptionalInt{Value: 2, Set: true}}},
Character: dnfrepo.CharacterRecord{CharacterID: "99", Level: 2},
}
monster := runtimeDungeonMonster{Spawn: worldmap.MonsterSpawn{MonsterID: 77, AutoLevel: 2, Rank: "[normal]", RandomDropCount: 2}}
for name, testCase := range map[string]struct {
runtime *runtimeDungeonState
monster runtimeDungeonMonster
sources *progression.MonsterExperienceSources
}{
"nil runtime": {runtime: nil, monster: monster, sources: sources},
"nil source": {runtime: runtime, monster: monster, sources: nil},
"missing character": {runtime: &runtimeDungeonState{Dungeon: runtime.Dungeon}, monster: monster, sources: sources},
"missing monster": {runtime: runtime, monster: runtimeDungeonMonster{}, sources: sources},
} {
t.Run(name, func(t *testing.T) {
_, err := currentDungeonMonsterRewardSources(testCase.runtime, testCase.monster, testCase.sources, nil)
if !errors.Is(err, errCurrentDungeonMonsterRewardSourceUnavailable) {
t.Fatalf("error=%v", err)
}
})
}
plan, err := currentDungeonMonsterRewardSources(runtime, monster, sources, []dungeonMonsterDropPoolEntry{{ItemID: 1199, Weight: 1}})
if err != nil {
t.Fatal(err)
}
if plan.Drop.ExplicitPoolCompatibilityReady || plan.Drop.GenericDropAwardReady || plan.FullAwardReady ||
!containsString(plan.Drop.Blockers, "coin_gold_drop_branch_unproved") {
t.Fatalf("unproved coin/generic drop was enabled: %+v", plan.Drop)
}
if plan.Experience.DungeonRateFound || plan.Experience.DungeonRate != 0 || plan.Experience.AwardReady {
t.Fatalf("missing dungeon rate received fallback: %+v", plan.Experience)
}
}
type monsterRewardEvidenceSource map[string]string
func (source monsterRewardEvidenceSource) ReadText(path string) (string, error) {
value, found := source[path]
if !found {
return "", fmt.Errorf("missing %s", path)
}
return value, nil
}
func monsterRewardEvidenceTestSource() monsterRewardEvidenceSource {
return monsterRewardEvidenceSource{
progression.MonsterExperienceTablePath: "1 30 2 60 3 72\n",
progression.ServerParameterPath: `[monster exp bonusrate]
1.00
[party user number exp bonusrate]
1 2 3 4
[party user number exp bonusrate starter server]
1 2 3 4
[dungeon difficulty exp bonusrate]
1.30 2.00 2.50 3.00 4.00
[clear exp bonusrate]
1.00
[clear rank exp bonusrate]
0.05 0.10 0.12 0.15 0.20
[experience increasing point]
7146 2.30
[/experience increasing point]
`,
progression.WorldMapExperiencePenaltyPath: `[penalty table info]
-16 0.30 -15 0.50 -14 0.50 -13 0.50 -12 0.70 -11 0.70 -10 0.70 -9 0.80 -8 0.80 -7 0.80 -6 0.90 -5 0.90 -4 0.90 -3 1.00 -2 1.00 -1 1.00 0 1.00 1 1.00 2 1.00 3 1.00 4 1.00 5 1.00 6 0.90 7 0.90 8 0.90 9 0.80 10 0.80 11 0.80 12 0.70 13 0.70 14 0.70 15 0.50 16 0.50 17 0.50 18 0.30 19 0.30 20 0.30
[/penalty table info]
`,
}
}
func containsString(values []string, target string) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}