poe2-bot/src-old/game/GridReader.ts
2026-02-13 01:12:11 -05:00

161 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { logger } from '../util/logger.js';
import type { OcrDaemon, GridItem, GridMatch } from './OcrDaemon.js';
import type { Region } from '../types.js';
// ── Grid type definitions ───────────────────────────────────────────────────
export interface GridLayout {
region: Region;
cols: number;
rows: number;
}
export interface CellCoord {
row: number;
col: number;
x: number;
y: number;
}
export interface ScanResult {
layout: GridLayout;
occupied: CellCoord[];
items: GridItem[];
matches?: GridMatch[];
}
// ── Calibrated grid layouts (2560×1440) ─────────────────────────────────────
export const GRID_LAYOUTS: Record<string, GridLayout> = {
/** Player inventory — always 12×5, right side (below equipment slots) */
inventory: {
region: { x: 1696, y: 788, width: 840, height: 350 },
cols: 12,
rows: 5,
},
/** Personal stash 12×12 — left side, tab not in folder */
stash12: {
region: { x: 23, y: 169, width: 840, height: 840 },
cols: 12,
rows: 12,
},
/** Personal stash 12×12 — left side, tab in folder */
stash12_folder: {
region: { x: 23, y: 216, width: 840, height: 840 },
cols: 12,
rows: 12,
},
/** Personal stash 24×24 (quad tab) — left side, tab not in folder */
stash24: {
region: { x: 23, y: 169, width: 840, height: 840 },
cols: 24,
rows: 24,
},
/** Personal stash 24×24 (quad tab) — left side, tab in folder */
stash24_folder: {
region: { x: 23, y: 216, width: 840, height: 840 },
cols: 24,
rows: 24,
},
/** Seller's public stash — always 12×12 */
seller: {
region: { x: 416, y: 299, width: 840, height: 840 },
cols: 12,
rows: 12,
},
/** NPC shop — 12×12 */
shop: {
region: { x: 23, y: 216, width: 840, height: 840 },
cols: 12,
rows: 12,
},
/** NPC vendor inventory — 12×12 */
vendor: {
region: { x: 416, y: 369, width: 840, height: 840 },
cols: 12,
rows: 12,
},
};
// Backward-compat exports
export const INVENTORY = GRID_LAYOUTS.inventory;
export const STASH_12x12 = GRID_LAYOUTS.stash12;
export const STASH_24x24 = GRID_LAYOUTS.stash24;
export const SELLER_12x12 = GRID_LAYOUTS.seller;
// ── GridReader ──────────────────────────────────────────────────────────────
export class GridReader {
constructor(private daemon: OcrDaemon) {}
/**
* Scan a named grid layout for occupied cells.
*/
async scan(layoutName: string, threshold?: number, targetRow?: number, targetCol?: number): Promise<ScanResult> {
const layout = GRID_LAYOUTS[layoutName];
if (!layout) throw new Error(`Unknown grid layout: ${layoutName}`);
const t = performance.now();
const { occupied, items, matches } = await this.getOccupiedCells(layout, threshold, targetRow, targetCol);
const ms = (performance.now() - t).toFixed(0);
logger.info(
{ layoutName, cols: layout.cols, rows: layout.rows, occupied: occupied.length, items: items.length, matches: matches?.length, ms },
'Grid scan complete',
);
return { layout, occupied, items, matches };
}
/** Get the screen-space center of a grid cell */
getCellCenter(layout: GridLayout, row: number, col: number): { x: number; y: number } {
const cellW = layout.region.width / layout.cols;
const cellH = layout.region.height / layout.rows;
return {
x: Math.round(layout.region.x + col * cellW + cellW / 2),
y: Math.round(layout.region.y + row * cellH + cellH / 2),
};
}
/** Scan the grid and return which cells are occupied and detected items */
async getOccupiedCells(layout: GridLayout, threshold?: number, targetRow?: number, targetCol?: number): Promise<{ occupied: CellCoord[]; items: GridItem[]; matches?: GridMatch[] }> {
const t = performance.now();
const result = await this.daemon.gridScan(
layout.region,
layout.cols,
layout.rows,
threshold,
targetRow,
targetCol,
);
const occupied: CellCoord[] = [];
for (let row = 0; row < result.cells.length; row++) {
for (let col = 0; col < result.cells[row].length; col++) {
if (result.cells[row][col]) {
const center = this.getCellCenter(layout, row, col);
occupied.push({ row, col, x: center.x, y: center.y });
}
}
}
const ms = (performance.now() - t).toFixed(0);
logger.info(
{ layout: `${layout.cols}x${layout.rows}`, occupied: occupied.length, items: result.items.length, matches: result.matches?.length, ms },
'Grid scan complete',
);
return { occupied, items: result.items, matches: result.matches };
}
/** Get all cell centers in the grid */
getAllCells(layout: GridLayout): CellCoord[] {
const cells: CellCoord[] = [];
for (let row = 0; row < layout.rows; row++) {
for (let col = 0; col < layout.cols; col++) {
const center = this.getCellCenter(layout, row, col);
cells.push({ row, col, x: center.x, y: center.y });
}
}
return cells;
}
}