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,431 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/worldmap"
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
func TestCurrentDungeonMonsterPlanUsesPVFDimensionMembershipAsOrdinaryPool(t *testing.T) {
|
||||
service, runtime, _ := prepareCurrentDungeonDropTest(t, 1, 3227)
|
||||
source := service.dungeonMonsterTable.source.(bridgePVFSource)
|
||||
source[dungeonCardDropRulePath] = currentDungeonGoldDropTestRuleText()
|
||||
source[dungeonCardGoldReferencePath] = "[gold drop ref table]\n20 100 0\n"
|
||||
source[currentDungeonDimensionGatePath] = "[dimensiondungeon list]\n700 25 0\n[/dimensiondungeon list]\n"
|
||||
source[currentDungeonDimensionInfoPath] = `[dimension dungeon info]
|
||||
[/dimension dungeon info]
|
||||
[dungeon difficulty condition info]
|
||||
1 70 2 80 3 85
|
||||
[/dungeon difficulty condition info]
|
||||
[drop item Info]
|
||||
0 0 3228
|
||||
0 1 3229
|
||||
[/drop item Info]
|
||||
`
|
||||
source[dungeonDropStackableList] = "3227 `material/test_drop.stk`\n3228 `material/test_dimension_drop.stk`\n3229 `material/test_dimension_drop_2.stk`\n"
|
||||
source["stackable/material/test_dimension_drop.stk"] = "[name]\n`Test Dimension Drop`\n[stackable type]\n`[material]`\n[stack limit]\n999\n[attach type]\n`[free]`\n"
|
||||
source["stackable/material/test_dimension_drop_2.stk"] = "[name]\n`Test Dimension Drop 2`\n[stackable type]\n`[material]`\n[stack limit]\n999\n[attach type]\n`[free]`\n"
|
||||
currentDungeonMonsterDropRulesByCatalog.Delete(service.dungeonMonsterTable)
|
||||
currentDungeonGoldReferencesByCatalog.Delete(service.dungeonMonsterTable)
|
||||
currentDungeonDimensionDropsByMonsterCatalog.Delete(service.dungeonMonsterTable)
|
||||
monster := runtime.Room.Snapshot().Monsters[0]
|
||||
wires, err := service.planCurrentDungeonMonsterDrops(runtime, monster, currentSceneActorObjectKey(99))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dimensionRows := 0
|
||||
for _, wire := range wires {
|
||||
itemID := binary.LittleEndian.Uint32(wire.Item.data[0x02:0x06])
|
||||
if itemID == 3228 || itemID == 3229 {
|
||||
dimensionRows++
|
||||
}
|
||||
}
|
||||
if dimensionRows > 1 {
|
||||
t.Fatalf("one ordinary-pool attempt emitted %d dimension rows; materials were appended directly: wires=%d", dimensionRows, len(wires))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentDungeonMonsterPlanKeepsDimensionFragmentsForSpecialRanks(t *testing.T) {
|
||||
for _, rank := range []string{"[champion]", "[super champion]", "[boss]"} {
|
||||
t.Run(rank, func(t *testing.T) {
|
||||
service, runtime, _ := prepareCurrentDungeonDropTest(t, 1, 3227)
|
||||
source := service.dungeonMonsterTable.source.(bridgePVFSource)
|
||||
source[dungeonCardDropRulePath] = currentDungeonGoldDropTestRuleText()
|
||||
source[dungeonCardGoldReferencePath] = "[gold drop ref table]\n20 100 0\n"
|
||||
source[currentDungeonDimensionGatePath] = "[dimensiondungeon list]\n700 25 0\n[/dimensiondungeon list]\n"
|
||||
source[currentDungeonDimensionInfoPath] = `[dimension dungeon info]
|
||||
[/dimension dungeon info]
|
||||
[dungeon difficulty condition info]
|
||||
1 70 2 80 3 85
|
||||
[/dungeon difficulty condition info]
|
||||
[drop item Info]
|
||||
0 0 3228
|
||||
0 1 3229
|
||||
[/drop item Info]
|
||||
`
|
||||
source[dungeonDropStackableList] = "3227 `material/test_drop.stk`\n3228 `material/test_dimension_drop.stk`\n3229 `material/test_dimension_drop_2.stk`\n"
|
||||
source["stackable/material/test_dimension_drop.stk"] = "[name]\n`Test Dimension Drop`\n[stackable type]\n`[material]`\n[stack limit]\n999\n[attach type]\n`[free]`\n"
|
||||
source["stackable/material/test_dimension_drop_2.stk"] = "[name]\n`Test Dimension Drop 2`\n[stackable type]\n`[material]`\n[stack limit]\n999\n[attach type]\n`[free]`\n"
|
||||
currentDungeonMonsterDropRulesByCatalog.Delete(service.dungeonMonsterTable)
|
||||
currentDungeonGoldReferencesByCatalog.Delete(service.dungeonMonsterTable)
|
||||
currentDungeonDimensionDropsByMonsterCatalog.Delete(service.dungeonMonsterTable)
|
||||
|
||||
runtime.Room.mu.Lock()
|
||||
runtime.Room.monsters[0].Spawn.Rank = rank
|
||||
runtime.Room.mu.Unlock()
|
||||
monster := runtime.Room.Snapshot().Monsters[0]
|
||||
wires, err := service.planCurrentDungeonMonsterDrops(runtime, monster, currentSceneActorObjectKey(99))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dimensionRows := 0
|
||||
for _, wire := range wires {
|
||||
itemID := binary.LittleEndian.Uint32(wire.Item.data[0x02:0x06])
|
||||
if itemID == 3228 || itemID == 3229 {
|
||||
dimensionRows++
|
||||
}
|
||||
}
|
||||
if dimensionRows != 2 {
|
||||
t.Fatalf("special rank %s emitted %d dimension rows, want both PVF tier/group rows: wires=%d", rank, dimensionRows, len(wires))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentDungeonMonsterPlanMapsEquipmentRollToCareerChroniclePool(t *testing.T) {
|
||||
service, runtime, _ := prepareCurrentDungeonDropTest(t, 1, 3227)
|
||||
source := service.dungeonMonsterTable.source.(bridgePVFSource)
|
||||
rules := currentDungeonGoldDropTestRuleText()
|
||||
rules = strings.Replace(rules, "1 200 10000 0 0 0 0", "1 200 0 0 10000 0 0", 1)
|
||||
rules = strings.Replace(rules, "[item drop ref table]\n1 0 3", "[item drop ref table]\n20 0 3", 1)
|
||||
source[dungeonCardDropRulePath] = rules
|
||||
source[dungeonCardGoldReferencePath] = "[gold drop ref table]\n20 100 0\n"
|
||||
source[currentDungeonDimensionGatePath] = "[dimensiondungeon list]\n700 25 0\n[/dimensiondungeon list]\n"
|
||||
source[currentDungeonDimensionInfoPath] = `[dimension dungeon info]
|
||||
[/dimension dungeon info]
|
||||
[dungeon difficulty condition info]
|
||||
1 70 2 80 3 85
|
||||
[/dungeon difficulty condition info]
|
||||
[drop item Info]
|
||||
0 0 3228
|
||||
[/drop item Info]
|
||||
`
|
||||
source[currentDungeonDimensionDropListPath] = `[chronicle grow type]
|
||||
0 1
|
||||
[normal chronicle list]
|
||||
9101 9102
|
||||
[/normal chronicle list]
|
||||
[set chronicle list]
|
||||
9201
|
||||
[/set chronicle list]
|
||||
[/chronicle grow type]
|
||||
`
|
||||
source[currentDungeonDimensionDropExcludePath] = "[exclude list]\n9102\n[/exclude list]\n"
|
||||
source[currentDungeonClearRewardRulePath] = "[chronicle set reward rate]\n0 50 0\n[/chronicle set reward rate]\n"
|
||||
source[dungeonDropStackableList] = "3227 `material/test_drop.stk`\n3228 `material/test_dimension_drop.stk`\n"
|
||||
source["stackable/material/test_dimension_drop.stk"] = "[name]\n`Test Dimension Drop`\n[stackable type]\n`[material]`\n[stack limit]\n999\n[attach type]\n`[free]`\n"
|
||||
source[dungeonDropEquipmentList] = "9101 `chronicle/test_normal.equ`\n9102 `chronicle/test_excluded.equ`\n9201 `chronicle/test_set.equ`\n"
|
||||
for _, itemPath := range []string{"equipment/chronicle/test_normal.equ", "equipment/chronicle/test_excluded.equ", "equipment/chronicle/test_set.equ"} {
|
||||
source[itemPath] = "[name]\n`Test Chronicle`\n[rarity]\n5\n[minimum level]\n20\n[durability]\n50\n[attach type]\n`[trade]`\n[equipment type]\n`[weapon]`\n"
|
||||
}
|
||||
currentDungeonMonsterDropRulesByCatalog.Delete(service.dungeonMonsterTable)
|
||||
currentDungeonGoldReferencesByCatalog.Delete(service.dungeonMonsterTable)
|
||||
currentDungeonDimensionDropsByMonsterCatalog.Delete(service.dungeonMonsterTable)
|
||||
loadedRules, err := currentDungeonMonsterDropRules(service.dungeonMonsterTable)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if row, found := loadedRules.Probability(20); !found || row.Rates[2] != 10000 {
|
||||
t.Fatalf("equipment probability row=%+v found=%t", row, found)
|
||||
}
|
||||
if row, found := loadedRules.ItemReference(20); !found {
|
||||
t.Fatalf("equipment item-reference row for level 20 is missing")
|
||||
} else if row.ValueA != 0 || row.ValueB != 3 {
|
||||
t.Fatalf("equipment item-reference row=%+v", row)
|
||||
}
|
||||
dimensionCatalog, err := currentDungeonDimensionDrops(service.dungeonMonsterTable)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pool := dimensionCatalog.ChronicleByCareer[currentDungeonDimensionChronicleKey{Job: 0, GrowType: 1}]; !reflect.DeepEqual(pool.Normal, []uint32{9101}) || !reflect.DeepEqual(pool.Set, []uint32{9201}) {
|
||||
t.Fatalf("career chronicle pool=%+v", pool)
|
||||
}
|
||||
|
||||
runtime.Character.Job = "0"
|
||||
runtime.Character.Stats["grow_type"] = 1
|
||||
runtime.Room.mu.Lock()
|
||||
runtime.Room.monsters[0].Spawn.RandomDropCount = 1
|
||||
runtime.Room.monsters[0].Spawn.Rank = "[boss]"
|
||||
runtime.Room.mu.Unlock()
|
||||
monster := runtime.Room.Snapshot().Monsters[0]
|
||||
if monster.Spawn.RandomDropCount != 1 || monster.Spawn.Rank != "[boss]" {
|
||||
t.Fatalf("monster random_drop_count=%d rank=%q", monster.Spawn.RandomDropCount, monster.Spawn.Rank)
|
||||
}
|
||||
wires, err := service.planCurrentDungeonMonsterDrops(runtime, monster, currentSceneActorObjectKey(99))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
counts := make(map[uint32]int)
|
||||
for _, wire := range wires {
|
||||
counts[binary.LittleEndian.Uint32(wire.Item.data[0x02:0x06])]++
|
||||
}
|
||||
if counts[9101] != 1 || counts[9102] != 0 || counts[9201] != 0 {
|
||||
t.Fatalf("career chronicle equipment rows=%v", counts)
|
||||
}
|
||||
dimensionCatalog.ChronicleRates[0] = currentDungeonDimensionChronicleRate{RewardPercent: 100, SetPercent: 100}
|
||||
itemCatalog, err := service.dungeonMonsterTable.DropCatalog()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
card, err := currentDungeonRandomDimensionCardRewardBundle(
|
||||
newCurrentDungeonDropLCG(1),
|
||||
currentDungeonGoldReference{Level: 20, Base: 100},
|
||||
currentDungeonCardGoldTerms{Difficulty: 1, PartyMembers: 1, PartyRate: 1, WeightedKills: 40},
|
||||
itemCatalog,
|
||||
dimensionCatalog,
|
||||
700,
|
||||
20,
|
||||
0,
|
||||
1,
|
||||
time.Unix(0, 0).UTC(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(card.Items) != 1 || card.Items[0].ItemID != 9201 || card.Items[0].Stackable {
|
||||
t.Fatalf("career chronicle clear-card reward=%+v", card)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentDungeonDimensionDropsAreDerivedFromPVF(t *testing.T) {
|
||||
source := bridgePVFSource{
|
||||
currentDungeonDimensionGatePath: `[dimensiondungeon list]
|
||||
62 25 0 65 25 0 67 25 0
|
||||
[/dimensiondungeon list]
|
||||
`,
|
||||
currentDungeonDimensionInfoPath: `[dimension dungeon info]
|
||||
0 7165 1 7166 2 7167
|
||||
[/dimension dungeon info]
|
||||
[dungeon difficulty condition info]
|
||||
1 70 2 80 3 85
|
||||
[/dungeon difficulty condition info]
|
||||
[drop item Info]
|
||||
0 0 3311
|
||||
1 0 3311
|
||||
1 1 10303569
|
||||
2 0 3311
|
||||
2 1 10303569
|
||||
[/drop item Info]
|
||||
`,
|
||||
}
|
||||
catalog, err := loadCurrentDungeonDimensionDrops(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tier, pool, matched, err := currentDungeonDimensionDropPool(catalog, 62, 90)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []dungeonMonsterDropPoolEntry{{ItemID: 3311, Weight: 100}, {ItemID: 10303569, Weight: 100}}
|
||||
if !matched || tier != 2 || !reflect.DeepEqual(pool, want) {
|
||||
t.Fatalf("legacy dimension pool matched=%t tier=%d pool=%v want=%v", matched, tier, pool, want)
|
||||
}
|
||||
|
||||
tier, pool, matched, err = currentDungeonDimensionDropPool(catalog, 7166, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !matched || tier != 1 || !reflect.DeepEqual(pool, want) {
|
||||
t.Fatalf("exact dimension pool matched=%t tier=%d pool=%v", matched, tier, pool)
|
||||
}
|
||||
|
||||
_, pool, matched, err = currentDungeonDimensionDropPool(catalog, 66, 90)
|
||||
if err != nil || matched || len(pool) != 0 {
|
||||
t.Fatalf("PVF-nonmember dungeon matched=%t pool=%v err=%v", matched, pool, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentDungeonDimensionChronicleDropsUseCareerPoolsRatesAndExcludeList(t *testing.T) {
|
||||
source := bridgePVFSource{
|
||||
currentDungeonDimensionGatePath: `[dimensiondungeon list]
|
||||
62 25 0
|
||||
[/dimensiondungeon list]
|
||||
`,
|
||||
currentDungeonDimensionInfoPath: `[dimension dungeon info]
|
||||
0 7165 1 7166 2 7167
|
||||
[/dimension dungeon info]
|
||||
[dungeon difficulty condition info]
|
||||
1 70 2 80 3 85
|
||||
[/dungeon difficulty condition info]
|
||||
[drop item Info]
|
||||
0 0 3311 1 0 3311 1 1 10303569 2 0 3311 2 1 10303569
|
||||
[/drop item Info]
|
||||
`,
|
||||
currentDungeonDimensionDropListPath: `[chronicle grow type]
|
||||
0 1
|
||||
[normal chronicle list]
|
||||
125012 125013
|
||||
[/normal chronicle list]
|
||||
[set chronicle list]
|
||||
120229 120230
|
||||
[/set chronicle list]
|
||||
[/chronicle grow type]
|
||||
[chronicle grow type]
|
||||
0 2
|
||||
[normal chronicle list]
|
||||
105000
|
||||
[/normal chronicle list]
|
||||
[set chronicle list]
|
||||
100332
|
||||
[/set chronicle list]
|
||||
[/chronicle grow type]
|
||||
`,
|
||||
currentDungeonDimensionDropExcludePath: `[exclude list]
|
||||
125013 120230
|
||||
[/exclude list]
|
||||
`,
|
||||
currentDungeonClearRewardRulePath: `[chronicle set reward rate]
|
||||
0 50 0 1 70 5 2 95 100
|
||||
[/chronicle set reward rate]
|
||||
`,
|
||||
}
|
||||
catalog, err := loadCurrentDungeonDimensionDrops(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pool := catalog.ChronicleByCareer[currentDungeonDimensionChronicleKey{Job: 0, GrowType: 1}]
|
||||
if !reflect.DeepEqual(pool.Normal, []uint32{125012}) || !reflect.DeepEqual(pool.Set, []uint32{120229}) {
|
||||
t.Fatalf("filtered career pool=%+v", pool)
|
||||
}
|
||||
if got := catalog.ChronicleRates[2]; got != (currentDungeonDimensionChronicleRate{RewardPercent: 95, SetPercent: 100}) {
|
||||
t.Fatalf("tier-2 rate=%+v", got)
|
||||
}
|
||||
|
||||
rng := newCurrentDungeonDropLCG(1)
|
||||
tier, itemID, group, matched, selected, err := currentDungeonDimensionChronicleItem(catalog, 7167, 90, 0, 1, rng, false)
|
||||
if err != nil || !matched || !selected || tier != 2 || itemID != 120229 || group != "set" {
|
||||
t.Fatalf("chronicle selection tier=%d item=%d group=%q matched=%t selected=%t err=%v", tier, itemID, group, matched, selected, err)
|
||||
}
|
||||
|
||||
_, _, _, matched, selected, err = currentDungeonDimensionChronicleItem(catalog, 7167, 90, 0, 3, rng, false)
|
||||
if err != nil || !matched || selected {
|
||||
t.Fatalf("unknown career matched=%t selected=%t err=%v", matched, selected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentDungeonDimensionChroniclePVFShapeFailsClosed(t *testing.T) {
|
||||
base := bridgePVFSource{
|
||||
currentDungeonDimensionGatePath: "[dimensiondungeon list]\n62 25 0\n[/dimensiondungeon list]\n",
|
||||
currentDungeonDimensionInfoPath: `[dimension dungeon info]
|
||||
[/dimension dungeon info]
|
||||
[dungeon difficulty condition info]
|
||||
1 70
|
||||
[/dungeon difficulty condition info]
|
||||
[drop item Info]
|
||||
0 0 3311
|
||||
[/drop item Info]
|
||||
`,
|
||||
currentDungeonDimensionDropListPath: `[chronicle grow type]
|
||||
0 1
|
||||
[normal chronicle list]
|
||||
125012
|
||||
[/normal chronicle list]
|
||||
[set chronicle list]
|
||||
120229
|
||||
[/set chronicle list]
|
||||
[/chronicle grow type]
|
||||
`,
|
||||
currentDungeonDimensionDropExcludePath: "[exclude list]\n[/exclude list]\n",
|
||||
currentDungeonClearRewardRulePath: "[chronicle set reward rate]\n0 101 0\n[/chronicle set reward rate]\n",
|
||||
}
|
||||
if _, err := loadCurrentDungeonDimensionDrops(base); err == nil {
|
||||
t.Fatal("out-of-range chronicle reward rate was accepted")
|
||||
}
|
||||
|
||||
delete(base, currentDungeonDimensionDropExcludePath)
|
||||
base[currentDungeonClearRewardRulePath] = "[chronicle set reward rate]\n0 50 0\n[/chronicle set reward rate]\n"
|
||||
if _, err := loadCurrentDungeonDimensionDrops(base); err == nil {
|
||||
t.Fatal("chronicle drop list without its exclusion table was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentDungeonDimensionDropPVFShapeFailsClosed(t *testing.T) {
|
||||
_, err := loadCurrentDungeonDimensionDrops(bridgePVFSource{
|
||||
currentDungeonDimensionGatePath: "[dimensiondungeon list]\n62 25\n[/dimensiondungeon list]\n",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("malformed dimension-dungeon tuple was accepted")
|
||||
}
|
||||
|
||||
catalog := ¤tDungeonDimensionDropCatalog{
|
||||
LegacyDungeons: map[int64]struct{}{62: {}},
|
||||
ItemsByTier: map[int][]currentDungeonDimensionDropEntry{},
|
||||
}
|
||||
if _, _, _, err := currentDungeonDimensionDropPool(catalog, 62, 90); err == nil {
|
||||
t.Fatal("dimension member without an item tier was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealScriptPVFDimensionDungeonDropsResolve(t *testing.T) {
|
||||
pvfPath := os.Getenv("DNF_WORLDMAP_REAL_PVF_SMOKE")
|
||||
if pvfPath == "" {
|
||||
t.Skip("set DNF_WORLDMAP_REAL_PVF_SMOKE to audit real dimension-dungeon drops")
|
||||
}
|
||||
archive, err := platformpvf.Open(pvfPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := worldmap.LoadSource(context.Background(), archive, worldmap.Options{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
monsterCatalog, err := newPVFDungeonMonsterCatalog(archive)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dimensionCatalog, err := currentDungeonDimensionDrops(monsterCatalog)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
itemCatalog, err := monsterCatalog.DropCatalog()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(dimensionCatalog.ChronicleByCareer) == 0 || len(dimensionCatalog.ChronicleRates) != 3 {
|
||||
t.Fatalf("chronicle career pools=%d rates=%v", len(dimensionCatalog.ChronicleByCareer), dimensionCatalog.ChronicleRates)
|
||||
}
|
||||
for key, pool := range dimensionCatalog.ChronicleByCareer {
|
||||
for group, entries := range map[string][]uint32{"normal": pool.Normal, "set": pool.Set} {
|
||||
for _, itemID := range entries {
|
||||
definition, resolveErr := itemCatalog.ResolveItem(itemID)
|
||||
if resolveErr != nil {
|
||||
t.Fatalf("chronicle job=%d grow=%d group=%s item=%d: %v", key.Job, key.GrowType, group, itemID, resolveErr)
|
||||
}
|
||||
if definition.Kind != dungeonDropItemEquipment || definition.Rarity != 5 {
|
||||
t.Fatalf("chronicle job=%d grow=%d group=%s item=%d kind=%s rarity=%d", key.Job, key.GrowType, group, itemID, definition.Kind, definition.Rarity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, dungeonID := range []int64{62, 63, 64, 65, 67} {
|
||||
tier, pool, matched, resolveErr := currentDungeonDimensionDropPool(dimensionCatalog, dungeonID, 90)
|
||||
if resolveErr != nil || !matched || len(pool) == 0 {
|
||||
t.Fatalf("dungeon=%d matched=%t tier=%d pool=%v err=%v", dungeonID, matched, tier, pool, resolveErr)
|
||||
}
|
||||
for _, entry := range pool {
|
||||
if _, resolveErr := itemCatalog.ResolveItem(entry.ItemID); resolveErr != nil {
|
||||
t.Fatalf("dungeon=%d tier=%d item=%d: %v", dungeonID, tier, entry.ItemID, resolveErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, _, matched, err := currentDungeonDimensionDropPool(dimensionCatalog, 66, 90); err != nil || matched {
|
||||
t.Fatalf("current PVF intentionally omits dungeon 66 from dimension membership: matched=%t err=%v", matched, err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user