switched to new way

This commit is contained in:
Boki 2026-02-13 01:12:51 -05:00
parent f22d182c8f
commit 4a65c8e17b
96 changed files with 4991 additions and 10025 deletions

View file

@ -0,0 +1,201 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Poe2Trade.Bot;
using Poe2Trade.Screen;
using Serilog;
namespace Poe2Trade.Ui.ViewModels;
public partial class DebugViewModel : ObservableObject
{
private readonly BotOrchestrator _bot;
[ObservableProperty] private string _findText = "";
[ObservableProperty] private string _debugResult = "";
[ObservableProperty] private string _selectedGridLayout = "inventory";
[ObservableProperty] private decimal? _clickX;
[ObservableProperty] private decimal? _clickY;
public string[] GridLayoutNames { get; } =
[
"inventory", "stash12", "stash12_folder", "stash24",
"stash24_folder", "seller", "shop", "vendor"
];
public DebugViewModel(BotOrchestrator bot)
{
_bot = bot;
}
private bool EnsureReady()
{
if (_bot.IsReady) return true;
DebugResult = "Bot not started yet. Press Start first.";
return false;
}
[RelayCommand]
private async Task TakeScreenshot()
{
if (!EnsureReady()) return;
try
{
var path = Path.Combine("debug", $"screenshot-{DateTime.Now:yyyyMMdd-HHmmss}.png");
Directory.CreateDirectory("debug");
await _bot.Screen.SaveScreenshot(path);
DebugResult = $"Screenshot saved: {path}";
}
catch (Exception ex)
{
DebugResult = $"Screenshot failed: {ex.Message}";
Log.Error(ex, "Screenshot failed");
}
}
[RelayCommand]
private async Task RunOcr()
{
if (!EnsureReady()) return;
try
{
var text = await _bot.Screen.ReadFullScreen();
DebugResult = string.IsNullOrWhiteSpace(text) ? "(no text detected)" : text;
}
catch (Exception ex)
{
DebugResult = $"OCR failed: {ex.Message}";
Log.Error(ex, "OCR failed");
}
}
[RelayCommand]
private async Task GoHideout()
{
if (!EnsureReady()) return;
try
{
await _bot.Game.FocusGame();
await _bot.Game.GoToHideout();
DebugResult = "Sent /hideout command";
}
catch (Exception ex)
{
DebugResult = $"Go hideout failed: {ex.Message}";
Log.Error(ex, "Go hideout failed");
}
}
[RelayCommand]
private async Task FindTextOnScreen()
{
if (!EnsureReady() || string.IsNullOrWhiteSpace(FindText)) return;
try
{
var pos = await _bot.Screen.FindTextOnScreen(FindText, fuzzy: true);
DebugResult = pos.HasValue
? $"Found '{FindText}' at ({pos.Value.X}, {pos.Value.Y})"
: $"Text '{FindText}' not found";
}
catch (Exception ex)
{
DebugResult = $"Find text failed: {ex.Message}";
Log.Error(ex, "Find text failed");
}
}
[RelayCommand]
private async Task FindAndClick()
{
if (!EnsureReady() || string.IsNullOrWhiteSpace(FindText)) return;
try
{
await _bot.Game.FocusGame();
var pos = await _bot.Inventory.FindAndClickNameplate(FindText);
DebugResult = pos.HasValue
? $"Clicked '{FindText}' at ({pos.Value.X}, {pos.Value.Y})"
: $"Text '{FindText}' not found";
}
catch (Exception ex)
{
DebugResult = $"Find & click failed: {ex.Message}";
Log.Error(ex, "Find & click failed");
}
}
[RelayCommand]
private async Task ClickAt()
{
if (!EnsureReady()) return;
var x = (int)(ClickX ?? 0);
var y = (int)(ClickY ?? 0);
try
{
await _bot.Game.FocusGame();
await _bot.Game.LeftClickAt(x, y);
DebugResult = $"Clicked at ({x}, {y})";
}
catch (Exception ex)
{
DebugResult = $"Click failed: {ex.Message}";
Log.Error(ex, "Click at failed");
}
}
[RelayCommand]
private async Task ScanGrid()
{
if (!EnsureReady()) return;
try
{
var result = await _bot.Screen.Grid.Scan(SelectedGridLayout);
DebugResult = $"Grid scan '{SelectedGridLayout}': " +
$"{result.Layout.Cols}x{result.Layout.Rows}, " +
$"{result.Occupied.Count} occupied, " +
$"{result.Items.Count} items";
}
catch (Exception ex)
{
DebugResult = $"Grid scan failed: {ex.Message}";
Log.Error(ex, "Grid scan failed");
}
}
[RelayCommand]
private async Task ClickAnge()
{
if (!EnsureReady()) return;
try
{
await _bot.Game.FocusGame();
var pos = await _bot.Inventory.FindAndClickNameplate("ANGE");
DebugResult = pos.HasValue ? $"Clicked ANGE at ({pos.Value.X}, {pos.Value.Y})" : "ANGE not found";
}
catch (Exception ex) { DebugResult = $"Failed: {ex.Message}"; }
}
[RelayCommand]
private async Task ClickStash()
{
if (!EnsureReady()) return;
try
{
await _bot.Game.FocusGame();
var pos = await _bot.Inventory.FindAndClickNameplate("STASH");
DebugResult = pos.HasValue ? $"Clicked STASH at ({pos.Value.X}, {pos.Value.Y})" : "STASH not found";
}
catch (Exception ex) { DebugResult = $"Failed: {ex.Message}"; }
}
[RelayCommand]
private async Task ClickSalvage()
{
if (!EnsureReady()) return;
try
{
await _bot.Game.FocusGame();
var pos = await _bot.Inventory.FindAndClickNameplate("SALVAGE BENCH");
DebugResult = pos.HasValue ? $"Clicked SALVAGE at ({pos.Value.X}, {pos.Value.Y})" : "SALVAGE BENCH not found";
}
catch (Exception ex) { DebugResult = $"Failed: {ex.Message}"; }
}
}

