109 lines
4.6 KiB
TypeScript
109 lines
4.6 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { applyAction, decideEnemyAction } from '../simulation/simulation';
|
|
import { type World, type Actor, type EntityId, type CombatantActor } from '../../core/types';
|
|
import { EntityManager } from '../EntityManager';
|
|
|
|
describe('Combat Simulation', () => {
|
|
let entityManager: EntityManager;
|
|
|
|
const createTestWorld = (actors: Map<EntityId, Actor>): World => {
|
|
return {
|
|
width: 10,
|
|
height: 10,
|
|
tiles: new Array(100).fill(0),
|
|
actors,
|
|
exit: { x: 9, y: 9 }
|
|
};
|
|
};
|
|
|
|
const createTestStats = (overrides: Partial<any> = {}) => ({
|
|
maxHp: 20, hp: 20, attack: 5, defense: 2, level: 1, exp: 0, expToNextLevel: 10,
|
|
statPoints: 0, skillPoints: 0, strength: 10, dexterity: 10, intelligence: 10, passiveNodes: [],
|
|
critChance: 0, critMultiplier: 100, accuracy: 100, lifesteal: 0, evasion: 0, blockChance: 0, luck: 0,
|
|
...overrides
|
|
});
|
|
|
|
describe('applyAction - success paths', () => {
|
|
it('should deal damage when player attacks enemy', () => {
|
|
const actors = new Map<EntityId, Actor>();
|
|
actors.set(1, {
|
|
id: 1, category: "combatant", isPlayer: true, type: "player", pos: { x: 3, y: 3 }, speed: 100, stats: createTestStats()
|
|
} as any);
|
|
actors.set(2, {
|
|
id: 2, category: "combatant", isPlayer: false, type: "rat", pos: { x: 4, y: 3 }, speed: 100, stats: createTestStats({ maxHp: 10, hp: 10, attack: 3, defense: 1 })
|
|
} as any);
|
|
|
|
const world = createTestWorld(actors);
|
|
entityManager = new EntityManager(world);
|
|
const events = applyAction(world, 1, { type: "attack", targetId: 2 }, entityManager);
|
|
|
|
const enemy = world.actors.get(2) as CombatantActor;
|
|
expect(enemy.stats.hp).toBeLessThan(10);
|
|
expect(events.some(e => e.type === "attacked")).toBe(true);
|
|
});
|
|
|
|
it("should kill enemy and spawn EXP orb without ID reuse collision", () => {
|
|
const actors = new Map<EntityId, Actor>();
|
|
actors.set(1, {
|
|
id: 1, category: "combatant", isPlayer: true, type: "player", pos: { x: 3, y: 3 }, speed: 100, stats: createTestStats({ attack: 50 })
|
|
} as any);
|
|
actors.set(2, {
|
|
id: 2, category: "combatant", isPlayer: false, type: "rat", pos: { x: 4, y: 3 }, speed: 100, stats: createTestStats({ maxHp: 10, hp: 10, attack: 3, defense: 1 })
|
|
} as any);
|
|
|
|
const world = createTestWorld(actors);
|
|
entityManager = new EntityManager(world);
|
|
applyAction(world, 1, { type: "attack", targetId: 2 }, entityManager);
|
|
|
|
// Enemy (id 2) should be gone
|
|
expect(world.actors.has(2)).toBe(false);
|
|
|
|
// A new ID should be generated for the orb (should be 3)
|
|
const orb = [...world.actors.values()].find(a => a.type === "exp_orb");
|
|
expect(orb).toBeDefined();
|
|
expect(orb!.id).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("decideEnemyAction - AI Logic", () => {
|
|
it("should path around walls", () => {
|
|
const actors = new Map<EntityId, Actor>();
|
|
const player = { id: 1, category: "combatant", isPlayer: true, pos: { x: 5, y: 3 }, stats: createTestStats() } as any;
|
|
const enemy = { id: 2, category: "combatant", isPlayer: false, pos: { x: 3, y: 3 }, stats: createTestStats() } as any;
|
|
actors.set(1, player);
|
|
actors.set(2, enemy);
|
|
|
|
const world = createTestWorld(actors);
|
|
world.tiles[3 * 10 + 4] = 4; // Wall
|
|
|
|
entityManager = new EntityManager(world);
|
|
const decision = decideEnemyAction(world, enemy, player, entityManager);
|
|
|
|
expect(decision.action.type).toBe("move");
|
|
});
|
|
|
|
it("should attack if player is adjacent", () => {
|
|
const actors = new Map<EntityId, Actor>();
|
|
const player = { id: 1, category: "combatant", isPlayer: true, pos: { x: 4, y: 3 }, stats: createTestStats() } as any;
|
|
const enemy = {
|
|
id: 2,
|
|
category: "combatant",
|
|
isPlayer: false,
|
|
pos: { x: 3, y: 3 },
|
|
stats: createTestStats(),
|
|
// Set AI state to pursuing so the enemy will attack when adjacent
|
|
aiState: "pursuing",
|
|
lastKnownPlayerPos: { x: 4, y: 3 }
|
|
} as any;
|
|
actors.set(1, player);
|
|
actors.set(2, enemy);
|
|
|
|
const world = createTestWorld(actors);
|
|
entityManager = new EntityManager(world);
|
|
|
|
const decision = decideEnemyAction(world, enemy, player, entityManager);
|
|
expect(decision.action).toEqual({ type: "attack", targetId: 1 });
|
|
});
|
|
});
|
|
});
|