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,346 @@
|
||||
package raid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
|
||||
)
|
||||
|
||||
type FiendWarArea struct {
|
||||
Index, WorldMap, Dungeon, LimitSeconds, MemberMax int
|
||||
Symbol uint32
|
||||
Kind string
|
||||
Next []int
|
||||
}
|
||||
|
||||
type FiendWarInstruction struct {
|
||||
Op string
|
||||
Symbol uint32
|
||||
Args []dnfpvf.Token
|
||||
}
|
||||
|
||||
type FiendWarRule struct {
|
||||
Conditions, Actions []FiendWarInstruction
|
||||
}
|
||||
|
||||
type FiendWarMerchantRate struct {
|
||||
Kind, Weight, DurationMS int
|
||||
}
|
||||
|
||||
type FiendWarMerchantPosition struct {
|
||||
Area, X, Y int
|
||||
}
|
||||
|
||||
type FiendWarBattleConfig struct {
|
||||
Raid *AntonConfig
|
||||
WaitingTown, WaitingArea int
|
||||
Areas map[int]FiendWarArea
|
||||
Symbols map[string]uint32
|
||||
Rules []FiendWarRule
|
||||
EnemyRates []int
|
||||
MerchantPositions []int
|
||||
MerchantRates []FiendWarMerchantRate
|
||||
MerchantPositionRows []FiendWarMerchantPosition
|
||||
DieImprisonSeconds []int
|
||||
DailyAccountEnterLimit int
|
||||
WaitingNPCs []int
|
||||
}
|
||||
|
||||
// Native sub_382A910. These are timer kinds, not packet opcodes.
|
||||
var FiendWarTimerKinds = map[string]byte{
|
||||
"move_enemy": 0, "create_monster": 1, "activate_radar": 2,
|
||||
"activate_battery": 3, "top_create_enemy": 4, "mid_create_enemy": 5,
|
||||
"bot_create_enemy": 6, "top_restore_center": 7, "mid_restore_center": 8,
|
||||
"bot_restore_center": 9, "warning_enemy": 10, "delete_monster": 11,
|
||||
}
|
||||
|
||||
func parseFiendWarBattle(recruitment *FiendWarConfig, doc, symbolDoc *dnfpvf.Document) (*FiendWarBattleConfig, error) {
|
||||
c := &FiendWarBattleConfig{Areas: make(map[int]FiendWarArea), Symbols: make(map[string]uint32)}
|
||||
// The .symbol file pairs an integer with the following section token.
|
||||
for i, token := range symbolDoc.Tokens {
|
||||
if token.Kind != dnfpvf.TokenSection {
|
||||
continue
|
||||
}
|
||||
if i == 0 || symbolDoc.Tokens[i-1].Kind != dnfpvf.TokenInt || symbolDoc.Tokens[i-1].Int <= 0 {
|
||||
return nil, fmt.Errorf("fiendwar symbol %q has no numeric index", token.Value)
|
||||
}
|
||||
name := antonSectionName(token.Value)
|
||||
if _, exists := c.Symbols[name]; exists {
|
||||
return nil, fmt.Errorf("duplicate fiendwar symbol %s", name)
|
||||
}
|
||||
c.Symbols[name] = uint32(symbolDoc.Tokens[i-1].Int)
|
||||
}
|
||||
phases, err := antonFindBlocks(doc, "phase", 0, len(doc.Sections))
|
||||
if err != nil || len(phases) != 1 {
|
||||
return nil, fmt.Errorf("fiendwar phase: %v", err)
|
||||
}
|
||||
end := phases[0].open
|
||||
c.DailyAccountEnterLimit, err = antonReadPositiveIntSection(doc, "daily account enter limit", 0, end)
|
||||
if err != nil || c.DailyAccountEnterLimit > 255 {
|
||||
return nil, fmt.Errorf("invalid fiendwar daily account limit: %v", err)
|
||||
}
|
||||
base := &AntonConfig{PhaseMax: 1, MemberMax: recruitment.MemberMax, StartMinimumMembers: recruitment.StartMinimumMembers, EntryCost: recruitment.EntryCost}
|
||||
if base.StartDelaySeconds, err = antonReadPositiveIntSection(doc, "start delay time", 0, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if base.PhaseTimeoutSeconds, err = antonReadPositiveIntListSection(doc, "phase time over", 0, end, 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if base.MovieEndSeconds, err = antonReadPositiveIntListSection(doc, "movie end time", 0, end, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values, err := fiendWarInts(doc, "waiting room", 0, end)
|
||||
if err != nil || len(values) != 2 {
|
||||
return nil, fmt.Errorf("fiendwar waiting room: %v", err)
|
||||
}
|
||||
c.WaitingTown, c.WaitingArea = values[0], values[1]
|
||||
if c.WaitingNPCs, err = fiendWarInts(doc, "waiting room npc", 0, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.DieImprisonSeconds, err = fiendWarInts(doc, "die imprison time", 0, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(c.DieImprisonSeconds) == 0 {
|
||||
return nil, fmt.Errorf("missing fiendwar imprisonment durations")
|
||||
}
|
||||
for _, seconds := range c.DieImprisonSeconds {
|
||||
if seconds <= 0 {
|
||||
return nil, fmt.Errorf("invalid fiendwar imprisonment duration")
|
||||
}
|
||||
}
|
||||
if c.EnemyRates, err = fiendWarInts(doc, "enemy create rate", 0, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(c.EnemyRates) == 0 || len(c.EnemyRates)%2 != 0 {
|
||||
return nil, fmt.Errorf("invalid fiendwar enemy rates")
|
||||
}
|
||||
for i := 0; i < len(c.EnemyRates); i += 2 {
|
||||
if c.EnemyRates[i] < 1 || c.EnemyRates[i] > 4 || c.EnemyRates[i+1] <= 0 {
|
||||
return nil, fmt.Errorf("invalid fiendwar enemy weight")
|
||||
}
|
||||
}
|
||||
if values, err = fiendWarInts(doc, "merchant create pos", 0, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%3 != 0 {
|
||||
return nil, fmt.Errorf("invalid fiendwar merchant positions")
|
||||
}
|
||||
for i := 0; i < len(values); i += 3 {
|
||||
row := FiendWarMerchantPosition{Area: values[i], X: values[i+1], Y: values[i+2]}
|
||||
c.MerchantPositionRows = append(c.MerchantPositionRows, row)
|
||||
c.MerchantPositions = append(c.MerchantPositions, row.Area)
|
||||
}
|
||||
if values, err = fiendWarInts(doc, "merchant create rate", 0, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values) == 0 || len(values)%3 != 0 {
|
||||
return nil, fmt.Errorf("invalid fiendwar merchant rates")
|
||||
}
|
||||
for i := 0; i < len(values); i += 3 {
|
||||
if values[i] < 0 || values[i+1] <= 0 || values[i+2] <= 0 {
|
||||
return nil, fmt.Errorf("invalid fiendwar merchant rate")
|
||||
}
|
||||
c.MerchantRates = append(c.MerchantRates, FiendWarMerchantRate{Kind: values[i], Weight: values[i+1], DurationMS: values[i+2]})
|
||||
}
|
||||
p := phases[0]
|
||||
name, err := antonParseSingleModeName(doc, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phase := AntonPhase{Name: name}
|
||||
phase.MinimalDungeonClearCount, err = antonReadPositiveIntSection(doc, "minimal dungeon clear count", p.open+1, p.close)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rewardBlocks, err := antonFindBlocks(doc, "clear reward item", p.open+1, p.close)
|
||||
if err != nil || len(rewardBlocks) != 1 {
|
||||
return nil, fmt.Errorf("fiendwar rewards: %v", err)
|
||||
}
|
||||
if phase.Rewards, err = antonParseRewards(doc, rewardBlocks[0], 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
areaBlocks, err := antonFindBlocks(doc, "area info", p.open+1, p.close)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dungeons := make(map[int]bool)
|
||||
for _, b := range areaBlocks {
|
||||
a := FiendWarArea{}
|
||||
for field, dst := range map[string]*int{"area index": &a.Index, "worldmap index": &a.WorldMap, "dungeon index": &a.Dungeon, "member max": &a.MemberMax} {
|
||||
v, e := fiendWarInts(doc, field, b.open+1, b.close)
|
||||
if e != nil || len(v) != 1 {
|
||||
return nil, fmt.Errorf("fiendwar area %s: %v", field, e)
|
||||
}
|
||||
*dst = v[0]
|
||||
}
|
||||
if a.Index < 1 || a.Index > 127 || a.MemberMax < 1 || a.MemberMax > recruitment.MemberMax {
|
||||
return nil, fmt.Errorf("invalid fiendwar area %+v", a)
|
||||
}
|
||||
if _, exists := c.Areas[a.Index]; exists {
|
||||
return nil, fmt.Errorf("duplicate fiendwar area %d", a.Index)
|
||||
}
|
||||
a.Symbol = c.Symbols[fmt.Sprintf("%d area state", a.Index)]
|
||||
if a.Symbol == 0 {
|
||||
return nil, fmt.Errorf("missing fiendwar area symbol %d", a.Index)
|
||||
}
|
||||
if a.Next, err = fiendWarInts(doc, "move available map", b.open+1, b.close); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := b.open + 1; i < b.close; i++ {
|
||||
field := antonSectionName(doc.Sections[i].Name)
|
||||
if strings.HasSuffix(field, " area") && field != "on giveup area" {
|
||||
a.Kind = strings.TrimSuffix(field, " area")
|
||||
}
|
||||
}
|
||||
if a.Dungeon > 0 {
|
||||
if dungeons[a.Dungeon] {
|
||||
return nil, fmt.Errorf("duplicate fiendwar dungeon %d", a.Dungeon)
|
||||
}
|
||||
dungeons[a.Dungeon] = true
|
||||
if a.LimitSeconds, err = antonReadPositiveIntSection(doc, "dungeon limit time", b.open+1, b.close); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Each area clear is one completed dungeon, not Anton's HP counter.
|
||||
phase.Dungeons = append(phase.Dungeons, AntonDungeonInfo{Index: uint32(a.Dungeon), InitState: AntonDungeonHide, HP: 1, PartyMax: 1})
|
||||
}
|
||||
c.Areas[a.Index] = a
|
||||
}
|
||||
if _, ok := c.Areas[c.WaitingArea]; !ok {
|
||||
return nil, fmt.Errorf("missing fiendwar waiting area")
|
||||
}
|
||||
for _, a := range c.Areas {
|
||||
for _, n := range a.Next {
|
||||
if _, ok := c.Areas[n]; !ok {
|
||||
return nil, fmt.Errorf("unknown fiendwar adjacent area %d", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
triggers, err := antonFindBlocks(doc, "trigger", p.open+1, p.close)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
behaviors, err := antonFindBlocks(doc, "behavior", p.open+1, p.close)
|
||||
if err != nil || len(triggers) != len(behaviors) || len(triggers) == 0 {
|
||||
return nil, fmt.Errorf("fiendwar rule pairs: %v", err)
|
||||
}
|
||||
for i, t := range triggers {
|
||||
b := behaviors[i]
|
||||
if t.close+1 != b.open {
|
||||
return nil, fmt.Errorf("fiendwar unpaired trigger %d", i)
|
||||
}
|
||||
conditions, e := c.parseInstructions(doc, t, true)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
actions, e := c.parseInstructions(doc, b, false)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
c.Rules = append(c.Rules, FiendWarRule{conditions, actions})
|
||||
}
|
||||
base.Phases = []AntonPhase{phase}
|
||||
c.Raid = base
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func fiendWarInts(doc *dnfpvf.Document, name string, start, end int) ([]int, error) {
|
||||
i, err := antonUniqueSection(doc, name, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokens, err := antonSectionTokens(doc, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values := make([]int, len(tokens))
|
||||
for j, t := range tokens {
|
||||
values[j], err = antonIntToken(t, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (c *FiendWarBattleConfig) parseInstructions(doc *dnfpvf.Document, b antonSectionBlock, condition bool) ([]FiendWarInstruction, error) {
|
||||
var out []FiendWarInstruction
|
||||
for i := b.open + 1; i < b.close; i++ {
|
||||
name := antonSectionName(doc.Sections[i].Name)
|
||||
args, err := antonSectionTokens(doc, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inst := FiendWarInstruction{Op: name, Args: args}
|
||||
if name == "if" || name == "set" || name == "on change symbol" {
|
||||
i++
|
||||
if i >= b.close {
|
||||
return nil, fmt.Errorf("fiendwar missing symbol after %s", name)
|
||||
}
|
||||
inst.Symbol = c.Symbols[antonSectionName(doc.Sections[i].Name)]
|
||||
if inst.Symbol == 0 {
|
||||
return nil, fmt.Errorf("unknown fiendwar symbol %s", doc.Sections[i].Name)
|
||||
}
|
||||
inst.Args, err = antonSectionTokens(doc, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if name == "if" && len(inst.Args) == 0 {
|
||||
i++
|
||||
if i >= b.close || antonSectionName(doc.Sections[i].Name) != "<" {
|
||||
return nil, fmt.Errorf("unsupported fiendwar comparison")
|
||||
}
|
||||
inst.Op = "less"
|
||||
inst.Args, err = antonSectionTokens(doc, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
shape := ""
|
||||
if condition {
|
||||
switch inst.Op {
|
||||
case "if", "less", "on change area state", "on giveup area", "on enter dungeon", "on enter map", "on giveup dungeon", "on change dungeon state":
|
||||
shape = "i"
|
||||
case "on change symbol":
|
||||
shape = ""
|
||||
case "check area state", "check dungeon state":
|
||||
shape = "is"
|
||||
case "check timer end":
|
||||
shape = "si"
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported fiendwar condition %s", inst.Op)
|
||||
}
|
||||
} else {
|
||||
switch inst.Op {
|
||||
case "set", "create enemy", "delete monster", "warning enemy":
|
||||
shape = "i"
|
||||
case "move enemy":
|
||||
shape = "ii"
|
||||
case "set timer":
|
||||
shape = "sii"
|
||||
case "reset timer":
|
||||
shape = "si"
|
||||
case "create merchant", "create monsters", "delete all enemy", "boss pattern init", "clear phase", "fail fiendwar":
|
||||
shape = ""
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported fiendwar action %s", inst.Op)
|
||||
}
|
||||
}
|
||||
if len(inst.Args) != len(shape) {
|
||||
return nil, fmt.Errorf("fiendwar %s argument count", inst.Op)
|
||||
}
|
||||
for j, k := range shape {
|
||||
if k == 'i' && inst.Args[j].Kind != dnfpvf.TokenInt || k == 's' && inst.Args[j].Kind != dnfpvf.TokenString {
|
||||
return nil, fmt.Errorf("fiendwar %s argument type", inst.Op)
|
||||
}
|
||||
}
|
||||
if strings.Contains(inst.Op, "timer") {
|
||||
if _, ok := FiendWarTimerKinds[inst.Args[0].Value]; !ok {
|
||||
return nil, fmt.Errorf("unknown fiendwar timer")
|
||||
}
|
||||
}
|
||||
out = append(out, inst)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in new issue
Block a user