View file

@ -0,0 +1,183 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Poe2Trade.Bot;
using Poe2Trade.Core;
using Serilog;
namespace Poe2Trade.Ui.ViewModels;
public class LogEntry
{
public string Time { get; init; } = "";
public string Level { get; init; } = "";
public string Message { get; init; } = "";
}
public partial class CellState : ObservableObject
{
[ObservableProperty] private bool _isOccupied;
[ObservableProperty] private bool _borderTop;
[ObservableProperty] private bool _borderBottom;
[ObservableProperty] private bool _borderLeft;
[ObservableProperty] private bool _borderRight;
}
public partial class MainWindowViewModel : ObservableObject
{
private readonly BotOrchestrator _bot;
[ObservableProperty]
private string _state = "Idle";
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(PauseButtonText))]
private bool _isPaused;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(StartCommand))]
[NotifyCanExecuteChangedFor(nameof(PauseCommand))]
private bool _isStarted;
[ObservableProperty] private string _newUrl = "";
[ObservableProperty] private string _newLinkName = "";
[ObservableProperty] private LinkMode _newLinkMode = LinkMode.Live;
[ObservableProperty] private int _tradesCompleted;
[ObservableProperty] private int _tradesFailed;
[ObservableProperty] private int _activeLinksCount;
public static LinkMode[] LinkModes { get; } = [LinkMode.Live, LinkMode.Scrap];
public MainWindowViewModel(BotOrchestrator bot)
{
_bot = bot;
_isPaused = bot.IsPaused;
for (var i = 0; i < 60; i++)
InventoryCells.Add(new CellState());
bot.StatusUpdated += () =>
{
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
State = bot.State;
IsPaused = bot.IsPaused;
var status = bot.GetStatus();
TradesCompleted = status.TradesCompleted;
TradesFailed = status.TradesFailed;
ActiveLinksCount = status.Links.Count(l => l.Active);
OnPropertyChanged(nameof(Links));
UpdateInventoryGrid();
});
};
bot.LogMessage += (level, message) =>
{
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
Logs.Add(new LogEntry
{
Time = DateTime.Now.ToString("HH:mm:ss"),
Level = level.ToUpperInvariant(),
Message = message
});
if (Logs.Count > 500) Logs.RemoveAt(0);
});
};
}
public string PauseButtonText => IsPaused ? "Resume" : "Pause";
public List<TradeLink> Links => _bot.Links.GetLinks();
public ObservableCollection<LogEntry> Logs { get; } = [];
public ObservableCollection<CellState> InventoryCells { get; } = [];
public int InventoryFreeCells => _bot.IsReady ? _bot.Inventory.Tracker.FreeCells : 60;
// Sub-ViewModels for tabs
public DebugViewModel? DebugVm { get; set; }
public SettingsViewModel? SettingsVm { get; set; }
[RelayCommand(CanExecute = nameof(CanStart))]
private async Task Start()
{
try
{
await _bot.Start(_bot.Config.TradeUrls);
IsStarted = true;
}
catch (Exception ex)
{
Log.Error(ex, "Failed to start bot");
Logs.Add(new LogEntry
{
Time = DateTime.Now.ToString("HH:mm:ss"),
Level = "ERROR",
Message = $"Start failed: {ex.Message}"
});
}
}
private bool CanStart() => !IsStarted;
[RelayCommand(CanExecute = nameof(CanPause))]
private void Pause()
{
if (_bot.IsPaused) _bot.Resume();
else _bot.Pause();
}
private bool CanPause() => IsStarted;
[RelayCommand]
private void AddLink()
{
if (string.IsNullOrWhiteSpace(NewUrl)) return;
_bot.AddLink(NewUrl, NewLinkName, NewLinkMode);
NewUrl = "";
NewLinkName = "";
}
[RelayCommand]
private void RemoveLink(string? id)
{
if (id != null) _bot.RemoveLink(id);
}
[RelayCommand]
private void ToggleLink(string? id)
{
if (id == null) return;
var link = _bot.Links.GetLink(id);
if (link != null) _bot.ToggleLink(id, !link.Active);
}
private void UpdateInventoryGrid()
{
if (!_bot.IsReady) return;
var (grid, items, _) = _bot.Inventory.GetInventoryState();
for (var r = 0; r < 5; r++)
for (var c = 0; c < 12; c++)
{
var cell = InventoryCells[r * 12 + c];
cell.IsOccupied = grid[r, c];
cell.BorderTop = false;
cell.BorderBottom = false;
cell.BorderLeft = false;
cell.BorderRight = false;
}
foreach (var item in items)
{
for (var r = item.Row; r < item.Row + item.H; r++)
for (var c = item.Col; c < item.Col + item.W; c++)
{
if (r >= 5 || c >= 12) continue;
var cell = InventoryCells[r * 12 + c];
if (r == item.Row) cell.BorderTop = true;
if (r == item.Row + item.H - 1) cell.BorderBottom = true;
if (c == item.Col) cell.BorderLeft = true;
if (c == item.Col + item.W - 1) cell.BorderRight = true;
}
}
OnPropertyChanged(nameof(InventoryFreeCells));
}
}

