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,199 @@
package raid
import (
"fmt"
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
)
const FiendWarPVFPath = "etc/raid/fiendwar/fiendwar.etc"
// FiendWarConfig keeps the area graph separate from the shared ticket/reward grammar.
type FiendWarConfig struct {
MemberMax int
StartMinimumMembers int
EntryCost AntonEntryCost
// These values live in the global section of fiendwar.etc and are used by
// the battle owner when a run is interrupted or failed.
DisconnectMemberWaitSeconds int
Retry bool
RetryItem AntonEntryCost
// Ranking rows belong to PHASE / CONDITION FOR RANK, not the globals.
DeadCount []FiendWarDeadCountRule
Battle *FiendWarBattleConfig
}
// FiendWarDeadCountRule maps an inclusive death-count range to the native
// result tier. The PVF uses four rows: min, max, tier.
type FiendWarDeadCountRule struct {
Min, Max, Tier int
}
// FiendWarDeadCountTier preserves the inclusive ranges from the PVF. Counts
// outside the configured rows have no proven native tier.
func FiendWarDeadCountTier(config *FiendWarConfig, count int) (int, bool) {
if config != nil {
for _, rule := range config.DeadCount {
if count >= rule.Min && count <= rule.Max {
return rule.Tier, true
}
}
}
return 0, false
}
func LoadFiendWarConfig(source dnfpvf.Source) (*FiendWarConfig, error) {
if source == nil {
return nil, fmt.Errorf("fiendwar raid PVF source is required")
}
text, err := source.ReadText(FiendWarPVFPath)
if err != nil {
return nil, err
}
document, err := dnfpvf.Parse(FiendWarPVFPath, text)
if err != nil {
return nil, err
}
config, err := ParseFiendWarConfig(document)
if err != nil {
return nil, err
}
symbolText, err := source.ReadText(FiendWarPVFPath + ".symbol")
if err != nil {
return nil, err
}
symbols, err := dnfpvf.Parse(FiendWarPVFPath+".symbol", symbolText)
if err != nil {
return nil, err
}
config.Battle, err = parseFiendWarBattle(config, document, symbols)
return config, err
}
func ParseFiendWarConfig(document *dnfpvf.Document) (*FiendWarConfig, error) {
if document == nil || antonPathKey(document.Path) != antonPathKey(FiendWarPVFPath) {
return nil, fmt.Errorf("invalid fiendwar raid PVF document")
}
phases, err := antonFindBlocks(document, "phase", 0, len(document.Sections))
if err != nil || len(phases) != 1 {
return nil, fmt.Errorf("invalid fiendwar phase boundary: %v", err)
}
end := phases[0].open
maximum, err := antonReadPositiveIntSection(document, "fiendwar member max", 0, end)
if err != nil {
return nil, err
}
minimum, err := antonReadPositiveIntSection(document, "fiendwar start minimum member", 0, end)
if err != nil {
return nil, err
}
if maximum > MaxAttackPartyMembers || minimum > maximum {
return nil, fmt.Errorf("invalid fiendwar roster bounds: %d..%d", minimum, maximum)
}
cost, err := antonParseEntryCost(document, 0, end)
if err != nil {
return nil, err
}
wait, err := antonReadPositiveIntSection(document, "disconn member wait time", 0, end)
if err != nil {
return nil, err
}
retryValues, err := fiendWarGlobalInts(document, "retry", 0, end)
if err != nil || len(retryValues) != 1 || (retryValues[0] != 0 && retryValues[0] != 1) {
return nil, fmt.Errorf("invalid fiendwar retry: %v", err)
}
retryItem, err := antonParseEntryCostNamed(document, "retry item", 0, end)
if err != nil {
return nil, err
}
ranks, err := antonFindBlocks(document, "condition for rank", phases[0].open+1, phases[0].close)
if err != nil || len(ranks) != 1 {
return nil, fmt.Errorf("invalid fiendwar rank boundary: %v", err)
}
dead, err := parseFiendWarDeadCounts(document, ranks[0].open+1, ranks[0].close)
if err != nil {
return nil, err
}
return &FiendWarConfig{
MemberMax: maximum, StartMinimumMembers: minimum, EntryCost: cost,
DisconnectMemberWaitSeconds: wait, Retry: retryValues[0] == 1,
RetryItem: retryItem, DeadCount: dead,
}, nil
}
func fiendWarGlobalInts(document *dnfpvf.Document, name string, start, end int) ([]int, error) {
i, err := antonUniqueSection(document, name, start, end)
if err != nil {
return nil, err
}
tokens, err := antonSectionTokens(document, i)
if err != nil {
return nil, err
}
values := make([]int, len(tokens))
for j, token := range tokens {
values[j], err = antonIntToken(token, name)
if err != nil {
return nil, err
}
}
return values, nil
}
func antonParseEntryCostNamed(document *dnfpvf.Document, name string, start, end int) (AntonEntryCost, error) {
sectionIndex, found, err := antonOptionalUniqueSection(document, name, start, end)
if err != nil {
return AntonEntryCost{}, err
}
if !found {
return AntonEntryCost{}, antonMissing(name, "required")
}
tokens, err := antonSectionTokens(document, sectionIndex)
if err != nil {
return AntonEntryCost{}, err
}
if len(tokens) != 2 {
return AntonEntryCost{}, fmt.Errorf("%s: want two values", name)
}
item, err := antonPositiveUint32Token(tokens[0], name+" item id")
if err != nil {
return AntonEntryCost{}, err
}
count, err := antonPositiveUint32Token(tokens[1], name+" count")
if err != nil {
return AntonEntryCost{}, err
}
return AntonEntryCost{ItemID: item, Count: count}, nil
}
func parseFiendWarDeadCounts(document *dnfpvf.Document, start, end int) ([]FiendWarDeadCountRule, error) {
want := antonSectionName("dead count")
sections := make([]int, 0, 4)
for i := start; i < end; i++ {
if antonSectionName(document.Sections[i].Name) == want {
sections = append(sections, i)
}
}
if len(sections) == 0 {
return nil, fmt.Errorf("dead count: missing rows")
}
out := make([]FiendWarDeadCountRule, 0, len(sections))
for _, section := range sections {
tokens, e := antonSectionTokens(document, section)
if e != nil || len(tokens) != 3 {
return nil, fmt.Errorf("dead count: want min/max/tier: %v", e)
}
values := [3]int{}
for i, token := range tokens {
values[i], e = antonIntToken(token, "dead count")
if e != nil {
return nil, e
}
}
if values[0] < 0 || values[1] < values[0] || values[2] < 0 {
return nil, fmt.Errorf("invalid dead count row %v", values)
}
out = append(out, FiendWarDeadCountRule{Min: values[0], Max: values[1], Tier: values[2]})
}
return out, nil
}