started adding navigation

This commit is contained in:
Boki 2026-02-13 10:43:35 -05:00
parent 32781b1462
commit 468e0a7246
20 changed files with 844 additions and 31 deletions

View file

@ -14,5 +14,6 @@
<conv:ActiveOpacityConverter x:Key="ActiveOpacity" />
<conv:CellBorderConverter x:Key="CellBorderConverter" />
<conv:BoolToOverlayBrushConverter x:Key="OccupiedOverlayBrush" />
<conv:MapRequirementsConverter x:Key="MapRequirementsText" />
</Application.Resources>
</Application>

View file

@ -49,6 +49,7 @@ public partial class App : Application
services.AddSingleton<MainWindowViewModel>();
services.AddSingleton<DebugViewModel>();
services.AddSingleton<SettingsViewModel>();
services.AddSingleton<MappingViewModel>();
var provider = services.BuildServiceProvider();
@ -58,6 +59,7 @@ public partial class App : Application
var mainVm = provider.GetRequiredService<MainWindowViewModel>();
mainVm.DebugVm = provider.GetRequiredService<DebugViewModel>();
mainVm.SettingsVm = provider.GetRequiredService<SettingsViewModel>();
mainVm.MappingVm = provider.GetRequiredService<MappingViewModel>();
var window = new MainWindow { DataContext = mainVm };
window.SetConfigStore(store);

View file

@ -94,6 +94,23 @@ public class BoolToOverlayBrushConverter : IValueConverter
=> throw new NotSupportedException();
}
public class MapRequirementsConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value switch
{
MapType.TrialOfChaos => "Trial Token x1",
MapType.Temple => "Identity Scroll x20",
MapType.Endgame => "Identity Scroll x20",
_ => "",
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
public class CellBorderConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)

View file

@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Poe2Trade.Bot;
using Poe2Trade.Core;
using Poe2Trade.Navigation;
using Serilog;
namespace Poe2Trade.Ui.ViewModels;
@ -42,6 +43,9 @@ public partial class MainWindowViewModel : ObservableObject
private bool _isStarted;
[ObservableProperty] private Bitmap? _inventoryImage;
[ObservableProperty] private Bitmap? _minimapImage;
[ObservableProperty] private string _navigationStateText = "";
private long _lastMinimapUpdate;
[ObservableProperty] private string _newUrl = "";
[ObservableProperty] private string _newLinkName = "";
@ -49,13 +53,16 @@ public partial class MainWindowViewModel : ObservableObject
[ObservableProperty] private int _tradesCompleted;
[ObservableProperty] private int _tradesFailed;
[ObservableProperty] private int _activeLinksCount;
[ObservableProperty] private BotMode _botMode;
public static LinkMode[] LinkModes { get; } = [LinkMode.Live, LinkMode.Scrap];
public static BotMode[] BotModes { get; } = [BotMode.Trading, BotMode.Mapping];
public MainWindowViewModel(BotOrchestrator bot)
{
_bot = bot;
_isPaused = bot.IsPaused;
_botMode = bot.Mode;
for (var i = 0; i < 60; i++)
InventoryCells.Add(new CellState());
@ -66,12 +73,14 @@ public partial class MainWindowViewModel : ObservableObject
{
State = bot.State;
IsPaused = bot.IsPaused;
BotMode = bot.Mode;
var status = bot.GetStatus();
TradesCompleted = status.TradesCompleted;
TradesFailed = status.TradesFailed;
ActiveLinksCount = status.Links.Count(l => l.Active);
OnPropertyChanged(nameof(Links));
UpdateInventoryGrid();
UpdateMinimapImage();
});
};
@ -99,6 +108,12 @@ public partial class MainWindowViewModel : ObservableObject
// Sub-ViewModels for tabs
public DebugViewModel? DebugVm { get; set; }
public SettingsViewModel? SettingsVm { get; set; }
public MappingViewModel? MappingVm { get; set; }
partial void OnBotModeChanged(BotMode value)
{
_bot.Mode = value;
}
[RelayCommand(CanExecute = nameof(CanStart))]
private async Task Start()
@ -198,4 +213,39 @@ public partial class MainWindowViewModel : ObservableObject
OnPropertyChanged(nameof(InventoryFreeCells));
}
private void UpdateMinimapImage()
{
var nav = _bot.Navigation;
var navState = nav.State;
NavigationStateText = navState == NavigationState.Idle ? "" : navState.ToString();
if (navState == NavigationState.Idle)
{
if (MinimapImage != null)
{
var old = MinimapImage;
MinimapImage = null;
old.Dispose();
}
return;
}
// Throttle: update at most once per second
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (now - _lastMinimapUpdate < 1000) return;
_lastMinimapUpdate = now;
try
{
var bytes = nav.GetViewportSnapshot();
var old = MinimapImage;
MinimapImage = new Bitmap(new MemoryStream(bytes));
old?.Dispose();
}
catch (Exception ex)
{
Log.Debug(ex, "Failed to update minimap image");
}
}
}

View file

@ -0,0 +1,25 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Poe2Trade.Bot;
using Poe2Trade.Core;
namespace Poe2Trade.Ui.ViewModels;
public partial class MappingViewModel : ObservableObject
{
private readonly BotOrchestrator _bot;
[ObservableProperty] private MapType _selectedMapType;
public static MapType[] MapTypes { get; } = [MapType.TrialOfChaos, MapType.Temple, MapType.Endgame];
public MappingViewModel(BotOrchestrator bot)
{
_bot = bot;
_selectedMapType = bot.Config.MapType;
}
partial void OnSelectedMapTypeChanged(MapType value)
{
_bot.Store.UpdateSettings(s => s.MapType = value);
}
}

