fix(skill): 修复冰尖柱 (Glacial Spike #55) 命中未触发爆炸动画与 64px 群体冰冻伤害判定缺陷 #430

Closed
opened 2026-09-24 07:55:29 +00:00 by troytt · 0 comments
Owner

问题背景与缺陷表现 (Background & Defect Analysis)

在当前客户端实操与技能实装中,法师 18 级核心冰系技能 冰尖柱 (Glacial Spike, Skill ID: 55) 存在严重的命中结算与原版一致性缺陷:

  1. 未触发爆炸与冰锥飞溅特效:冰尖柱飞行投射物击中敌方单位或撞击墙体/障碍物时,未生成原版标志性的冰爆中心云(freezeexplode / FreezeExplodeCenter)与次级碎冰飞溅特效(glacialspikeejecta / FreezeExplodeEjecta),缺乏打击感。
  2. 缺失群体 64px 溅射冰冻与伤害判定:原版冰尖柱属于典型的 AoE 冰爆技能,但目前在客户端战斗链路中,仅有单体直击判定,未对以碰撞点为中心 64 像素(4 subtiles,约 2.6 码)范围内的敌群造成群体冰冷伤害与固化冰冻效果。
  3. 缺少冻结视觉定身(Solid Freeze)反馈:即使目标处于 frozenTicks > 0,场景渲染层(src/scene/act-scene.ts)仅有 chillTicks(减速迟缓)着色逻辑,缺失绝对冰冻(Freeze)的动画帧冻结与高饱和度冰蓝材质着色,导致玩家视觉上误以为怪物完全未被冰冻。

暴雪 1.13c 原版权威数据与机制基准 (Blizzard 1.13c Ground Truth Invariants)

根据 Diablo II v1.13c 官方数据表(Skills.txt、Missiles.txt)与逆向汇编规范:

1. Skills.txt (Row 55: Glacial Spike)

  • Skill ID: 55 (#055), Class: sor, ReqLevel: 18
  • Missile: srvmissile = "glacialspike", cltmissile = "glacialspike"
  • Cast Overlay: ice_cast_2
  • Param1 (爆炸半径): 4 subtiles(即 4 * 16 = 64 像素,在 2:1 等轴测视角下为 64px 椭圆判定,约 2.6 码)
  • Param2 (半径等级成长): 0(固定 64px,不随等级扩大)
  • Param3 (基础冰冻时长): 50 帧(2.00 秒)
  • Param4 (冰冻每级成长): 3 帧 / 级(+0.12 秒 / 级)
  • Param7 (暴风雪协同): 3(每投入 1 点暴风雪基础技能,冰冻时间延长 +3%)
  • Param8 (伤害协同): 5(每投入 1 点冰弹 / 冰风暴 / 冰封球,冰冷伤害增加 +5%)
  • 公式:
    • 冰冻时间公式:auralencalc = "ln34 * (100 + skill('Blizzard'.blvl) * par7) / 100"
    • 伤害公式:EDmgSymPerCalc = "(skill('Ice Bolt'.blvl) + skill('Ice Blast'.blvl) + skill('Frozen Orb'.blvl)) * par8"
    • 伤害档位(HitShift = 7,基数需除以 2):1 级为 16–24,20 级无协同为 225–242.5。

2. Missiles.txt (Row 96: glacialspike)

  • Functions: pSrvDoFunc = 1 (直线飞行), pSrvHitFunc = 13 (原版 MISSMODE_SrvHit13_GlacialSpike)
  • Hit Params:
    • Server Hit Param 1: damage radius (0 = skill) -> 引用技能 Param1 (4 subtiles = 64px)
    • Server Hit Param 2: freeze length (0 = skill) -> 引用技能 Param3/Param4 及协同公式
  • SubMissiles (次级投射物与命中特效):
    • CltHitSubMissile1 = "freezingarrowexp1" (即 freezeexplode / FreezeExplodeCenter,ID 88,AnimLen 15)
    • CltHitSubMissile2 = "freezingarrowexp2" (即 glacialspikeejecta / FreezeExplodeEjecta,ID 89,飞溅冰锥)
    • HitSound = "sorceress_iceblast_impact_1"
  • Kinematics: Vel = 16 (400 px/s), Range = 40 (640 px), CollideKill = 1, LastCollide = 1

缺陷根因深度剖析 (Root Cause Analysis)

根因 1:src/game/skills.ts (tickProjectiles) 投射物命���未分发爆炸投射物与次级投射物

在 tickProjectiles 中(约 4597–4618 行):

if (!projectile.pierce) {
  struck = true
  if (isBlizzardShard(projectile.missileType)) {
    const expType = getBlizzardExplosionMissile(projectile.missileType!)
    spawned.push({ ... })
  }
  break
}

当投射物击中非穿透目标时,当前逻辑仅对暴风雪碎片(isBlizzardShard)推入了爆炸投射物,而对 glacialspike、fireball 等需要爆裂效果的投射物,完全没有根据 explosionMissile / hitSubMissile 产生爆破投射物派生!

根因 2:src/game/engine.ts 中怪物实例索引与循环下标混淆,导致 AoE 溅射结算脱靶

在 engine.tick()(约 440–505 行)中:

const targets = this.world.monsters.map((monster, index) => ({
  index, x: monster.x, y: monster.y, radius: 20, alive: monster.state !== 'dead',
}))
// ...
for (const hit of outcome.hits) {
  damageMonster(this.world, hit.targetIndex, hit.damage) // hit.targetIndex 为 map 下标 index
  // ...
  if (hit.aoeRadius && hit.aoeRadius > 0) {
    for (const m of this.world.monsters) {
      if (m.index === hit.targetIndex || m.state === 'dead' || m.hp <= 0) continue
      // ...
      if (Math.hypot(dx, dy) <= hit.aoeRadius) {
        damageMonster(this.world, m.index, hit.damage) // 💥 错误!m.index 为怪物 ID,但 damageMonster 内部直接取 world.monsters[index]!
      }
    }
  }
}
  1. hit.targetIndex 来自 targets(即数组下标 0, 1, 2, ...),而排除自身时却使用了 m.index === hit.targetIndex(m.index 为怪物在生成时赋予的 ID,一旦有怪物死亡清理或非连续下标,就会出现自身判定混乱)。
  2. 在对范围内的次要目标派发伤害时,传入了 m.index,而 damageMonster(world, monsterIndex) 是按数组下标寻址 world.monsters[monsterIndex],造成严重的寻址错位!

根因 3:场景渲染层缺失 Solid Freeze 视觉呈现 (src/scene/act-scene.ts)

在 src/scene/act-scene.ts 第 5949 行与 5976 行:

const isChilled = monster.chillTicks !== undefined && monster.chillTicks > 0
// ...
...(isChilled ? { tint: [0.4, 0.65, 1.0, 1.0] as const, paletteRow: 1 } : {})

渲染管线仅检查了减速状态 chillTicks,完全没有处理 frozenTicks > 0(绝对冰冻)!

  • 处于绝对冰冻状态下的怪物不仅应当显示更高饱和度的冰蓝材质高亮(Solid Ice Freeze),其骨骼/帧动画更新器(monsterAnimators)必须在被冻结期间暂停步进,使怪物在物理上和视觉上完全定格在冰雕状态!

根因 4:爆裂动画美术资源未接入次级冰锥 (glacialspikeejecta)

在 src/game/engine.ts 中,只读取了单一的 mslData?.explosionMissile(freezeexplode),遗漏了 1.13c 原版规范的次级辐射状冰渣飞溅动画 glacialspikeejecta(public/missiles/freezeexplodeejecta.png)。


修复方案与任务规划 (Remediation Plan)

  1. 修正投射物碰撞与爆炸派生链路 (src/game/skills.ts / src/game/engine.ts):
    • 对齐 1.13c pSrvHitFunc = 13 规范:在投射物触碰怪物或撞击墙体时,于命中点正确生成 freezeexplode 与 glacialspikeejecta 爆破特效。
    • 触发原版命中打击音效 sorceress_iceblast_impact_1。
  2. 修复 AoE 溅射计算与索引寻址:
    • 统一使用怪物唯一标识或严格正确的数组索引匹配,消除 m.index 与 world.monsters 数组下标的脱节。
    • 以命中坐标为中心,按 2:1 等轴测椭圆公式判定 64px 半径内所有敌方活体单位,统一定额施加冰尖柱等级伤害与对应帧数的绝对冰冻状态(frozenTicks),自动跳过具备 cannot_be_frozen 词缀或冰系免疫(Cold Immune)的单位。
  3. 实装绝对冰冻 (Solid Freeze) 动画定格与冰雕着色:
    • 当怪物 frozenTicks > 0 时,定格当前动画帧(跳过动画步进),渲���层应用亮蓝冰冻着色(Solid Freeze Tint),解冻后恢复行动与常态材质。
  4. 全维度自动化单元测试与实机验证:
    • 完善 tests/skills/sor/skill-055-glacial-spike.test.ts,覆盖:
      • Case 1:直击命中主目标触发伤害与固化冰冻;
      • Case 2:64px 溅射范围内多个目标同步受到全额伤害与固化冰冻;
      • Case 3:撞击阻挡墙体时于障碍点正确引爆并对紧邻敌人产生 64px 爆炸伤害与冰冻;
      • Case 4:冰冷免疫怪物不受冰冻影响,受到 cannot_be_frozen 保护的怪物仅受伤害不被定身。
## 问题背景与缺陷表现 (Background & Defect Analysis) 在当前客户端实操与技能实装中,法师 18 级核心冰系技能 **冰尖柱 (Glacial Spike, Skill ID: 55)** 存在严重的命中结算与原版一致性缺陷: 1. **未触发爆炸与冰锥飞溅特效**:冰尖柱飞行投射物击中敌方单位或撞击墙体/障碍物时,未生成原版标志性的冰爆中心云(`freezeexplode` / `FreezeExplodeCenter`)与次级碎冰飞溅特效(`glacialspikeejecta` / `FreezeExplodeEjecta`),缺乏打击感。 2. **缺失群体 64px 溅射冰冻与伤害判定**:原版冰尖柱属于典型的 AoE 冰爆技能,但目前在客户端战斗链路中,仅有单体直击判定,未对以碰撞点为中心 64 像素(4 subtiles,约 2.6 码)范围内的敌群造成群体冰冷伤害与固化冰冻效果。 3. **缺少冻结视觉定身(Solid Freeze)反馈**:即使目标处于 `frozenTicks > 0`,场景渲染层(`src/scene/act-scene.ts`)仅有 `chillTicks`(减速迟缓)着色逻辑,缺失绝对冰冻(Freeze)的动画帧冻结与高饱和度冰蓝材质着色,导致玩家视觉上误以为怪物完全未被冰冻。 --- ## 暴雪 1.13c 原版权威数据与机制基准 (Blizzard 1.13c Ground Truth Invariants) 根据 Diablo II v1.13c 官方数据表(`Skills.txt`、`Missiles.txt`)与逆向汇编规范: ### 1. `Skills.txt` (Row 55: Glacial Spike) - **Skill ID**: `55` (`#055`), **Class**: `sor`, **ReqLevel**: `18` - **Missile**: `srvmissile = "glacialspike"`, `cltmissile = "glacialspike"` - **Cast Overlay**: `ice_cast_2` - **Param1 (爆炸半径)**: `4` subtiles(即 4 * 16 = 64 像素,在 2:1 等轴测视角下为 64px 椭圆判定,约 2.6 码) - **Param2 (半径等级成长)**: `0`(固定 64px,不随等级扩大) - **Param3 (基础冰冻时长)**: `50` 帧(2.00 秒) - **Param4 (冰冻每级成长)**: `3` 帧 / 级(+0.12 秒 / 级) - **Param7 (暴风雪协同)**: `3`(每投入 1 点暴风雪基础技能,冰冻时间延长 +3%) - **Param8 (伤害协同)**: `5`(每投入 1 点冰弹 / 冰风暴 / 冰封球,冰冷伤害增加 +5%) - **公式**: - 冰冻时间公式:`auralencalc = "ln34 * (100 + skill('Blizzard'.blvl) * par7) / 100"` - 伤害公式:`EDmgSymPerCalc = "(skill('Ice Bolt'.blvl) + skill('Ice Blast'.blvl) + skill('Frozen Orb'.blvl)) * par8"` - 伤害档位(HitShift = 7,基数需除以 2):1 级为 16–24,20 级无协同为 225–242.5。 ### 2. `Missiles.txt` (Row 96: glacialspike) - **Functions**: `pSrvDoFunc = 1` (直线飞行), `pSrvHitFunc = 13` (原版 MISSMODE_SrvHit13_GlacialSpike) - **Hit Params**: - Server Hit Param 1: `damage radius (0 = skill)` -> 引用技能 Param1 (4 subtiles = 64px) - Server Hit Param 2: `freeze length (0 = skill)` -> 引用技能 Param3/Param4 及协同公式 - **SubMissiles (次级投射物与命中特效)**: - `CltHitSubMissile1 = "freezingarrowexp1"` (即 `freezeexplode` / `FreezeExplodeCenter`,ID 88,AnimLen 15) - `CltHitSubMissile2 = "freezingarrowexp2"` (即 `glacialspikeejecta` / `FreezeExplodeEjecta`,ID 89,飞溅冰锥) - `HitSound = "sorceress_iceblast_impact_1"` - **Kinematics**: `Vel = 16` (400 px/s), `Range = 40` (640 px), `CollideKill = 1`, `LastCollide = 1` --- ## 缺陷根因深度剖析 (Root Cause Analysis) ### 根因 1:`src/game/skills.ts` (`tickProjectiles`) 投射物命���未分发爆炸投射物与次级投射物 在 `tickProjectiles` 中(约 4597–4618 行): ```typescript if (!projectile.pierce) { struck = true if (isBlizzardShard(projectile.missileType)) { const expType = getBlizzardExplosionMissile(projectile.missileType!) spawned.push({ ... }) } break } ``` 当投射物击中非穿透目标时,当前逻辑仅对暴风雪碎片(`isBlizzardShard`)推入了爆炸投射物,而对 `glacialspike`、`fireball` 等需要爆裂效果的投射物,**完全没有根据 `explosionMissile` / `hitSubMissile` 产生爆破投射物派生**! ### 根因 2:`src/game/engine.ts` 中怪物实例索引与循环下标混淆,导致 AoE 溅射结算脱靶 在 `engine.tick()`(约 440–505 行)中: ```typescript const targets = this.world.monsters.map((monster, index) => ({ index, x: monster.x, y: monster.y, radius: 20, alive: monster.state !== 'dead', })) // ... for (const hit of outcome.hits) { damageMonster(this.world, hit.targetIndex, hit.damage) // hit.targetIndex 为 map 下标 index // ... if (hit.aoeRadius && hit.aoeRadius > 0) { for (const m of this.world.monsters) { if (m.index === hit.targetIndex || m.state === 'dead' || m.hp <= 0) continue // ... if (Math.hypot(dx, dy) <= hit.aoeRadius) { damageMonster(this.world, m.index, hit.damage) // 💥 错误!m.index 为怪物 ID,但 damageMonster 内部直接取 world.monsters[index]! } } } } ``` 1. `hit.targetIndex` 来自 `targets`(即数组下标 `0, 1, 2, ...`),而排除自身时却使用了 `m.index === hit.targetIndex`(`m.index` 为怪物在生成时赋予的 ID,一旦有怪物死亡清理或非连续下标,就会出现自身判定混乱)。 2. 在对范围内的次要目标派发伤害时,传入了 `m.index`,而 `damageMonster(world, monsterIndex)` 是按数组下标寻址 `world.monsters[monsterIndex]`,造成严重的寻址错位! ### 根因 3:场景渲染层缺失 Solid Freeze 视觉呈现 (`src/scene/act-scene.ts`) 在 `src/scene/act-scene.ts` 第 5949 行与 5976 行: ```typescript const isChilled = monster.chillTicks !== undefined && monster.chillTicks > 0 // ... ...(isChilled ? { tint: [0.4, 0.65, 1.0, 1.0] as const, paletteRow: 1 } : {}) ``` 渲染管线**仅检查了减速状态 `chillTicks`**,完全没有处理 `frozenTicks > 0`(绝对冰冻)! - 处于绝对冰冻状态下的怪物不仅应当显示更高饱和度的冰蓝材质高亮(Solid Ice Freeze),其骨骼/帧动画更新器(`monsterAnimators`)必须在被冻结期间暂停步进,使怪物在物理上和视觉上完全定格在冰雕状态! ### 根因 4:爆裂动画美术资源未接入次级冰锥 (`glacialspikeejecta`) 在 `src/game/engine.ts` 中,只读取了单一的 `mslData?.explosionMissile`(`freezeexplode`),遗漏了 1.13c 原版规范的次级辐射状冰渣飞溅动画 `glacialspikeejecta`(`public/missiles/freezeexplodeejecta.png`)。 --- ## 修复方案与任务规划 (Remediation Plan) 1. **修正投射物碰撞与爆炸派生链路 (`src/game/skills.ts` / `src/game/engine.ts`)**: - 对齐 1.13c `pSrvHitFunc = 13` 规范:在投射物触碰怪物或撞击墙体时,于命中点正确生成 `freezeexplode` 与 `glacialspikeejecta` 爆破特效。 - 触发原版命中打击音效 `sorceress_iceblast_impact_1`。 2. **修复 AoE 溅射计算与索引寻址**: - 统一使用怪物唯一标识或严格正确的数组索引匹配,消除 `m.index` 与 `world.monsters` 数组下标的脱节。 - 以命中坐标为中心,按 2:1 等轴测椭圆公式判定 64px 半径内所有敌方活体单位,统一定额施加冰尖柱等级伤害与对应帧数的绝对冰冻状态(`frozenTicks`),自动跳过具备 `cannot_be_frozen` 词缀或冰系免疫(Cold Immune)的单位。 3. **实装绝对冰冻 (Solid Freeze) 动画定格与冰雕着色**: - 当怪物 `frozenTicks > 0` 时,定格当前动画帧(跳过动画步进),渲���层应用亮蓝冰冻着色(Solid Freeze Tint),解冻后恢复行动与常态材质。 4. **全维度自动化单元测试与实机验证**: - 完善 `tests/skills/sor/skill-055-glacial-spike.test.ts`,覆盖: - Case 1:直击命中主目标触发伤害与固化冰冻; - Case 2:64px 溅射范围内多个目标同步受到全额伤害与固化冰冻; - Case 3:撞击阻挡墙体时于障碍点正确引爆并对紧邻敌人产生 64px 爆炸伤害与冰冻; - Case 4:冰冷免疫怪物不受冰冻影响,受到 `cannot_be_frozen` 保护的怪物仅受伤害不被定身。
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: troytt/diablo2-web#430
No description provided.