Files
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

140 lines
5.0 KiB
Go

package dnfbridge
import (
"encoding/binary"
"fmt"
"sort"
"google.golang.org/protobuf/encoding/protowire"
darkcombo "longheng.io/server/internal/modules/dnf/darkknightcombo"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
)
const (
currentSkillInfoMsgID = 19
currentSkillInfoMessageType = 19
currentSkillInfoTreeIndex = 0
currentSkillInfoTreeCount = 2
currentSkillPrimaryQuickEnd = 6
currentSkillExtensionStart = 198
currentSkillSlotCount = 204
)
func buildCurrentSceneSkillInfoBody(record dnfrepo.SkillRecord, layouts map[int]dnfrepo.SkillLayout, activeTree byte) ([]byte, []int, error) {
return buildCurrentSceneSkillInfoBodyWithDarkKnightCombo(record, layouts, activeTree, nil)
}
func buildCurrentSceneSkillInfoBodyWithDarkKnightCombo(
record dnfrepo.SkillRecord,
layouts map[int]dnfrepo.SkillLayout,
activeTree byte,
pages []darkcombo.Page,
) ([]byte, []int, error) {
if activeTree >= currentSkillInfoTreeCount {
return nil, nil, fmt.Errorf("current active skill tree out of range: tree=%d", activeTree)
}
comboChildren := make(map[int]map[uint16][]uint16, len(pages))
for _, page := range pages {
treeIndex := int(page.Index)
if treeIndex < 0 || treeIndex >= currentSkillInfoTreeCount {
return nil, nil, fmt.Errorf("dark knight combo page outside skill tree range: page=%d", page.Index)
}
if comboChildren[treeIndex] == nil {
comboChildren[treeIndex] = make(map[uint16][]uint16, len(page.Roots))
}
for _, root := range page.Roots {
comboChildren[treeIndex][root.SkillID] = append([]uint16(nil), root.Children...)
}
}
var message []byte
message = appendProtoVarint(message, 1, currentSkillInfoMessageType)
activeSlots := make([]int, 0, 14)
for treeIndex := 0; treeIndex < currentSkillInfoTreeCount; treeIndex++ {
state, ok := dnfrepo.SkillTree(record, treeIndex)
if !ok {
// Legacy/synthetic commit snapshots may reach this formatter before the
// login backfill has created both durable pages. Keep the projection
// backward-compatible with an empty page; the scene owner still creates
// and persists the PVF-backed alternate tree before interactive use.
if treeIndex == currentSkillInfoTreeIndex {
state = dnfrepo.SkillTreeState{Skills: record.Skills, Points: record.Points}
}
}
layout := layouts[treeIndex]
slots := make([]int, 0, len(layout))
for slot, skillID := range layout {
if slot < 0 || slot >= currentSkillSlotCount {
return nil, nil, fmt.Errorf("current skill slot outside stock client range: slot=%d skill=%d count=%d", slot, skillID, currentSkillSlotCount)
}
skillState, ok := state.Skills[int64(skillID)]
if !ok || skillState.Level <= 0 {
continue
}
if slot < 0 || slot > 0x7fffffff {
return nil, nil, fmt.Errorf("current skill slot out of int32 range: slot=%d skill=%d", slot, skillID)
}
slots = append(slots, slot)
}
sort.Ints(slots)
var tree []byte
tree = appendProtoVarint(tree, 1, uint64(nonNegativeInt(state.Points.RemainingSP)))
tree = appendProtoVarint(tree, 2, uint64(nonNegativeInt(state.Points.RemainingTP)))
for _, slot := range slots {
skillID := layout[slot]
skillState := state.Skills[int64(skillID)]
if skillState.Level > 0x7fffffff {
return nil, nil, fmt.Errorf("current skill level out of client rank range: skill=%d level=%d", skillID, skillState.Level)
}
var skill []byte
skill = appendProtoVarint(skill, 1, uint64(slot))
skill = appendProtoVarint(skill, 2, uint64(skillID))
// sub_2666AE0 preserves the wire rank unless a current actor mapping
// explicitly replaces it. Persisted learned levels therefore stay direct.
skill = appendProtoVarint(skill, 3, uint64(skillState.Level))
if children, comboRoot := comboChildren[treeIndex][skillID]; comboRoot {
for _, childSkillID := range children {
skill = appendProtoVarint(skill, 4, uint64(childSkillID))
}
} else {
for _, commandByte := range record.Commands[int64(skillID)] {
skill = appendProtoVarint(skill, 4, uint64(commandByte))
}
}
tree = appendProtoBytes(tree, 3, skill)
if treeIndex == int(activeTree) && isCurrentSceneQuickSkillSlot(slot) {
activeSlots = append(activeSlots, slot)
}
}
message = appendProtoBytes(message, 2, tree)
}
body := make([]byte, 4, 4+len(message))
binary.LittleEndian.PutUint32(body, uint32(len(message)))
body = append(body, message...)
return body, activeSlots, nil
}
func isCurrentSceneQuickSkillSlot(slot int) bool {
return (slot >= 0 && slot < currentSkillPrimaryQuickEnd) ||
(slot >= currentSkillExtensionStart && slot < currentSkillSlotCount)
}
func appendProtoVarint(dst []byte, field protowire.Number, value uint64) []byte {
dst = protowire.AppendTag(dst, field, protowire.VarintType)
return protowire.AppendVarint(dst, value)
}
func appendProtoBytes(dst []byte, field protowire.Number, value []byte) []byte {
dst = protowire.AppendTag(dst, field, protowire.BytesType)
return protowire.AppendBytes(dst, value)
}
func nonNegativeInt(value int) int {
if value < 0 {
return 0
}
return value
}