服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
71 lines
3.4 KiB
Go
71 lines
3.4 KiB
Go
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)
|
||
}
|