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,135 @@
// 本文件解析任务类 C2S 请求体。
// C# QuestManager 会在 body 长度大于 2 时去掉前 2 字节 wire-type echo,再交给 QuestService。
package quest
import (
"encoding/binary"
"fmt"
"longheng.io/server/internal/modules/dnf/dnfenum"
)
const (
CurrentFinishQuestRequestBodySize = 10
CurrentFinishQuestObservedTailMarker = ^uint16(0)
// CurrentSetTriggerRequestBodySize is the scalar op33 shape:
// echo | quest | add/remove | reserved-or-answer.
CurrentSetTriggerRequestBodySize = 6
// CurrentMaskedSetTriggerRequestBodySize is the current-client packed
// objective shape used by multi-lane tasks such as the Hell Party
// challenges: echo | quest | lane mask | add/remove | reserved.
CurrentMaskedSetTriggerRequestBodySize = 7
)
// QuestIDRequest 描述只携带 questId 的任务请求。
type QuestIDRequest struct {
QuestID uint16
}
// SetTriggerRequest 描述任务触发进度请求。
type SetTriggerRequest struct {
QuestID uint16
TriggerType byte
TriggerArgument byte
IsIncrement bool
}
// FinishQuestRequest 描述完成任务请求。
type FinishQuestRequest struct {
QuestID uint16
RewardSelectIndex uint16
HasRewardSelect bool
Multiplier uint16
Reserved uint16
}
// DecodeQuestIDRequest 解析接取/放弃任务请求。
func DecodeQuestIDRequest(body []byte) (QuestIDRequest, error) {
qBody := stripEcho(body)
if len(qBody) < 2 {
return QuestIDRequest{}, fmt.Errorf("body too short after echo strip: got %d want >= 2", len(qBody))
}
return QuestIDRequest{QuestID: binary.LittleEndian.Uint16(qBody)}, nil
}
// DecodeSetTriggerRequest parses both proven current-client op33 forms. The
// seven-byte form is not a transport trailer: its 0x10/0x20/0x40 mask selects
// one 9-bit lane of the packed quest trigger. Older six-byte masked clones are
// retained only as a compatibility input for an already-supported profile.
func DecodeSetTriggerRequest(body []byte) (SetTriggerRequest, error) {
if len(body) != CurrentSetTriggerRequestBodySize && len(body) != CurrentMaskedSetTriggerRequestBodySize {
return SetTriggerRequest{}, fmt.Errorf("set quest trigger body size: got %d want %d or %d", len(body), CurrentSetTriggerRequestBodySize, CurrentMaskedSetTriggerRequestBodySize)
}
if echo := binary.LittleEndian.Uint16(body[:2]); echo != uint16(dnfenum.CmdPacketSetQuestTrigger) {
return SetTriggerRequest{}, fmt.Errorf("set quest trigger echo: got %d want %d", echo, dnfenum.CmdPacketSetQuestTrigger)
}
request := SetTriggerRequest{QuestID: binary.LittleEndian.Uint16(body[2:4])}
if request.QuestID == 0 {
return SetTriggerRequest{}, fmt.Errorf("set quest trigger has zero quest ID")
}
if len(body) == CurrentMaskedSetTriggerRequestBodySize {
switch body[4] {
case 0x10, 0x20, 0x40:
request.TriggerType = body[4]
default:
return SetTriggerRequest{}, fmt.Errorf("set quest trigger mask: got %#x want 0x10, 0x20, or 0x40", body[4])
}
if body[5] > 1 {
return SetTriggerRequest{}, fmt.Errorf("set quest trigger add/remove: got %d want boolean", body[5])
}
if body[6] != 0 {
return SetTriggerRequest{}, fmt.Errorf("set quest trigger masked reserved: got %#x want 0", body[6])
}
request.IsIncrement = body[5] != 0
return request, nil
}
// Retain the established local six-byte clone unchanged. Its last two
// bytes are trigger type and trigger argument; several existing quest
// families (notably seeking) still use type 1 through that compatibility
// route. The current masked Hell Party form above is unambiguous because it
// is seven bytes.
request.TriggerType = body[4]
request.TriggerArgument = body[5]
request.IsIncrement = body[5] != 0
return request, nil
}
// DecodeFinishQuestRequest 解析完成任务请求。
func DecodeFinishQuestRequest(body []byte) (FinishQuestRequest, error) {
if len(body) != CurrentFinishQuestRequestBodySize {
return FinishQuestRequest{}, fmt.Errorf("current EXE finish-quest body size: got %d want %d", len(body), CurrentFinishQuestRequestBodySize)
}
if echo := binary.LittleEndian.Uint16(body[0:2]); echo != uint16(dnfenum.CmdPacketFinishQuest) {
return FinishQuestRequest{}, fmt.Errorf("current EXE finish-quest echo: got %d want %d", echo, dnfenum.CmdPacketFinishQuest)
}
req := FinishQuestRequest{
QuestID: binary.LittleEndian.Uint16(body[2:4]),
RewardSelectIndex: binary.LittleEndian.Uint16(body[4:6]),
Multiplier: binary.LittleEndian.Uint16(body[6:8]),
Reserved: binary.LittleEndian.Uint16(body[8:10]),
}
req.HasRewardSelect = req.RewardSelectIndex != ^uint16(0)
return req, nil
}
func stripEcho(body []byte) []byte {
if body == nil || len(body) <= 2 {
return body
}
return body[2:]
}
func (r QuestIDRequest) String() string {
return fmt.Sprintf("quest=%d", r.QuestID)
}
func (r SetTriggerRequest) String() string {
return fmt.Sprintf("quest=%d triggerType=%d argument=%d increment=%t", r.QuestID, r.TriggerType, r.TriggerArgument, r.IsIncrement)
}
func (r FinishQuestRequest) String() string {
return fmt.Sprintf("quest=%d rewardSelect=%d hasReward=%t multiplier=%d reserved=%d", r.QuestID, r.RewardSelectIndex, r.HasRewardSelect, r.Multiplier, r.Reserved)
}