Files
90CNServer/server/internal/services/dnfbridge/account_adventure_progress.go
T
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

71 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dnfbridge
import "longheng.io/server/internal/modules/dnf/adventuregroup"
// 冒险团商店每个页签的"当前窗口进度"。
//
// 现场取证(2026-09-23):商店点数 8 字节条目 = `{数量, 当前窗口进度}`,
// 而且条目**按位置**对应页签(不是按字段匹配)——
//
// - 数量(第 0 个 dword)决定页签头部的 "X / 250 个";
// - 进度(第 2 个 dword)决定那条"获得1个结晶"进度条的百分比,
// 分母是 PVF 的 `[exp to get shop point]`(1500 万);
// 成长胶囊那一格的分母是 `[capsule max exp]`。
//
// 证据:往第 0 条(骁勇结晶)的第 2 个 dword 写 1,500,000,商店里那条进度条
// 正好显示 10%;同一轮往报文 raw+24 / raw+32 写的探针值完全不显示,
// 说明进度既不在报文头部也不在别处,就在这个字段里。
//
// 以前这里写的是"分类号"(0/1/2/3/4),于是骁勇结晶那一格恒为 0 → 恒 0.00%。
func currentAdventureInfoShopPointProgress(
index int,
projection adventuregroup.Projection,
) uint32 {
switch index {
case int(adventuregroup.ShopPointBrave):
// 距下一个骁勇结晶还差多少经验(PVF 1500 万一个,余数即进度)。
return clampCurrentAdventureProgress(projection.Runtime.BraveExperience)
case currentAdventureInfoCapsuleProgressEntry:
// 成长胶囊页签(4 个页签里的第 4 个):当前这一格的进度。
// 胶囊可以攒 10 格,所以必须取"累计值 mod 单格",否则攒过 100%
// 之后客户端会算出 100% 以上、看起来卡住不动。
gauge := projection.CapsuleGauge
if gauge == 0 {
return 0
}
return clampCurrentAdventureProgress(projection.Runtime.GrowthExperience % gauge)
default:
// 荣耀结晶(远征队奖励)与纯粹结晶(帝国竞技场)没有经验窗口。
return 0
}
}
// currentAdventureInfoCapsuleClaimCount 返回成长胶囊页签"可获得的经验值"那一栏
// 要发的值 = 现在可以领取几次(每次 = 10% 档 = 1,000,000 点经验值)。
//
// 取证(2026-09-23):
// - 客户端解析 sub_C7DAF0 时把报文 raw+7416 存进 manager+0x310,
// 0x1DE1F30 就是 `mov eax,[ecx+0x310]; ret`;
// - 0x13F0FA0 拿到这个值后 `for (i = 0; i < count; i++)` 逐个拼字符串建列表
// —— 它是**次数**,不是经验值总量。第一版当成经验值发(1,100 万)会让
// 客户端建上千万个字符串而卡死,所以这里只用 adventuregroup 算出来的、
// 上限为"10 格 × 每格 10 次 = 100"的次数;
// - 同一个 manager 上的 0x308 是"领取请求已发出"标志,0xC79730 发 op1491 时
// **不带任何字段**,和服务端的空 body 校验一致;
// - 页签上那句说明文字自己写着"累积10%以上时可以领取经验值"、
// "经验值按照10% : 1,000,000点的比例换算"、"角色Lv : Lv.50 ~ 86"。
func currentAdventureInfoCapsuleClaimCount(projection adventuregroup.Projection) uint32 {
return projection.CapsuleClaimCount
}
// currentAdventureInfoCapsuleProgressEntry 是成长胶囊页签在商店点数数组里的
// 位置:数组 5 条 = 3 个结晶分类 + 成长胶囊 + 1 条备用,页签顺序与 XUI 一致。
const currentAdventureInfoCapsuleProgressEntry = 3
func clampCurrentAdventureProgress(value uint64) uint32 {
if value > uint64(^uint32(0)) {
return ^uint32(0)
}
return uint32(value)
}