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,154 @@
|
||||
// 本文件解析时装和称号类 C2S 请求体。
|
||||
// 字段顺序来自当前已整理的 C# 旧实现与 Go 侧现有协议号对齐结果,成功回包仍需 EXE/MCP 闭合。
|
||||
package avatartitle
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const listTypeAvatar byte = 1
|
||||
|
||||
// CompoundAvatarRequest 描述时装合成请求。
|
||||
type CompoundAvatarRequest struct {
|
||||
ConsumeSlot int16
|
||||
Slot1 int16
|
||||
ExpectedItemID1 int32
|
||||
Slot2 int16
|
||||
ExpectedItemID2 int32
|
||||
RequestItemID int32
|
||||
AbilityNo uint16
|
||||
RequestedItemFlag byte
|
||||
MaterialClass byte
|
||||
}
|
||||
|
||||
// AvatarSocketRequest 描述时装开孔请求。
|
||||
type AvatarSocketRequest struct {
|
||||
TargetSlot int16
|
||||
TargetItemID int32
|
||||
MaterialSlot int16
|
||||
}
|
||||
|
||||
// AvatarEmblemRequest 描述徽章镶嵌请求。
|
||||
type AvatarEmblemRequest struct {
|
||||
TargetSlot int16
|
||||
TargetItemID int32
|
||||
Emblems []EmblemApply
|
||||
}
|
||||
|
||||
// EmblemApply 描述单个徽章应用记录。
|
||||
type EmblemApply struct {
|
||||
EmblemSlot int16
|
||||
EmblemItemID int32
|
||||
SocketIndex byte
|
||||
}
|
||||
|
||||
// TitleBookRequest 描述称号簿放入或取出请求。
|
||||
type TitleBookRequest struct {
|
||||
ItemSpaceRaw int32
|
||||
Slot int16
|
||||
ItemID int32
|
||||
Category int32
|
||||
Index int32
|
||||
}
|
||||
|
||||
// DecodeCompoundAvatarRequest 解析时装合成请求。
|
||||
func DecodeCompoundAvatarRequest(body []byte) (CompoundAvatarRequest, error) {
|
||||
if len(body) < 22 {
|
||||
return CompoundAvatarRequest{}, fmt.Errorf("body too short: got %d want >= 22", len(body))
|
||||
}
|
||||
return CompoundAvatarRequest{
|
||||
ConsumeSlot: readI16(body, 0),
|
||||
Slot1: readI16(body, 2),
|
||||
ExpectedItemID1: readI32(body, 4),
|
||||
Slot2: readI16(body, 8),
|
||||
ExpectedItemID2: readI32(body, 10),
|
||||
RequestItemID: readI32(body, 14),
|
||||
AbilityNo: binary.LittleEndian.Uint16(body[18:20]),
|
||||
RequestedItemFlag: body[20],
|
||||
MaterialClass: body[21],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DecodeAvatarSocketRequest 解析时装开孔请求。
|
||||
func DecodeAvatarSocketRequest(body []byte) (AvatarSocketRequest, error) {
|
||||
if len(body) < 8 {
|
||||
return AvatarSocketRequest{}, fmt.Errorf("body too short: got %d want >= 8", len(body))
|
||||
}
|
||||
return AvatarSocketRequest{
|
||||
TargetSlot: readI16(body, 0),
|
||||
TargetItemID: readI32(body, 2),
|
||||
MaterialSlot: readI16(body, 6),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DecodeAvatarEmblemRequest 解析时装徽章请求,兼容首字节为 Avatar list type 的包体。
|
||||
func DecodeAvatarEmblemRequest(body []byte) (AvatarEmblemRequest, error) {
|
||||
if len(body) >= 8 && body[0] == listTypeAvatar {
|
||||
return decodeEmblemAt(body, 1)
|
||||
}
|
||||
return decodeEmblemAt(body, 0)
|
||||
}
|
||||
|
||||
// DecodeTitleBookRequest 解析称号簿请求。
|
||||
func DecodeTitleBookRequest(body []byte) (TitleBookRequest, error) {
|
||||
if len(body) < 20 {
|
||||
return TitleBookRequest{}, fmt.Errorf("body too short: got %d want >= 20", len(body))
|
||||
}
|
||||
return TitleBookRequest{
|
||||
ItemSpaceRaw: readI32(body, 0),
|
||||
Slot: int16(readI32(body, 4)),
|
||||
ItemID: readI32(body, 8),
|
||||
Category: readI32(body, 12),
|
||||
Index: readI32(body, 16),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeEmblemAt(body []byte, offset int) (AvatarEmblemRequest, error) {
|
||||
if len(body) < offset+7 {
|
||||
return AvatarEmblemRequest{}, fmt.Errorf("body too short: got %d want >= %d", len(body), offset+7)
|
||||
}
|
||||
count := int(body[offset+6])
|
||||
req := AvatarEmblemRequest{
|
||||
TargetSlot: readI16(body, offset),
|
||||
TargetItemID: readI32(body, offset+2),
|
||||
Emblems: make([]EmblemApply, 0, count),
|
||||
}
|
||||
entryOffset := offset + 7
|
||||
for i := 0; i < count; i++ {
|
||||
if entryOffset+7 > len(body) {
|
||||
return AvatarEmblemRequest{}, fmt.Errorf("emblem entry too short: index %d", i)
|
||||
}
|
||||
req.Emblems = append(req.Emblems, EmblemApply{
|
||||
EmblemSlot: readI16(body, entryOffset),
|
||||
EmblemItemID: readI32(body, entryOffset+2),
|
||||
SocketIndex: body[entryOffset+6],
|
||||
})
|
||||
entryOffset += 7
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (r CompoundAvatarRequest) String() string {
|
||||
return fmt.Sprintf("consume=%d slot1=(%d,%d) slot2=(%d,%d) requestItem=%d ability=%d materialClass=%d", r.ConsumeSlot, r.Slot1, r.ExpectedItemID1, r.Slot2, r.ExpectedItemID2, r.RequestItemID, r.AbilityNo, r.MaterialClass)
|
||||
}
|
||||
|
||||
func (r AvatarSocketRequest) String() string {
|
||||
return fmt.Sprintf("target=(%d,%d) material=%d", r.TargetSlot, r.TargetItemID, r.MaterialSlot)
|
||||
}
|
||||
|
||||
func (r AvatarEmblemRequest) String() string {
|
||||
return fmt.Sprintf("target=(%d,%d) emblems=%d", r.TargetSlot, r.TargetItemID, len(r.Emblems))
|
||||
}
|
||||
|
||||
func (r TitleBookRequest) String() string {
|
||||
return fmt.Sprintf("space=%d slot=%d item=%d category=%d index=%d", r.ItemSpaceRaw, r.Slot, r.ItemID, r.Category, r.Index)
|
||||
}
|
||||
|
||||
func readI16(body []byte, offset int) int16 {
|
||||
return int16(binary.LittleEndian.Uint16(body[offset:]))
|
||||
}
|
||||
|
||||
func readI32(body []byte, offset int) int32 {
|
||||
return int32(binary.LittleEndian.Uint32(body[offset:]))
|
||||
}
|
||||
Reference in new issue
Block a user