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,89 @@
package dnfbridge
import (
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
)
// accountSharedFirstGrant 是"先试账号共享仓"的结果。
type accountSharedFirstGrant struct {
// Slot 是该道具的账号共享固定格(354..365);不是账号共享道具时为 0。
// 即使本次一个都没放进去也照常返回。
Slot int16
// Placed 是本次真正并进账号仓的数量。
Placed int64
// Remainder 是账号仓没能装下、需要调用方按自己的容器规则溢出的数量。
Remainder uint32
// Changed 表示账号记录被改动了(调用方据此决定要不要提交账号记录)。
Changed bool
}
// grantCurrentAccountSharedFirst 是**所有获取途径的统一入口**:先把这个道具并进它在
// 账号共享仓库里的固定格,再把没能放进去的余量交回调用方,由调用方走自己原有的
// 角色侧发放(背包 / 勋章栏 list 38 / 复活币钱包 / 邮件…)。
//
// # 为什么要有它
//
// 口径只有一条:六个晶块(354..359)与六个灵魂(360..365)在同一个账号里只有那一个家。
// 可这套判定过去在 dnfbridge 里有 8 处各写一遍、另有 30 多处根本没写 ——
// 2026-09-21 的事故就是这么来的:NPC 商店**金币购买**那条自己写了一遍落点判定而漏了
// 固定槽这一支,20 个无色落进角色背包 0:154,同一条 list-0 报文里于是出现两行同 itemID,
// 客户端把背包那行显示成了仓库的数量。
//
// 新增功能只要调这一个函数:落点规则、容器额度规则、"固定格被别人占着"的处理,
// 以及新建行的溯源与 0x77 行盖章,都不必再抄一遍。
//
// # 与 dnfrepo.GrantAccountSharedWarehouseStack 的分工
//
// 那边是"并进固定格"的纯逻辑(任何包都能用,含额度与外来占用者判定);
// 这一层补上 bridge 专属的两件事:给入库后的行盖 0x77 raw entry(行构造器只在这里),
// 以及按 source 写溯源 Extra。
//
// # 调用方必须做的一件事
//
// **Remainder > 0 时必须把余量发出去**(溢出到背包或邮件)。原语只负责不吞掉它,
// 不负责替调用方决定送到哪 —— 少发一次就是玩家丢件。
//
// 该道具没有固定格(元素结晶、普通材料…)时返回 {Changed:false, Remainder:count},
// 调用方照常走原有发放路径,行为与改造前逐字一致。
func grantCurrentAccountSharedFirst(
account *dnfrepo.AccountInventoryRecord,
definition dungeonDropItemDefinition,
count uint32,
source string,
) accountSharedFirstGrant {
if account == nil || count == 0 {
return accountSharedFirstGrant{Remainder: count}
}
grant := dnfrepo.GrantAccountSharedWarehouseStack(
account,
int64(definition.ItemID),
definition.StackLimit,
int64(count),
dnfrepo.AccountSharedGrantMeta{
Source: source,
ItemKind: string(definition.Kind),
PVFPath: definition.PVFPath,
StackableType: definition.StackableType,
},
)
if !grant.Changed {
return accountSharedFirstGrant{Slot: grant.Slot, Remainder: count}
}
// 0x77 行由 bridge 这一侧盖章:原语刻意不碰 RawEntry,因为它不认识线格式。
key := dnfrepo.AccountSharedInventorySlotKey(grant.Slot)
stack := account.Slots[key]
entry := currentItemListEntryFromStack(dnfrepo.MainInventoryListType, grant.Slot, stack)
stack.RawEntry = append([]byte(nil), entry.data[:]...)
account.Slots[key] = stack
remainder := uint32(0)
if grant.Remainder > 0 {
remainder = uint32(grant.Remainder)
}
return accountSharedFirstGrant{
Slot: grant.Slot,
Placed: grant.Placed,
Remainder: remainder,
Changed: true,
}
}