DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
hanfuit committed 2026-11-04 14:31:55 +08:00
commit 645b64fe21
4388 files changed
+1405457

No files matched your search

@@ -0,0 +1,733 @@
package dnfbridge
import (
"errors"
"fmt"
"math"
"path"
"strings"
"sync"
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
dnfraid "longheng.io/server/internal/modules/dnf/raid"
)
const currentAntonRewardPhaseCount = 2
// currentLukeRewardPhaseCount / currentFiendWarRewardPhaseCount 是这两张奖励表
// **各自**声明的阶段数。原来是拿安东的 2 当通用常量,超时空只有 1 个 [PHASE]
// (fiendwar.etc 的 `[PHASE MAX] 1`),于是它整条发奖在提交时被拒:
//
// 真机 2026-10-02 13:52:48(raid 12058625,16 分制里的第 2 局,5 分 39 秒通关):
// game-raid-reward-auto-flip-commit-failed flipped=4
// error=anton reward PVF definition set is invalid: phases=1 want=2
// → 四个人的翻牌都翻开了(flipped=4)却被整批回滚,客户端只看到一个「0」,
// 这就是操作员看到的「翻牌翻不开」。超时空的最后一次翻牌本来就是**攻坚结束**
// 的那一次(与卢克/安徒恩二阶段结束同一条路:movie → 倒计时 → 自动翻开 → 提交)。
const (
currentLukeRewardPhaseCount = 2
currentFiendWarRewardPhaseCount = 1
)
var currentAntonRewardDefinitionOrder = [...]uint32{
10094735,
10094733,
10157773,
10157775,
10157776,
10157777,
10157778,
10161936,
10157783,
10157784,
10157785,
10157786,
}
var (
errAntonRewardPVFCatalogRequired = errors.New("anton reward PVF item catalog is required")
errAntonRewardPVFConfigRequired = errors.New("anton reward PVF config is required")
errAntonRewardPVFDefinitionSet = errors.New("anton reward PVF definition set is invalid")
errAntonRewardPVFDefinitionMissing = errors.New("anton reward PVF definition item is unresolved")
errAntonRewardPVFSectionShape = errors.New("anton reward PVF int data shape is invalid")
errAntonRewardPVFValueInvalid = errors.New("anton reward PVF value is invalid")
errAntonRewardPVFFinalItemUnresolved = errors.New("anton reward PVF final item is unresolved")
errAntonRewardPVFNestingInvalid = errors.New("anton reward PVF nesting is invalid")
errAntonRewardPVFRNGRequired = errors.New("anton reward PVF RNG is required")
errAntonRewardPVFPoolUnresolved = errors.New("anton reward PVF pool is unresolved")
)
// antonRewardPVFRNG is deliberately tiny so runtime code can inject a seeded
// generator and tests can pin every outer and inner weighted choice.
type antonRewardPVFRNG interface {
Uint64() uint64
}
// antonRewardPVFEntry preserves one exact {final item, weight, count} triple
// from a definition item's [int data] section. Duplicate rows remain distinct.
type antonRewardPVFEntry struct {
ItemID uint32
Weight uint32
Count uint32
}
type antonRewardPVFDefinition struct {
DefinitionItemID uint32
Kind dungeonDropItemKind
PVFPath string
DirectFinal bool
Entries []antonRewardPVFEntry
TotalWeight uint64
}
type antonRewardPVFPoolEntry struct {
DefinitionItemID uint32
Rank int
Weight uint32
Flag byte
}
type antonRewardPVFPool struct {
PhaseIndex int
Kind string
Entries []antonRewardPVFPoolEntry
TotalWeight uint64
}
type antonRewardPVFPoolKey struct {
phaseIndex int
kind string
}
// antonRewardPVFResolved is a final grant instruction. DefinitionItemID and
// NestedDefinitionItemID are evidence only. ItemID is either a leaf from an
// [int data] table or the definition item itself when the PVF declares a
// directly grantable [booster info] reward box.
type antonRewardPVFResolved struct {
PhaseIndex int
PoolKind string
PoolWeight uint32
Rank int
Flag byte
DefinitionItemID uint32
DefinitionPVFPath string
NestedDefinitionItemID uint32
NestedDefinitionPVFPath string
ItemID uint32
Count uint32
ItemKind dungeonDropItemKind
ItemPVFPath string
}
type antonRewardPVFCatalogSnapshot struct {
Definitions int
DefinitionEntries int
PhasePools int
PhasePoolEntries int
NestedReferences int
}
// antonRewardPVFCatalog contains the current 90 Anton reward definitions for
// both the team pools and the native single-mode pool.
// It does not mutate inventory or own raid state.
type antonRewardPVFCatalog struct {
definitions map[uint32]antonRewardPVFDefinition
definitionOrder []uint32
pools map[antonRewardPVFPoolKey]antonRewardPVFPool
poolOrder []antonRewardPVFPoolKey
itemRefs map[uint32]dungeonDropItemReference
source dnfpvf.Source
finalMu sync.Mutex
finalCache map[uint32]dungeonDropItemDefinition
}
// newAntonRewardPVFCatalog reads the definition documents referenced by the
// current team and single-mode anton.etc pools. The existing item catalog has
// already paid the list-index cost; no final item document is opened here.
// newAntonRewardPVFCatalog reads the definition documents referenced by the
// 安东奖励表。期望的阶段数由**每个团本自己**给出(安东/卢克 2、超时空 1)。
func newAntonRewardPVFCatalog(items *pvfDungeonDropCatalog, config *dnfraid.AntonConfig) (*antonRewardPVFCatalog, error) {
return newRaidRewardPVFCatalog(items, config, currentAntonRewardDefinitionOrder[:], true, currentAntonRewardPhaseCount)
}
func newRaidRewardPVFCatalog(items *pvfDungeonDropCatalog, config *dnfraid.AntonConfig, requiredDefinitions []uint32, requireAll bool, expectedPhases int) (*antonRewardPVFCatalog, error) {
if items == nil {
return nil, errAntonRewardPVFCatalogRequired
}
if config == nil {
return nil, errAntonRewardPVFConfigRequired
}
items.mu.Lock()
source := items.source
itemRefs := make(map[uint32]dungeonDropItemReference, len(items.itemRefs))
for itemID, reference := range items.itemRefs {
itemRefs[itemID] = reference
}
items.mu.Unlock()
if source == nil {
return nil, errAntonRewardPVFCatalogRequired
}
pools, poolOrder, err := buildRaidRewardPVFPools(config, requiredDefinitions, requireAll, expectedPhases)
if err != nil {
return nil, err
}
definitionOrder := raidRewardDefinitionOrder(config, requiredDefinitions)
catalog := &antonRewardPVFCatalog{
definitions: make(map[uint32]antonRewardPVFDefinition, len(definitionOrder)),
definitionOrder: definitionOrder,
pools: pools,
poolOrder: poolOrder,
itemRefs: itemRefs,
source: source,
finalCache: make(map[uint32]dungeonDropItemDefinition),
}
for _, definitionItemID := range catalog.definitionOrder {
definition, loadErr := loadAntonRewardPVFDefinition(source, itemRefs, definitionItemID)
if loadErr != nil {
return nil, loadErr
}
catalog.definitions[definitionItemID] = definition
}
if err := catalog.validateFinalReferences(); err != nil {
return nil, err
}
return catalog, nil
}
func antonRewardDefinitionOrder(config *dnfraid.AntonConfig) []uint32 {
return raidRewardDefinitionOrder(config, currentAntonRewardDefinitionOrder[:])
}
func raidRewardDefinitionOrder(config *dnfraid.AntonConfig, requiredDefinitions []uint32) []uint32 {
order := append([]uint32(nil), requiredDefinitions...)
seen := make(map[uint32]struct{}, len(order))
for _, id := range order {
seen[id] = struct{}{}
}
appendRewards := func(rewards []dnfraid.AntonStateReward) {
for _, reward := range rewards {
if reward.DefinitionItemID == 0 {
continue
}
if _, exists := seen[reward.DefinitionItemID]; exists {
continue
}
seen[reward.DefinitionItemID] = struct{}{}
order = append(order, reward.DefinitionItemID)
}
}
if config != nil {
for _, phase := range config.Phases {
appendRewards(phase.Rewards)
}
if config.SingleMode != nil {
appendRewards(config.SingleMode.Phase.Rewards)
}
}
return order
}
func buildAntonRewardPVFPools(config *dnfraid.AntonConfig) (map[antonRewardPVFPoolKey]antonRewardPVFPool, []antonRewardPVFPoolKey, error) {
return buildRaidRewardPVFPools(config, currentAntonRewardDefinitionOrder[:], true, currentAntonRewardPhaseCount)
}
func buildRaidRewardPVFPools(config *dnfraid.AntonConfig, requiredDefinitions []uint32, requireAll bool, expectedPhases int) (map[antonRewardPVFPoolKey]antonRewardPVFPool, []antonRewardPVFPoolKey, error) {
if len(config.Phases) != expectedPhases {
return nil, nil, fmt.Errorf("%w: phases=%d want=%d", errAntonRewardPVFDefinitionSet, len(config.Phases), expectedPhases)
}
allowed := make(map[uint32]struct{}, len(requiredDefinitions))
for _, itemID := range requiredDefinitions {
allowed[itemID] = struct{}{}
}
seenDefinitions := make(map[uint32]struct{}, len(allowed))
pools := make(map[antonRewardPVFPoolKey]antonRewardPVFPool)
poolOrder := make([]antonRewardPVFPoolKey, 0, 6)
poolDefinitions := make(map[antonRewardPVFPoolKey]map[uint32]struct{})
for phaseIndex, phase := range config.Phases {
if len(phase.Rewards) == 0 {
return nil, nil, fmt.Errorf("%w: phase=%d has no rewards", errAntonRewardPVFDefinitionSet, phaseIndex)
}
for rewardIndex, reward := range phase.Rewards {
kind := normalizeAntonRewardPVFPoolKind(reward.Kind)
if kind == "" || reward.DefinitionItemID == 0 || reward.Weight <= 0 || uint64(reward.Weight) > math.MaxUint32 {
return nil, nil, fmt.Errorf("%w: phase=%d reward=%d kind=%q definition=%d weight=%d", errAntonRewardPVFValueInvalid, phaseIndex, rewardIndex, reward.Kind, reward.DefinitionItemID, reward.Weight)
}
if requireAll {
if _, ok := allowed[reward.DefinitionItemID]; !ok {
return nil, nil, fmt.Errorf("%w: phase=%d reward=%d unexpected_definition=%d", errAntonRewardPVFDefinitionSet, phaseIndex, rewardIndex, reward.DefinitionItemID)
}
}
key := antonRewardPVFPoolKey{phaseIndex: phaseIndex, kind: kind}
pool, found := pools[key]
if !found {
pool = antonRewardPVFPool{PhaseIndex: phaseIndex, Kind: kind}
poolOrder = append(poolOrder, key)
poolDefinitions[key] = make(map[uint32]struct{})
}
if _, duplicate := poolDefinitions[key][reward.DefinitionItemID]; duplicate {
return nil, nil, fmt.Errorf("%w: phase=%d kind=%s duplicate_definition=%d", errAntonRewardPVFDefinitionSet, phaseIndex, kind, reward.DefinitionItemID)
}
weight := uint32(reward.Weight)
if math.MaxUint64-pool.TotalWeight < uint64(weight) {
return nil, nil, fmt.Errorf("%w: phase=%d kind=%s total_weight_overflow", errAntonRewardPVFValueInvalid, phaseIndex, kind)
}
pool.Entries = append(pool.Entries, antonRewardPVFPoolEntry{
DefinitionItemID: reward.DefinitionItemID,
Rank: reward.Rank,
Weight: weight,
Flag: reward.Flag,
})
pool.TotalWeight += uint64(weight)
pools[key] = pool
poolDefinitions[key][reward.DefinitionItemID] = struct{}{}
seenDefinitions[reward.DefinitionItemID] = struct{}{}
}
}
if requireAll {
for _, definitionItemID := range requiredDefinitions {
if _, found := seenDefinitions[definitionItemID]; !found {
return nil, nil, fmt.Errorf("%w: missing_definition=%d", errAntonRewardPVFDefinitionSet, definitionItemID)
}
}
}
if config.SingleMode != nil {
singlePhaseIndex := len(config.Phases)
phase := config.SingleMode.Phase
if len(phase.Rewards) == 0 {
return nil, nil, fmt.Errorf("%w: single phase has no rewards", errAntonRewardPVFDefinitionSet)
}
for rewardIndex, reward := range phase.Rewards {
kind := normalizeAntonRewardPVFPoolKind(reward.Kind)
if kind == "" || reward.DefinitionItemID == 0 || reward.Weight <= 0 || uint64(reward.Weight) > math.MaxUint32 {
return nil, nil, fmt.Errorf("%w: single reward=%d kind=%q definition=%d weight=%d", errAntonRewardPVFValueInvalid, rewardIndex, reward.Kind, reward.DefinitionItemID, reward.Weight)
}
key := antonRewardPVFPoolKey{phaseIndex: singlePhaseIndex, kind: kind}
pool, found := pools[key]
if !found {
pool = antonRewardPVFPool{PhaseIndex: singlePhaseIndex, Kind: kind}
poolOrder = append(poolOrder, key)
poolDefinitions[key] = make(map[uint32]struct{})
}
if _, duplicate := poolDefinitions[key][reward.DefinitionItemID]; duplicate {
return nil, nil, fmt.Errorf("%w: single kind=%s duplicate_definition=%d", errAntonRewardPVFDefinitionSet, kind, reward.DefinitionItemID)
}
weight := uint32(reward.Weight)
if math.MaxUint64-pool.TotalWeight < uint64(weight) {
return nil, nil, fmt.Errorf("%w: single kind=%s total_weight_overflow", errAntonRewardPVFValueInvalid, kind)
}
pool.Entries = append(pool.Entries, antonRewardPVFPoolEntry{
DefinitionItemID: reward.DefinitionItemID,
Rank: reward.Rank,
Weight: weight,
Flag: reward.Flag,
})
pool.TotalWeight += uint64(weight)
pools[key] = pool
poolDefinitions[key][reward.DefinitionItemID] = struct{}{}
}
}
return pools, poolOrder, nil
}
func loadAntonRewardPVFDefinition(source dnfpvf.Source, itemRefs map[uint32]dungeonDropItemReference, definitionItemID uint32) (antonRewardPVFDefinition, error) {
reference, found := itemRefs[definitionItemID]
if !found {
return antonRewardPVFDefinition{}, fmt.Errorf("%w: definition=%d", errAntonRewardPVFDefinitionMissing, definitionItemID)
}
documentPath, err := antonRewardPVFReferencePath(reference)
if err != nil {
return antonRewardPVFDefinition{}, fmt.Errorf("%w: definition=%d: %v", errAntonRewardPVFDefinitionMissing, definitionItemID, err)
}
text, err := source.ReadText(documentPath)
if err != nil {
return antonRewardPVFDefinition{}, fmt.Errorf("read anton reward definition=%d path=%s: %w", definitionItemID, documentPath, err)
}
document, err := dnfpvf.Parse(documentPath, text)
if err != nil {
return antonRewardPVFDefinition{}, fmt.Errorf("parse anton reward definition=%d path=%s: %w", definitionItemID, documentPath, err)
}
directFinal := raidRewardPVFHasSection(document, "booster info") && !raidRewardPVFHasSection(document, "int data")
entries := []antonRewardPVFEntry{{ItemID: definitionItemID, Weight: 1, Count: 1}}
totalWeight := uint64(1)
if !directFinal {
entries, totalWeight, err = parseAntonRewardPVFEntries(document, definitionItemID)
if err != nil {
return antonRewardPVFDefinition{}, err
}
}
return antonRewardPVFDefinition{
DefinitionItemID: definitionItemID,
Kind: reference.kind,
PVFPath: documentPath,
DirectFinal: directFinal,
Entries: entries,
TotalWeight: totalWeight,
}, nil
}
func raidRewardPVFHasSection(document *dnfpvf.Document, name string) bool {
if document == nil {
return false
}
want := normalizeAntonRewardPVFSection(name)
for _, section := range document.Sections {
if normalizeAntonRewardPVFSection(section.Name) == want {
return true
}
}
return false
}
func parseAntonRewardPVFEntries(document *dnfpvf.Document, definitionItemID uint32) ([]antonRewardPVFEntry, uint64, error) {
if document == nil {
return nil, 0, fmt.Errorf("%w: definition=%d nil_document", errAntonRewardPVFSectionShape, definitionItemID)
}
openIndex, closeIndex := -1, -1
for index, section := range document.Sections {
switch normalizeAntonRewardPVFSection(section.Name) {
case "int data":
if openIndex >= 0 {
return nil, 0, fmt.Errorf("%w: definition=%d duplicate_int_data", errAntonRewardPVFSectionShape, definitionItemID)
}
openIndex = index
case "/int data":
if closeIndex >= 0 {
return nil, 0, fmt.Errorf("%w: definition=%d duplicate_int_data_close", errAntonRewardPVFSectionShape, definitionItemID)
}
closeIndex = index
}
}
if openIndex < 0 || closeIndex != openIndex+1 {
return nil, 0, fmt.Errorf("%w: definition=%d int_data=%d close=%d", errAntonRewardPVFSectionShape, definitionItemID, openIndex, closeIndex)
}
closingTokens, err := antonRewardPVFSectionTokens(document, closeIndex)
if err != nil || len(closingTokens) != 0 {
return nil, 0, fmt.Errorf("%w: definition=%d closing_values=%d", errAntonRewardPVFSectionShape, definitionItemID, len(closingTokens))
}
tokens, err := antonRewardPVFSectionTokens(document, openIndex)
if err != nil {
return nil, 0, err
}
return parseRaidRewardTripleTokens(tokens, definitionItemID)
}
func parseRaidRewardTripleTokens(tokens []dnfpvf.Token, definitionItemID uint32) ([]antonRewardPVFEntry, uint64, error) {
if len(tokens) == 0 || len(tokens)%3 != 0 {
return nil, 0, fmt.Errorf("%w: definition=%d values=%d row_width=3", errAntonRewardPVFSectionShape, definitionItemID, len(tokens))
}
values := make([]int64, 0, len(tokens))
for tokenIndex, token := range tokens {
if token.Kind != dnfpvf.TokenInt {
return nil, 0, fmt.Errorf("%w: definition=%d token=%d kind=%s", errAntonRewardPVFSectionShape, definitionItemID, tokenIndex, token.Kind)
}
values = append(values, token.Int)
}
entries := make([]antonRewardPVFEntry, 0, len(values)/3)
var totalWeight uint64
for offset := 0; offset < len(values); offset += 3 {
itemID, weight, count := values[offset], values[offset+1], values[offset+2]
if itemID <= 0 || itemID > math.MaxUint32 || weight <= 0 || weight > math.MaxUint32 || count <= 0 || count > math.MaxUint32 {
return nil, 0, fmt.Errorf("%w: definition=%d row=%d item=%d weight=%d count=%d", errAntonRewardPVFValueInvalid, definitionItemID, offset/3, itemID, weight, count)
}
if math.MaxUint64-totalWeight < uint64(weight) {
return nil, 0, fmt.Errorf("%w: definition=%d total_weight_overflow", errAntonRewardPVFValueInvalid, definitionItemID)
}
entries = append(entries, antonRewardPVFEntry{ItemID: uint32(itemID), Weight: uint32(weight), Count: uint32(count)})
totalWeight += uint64(weight)
}
return entries, totalWeight, nil
}
func antonRewardPVFSectionTokens(document *dnfpvf.Document, sectionIndex int) ([]dnfpvf.Token, error) {
if document == nil || sectionIndex < 0 || sectionIndex >= len(document.Sections) {
return nil, fmt.Errorf("%w: section_index=%d", errAntonRewardPVFSectionShape, sectionIndex)
}
section := document.Sections[sectionIndex]
if section.Start < 0 || section.End < section.Start || section.End > len(document.Tokens) {
return nil, fmt.Errorf("%w: section=%s range=%d:%d tokens=%d", errAntonRewardPVFSectionShape, section.Name, section.Start, section.End, len(document.Tokens))
}
return document.Tokens[section.Start:section.End], nil
}
func (c *antonRewardPVFCatalog) validateFinalReferences() error {
for _, definitionItemID := range c.definitionOrder {
definition := c.definitions[definitionItemID]
if definition.DirectFinal {
if _, err := c.finalItemReference(definitionItemID); err != nil {
return fmt.Errorf("definition=%d direct_final: %w", definitionItemID, err)
}
continue
}
for entryIndex, entry := range definition.Entries {
nested, isNested := c.definitions[entry.ItemID]
if !isNested {
if _, err := c.finalItemReference(entry.ItemID); err != nil {
return fmt.Errorf("definition=%d entry=%d: %w", definitionItemID, entryIndex, err)
}
continue
}
if nested.DefinitionItemID == definitionItemID {
return fmt.Errorf("%w: definition=%d self_reference", errAntonRewardPVFNestingInvalid, definitionItemID)
}
if nested.DirectFinal {
if _, err := c.finalItemReference(nested.DefinitionItemID); err != nil {
return fmt.Errorf("definition=%d entry=%d nested_direct=%d: %w", definitionItemID, entryIndex, nested.DefinitionItemID, err)
}
continue
}
for nestedIndex, nestedEntry := range nested.Entries {
if _, deeper := c.definitions[nestedEntry.ItemID]; deeper {
return fmt.Errorf("%w: definition=%d entry=%d nested=%d nested_entry=%d deeper=%d", errAntonRewardPVFNestingInvalid, definitionItemID, entryIndex, nested.DefinitionItemID, nestedIndex, nestedEntry.ItemID)
}
if uint64(entry.Count)*uint64(nestedEntry.Count) > math.MaxUint32 {
return fmt.Errorf("%w: definition=%d nested=%d count_overflow=%d*%d", errAntonRewardPVFValueInvalid, definitionItemID, nested.DefinitionItemID, entry.Count, nestedEntry.Count)
}
if _, err := c.finalItemReference(nestedEntry.ItemID); err != nil {
return fmt.Errorf("definition=%d entry=%d nested=%d nested_entry=%d: %w", definitionItemID, entryIndex, nested.DefinitionItemID, nestedIndex, err)
}
}
}
}
return nil
}
func (c *antonRewardPVFCatalog) finalItemReference(itemID uint32) (dungeonDropItemReference, error) {
reference, found := c.itemRefs[itemID]
if !found {
return dungeonDropItemReference{}, fmt.Errorf("%w: item=%d", errAntonRewardPVFFinalItemUnresolved, itemID)
}
if _, err := antonRewardPVFReferencePath(reference); err != nil {
return dungeonDropItemReference{}, fmt.Errorf("%w: item=%d: %v", errAntonRewardPVFFinalItemUnresolved, itemID, err)
}
return reference, nil
}
func (c *antonRewardPVFCatalog) Definition(definitionItemID uint32) (antonRewardPVFDefinition, bool) {
if c == nil {
return antonRewardPVFDefinition{}, false
}
definition, found := c.definitions[definitionItemID]
if !found {
return antonRewardPVFDefinition{}, false
}
definition.Entries = append([]antonRewardPVFEntry(nil), definition.Entries...)
return definition, true
}
func (c *antonRewardPVFCatalog) DefinitionIDs() []uint32 {
if c == nil {
return nil
}
return append([]uint32(nil), c.definitionOrder...)
}
func (c *antonRewardPVFCatalog) Pool(phaseIndex int, kind string) (antonRewardPVFPool, bool) {
if c == nil {
return antonRewardPVFPool{}, false
}
key := antonRewardPVFPoolKey{phaseIndex: phaseIndex, kind: normalizeAntonRewardPVFPoolKind(kind)}
pool, found := c.pools[key]
if !found {
return antonRewardPVFPool{}, false
}
pool.Entries = append([]antonRewardPVFPoolEntry(nil), pool.Entries...)
return pool, true
}
func (c *antonRewardPVFCatalog) ResolvePool(phaseIndex int, kind string, rng antonRewardPVFRNG) (antonRewardPVFResolved, error) {
if c == nil {
return antonRewardPVFResolved{}, errAntonRewardPVFCatalogRequired
}
if rng == nil {
return antonRewardPVFResolved{}, errAntonRewardPVFRNGRequired
}
pool, found := c.Pool(phaseIndex, kind)
if !found || len(pool.Entries) == 0 || pool.TotalWeight == 0 {
return antonRewardPVFResolved{}, fmt.Errorf("%w: phase=%d kind=%q", errAntonRewardPVFPoolUnresolved, phaseIndex, kind)
}
selected := selectAntonRewardPVFPoolEntry(pool, rng.Uint64())
resolved, err := c.ResolveDefinition(selected.DefinitionItemID, rng)
if err != nil {
return antonRewardPVFResolved{}, err
}
resolved.PhaseIndex = phaseIndex
resolved.PoolKind = pool.Kind
resolved.PoolWeight = selected.Weight
resolved.Rank = selected.Rank
resolved.Flag = selected.Flag
return resolved, nil
}
func (c *antonRewardPVFCatalog) ResolveDefinition(definitionItemID uint32, rng antonRewardPVFRNG) (antonRewardPVFResolved, error) {
if c == nil {
return antonRewardPVFResolved{}, errAntonRewardPVFCatalogRequired
}
if rng == nil {
return antonRewardPVFResolved{}, errAntonRewardPVFRNGRequired
}
definition, found := c.definitions[definitionItemID]
if !found || len(definition.Entries) == 0 || definition.TotalWeight == 0 {
return antonRewardPVFResolved{}, fmt.Errorf("%w: definition=%d", errAntonRewardPVFDefinitionMissing, definitionItemID)
}
selected := selectAntonRewardPVFDefinitionEntry(definition, rng.Uint64())
count := selected.Count
resolved := antonRewardPVFResolved{
PhaseIndex: -1,
DefinitionItemID: definitionItemID,
DefinitionPVFPath: definition.PVFPath,
}
if definition.DirectFinal {
selected = antonRewardPVFEntry{ItemID: definition.DefinitionItemID, Weight: 1, Count: 1}
count = 1
}
if nested, isNested := c.definitions[selected.ItemID]; isNested {
if nested.DirectFinal {
resolved.NestedDefinitionItemID = nested.DefinitionItemID
resolved.NestedDefinitionPVFPath = nested.PVFPath
selected = antonRewardPVFEntry{ItemID: nested.DefinitionItemID, Weight: 1, Count: count}
} else {
nestedSelected := selectAntonRewardPVFDefinitionEntry(nested, rng.Uint64())
if _, deeper := c.definitions[nestedSelected.ItemID]; deeper {
return antonRewardPVFResolved{}, fmt.Errorf("%w: definition=%d nested=%d deeper=%d", errAntonRewardPVFNestingInvalid, definitionItemID, nested.DefinitionItemID, nestedSelected.ItemID)
}
count64 := uint64(count) * uint64(nestedSelected.Count)
if count64 == 0 || count64 > math.MaxUint32 {
return antonRewardPVFResolved{}, fmt.Errorf("%w: definition=%d nested=%d count=%d", errAntonRewardPVFValueInvalid, definitionItemID, nested.DefinitionItemID, count64)
}
resolved.NestedDefinitionItemID = nested.DefinitionItemID
resolved.NestedDefinitionPVFPath = nested.PVFPath
selected = nestedSelected
count = uint32(count64)
}
}
if stillDefinition, exists := c.definitions[selected.ItemID]; exists && !stillDefinition.DirectFinal {
return antonRewardPVFResolved{}, fmt.Errorf("%w: definition=%d final=%d", errAntonRewardPVFNestingInvalid, definitionItemID, selected.ItemID)
}
finalReference, err := c.finalItemReference(selected.ItemID)
if err != nil {
return antonRewardPVFResolved{}, err
}
finalPath, err := antonRewardPVFReferencePath(finalReference)
if err != nil {
return antonRewardPVFResolved{}, err
}
resolved.ItemID = selected.ItemID
resolved.Count = count
resolved.ItemKind = finalReference.kind
resolved.ItemPVFPath = finalPath
return resolved, nil
}
// ResolveFinalItem expands metadata for one already selected final item. The
// underlying resolver opens only that item's indexed document and caches it;
// it never initializes the generic dungeon-drop pools.
func (c *antonRewardPVFCatalog) ResolveFinalItem(itemID uint32) (dungeonDropItemDefinition, error) {
if c == nil || c.source == nil {
return dungeonDropItemDefinition{}, errAntonRewardPVFCatalogRequired
}
if definition, isDefinition := c.definitions[itemID]; isDefinition && !definition.DirectFinal {
return dungeonDropItemDefinition{}, fmt.Errorf("%w: definition=%d is not final", errAntonRewardPVFNestingInvalid, itemID)
}
reference, err := c.finalItemReference(itemID)
if err != nil {
return dungeonDropItemDefinition{}, err
}
exactPath, err := antonRewardPVFReferencePath(reference)
if err != nil {
return dungeonDropItemDefinition{}, err
}
c.finalMu.Lock()
defer c.finalMu.Unlock()
if cached, found := c.finalCache[itemID]; found {
return cached, nil
}
// Supplying the already rooted path makes ResolveItem's first and only
// successful read the exact selected document. The one-item catalog cannot
// initialize monster or generic item pools.
single := &pvfDungeonDropCatalog{
source: c.source,
itemRefs: map[uint32]dungeonDropItemReference{
itemID: {kind: reference.kind, path: exactPath},
},
itemCache: make(map[uint32]dungeonDropItemDefinition, 1),
}
definition, err := single.ResolveItem(itemID)
if err != nil {
return dungeonDropItemDefinition{}, fmt.Errorf("resolve Anton final item=%d: %w", itemID, err)
}
c.finalCache[itemID] = definition
return definition, nil
}
func (c *antonRewardPVFCatalog) Snapshot() antonRewardPVFCatalogSnapshot {
if c == nil {
return antonRewardPVFCatalogSnapshot{}
}
snapshot := antonRewardPVFCatalogSnapshot{Definitions: len(c.definitions), PhasePools: len(c.pools)}
for _, definition := range c.definitions {
snapshot.DefinitionEntries += len(definition.Entries)
for _, entry := range definition.Entries {
if _, nested := c.definitions[entry.ItemID]; nested {
snapshot.NestedReferences++
}
}
}
for _, pool := range c.pools {
snapshot.PhasePoolEntries += len(pool.Entries)
}
return snapshot
}
func selectAntonRewardPVFPoolEntry(pool antonRewardPVFPool, random uint64) antonRewardPVFPoolEntry {
want := random % pool.TotalWeight
var cumulative uint64
for _, entry := range pool.Entries {
cumulative += uint64(entry.Weight)
if want < cumulative {
return entry
}
}
return pool.Entries[len(pool.Entries)-1]
}
func selectAntonRewardPVFDefinitionEntry(definition antonRewardPVFDefinition, random uint64) antonRewardPVFEntry {
want := random % definition.TotalWeight
var cumulative uint64
for _, entry := range definition.Entries {
cumulative += uint64(entry.Weight)
if want < cumulative {
return entry
}
}
return definition.Entries[len(definition.Entries)-1]
}
func antonRewardPVFReferencePath(reference dungeonDropItemReference) (string, error) {
if reference.kind != dungeonDropItemStackable && reference.kind != dungeonDropItemEquipment {
return "", fmt.Errorf("unsupported_kind=%q", reference.kind)
}
clean := cleanDungeonDropPath(reference.path)
if clean == "" {
return "", errors.New("empty_path")
}
root := string(reference.kind)
if !strings.HasPrefix(strings.ToLower(clean), strings.ToLower(root)+"/") {
clean = path.Join(root, clean)
}
clean = path.Clean(clean)
if clean == "." || clean == ".." || strings.HasPrefix(clean, "../") || strings.HasPrefix(clean, "/") {
return "", fmt.Errorf("path_outside_item_root=%q", clean)
}
return clean, nil
}
func normalizeAntonRewardPVFPoolKind(value string) string {
return strings.ToLower(strings.TrimSpace(value))
}
func normalizeAntonRewardPVFSection(value string) string {
return strings.ToLower(strings.TrimSpace(value))
}