From f64440c378149ad6fd4f6f5c74ef21e3ae820f8a Mon Sep 17 00:00:00 2001 From: troytt <47798984@qq.com> Date: Thu, 24 Sep 2026 07:18:35 +0000 Subject: [PATCH] fix(combat): resolve static typecheck errors and event double-clear race (Fixes #404) --- src/scene/act-scene.ts | 3 --- tests/combat-kill-lifecycle.test.ts | 33 +++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/scene/act-scene.ts b/src/scene/act-scene.ts index be33add..c982454 100644 --- a/src/scene/act-scene.ts +++ b/src/scene/act-scene.ts @@ -5627,9 +5627,6 @@ if (typeof window !== 'undefined') { const loop = new GameLoop({ tickRate: 25, onTick: () => { - // Clean tick boundary: flush previous tick events before mouseController runs - engine.world.events = [] - if (engine.world.pendingKills) engine.world.pendingKills = [] const mouseStep = mouseController.tick() const effectiveMovement = mouseStep.movement if (hudManager) { diff --git a/tests/combat-kill-lifecycle.test.ts b/tests/combat-kill-lifecycle.test.ts index caee51d..1a85857 100644 --- a/tests/combat-kill-lifecycle.test.ts +++ b/tests/combat-kill-lifecycle.test.ts @@ -4,7 +4,8 @@ import { MountedArchives } from '../src/mpq/mount.ts' import { MpqArchive } from '../src/mpq/archive.ts' import { fileSource } from '../src/mpq/file-source.ts' import { loadDropTables, type DropTables } from '../src/game/drop-pipeline.ts' -import { GameEngine, type GameEngineOptions } from '../src/game/engine.ts' +import { GameEngine, type GameEngineOptions, type WorldMapProvider } from '../src/game/engine.ts' +import type { SkillDef } from '../src/game/skills.ts' import { damageMonster, type Monster } from '../src/game/combat.ts' import { DEMO_EXPERIENCE, DEMO_NPCS, DEMO_QUESTS } from '../src/game/demo-data.ts' @@ -24,7 +25,9 @@ describe('Combat Kill Event Lifecycle & Single-Drop Gate (Issue #404)', () => { dropTables = await loadDropTables(archives) }) - const dummyTerrain = { + const dummyTerrain: WorldMapProvider = { + widthPx: 1000, + heightPx: 1000, overlap: () => 0, isMissileBlocked: () => false, } @@ -70,7 +73,6 @@ describe('Combat Kill Event Lifecycle & Single-Drop Gate (Issue #404)', () => { function createTestEngine(): GameEngine { const engineOpts: GameEngineOptions = { spawn: { x: 100, y: 100 }, - combatPlayer: { x: 100, y: 100, maxHp: 100, maxMana: 100 }, stats: [ { id: 'fallen1', @@ -94,6 +96,12 @@ describe('Combat Kill Event Lifecycle & Single-Drop Gate (Issue #404)', () => { name: 'Attack', manaCost: 0, cooldownTicks: 1, + range: 48, + projectile: false, + speed: 0, + baseMinDamage: 10, + baseMaxDamage: 20, + damagePerLevel: 2, radius: 48, }, ], @@ -116,6 +124,10 @@ describe('Combat Kill Event Lifecycle & Single-Drop Gate (Issue #404)', () => { } const engine = new GameEngine(dummyTerrain, engineOpts) + engine.world.player.maxHp = 100 + engine.world.player.hp = 100 + engine.world.player.maxMana = 100 + engine.world.player.mana = 100 engine.world.monsters = [createMonster(0, 110, 100, 20)] return engine } @@ -155,14 +167,23 @@ describe('Combat Kill Event Lifecycle & Single-Drop Gate (Issue #404)', () => { ] // Configure Nova (instant AoE skill) - engine.opts.skills.push({ + const novaDef: SkillDef = { id: 'nova', name: 'Nova', manaCost: 0, cooldownTicks: 1, + range: 80, + projectile: false, + speed: 0, + baseMinDamage: 50, + baseMaxDamage: 50, + damagePerLevel: 0, radius: 80, - damage: 50, // Lethal to 20 hp monsters - }) + } + engine.opts = { + ...engine.opts, + skills: [...engine.opts.skills, novaDef], + } engine.selectedSkill = engine.opts.skills.length - 1 const initialDropsRolled = engine.metrics.dropsRolled