服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
136 lines
5.1 KiB
Go
136 lines
5.1 KiB
Go
// 本文件解析任务类 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)
|
|
}
|