inventory types
This commit is contained in:
parent
cf5d944fd1
commit
3d7a8aafdf
9 changed files with 532 additions and 369 deletions
|
|
@ -1,8 +1,8 @@
|
|||
import { GameController } from '../game/GameController.js';
|
||||
import { ScreenReader } from '../game/ScreenReader.js';
|
||||
import { ClientLogWatcher } from '../log/ClientLogWatcher.js';
|
||||
import { TradeMonitor } from '../trade/TradeMonitor.js';
|
||||
import { sleep, randomDelay } from '../util/sleep.js';
|
||||
import { InventoryManager } from '../inventory/InventoryManager.js';
|
||||
import { sleep } from '../util/sleep.js';
|
||||
import { logger } from '../util/logger.js';
|
||||
import type { Config, TradeInfo, TradeState, Region } from '../types.js';
|
||||
import type { Page } from 'playwright';
|
||||
|
|
@ -20,21 +20,21 @@ export class TradeExecutor {
|
|||
private state: TradeState = 'IDLE';
|
||||
private gameController: GameController;
|
||||
private screenReader: ScreenReader;
|
||||
private logWatcher: ClientLogWatcher;
|
||||
private tradeMonitor: TradeMonitor;
|
||||
private inventoryManager: InventoryManager;
|
||||
private config: Config;
|
||||
|
||||
constructor(
|
||||
gameController: GameController,
|
||||
screenReader: ScreenReader,
|
||||
logWatcher: ClientLogWatcher,
|
||||
tradeMonitor: TradeMonitor,
|
||||
inventoryManager: InventoryManager,
|
||||
config: Config,
|
||||
) {
|
||||
this.gameController = gameController;
|
||||
this.screenReader = screenReader;
|
||||
this.logWatcher = logWatcher;
|
||||
this.tradeMonitor = tradeMonitor;
|
||||
this.inventoryManager = inventoryManager;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
|
@ -50,19 +50,19 @@ export class TradeExecutor {
|
|||
this.state = 'TRAVELING';
|
||||
logger.info({ searchId: trade.searchId }, 'Clicking Travel to Hideout...');
|
||||
|
||||
const travelClicked = await this.tradeMonitor.clickTravelToHideout(
|
||||
page,
|
||||
trade.itemIds[0],
|
||||
// Register listener BEFORE clicking, then click inside the callback
|
||||
const arrived = await this.inventoryManager.waitForAreaTransition(
|
||||
this.config.travelTimeoutMs,
|
||||
async () => {
|
||||
const travelClicked = await this.tradeMonitor.clickTravelToHideout(
|
||||
page,
|
||||
trade.itemIds[0],
|
||||
);
|
||||
if (!travelClicked) {
|
||||
throw new Error('Failed to click Travel to Hideout');
|
||||
}
|
||||
},
|
||||
);
|
||||
if (!travelClicked) {
|
||||
logger.error('Failed to click Travel to Hideout');
|
||||
this.state = 'FAILED';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Wait for area transition (arrival at seller's hideout)
|
||||
logger.info('Waiting for area transition...');
|
||||
const arrived = await this.waitForAreaTransition(this.config.travelTimeoutMs);
|
||||
if (!arrived) {
|
||||
logger.error('Timed out waiting for hideout arrival');
|
||||
this.state = 'FAILED';
|
||||
|
|
@ -70,6 +70,7 @@ export class TradeExecutor {
|
|||
}
|
||||
|
||||
this.state = 'IN_SELLERS_HIDEOUT';
|
||||
this.inventoryManager.setLocation(false);
|
||||
logger.info('Arrived at seller hideout');
|
||||
|
||||
// Step 3: Focus game window and click on Ange then Stash
|
||||
|
|
@ -77,7 +78,7 @@ export class TradeExecutor {
|
|||
await sleep(1500); // Wait for hideout to render
|
||||
|
||||
// Click on Ange NPC to interact
|
||||
const angePos = await this.findAndClickNameplate('Ange');
|
||||
const angePos = await this.inventoryManager.findAndClickNameplate('Ange');
|
||||
if (!angePos) {
|
||||
logger.warn('Could not find Ange nameplate, trying Stash directly');
|
||||
} else {
|
||||
|
|
@ -85,7 +86,7 @@ export class TradeExecutor {
|
|||
}
|
||||
|
||||
// Click on Stash to open it
|
||||
const stashPos = await this.findAndClickNameplate('Stash');
|
||||
const stashPos = await this.inventoryManager.findAndClickNameplate('Stash');
|
||||
if (!stashPos) {
|
||||
logger.error('Could not find Stash nameplate in seller hideout');
|
||||
this.state = 'FAILED';
|
||||
|
|
@ -115,13 +116,17 @@ export class TradeExecutor {
|
|||
logger.info('Traveling to own hideout...');
|
||||
await this.gameController.focusGame();
|
||||
await sleep(300);
|
||||
await this.gameController.goToHideout();
|
||||
|
||||
const home = await this.waitForAreaTransition(this.config.travelTimeoutMs);
|
||||
const home = await this.inventoryManager.waitForAreaTransition(
|
||||
this.config.travelTimeoutMs,
|
||||
() => this.gameController.goToHideout(),
|
||||
);
|
||||
if (!home) {
|
||||
logger.warn('Timed out going home, continuing anyway...');
|
||||
}
|
||||
|
||||
this.inventoryManager.setLocation(true);
|
||||
|
||||
// Step 7: Store items in stash
|
||||
this.state = 'IN_HIDEOUT';
|
||||
await sleep(1000);
|
||||
|
|
@ -185,67 +190,8 @@ export class TradeExecutor {
|
|||
}
|
||||
|
||||
private async storeItems(): Promise<void> {
|
||||
logger.info('Storing purchased items in stash...');
|
||||
|
||||
// Focus game and find Stash in own hideout
|
||||
await this.gameController.focusGame();
|
||||
await sleep(500);
|
||||
|
||||
const stashPos = await this.findAndClickNameplate('Stash');
|
||||
if (!stashPos) {
|
||||
logger.error('Could not find Stash nameplate in own hideout');
|
||||
return;
|
||||
}
|
||||
await sleep(1000); // Wait for stash to open
|
||||
|
||||
// Open inventory
|
||||
await this.gameController.openInventory();
|
||||
await sleep(500);
|
||||
|
||||
// TODO: Implement inventory scanning to find purchased items
|
||||
// and Ctrl+right-click each to transfer to stash
|
||||
|
||||
logger.info('Item storage complete (needs calibration)');
|
||||
}
|
||||
|
||||
private async findAndClickNameplate(
|
||||
name: string,
|
||||
maxRetries: number = 3,
|
||||
retryDelayMs: number = 1000,
|
||||
): Promise<{ x: number; y: number } | null> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
logger.info({ name, attempt, maxRetries }, 'Searching for nameplate...');
|
||||
const pos = await this.screenReader.findTextOnScreen(name);
|
||||
|
||||
if (pos) {
|
||||
logger.info({ name, x: pos.x, y: pos.y }, 'Clicking nameplate');
|
||||
await this.gameController.leftClickAt(pos.x, pos.y);
|
||||
return pos;
|
||||
}
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
logger.debug({ name, attempt }, 'Nameplate not found, retrying...');
|
||||
await sleep(retryDelayMs);
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn({ name, maxRetries }, 'Nameplate not found after all retries');
|
||||
return null;
|
||||
}
|
||||
|
||||
private waitForAreaTransition(timeoutMs: number): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const timer = setTimeout(() => {
|
||||
this.logWatcher.removeListener('area-entered', handler);
|
||||
resolve(false);
|
||||
}, timeoutMs);
|
||||
|
||||
const handler = () => {
|
||||
clearTimeout(timer);
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
this.logWatcher.once('area-entered', handler);
|
||||
});
|
||||
logger.info('Storing purchased items...');
|
||||
await this.inventoryManager.processInventory();
|
||||
logger.info('Item storage complete');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue