v2 (WPF)
This commit is contained in:
239
ARAMUtility/ViewModel/MainViewModel.cs
Normal file
239
ARAMUtility/ViewModel/MainViewModel.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LeagueAPI;
|
||||
using LeagueAPI.ARAM;
|
||||
using LeagueAPI.Models.ChampSelect;
|
||||
using LeagueAPI.Models.DDragon.Champions;
|
||||
using LeagueAPI.Models.ReadyCheck;
|
||||
|
||||
namespace ARAMUtility.ViewModel;
|
||||
|
||||
public partial class MainViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private const int TEAM_CHAMPIONS_MAX = 5;
|
||||
private const int BENCH_CHAMPIONS_MAX = 10;
|
||||
|
||||
private bool _isDisposed;
|
||||
private readonly Lock _syncRoot = new();
|
||||
private readonly SemaphoreSlim _championUpdateSemaphore = new(1, 1);
|
||||
|
||||
private Dispatcher Dispatcher { get; } = Dispatcher.CurrentDispatcher;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Title { get; set; } = "ARAM Utility";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ChampionViewModel> TeamChampions { get; private set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ChampionViewModel> BenchChampions { get; private set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsDisconnected { get; private set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string ConnectionStatus { get; private set; } = "Not connected.";
|
||||
|
||||
private Dictionary<int, ChampionData> _allChampions = [];
|
||||
private List<int> _needChampionIds = [];
|
||||
private readonly APIClient _client = new();
|
||||
private readonly LcuWebsocket _lcuWebsocket = new();
|
||||
private Task _lcuWebsocketTask;
|
||||
private readonly ARAMBalanceService _aramBalanceService = new();
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
_lcuWebsocket.Connecting += (_, _) => UpdateConnectionStatus(false, "Connecting ...");
|
||||
_lcuWebsocket.Connected += (_, _) => UpdateConnectionStatus(false, "Connected.");
|
||||
_lcuWebsocket.Disconnected += (_, _) => UpdateConnectionStatus(true, "Disconnected.");
|
||||
|
||||
_lcuWebsocket.LcuApiEvent += OnLcuApiEvent;
|
||||
#if DEBUG
|
||||
_lcuWebsocket.LcuApiException += (sender, e) =>
|
||||
{
|
||||
File.AppendAllText("socketerror.log", $"[{DateTime.Now:s}] {e.Message}\n{e.StackTrace}\n\n");
|
||||
};
|
||||
#endif
|
||||
_lcuWebsocketTask = _lcuWebsocket.Connect();
|
||||
}
|
||||
|
||||
internal async void OnInit(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
ChampionData[] champions = await _client.GetAllChampionsAsync();
|
||||
Dictionary<int, ChampionData> championDictionary = champions
|
||||
.Where(c => c.Id != -1)
|
||||
.ToDictionary(key => key.Id);
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_allChampions = championDictionary;
|
||||
}
|
||||
await UpdateNeedChampionIdsAsync();
|
||||
await FillChampionLists();
|
||||
}
|
||||
|
||||
private void UpdateConnectionStatus(bool isConnected, string statusMessage)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
IsDisconnected = isConnected;
|
||||
ConnectionStatus = statusMessage;
|
||||
});
|
||||
}
|
||||
|
||||
private async void OnLcuApiEvent(object? sender, LcuApiEvent apiEvent)
|
||||
{
|
||||
switch (apiEvent.Uri)
|
||||
{
|
||||
case "/lol-matchmaking/v1/ready-check":
|
||||
LolMatchmakingMatchmakingReadyCheckResource? readyCheck = apiEvent.Data.Deserialize<LolMatchmakingMatchmakingReadyCheckResource>();
|
||||
if (readyCheck is not null && readyCheck.PlayerResponse == LolMatchmakingMatchmakingReadyCheckResponse.Accepted)
|
||||
{
|
||||
await UpdateNeedChampionIdsAsync();
|
||||
}
|
||||
break;
|
||||
case "/lol-champ-select/v1/session":
|
||||
ChampSelectSession? session = apiEvent.Data.Deserialize<ChampSelectSession>();
|
||||
await ShowChampionsAsync(session);
|
||||
break;
|
||||
default:
|
||||
File.AppendAllText("socket.log", $"{apiEvent.Uri}: {apiEvent.EventType} - {apiEvent.Data?.ToJsonString()}\n");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task UpdateNeedChampionIdsAsync()
|
||||
{
|
||||
IEnumerable<int> completedChampionIds = await _client.GetAllRandomAllChampionsCompletedChampionsAsync();
|
||||
IEnumerable<int> needChampionIds = _allChampions.Keys.Except(completedChampionIds);
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_needChampionIds = [.. needChampionIds];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FillChampionLists()
|
||||
{
|
||||
string defaultImagePath = await ResourceService.GetChampionIconPathAsync(-1);
|
||||
while (TeamChampions.Count < TEAM_CHAMPIONS_MAX)
|
||||
{
|
||||
TeamChampions.Add(GetEmptyChampionViewModel());
|
||||
}
|
||||
while (BenchChampions.Count < BENCH_CHAMPIONS_MAX)
|
||||
{
|
||||
BenchChampions.Add(GetEmptyChampionViewModel());
|
||||
}
|
||||
|
||||
ChampionViewModel GetEmptyChampionViewModel()
|
||||
{
|
||||
return new ChampionViewModel(new ChampionData()) { ImagePath = defaultImagePath, IsNeededForChallenge = false };
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowChampionsAsync(ChampSelectSession? session)
|
||||
{
|
||||
await _championUpdateSemaphore.WaitAsync();
|
||||
|
||||
(IEnumerable<int> teamChampions, IEnumerable<int> benchChampions) = APIClient.GetSelectableChampionIds(session);
|
||||
if (!teamChampions.Any() && !benchChampions.Any())
|
||||
{
|
||||
TeamChampions.Clear();
|
||||
BenchChampions.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
await UpdateChampions(TeamChampions, teamChampions);
|
||||
await UpdateChampions(BenchChampions, benchChampions);
|
||||
}
|
||||
|
||||
await FillChampionLists();
|
||||
_championUpdateSemaphore.Release();
|
||||
|
||||
async Task UpdateChampions(ObservableCollection<ChampionViewModel> viewModel, IEnumerable<int> championIds)
|
||||
{
|
||||
viewModel.Clear();
|
||||
await _aramBalanceService.EnsureIsLoadedAsync();
|
||||
|
||||
foreach (int championId in championIds)
|
||||
{
|
||||
ChampionData? championData = await _client.GetChampionByIdAsync(championId);
|
||||
if (championData is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string imagePath = await ResourceService.GetChampionIconPathAsync(championId);
|
||||
ChampionViewModel vm = new(championData with { AramBalance = _aramBalanceService.GetAramStats(championData.Id) })
|
||||
{
|
||||
IsNeededForChallenge = _needChampionIds.Contains(championData.Id),
|
||||
ImagePath = imagePath,
|
||||
};
|
||||
viewModel.Add(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(IsDisconnected))]
|
||||
private async Task Connect()
|
||||
{
|
||||
if (_lcuWebsocketTask is null || _lcuWebsocketTask.IsCompleted)
|
||||
{
|
||||
_lcuWebsocketTask?.Dispose();
|
||||
_lcuWebsocketTask = _lcuWebsocket.Connect();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(AllowConcurrentExecutions = false)]
|
||||
private async Task OpenLobby()
|
||||
{
|
||||
await _client.CreateMayhemLobbyAsync();
|
||||
}
|
||||
|
||||
[RelayCommand(AllowConcurrentExecutions = false)]
|
||||
private async Task StartQueueing()
|
||||
{
|
||||
await _client.StartMatchmakingQueueAsync();
|
||||
}
|
||||
|
||||
[RelayCommand(AllowConcurrentExecutions = false)]
|
||||
private async Task ReloadARAMBalance()
|
||||
{
|
||||
await _aramBalanceService.ReloadAsync(force: true);
|
||||
MessageBox.Show("Reloaded ARAM balance data.", "Info", MessageBoxButton.OK, MessageBoxImage.Exclamation);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Quit()
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_allChampions = [];
|
||||
_client.Dispose();
|
||||
_lcuWebsocket.Dispose();
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user