Skip to content

Commit 3473264

Browse files
committed
fix(xray): evaluate full multi-hex preview footprint
1 parent 8e30f56 commit 3473264

4 files changed

Lines changed: 170 additions & 2 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { describe, expect, jest, test } from '@jest/globals';
2+
3+
jest.mock('phaser-ce', () => ({}));
4+
jest.mock('../../creature', () => ({
5+
Creature: class Creature {},
6+
}));
7+
8+
import { Creature } from '../../creature';
9+
import { HexGrid } from '../../utility/hexgrid';
10+
11+
/* eslint-disable @typescript-eslint/no-explicit-any */
12+
13+
function makeCreature(hexagons: Array<{ ghostOverlap: jest.Mock }>) {
14+
const CreatureCtor = Creature as unknown as { new (): Creature };
15+
const creature = new CreatureCtor() as any;
16+
creature.hexagons = hexagons;
17+
creature.xray = jest.fn();
18+
return creature;
19+
}
20+
21+
describe('HexGrid.xray', () => {
22+
test('checks every preview footprint hex for obstructing creatures', () => {
23+
const activeHex = { ghostOverlap: jest.fn() };
24+
const activeCreature = makeCreature([activeHex]);
25+
const otherCreature = makeCreature([]);
26+
const frontPreviewHex = { creature: undefined, ghostOverlap: jest.fn() };
27+
const backPreviewHex = { creature: undefined, ghostOverlap: jest.fn() };
28+
29+
const grid = Object.create(HexGrid.prototype) as any;
30+
grid.game = {
31+
creatures: [activeCreature, otherCreature],
32+
activeCreature,
33+
};
34+
35+
grid.xray(frontPreviewHex as never, [frontPreviewHex as never, backPreviewHex as never]);
36+
37+
expect(frontPreviewHex.ghostOverlap).toHaveBeenCalledTimes(1);
38+
expect(backPreviewHex.ghostOverlap).toHaveBeenCalledTimes(1);
39+
expect(activeHex.ghostOverlap).toHaveBeenCalledWith(activeCreature);
40+
expect(activeCreature.xray).toHaveBeenLastCalledWith(false);
41+
});
42+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, expect, jest, test } from '@jest/globals';
2+
import { getQueryFootprintHexes } from '../../utility/query_footprint';
3+
4+
type TestHex = {
5+
x: number;
6+
y: number;
7+
isWalkable: jest.Mock;
8+
};
9+
10+
function buildGrid(width: number, height = 1) {
11+
const hexes: TestHex[][] = [];
12+
13+
for (let y = 0; y < height; y++) {
14+
const row: TestHex[] = [];
15+
for (let x = 0; x < width; x++) {
16+
row.push({
17+
x,
18+
y,
19+
isWalkable: jest.fn((size: number) => x - size + 1 >= 0),
20+
});
21+
}
22+
hexes.push(row);
23+
}
24+
25+
return { hexes };
26+
}
27+
28+
describe('getQueryFootprintHexes', () => {
29+
test('returns every occupied hex for a non-flipped multi-hex preview', () => {
30+
const grid = buildGrid(10);
31+
const footprint = getQueryFootprintHexes(
32+
grid as never,
33+
grid.hexes[0][6] as never,
34+
3,
35+
false,
36+
12,
37+
);
38+
39+
expect(footprint.map(({ x, y }) => [x, y])).toEqual([
40+
[6, 0],
41+
[5, 0],
42+
[4, 0],
43+
]);
44+
expect(grid.hexes[0][6].isWalkable).toHaveBeenCalledWith(3, 12);
45+
});
46+
47+
test('offsets the footprint when the active player is flipped', () => {
48+
const grid = buildGrid(10);
49+
const footprint = getQueryFootprintHexes(grid as never, grid.hexes[0][6] as never, 3, true, 12);
50+
51+
expect(footprint.map(({ x, y }) => [x, y])).toEqual([
52+
[8, 0],
53+
[7, 0],
54+
[6, 0],
55+
]);
56+
expect(grid.hexes[0][8].isWalkable).toHaveBeenCalledWith(3, 12);
57+
});
58+
});

src/utility/hexgrid.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { HEX_WIDTH_PX } from './const';
1212
import { Point } from './pointfacade';
1313
import { AugmentedMatrix } from './matrices';
1414
import { PierceThroughBehavior } from '../ability';
15+
import { getQueryFootprintHexes } from './query_footprint';
1516

1617
interface GridDefinition {
1718
numRows: number;
@@ -132,6 +133,7 @@ export class HexGrid {
132133
* Last hex passed to xray(). Used to reapply the effect on tab focus.
133134
*/
134135
lastXrayHex: Hex | null = null;
136+
lastXrayHexes: Hex[] | null = null;
135137

136138
/**
137139
* The hex the physical mouse pointer is currently over, updated before any
@@ -1326,7 +1328,11 @@ export class HexGrid {
13261328
const { y } = hex;
13271329

13281330
// Xray
1329-
this.xray(hex);
1331+
const xrayHexes =
1332+
hex.reachable && !(hex.creature instanceof Creature)
1333+
? getQueryFootprintHexes(this, hex, o.size, o.flipped, o.id)
1334+
: undefined;
1335+
this.xray(hex, xrayHexes);
13301336

13311337
// Clear display and overlay
13321338
game.UI.xrayQueue(-1);
@@ -1467,8 +1473,18 @@ export class HexGrid {
14671473
*
14681474
* If hex contain creature call ghostOverlap for each creature hexes
14691475
*/
1470-
xray(hex: Hex) {
1476+
xray(hex: Hex, referenceHexes?: Hex[]) {
1477+
const shouldReuseLastFootprint =
1478+
!referenceHexes && this.lastXrayHex === hex && this.lastXrayHexes?.length;
14711479
this.lastXrayHex = hex;
1480+
if (referenceHexes?.length) {
1481+
this.lastXrayHexes = referenceHexes;
1482+
} else if (shouldReuseLastFootprint) {
1483+
referenceHexes = this.lastXrayHexes;
1484+
} else {
1485+
this.lastXrayHexes = null;
1486+
}
1487+
14721488
// Clear previous ghost
14731489
this.game.creatures.forEach((creature) => {
14741490
if (creature instanceof Creature) {
@@ -1491,6 +1507,8 @@ export class HexGrid {
14911507
item.ghostOverlap(hex.creature as Creature);
14921508
});
14931509
}
1510+
} else if (referenceHexes?.length) {
1511+
referenceHexes.forEach((item) => item.ghostOverlap());
14941512
} else {
14951513
hex.ghostOverlap();
14961514
}
@@ -1969,6 +1987,7 @@ export class HexGrid {
19691987
*/
19701988
clearAllXray() {
19711989
this.lastXrayHex = null;
1990+
this.lastXrayHexes = null;
19721991
this.game.creatures.forEach((c) => {
19731992
if (c instanceof Creature) c.xray(false);
19741993
});

src/utility/query_footprint.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { Hex } from './hex';
2+
3+
type HexGridLike = {
4+
hexes: Array<Array<Hex | undefined>>;
5+
};
6+
7+
export function getQueryFootprintHexes(
8+
grid: HexGridLike,
9+
hex: Hex,
10+
size = 1,
11+
flipped = false,
12+
id = 0,
13+
): Hex[] {
14+
if (!hex || size < 1) {
15+
return [];
16+
}
17+
18+
const row = grid.hexes[hex.y];
19+
if (!row) {
20+
return [];
21+
}
22+
23+
let x = hex.x;
24+
const offset = flipped ? size - 1 : 0;
25+
const mult = flipped ? 1 : -1;
26+
27+
for (let i = 0; i < size; i++) {
28+
const candidateX = x + offset - i * mult;
29+
const candidate = row[candidateX];
30+
if (!candidate) {
31+
continue;
32+
}
33+
34+
if (candidate.isWalkable(size, id)) {
35+
x += offset - i * mult;
36+
break;
37+
}
38+
}
39+
40+
const footprint: Hex[] = [];
41+
for (let i = 0; i < size; i++) {
42+
const occupiedHex = row[x - i];
43+
if (occupiedHex) {
44+
footprint.push(occupiedHex);
45+
}
46+
}
47+
48+
return footprint;
49+
}

0 commit comments

Comments
 (0)