switched to new way :)
This commit is contained in:
parent
b03a2a25f1
commit
f22d182c8f
30 changed files with 0 additions and 0 deletions
|
|
@ -1,207 +0,0 @@
|
|||
import { GameController } from '../game/GameController.js';
|
||||
import { ScreenReader } from '../game/ScreenReader.js';
|
||||
import { TradeMonitor } from '../trade/TradeMonitor.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';
|
||||
|
||||
// Default screen regions for 1920x1080 - these need calibration
|
||||
const DEFAULT_REGIONS = {
|
||||
stashArea: { x: 20, y: 140, width: 630, height: 750 },
|
||||
priceWarningDialog: { x: 600, y: 350, width: 700, height: 300 },
|
||||
priceWarningNoButton: { x: 820, y: 560, width: 120, height: 40 },
|
||||
inventoryArea: { x: 1260, y: 580, width: 630, height: 280 },
|
||||
stashTabArea: { x: 20, y: 100, width: 630, height: 40 },
|
||||
};
|
||||
|
||||
export class TradeExecutor {
|
||||
private state: TradeState = 'IDLE';
|
||||
private gameController: GameController;
|
||||
private screenReader: ScreenReader;
|
||||
private tradeMonitor: TradeMonitor;
|
||||
private inventoryManager: InventoryManager;
|
||||
private config: Config;
|
||||
private _onStateChange?: (state: string) => void;
|
||||
|
||||
constructor(
|
||||
gameController: GameController,
|
||||
screenReader: ScreenReader,
|
||||
tradeMonitor: TradeMonitor,
|
||||
inventoryManager: InventoryManager,
|
||||
config: Config,
|
||||
) {
|
||||
this.gameController = gameController;
|
||||
this.screenReader = screenReader;
|
||||
this.tradeMonitor = tradeMonitor;
|
||||
this.inventoryManager = inventoryManager;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
set onStateChange(cb: (state: string) => void) {
|
||||
this._onStateChange = cb;
|
||||
}
|
||||
|
||||
getState(): TradeState {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
private setState(s: TradeState): void {
|
||||
this.state = s;
|
||||
this._onStateChange?.(s);
|
||||
}
|
||||
|
||||
async executeTrade(trade: TradeInfo): Promise<boolean> {
|
||||
const page = trade.page as Page;
|
||||
|
||||
try {
|
||||
// Step 1: Click "Travel to Hideout" on the trade website
|
||||
this.setState('TRAVELING');
|
||||
logger.info({ searchId: trade.searchId }, 'Clicking Travel to Hideout...');
|
||||
|
||||
// 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 (!arrived) {
|
||||
logger.error('Timed out waiting for hideout arrival');
|
||||
this.setState('FAILED');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState('IN_SELLERS_HIDEOUT');
|
||||
this.inventoryManager.setLocation(false);
|
||||
logger.info('Arrived at seller hideout');
|
||||
|
||||
// Step 3: Focus game window and click on Ange then Stash
|
||||
await this.gameController.focusGame();
|
||||
await sleep(1500); // Wait for hideout to render
|
||||
|
||||
// Click on Ange NPC to interact
|
||||
const angePos = await this.inventoryManager.findAndClickNameplate('Ange');
|
||||
if (!angePos) {
|
||||
logger.warn('Could not find Ange nameplate, trying Stash directly');
|
||||
} else {
|
||||
await sleep(1000); // Wait for NPC interaction
|
||||
}
|
||||
|
||||
// Click on Stash to open it
|
||||
const stashPos = await this.inventoryManager.findAndClickNameplate('Stash');
|
||||
if (!stashPos) {
|
||||
logger.error('Could not find Stash nameplate in seller hideout');
|
||||
this.setState('FAILED');
|
||||
return false;
|
||||
}
|
||||
await sleep(1000); // Wait for stash to open
|
||||
|
||||
// Step 4: Scan stash and buy items
|
||||
this.setState('SCANNING_STASH');
|
||||
logger.info('Scanning stash for items...');
|
||||
|
||||
await this.scanAndBuyItems();
|
||||
|
||||
// Step 5: Wait for more items
|
||||
this.setState('WAITING_FOR_MORE');
|
||||
logger.info(
|
||||
{ waitMs: this.config.waitForMoreItemsMs },
|
||||
'Waiting for seller to add more items...',
|
||||
);
|
||||
await sleep(this.config.waitForMoreItemsMs);
|
||||
|
||||
// Do one more scan after waiting
|
||||
await this.scanAndBuyItems();
|
||||
|
||||
// Step 6: Go back to own hideout
|
||||
this.setState('GOING_HOME');
|
||||
logger.info('Traveling to own hideout...');
|
||||
await this.gameController.focusGame();
|
||||
await sleep(300);
|
||||
|
||||
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.setState('IN_HIDEOUT');
|
||||
await sleep(1000);
|
||||
await this.storeItems();
|
||||
|
||||
this.setState('IDLE');
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Trade execution failed');
|
||||
this.setState('FAILED');
|
||||
|
||||
// Try to recover by going home
|
||||
try {
|
||||
await this.gameController.focusGame();
|
||||
await this.gameController.pressEscape(); // Close any open dialogs
|
||||
await sleep(500);
|
||||
await this.gameController.goToHideout();
|
||||
} catch {
|
||||
// Best-effort recovery
|
||||
}
|
||||
|
||||
this.setState('IDLE');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async scanAndBuyItems(): Promise<void> {
|
||||
// Take a screenshot of the stash area
|
||||
const stashText = await this.screenReader.readRegionText(DEFAULT_REGIONS.stashArea);
|
||||
logger.info({ stashText: stashText.substring(0, 200) }, 'Stash OCR result');
|
||||
|
||||
// For now, we'll use a simple grid-based approach to click items
|
||||
// The exact positions depend on the stash layout and resolution
|
||||
// This needs calibration with real game screenshots
|
||||
//
|
||||
// TODO: Implement item matching logic based on OCR text
|
||||
// For now, we'll Ctrl+right-click at known grid positions
|
||||
|
||||
this.setState('BUYING');
|
||||
|
||||
// Check for price warning dialog after each buy
|
||||
await this.checkPriceWarning();
|
||||
}
|
||||
|
||||
private async checkPriceWarning(): Promise<void> {
|
||||
// Check if a price warning dialog appeared
|
||||
const hasWarning = await this.screenReader.checkForText(
|
||||
DEFAULT_REGIONS.priceWarningDialog,
|
||||
'price',
|
||||
);
|
||||
|
||||
if (hasWarning) {
|
||||
logger.warn('Price mismatch warning detected! Clicking No.');
|
||||
// Click the "No" button
|
||||
await this.gameController.leftClickAt(
|
||||
DEFAULT_REGIONS.priceWarningNoButton.x + DEFAULT_REGIONS.priceWarningNoButton.width / 2,
|
||||
DEFAULT_REGIONS.priceWarningNoButton.y + DEFAULT_REGIONS.priceWarningNoButton.height / 2,
|
||||
);
|
||||
await sleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
private async storeItems(): Promise<void> {
|
||||
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