refactoring
This commit is contained in:
parent
696fd07e86
commit
50d32abd49
20 changed files with 334 additions and 225 deletions
|
|
@ -28,29 +28,37 @@ public class BotOrchestrator : IAsyncDisposable
|
||||||
private readonly long _startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
private readonly long _startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
private bool _started;
|
private bool _started;
|
||||||
|
|
||||||
public LinkManager Links { get; }
|
|
||||||
public ConfigStore Store { get; }
|
public ConfigStore Store { get; }
|
||||||
public AppConfig Config { get; }
|
public SavedSettings Config => Store.Settings;
|
||||||
|
public LinkManager Links { get; }
|
||||||
public GameController Game { get; private set; } = null!;
|
public IGameController Game { get; }
|
||||||
public ScreenReader Screen { get; private set; } = null!;
|
public IScreenReader Screen { get; }
|
||||||
public ClientLogWatcher LogWatcher { get; private set; } = null!;
|
public IClientLogWatcher LogWatcher { get; }
|
||||||
public TradeMonitor TradeMonitor { get; private set; } = null!;
|
public ITradeMonitor TradeMonitor { get; }
|
||||||
public InventoryManager Inventory { get; private set; } = null!;
|
public IInventoryManager Inventory { get; }
|
||||||
public TradeExecutor TradeExecutor { get; private set; } = null!;
|
public TradeExecutor TradeExecutor { get; }
|
||||||
public TradeQueue TradeQueue { get; private set; } = null!;
|
public TradeQueue TradeQueue { get; }
|
||||||
private readonly Dictionary<string, ScrapExecutor> _scrapExecutors = new();
|
private readonly Dictionary<string, ScrapExecutor> _scrapExecutors = new();
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
public event Action? StatusUpdated;
|
public event Action? StatusUpdated;
|
||||||
public event Action<string, string>? LogMessage; // level, message
|
public event Action<string, string>? LogMessage; // level, message
|
||||||
|
|
||||||
public BotOrchestrator(ConfigStore store, AppConfig config)
|
public BotOrchestrator(ConfigStore store, IGameController game, IScreenReader screen,
|
||||||
|
IClientLogWatcher logWatcher, ITradeMonitor tradeMonitor,
|
||||||
|
IInventoryManager inventory, TradeExecutor tradeExecutor,
|
||||||
|
TradeQueue tradeQueue, LinkManager links)
|
||||||
{
|
{
|
||||||
Store = store;
|
Store = store;
|
||||||
Config = config;
|
Game = game;
|
||||||
|
Screen = screen;
|
||||||
|
LogWatcher = logWatcher;
|
||||||
|
TradeMonitor = tradeMonitor;
|
||||||
|
Inventory = inventory;
|
||||||
|
TradeExecutor = tradeExecutor;
|
||||||
|
TradeQueue = tradeQueue;
|
||||||
|
Links = links;
|
||||||
_paused = store.Settings.Paused;
|
_paused = store.Settings.Paused;
|
||||||
Links = new LinkManager(store);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsReady => _started;
|
public bool IsReady => _started;
|
||||||
|
|
@ -139,18 +147,12 @@ public class BotOrchestrator : IAsyncDisposable
|
||||||
|
|
||||||
public async Task Start(IReadOnlyList<string> cliUrls)
|
public async Task Start(IReadOnlyList<string> cliUrls)
|
||||||
{
|
{
|
||||||
Screen = new ScreenReader();
|
|
||||||
Game = new GameController(Config);
|
|
||||||
LogWatcher = new ClientLogWatcher(Config.Poe2LogPath);
|
|
||||||
LogWatcher.Start();
|
LogWatcher.Start();
|
||||||
Emit("info", "Watching Client.txt for game events");
|
Emit("info", "Watching Client.txt for game events");
|
||||||
|
|
||||||
TradeMonitor = new TradeMonitor(Config);
|
|
||||||
await TradeMonitor.Start();
|
await TradeMonitor.Start();
|
||||||
Emit("info", "Browser launched");
|
Emit("info", "Browser launched");
|
||||||
|
|
||||||
Inventory = new InventoryManager(Game, Screen, LogWatcher, Config);
|
|
||||||
|
|
||||||
// Warmup OCR daemon
|
// Warmup OCR daemon
|
||||||
var ocrWarmup = Screen.Warmup().ContinueWith(t =>
|
var ocrWarmup = Screen.Warmup().ContinueWith(t =>
|
||||||
{
|
{
|
||||||
|
|
@ -183,10 +185,8 @@ public class BotOrchestrator : IAsyncDisposable
|
||||||
await Inventory.ClearToStash();
|
await Inventory.ClearToStash();
|
||||||
Emit("info", "Inventory cleared");
|
Emit("info", "Inventory cleared");
|
||||||
|
|
||||||
// Create executors
|
// Wire executor events
|
||||||
TradeExecutor = new TradeExecutor(Game, Screen, TradeMonitor, Inventory, Config);
|
|
||||||
TradeExecutor.StateChanged += _ => UpdateExecutorState();
|
TradeExecutor.StateChanged += _ => UpdateExecutorState();
|
||||||
TradeQueue = new TradeQueue(TradeExecutor, Config);
|
|
||||||
TradeQueue.TradeCompleted += () => { _tradesCompleted++; StatusUpdated?.Invoke(); };
|
TradeQueue.TradeCompleted += () => { _tradesCompleted++; StatusUpdated?.Invoke(); };
|
||||||
TradeQueue.TradeFailed += () => { _tradesFailed++; StatusUpdated?.Invoke(); };
|
TradeQueue.TradeFailed += () => { _tradesFailed++; StatusUpdated?.Invoke(); };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,18 @@ public class ScrapExecutor
|
||||||
private bool _stopped;
|
private bool _stopped;
|
||||||
private IPage? _activePage;
|
private IPage? _activePage;
|
||||||
private PostAction _postAction = PostAction.Salvage;
|
private PostAction _postAction = PostAction.Salvage;
|
||||||
private readonly GameController _game;
|
private readonly IGameController _game;
|
||||||
private readonly ScreenReader _screen;
|
private readonly IScreenReader _screen;
|
||||||
private readonly TradeMonitor _tradeMonitor;
|
private readonly ITradeMonitor _tradeMonitor;
|
||||||
private readonly InventoryManager _inventory;
|
private readonly IInventoryManager _inventory;
|
||||||
private readonly AppConfig _config;
|
private readonly SavedSettings _config;
|
||||||
|
|
||||||
public event Action<ScrapState>? StateChanged;
|
public event Action<ScrapState>? StateChanged;
|
||||||
public event Action? ItemBought;
|
public event Action? ItemBought;
|
||||||
public event Action? ItemFailed;
|
public event Action? ItemFailed;
|
||||||
|
|
||||||
public ScrapExecutor(GameController game, ScreenReader screen, TradeMonitor tradeMonitor,
|
public ScrapExecutor(IGameController game, IScreenReader screen, ITradeMonitor tradeMonitor,
|
||||||
InventoryManager inventory, AppConfig config)
|
IInventoryManager inventory, SavedSettings config)
|
||||||
{
|
{
|
||||||
_game = game;
|
_game = game;
|
||||||
_screen = screen;
|
_screen = screen;
|
||||||
|
|
@ -109,7 +109,7 @@ public class ScrapExecutor
|
||||||
if (items.Count == 0)
|
if (items.Count == 0)
|
||||||
{
|
{
|
||||||
Log.Information("No items after refresh, waiting...");
|
Log.Information("No items after refresh, waiting...");
|
||||||
await Helpers.Sleep(5000);
|
await Helpers.Sleep(Delays.EmptyRefreshWait);
|
||||||
if (_stopped) break;
|
if (_stopped) break;
|
||||||
items = await RefreshPage(page);
|
items = await RefreshPage(page);
|
||||||
}
|
}
|
||||||
|
|
@ -124,34 +124,8 @@ public class ScrapExecutor
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var alreadyAtSeller = !_inventory.IsAtOwnHideout
|
if (!await TravelToSellerIfNeeded(page, item))
|
||||||
&& !string.IsNullOrEmpty(item.Account)
|
|
||||||
&& item.Account == _inventory.SellerAccount;
|
|
||||||
|
|
||||||
if (alreadyAtSeller)
|
|
||||||
{
|
|
||||||
Log.Information("Already at seller hideout, skipping travel");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SetState(ScrapState.Traveling);
|
|
||||||
var arrived = await _inventory.WaitForAreaTransition(
|
|
||||||
_config.TravelTimeoutMs,
|
|
||||||
async () =>
|
|
||||||
{
|
|
||||||
if (!await _tradeMonitor.ClickTravelToHideout(page, item.Id))
|
|
||||||
throw new Exception("Failed to click Travel to Hideout");
|
|
||||||
});
|
|
||||||
if (!arrived)
|
|
||||||
{
|
|
||||||
Log.Error("Timed out waiting for hideout arrival: {ItemId}", item.Id);
|
|
||||||
SetState(ScrapState.Failed);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
_inventory.SetLocation(false, item.Account);
|
|
||||||
await _game.FocusGame();
|
|
||||||
await Helpers.Sleep(1500);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetState(ScrapState.Buying);
|
SetState(ScrapState.Buying);
|
||||||
var sellerLayout = GridLayouts.Seller;
|
var sellerLayout = GridLayouts.Seller;
|
||||||
|
|
@ -176,6 +150,38 @@ public class ScrapExecutor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> TravelToSellerIfNeeded(IPage page, TradeItem item)
|
||||||
|
{
|
||||||
|
var alreadyAtSeller = !_inventory.IsAtOwnHideout
|
||||||
|
&& !string.IsNullOrEmpty(item.Account)
|
||||||
|
&& item.Account == _inventory.SellerAccount;
|
||||||
|
|
||||||
|
if (alreadyAtSeller)
|
||||||
|
{
|
||||||
|
Log.Information("Already at seller hideout, skipping travel");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetState(ScrapState.Traveling);
|
||||||
|
var arrived = await _inventory.WaitForAreaTransition(
|
||||||
|
_config.TravelTimeoutMs,
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
if (!await _tradeMonitor.ClickTravelToHideout(page, item.Id))
|
||||||
|
throw new Exception("Failed to click Travel to Hideout");
|
||||||
|
});
|
||||||
|
if (!arrived)
|
||||||
|
{
|
||||||
|
Log.Error("Timed out waiting for hideout arrival: {ItemId}", item.Id);
|
||||||
|
SetState(ScrapState.Failed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_inventory.SetLocation(false, item.Account);
|
||||||
|
await _game.FocusGame();
|
||||||
|
await Helpers.Sleep(Delays.PostTravel);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ProcessItems()
|
private async Task ProcessItems()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -210,12 +216,15 @@ public class ScrapExecutor
|
||||||
items.Add(TradeMonitor.ParseTradeItem(r));
|
items.Add(TradeMonitor.ParseTradeItem(r));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Debug(ex, "Non-JSON trade response");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
page.Response += OnResponse;
|
page.Response += OnResponse;
|
||||||
await page.ReloadAsync(new PageReloadOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
await page.ReloadAsync(new PageReloadOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
||||||
await Helpers.Sleep(2000);
|
await Helpers.Sleep(Delays.PageLoad);
|
||||||
page.Response -= OnResponse;
|
page.Response -= OnResponse;
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,16 @@ namespace Poe2Trade.Bot;
|
||||||
public class TradeExecutor
|
public class TradeExecutor
|
||||||
{
|
{
|
||||||
private TradeState _state = TradeState.Idle;
|
private TradeState _state = TradeState.Idle;
|
||||||
private readonly GameController _game;
|
private readonly IGameController _game;
|
||||||
private readonly ScreenReader _screen;
|
private readonly IScreenReader _screen;
|
||||||
private readonly TradeMonitor _tradeMonitor;
|
private readonly ITradeMonitor _tradeMonitor;
|
||||||
private readonly InventoryManager _inventory;
|
private readonly IInventoryManager _inventory;
|
||||||
private readonly AppConfig _config;
|
private readonly SavedSettings _config;
|
||||||
|
|
||||||
public event Action<TradeState>? StateChanged;
|
public event Action<TradeState>? StateChanged;
|
||||||
|
|
||||||
public TradeExecutor(GameController game, ScreenReader screen, TradeMonitor tradeMonitor,
|
public TradeExecutor(IGameController game, IScreenReader screen, ITradeMonitor tradeMonitor,
|
||||||
InventoryManager inventory, AppConfig config)
|
IInventoryManager inventory, SavedSettings config)
|
||||||
{
|
{
|
||||||
_game = game;
|
_game = game;
|
||||||
_screen = screen;
|
_screen = screen;
|
||||||
|
|
@ -48,7 +48,41 @@ public class TradeExecutor
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Step 1: Travel to seller hideout
|
if (!await TravelToSeller(page, trade))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!await FindSellerStash())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SetState(TradeState.ScanningStash);
|
||||||
|
await ScanAndBuyItems();
|
||||||
|
|
||||||
|
SetState(TradeState.WaitingForMore);
|
||||||
|
Log.Information("Waiting {Ms}ms for more items...", _config.WaitForMoreItemsMs);
|
||||||
|
await Helpers.Sleep(_config.WaitForMoreItemsMs);
|
||||||
|
await ScanAndBuyItems();
|
||||||
|
|
||||||
|
await ReturnHome();
|
||||||
|
|
||||||
|
SetState(TradeState.InHideout);
|
||||||
|
await Helpers.Sleep(Delays.PostStashOpen);
|
||||||
|
await _inventory.ProcessInventory();
|
||||||
|
|
||||||
|
SetState(TradeState.Idle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Trade execution failed");
|
||||||
|
SetState(TradeState.Failed);
|
||||||
|
await RecoverFromError();
|
||||||
|
SetState(TradeState.Idle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> TravelToSeller(IPage page, TradeInfo trade)
|
||||||
|
{
|
||||||
SetState(TradeState.Traveling);
|
SetState(TradeState.Traveling);
|
||||||
Log.Information("Clicking Travel to Hideout for {SearchId}...", trade.SearchId);
|
Log.Information("Clicking Travel to Hideout for {SearchId}...", trade.SearchId);
|
||||||
|
|
||||||
|
|
@ -69,16 +103,19 @@ public class TradeExecutor
|
||||||
SetState(TradeState.InSellersHideout);
|
SetState(TradeState.InSellersHideout);
|
||||||
_inventory.SetLocation(false);
|
_inventory.SetLocation(false);
|
||||||
Log.Information("Arrived at seller hideout");
|
Log.Information("Arrived at seller hideout");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Step 2: Focus game and find stash
|
private async Task<bool> FindSellerStash()
|
||||||
|
{
|
||||||
await _game.FocusGame();
|
await _game.FocusGame();
|
||||||
await Helpers.Sleep(1500);
|
await Helpers.Sleep(Delays.PostTravel);
|
||||||
|
|
||||||
var angePos = await _inventory.FindAndClickNameplate("Ange");
|
var angePos = await _inventory.FindAndClickNameplate("Ange");
|
||||||
if (angePos == null)
|
if (angePos == null)
|
||||||
Log.Warning("Could not find Ange nameplate, trying Stash directly");
|
Log.Warning("Could not find Ange nameplate, trying Stash directly");
|
||||||
else
|
else
|
||||||
await Helpers.Sleep(1000);
|
await Helpers.Sleep(Delays.PostStashOpen);
|
||||||
|
|
||||||
var stashPos = await _inventory.FindAndClickNameplate("Stash");
|
var stashPos = await _inventory.FindAndClickNameplate("Stash");
|
||||||
if (stashPos == null)
|
if (stashPos == null)
|
||||||
|
|
@ -87,60 +124,41 @@ public class TradeExecutor
|
||||||
SetState(TradeState.Failed);
|
SetState(TradeState.Failed);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await Helpers.Sleep(1000);
|
await Helpers.Sleep(Delays.PostStashOpen);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Step 3: Scan stash and buy
|
private async Task ReturnHome()
|
||||||
SetState(TradeState.ScanningStash);
|
{
|
||||||
await ScanAndBuyItems();
|
|
||||||
|
|
||||||
// Step 4: Wait for more
|
|
||||||
SetState(TradeState.WaitingForMore);
|
|
||||||
Log.Information("Waiting {Ms}ms for more items...", _config.WaitForMoreItemsMs);
|
|
||||||
await Helpers.Sleep(_config.WaitForMoreItemsMs);
|
|
||||||
await ScanAndBuyItems();
|
|
||||||
|
|
||||||
// Step 5: Go home
|
|
||||||
SetState(TradeState.GoingHome);
|
SetState(TradeState.GoingHome);
|
||||||
await _game.FocusGame();
|
await _game.FocusGame();
|
||||||
await Helpers.Sleep(300);
|
await Helpers.Sleep(Delays.PostFocus);
|
||||||
|
|
||||||
var home = await _inventory.WaitForAreaTransition(
|
var home = await _inventory.WaitForAreaTransition(
|
||||||
_config.TravelTimeoutMs, () => _game.GoToHideout());
|
_config.TravelTimeoutMs, () => _game.GoToHideout());
|
||||||
if (!home) Log.Warning("Timed out going home");
|
if (!home) Log.Warning("Timed out going home");
|
||||||
|
|
||||||
_inventory.SetLocation(true);
|
_inventory.SetLocation(true);
|
||||||
|
|
||||||
// Step 6: Store items
|
|
||||||
SetState(TradeState.InHideout);
|
|
||||||
await Helpers.Sleep(1000);
|
|
||||||
await _inventory.ProcessInventory();
|
|
||||||
|
|
||||||
SetState(TradeState.Idle);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Trade execution failed");
|
|
||||||
SetState(TradeState.Failed);
|
|
||||||
|
|
||||||
|
private async Task RecoverFromError()
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _game.FocusGame();
|
await _game.FocusGame();
|
||||||
await _game.PressEscape();
|
await _game.PressEscape();
|
||||||
await Helpers.Sleep(500);
|
await Helpers.Sleep(Delays.PostEscape);
|
||||||
await _game.GoToHideout();
|
await _game.GoToHideout();
|
||||||
}
|
}
|
||||||
catch { /* best-effort recovery */ }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
SetState(TradeState.Idle);
|
Log.Debug(ex, "Recovery failed");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ScanAndBuyItems()
|
private async Task ScanAndBuyItems()
|
||||||
{
|
{
|
||||||
var stashRegion = new Region(20, 140, 630, 750);
|
var stashText = await _screen.ReadRegionText(GridLayouts.SellerStashOcr);
|
||||||
var stashText = await _screen.ReadRegionText(stashRegion);
|
|
||||||
Log.Information("Stash OCR: {Text}", stashText.Length > 200 ? stashText[..200] : stashText);
|
Log.Information("Stash OCR: {Text}", stashText.Length > 200 ? stashText[..200] : stashText);
|
||||||
SetState(TradeState.Buying);
|
SetState(TradeState.Buying);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ public class TradeQueue
|
||||||
{
|
{
|
||||||
private readonly Queue<TradeInfo> _queue = new();
|
private readonly Queue<TradeInfo> _queue = new();
|
||||||
private readonly TradeExecutor _executor;
|
private readonly TradeExecutor _executor;
|
||||||
private readonly AppConfig _config;
|
private readonly SavedSettings _config;
|
||||||
private bool _processing;
|
private bool _processing;
|
||||||
|
|
||||||
public TradeQueue(TradeExecutor executor, AppConfig config)
|
public TradeQueue(TradeExecutor executor, SavedSettings config)
|
||||||
{
|
{
|
||||||
_executor = executor;
|
_executor = executor;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace Poe2Trade.Core;
|
|
||||||
|
|
||||||
public class AppConfig
|
|
||||||
{
|
|
||||||
public List<string> TradeUrls { get; set; } = [];
|
|
||||||
public string Poe2LogPath { get; set; } = @"C:\Program Files (x86)\Steam\steamapps\common\Path of Exile 2\logs\Client.txt";
|
|
||||||
public string Poe2WindowTitle { get; set; } = "Path of Exile 2";
|
|
||||||
public string BrowserUserDataDir { get; set; } = "./browser-data";
|
|
||||||
public int TravelTimeoutMs { get; set; } = 15000;
|
|
||||||
public int StashScanTimeoutMs { get; set; } = 10000;
|
|
||||||
public int WaitForMoreItemsMs { get; set; } = 20000;
|
|
||||||
public int BetweenTradesDelayMs { get; set; } = 5000;
|
|
||||||
|
|
||||||
public static AppConfig Load(string? configPath = null)
|
|
||||||
{
|
|
||||||
var builder = new ConfigurationBuilder();
|
|
||||||
var path = configPath ?? "appsettings.json";
|
|
||||||
if (File.Exists(path))
|
|
||||||
{
|
|
||||||
builder.AddJsonFile(path, optional: true);
|
|
||||||
}
|
|
||||||
var configuration = builder.Build();
|
|
||||||
var config = new AppConfig();
|
|
||||||
configuration.Bind(config);
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
src/Poe2Trade.Core/Delays.cs
Normal file
12
src/Poe2Trade.Core/Delays.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Poe2Trade.Core;
|
||||||
|
|
||||||
|
public static class Delays
|
||||||
|
{
|
||||||
|
public const int PostFocus = 300;
|
||||||
|
public const int PostTravel = 1500;
|
||||||
|
public const int PostStashOpen = 1000;
|
||||||
|
public const int ClickInterval = 150;
|
||||||
|
public const int PostEscape = 500;
|
||||||
|
public const int PageLoad = 2000;
|
||||||
|
public const int EmptyRefreshWait = 5000;
|
||||||
|
}
|
||||||
|
|
@ -8,9 +8,6 @@
|
||||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
|
||||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ using Serilog;
|
||||||
|
|
||||||
namespace Poe2Trade.Game;
|
namespace Poe2Trade.Game;
|
||||||
|
|
||||||
public class GameController
|
public class GameController : IGameController
|
||||||
{
|
{
|
||||||
private readonly WindowManager _windowManager;
|
private readonly WindowManager _windowManager;
|
||||||
private readonly InputSender _input;
|
private readonly InputSender _input;
|
||||||
|
|
||||||
public GameController(AppConfig config)
|
public GameController(SavedSettings config)
|
||||||
{
|
{
|
||||||
_windowManager = new WindowManager(config.Poe2WindowTitle);
|
_windowManager = new WindowManager(config.Poe2WindowTitle);
|
||||||
_input = new InputSender();
|
_input = new InputSender();
|
||||||
|
|
|
||||||
22
src/Poe2Trade.Game/IGameController.cs
Normal file
22
src/Poe2Trade.Game/IGameController.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Poe2Trade.Game;
|
||||||
|
|
||||||
|
public interface IGameController
|
||||||
|
{
|
||||||
|
Task<bool> FocusGame();
|
||||||
|
bool IsGameFocused();
|
||||||
|
RECT? GetWindowRect();
|
||||||
|
Task SendChat(string message);
|
||||||
|
Task SendChatViaPaste(string message);
|
||||||
|
Task GoToHideout();
|
||||||
|
Task CtrlLeftClickAt(int x, int y);
|
||||||
|
Task CtrlRightClickAt(int x, int y);
|
||||||
|
Task LeftClickAt(int x, int y);
|
||||||
|
Task RightClickAt(int x, int y);
|
||||||
|
Task MoveMouseTo(int x, int y);
|
||||||
|
void MoveMouseInstant(int x, int y);
|
||||||
|
Task MoveMouseFast(int x, int y);
|
||||||
|
Task PressEscape();
|
||||||
|
Task OpenInventory();
|
||||||
|
Task HoldCtrl();
|
||||||
|
Task ReleaseCtrl();
|
||||||
|
}
|
||||||
20
src/Poe2Trade.Inventory/IInventoryManager.cs
Normal file
20
src/Poe2Trade.Inventory/IInventoryManager.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Poe2Trade.Core;
|
||||||
|
|
||||||
|
namespace Poe2Trade.Inventory;
|
||||||
|
|
||||||
|
public interface IInventoryManager
|
||||||
|
{
|
||||||
|
InventoryTracker Tracker { get; }
|
||||||
|
bool IsAtOwnHideout { get; }
|
||||||
|
string SellerAccount { get; }
|
||||||
|
void SetLocation(bool atHome, string? seller = null);
|
||||||
|
Task ScanInventory(PostAction defaultAction = PostAction.Stash);
|
||||||
|
Task ClearToStash();
|
||||||
|
Task<bool> EnsureAtOwnHideout();
|
||||||
|
Task ProcessInventory();
|
||||||
|
Task<bool> WaitForAreaTransition(int timeoutMs, Func<Task>? triggerAction = null);
|
||||||
|
Task<(int X, int Y)?> FindAndClickNameplate(string name, int maxRetries = 3, int retryDelayMs = 1000);
|
||||||
|
Task DepositItemsToStash(List<PlacedItem> items);
|
||||||
|
Task<bool> SalvageItems(List<PlacedItem> items);
|
||||||
|
(bool[,] Grid, List<PlacedItem> Items, int Free) GetInventoryState();
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ using Serilog;
|
||||||
|
|
||||||
namespace Poe2Trade.Inventory;
|
namespace Poe2Trade.Inventory;
|
||||||
|
|
||||||
public class InventoryManager
|
public class InventoryManager : IInventoryManager
|
||||||
{
|
{
|
||||||
private static readonly string SalvageTemplate = Path.Combine("assets", "salvage.png");
|
private static readonly string SalvageTemplate = Path.Combine("assets", "salvage.png");
|
||||||
|
|
||||||
|
|
@ -14,15 +14,15 @@ public class InventoryManager
|
||||||
|
|
||||||
private bool _atOwnHideout = true;
|
private bool _atOwnHideout = true;
|
||||||
private string _sellerAccount = "";
|
private string _sellerAccount = "";
|
||||||
private readonly GameController _game;
|
private readonly IGameController _game;
|
||||||
private readonly ScreenReader _screen;
|
private readonly IScreenReader _screen;
|
||||||
private readonly ClientLogWatcher _logWatcher;
|
private readonly IClientLogWatcher _logWatcher;
|
||||||
private readonly AppConfig _config;
|
private readonly SavedSettings _config;
|
||||||
|
|
||||||
public bool IsAtOwnHideout => _atOwnHideout;
|
public bool IsAtOwnHideout => _atOwnHideout;
|
||||||
public string SellerAccount => _sellerAccount;
|
public string SellerAccount => _sellerAccount;
|
||||||
|
|
||||||
public InventoryManager(GameController game, ScreenReader screen, ClientLogWatcher logWatcher, AppConfig config)
|
public InventoryManager(IGameController game, IScreenReader screen, IClientLogWatcher logWatcher, SavedSettings config)
|
||||||
{
|
{
|
||||||
_game = game;
|
_game = game;
|
||||||
_screen = screen;
|
_screen = screen;
|
||||||
|
|
@ -40,7 +40,7 @@ public class InventoryManager
|
||||||
{
|
{
|
||||||
Log.Information("Scanning inventory...");
|
Log.Information("Scanning inventory...");
|
||||||
await _game.FocusGame();
|
await _game.FocusGame();
|
||||||
await Helpers.Sleep(300);
|
await Helpers.Sleep(Delays.PostFocus);
|
||||||
await _game.OpenInventory();
|
await _game.OpenInventory();
|
||||||
|
|
||||||
var result = await _screen.Grid.Scan("inventory");
|
var result = await _screen.Grid.Scan("inventory");
|
||||||
|
|
@ -54,7 +54,7 @@ public class InventoryManager
|
||||||
Tracker.InitFromScan(cells, result.Items, defaultAction);
|
Tracker.InitFromScan(cells, result.Items, defaultAction);
|
||||||
|
|
||||||
await _game.PressEscape();
|
await _game.PressEscape();
|
||||||
await Helpers.Sleep(300);
|
await Helpers.Sleep(Delays.PostFocus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClearToStash()
|
public async Task ClearToStash()
|
||||||
|
|
@ -83,7 +83,7 @@ public class InventoryManager
|
||||||
}
|
}
|
||||||
|
|
||||||
await _game.FocusGame();
|
await _game.FocusGame();
|
||||||
await Helpers.Sleep(300);
|
await Helpers.Sleep(Delays.PostFocus);
|
||||||
|
|
||||||
var arrived = await WaitForAreaTransition(_config.TravelTimeoutMs, () => _game.GoToHideout());
|
var arrived = await WaitForAreaTransition(_config.TravelTimeoutMs, () => _game.GoToHideout());
|
||||||
if (!arrived)
|
if (!arrived)
|
||||||
|
|
@ -92,7 +92,7 @@ public class InventoryManager
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Helpers.Sleep(1500);
|
await Helpers.Sleep(Delays.PostTravel);
|
||||||
_atOwnHideout = true;
|
_atOwnHideout = true;
|
||||||
_sellerAccount = "";
|
_sellerAccount = "";
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -108,23 +108,13 @@ public class InventoryManager
|
||||||
Log.Error("Could not find Stash nameplate");
|
Log.Error("Could not find Stash nameplate");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await Helpers.Sleep(1000);
|
await Helpers.Sleep(Delays.PostStashOpen);
|
||||||
|
|
||||||
var inventoryLayout = GridLayouts.Inventory;
|
|
||||||
Log.Information("Depositing {Count} items to stash", items.Count);
|
Log.Information("Depositing {Count} items to stash", items.Count);
|
||||||
|
await CtrlClickItems(items, GridLayouts.Inventory);
|
||||||
await _game.HoldCtrl();
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
|
||||||
var center = _screen.Grid.GetCellCenter(inventoryLayout, item.Row, item.Col);
|
|
||||||
await _game.LeftClickAt(center.X, center.Y);
|
|
||||||
await Helpers.Sleep(150);
|
|
||||||
}
|
|
||||||
await _game.ReleaseCtrl();
|
|
||||||
await Helpers.Sleep(500);
|
|
||||||
|
|
||||||
await _game.PressEscape();
|
await _game.PressEscape();
|
||||||
await Helpers.Sleep(500);
|
await Helpers.Sleep(Delays.PostEscape);
|
||||||
Log.Information("Items deposited to stash");
|
Log.Information("Items deposited to stash");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,35 +128,38 @@ public class InventoryManager
|
||||||
Log.Error("Could not find Salvage nameplate");
|
Log.Error("Could not find Salvage nameplate");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await Helpers.Sleep(1000);
|
await Helpers.Sleep(Delays.PostStashOpen);
|
||||||
|
|
||||||
var salvageBtn = await _screen.TemplateMatch(SalvageTemplate);
|
var salvageBtn = await _screen.TemplateMatch(SalvageTemplate);
|
||||||
if (salvageBtn != null)
|
if (salvageBtn != null)
|
||||||
{
|
{
|
||||||
await _game.LeftClickAt(salvageBtn.X, salvageBtn.Y);
|
await _game.LeftClickAt(salvageBtn.X, salvageBtn.Y);
|
||||||
await Helpers.Sleep(500);
|
await Helpers.Sleep(Delays.PostEscape);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Warning("Could not find salvage button via template match");
|
Log.Warning("Could not find salvage button via template match");
|
||||||
}
|
}
|
||||||
|
|
||||||
var inventoryLayout = GridLayouts.Inventory;
|
|
||||||
Log.Information("Salvaging {Count} inventory items", items.Count);
|
Log.Information("Salvaging {Count} inventory items", items.Count);
|
||||||
|
await CtrlClickItems(items, GridLayouts.Inventory);
|
||||||
|
|
||||||
|
await _game.PressEscape();
|
||||||
|
await Helpers.Sleep(Delays.PostEscape);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CtrlClickItems(List<PlacedItem> items, GridLayout layout, int clickDelayMs = Delays.ClickInterval)
|
||||||
|
{
|
||||||
await _game.HoldCtrl();
|
await _game.HoldCtrl();
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
var center = _screen.Grid.GetCellCenter(inventoryLayout, item.Row, item.Col);
|
var center = _screen.Grid.GetCellCenter(layout, item.Row, item.Col);
|
||||||
await _game.LeftClickAt(center.X, center.Y);
|
await _game.LeftClickAt(center.X, center.Y);
|
||||||
await Helpers.Sleep(150);
|
await Helpers.Sleep(clickDelayMs);
|
||||||
}
|
}
|
||||||
await _game.ReleaseCtrl();
|
await _game.ReleaseCtrl();
|
||||||
await Helpers.Sleep(500);
|
await Helpers.Sleep(Delays.PostEscape);
|
||||||
|
|
||||||
await _game.PressEscape();
|
|
||||||
await Helpers.Sleep(500);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ProcessInventory()
|
public async Task ProcessInventory()
|
||||||
|
|
@ -201,7 +194,7 @@ public class InventoryManager
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Inventory processing failed");
|
Log.Error(ex, "Inventory processing failed");
|
||||||
try { await _game.PressEscape(); await Helpers.Sleep(300); } catch { }
|
try { await _game.PressEscape(); await Helpers.Sleep(Delays.PostFocus); } catch { }
|
||||||
Tracker.Clear();
|
Tracker.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ public static class GridLayouts
|
||||||
|
|
||||||
public static readonly GridLayout Inventory = All["inventory"];
|
public static readonly GridLayout Inventory = All["inventory"];
|
||||||
public static readonly GridLayout Seller = All["seller"];
|
public static readonly GridLayout Seller = All["seller"];
|
||||||
|
public static readonly Region SellerStashOcr = new(20, 140, 630, 750);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GridReader
|
public class GridReader
|
||||||
|
|
|
||||||
22
src/Poe2Trade.Screen/IScreenReader.cs
Normal file
22
src/Poe2Trade.Screen/IScreenReader.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
using Poe2Trade.Core;
|
||||||
|
|
||||||
|
namespace Poe2Trade.Screen;
|
||||||
|
|
||||||
|
public interface IScreenReader : IDisposable
|
||||||
|
{
|
||||||
|
GridReader Grid { get; }
|
||||||
|
Task Warmup();
|
||||||
|
Task<byte[]> CaptureScreen();
|
||||||
|
Task<byte[]> CaptureRegion(Region region);
|
||||||
|
Task<OcrResponse> Ocr(Region? region = null, string? preprocess = null);
|
||||||
|
Task<(int X, int Y)?> FindTextOnScreen(string searchText, bool fuzzy = false);
|
||||||
|
Task<string> ReadFullScreen();
|
||||||
|
Task<(int X, int Y)?> FindTextInRegion(Region region, string searchText);
|
||||||
|
Task<string> ReadRegionText(Region region);
|
||||||
|
Task<bool> CheckForText(Region region, string searchText);
|
||||||
|
Task Snapshot();
|
||||||
|
Task<DiffOcrResponse> DiffOcr(string? savePath = null, Region? region = null);
|
||||||
|
Task<TemplateMatchResult?> TemplateMatch(string templatePath, Region? region = null);
|
||||||
|
Task SaveScreenshot(string path);
|
||||||
|
Task SaveRegion(Region region, string path);
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ using Serilog;
|
||||||
|
|
||||||
namespace Poe2Trade.Screen;
|
namespace Poe2Trade.Screen;
|
||||||
|
|
||||||
public class ScreenReader : IDisposable
|
public class ScreenReader : IScreenReader
|
||||||
{
|
{
|
||||||
private readonly DiffCropHandler _diffCrop = new();
|
private readonly DiffCropHandler _diffCrop = new();
|
||||||
private readonly GridHandler _gridHandler = new();
|
private readonly GridHandler _gridHandler = new();
|
||||||
|
|
|
||||||
15
src/Poe2Trade.Trade/ITradeMonitor.cs
Normal file
15
src/Poe2Trade.Trade/ITradeMonitor.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.Playwright;
|
||||||
|
using Poe2Trade.Core;
|
||||||
|
|
||||||
|
namespace Poe2Trade.Trade;
|
||||||
|
|
||||||
|
public interface ITradeMonitor : IAsyncDisposable
|
||||||
|
{
|
||||||
|
event Action<string, List<string>, IPage>? NewListings;
|
||||||
|
Task Start(string? dashboardUrl = null);
|
||||||
|
Task AddSearch(string tradeUrl);
|
||||||
|
Task PauseSearch(string searchId);
|
||||||
|
Task<bool> ClickTravelToHideout(IPage page, string? itemId = null);
|
||||||
|
Task<(IPage Page, List<TradeItem> Items)> OpenScrapPage(string tradeUrl);
|
||||||
|
string ExtractSearchId(string url);
|
||||||
|
}
|
||||||
|
|
@ -5,12 +5,12 @@ using Serilog;
|
||||||
|
|
||||||
namespace Poe2Trade.Trade;
|
namespace Poe2Trade.Trade;
|
||||||
|
|
||||||
public class TradeMonitor : IAsyncDisposable
|
public class TradeMonitor : ITradeMonitor
|
||||||
{
|
{
|
||||||
private IBrowserContext? _context;
|
private IBrowserContext? _context;
|
||||||
private readonly Dictionary<string, IPage> _pages = new();
|
private readonly Dictionary<string, IPage> _pages = new();
|
||||||
private readonly HashSet<string> _pausedSearches = new();
|
private readonly HashSet<string> _pausedSearches = new();
|
||||||
private readonly AppConfig _config;
|
private readonly SavedSettings _config;
|
||||||
|
|
||||||
private const string StealthScript = """
|
private const string StealthScript = """
|
||||||
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
||||||
|
|
@ -38,7 +38,7 @@ public class TradeMonitor : IAsyncDisposable
|
||||||
|
|
||||||
public event Action<string, List<string>, IPage>? NewListings;
|
public event Action<string, List<string>, IPage>? NewListings;
|
||||||
|
|
||||||
public TradeMonitor(AppConfig config)
|
public TradeMonitor(SavedSettings config)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +96,7 @@ public class TradeMonitor : IAsyncDisposable
|
||||||
_pages[searchId] = page;
|
_pages[searchId] = page;
|
||||||
|
|
||||||
await page.GotoAsync(tradeUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
await page.GotoAsync(tradeUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
||||||
await Helpers.Sleep(2000);
|
await Helpers.Sleep(Delays.PageLoad);
|
||||||
|
|
||||||
page.WebSocket += (_, ws) => HandleWebSocket(ws, searchId, page);
|
page.WebSocket += (_, ws) => HandleWebSocket(ws, searchId, page);
|
||||||
|
|
||||||
|
|
@ -177,11 +177,11 @@ public class TradeMonitor : IAsyncDisposable
|
||||||
items.Add(ParseTradeItem(r));
|
items.Add(ParseTradeItem(r));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { /* Response may not be JSON */ }
|
catch (Exception ex) { Log.Debug(ex, "Non-JSON trade response"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
await page.GotoAsync(tradeUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
await page.GotoAsync(tradeUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
|
||||||
await Helpers.Sleep(2000);
|
await Helpers.Sleep(Delays.PageLoad);
|
||||||
Log.Information("Scrap page opened: {Url} ({Count} items)", tradeUrl, items.Count);
|
Log.Information("Scrap page opened: {Url} ({Count} items)", tradeUrl, items.Count);
|
||||||
return (page, items);
|
return (page, items);
|
||||||
}
|
}
|
||||||
|
|
@ -259,7 +259,7 @@ public class TradeMonitor : IAsyncDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { /* Not all frames are JSON */ }
|
catch (Exception ex) { Log.Debug(ex, "Non-JSON WebSocket frame"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.Close += (_, _) => Log.Warning("WebSocket closed: {SearchId}", searchId);
|
ws.Close += (_, _) => Log.Warning("WebSocket closed: {SearchId}", searchId);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Poe2Trade.Bot;
|
using Poe2Trade.Bot;
|
||||||
using Poe2Trade.Core;
|
using Poe2Trade.Core;
|
||||||
|
using Poe2Trade.Game;
|
||||||
|
using Poe2Trade.GameLog;
|
||||||
|
using Poe2Trade.Inventory;
|
||||||
|
using Poe2Trade.Screen;
|
||||||
|
using Poe2Trade.Trade;
|
||||||
using Poe2Trade.Ui.ViewModels;
|
using Poe2Trade.Ui.ViewModels;
|
||||||
using Poe2Trade.Ui.Views;
|
using Poe2Trade.Ui.Views;
|
||||||
|
|
||||||
|
|
@ -19,15 +25,39 @@ public partial class App : Application
|
||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
var store = new ConfigStore();
|
var services = new ServiceCollection();
|
||||||
var config = AppConfig.Load();
|
|
||||||
var bot = new BotOrchestrator(store, config);
|
|
||||||
|
|
||||||
var mainVm = new MainWindowViewModel(bot)
|
// Config
|
||||||
{
|
services.AddSingleton<ConfigStore>();
|
||||||
DebugVm = new DebugViewModel(bot),
|
services.AddSingleton(sp => sp.GetRequiredService<ConfigStore>().Settings);
|
||||||
SettingsVm = new SettingsViewModel(bot)
|
|
||||||
};
|
// Services
|
||||||
|
services.AddSingleton<IGameController, GameController>();
|
||||||
|
services.AddSingleton<IScreenReader, ScreenReader>();
|
||||||
|
services.AddSingleton<IClientLogWatcher>(sp =>
|
||||||
|
new ClientLogWatcher(sp.GetRequiredService<SavedSettings>().Poe2LogPath));
|
||||||
|
services.AddSingleton<ITradeMonitor, TradeMonitor>();
|
||||||
|
services.AddSingleton<IInventoryManager, InventoryManager>();
|
||||||
|
|
||||||
|
// Bot
|
||||||
|
services.AddSingleton<LinkManager>();
|
||||||
|
services.AddSingleton<TradeExecutor>();
|
||||||
|
services.AddSingleton<TradeQueue>();
|
||||||
|
services.AddSingleton<BotOrchestrator>();
|
||||||
|
|
||||||
|
// ViewModels
|
||||||
|
services.AddSingleton<MainWindowViewModel>();
|
||||||
|
services.AddSingleton<DebugViewModel>();
|
||||||
|
services.AddSingleton<SettingsViewModel>();
|
||||||
|
|
||||||
|
var provider = services.BuildServiceProvider();
|
||||||
|
|
||||||
|
var store = provider.GetRequiredService<ConfigStore>();
|
||||||
|
var bot = provider.GetRequiredService<BotOrchestrator>();
|
||||||
|
|
||||||
|
var mainVm = provider.GetRequiredService<MainWindowViewModel>();
|
||||||
|
mainVm.DebugVm = provider.GetRequiredService<DebugViewModel>();
|
||||||
|
mainVm.SettingsVm = provider.GetRequiredService<SettingsViewModel>();
|
||||||
|
|
||||||
var window = new MainWindow { DataContext = mainVm };
|
var window = new MainWindow { DataContext = mainVm };
|
||||||
window.SetConfigStore(store);
|
window.SetConfigStore(store);
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,15 @@
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.2.3" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.3" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Poe2Trade.Core\Poe2Trade.Core.csproj" />
|
<ProjectReference Include="..\Poe2Trade.Core\Poe2Trade.Core.csproj" />
|
||||||
<ProjectReference Include="..\Poe2Trade.Bot\Poe2Trade.Bot.csproj" />
|
<ProjectReference Include="..\Poe2Trade.Bot\Poe2Trade.Bot.csproj" />
|
||||||
|
<ProjectReference Include="..\Poe2Trade.Game\Poe2Trade.Game.csproj" />
|
||||||
|
<ProjectReference Include="..\Poe2Trade.Screen\Poe2Trade.Screen.csproj" />
|
||||||
|
<ProjectReference Include="..\Poe2Trade.Trade\Poe2Trade.Trade.csproj" />
|
||||||
|
<ProjectReference Include="..\Poe2Trade.Log\Poe2Trade.Log.csproj" />
|
||||||
|
<ProjectReference Include="..\Poe2Trade.Inventory\Poe2Trade.Inventory.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ public partial class MainWindowViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _bot.Start(_bot.Config.TradeUrls);
|
await _bot.Start([]);
|
||||||
IsStarted = true;
|
IsStarted = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,6 @@ public partial class SettingsViewModel : ObservableObject
|
||||||
s.BetweenTradesDelayMs = (int)(BetweenTradesDelayMs ?? 5000);
|
s.BetweenTradesDelayMs = (int)(BetweenTradesDelayMs ?? 5000);
|
||||||
});
|
});
|
||||||
|
|
||||||
_bot.Config.Poe2LogPath = Poe2LogPath;
|
|
||||||
_bot.Config.Poe2WindowTitle = WindowTitle;
|
|
||||||
_bot.Config.TravelTimeoutMs = (int)(TravelTimeoutMs ?? 15000);
|
|
||||||
_bot.Config.StashScanTimeoutMs = (int)(StashScanTimeoutMs ?? 10000);
|
|
||||||
_bot.Config.WaitForMoreItemsMs = (int)(WaitForMoreItemsMs ?? 20000);
|
|
||||||
_bot.Config.BetweenTradesDelayMs = (int)(BetweenTradesDelayMs ?? 5000);
|
|
||||||
|
|
||||||
IsSaved = true;
|
IsSaved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue