poe2-bot/src/Poe2Trade.Ui/ViewModels/SettingsViewModel.cs
2026-02-21 20:57:22 -05:00

213 lines
7.3 KiB
C#

using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Poe2Trade.Bot;
using Poe2Trade.Core;
using Poe2Trade.Inventory;
using Serilog;
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 _headless = true;
[ObservableProperty] private bool _showHudDebug;
[ObservableProperty] private bool _isSaved;
[ObservableProperty] private string _calibrationStatus = "";
[ObservableProperty] private string _stashCalibratedAt = "";
[ObservableProperty] private string _shopCalibratedAt = "";
public ObservableCollection<StashTabViewModel> StashTabs { get; } = [];
public ObservableCollection<StashTabViewModel> ShopTabs { get; } = [];
public SettingsViewModel(BotOrchestrator bot)
{
_bot = bot;
LoadFromConfig();
LoadTabs();
}
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;
Headless = s.Headless;
ShowHudDebug = s.ShowHudDebug;
}
private void LoadTabs()
{
var s = _bot.Store.Settings;
StashTabs.Clear();
if (s.StashCalibration != null)
{
foreach (var tab in s.StashCalibration.Tabs)
StashTabs.Add(new StashTabViewModel(tab));
StashCalibratedAt = FormatTimestamp(s.StashCalibration.CalibratedAt);
}
else
{
StashCalibratedAt = "Not calibrated";
}
ShopTabs.Clear();
if (s.ShopCalibration != null)
{
foreach (var tab in s.ShopCalibration.Tabs)
ShopTabs.Add(new StashTabViewModel(tab));
ShopCalibratedAt = FormatTimestamp(s.ShopCalibration.CalibratedAt);
}
else
{
ShopCalibratedAt = "Not calibrated";
}
}
private static string FormatTimestamp(long unixMs)
{
if (unixMs == 0) return "Not calibrated";
var dt = DateTimeOffset.FromUnixTimeMilliseconds(unixMs).LocalDateTime;
return dt.ToString("yyyy-MM-dd HH:mm");
}
[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);
s.Headless = Headless;
s.ShowHudDebug = ShowHudDebug;
});
IsSaved = true;
}
[RelayCommand]
private void SaveTabs()
{
_bot.Store.UpdateSettings(s =>
{
// Models are already updated via write-through in StashTabViewModel
// Just trigger a save
if (s.StashCalibration != null)
s.StashCalibration = s.StashCalibration;
if (s.ShopCalibration != null)
s.ShopCalibration = s.ShopCalibration;
});
CalibrationStatus = "Tabs saved!";
}
[RelayCommand]
private async Task CalibrateStash()
{
try
{
var calibrator = new StashCalibrator(_bot.Screen, _bot.Game);
CalibrationStatus = "Calibrating stash...";
await _bot.Game.FocusGame();
await Helpers.RandomDelay(150, 300);
var pos = await _bot.Inventory.FindAndClickNameplate("STASH");
if (!pos.HasValue)
{
CalibrationStatus = "STASH not found. Stand near your stash.";
return;
}
await Helpers.RandomDelay(300, 500);
var cal = await calibrator.CalibrateOpenPanel();
await _bot.Game.PressEscape();
await Helpers.RandomDelay(200, 400);
_bot.Store.UpdateSettings(s => s.StashCalibration = cal);
LoadTabs();
CalibrationStatus = $"Stash calibrated — {cal.Tabs.Count} tabs found.";
}
catch (Exception ex)
{
CalibrationStatus = $"Stash calibration failed: {ex.Message}";
Log.Error(ex, "Stash calibration failed");
}
}
[RelayCommand]
private async Task CalibrateShop()
{
try
{
var calibrator = new StashCalibrator(_bot.Screen, _bot.Game);
CalibrationStatus = "Calibrating shop...";
await _bot.Game.FocusGame();
await Helpers.RandomDelay(150, 300);
var pos = await _bot.Inventory.FindAndClickNameplate("ANGE");
if (!pos.HasValue)
{
CalibrationStatus = "ANGE not found. Stand near the vendor.";
return;
}
await Helpers.RandomDelay(800, 1200);
// ANGE opens a dialog — click "Manage Shop"
var dialogRegion = new Region(pos.Value.X, pos.Value.Y, 460, 600);
var managePos = await _bot.Screen.FindTextInRegion(dialogRegion, "Manage");
if (managePos.HasValue)
{
await _bot.Game.LeftClickAt(managePos.Value.X, managePos.Value.Y);
await Helpers.RandomDelay(300, 500);
}
else
{
Log.Warning("'Manage Shop' not found in dialog region, saving debug capture");
await _bot.Screen.SaveRegion(dialogRegion, "debug/calibrate-dialog.png");
}
var cal = await calibrator.CalibrateOpenPanel(firstFolderOnly: true);
await _bot.Game.PressEscape();
await Helpers.RandomDelay(200, 400);
_bot.Store.UpdateSettings(s => s.ShopCalibration = cal);
LoadTabs();
CalibrationStatus = $"Shop calibrated — {cal.Tabs.Count} tabs found.";
}
catch (Exception ex)
{
CalibrationStatus = $"Shop calibration failed: {ex.Message}";
Log.Error(ex, "Shop calibration failed");
}
}
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;
partial void OnHeadlessChanged(bool value) => IsSaved = false;
partial void OnShowHudDebugChanged(bool value) => IsSaved = false;
}