Allow melee attacking diagonally as well

This commit is contained in:
Peter Stockings
2026-01-06 10:53:13 +11:00
parent 0263495d0b
commit a9779348e9
3 changed files with 149 additions and 22 deletions

View File

@@ -377,4 +377,82 @@ describe('Combat Simulation', () => {
expect(events.some(e => e.type === "leveled-up")).toBe(true);
});
});
describe("Diagonal Mechanics", () => {
it("should allow enemy to attack player diagonally", () => {
const actors = new Map<EntityId, Actor>();
// Enemy at 4,4. Player at 5,5 (diagonal)
const enemy = {
id: 1,
category: "combatant",
isPlayer: false,
pos: { x: 4, y: 4 },
stats: createTestStats(),
aiState: "pursuing", // Skip alert phase
energy: 0
} as any;
const player = {
id: 2,
category: "combatant",
isPlayer: true,
pos: { x: 5, y: 5 },
stats: createTestStats(),
energy: 0
} as any;
actors.set(1, enemy);
actors.set(2, player);
const world = createTestWorld(actors);
// Enemy should decide to attack
const decision = decideEnemyAction(world, enemy, player, new EntityManager(world));
expect(decision.action.type).toBe("attack");
if (decision.action.type === "attack") {
expect(decision.action.targetId).toBe(player.id);
}
});
it("should allow player to attack enemy diagonally via applyAction", () => {
const actors = new Map<EntityId, Actor>();
const player = { id: 1, category: "combatant", isPlayer: true, pos: { x: 4, y: 4 }, stats: createTestStats(), energy: 0 } as any;
const enemy = { id: 2, category: "combatant", isPlayer: false, pos: { x: 5, y: 5 }, stats: createTestStats(), energy: 0 } as any;
actors.set(1, player);
actors.set(2, enemy);
const world = createTestWorld(actors);
const action: any = { type: "attack", targetId: 2 };
const events = applyAction(world, 1, action, new EntityManager(world));
const attackEvent = events.find(e => e.type === "attacked");
expect(attackEvent).toBeDefined();
expect(attackEvent?.targetId).toBe(2);
});
it("should NOT generate diagonal move for enemy", () => {
const actors = new Map<EntityId, Actor>();
// Enemy at 4,4. Player at 4,6. Dist 2.
const enemy = {
id: 1,
category: "combatant",
isPlayer: false,
pos: { x: 4, y: 4 },
stats: createTestStats(),
aiState: "pursuing",
energy: 0
} as any;
const player = { id: 2, category: "combatant", isPlayer: true, pos: { x: 4, y: 6 }, stats: createTestStats(), energy: 0 } as any;
actors.set(1, enemy);
actors.set(2, player);
const world = createTestWorld(actors);
const decision = decideEnemyAction(world, enemy, player, new EntityManager(world));
if (decision.action.type === "move") {
const { dx, dy } = decision.action;
// Should be (0, 1) or cardinal, sum of abs should be 1
expect(Math.abs(dx) + Math.abs(dy)).toBe(1);
}
});
});
});

View File

@@ -309,7 +309,6 @@ export function decideEnemyAction(w: World, enemy: CombatantActor, player: Comba
const canSee = canEnemySeePlayer(w, enemy, player);
const dx = player.pos.x - enemy.pos.x;
const dy = player.pos.y - enemy.pos.y;
const dist = Math.abs(dx) + Math.abs(dy);
// State transitions
let justAlerted = false;
@@ -369,8 +368,9 @@ export function decideEnemyAction(w: World, enemy: CombatantActor, player: Comba
const targetDx = targetPos.x - enemy.pos.x;
const targetDy = targetPos.y - enemy.pos.y;
// If adjacent to player, attack
if (dist === 1 && canSee) {
// If adjacent or diagonal to player, attack
const chebyshevDist = Math.max(Math.abs(dx), Math.abs(dy));
if (chebyshevDist === 1 && canSee) {
return { action: { type: "attack", targetId: player.id }, justAlerted };
}