Files
rogue/src/engine/gameplay/__tests__/CombatLogic.test.ts

130 lines
4.4 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest';
import { traceProjectile } from '../CombatLogic';
import type { World, EntityId } from '../../../core/types';
import { EntityAccessor } from '../../EntityAccessor';
import { TileType } from '../../../core/terrain';
import { ECSWorld } from '../../ecs/World';
describe('CombatLogic', () => {
// Mock World
let mockWorld: World;
let ecsWorld: ECSWorld;
let accessor: EntityAccessor;
// Helper to set wall
const setWall = (x: number, y: number) => {
mockWorld.tiles[y * mockWorld.width + x] = TileType.WALL;
};
beforeEach(() => {
mockWorld = {
width: 10,
height: 10,
tiles: new Array(100).fill(TileType.EMPTY),
exit: { x: 9, y: 9 },
trackPath: []
};
ecsWorld = new ECSWorld();
// Shooter ID 1
accessor = new EntityAccessor(mockWorld, 1 as EntityId, ecsWorld);
});
function syncActor(actor: any) {
ecsWorld.addComponent(actor.id as EntityId, "position", actor.pos);
if (actor.category === 'combatant') {
ecsWorld.addComponent(actor.id as EntityId, "actorType", { type: actor.type });
ecsWorld.addComponent(actor.id as EntityId, "stats", { hp: 10 } as any);
if (actor.isPlayer) ecsWorld.addComponent(actor.id as EntityId, "player", {});
} else if (actor.category === 'item_drop') {
ecsWorld.addComponent(actor.id as EntityId, "groundItem", { item: actor.item || {} });
}
}
describe('traceProjectile', () => {
it('should travel full path if no obstacles', () => {
const start = { x: 0, y: 0 };
const end = { x: 5, y: 0 };
const result = traceProjectile(mockWorld, start, end, accessor);
expect(result.blockedPos).toEqual(end);
expect(result.hitActorId).toBeUndefined();
expect(result.path).toHaveLength(6);
});
it('should stop at wall', () => {
const start = { x: 0, y: 0 };
const end = { x: 5, y: 0 };
setWall(3, 0); // Wall at (3,0)
const result = traceProjectile(mockWorld, start, end, accessor);
expect(result.blockedPos).toEqual({ x: 2, y: 0 });
expect(result.hitActorId).toBeUndefined();
});
it('should stop at enemy', () => {
const start = { x: 0, y: 0 };
const end = { x: 5, y: 0 };
// Place enemy at (3,0)
const enemyId = 2 as EntityId;
const enemy = {
id: enemyId,
type: 'rat',
category: 'combatant',
pos: { x: 3, y: 0 },
isPlayer: false
};
syncActor(enemy);
const result = traceProjectile(mockWorld, start, end, accessor, 1 as EntityId); // Shooter 1
expect(result.blockedPos).toEqual({ x: 3, y: 0 });
expect(result.hitActorId).toBe(enemyId);
});
it('should ignore shooter position', () => {
const start = { x: 0, y: 0 };
const end = { x: 5, y: 0 };
// Shooter at start
const shooter = {
id: 1 as EntityId,
type: 'player',
category: 'combatant',
pos: { x: 0, y: 0 },
isPlayer: true
};
syncActor(shooter);
const result = traceProjectile(mockWorld, start, end, accessor, 1 as EntityId);
// Should not hit self
expect(result.hitActorId).toBeUndefined();
expect(result.blockedPos).toEqual(end);
});
it('should ignore non-combatant actors (e.g. items)', () => {
const start = { x: 0, y: 0 };
const end = { x: 5, y: 0 };
// Item at (3,0)
const item = {
id: 99 as EntityId,
category: 'item_drop',
pos: { x: 3, y: 0 },
item: { name: 'Test Item' }
};
syncActor(item);
const result = traceProjectile(mockWorld, start, end, accessor);
// Should pass through item
expect(result.blockedPos).toEqual(end);
expect(result.hitActorId).toBeUndefined();
});
});
});