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

157 lines
4.6 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 { 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 });
}
}