分析对象:
Torchlight2.exe(32 位客户端,基址0x400000)
核心定位:解析掉落装备从生成到落地稳定期间的表现层管线,涵盖逐件飞出动画、稀有度粒子布局选择与三阶段音效调度。
0. 结论速览
原版装备掉落并非简单的物理刚体抛射。引擎首先依据逐件延迟将模型置为隐藏,延迟耗尽后由 sub_58EB70 构造名为 DROP 的三控制点 CPath;随后 sub_57D2A0 逐帧将路径进度递增 dt × 20,经 Catmull–Rom 曲线插值计算世界位置,到达路径末端时触发落地音效并结束飞行动画。
品质光晕也并非单一光柱仅靠着色器换色,sub_5808F0 在四套独立的粒子布局资源间做离散选择:
| 选择分支 | 布局资源 | 核心呈现元素 |
|---|---|---|
| 魔法 / 附魔装备 | MAGIC-ITEM.LAYOUT | 蓝色直立 Light、蓝色碎片、火花、gleam |
| 普通白色装备 | GENERIC-ITEM.LAYOUT | gleam 与垂直上升火花(无直立光柱) |
| 任务专属物品 | QUEST-ITEM.LAYOUT | 黄色光晕、尖刺、爆闪、火花与具名 Beam 节点 |
| 暗金 / 唯一装备 | UNIQUE-ITEM.LAYOUT | 橙色碎片、橙色 LightFloor、火花、gleam |
数据定义侧亦支持以 NODROPPARTICLES 彻底关停掉落粒子,或通过 DROPPARTICLE 指定自定义 LAYOUT 覆盖默认规则。
音效管线分为三个正交阶段:FALL_SOUND、LAND_SOUND、TAKE_SOUND 分别写入声音库槽位 25、26、27,在起飞开始、飞行结束落地、以及玩家拾取时各触发一次。
1. CEquipment 表现层虚表偏移校正
CEquipment 主虚表起始于 0x2174864。直接解析虚表槽位指针可得:
| 虚表偏移 | 指针位置 | 目标函数 | 实际职责 |
|---|---|---|---|
+460 | 0x2174A30 | sub_5A4C00 | 模型/部件显隐状态切换 |
+468 | 0x2174A38 | sub_5A51D0 | 依据地面法线调整模型静态朝向姿态 |
+532 | 0x2174A78 | sub_58EB70 | 完整装备掉落表现初始化入口 |
对应虚表字节序列自 0x2174A30 起为:
00 4C 5A 00 30 D4 57 00 D0 51 5A 00 ... 70 EB 58 00此前的推测将 +460 标注为落地弹跳、+532 仅负责延迟显形,经底层指令核对均不准确:+460 本质是部件显隐切换,+532 则是集成了路径曲线、姿态矩阵、粒子挂接与起飞音效的完整掉落表现初始化函数。
2. 延迟与飞出状态
与表现直接相关的 CEquipment 字段如下:
| 偏移 | 类型 | 用途 | 指令证据 |
|---|---|---|---|
+0x4AC | float | 逐件掉落延迟 | 0x583017: fcom [esi+4ACh];0x583032: fst [esi+4ACh] |
+0x288 | CPath* | DROP 路径 | 0x57D2B6: mov ecx,[ebx+288h] |
+0x28C | float | 路径进度 | 0x57D2E4: fadd [ebx+28Ch] |
+0x290 | byte | 飞行/延迟状态 | 0x58300C: cmp byte ptr [esi+290h],0 |
+0x360 | Matrix4 | 旋转所用起始姿态 | 0x57D3F3: lea ecx,[ebx+360h] |
+0x43C | 粒子实例指针 | 掉落品质粒子 | 0x580A9E: mov ecx,[esi+43Ch] |
+0x1F0 | 声音库 | 25/26/27 三声音槽 | 0x592B29: mov ecx,[esi+1F0h] |
sub_582FF0 是逐帧外层更新。其关键控制流是:
if (equipment->drop_active) { // +0x290
if (0.0f <= equipment->drop_delay) { // +0x4AC
equipment->drop_delay -= dt;
if (equipment->drop_delay < 0.0f)
equipment->vftable_plus_532(); // sub_58EB70
else
equipment->set_parts_visible(0, 1); // vtable+460
}
equipment_update_drop_flight(equipment, dt); // sub_57D2A0
}调用 +532 的原始指令为:
0x583041 8B 82 14 02 00 00 mov eax,[edx+214h]
0x583047 FF D0 call eax0x214 即十进制 532。延迟尚未结束时则从 [edx+0x1CC] 取 +460,传入 (0,1)。
3. sub_58EB70:装备掉落初始化
3.1 延迟分支
若 +0x4AC > 0,函数只调用 vtable+460(0,1) 隐藏模型,将 +0x290 置为 1, 然后返回。真正的飞出初始化由上一节的逐帧函数在延迟变为负数时再次调用。
延迟由 C-0178 已确认的发放循环按“第几件装备”递增;装备和金币各自使用独立计数器。 因此同一点生成的多件装备虽然逻辑落点重合,视觉上仍会依次飞出。
3.2 初始化步骤
延迟到期后的顺序是:
- 调用
sub_58DEB0整理渲染状态。 - 在
[0,360]内随机初始 yaw,并乘0.017453292转为弧度。 - 通过
vtable+460(1,1)显示模型,调用sub_5808F0附着品质粒子。 - 创建或重置名为
DROP的CPath,清+0x28C,置+0x290=1。 - 向路径加入起点、中点和落点三个控制点;中间弧度带
3.5..4.5的随机抬升/偏转, 每点以-60/+60为角度边界传给sub_675690。 - 调
sub_5F5240向地面扫描并求法线,再经vtable+468/sub_5A51D0贴合斜坡。 - 调
sub_577610对武器类别作额外展示姿态校正,保存起始矩阵到+0x360。 - 播放声音槽 25,即
FALL_SOUND。
默认分支的落点 Y 为地表再抬 1.51;另一分支抬 1.01。若是武器再加 0.05, unit type 188 还会再加 0.25。这些是展示高度,不应回写成 C-0178 的逻辑散落坐标。
3.3 曲线类型
sub_674C40(path, progress) 的展开公式含标准的 0.5 系数和相邻四点组合,是 Catmull–Rom spline 取样。这里没有重力积分、速度碰撞或刚体反弹;看起来像抛物线, 实质是预先构造的样条路径。
4. sub_57D2A0:每帧推进与落地
函数先要求:
+0x290 != 0
+0x288 != nullptr
+0x4AC <= 0之后执行:
progress += dt * 20.0f;
if (progress >= path->total_length) {
progress = path->total_length;
virtual_plus_48(1);
drop_active = false;
play_sound_slot(26); // LAND_SOUND
}
position = path->sample(progress); // sub_674C4020.0f 取自 0x2142624。关键指令:
0x57D2D7 fld [esp+arg_0]
0x57D2DE fmul ds:flt_2142624 ; 20.0
0x57D2E4 fadd [ebx+28Ch]
0x57D2EA fst [ebx+28Ch]
0x57D2F0 fcomp [ecx+24h] ; 路径总长
0x57D32D push 1Ah ; 槽 26
0x57D32F call sub_664F10
0x57D344 mov ecx,[ebx+288h]
0x57D34D call sub_674C40unit type 8 / 21 / 33 / 42 还会按 min(progress / total_length, 1) × 2π 在整段飞行中旋转一周。本文不强行给这四个数补 未证实的正式枚举名;控制流本身已钉死。
5. sub_5808F0:品质粒子选择
5.1 数据覆盖优先级
函数首先读取装备持有的原始 DAT group(CEquipment+0x1B0):
NODROPPARTICLES=true:跳过全部掉落粒子。DROPPARTICLE非空:装入指定 LAYOUT,覆盖默认品质选择。- 否则进入默认四分支。
发行 MEDIA/UNITS 普查结果:NODROPPARTICLES 只有 1 个 DAT;DROPPARTICLE 出现于 31 个 DAT,共 5 个不同路径,其中 27 个使用任务物品的 greenlights 布局。
5.2 默认四分支
if (sub_512D80(item))
index = 23; // QUEST-ITEM
else if (hasUnitType(0x36) || hasUnitType(0xF1))
index = 24; // UNIQUE-ITEM
else
index = 22 - virtual_plus_448(); // true -> 21 MAGIC, false -> 22 GENERIC直接证据:
0x580A54 call sub_512D80
0x580A5D mov edi,17h ; 23
0x580A64 push 36h
0x580A71 push 0F1h
0x580A83 mov eax,[edx+1C0h] ; vtable+448
0x580A94 add edi,16h ; 得 21 或 22
0x580A99 mov edi,18h ; 24主虚表的 +448 指向 sub_57EB60。它不仅检查某个“稀有度整数”,还会看 unit type、 affix/effect 向量并回退到 sub_577DB0;因此最稳妥的移植语义是“需要魔法掉落表现的 装备谓词”,不要简化成 rarity >= rare。
四个索引对应的路径由静态初始化函数 sub_107BE50 直接写死:
0x107C01C MAGIC-ITEM.LAYOUT -> 静态表项 21
0x107C031 GENERIC-ITEM.LAYOUT -> 静态表项 22
0x107C046 QUEST-ITEM.LAYOUT -> 静态表项 23
0x107C05B UNIQUE-ITEM.LAYOUT -> 静态表项 245.3 “光柱”的真实构成
GENERIC-ITEM:XING6.DDS的 Gleam 和 Rising sparkles。没有 Beam,也没有直立 Light。MAGIC-ITEM:GLOWBLUE.DDS的Billboard Up、IS LIGHT=true的 Light,另有蓝色 碎片、BLUEPARTICLE 上升火花与 gleam。QUEST-ITEM:GLOWYELLOW、BLUESTREAK、STAR3、MULTI-SPARKLE CLEAR、FLASH09,再加一个明确名为Beam的 Particle。Beam 使用RAY33.DDS,RENDER TYPE=Billboard Forward Camera、ORIGIN=Bottom Center、HEIGHT=10。UNIQUE-ITEM:STREAK、FRAGMENT_4X4ORANGE、FAST火花、XING6,以及GLOWORANGE.DDS的Billboard UpLightFloor。这是橙色地面亮光,不是同一个 Beam。
6. 三阶段声音
装备 DAT 加载函数 CEquipment__loadFromDat/sub_5923E0 读取三个键:
| DAT 键 | 声音槽 | 装载指令 | 播放时机 |
|---|---|---|---|
FALL_SOUND | 25 | 0x592B31: push 19h | sub_58EB70 飞出开始 |
LAND_SOUND | 26 | 0x592BCD: push 1Ah | sub_57D2A0 路径结束 |
TAKE_SOUND | 27 | 0x592C69: push 1Bh | sub_577830 拾取 |
三处随后均调用 sub_665FB0 将解析出的声音装进 CEquipment+0x1F0 的 sound bank。 实际播放通过 sub_664F10 按槽位、位置与声音配置分派。
有一个容易复刻错的时序:sub_53B110 在把装备加入背包之前就调用 sub_577830(item,0)。若背包已满,物品随后会被放回拾取者脚下,但拾取音已经播放。 所以声音对应“拾取动作被接受并从地面摘除”,不对应“背包最终成功容纳”。
发行 MEDIA/UNITS 有 624 个 DAT 显式写 TAKE_SOUND,共 16 种值;最高频是 ClothGet=279、GemDrop=117、LEATHERGET=69、Pickup=64。ITEMS.DAT 中通用 Pickup 与许多 *Get 最终使用 media/sounds/ui/pickup.wav。
7. 金币表现
金币类使用同样的三声音阶段,但有自己的实现:
sub_5A7D90:播放槽 25,创建DROP路径,附着MEDIA/PARTICLES/DROPS/GOLD_TRAIL.LAYOUT。sub_5A7900:到达落点时播放槽 26。sub_5A78B0:拾取时播放槽 27。
GOLD_TRAIL.LAYOUT 用 MULTIPARTICLE/COINS.DDS,渲染方式 Alpha,粒子寿命 0.15,发射率 3,所以金币飞行时会留下短促硬币拖尾。玩家 BASE.DAT 的 GOLD_SOUND 为 GOLDPICKUP;MEDIA/SOUNDS/ITEMS.DAT 中 GoldPickup 指向 media/sounds/item/drop/dropgold.wav,音量 0.15、频率变化 5000、半径 10。
8. 重制实现与表现层建议
在重制或独立运行时实现中,建议将逻辑结算落点与表现层动画路径严格解耦:
- 逻辑散点依据确定性掉落公式计算世界坐标,装备实体生成时不应施加随机力;
- 装备与金币各自维护独立计数器累加生成延迟,延迟生效期间隐藏渲染模型;
- 延迟结束时刻构建三控制点 Catmull–Rom 路径,并以标定速率(
20 units/s)沿曲线步进; - 落地判定点依据地面碰撞网格法线校正模型的静止旋转姿态,但不改变底层所属权及交互包围盒;
- 优先响应数据中的
NODROPPARTICLES与DROPPARTICLE显式配置;无自定义覆盖时再执行基于稀有度的四套默认布局分派; - 粒子效果应完整还原各 LAYOUT 预设内的多发射器与贴图图元,避免简化为单一颜色光柱;
- 严格在 launch / land / take 三个生命周期事件点触发音效,保持与原版一致的事件时序。
9. 自动化验证覆盖与后续边界
配套自动化验证用例覆盖以下关键项:
- 客户端发行版特征字节与虚表指针;
- 逐件延迟与路径推进相关的字段偏移及浮点运算指令;
- 四类默认粒子布局与覆盖字段解析;
- 声音槽位映射与资源文件路径。
以下边界逻辑保留后续深入追踪:
unit+0x8A3决定的0.25秒 stagger 触发来源;NetPickupItemMsg(49)与NetPickupNewItemMsg(50)网络消息包结构;- 掉落物在场景中的生命周期与超时清理机制;
- 飞行旋转判定中涉及的 unit type
8/21/33/42具体枚举名称。
上述未决项不影响本文关于飞出曲线、粒子布局与音效阶段的核心结论。