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,137 @@
|
||||
package skillcmd
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrResponsePointRange = errors.New("skill response point value is out of uint16 range")
|
||||
ErrResponseEntryRange = errors.New("skill response entry is out of range")
|
||||
ErrResponseSlot = errors.New("skill response slot is unresolved")
|
||||
)
|
||||
|
||||
// currentEXEBuySkillFailureCode is a native, bounded op29 rejection understood
|
||||
// by the current NoPack.exe. sub_1D1E080 enters its failure branch when the
|
||||
// common dispatcher consumes success=0 and passes the following byte as a4;
|
||||
// 0x12 is one of the explicitly handled codes and, importantly, releases the
|
||||
// client's pending skill-learn operation.
|
||||
const currentEXEBuySkillFailureCode byte = 0x12
|
||||
|
||||
// BuildChangeSkillSlotSuccess matches current class1/op28 sub_1D0A4B0. The
|
||||
// dispatcher consumes the leading success byte; the handler then reads exactly
|
||||
// tree, from, and to before swapping the client-side vector.
|
||||
func BuildChangeSkillSlotSuccess(result SlotMutationResult) ([]byte, error) {
|
||||
cmd := Command{
|
||||
SkillTree: result.SkillTree,
|
||||
From: result.From,
|
||||
To: result.To,
|
||||
ContextIndex: -1,
|
||||
Mode: 0,
|
||||
}
|
||||
if err := validateSkillSlotCommand(cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte{1, result.SkillTree, result.From, result.To}, nil
|
||||
}
|
||||
|
||||
// BuildChangeSkillSlotFailure matches the common current-EXE dispatcher path:
|
||||
// a zero success byte closes the pending drag without invoking sub_1D0A4B0's
|
||||
// local slot-swap reader.
|
||||
func BuildChangeSkillSlotFailure() []byte {
|
||||
return []byte{0}
|
||||
}
|
||||
|
||||
// BuildChangeAnotherSkillTreeSuccess matches current class1/op260
|
||||
// sub_1D0C8F0: the dispatcher consumes success and the reader consumes exactly
|
||||
// one target-tree byte.
|
||||
func BuildChangeAnotherSkillTreeSuccess(result TreeSwitchMutationResult) ([]byte, error) {
|
||||
if result.Current >= currentEXESkillTreeCount || result.Target >= currentEXESkillTreeCount || result.Target == result.Current {
|
||||
return nil, fmt.Errorf("%w: current=%d target=%d", ErrSkillTree, result.Current, result.Target)
|
||||
}
|
||||
return []byte{1, result.Target}, nil
|
||||
}
|
||||
|
||||
// BuildChangeAnotherSkillTreeFailure is the live server's bounded op260
|
||||
// rejection: failure followed by resource/error code 19.
|
||||
func BuildChangeAnotherSkillTreeFailure() []byte {
|
||||
return []byte{0, 19}
|
||||
}
|
||||
|
||||
// BuildBuySkillSuccess encodes the complete current upper body. The leading
|
||||
// success byte is consumed by the dispatcher before sub_1D1E080 reads tree.
|
||||
func BuildBuySkillSuccess(result MutationResult) ([]byte, error) {
|
||||
if err := validateBuySkillTree(result.SkillTree); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.Points.RemainingSP < 0 || result.Points.RemainingSP > math.MaxUint16 ||
|
||||
result.Points.RemainingTP < 0 || result.Points.RemainingTP > math.MaxUint16 {
|
||||
return nil, ErrResponsePointRange
|
||||
}
|
||||
if len(result.Entries) > math.MaxUint8 {
|
||||
return nil, ErrResponseEntryRange
|
||||
}
|
||||
size := 8
|
||||
for _, entry := range result.Entries {
|
||||
if entry.Slot < 0 || entry.Slot > math.MaxUint8 {
|
||||
return nil, fmt.Errorf("%w: skill=%d slot=%d", ErrResponseSlot, entry.SkillID, entry.Slot)
|
||||
}
|
||||
if entry.Level < 0 || entry.Level > math.MaxUint8 || len(entry.CommandData) > math.MaxUint8 {
|
||||
return nil, fmt.Errorf("%w: skill=%d level=%d command=%d", ErrResponseEntryRange, entry.SkillID, entry.Level, len(entry.CommandData))
|
||||
}
|
||||
size += 5
|
||||
if len(entry.CommandData) > 0 {
|
||||
size += 1 + len(entry.CommandData)
|
||||
}
|
||||
}
|
||||
body := make([]byte, 0, size)
|
||||
body = append(body, 1, result.SkillTree)
|
||||
var number [2]byte
|
||||
binary.LittleEndian.PutUint16(number[:], uint16(result.Points.RemainingSP))
|
||||
body = append(body, number[:]...)
|
||||
binary.LittleEndian.PutUint16(number[:], uint16(result.Points.RemainingTP))
|
||||
body = append(body, number[:]...)
|
||||
body = append(body, byte(len(result.Entries)))
|
||||
for _, entry := range result.Entries {
|
||||
body = append(body, byte(entry.Slot))
|
||||
binary.LittleEndian.PutUint16(number[:], entry.SkillID)
|
||||
body = append(body, number[:]...)
|
||||
body = append(body, byte(entry.Level))
|
||||
if len(entry.CommandData) == 0 {
|
||||
body = append(body, 0)
|
||||
continue
|
||||
}
|
||||
body = append(body, 1, byte(len(entry.CommandData)))
|
||||
body = append(body, entry.CommandData...)
|
||||
}
|
||||
body = append(body, result.FinalMode)
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func BuildBuySkillFailure(code byte) []byte {
|
||||
return []byte{0, code}
|
||||
}
|
||||
|
||||
// BuildSkillInitSuccess matches current class1/op491 sub_1FEC390. The common
|
||||
// dispatcher consumes byte 0, then the handler reads tree and a non-zero
|
||||
// success/refresh flag.
|
||||
func BuildSkillInitSuccess(result ResetMutationResult) ([]byte, error) {
|
||||
if err := validateBuySkillTree(result.SkillTree); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte{1, result.SkillTree, 1}, nil
|
||||
}
|
||||
|
||||
// BuildSkillCommandSuccess mirrors the current EXE ACK-only reader: success
|
||||
// byte followed by the raw C2S command body.
|
||||
func BuildSkillCommandSuccess(body []byte) []byte {
|
||||
result := make([]byte, 1, len(body)+1)
|
||||
result[0] = 1
|
||||
return append(result, body...)
|
||||
}
|
||||
|
||||
func BuildSkillCommandAllDefaultSuccess() []byte {
|
||||
return []byte{1}
|
||||
}
|
||||
Reference in new issue
Block a user