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,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
|
||||
}
|
||||
Reference in new issue
Block a user