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,87 @@
package dnfbridge
import dnfrepo "longheng.io/server/internal/modules/dnf/repository"
const (
defaultCharacterSlots = dnfrepo.DefaultCharacterSlotLimit
maxCharacterSlots = dnfrepo.MaxCharacterSlotLimit
noPackRosterWireSlotLimit = defaultCharacterSlots
// The account starts with 32 unlocked character slots. The current EXE's
// roster transport is separate: mode-2 carries slots 0..35 and op679 asks
// for each following 36-slot batch through an op2 mode-4 body.
currentRosterExtensionBatchSize = 36
latestRosterRouteNormal = 0
latestRosterContextNormal = 0
currentSceneObjectRoute = 0
currentSceneObjectContext = 0
upperMaxCharacter = 0xffff
latestCharacterStateRoute = 1
latestCharacterCreateEnabled = 1
latestCharacterStateActive = 1
latestCharacterStateNormal = 0
rosterNameMaxBytes = 30
rosterWideNameUnits = 31
noPackRosterPreJobBytes = 2
noPackRosterPostGrowBytes = 2
rosterLinkedIDBlockSize = 8
// Current sub_20026C0 consumes a mandatory type-44 raw block after every
// equipment item ID. Even an empty block has its u32 length prefix.
noPackRosterEquipRowBytes = 19
noPackRosterPostEquipBytes = 38
noPackRosterEntryTailBytes = noPackRosterPreJobBytes + 1 + 1 + noPackRosterPostGrowBytes + 1 + 4 + 4 + 1 + noPackRosterPostEquipBytes
rosterDefaultPageCount = 1
createCodeDuplicated = 0x00
createCodeSlotFull = 0x04
createCodeServerError = 0x14
// Current sub_1CFC9C0 silently returns for check-name failure status 0.
// Status 0x14 selects its visible duplicate-name prompt; the same value is
// used by the nearest 90US reference after live status-0/2 responses proved
// silent on the current client.
checkNameCodeDuplicated = 0x14
)
// currentSceneActorOwnerServerGroup 返回 actor 记录里那个 "server group" 字节。
//
// 移植说明:Server-90 里这个值在 90cn-new-native-v1 profile 下取
// channelAdvertiseID(),旧 profile 下取 currentSceneObjectRoute。本部署服务端
// 只有旧 profile(instance.go 硬性校验 90cn-decode-bypass-v1,全库无 new-native
// 代码),所以这里直接返回 currentSceneObjectRoute,与 Server-90 在旧 profile
// 下的行为逐字节一致。
//
// 支援兵的 mode1 需要这个值:它不能用「本地选中角色」的 server group,
// 否则客户端会把支援兵和本地 actor 归到同一格。
func (s *Service) currentSceneActorOwnerServerGroup() byte {
return currentSceneObjectRoute
}
func currentConnectionTownActorOwnerContext(session *gameSession) byte { if session == nil {
return currentSceneObjectContext
}
return session.connectionTownActorOwnerChannel
}
func currentTownActorOwnerContext(session *gameSession) byte {
if session == nil {
return currentSceneObjectContext
}
return session.townActorOwnerChannel
}
// currentPartySceneOwnerContext is deliberately separate from the selected
// actor owner. The local mode0/mode1 actor uses context 0 in the current client,
// while op9/op11 party records and remote actors are keyed by the connected
// channel. Test/manual sessions may provide an explicit connection owner; live
// account-bound sessions retain the channel in session.channel even though the
// local actor field is zero.
func currentPartySceneOwnerContext(session *gameSession) byte {
if session == nil {
return currentSceneObjectContext
}
if session.connectionTownActorOwnerChannel != currentSceneObjectContext {
return session.connectionTownActorOwnerChannel
}
if session.channel.ID > 0 && session.channel.ID <= 0xff {
return byte(session.channel.ID)
}
return session.townActorOwnerChannel
}