feat(combat): 修复全伤害源击杀事件生命周期与单次掉落门控 (Combat Kill Event Lifecycle & Single-Drop Gate) #404
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
问题背景与现状分析
目前在客户端主场景(
src/scene/act-scene.ts与src/game/engine.ts)中,存在严重的击杀事件丢失缺陷:act-scene.ts每帧先执行mouseController.tick()(触发玩家点击普攻skillId === 0、冰霜新星44、静态力场42、新星48等技能调用damageMonster),随后再调用engine.tick()。engine.tick()内部第 413 行处理瞬发技能时同样先调用damageMonster(this.world, ...),随后在第 420 行调用tickCombat(...)。src/game/combat.ts的tickCombatMulti在首行(第 782 行)无条件执行了world.events = [],导致在tickCombatMulti之前产生的所有{ kind: "kill", ... }事件被全部静默清空!engine.tick()第 845 行遍历this.world.events结算掉落时,前置技能与普攻产生的击杀事件早已消失,导致玩家打死怪物却不触发任何掉落。原版 Diablo II v1.13c 核心机制规范(Ground Truth)
1. 全伤害源统一死亡结算 (
D2Game.dllSUnitDmg_KillMonster)tickProjectiles)、AoE 爆炸溅射、瞬发范围法术(如新星/冰霜新星/静态力场)、持续地面火墙/陨石余火、圣骑士/德鲁伊周期光环脉冲(神圣火焰/神圣冰冻/神圣冲击)、雷云风暴(Thunder Storm)或召唤物攻击——只要怪物生命值从> 0降至<= 0并进入dead状态,必须无一遗漏地触发死亡经验、任务计数与战利品掉落管线。2. 单次掉落幂等门控 (Idempotent Single-Drop Gate)
Monster)在其生命周期内从存活转为死亡状态时,战利品掉落(D2GAME_DropMonsterItems)严格只执行一次。后续尸体被救赎(Redemption)、尸爆(Corpse Explosion)或其它 AoE 再次扫过时,绝不重复触发常规死亡掉落。改造与验收标准
world.events在tickCombatMulti与engine.tick()之间的清空时序(或引入待结算击杀队列),确保mouseController.tick()、castSkill、tickProjectiles、光环脉冲及近战攻击产生的kill事件 100% 送达engine.tick()掉落结算循环。Monster状态上增加幂等掉落标记(如dropRolled?: boolean),保证每只怪死亡时严格结算 1 次掉落。npm run typecheck与npm test100% 通过。修复与验证完成总结 (Milestone #11 / Issue #404)
1. 核心修复说明
src/game/combat.ts:Monster增加dropRolled?: boolean字段,实现单怪单次掉落幂等门控。CombatEvent增加monsterIndex?: number字段,确保击杀事件精准关联到怪物实例。CombatWorld增加pendingKills?: CombatEvent[]字段,并在createWorld/createCombatWorld中正确初始化。CombatOptions增加preserveEvents?: boolean字段;tickCombatMulti仅在!options.preserveEvents时重置world.events = [],彻底修复中途擦除事件的竞争时序。buildKillEvent携带monsterIndex: monster.index。damageMonster与tickPlayer击杀怪物时,同步推入world.events与world.pendingKills,并设置monster.dropRolled = monster.dropRolled ?? false。src/game/engine.ts:engine.tick()调用tickCombat时传入preserveEvents: true。world.pendingKills无损排空并合并入world.events。if (monster?.dropRolled) continue; monster.dropRolled = true;,严格杜绝多伤害并发命中或死灵/巫师复活怪物二次掉落。src/scene/act-scene.ts:onTick帧边界前置重置事件队列,确保前置mouseController.tick()产生的近战与技能击杀事件完整保留至掉落结算。2. 自动化测试验证
新增专用测试套件
tests/combat-kill-lifecycle.test.ts,验证全部 5 类极限边界条件:monster.dropRolled === true。engine.tick中正常结算掉落。3. 质量门禁
npm run typecheck: 0 errorsnpx vitest run tests/combat-kill-lifecycle.test.ts tests/combat.test.ts tests/drop-pipeline.test.ts: 3/3 suites passed, 63/63 tests passedmain(Commitd5b6954),临时 worktree 已清理。门禁全员核准与审计 CLEAN 合入报告 (Milestone #11 / Issue #404 Passed Gate 2)
1. 验证结果汇总
2842aa21): APPROVE (tests/combat-kill-lifecycle.test.ts规范类型声明完全对齐,act-scene.ts双重清空缺陷消除,无@ts-ignore)bcb9fbc6): APPROVE (前置点击事件跨帧队列可靠送入pendingKills,dropsRolled计数与 groundItem 落地正常)692ee669): APPROVE (100并发同时致死打击单次掉落验证、50次连续前置快速施法未丢失掉落验证、50轮复活重杀 UNITFLAG_NOITEM 防重掉落实测 100% 确定性通过)83145f77): APPROVE (普攻、直接投射物、范围爆炸、瞬发AoE、元素脉冲、被动反击 6 大类共 17 种伤害源 100% 触发单次掉落)66ee5f30): CLEAN (npm run typecheck0 错误,npm run build0 错误构建成功,无作弊逻辑或测试篡改)2. 合入与提交信息
f64440c(fix(combat): resolve static typecheck errors and event double-clear race (Fixes #404))main并推至origin/main,独立 worktree.worktrees/issue-404-fix与临时分支已清理完毕,主工作区保持 100% 纯净。