Files
90CNServer/server/internal/services/dnfbridge/anton_reward_pvf_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

701 lines
30 KiB
Go

package dnfbridge
import (
"errors"
"fmt"
"math"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
dnfraid "longheng.io/server/internal/modules/dnf/raid"
platformpvf "longheng.io/server/internal/platform/pvf"
)
func TestRealScriptPVFLukeSingleRewardPoolsResolveFinalItems(t *testing.T) {
pvfPath := realLukeRewardPVFPath()
archive, err := platformpvf.Open(pvfPath)
if err != nil {
t.Skipf("runtime Script.pvf unavailable: %v", err)
}
config, err := dnfraid.LoadLukeConfig(archive)
if err != nil {
t.Fatalf("load Luke raid config: %v", err)
}
items, err := newPVFDungeonDropCatalog(archive)
if err != nil {
t.Fatalf("load real PVF item catalog: %v", err)
}
catalog, err := newRaidRewardPVFCatalog(items, config.Raid, nil, false, currentLukeRewardPhaseCount)
if err != nil {
t.Fatalf("load Luke reward catalog: %v", err)
}
singlePhaseIndex := len(config.Raid.Phases)
for _, kind := range []string{"party_card", "squad_item"} {
pool, found := catalog.Pool(singlePhaseIndex, kind)
if !found || len(pool.Entries) == 0 || pool.TotalWeight == 0 {
t.Fatalf("Luke single %s pool=%+v found=%t", kind, pool, found)
}
resolved, resolveErr := catalog.ResolvePool(singlePhaseIndex, kind, &antonRewardPVFSequenceRNG{})
if resolveErr != nil {
t.Fatalf("resolve Luke single %s: %v", kind, resolveErr)
}
definition, definitionErr := catalog.ResolveFinalItem(resolved.ItemID)
if definitionErr != nil {
t.Fatalf("resolve Luke single %s final item %d: %v", kind, resolved.ItemID, definitionErr)
}
if definition.ItemID != resolved.ItemID || resolved.Count == 0 || resolved.ItemPVFPath == "" {
t.Fatalf("Luke single %s resolved=%+v definition=%+v", kind, resolved, definition)
}
}
}
func TestRealScriptPVFLukeSingleRewardDefinitionsIncludeDarkRootRows(t *testing.T) {
pvfPath := strings.TrimSpace(os.Getenv("DNF_LUKE_REWARD_REAL_PVF_SMOKE"))
if pvfPath == "" {
t.Skip("set DNF_LUKE_REWARD_REAL_PVF_SMOKE to verify transplanted Luke reward rows")
}
archive, err := platformpvf.Open(pvfPath)
if err != nil {
t.Skipf("runtime Script.pvf unavailable: %v", err)
}
for itemID, want := range map[uint32][]int64{
10302724: {10159143, 100000, 4, 10159143, 100000, 5, 10159143, 100000, 6},
10302725: {10159143, 100000, 2, 10159143, 100000, 3},
} {
documentPath := fmt.Sprintf("stackable/10302001/%d.stk", itemID)
text, readErr := archive.ReadText(documentPath)
if readErr != nil {
t.Fatalf("read Luke single reward %d: %v", itemID, readErr)
}
document, parseErr := dnfpvf.Parse(documentPath, text)
if parseErr != nil {
t.Fatalf("parse Luke single reward %d: %v", itemID, parseErr)
}
if got := document.Ints("int data"); !reflect.DeepEqual(got, want) {
t.Fatalf("Luke single reward %d int data=%v want=%v", itemID, got, want)
}
}
const darkRootBoosterRow = "1 10159143 36500 5 10159143 33255 6 10159143 30250 7"
for itemID := uint32(10302726); itemID <= 10302733; itemID++ {
documentPath := fmt.Sprintf("stackable/10302001/%d.stk", itemID)
text, readErr := archive.ReadText(documentPath)
if readErr != nil {
t.Fatalf("read Luke single reward %d: %v", itemID, readErr)
}
normalized := strings.Join(strings.Fields(text), " ")
if !strings.Contains(normalized, "[booster info]") || !strings.Contains(normalized, darkRootBoosterRow) {
t.Fatalf("Luke single reward %d is missing the 5-7 dark-root booster row", itemID)
}
}
}
func realLukeRewardPVFPath() string {
if configured := strings.TrimSpace(os.Getenv("DNF_LUKE_REWARD_REAL_PVF_SMOKE")); configured != "" {
return configured
}
return filepath.Clean(filepath.Join("..", "..", "..", "..", "runtime", "data", "dnf", "Script.pvf"))
}
func TestAntonRewardPVFCatalogReadsOnlyCurrentDefinitionsAndBuildsPhasePools(t *testing.T) {
items, source, finalByDefinition := newAntonRewardPVFTestItems(t)
catalog, err := newAntonRewardPVFCatalog(items, antonRewardPVFTestConfig())
if err != nil {
t.Fatal(err)
}
expectedPaths := antonRewardPVFTestDefinitionPaths(t, items.itemRefs)
if !reflect.DeepEqual(source.paths, expectedPaths) {
t.Fatalf("read paths=%v\nwant=%v", source.paths, expectedPaths)
}
if got := catalog.DefinitionIDs(); !reflect.DeepEqual(got, currentAntonRewardDefinitionOrder[:]) {
t.Fatalf("definition ids=%v", got)
}
if snapshot := catalog.Snapshot(); snapshot != (antonRewardPVFCatalogSnapshot{
Definitions: 12,
DefinitionEntries: 13,
PhasePools: 6,
PhasePoolEntries: 13,
}) {
t.Fatalf("snapshot=%+v", snapshot)
}
for index, definitionItemID := range currentAntonRewardDefinitionOrder {
definition, found := catalog.Definition(definitionItemID)
if !found || definition.DefinitionItemID != definitionItemID || definition.Kind != dungeonDropItemStackable || definition.PVFPath != expectedPaths[index] || definition.TotalWeight == 0 || len(definition.Entries) == 0 {
t.Fatalf("definition=%d got=%+v found=%t", definitionItemID, definition, found)
}
for _, entry := range definition.Entries {
if entry.ItemID == definitionItemID {
t.Fatalf("definition item leaked into its final table: %+v", definition)
}
}
resolved, resolveErr := catalog.ResolveDefinition(definitionItemID, &antonRewardPVFSequenceRNG{})
if resolveErr != nil {
t.Fatalf("resolve definition=%d: %v", definitionItemID, resolveErr)
}
if resolved.ItemID != finalByDefinition[definitionItemID] || resolved.Count == 0 || resolved.DefinitionItemID != definitionItemID || resolved.ItemPVFPath == "" {
t.Fatalf("definition=%d resolved=%+v", definitionItemID, resolved)
}
}
phaseOneSquad, found := catalog.Pool(0, " SQUAD_ITEM ")
if !found || phaseOneSquad.TotalWeight != 100 || !reflect.DeepEqual(phaseOneSquad.Entries, []antonRewardPVFPoolEntry{
{DefinitionItemID: 10157773, Rank: -1, Weight: 50, Flag: 0},
{DefinitionItemID: 10157775, Rank: -1, Weight: 1, Flag: 1},
{DefinitionItemID: 10157776, Rank: -1, Weight: 1, Flag: 1},
{DefinitionItemID: 10157777, Rank: -1, Weight: 48, Flag: 1},
}) {
t.Fatalf("phase one squad=%+v found=%t", phaseOneSquad, found)
}
phaseTwoSquad, found := catalog.Pool(1, "squad_item")
if !found || phaseTwoSquad.TotalWeight != 1000 || !reflect.DeepEqual(phaseTwoSquad.Entries, []antonRewardPVFPoolEntry{
{DefinitionItemID: 10161936, Rank: -1, Weight: 775, Flag: 0},
{DefinitionItemID: 10157783, Rank: -1, Weight: 145, Flag: 1},
{DefinitionItemID: 10157784, Rank: -1, Weight: 50, Flag: 1},
{DefinitionItemID: 10157785, Rank: -1, Weight: 25, Flag: 1},
{DefinitionItemID: 10157786, Rank: -1, Weight: 5, Flag: 1},
}) {
t.Fatalf("phase two squad=%+v found=%t", phaseTwoSquad, found)
}
if party, ok := catalog.Pool(0, "party_card"); !ok || len(party.Entries) != 1 || party.Entries[0].DefinitionItemID != 10094735 {
t.Fatalf("phase one party=%+v found=%t", party, ok)
}
if pcroom, ok := catalog.Pool(1, "pcroom_card"); !ok || len(pcroom.Entries) != 1 || pcroom.Entries[0].DefinitionItemID != 10094733 {
t.Fatalf("phase two pcroom=%+v found=%t", pcroom, ok)
}
}
func TestAntonRewardPVFPoolsIncludeSingleModeAfterTeamPhases(t *testing.T) {
config := antonRewardPVFTestConfig()
config.SingleMode = &dnfraid.AntonSingleModeConfig{Phase: dnfraid.AntonPhase{Rewards: []dnfraid.AntonStateReward{
{Kind: "party_card", Rank: -1, Weight: 100, DefinitionItemID: 10164076},
{Kind: "squad_item", Rank: -1, Weight: 900, DefinitionItemID: 10164077},
}}}
pools, _, err := buildAntonRewardPVFPools(config)
if err != nil {
t.Fatal(err)
}
if pool, found := pools[antonRewardPVFPoolKey{phaseIndex: len(config.Phases), kind: "party_card"}]; !found || len(pool.Entries) != 1 || pool.Entries[0].DefinitionItemID != 10164076 {
t.Fatalf("single party pool=%+v found=%t", pool, found)
}
if pool, found := pools[antonRewardPVFPoolKey{phaseIndex: len(config.Phases), kind: "squad_item"}]; !found || len(pool.Entries) != 1 || pool.Entries[0].DefinitionItemID != 10164077 {
t.Fatalf("single squad pool=%+v found=%t", pool, found)
}
if got := antonRewardDefinitionOrder(config); len(got) != len(currentAntonRewardDefinitionOrder)+2 || got[len(got)-2] != 10164076 || got[len(got)-1] != 10164077 {
t.Fatalf("single definition order=%v", got)
}
}
func TestAntonRewardPVFWeightedResolveUsesInjectedRNGAndOneNestedDefinition(t *testing.T) {
items, source, finalByDefinition := newAntonRewardPVFTestItems(t)
outerPath := mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)
nestedPath := mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094733)
firstFinal := finalByDefinition[10094733]
secondFinal := uint32(29000001)
items.itemRefs[secondFinal] = dungeonDropItemReference{kind: dungeonDropItemEquipment, path: "anton_reward/second_nested.equ"}
source.documents[outerPath] = "[int data]\n10094733 1 2\n[/int data]\n"
source.documents[nestedPath] = fmt.Sprintf("[int data]\n%d 2 3 %d 3 4\n[/int data]\n", firstFinal, secondFinal)
catalog, err := newAntonRewardPVFCatalog(items, antonRewardPVFTestConfig())
if err != nil {
t.Fatal(err)
}
if snapshot := catalog.Snapshot(); snapshot.NestedReferences != 1 {
t.Fatalf("snapshot=%+v", snapshot)
}
first, err := catalog.ResolveDefinition(10094735, &antonRewardPVFSequenceRNG{values: []uint64{0, 0}})
if err != nil {
t.Fatal(err)
}
if first.DefinitionItemID != 10094735 || first.NestedDefinitionItemID != 10094733 || first.ItemID != firstFinal || first.Count != 6 || first.ItemKind != dungeonDropItemEquipment {
t.Fatalf("first nested resolve=%+v", first)
}
second, err := catalog.ResolveDefinition(10094735, &antonRewardPVFSequenceRNG{values: []uint64{0, 2}})
if err != nil {
t.Fatal(err)
}
if second.NestedDefinitionItemID != 10094733 || second.ItemID != secondFinal || second.Count != 8 || second.ItemKind != dungeonDropItemEquipment || second.ItemPVFPath != "equipment/anton_reward/second_nested.equ" {
t.Fatalf("second nested resolve=%+v", second)
}
selected, err := catalog.ResolvePool(0, "squad_item", &antonRewardPVFSequenceRNG{values: []uint64{50, 0}})
if err != nil {
t.Fatal(err)
}
if selected.DefinitionItemID != 10157775 || selected.PoolWeight != 1 || selected.Flag != 1 || selected.PhaseIndex != 0 || selected.PoolKind != "squad_item" {
t.Fatalf("phase pool roll 50=%+v", selected)
}
selected, err = catalog.ResolvePool(0, "squad_item", &antonRewardPVFSequenceRNG{values: []uint64{52, 0}})
if err != nil {
t.Fatal(err)
}
if selected.DefinitionItemID != 10157777 || selected.PoolWeight != 48 {
t.Fatalf("phase pool roll 52=%+v", selected)
}
}
func TestRaidRewardPVFDirectBoosterDefinitionGrantsTheBoxItself(t *testing.T) {
items, source, _ := newAntonRewardPVFTestItems(t)
definitionItemID := currentAntonRewardDefinitionOrder[0]
definitionPath := mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, definitionItemID)
// Luke's current reward definitions are booster boxes with one or more
// [etc] groups. Those rows describe what opening the box may contain; the
// Raid reward itself is the box and must not pre-open that second RNG layer.
source.documents[definitionPath] = "[booster]\n[booster info]\n[etc]\n1 20000001 1000 3\n[etc]\n1 20000002 1000 1\n"
catalog, err := newAntonRewardPVFCatalog(items, antonRewardPVFTestConfig())
if err != nil {
t.Fatal(err)
}
definition, found := catalog.Definition(definitionItemID)
if !found || !definition.DirectFinal || len(definition.Entries) != 1 ||
definition.Entries[0] != (antonRewardPVFEntry{ItemID: definitionItemID, Weight: 1, Count: 1}) {
t.Fatalf("direct booster definition=%+v found=%t", definition, found)
}
resolved, err := catalog.ResolveDefinition(definitionItemID, &antonRewardPVFSequenceRNG{})
if err != nil {
t.Fatal(err)
}
if resolved.ItemID != definitionItemID || resolved.Count != 1 || resolved.ItemPVFPath != definition.PVFPath {
t.Fatalf("direct booster resolved=%+v definition=%+v", resolved, definition)
}
}
func TestAntonRewardPVFResolveFinalItemReadsOnlySelectedDocumentAndCachesMetadata(t *testing.T) {
items, source, finalByDefinition := newAntonRewardPVFTestItems(t)
catalog, err := newAntonRewardPVFCatalog(items, antonRewardPVFTestConfig())
if err != nil {
t.Fatal(err)
}
finalItemID := finalByDefinition[10094735]
reference := items.itemRefs[finalItemID]
finalPath, err := antonRewardPVFReferencePath(reference)
if err != nil {
t.Fatal(err)
}
source.documents[finalPath] = "[stackable type]\n`[material]`\n[stack limit]\n999\n"
source.paths = nil
first, err := catalog.ResolveFinalItem(finalItemID)
if err != nil {
t.Fatal(err)
}
second, err := catalog.ResolveFinalItem(finalItemID)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(first, second) || first.ItemID != finalItemID || first.Kind != dungeonDropItemStackable || first.PVFPath != finalPath || first.StackableType != "[material]" || first.StackLimit != 999 || first.SlotStart != 121 || first.SlotEnd != 176 {
t.Fatalf("final metadata first=%+v second=%+v", first, second)
}
if !reflect.DeepEqual(source.paths, []string{finalPath}) {
t.Fatalf("final item reads=%v want exact cached path %q", source.paths, finalPath)
}
if _, err := catalog.ResolveFinalItem(10094735); !errors.Is(err, errAntonRewardPVFNestingInvalid) {
t.Fatalf("definition-as-final error=%v", err)
}
}
func TestAntonRewardPVFCatalogRejectsMalformedDefinitionTablesAndUnresolvedFinals(t *testing.T) {
tests := []struct {
name string
mutate func(*testing.T, *pvfDungeonDropCatalog, *antonRewardPVFRecordingSource)
want error
}{
{
name: "missing int data",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[name]\n`missing`\n"
},
want: errAntonRewardPVFSectionShape,
},
{
name: "duplicate int data",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
final := uint32(20000001)
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = fmt.Sprintf("[int data]\n%d 1 1\n[/int data]\n[int data]\n%d 1 1\n[/int data]\n", final, final)
},
want: errAntonRewardPVFSectionShape,
},
{
name: "unclosed int data",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[int data]\n20000001 1 1\n"
},
want: errAntonRewardPVFSectionShape,
},
{
name: "partial triple",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[int data]\n20000001 1\n[/int data]\n"
},
want: errAntonRewardPVFSectionShape,
},
{
name: "non integer token",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[int data]\n20000001 1 `one`\n[/int data]\n"
},
want: errAntonRewardPVFSectionShape,
},
{
name: "zero item",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[int data]\n0 1 1\n[/int data]\n"
},
want: errAntonRewardPVFValueInvalid,
},
{
name: "zero weight",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[int data]\n20000001 0 1\n[/int data]\n"
},
want: errAntonRewardPVFValueInvalid,
},
{
name: "zero count",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[int data]\n20000001 1 0\n[/int data]\n"
},
want: errAntonRewardPVFValueInvalid,
},
{
name: "unresolved final item",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[int data]\n29999999 1 1\n[/int data]\n"
},
want: errAntonRewardPVFFinalItemUnresolved,
},
{
name: "self reference",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[int data]\n10094735 1 1\n[/int data]\n"
},
want: errAntonRewardPVFNestingInvalid,
},
{
name: "more than one nested definition",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = "[int data]\n10094733 1 1\n[/int data]\n"
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094733)] = "[int data]\n10157773 1 1\n[/int data]\n"
},
want: errAntonRewardPVFNestingInvalid,
},
{
name: "nested count overflow",
mutate: func(t *testing.T, items *pvfDungeonDropCatalog, source *antonRewardPVFRecordingSource) {
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094735)] = fmt.Sprintf("[int data]\n10094733 1 %d\n[/int data]\n", uint64(math.MaxUint32))
final := uint32(20000002)
source.documents[mustAntonRewardPVFTestDefinitionPath(t, items.itemRefs, 10094733)] = fmt.Sprintf("[int data]\n%d 1 2\n[/int data]\n", final)
},
want: errAntonRewardPVFValueInvalid,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
items, source, _ := newAntonRewardPVFTestItems(t)
test.mutate(t, items, source)
_, err := newAntonRewardPVFCatalog(items, antonRewardPVFTestConfig())
if !errors.Is(err, test.want) {
t.Fatalf("error=%v want errors.Is(%v)", err, test.want)
}
})
}
}
func TestAntonRewardPVFCatalogRejectsIncompleteOrUnexpectedConfigAndInvalidResolve(t *testing.T) {
if _, err := newAntonRewardPVFCatalog(nil, antonRewardPVFTestConfig()); !errors.Is(err, errAntonRewardPVFCatalogRequired) {
t.Fatalf("nil catalog error=%v", err)
}
items, _, _ := newAntonRewardPVFTestItems(t)
if _, err := newAntonRewardPVFCatalog(items, nil); !errors.Is(err, errAntonRewardPVFConfigRequired) {
t.Fatalf("nil config error=%v", err)
}
t.Run("missing current definition", func(t *testing.T) {
config := antonRewardPVFTestConfig()
config.Phases[1].Rewards = config.Phases[1].Rewards[:len(config.Phases[1].Rewards)-1]
if _, err := newAntonRewardPVFCatalog(items, config); !errors.Is(err, errAntonRewardPVFDefinitionSet) {
t.Fatalf("error=%v", err)
}
})
t.Run("unexpected definition", func(t *testing.T) {
config := antonRewardPVFTestConfig()
config.Phases[0].Rewards[0].DefinitionItemID = 42
if _, err := newAntonRewardPVFCatalog(items, config); !errors.Is(err, errAntonRewardPVFDefinitionSet) {
t.Fatalf("error=%v", err)
}
})
t.Run("nonpositive outer weight", func(t *testing.T) {
config := antonRewardPVFTestConfig()
config.Phases[0].Rewards[0].Weight = 0
if _, err := newAntonRewardPVFCatalog(items, config); !errors.Is(err, errAntonRewardPVFValueInvalid) {
t.Fatalf("error=%v", err)
}
})
catalog, err := newAntonRewardPVFCatalog(items, antonRewardPVFTestConfig())
if err != nil {
t.Fatal(err)
}
if _, err := catalog.ResolveDefinition(10094735, nil); !errors.Is(err, errAntonRewardPVFRNGRequired) {
t.Fatalf("nil definition RNG error=%v", err)
}
if _, err := catalog.ResolveDefinition(42, &antonRewardPVFSequenceRNG{}); !errors.Is(err, errAntonRewardPVFDefinitionMissing) {
t.Fatalf("unknown definition error=%v", err)
}
if _, err := catalog.ResolvePool(3, "squad_item", &antonRewardPVFSequenceRNG{}); !errors.Is(err, errAntonRewardPVFPoolUnresolved) {
t.Fatalf("unknown pool error=%v", err)
}
if _, err := catalog.ResolvePool(0, "squad_item", nil); !errors.Is(err, errAntonRewardPVFRNGRequired) {
t.Fatalf("nil pool RNG error=%v", err)
}
}
func TestRealScriptPVFAntonRewardCatalogReadsOnlyCurrentDefinitionDocuments(t *testing.T) {
pvfPath := os.Getenv("DNF_ANTON_REWARD_REAL_PVF_SMOKE")
if pvfPath == "" {
t.Skip("set DNF_ANTON_REWARD_REAL_PVF_SMOKE to verify current Anton reward definitions")
}
archive, err := platformpvf.Open(pvfPath)
if err != nil {
t.Fatal(err)
}
config, err := dnfraid.LoadAntonConfig(archive)
if err != nil {
t.Fatal(err)
}
itemRefs := antonRewardPVFRealItemRefs(t, archive)
expectedPaths := make([]string, 0, len(antonRewardDefinitionOrder(config)))
for _, definitionItemID := range antonRewardDefinitionOrder(config) {
expectedPaths = append(expectedPaths, mustAntonRewardPVFTestDefinitionPath(t, itemRefs, definitionItemID))
}
allowed := make(map[string]struct{}, len(expectedPaths))
for _, documentPath := range expectedPaths {
allowed[documentPath] = struct{}{}
}
source := &antonRewardPVFRecordingSource{delegate: archive, allowed: allowed}
items := &pvfDungeonDropCatalog{source: source, itemRefs: itemRefs}
catalog, err := newAntonRewardPVFCatalog(items, config)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(source.paths, expectedPaths) {
t.Fatalf("real definition reads=%v\nwant=%v", source.paths, expectedPaths)
}
if snapshot := catalog.Snapshot(); snapshot.Definitions != len(antonRewardDefinitionOrder(config)) ||
snapshot.PhasePools != 9 || snapshot.DefinitionEntries < 1128 || snapshot.PhasePoolEntries != 22 {
t.Fatalf("real snapshot=%+v", snapshot)
}
wantShape := map[uint32]struct {
entries int
weight uint64
}{
10094735: {entries: 4, weight: 106},
10094733: {entries: 1, weight: 100},
10157773: {entries: 6, weight: 600},
10157775: {entries: 130, weight: 13000},
10157776: {entries: 19, weight: 1260},
10157777: {entries: 907, weight: 67000},
10157778: {entries: 6, weight: 600},
10161936: {entries: 6, weight: 124},
10157783: {entries: 1, weight: 100},
10157784: {entries: 37, weight: 370},
10157785: {entries: 10, weight: 750},
10157786: {entries: 1, weight: 1000},
}
for _, definitionItemID := range currentAntonRewardDefinitionOrder {
definition, found := catalog.Definition(definitionItemID)
if !found || len(definition.Entries) == 0 || definition.TotalWeight == 0 {
t.Fatalf("real definition=%d got=%+v found=%t", definitionItemID, definition, found)
}
if shape, pinned := wantShape[definitionItemID]; pinned && (len(definition.Entries) != shape.entries || definition.TotalWeight != shape.weight) {
t.Fatalf("real definition=%d entries=%d weight=%d want=%+v", definitionItemID, len(definition.Entries), definition.TotalWeight, shape)
}
for entryIndex, entry := range definition.Entries {
if _, isDefinition := catalog.definitions[entry.ItemID]; isDefinition {
t.Fatalf("real definition=%d entry=%d points to definition=%d", definitionItemID, entryIndex, entry.ItemID)
}
if _, found := itemRefs[entry.ItemID]; !found {
t.Fatalf("real definition=%d entry=%d final item=%d is not indexed", definitionItemID, entryIndex, entry.ItemID)
}
}
t.Logf("anton reward definition=%d path=%s entries=%d total_weight=%d", definitionItemID, definition.PVFPath, len(definition.Entries), definition.TotalWeight)
}
if definition, _ := catalog.Definition(10094735); !reflect.DeepEqual(definition.Entries, []antonRewardPVFEntry{
{ItemID: 10093974, Weight: 100, Count: 5},
{ItemID: 10093974, Weight: 3, Count: 6},
{ItemID: 10093974, Weight: 2, Count: 7},
{ItemID: 10093974, Weight: 1, Count: 8},
}) {
t.Fatalf("real phase-one party card=%+v", definition.Entries)
}
if definition, _ := catalog.Definition(10161936); !reflect.DeepEqual(definition.Entries, []antonRewardPVFEntry{
{ItemID: 10093974, Weight: 100, Count: 10},
{ItemID: 10093974, Weight: 10, Count: 11},
{ItemID: 10093974, Weight: 5, Count: 12},
{ItemID: 10093974, Weight: 4, Count: 13},
{ItemID: 10093974, Weight: 3, Count: 14},
{ItemID: 10093974, Weight: 2, Count: 15},
}) {
t.Fatalf("real phase-two party definition=%+v", definition.Entries)
}
}
type antonRewardPVFRecordingSource struct {
documents map[string]string
delegate dnfpvf.Source
allowed map[string]struct{}
paths []string
}
func (source *antonRewardPVFRecordingSource) ReadText(documentPath string) (string, error) {
source.paths = append(source.paths, documentPath)
if source.allowed != nil {
if _, allowed := source.allowed[documentPath]; !allowed {
return "", fmt.Errorf("unexpected Anton reward PVF read %q", documentPath)
}
}
if source.documents != nil {
text, found := source.documents[documentPath]
if !found {
return "", fmt.Errorf("Anton reward PVF test document missing %q", documentPath)
}
return text, nil
}
if source.delegate == nil {
return "", fmt.Errorf("Anton reward PVF source missing %q", documentPath)
}
return source.delegate.ReadText(documentPath)
}
type antonRewardPVFSequenceRNG struct {
values []uint64
index int
}
func (rng *antonRewardPVFSequenceRNG) Uint64() uint64 {
if rng == nil || len(rng.values) == 0 {
return 0
}
if rng.index >= len(rng.values) {
return rng.values[len(rng.values)-1]
}
value := rng.values[rng.index]
rng.index++
return value
}
func newAntonRewardPVFTestItems(t *testing.T) (*pvfDungeonDropCatalog, *antonRewardPVFRecordingSource, map[uint32]uint32) {
t.Helper()
source := &antonRewardPVFRecordingSource{documents: make(map[string]string)}
itemRefs := make(map[uint32]dungeonDropItemReference, len(currentAntonRewardDefinitionOrder)*2+1)
finalByDefinition := make(map[uint32]uint32, len(currentAntonRewardDefinitionOrder))
for index, definitionItemID := range currentAntonRewardDefinitionOrder {
itemRefs[definitionItemID] = dungeonDropItemReference{
kind: dungeonDropItemStackable,
path: fmt.Sprintf("anton_reward/%d.stk", definitionItemID),
}
finalItemID := uint32(20000001 + index)
finalByDefinition[definitionItemID] = finalItemID
kind := dungeonDropItemStackable
extension := "stk"
if index%2 == 1 {
kind = dungeonDropItemEquipment
extension = "equ"
}
itemRefs[finalItemID] = dungeonDropItemReference{
kind: kind,
path: fmt.Sprintf("anton_reward/final_%d.%s", finalItemID, extension),
}
}
items := &pvfDungeonDropCatalog{source: source, itemRefs: itemRefs}
for index, definitionItemID := range currentAntonRewardDefinitionOrder {
documentPath := mustAntonRewardPVFTestDefinitionPath(t, itemRefs, definitionItemID)
finalItemID := finalByDefinition[definitionItemID]
if index == 0 {
extraFinal := uint32(29000000)
itemRefs[extraFinal] = dungeonDropItemReference{kind: dungeonDropItemStackable, path: "anton_reward/extra_final.stk"}
source.documents[documentPath] = fmt.Sprintf("[int data]\n%d 2 5 %d 3 7\n[/int data]\n", finalItemID, extraFinal)
continue
}
source.documents[documentPath] = fmt.Sprintf("[int data]\n%d %d %d\n[/int data]\n", finalItemID, index+1, index+1)
}
return items, source, finalByDefinition
}
func antonRewardPVFTestConfig() *dnfraid.AntonConfig {
return &dnfraid.AntonConfig{Phases: []dnfraid.AntonPhase{
{Rewards: []dnfraid.AntonStateReward{
{Kind: "party_card", Rank: -1, Weight: 100, DefinitionItemID: 10094735, Flag: 0},
{Kind: "pcroom_card", Rank: -1, Weight: 100, DefinitionItemID: 10094733, Flag: 0},
{Kind: "squad_item", Rank: -1, Weight: 50, DefinitionItemID: 10157773, Flag: 0},
{Kind: "squad_item", Rank: -1, Weight: 1, DefinitionItemID: 10157775, Flag: 1},
{Kind: "squad_item", Rank: -1, Weight: 1, DefinitionItemID: 10157776, Flag: 1},
{Kind: "squad_item", Rank: -1, Weight: 48, DefinitionItemID: 10157777, Flag: 1},
}},
{Rewards: []dnfraid.AntonStateReward{
{Kind: "party_card", Rank: -1, Weight: 100, DefinitionItemID: 10157778, Flag: 0},
{Kind: "pcroom_card", Rank: -1, Weight: 100, DefinitionItemID: 10094733, Flag: 0},
{Kind: "squad_item", Rank: -1, Weight: 775, DefinitionItemID: 10161936, Flag: 0},
{Kind: "squad_item", Rank: -1, Weight: 145, DefinitionItemID: 10157783, Flag: 1},
{Kind: "squad_item", Rank: -1, Weight: 50, DefinitionItemID: 10157784, Flag: 1},
{Kind: "squad_item", Rank: -1, Weight: 25, DefinitionItemID: 10157785, Flag: 1},
{Kind: "squad_item", Rank: -1, Weight: 5, DefinitionItemID: 10157786, Flag: 1},
}},
}}
}
func antonRewardPVFTestDefinitionPaths(t *testing.T, itemRefs map[uint32]dungeonDropItemReference) []string {
t.Helper()
paths := make([]string, 0, len(currentAntonRewardDefinitionOrder))
for _, definitionItemID := range currentAntonRewardDefinitionOrder {
paths = append(paths, mustAntonRewardPVFTestDefinitionPath(t, itemRefs, definitionItemID))
}
return paths
}
func mustAntonRewardPVFTestDefinitionPath(t *testing.T, itemRefs map[uint32]dungeonDropItemReference, definitionItemID uint32) string {
t.Helper()
reference, found := itemRefs[definitionItemID]
if !found {
t.Fatalf("definition item %d is not indexed", definitionItemID)
}
documentPath, err := antonRewardPVFReferencePath(reference)
if err != nil {
t.Fatalf("definition item %d path: %v", definitionItemID, err)
}
return documentPath
}
func antonRewardPVFRealItemRefs(t *testing.T, source dnfpvf.Source) map[uint32]dungeonDropItemReference {
t.Helper()
stackablePaths, _, err := loadDungeonDropList(source, dungeonDropStackableList)
if err != nil {
t.Fatal(err)
}
equipmentPaths, _, err := loadDungeonDropList(source, dungeonDropEquipmentList)
if err != nil {
t.Fatal(err)
}
itemRefs := make(map[uint32]dungeonDropItemReference, len(stackablePaths)+len(equipmentPaths))
for itemID, itemPath := range stackablePaths {
if itemID > 0 && itemID <= math.MaxUint32 {
itemRefs[uint32(itemID)] = dungeonDropItemReference{kind: dungeonDropItemStackable, path: itemPath}
}
}
for itemID, itemPath := range equipmentPaths {
if itemID <= 0 || itemID > math.MaxUint32 {
continue
}
if _, found := itemRefs[uint32(itemID)]; !found {
itemRefs[uint32(itemID)] = dungeonDropItemReference{kind: dungeonDropItemEquipment, path: itemPath}
}
}
return itemRefs
}