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,440 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
const (
|
||||
currentDungeonDimensionGatePath = "Etc/DimensionGate.etc"
|
||||
currentDungeonDimensionInfoPath = "Etc/DungeonEtc/DimensionDungeonInfo.etc"
|
||||
currentDungeonDimensionDropListPath = "Etc/DimensionGateDropList.etc"
|
||||
currentDungeonDimensionDropExcludePath = "Etc/DimensionGateDropList_Exclude.etc"
|
||||
currentDungeonClearRewardRulePath = "Etc/ItemDropInfo_ClearReward.etc"
|
||||
|
||||
// The same-version reference server adds an area material to the ordinary
|
||||
// monster-template pool with weight 100. The weight only selects within
|
||||
// the pool; the existing 1000/10000 outer roll remains the drop rate.
|
||||
currentDungeonDimensionMaterialWeight uint32 = 100
|
||||
)
|
||||
|
||||
var errCurrentDungeonDimensionDropInvalid = errors.New("current dungeon dimension drop PVF data is invalid")
|
||||
|
||||
type currentDungeonDimensionDropEntry struct {
|
||||
Group int
|
||||
ItemID uint32
|
||||
}
|
||||
|
||||
type currentDungeonDimensionLevelThreshold struct {
|
||||
Tier int
|
||||
MinimumLevel int
|
||||
}
|
||||
|
||||
type currentDungeonDimensionChronicleKey struct {
|
||||
Job byte
|
||||
GrowType byte
|
||||
}
|
||||
|
||||
type currentDungeonDimensionChroniclePool struct {
|
||||
Normal []uint32
|
||||
Set []uint32
|
||||
}
|
||||
|
||||
// currentDungeonDimensionChronicleRate is the exact three-column row from
|
||||
// ItemDropInfo_ClearReward.etc [chronicle set reward rate]. RewardPercent is
|
||||
// used by the clear-card path. SetPercent chooses the rarer set pool after a
|
||||
// monster's ordinary PVF equipment roll has already succeeded.
|
||||
type currentDungeonDimensionChronicleRate struct {
|
||||
RewardPercent int
|
||||
SetPercent int
|
||||
}
|
||||
|
||||
type currentDungeonDimensionDropCatalog struct {
|
||||
LegacyDungeons map[int64]struct{}
|
||||
ExactTiers map[int64]int
|
||||
LevelThresholds []currentDungeonDimensionLevelThreshold
|
||||
ItemsByTier map[int][]currentDungeonDimensionDropEntry
|
||||
ChronicleByCareer map[currentDungeonDimensionChronicleKey]currentDungeonDimensionChroniclePool
|
||||
ChronicleRates map[int]currentDungeonDimensionChronicleRate
|
||||
}
|
||||
|
||||
type currentDungeonDimensionDropCacheEntry struct {
|
||||
catalog *currentDungeonDimensionDropCatalog
|
||||
err error
|
||||
}
|
||||
|
||||
var currentDungeonDimensionDropsByMonsterCatalog sync.Map
|
||||
|
||||
func currentDungeonDimensionDrops(catalog *pvfDungeonMonsterCatalog) (*currentDungeonDimensionDropCatalog, error) {
|
||||
if catalog == nil || catalog.source == nil {
|
||||
return nil, errDungeonDropSourceRequired
|
||||
}
|
||||
if cached, found := currentDungeonDimensionDropsByMonsterCatalog.Load(catalog); found {
|
||||
entry := cached.(currentDungeonDimensionDropCacheEntry)
|
||||
return entry.catalog, entry.err
|
||||
}
|
||||
loaded, err := loadCurrentDungeonDimensionDrops(catalog.source)
|
||||
entry := currentDungeonDimensionDropCacheEntry{catalog: loaded, err: err}
|
||||
actual, _ := currentDungeonDimensionDropsByMonsterCatalog.LoadOrStore(catalog, entry)
|
||||
entry = actual.(currentDungeonDimensionDropCacheEntry)
|
||||
return entry.catalog, entry.err
|
||||
}
|
||||
|
||||
func loadCurrentDungeonDimensionDrops(source dnfpvf.Source) (*currentDungeonDimensionDropCatalog, error) {
|
||||
if source == nil {
|
||||
return nil, errDungeonDropSourceRequired
|
||||
}
|
||||
catalog := ¤tDungeonDimensionDropCatalog{
|
||||
LegacyDungeons: make(map[int64]struct{}),
|
||||
ExactTiers: make(map[int64]int),
|
||||
ItemsByTier: make(map[int][]currentDungeonDimensionDropEntry),
|
||||
ChronicleByCareer: make(map[currentDungeonDimensionChronicleKey]currentDungeonDimensionChroniclePool),
|
||||
ChronicleRates: make(map[int]currentDungeonDimensionChronicleRate),
|
||||
}
|
||||
|
||||
gateText, err := source.ReadText(currentDungeonDimensionGatePath)
|
||||
if err != nil && !currentDungeonDimensionDropDocumentMissing(err) {
|
||||
return nil, fmt.Errorf("read %s: %w", currentDungeonDimensionGatePath, err)
|
||||
}
|
||||
if strings.TrimSpace(gateText) != "" {
|
||||
document, parseErr := dnfpvf.Parse(currentDungeonDimensionGatePath, gateText)
|
||||
if parseErr != nil {
|
||||
return nil, parseErr
|
||||
}
|
||||
values, sectionErr := currentDungeonDimensionSectionInts(document, "dimensiondungeon list", false)
|
||||
if sectionErr != nil {
|
||||
return nil, sectionErr
|
||||
}
|
||||
if len(values)%3 != 0 {
|
||||
return nil, fmt.Errorf("%w: %s [dimensiondungeon list] values=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionGatePath, len(values))
|
||||
}
|
||||
for index := 0; index < len(values); index += 3 {
|
||||
dungeonID := values[index]
|
||||
if dungeonID <= 0 {
|
||||
return nil, fmt.Errorf("%w: %s dungeon_id=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionGatePath, dungeonID)
|
||||
}
|
||||
catalog.LegacyDungeons[dungeonID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
infoText, err := source.ReadText(currentDungeonDimensionInfoPath)
|
||||
if err != nil && !currentDungeonDimensionDropDocumentMissing(err) {
|
||||
return nil, fmt.Errorf("read %s: %w", currentDungeonDimensionInfoPath, err)
|
||||
}
|
||||
if strings.TrimSpace(infoText) == "" {
|
||||
return catalog, nil
|
||||
}
|
||||
document, err := dnfpvf.Parse(currentDungeonDimensionInfoPath, infoText)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infoValues, err := currentDungeonDimensionSectionInts(document, "dimension dungeon info", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(infoValues)%2 != 0 {
|
||||
return nil, fmt.Errorf("%w: %s [dimension dungeon info] values=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionInfoPath, len(infoValues))
|
||||
}
|
||||
for index := 0; index < len(infoValues); index += 2 {
|
||||
tier, dungeonID := infoValues[index], infoValues[index+1]
|
||||
if tier < 0 || tier > int64(^uint8(0)) || dungeonID <= 0 {
|
||||
return nil, fmt.Errorf("%w: %s tier=%d dungeon_id=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionInfoPath, tier, dungeonID)
|
||||
}
|
||||
if _, duplicate := catalog.ExactTiers[dungeonID]; duplicate {
|
||||
return nil, fmt.Errorf("%w: %s duplicate dungeon_id=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionInfoPath, dungeonID)
|
||||
}
|
||||
catalog.ExactTiers[dungeonID] = int(tier)
|
||||
}
|
||||
|
||||
thresholdValues, err := currentDungeonDimensionSectionInts(document, "dungeon difficulty condition info", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(thresholdValues)%2 != 0 {
|
||||
return nil, fmt.Errorf("%w: %s [dungeon difficulty condition info] values=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionInfoPath, len(thresholdValues))
|
||||
}
|
||||
for index := 0; index < len(thresholdValues); index += 2 {
|
||||
tier, level := thresholdValues[index], thresholdValues[index+1]
|
||||
if tier < 0 || tier > int64(^uint8(0)) || level < 0 || level > int64(^uint8(0)) {
|
||||
return nil, fmt.Errorf("%w: %s threshold tier=%d level=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionInfoPath, tier, level)
|
||||
}
|
||||
catalog.LevelThresholds = append(catalog.LevelThresholds, currentDungeonDimensionLevelThreshold{Tier: int(tier), MinimumLevel: int(level)})
|
||||
}
|
||||
sort.SliceStable(catalog.LevelThresholds, func(i, j int) bool {
|
||||
if catalog.LevelThresholds[i].MinimumLevel == catalog.LevelThresholds[j].MinimumLevel {
|
||||
return catalog.LevelThresholds[i].Tier < catalog.LevelThresholds[j].Tier
|
||||
}
|
||||
return catalog.LevelThresholds[i].MinimumLevel < catalog.LevelThresholds[j].MinimumLevel
|
||||
})
|
||||
|
||||
dropValues, err := currentDungeonDimensionSectionInts(document, "drop item Info", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(dropValues)%3 != 0 {
|
||||
return nil, fmt.Errorf("%w: %s [drop item Info] values=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionInfoPath, len(dropValues))
|
||||
}
|
||||
for index := 0; index < len(dropValues); index += 3 {
|
||||
tier, group, itemID := dropValues[index], dropValues[index+1], dropValues[index+2]
|
||||
if tier < 0 || tier > int64(^uint8(0)) || group < 0 || group > int64(^uint8(0)) || itemID <= 0 || itemID > int64(^uint32(0)) {
|
||||
return nil, fmt.Errorf("%w: %s drop tier=%d group=%d item_id=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionInfoPath, tier, group, itemID)
|
||||
}
|
||||
catalog.ItemsByTier[int(tier)] = append(catalog.ItemsByTier[int(tier)], currentDungeonDimensionDropEntry{Group: int(group), ItemID: uint32(itemID)})
|
||||
}
|
||||
if err := loadCurrentDungeonDimensionChronicleDrops(source, catalog); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return catalog, nil
|
||||
}
|
||||
|
||||
func loadCurrentDungeonDimensionChronicleDrops(source dnfpvf.Source, catalog *currentDungeonDimensionDropCatalog) error {
|
||||
if source == nil || catalog == nil {
|
||||
return errDungeonDropSourceRequired
|
||||
}
|
||||
dropText, err := source.ReadText(currentDungeonDimensionDropListPath)
|
||||
if err != nil {
|
||||
if currentDungeonDimensionDropDocumentMissing(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("read %s: %w", currentDungeonDimensionDropListPath, err)
|
||||
}
|
||||
if strings.TrimSpace(dropText) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
excluded := make(map[uint32]struct{})
|
||||
excludeText, err := source.ReadText(currentDungeonDimensionDropExcludePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read %s: %w", currentDungeonDimensionDropExcludePath, err)
|
||||
}
|
||||
excludeDocument, err := dnfpvf.Parse(currentDungeonDimensionDropExcludePath, excludeText)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
excludeValues, err := currentDungeonDimensionSectionInts(excludeDocument, "exclude list", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, itemID := range excludeValues {
|
||||
if itemID <= 0 || itemID > int64(^uint32(0)) {
|
||||
return fmt.Errorf("%w: %s item_id=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionDropExcludePath, itemID)
|
||||
}
|
||||
excluded[uint32(itemID)] = struct{}{}
|
||||
}
|
||||
|
||||
document, err := dnfpvf.Parse(currentDungeonDimensionDropListPath, dropText)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for sectionIndex := 0; sectionIndex < len(document.Sections); sectionIndex++ {
|
||||
section := document.Sections[sectionIndex]
|
||||
if !strings.EqualFold(strings.TrimSpace(section.Name), "chronicle grow type") {
|
||||
continue
|
||||
}
|
||||
careerValues := magicBoxSectionInts(document, section)
|
||||
if len(careerValues) != 2 || careerValues[0] < 0 || careerValues[0] > int64(^uint8(0)) || careerValues[1] < 0 || careerValues[1] > int64(^uint8(0)) {
|
||||
return fmt.Errorf("%w: %s section=%d career=%v", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionDropListPath, sectionIndex, careerValues)
|
||||
}
|
||||
key := currentDungeonDimensionChronicleKey{Job: byte(careerValues[0]), GrowType: byte(careerValues[1])}
|
||||
if _, duplicate := catalog.ChronicleByCareer[key]; duplicate {
|
||||
return fmt.Errorf("%w: %s duplicate job=%d grow_type=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionDropListPath, key.Job, key.GrowType)
|
||||
}
|
||||
pool := currentDungeonDimensionChroniclePool{}
|
||||
normalFound, setFound, closed := false, false, false
|
||||
for nestedIndex := sectionIndex + 1; nestedIndex < len(document.Sections); nestedIndex++ {
|
||||
nested := document.Sections[nestedIndex]
|
||||
name := strings.ToLower(strings.TrimSpace(nested.Name))
|
||||
if name == "/chronicle grow type" {
|
||||
sectionIndex = nestedIndex
|
||||
closed = true
|
||||
break
|
||||
}
|
||||
var destination *[]uint32
|
||||
switch name {
|
||||
case "normal chronicle list":
|
||||
if normalFound {
|
||||
return fmt.Errorf("%w: %s duplicate normal list job=%d grow_type=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionDropListPath, key.Job, key.GrowType)
|
||||
}
|
||||
normalFound = true
|
||||
destination = &pool.Normal
|
||||
case "set chronicle list":
|
||||
if setFound {
|
||||
return fmt.Errorf("%w: %s duplicate set list job=%d grow_type=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionDropListPath, key.Job, key.GrowType)
|
||||
}
|
||||
setFound = true
|
||||
destination = &pool.Set
|
||||
default:
|
||||
continue
|
||||
}
|
||||
for _, itemID := range magicBoxSectionInts(document, nested) {
|
||||
if itemID <= 0 || itemID > int64(^uint32(0)) {
|
||||
return fmt.Errorf("%w: %s job=%d grow_type=%d item_id=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionDropListPath, key.Job, key.GrowType, itemID)
|
||||
}
|
||||
id := uint32(itemID)
|
||||
if _, blocked := excluded[id]; blocked {
|
||||
continue
|
||||
}
|
||||
*destination = append(*destination, id)
|
||||
}
|
||||
}
|
||||
if !closed || !normalFound || !setFound {
|
||||
return fmt.Errorf("%w: %s incomplete job=%d grow_type=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionDropListPath, key.Job, key.GrowType)
|
||||
}
|
||||
catalog.ChronicleByCareer[key] = pool
|
||||
}
|
||||
if len(catalog.ChronicleByCareer) == 0 {
|
||||
return fmt.Errorf("%w: %s has no career rows", errCurrentDungeonDimensionDropInvalid, currentDungeonDimensionDropListPath)
|
||||
}
|
||||
|
||||
rateText, err := source.ReadText(currentDungeonClearRewardRulePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read %s: %w", currentDungeonClearRewardRulePath, err)
|
||||
}
|
||||
rateDocument, err := dnfpvf.Parse(currentDungeonClearRewardRulePath, rateText)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rateValues, err := currentDungeonDimensionSectionInts(rateDocument, "chronicle set reward rate", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(rateValues)%3 != 0 {
|
||||
return fmt.Errorf("%w: %s [chronicle set reward rate] values=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonClearRewardRulePath, len(rateValues))
|
||||
}
|
||||
for index := 0; index < len(rateValues); index += 3 {
|
||||
tier, rewardPercent, setPercent := rateValues[index], rateValues[index+1], rateValues[index+2]
|
||||
if tier < 0 || tier > int64(^uint8(0)) || rewardPercent < 0 || rewardPercent > 100 || setPercent < 0 || setPercent > 100 {
|
||||
return fmt.Errorf("%w: %s tier=%d reward=%d set=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonClearRewardRulePath, tier, rewardPercent, setPercent)
|
||||
}
|
||||
if _, duplicate := catalog.ChronicleRates[int(tier)]; duplicate {
|
||||
return fmt.Errorf("%w: %s duplicate tier=%d", errCurrentDungeonDimensionDropInvalid, currentDungeonClearRewardRulePath, tier)
|
||||
}
|
||||
catalog.ChronicleRates[int(tier)] = currentDungeonDimensionChronicleRate{RewardPercent: int(rewardPercent), SetPercent: int(setPercent)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func currentDungeonDimensionTier(catalog *currentDungeonDimensionDropCatalog, dungeonID int64, characterLevel int) (int, bool) {
|
||||
if catalog == nil || dungeonID <= 0 {
|
||||
return 0, false
|
||||
}
|
||||
tier, matched := catalog.ExactTiers[dungeonID]
|
||||
if matched {
|
||||
return tier, true
|
||||
}
|
||||
if _, matched = catalog.LegacyDungeons[dungeonID]; !matched {
|
||||
return 0, false
|
||||
}
|
||||
for _, threshold := range catalog.LevelThresholds {
|
||||
if characterLevel < threshold.MinimumLevel {
|
||||
break
|
||||
}
|
||||
tier = threshold.Tier
|
||||
}
|
||||
return tier, true
|
||||
}
|
||||
|
||||
func currentDungeonDimensionDropPool(
|
||||
catalog *currentDungeonDimensionDropCatalog,
|
||||
dungeonID int64,
|
||||
characterLevel int,
|
||||
) (int, []dungeonMonsterDropPoolEntry, bool, error) {
|
||||
if catalog == nil || dungeonID <= 0 {
|
||||
return 0, nil, false, nil
|
||||
}
|
||||
tier, matched := currentDungeonDimensionTier(catalog, dungeonID, characterLevel)
|
||||
if !matched {
|
||||
return 0, nil, false, nil
|
||||
}
|
||||
selectedTier := -1
|
||||
for candidate := range catalog.ItemsByTier {
|
||||
if candidate <= tier && candidate > selectedTier && len(catalog.ItemsByTier[candidate]) > 0 {
|
||||
selectedTier = candidate
|
||||
}
|
||||
}
|
||||
if selectedTier < 0 {
|
||||
return tier, nil, true, fmt.Errorf("%w: dungeon_id=%d tier=%d has no drop-item row", errCurrentDungeonDimensionDropInvalid, dungeonID, tier)
|
||||
}
|
||||
entries := catalog.ItemsByTier[selectedTier]
|
||||
pool := make([]dungeonMonsterDropPoolEntry, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
pool = append(pool, dungeonMonsterDropPoolEntry{
|
||||
ItemID: entry.ItemID,
|
||||
Weight: currentDungeonDimensionMaterialWeight,
|
||||
})
|
||||
}
|
||||
return selectedTier, pool, true, nil
|
||||
}
|
||||
|
||||
func currentDungeonDimensionChronicleItem(
|
||||
catalog *currentDungeonDimensionDropCatalog,
|
||||
dungeonID int64,
|
||||
characterLevel int,
|
||||
job byte,
|
||||
growType byte,
|
||||
rng *currentDungeonDropLCG,
|
||||
applyRewardRate bool,
|
||||
) (int, uint32, string, bool, bool, error) {
|
||||
tier, matched := currentDungeonDimensionTier(catalog, dungeonID, characterLevel)
|
||||
if !matched {
|
||||
return 0, 0, "", false, false, nil
|
||||
}
|
||||
if rng == nil {
|
||||
return tier, 0, "", true, false, errCurrentDungeonDimensionDropInvalid
|
||||
}
|
||||
pool, found := catalog.ChronicleByCareer[currentDungeonDimensionChronicleKey{Job: job, GrowType: growType}]
|
||||
if !found || (len(pool.Normal) == 0 && len(pool.Set) == 0) {
|
||||
return tier, 0, "", true, false, nil
|
||||
}
|
||||
rate, found := catalog.ChronicleRates[tier]
|
||||
if !found {
|
||||
return tier, 0, "", true, false, fmt.Errorf("%w: dungeon_id=%d tier=%d has no chronicle rate", errCurrentDungeonDimensionDropInvalid, dungeonID, tier)
|
||||
}
|
||||
if applyRewardRate && (rate.RewardPercent == 0 || rng.Next(100) >= rate.RewardPercent) {
|
||||
return tier, 0, "", true, false, nil
|
||||
}
|
||||
selectedPool := pool.Normal
|
||||
group := "normal"
|
||||
if len(pool.Set) > 0 && rate.SetPercent > 0 && rng.Next(100) < rate.SetPercent {
|
||||
selectedPool = pool.Set
|
||||
group = "set"
|
||||
}
|
||||
if len(selectedPool) == 0 {
|
||||
selectedPool = pool.Set
|
||||
group = "set"
|
||||
}
|
||||
if len(selectedPool) == 0 {
|
||||
return tier, 0, "", true, false, nil
|
||||
}
|
||||
return tier, selectedPool[rng.Next(len(selectedPool))], group, true, true, nil
|
||||
}
|
||||
|
||||
func currentDungeonDimensionSectionInts(document *dnfpvf.Document, section string, optional bool) ([]int64, error) {
|
||||
if document == nil {
|
||||
return nil, fmt.Errorf("%w: section=%s document=nil", errCurrentDungeonDimensionDropInvalid, section)
|
||||
}
|
||||
tokens, found := document.Section(section)
|
||||
if !found {
|
||||
if optional {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s section=[%s] missing", errCurrentDungeonDimensionDropInvalid, document.Path, section)
|
||||
}
|
||||
values := make([]int64, 0, len(tokens))
|
||||
for _, token := range tokens {
|
||||
if token.Kind == dnfpvf.TokenInt {
|
||||
values = append(values, token.Int)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func currentDungeonDimensionDropDocumentMissing(err error) bool {
|
||||
return errors.Is(err, dnfpvf.ErrDocNotFound) || errors.Is(err, platformpvf.ErrFileNotFound)
|
||||
}
|
||||
Reference in new issue
Block a user