Show champs ingame
This commit is contained in:
@@ -9,7 +9,9 @@ using LeagueAPI;
|
|||||||
using LeagueAPI.ARAM;
|
using LeagueAPI.ARAM;
|
||||||
using LeagueAPI.Models.ChampSelect;
|
using LeagueAPI.Models.ChampSelect;
|
||||||
using LeagueAPI.Models.DDragon.Champions;
|
using LeagueAPI.Models.DDragon.Champions;
|
||||||
|
using LeagueAPI.Models.GameClient;
|
||||||
using LeagueAPI.Models.ReadyCheck;
|
using LeagueAPI.Models.ReadyCheck;
|
||||||
|
using LeagueAPI.Utils;
|
||||||
|
|
||||||
namespace ARAMUtility.ViewModel;
|
namespace ARAMUtility.ViewModel;
|
||||||
|
|
||||||
@@ -56,6 +58,8 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
private readonly LcuWebsocket _lcuWebsocket = new();
|
private readonly LcuWebsocket _lcuWebsocket = new();
|
||||||
private Task _lcuWebsocketTask;
|
private Task _lcuWebsocketTask;
|
||||||
private readonly ARAMBalanceService _aramBalanceService = new();
|
private readonly ARAMBalanceService _aramBalanceService = new();
|
||||||
|
private readonly ProcessWatcher _leagueWatcher = new();
|
||||||
|
private readonly CachingHttpClient _localCachingClient = new(insecure: true);
|
||||||
|
|
||||||
public MainViewModel()
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
@@ -71,6 +75,8 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
_lcuWebsocketTask = _lcuWebsocket.Connect();
|
_lcuWebsocketTask = _lcuWebsocket.Connect();
|
||||||
|
|
||||||
|
_leagueWatcher.LeagueOfLegendsExeFound += UpdateChampionListsFromGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async void OnInit(object? sender, RoutedEventArgs e)
|
internal async void OnInit(object? sender, RoutedEventArgs e)
|
||||||
@@ -152,6 +158,8 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
private async Task FillChampionLists()
|
private async Task FillChampionLists()
|
||||||
{
|
{
|
||||||
string defaultImagePath = await ResourceService.GetChampionIconPathAsync(-1);
|
string defaultImagePath = await ResourceService.GetChampionIconPathAsync(-1);
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
while (TeamChampions.Count < TEAM_CHAMPIONS_MAX)
|
while (TeamChampions.Count < TEAM_CHAMPIONS_MAX)
|
||||||
{
|
{
|
||||||
TeamChampions.Add(GetEmptyChampionViewModel());
|
TeamChampions.Add(GetEmptyChampionViewModel());
|
||||||
@@ -160,6 +168,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
{
|
{
|
||||||
BenchChampions.Add(GetEmptyChampionViewModel());
|
BenchChampions.Add(GetEmptyChampionViewModel());
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ChampionViewModel GetEmptyChampionViewModel()
|
ChampionViewModel GetEmptyChampionViewModel()
|
||||||
{
|
{
|
||||||
@@ -185,10 +194,24 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
|
|
||||||
await FillChampionLists();
|
await FillChampionLists();
|
||||||
_championUpdateSemaphore.Release();
|
_championUpdateSemaphore.Release();
|
||||||
|
}
|
||||||
|
|
||||||
async Task UpdateChampions(ObservableCollection<ChampionViewModel> viewModel, IEnumerable<int> championIds)
|
private async Task UpdateChampions(ObservableCollection<ChampionViewModel> viewModel, IEnumerable<int> championIds)
|
||||||
{
|
{
|
||||||
viewModel.Clear();
|
try
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() => viewModel.Clear());
|
||||||
|
}
|
||||||
|
catch (NotSupportedException)
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
while (viewModel.Count > 0)
|
||||||
|
{
|
||||||
|
viewModel.RemoveAt(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
await _aramBalanceService.EnsureIsLoadedAsync();
|
await _aramBalanceService.EnsureIsLoadedAsync();
|
||||||
|
|
||||||
foreach (int championId in championIds)
|
foreach (int championId in championIds)
|
||||||
@@ -205,9 +228,40 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
IsNeededForChallenge = _needChampionIds.Contains(championData.Id),
|
IsNeededForChallenge = _needChampionIds.Contains(championData.Id),
|
||||||
ImagePath = imagePath,
|
ImagePath = imagePath,
|
||||||
};
|
};
|
||||||
viewModel.Add(vm);
|
Dispatcher.Invoke(() => { viewModel.Add(vm); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void UpdateChampionListsFromGame(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||||
|
string playerlistJson = await _localCachingClient.GetStringAsync("https://127.0.0.1:2999/liveclientdata/playerlist", TimeSpan.FromSeconds(30));
|
||||||
|
PlayerResponse[]? allPlayerResponse = JsonSerializer.Deserialize<PlayerResponse[]>(playerlistJson);
|
||||||
|
if (allPlayerResponse is not { Length: > 0 })
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<int> blueTeam = [];
|
||||||
|
List<int> redTeam = [];
|
||||||
|
foreach (PlayerResponse player in allPlayerResponse)
|
||||||
|
{
|
||||||
|
if (player.Team != TeamId.ORDER && player.Team != TeamId.CHAOS)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int championId = await _client.GetChampionIdByNameAsync(player.ChampionName);
|
||||||
|
if (player.Team == TeamId.ORDER)
|
||||||
|
{
|
||||||
|
blueTeam.Add(championId);
|
||||||
|
}
|
||||||
|
if (player.Team == TeamId.CHAOS)
|
||||||
|
{
|
||||||
|
redTeam.Add(championId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await UpdateChampions(TeamChampions, blueTeam);
|
||||||
|
await UpdateChampions(BenchChampions, redTeam);
|
||||||
|
await FillChampionLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand(AllowConcurrentExecutions = false)]
|
[RelayCommand(AllowConcurrentExecutions = false)]
|
||||||
@@ -253,8 +307,9 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_allChampions = [];
|
_allChampions = [];
|
||||||
_client.Dispose();
|
_client?.Dispose();
|
||||||
_lcuWebsocket.Dispose();
|
_lcuWebsocket?.Dispose();
|
||||||
|
_localCachingClient?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|||||||
+17
-3
@@ -24,6 +24,8 @@ public class APIClient : IDisposable
|
|||||||
|
|
||||||
private readonly Dictionary<string, ChampionResponse> _championResponseCache = [];
|
private readonly Dictionary<string, ChampionResponse> _championResponseCache = [];
|
||||||
|
|
||||||
|
private readonly CachingHttpClient _ddragonClient = new();
|
||||||
|
|
||||||
public APIClient()
|
public APIClient()
|
||||||
{
|
{
|
||||||
_lcuHttpClient = new(new LcuHttpClientHandler());
|
_lcuHttpClient = new(new LcuHttpClientHandler());
|
||||||
@@ -81,12 +83,12 @@ public class APIClient : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region DDragon
|
#region DDragon
|
||||||
private static async Task<string> DDragonGetAsync(string path)
|
private async Task<string> DDragonGetAsync(string path)
|
||||||
{
|
{
|
||||||
return await CachingHttpClient.GetStringAsync($"{DDRAGON_BASE_URL}{path}");
|
return await _ddragonClient.GetStringAsync($"{DDRAGON_BASE_URL}{path}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<T?> DDragonGetAsync<T>(string path)
|
private async Task<T?> DDragonGetAsync<T>(string path)
|
||||||
{
|
{
|
||||||
string json = await DDragonGetAsync(path);
|
string json = await DDragonGetAsync(path);
|
||||||
return JsonSerializer.Deserialize<T>(json);
|
return JsonSerializer.Deserialize<T>(json);
|
||||||
@@ -135,6 +137,17 @@ public class APIClient : IDisposable
|
|||||||
|
|
||||||
return championData.FirstOrDefault(c => c.Id == id);
|
return championData.FirstOrDefault(c => c.Id == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetChampionIdByNameAsync(string name)
|
||||||
|
{
|
||||||
|
ChampionData[] championData = await GetAllChampionsAsync();
|
||||||
|
if (championData is not { Length: > 0 })
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return championData.FirstOrDefault(c => c.Name == name)?.Id ?? -1;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region IDisposable
|
#region IDisposable
|
||||||
@@ -145,6 +158,7 @@ public class APIClient : IDisposable
|
|||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_lcuHttpClient?.Dispose();
|
_lcuHttpClient?.Dispose();
|
||||||
|
_ddragonClient?.Dispose();
|
||||||
}
|
}
|
||||||
_championResponseCache?.Clear();
|
_championResponseCache?.Clear();
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,77 @@
|
|||||||
namespace LeagueAPI;
|
namespace LeagueAPI;
|
||||||
|
|
||||||
public class HttpCache : Dictionary<string, string> { }
|
public class HttpCache : Dictionary<string, InvalidatableCacheObject<string>> { }
|
||||||
|
public record struct InvalidatableCacheObject<T>(
|
||||||
public static class CachingHttpClient
|
T Value,
|
||||||
|
DateTime? InvalidateTime = null
|
||||||
|
)
|
||||||
{
|
{
|
||||||
private static HttpCache _cache = ResourceService.GetHttpCache() ?? [];
|
public readonly bool IsValid()
|
||||||
private static HttpClient _client = new();
|
|
||||||
|
|
||||||
public static async Task<string> GetStringAsync(string requestUri)
|
|
||||||
{
|
{
|
||||||
if (_cache.TryGetValue(requestUri, out string? response))
|
return !InvalidateTime.HasValue || InvalidateTime < DateTime.UtcNow;
|
||||||
{
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
return await _client.GetStringAsync(requestUri);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CachingHttpClient : IDisposable
|
||||||
|
{
|
||||||
|
private bool _isDisposed;
|
||||||
|
private readonly HttpCache _cache = ResourceService.GetHttpCache() ?? [];
|
||||||
|
private readonly HttpClient _client = new();
|
||||||
|
|
||||||
|
public CachingHttpClient(bool insecure = false)
|
||||||
|
{
|
||||||
|
if (insecure)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
|
||||||
|
};
|
||||||
|
_client = new(handler);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_client = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetStringAsync(string requestUri, TimeSpan? invalidateAfter = null)
|
||||||
|
{
|
||||||
|
if (_cache.TryGetValue(requestUri, out InvalidatableCacheObject<string> response) && response.IsValid())
|
||||||
|
{
|
||||||
|
return response.Value;
|
||||||
|
}
|
||||||
|
string result = await _client.GetStringAsync(requestUri);
|
||||||
|
if (invalidateAfter is not null && invalidateAfter > TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
_cache[requestUri] = new(result, DateTime.UtcNow + invalidateAfter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cache[requestUri] = new(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_client?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache?.Clear();
|
||||||
|
_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
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace LeagueAPI.Models.GameClient;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public enum TeamId
|
||||||
|
{
|
||||||
|
ALL,
|
||||||
|
UNKNOWN,
|
||||||
|
ORDER,
|
||||||
|
CHAOS,
|
||||||
|
NEUTRAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
public record class PlayerResponse(
|
||||||
|
[property: JsonPropertyName("championName")] string ChampionName,
|
||||||
|
[property: JsonPropertyName("isBot")] bool IsBot,
|
||||||
|
[property: JsonPropertyName("rawChampionName")] string RawChampionName,
|
||||||
|
[property: JsonPropertyName("team")] TeamId Team,
|
||||||
|
[property: JsonPropertyName("riotId")] string RiotId,
|
||||||
|
[property: JsonPropertyName("riotIdGameName")] string RiotIdGameName,
|
||||||
|
[property: JsonPropertyName("riotIdTagLine")] string RiotIdTagLine
|
||||||
|
);
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace LeagueAPI.Utils;
|
||||||
|
|
||||||
|
public class ProcessWatcher : IDisposable
|
||||||
|
{
|
||||||
|
private bool _isDisposed;
|
||||||
|
private Timer _timer;
|
||||||
|
|
||||||
|
public bool IsGameRunning { get; private set; } = false;
|
||||||
|
|
||||||
|
public event EventHandler? LeagueOfLegendsExeFound;
|
||||||
|
|
||||||
|
public TimeSpan Interval
|
||||||
|
{
|
||||||
|
get => field;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (field != value)
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
_timer?.Change(TimeSpan.Zero, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
public ProcessWatcher()
|
||||||
|
{
|
||||||
|
_timer = new(OnTimerTick, null, TimeSpan.Zero, Interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTimerTick(object? state)
|
||||||
|
{
|
||||||
|
if (Process.GetProcessesByName("League of Legends").Length != 0)
|
||||||
|
{
|
||||||
|
if (!IsGameRunning)
|
||||||
|
{
|
||||||
|
LeagueOfLegendsExeFound?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
IsGameRunning = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IsGameRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_timer?.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