Files
90CNServer/server/internal/modules/dnf/adventuregroup/load.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

225 lines
7.2 KiB
Go

package adventuregroup
import (
"context"
"fmt"
"math"
"strings"
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
)
const (
pointBonusSection = "point bonus"
managePointSection = "manage level point"
manageLevelMaxSection = "manage level max"
expBonusSection = "exp bonus"
goldBonusSection = "gold bonus"
manageOptionSection = "manage option"
)
// Load parses the typed character-management document. It remains the narrow
// calculator loader used by domain tests and tooling.
func Load(ctx context.Context, source dnfpvf.Source) (*Tables, error) {
if source == nil {
return nil, dnfpvf.ErrSourceRequired
}
if ctx == nil {
ctx = context.Background()
}
if err := ctx.Err(); err != nil {
return nil, err
}
text, err := source.ReadText(CharacterManagementPath)
if err != nil {
return nil, fmt.Errorf("read dnf adventure-group PVF %q: %w", CharacterManagementPath, err)
}
if err := ctx.Err(); err != nil {
return nil, err
}
doc, err := dnfpvf.Parse(CharacterManagementPath, text)
if err != nil {
return nil, fmt.Errorf("parse dnf adventure-group PVF %q: %w", CharacterManagementPath, err)
}
tables, err := parseTables(doc)
if err != nil {
return nil, err
}
return tables, nil
}
// LoadComplete loads the management table together with the current 2018
// expedition, shop and growth-capsule documents. Production composition uses
// this strict compatibility-unit loader.
func LoadComplete(ctx context.Context, source dnfpvf.Source) (*Tables, error) {
tables, err := Load(ctx, source)
if err != nil {
return nil, err
}
runtime, err := loadRuntimeConfig(ctx, source)
if err != nil {
return nil, err
}
tables.runtime = runtime
return tables, nil
}
func parseTables(doc *dnfpvf.Document) (*Tables, error) {
pointValues, err := numericSection(doc, pointBonusSection, false)
if err != nil {
return nil, err
}
pointRanges, err := parsePointRanges(pointValues)
if err != nil {
return nil, err
}
thresholdValues, err := numericSection(doc, managePointSection, false)
if err != nil {
return nil, err
}
thresholds, err := parseThresholds(thresholdValues)
if err != nil {
return nil, err
}
maxValues, err := numericSection(doc, manageLevelMaxSection, false)
if err != nil {
return nil, err
}
if len(maxValues) != 1 || maxValues[0] <= 0 || maxValues[0] > int64(math.MaxInt) {
return nil, fmt.Errorf("%w: [%s] values=%v", ErrTableMalformed, manageLevelMaxSection, maxValues)
}
manageLevelMax := int(maxValues[0])
expValues, err := numericSection(doc, expBonusSection, true)
if err != nil {
return nil, err
}
expBonus, err := parseLevelValueTable(expBonusSection, expValues, manageLevelMax, true)
if err != nil {
return nil, err
}
goldValues, err := numericSection(doc, goldBonusSection, true)
if err != nil {
return nil, err
}
goldBonus, err := parseLevelValueTable(goldBonusSection, goldValues, manageLevelMax, true)
if err != nil {
return nil, err
}
optionValues, err := numericSection(doc, manageOptionSection, false)
if err != nil {
return nil, err
}
options, err := parseLevelValueTable(manageOptionSection, optionValues, manageLevelMax, false)
if err != nil {
return nil, err
}
return &Tables{
pointRanges: pointRanges,
manageThresholds: thresholds,
manageLevelMax: manageLevelMax,
expBonusByLevel: expBonus,
goldBonusByLevel: goldBonus,
optionByLevel: options,
}, nil
}
func numericSection(doc *dnfpvf.Document, name string, allowEmpty bool) ([]int64, error) {
if doc == nil {
return nil, fmt.Errorf("%w: %s [%s]", ErrTableEmpty, CharacterManagementPath, name)
}
count := 0
for _, section := range doc.Sections {
if strings.EqualFold(strings.TrimSpace(section.Name), name) {
count++
}
}
if count == 0 {
return nil, fmt.Errorf("%w: %s missing [%s]", ErrTableEmpty, CharacterManagementPath, name)
}
if count != 1 {
return nil, fmt.Errorf("%w: %s duplicate [%s] count=%d", ErrTableMalformed, CharacterManagementPath, name, count)
}
tokens, ok := doc.Section(name)
if !ok {
return nil, fmt.Errorf("%w: %s unreadable [%s]", ErrTableMalformed, CharacterManagementPath, name)
}
values := make([]int64, 0, len(tokens))
for _, token := range tokens {
if token.Kind != dnfpvf.TokenInt {
return nil, fmt.Errorf("%w: %s [%s] non-integer token %q at %d:%d", ErrTableMalformed, CharacterManagementPath, name, token.Raw, token.Line, token.Column)
}
values = append(values, token.Int)
}
if len(values) == 0 && !allowEmpty {
return nil, fmt.Errorf("%w: %s [%s]", ErrTableEmpty, CharacterManagementPath, name)
}
return values, nil
}
func parsePointRanges(values []int64) ([]pointRange, error) {
if len(values)%3 != 0 {
return nil, fmt.Errorf("%w: [%s] integers=%d", ErrTableMalformed, pointBonusSection, len(values))
}
rows := make([]pointRange, 0, len(values)/3)
previousMax := 0
for index := 0; index < len(values); index += 3 {
minValue, maxValue, pointValue := values[index], values[index+1], values[index+2]
if minValue <= 0 || minValue > int64(math.MaxInt) || maxValue < minValue || maxValue > int64(math.MaxInt) || pointValue < 0 {
return nil, fmt.Errorf("%w: [%s] row=%d min=%d max=%d point=%d", ErrTableMalformed, pointBonusSection, index/3, minValue, maxValue, pointValue)
}
minLevel, maxLevel := int(minValue), int(maxValue)
if minLevel <= previousMax {
return nil, fmt.Errorf("%w: [%s] row=%d range=%d..%d overlaps or is unsorted after max=%d", ErrTableMalformed, pointBonusSection, index/3, minLevel, maxLevel, previousMax)
}
rows = append(rows, pointRange{minLevel: minLevel, maxLevel: maxLevel, point: uint64(pointValue)})
previousMax = maxLevel
}
return rows, nil
}
func parseThresholds(values []int64) ([]uint64, error) {
thresholds := make([]uint64, len(values))
var previous uint64
for index, value := range values {
if value <= 0 {
return nil, fmt.Errorf("%w: [%s] index=%d value=%d", ErrTableMalformed, managePointSection, index, value)
}
threshold := uint64(value)
if index > 0 && threshold <= previous {
return nil, fmt.Errorf("%w: [%s] index=%d value=%d previous=%d", ErrTableMalformed, managePointSection, index, threshold, previous)
}
thresholds[index] = threshold
previous = threshold
}
return thresholds, nil
}
func parseLevelValueTable(name string, values []int64, manageLevelMax int, allowEmpty bool) (map[int]uint64, error) {
if len(values) == 0 {
if allowEmpty {
return map[int]uint64{}, nil
}
return nil, fmt.Errorf("%w: [%s]", ErrTableEmpty, name)
}
if len(values)%2 != 0 {
return nil, fmt.Errorf("%w: [%s] integers=%d", ErrTableMalformed, name, len(values))
}
table := make(map[int]uint64, len(values)/2)
previousLevel := 0
for index := 0; index < len(values); index += 2 {
levelValue, dataValue := values[index], values[index+1]
if levelValue <= 0 || levelValue > int64(manageLevelMax) || levelValue > int64(math.MaxInt) || dataValue < 0 {
return nil, fmt.Errorf("%w: [%s] row=%d level=%d value=%d", ErrTableMalformed, name, index/2, levelValue, dataValue)
}
level := int(levelValue)
if level <= previousLevel {
return nil, fmt.Errorf("%w: [%s] row=%d level=%d previous=%d", ErrTableMalformed, name, index/2, level, previousLevel)
}
table[level] = uint64(dataValue)
previousLevel = level
}
return table, nil
}