import { logger } from '../util/logger.js'; import type { PostAction } from '../types.js'; const ROWS = 5; const COLS = 12; export interface PlacedItem { row: number; col: number; w: number; h: number; postAction: PostAction; } export class InventoryTracker { private grid: boolean[][]; private items: PlacedItem[] = []; constructor() { this.grid = Array.from({ length: ROWS }, () => Array(COLS).fill(false)); } /** Initialize from a grid scan result (occupied cells + detected items). */ initFromScan( cells: boolean[][], items: { row: number; col: number; w: number; h: number }[], defaultAction: PostAction = 'stash', ): void { // Reset for (let r = 0; r < ROWS; r++) { this.grid[r].fill(false); } this.items = []; // Mark occupied cells from scan for (let r = 0; r < Math.min(cells.length, ROWS); r++) { for (let c = 0; c < Math.min(cells[r].length, COLS); c++) { this.grid[r][c] = cells[r][c]; } } // Record detected items, filtering out impossibly large ones (max POE2 item = 2×4) for (const item of items) { if (item.w > 2 || item.h > 4) { logger.warn({ row: item.row, col: item.col, w: item.w, h: item.h }, 'Ignoring oversized item (false positive)'); continue; } this.items.push({ row: item.row, col: item.col, w: item.w, h: item.h, postAction: defaultAction }); } logger.info({ occupied: ROWS * COLS - this.freeCells, items: this.items.length, free: this.freeCells }, 'Inventory initialized from scan'); } /** Try to place an item of size w×h. Column-first to match game's left-priority placement. */ tryPlace(w: number, h: number, postAction: PostAction = 'stash'): { row: number; col: number } | null { for (let col = 0; col <= COLS - w; col++) { for (let row = 0; row <= ROWS - h; row++) { if (this.fits(row, col, w, h)) { this.place(row, col, w, h, postAction); logger.info({ row, col, w, h, postAction, free: this.freeCells }, 'Item placed in inventory'); return { row, col }; } } } return null; } /** Check if an item of size w×h can fit anywhere. */ canFit(w: number, h: number): boolean { for (let col = 0; col <= COLS - w; col++) { for (let row = 0; row <= ROWS - h; row++) { if (this.fits(row, col, w, h)) return true; } } return false; } /** Get all placed items. */ getItems(): PlacedItem[] { return [...this.items]; } /** Get items with a specific postAction. */ getItemsByAction(action: PostAction): PlacedItem[] { return this.items.filter(i => i.postAction === action); } /** Check if any items have a specific postAction. */ hasItemsWithAction(action: PostAction): boolean { return this.items.some(i => i.postAction === action); } /** Remove a specific item from tracking and unmark its grid cells. */ removeItem(item: PlacedItem): void { const idx = this.items.indexOf(item); if (idx === -1) return; // Unmark grid cells for (let r = item.row; r < item.row + item.h; r++) { for (let c = item.col; c < item.col + item.w; c++) { this.grid[r][c] = false; } } this.items.splice(idx, 1); } /** Remove all items with a specific postAction. */ removeItemsByAction(action: PostAction): void { const toRemove = this.items.filter(i => i.postAction === action); for (const item of toRemove) { this.removeItem(item); } logger.info({ action, removed: toRemove.length, remaining: this.items.length }, 'Removed items by action'); } /** Get a copy of the occupancy grid. */ getGrid(): boolean[][] { return this.grid.map(row => [...row]); } /** Clear entire grid. */ clear(): void { for (let r = 0; r < ROWS; r++) { this.grid[r].fill(false); } this.items = []; logger.info('Inventory cleared'); } /** Get remaining free cells count. */ get freeCells(): number { let count = 0; for (let r = 0; r < ROWS; r++) { for (let c = 0; c < COLS; c++) { if (!this.grid[r][c]) count++; } } return count; } private fits(row: number, col: number, w: number, h: number): boolean { for (let r = row; r < row + h; r++) { for (let c = col; c < col + w; c++) { if (this.grid[r][c]) return false; } } return true; } private place(row: number, col: number, w: number, h: number, postAction: PostAction): void { for (let r = row; r < row + h; r++) { for (let c = col; c < col + w; c++) { this.grid[r][c] = true; } } this.items.push({ row, col, w, h, postAction }); } }