View file

@ -0,0 +1,65 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Poe2Trade.Bot;
namespace Poe2Trade.Ui.ViewModels;
public partial class SettingsViewModel : ObservableObject
{
private readonly BotOrchestrator _bot;
[ObservableProperty] private string _poe2LogPath = "";
[ObservableProperty] private string _windowTitle = "";
[ObservableProperty] private decimal? _travelTimeoutMs = 15000;
[ObservableProperty] private decimal? _stashScanTimeoutMs = 10000;
[ObservableProperty] private decimal? _waitForMoreItemsMs = 20000;
[ObservableProperty] private decimal? _betweenTradesDelayMs = 5000;
[ObservableProperty] private bool _isSaved;
public SettingsViewModel(BotOrchestrator bot)
{
_bot = bot;
LoadFromConfig();
}
private void LoadFromConfig()
{
var s = _bot.Store.Settings;
Poe2LogPath = s.Poe2LogPath;
WindowTitle = s.Poe2WindowTitle;
TravelTimeoutMs = s.TravelTimeoutMs;
StashScanTimeoutMs = s.StashScanTimeoutMs;
WaitForMoreItemsMs = s.WaitForMoreItemsMs;
BetweenTradesDelayMs = s.BetweenTradesDelayMs;
}
[RelayCommand]
private void SaveSettings()
{
_bot.Store.UpdateSettings(s =>
{
s.Poe2LogPath = Poe2LogPath;
s.Poe2WindowTitle = WindowTitle;
s.TravelTimeoutMs = (int)(TravelTimeoutMs ?? 15000);
s.StashScanTimeoutMs = (int)(StashScanTimeoutMs ?? 10000);
s.WaitForMoreItemsMs = (int)(WaitForMoreItemsMs ?? 20000);
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;
}
partial void OnPoe2LogPathChanged(string value) => IsSaved = false;
partial void OnWindowTitleChanged(string value) => IsSaved = false;
partial void OnTravelTimeoutMsChanged(decimal? value) => IsSaved = false;
partial void OnStashScanTimeoutMsChanged(decimal? value) => IsSaved = false;
partial void OnWaitForMoreItemsMsChanged(decimal? value) => IsSaved = false;
partial void OnBetweenTradesDelayMsChanged(decimal? value) => IsSaved = false;
}