View file

@ -56,7 +56,10 @@
</StackPanel>
<!-- Controls -->
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8">
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8"
VerticalAlignment="Center">
<ComboBox ItemsSource="{x:Static vm:MainWindowViewModel.BotModes}"
SelectedItem="{Binding BotMode}" Width="110" />
<Button Content="Start" Command="{Binding StartCommand}" />
<Button Content="{Binding PauseButtonText}" Command="{Binding PauseCommand}" />
</StackPanel>
@ -70,36 +73,61 @@
<TabItem Header="State">
<Grid RowDefinitions="Auto,*" Margin="0,6,0,0">
<!-- Inventory Grid (12x5) -->
<Border Grid.Row="0" Background="#161b22" BorderBrush="#30363d"
BorderThickness="1" CornerRadius="8" Padding="8" Margin="0,0,0,6">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="INVENTORY" FontSize="11" FontWeight="SemiBold"
Foreground="#8b949e" />
<TextBlock Text="{Binding InventoryFreeCells, StringFormat='{}{0}/60 free'}"
FontSize="11" Foreground="#8b949e" Margin="12,0,0,0" />
</StackPanel>
<Grid MaxHeight="170">
<Image Source="{Binding InventoryImage}" Stretch="Uniform" />
<ItemsControl ItemsSource="{Binding InventoryCells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="12" Rows="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:CellState">
<Border Margin="1" CornerRadius="2"
Background="{Binding IsOccupied, Converter={StaticResource OccupiedOverlayBrush}}"
BorderBrush="#3fb950"
BorderThickness="{Binding Converter={StaticResource CellBorderConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DockPanel>
</Border>
<!-- Top row: Inventory + Minimap side by side -->
<Grid Grid.Row="0" ColumnDefinitions="*,Auto" Margin="0,0,0,6">
<!-- Inventory Grid (12x5) -->
<Border Grid.Column="0" Background="#161b22" BorderBrush="#30363d"
BorderThickness="1" CornerRadius="8" Padding="8" Margin="0,0,6,0">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="INVENTORY" FontSize="11" FontWeight="SemiBold"
Foreground="#8b949e" />
<TextBlock Text="{Binding InventoryFreeCells, StringFormat='{}{0}/60 free'}"
FontSize="11" Foreground="#8b949e" Margin="12,0,0,0" />
</StackPanel>
<Grid MaxHeight="170">
<Image Source="{Binding InventoryImage}" Stretch="Uniform" />
<ItemsControl ItemsSource="{Binding InventoryCells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="12" Rows="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:CellState">
<Border Margin="1" CornerRadius="2"
Background="{Binding IsOccupied, Converter={StaticResource OccupiedOverlayBrush}}"
BorderBrush="#3fb950"
BorderThickness="{Binding Converter={StaticResource CellBorderConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DockPanel>
</Border>
<!-- Minimap -->
<Border Grid.Column="1" Background="#161b22" BorderBrush="#30363d"
BorderThickness="1" CornerRadius="8" Padding="8" Width="200">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="MINIMAP" FontSize="11" FontWeight="SemiBold"
Foreground="#8b949e" />
<TextBlock Text="{Binding NavigationStateText}"
FontSize="11" Foreground="#8b949e" Margin="8,0,0,0" />
</StackPanel>
<Grid>
<Image Source="{Binding MinimapImage}" Stretch="Uniform"
RenderOptions.BitmapInterpolationMode="None" />
<TextBlock Text="Idle"
IsVisible="{Binding MinimapImage, Converter={x:Static ObjectConverters.IsNull}}"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="12" Foreground="#484f58" />
</Grid>
</DockPanel>
</Border>
</Grid>
<!-- Logs -->
<Border Grid.Row="1" Background="#0d1117" BorderBrush="#30363d"
@ -178,6 +206,24 @@
</Border>
</TabItem>
<!-- ========== MAPPING TAB ========== -->
<TabItem Header="Mapping">
<Border DataContext="{Binding MappingVm}" Background="#161b22"
BorderBrush="#30363d" BorderThickness="1" CornerRadius="8"
Padding="10" Margin="0,6,0,0">
<StackPanel Spacing="8" x:DataType="vm:MappingViewModel">
<TextBlock Text="MAP TYPE" FontSize="11" FontWeight="SemiBold"
Foreground="#8b949e" />
<ComboBox ItemsSource="{x:Static vm:MappingViewModel.MapTypes}"
SelectedItem="{Binding SelectedMapType}" Width="200" />
<TextBlock Text="REQUIRED ITEMS" FontSize="11" FontWeight="SemiBold"
Foreground="#8b949e" Margin="0,8,0,0" />
<TextBlock Text="{Binding SelectedMapType, Converter={StaticResource MapRequirementsText}}"
FontSize="13" Foreground="#e6edf3" />
</StackPanel>
</Border>
</TabItem>
<!-- ========== DEBUG TAB ========== -->
<TabItem Header="Debug">
<ScrollViewer DataContext="{Binding DebugVm}" Margin="0,6,0,0">