Compare commits
5 Commits
1f258ef61c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
9f6105b970
|
|||
|
06d5113711
|
|||
|
b6a4903847
|
|||
|
61eee88078
|
|||
|
076c0a9a8d
|
@@ -15,7 +15,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -7,9 +7,10 @@
|
|||||||
xmlns:vm="clr-namespace:ARAMUtility.ViewModel"
|
xmlns:vm="clr-namespace:ARAMUtility.ViewModel"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
Height="480"
|
Height="500"
|
||||||
Width="700"
|
Width="700"
|
||||||
Background="#0a1a2a"
|
Background="#0a1a2a"
|
||||||
|
Topmost="True"
|
||||||
>
|
>
|
||||||
<Window.DataContext>
|
<Window.DataContext>
|
||||||
<vm:MainViewModel />
|
<vm:MainViewModel />
|
||||||
@@ -60,32 +61,17 @@
|
|||||||
<MenuItem Header="Start Queueing" Command="{Binding StartQueueingCommand}" />
|
<MenuItem Header="Start Queueing" Command="{Binding StartQueueingCommand}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="Topmost" IsCheckable="True" IsChecked="{Binding Topmost, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
|
<MenuItem Header="Topmost" IsCheckable="True" IsChecked="{Binding Topmost, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
|
||||||
|
<MenuItem Header="Auto Accept" IsCheckable="True" IsChecked="{Binding AutoAccept}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="Reload ARAM Balance" Command="{Binding ReloadARAMBalanceCommand}" />
|
<MenuItem Header="Reload ARAM Balance" Command="{Binding ReloadARAMBalanceCommand}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="Quit" Command="{Binding QuitCommand}" />
|
<MenuItem Header="Quit" Command="{Binding QuitCommand}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="{Binding ConnectionStatus}" Command="{Binding ConnectCommand}" />
|
<MenuItem Header="Reconnect" Command="{Binding ConnectCommand}" Visibility="{Binding ReconnectButtonVisibility, Mode=OneWay}" />
|
||||||
<MenuItem Header="Update ARAM">
|
|
||||||
<StackPanel Orientation="Vertical">
|
|
||||||
<ComboBox ItemsSource="{Binding .}"
|
|
||||||
SelectedIndex="0"
|
|
||||||
SelectedItem="{Binding .}"
|
|
||||||
/>
|
|
||||||
<ItemsControl ItemsSource="{Binding .}">
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<Label Content="{Binding .}" />
|
|
||||||
<TextBox Text="{Binding .}" />
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
<Button Content="Save" />
|
|
||||||
</StackPanel>
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
|
<StatusBar DockPanel.Dock="Bottom">
|
||||||
|
<StatusBarItem Content="{Binding Status}" />
|
||||||
|
</StatusBar>
|
||||||
<Viewbox Stretch="Uniform" StretchDirection="DownOnly">
|
<Viewbox Stretch="Uniform" StretchDirection="DownOnly">
|
||||||
<Grid Margin="10">
|
<Grid Margin="10">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
Topmost = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (DataContext is MainViewModel viewModel)
|
if (DataContext is MainViewModel viewModel)
|
||||||
{
|
{
|
||||||
Loaded += viewModel.OnInit;
|
Loaded += viewModel.OnInit;
|
||||||
|
|||||||
@@ -24,46 +24,9 @@ public partial class ChampionViewModel(ChampionData data) : ObservableObject
|
|||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
sb.AppendLine(data.Name);
|
sb.AppendLine(data.Name);
|
||||||
foreach (KeyValuePair<string, double> kv in data.AramBalance)
|
if (data.AramBalance.HasValue)
|
||||||
{
|
{
|
||||||
switch (kv.Key)
|
data.AramBalance.Value.ToDisplayString(sb);
|
||||||
{
|
|
||||||
case "dmg_dealt":
|
|
||||||
if (kv.Value == 1) { continue; }
|
|
||||||
sb.AppendFormat("Dmg Dealt: {0:+#0%;-#0%}", kv.Value - 1);
|
|
||||||
break;
|
|
||||||
case "dmg_taken":
|
|
||||||
if (kv.Value == 1) { continue; }
|
|
||||||
sb.AppendFormat("Dmg Taken: {0:+#0%;-#0%}", kv.Value - 1);
|
|
||||||
break;
|
|
||||||
case "healing":
|
|
||||||
if (kv.Value == 1) { continue; }
|
|
||||||
sb.AppendFormat("Healing: {0:+#0%;-#0%}", kv.Value - 1);
|
|
||||||
break;
|
|
||||||
case "energyregen_mod":
|
|
||||||
if (kv.Value == 1) { continue; }
|
|
||||||
sb.AppendFormat("Energy Regen: {0:+#0%;-#0%}", kv.Value - 1);
|
|
||||||
break;
|
|
||||||
case "tenacity":
|
|
||||||
if (kv.Value == 1) { continue; }
|
|
||||||
sb.AppendFormat("Tenacity: {0:+#0%;-#0%}", kv.Value - 1);
|
|
||||||
break;
|
|
||||||
case "shielding":
|
|
||||||
if (kv.Value == 1) { continue; }
|
|
||||||
sb.AppendFormat("Shielding: {0:+#0%;-#0%}", kv.Value - 1);
|
|
||||||
break;
|
|
||||||
case "ability_haste":
|
|
||||||
sb.AppendFormat("Ability Haste: {0}", kv.Value);
|
|
||||||
break;
|
|
||||||
case "total_as":
|
|
||||||
if (kv.Value == 1) { continue; }
|
|
||||||
sb.AppendFormat("Total AS: {0}", kv.Value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
sb.AppendFormat("{0}: {1}", kv.Key, kv.Value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sb.AppendLine();
|
|
||||||
}
|
}
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -18,6 +20,13 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
private const int TEAM_CHAMPIONS_MAX = 5;
|
private const int TEAM_CHAMPIONS_MAX = 5;
|
||||||
private const int BENCH_CHAMPIONS_MAX = 10;
|
private const int BENCH_CHAMPIONS_MAX = 10;
|
||||||
|
|
||||||
|
public enum ConnectionStatus
|
||||||
|
{
|
||||||
|
Disconnected,
|
||||||
|
Connecting,
|
||||||
|
Connected,
|
||||||
|
}
|
||||||
|
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
private readonly Lock _syncRoot = new();
|
private readonly Lock _syncRoot = new();
|
||||||
private readonly SemaphoreSlim _championUpdateSemaphore = new(1, 1);
|
private readonly SemaphoreSlim _championUpdateSemaphore = new(1, 1);
|
||||||
@@ -36,10 +45,12 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial bool IsDisconnected { get; private set; } = false;
|
public partial bool IsDisconnected { get; private set; } = false;
|
||||||
|
|
||||||
|
[ObservableProperty, NotifyPropertyChangedFor(nameof(ReconnectButtonVisibility))]
|
||||||
|
public partial ConnectionStatus Status { get; private set; } = ConnectionStatus.Disconnected;
|
||||||
|
public Visibility ReconnectButtonVisibility => Status == ConnectionStatus.Disconnected ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial string ConnectionStatus { get; private set; } = "Not connected.";
|
public partial bool AutoAccept { get; set; } = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private Dictionary<int, ChampionData> _allChampions = [];
|
private Dictionary<int, ChampionData> _allChampions = [];
|
||||||
private List<int> _needChampionIds = [];
|
private List<int> _needChampionIds = [];
|
||||||
@@ -47,12 +58,14 @@ 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()
|
||||||
{
|
{
|
||||||
_lcuWebsocket.Connecting += (_, _) => UpdateConnectionStatus(false, "Connecting ...");
|
_lcuWebsocket.Connecting += (_, _) => UpdateConnectionStatus(false, ConnectionStatus.Connecting);
|
||||||
_lcuWebsocket.Connected += (_, _) => UpdateConnectionStatus(false, "Connected");
|
_lcuWebsocket.Connected += (_, _) => UpdateConnectionStatus(false, ConnectionStatus.Connected);
|
||||||
_lcuWebsocket.Disconnected += (_, _) => UpdateConnectionStatus(true, "Reconnect?");
|
_lcuWebsocket.Disconnected += (_, _) => UpdateConnectionStatus(true, ConnectionStatus.Disconnected);
|
||||||
|
|
||||||
_lcuWebsocket.LcuApiEvent += OnLcuApiEvent;
|
_lcuWebsocket.LcuApiEvent += OnLcuApiEvent;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -62,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)
|
||||||
@@ -74,16 +89,27 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
{
|
{
|
||||||
_allChampions = championDictionary;
|
_allChampions = championDictionary;
|
||||||
}
|
}
|
||||||
await UpdateNeedChampionIdsAsync();
|
try
|
||||||
await FillChampionLists();
|
{
|
||||||
|
await UpdateNeedChampionIdsAsync();
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
UpdateConnectionStatus(false, ConnectionStatus.Disconnected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await FillChampionLists();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateConnectionStatus(bool isConnected, string statusMessage)
|
private void UpdateConnectionStatus(bool isConnected, ConnectionStatus status)
|
||||||
{
|
{
|
||||||
Dispatcher.Invoke(() =>
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
IsDisconnected = isConnected;
|
IsDisconnected = isConnected;
|
||||||
ConnectionStatus = statusMessage;
|
Status = status;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,9 +119,20 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
{
|
{
|
||||||
case "/lol-matchmaking/v1/ready-check":
|
case "/lol-matchmaking/v1/ready-check":
|
||||||
LolMatchmakingMatchmakingReadyCheckResource? readyCheck = apiEvent.Data.Deserialize<LolMatchmakingMatchmakingReadyCheckResource>();
|
LolMatchmakingMatchmakingReadyCheckResource? readyCheck = apiEvent.Data.Deserialize<LolMatchmakingMatchmakingReadyCheckResource>();
|
||||||
if (readyCheck is not null && readyCheck.PlayerResponse == LolMatchmakingMatchmakingReadyCheckResponse.Accepted)
|
if (readyCheck is not null)
|
||||||
{
|
{
|
||||||
await UpdateNeedChampionIdsAsync();
|
if (readyCheck.PlayerResponse != LolMatchmakingMatchmakingReadyCheckResponse.Accepted)
|
||||||
|
{
|
||||||
|
if (AutoAccept)
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(Random.Shared.Next(100, 1500)));
|
||||||
|
await _client.MatchmakingAcceptAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await UpdateNeedChampionIdsAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "/lol-champ-select/v1/session":
|
case "/lol-champ-select/v1/session":
|
||||||
@@ -121,14 +158,17 @@ 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);
|
||||||
while (TeamChampions.Count < TEAM_CHAMPIONS_MAX)
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
TeamChampions.Add(GetEmptyChampionViewModel());
|
while (TeamChampions.Count < TEAM_CHAMPIONS_MAX)
|
||||||
}
|
{
|
||||||
while (BenchChampions.Count < BENCH_CHAMPIONS_MAX)
|
TeamChampions.Add(GetEmptyChampionViewModel());
|
||||||
{
|
}
|
||||||
BenchChampions.Add(GetEmptyChampionViewModel());
|
while (BenchChampions.Count < BENCH_CHAMPIONS_MAX)
|
||||||
}
|
{
|
||||||
|
BenchChampions.Add(GetEmptyChampionViewModel());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ChampionViewModel GetEmptyChampionViewModel()
|
ChampionViewModel GetEmptyChampionViewModel()
|
||||||
{
|
{
|
||||||
@@ -154,32 +194,77 @@ 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)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
viewModel.Clear();
|
Dispatcher.Invoke(() => viewModel.Clear());
|
||||||
await _aramBalanceService.EnsureIsLoadedAsync();
|
}
|
||||||
|
catch (NotSupportedException)
|
||||||
foreach (int championId in championIds)
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
ChampionData? championData = await _client.GetChampionByIdAsync(championId);
|
while (viewModel.Count > 0)
|
||||||
if (championData is null)
|
|
||||||
{
|
{
|
||||||
continue;
|
viewModel.RemoveAt(0);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await _aramBalanceService.EnsureIsLoadedAsync();
|
||||||
|
|
||||||
string imagePath = await ResourceService.GetChampionIconPathAsync(championId);
|
foreach (int championId in championIds)
|
||||||
ChampionViewModel vm = new(championData with { AramBalance = _aramBalanceService.GetAramStats(championData.Id) })
|
{
|
||||||
{
|
ChampionData? championData = await _client.GetChampionByIdAsync(championId);
|
||||||
IsNeededForChallenge = _needChampionIds.Contains(championData.Id),
|
if (championData is null || championData.Name is null)
|
||||||
ImagePath = imagePath,
|
{
|
||||||
};
|
continue;
|
||||||
viewModel.Add(vm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string imagePath = await ResourceService.GetChampionIconPathAsync(championId);
|
||||||
|
ChampionViewModel vm = new(championData with { AramBalance = _aramBalanceService.GetAramChampion(championData.Name) })
|
||||||
|
{
|
||||||
|
IsNeededForChallenge = _needChampionIds.Contains(championData.Id),
|
||||||
|
ImagePath = imagePath,
|
||||||
|
};
|
||||||
|
Dispatcher.Invoke(() => { viewModel.Add(vm); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(IsDisconnected))]
|
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)]
|
||||||
private async Task Connect()
|
private async Task Connect()
|
||||||
{
|
{
|
||||||
if (_lcuWebsocketTask is null || _lcuWebsocketTask.IsCompleted)
|
if (_lcuWebsocketTask is null || _lcuWebsocketTask.IsCompleted)
|
||||||
@@ -207,7 +292,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
|||||||
await _aramBalanceService.ReloadAsync(force: true);
|
await _aramBalanceService.ReloadAsync(force: true);
|
||||||
MessageBox.Show("Reloaded ARAM balance data.", "Info", MessageBoxButton.OK, MessageBoxImage.Exclamation);
|
MessageBox.Show("Reloaded ARAM balance data.", "Info", MessageBoxButton.OK, MessageBoxImage.Exclamation);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task Quit()
|
private async Task Quit()
|
||||||
{
|
{
|
||||||
@@ -222,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,27 +1,17 @@
|
|||||||
using System.Text.Json;
|
using System.Text;
|
||||||
using CliWrap;
|
using System.Text.Json;
|
||||||
using CliWrap.Buffered;
|
using System.Text.Json.Serialization;
|
||||||
using MoonSharp.Interpreter;
|
|
||||||
|
|
||||||
namespace LeagueAPI.ARAM;
|
namespace LeagueAPI.ARAM;
|
||||||
|
|
||||||
public record class WikiChampion(int Id, WikiChampionStats Stats);
|
public class ARAMBalanceLookup : Dictionary<string, AramChampion> { }
|
||||||
public record class WikiChampionStats(Dictionary<string, double> Aram);
|
|
||||||
|
|
||||||
public class ARAMBalanceLookup : Dictionary<int, Dictionary<string, double>> { }
|
|
||||||
|
|
||||||
public class ARAMBalanceService
|
public class ARAMBalanceService
|
||||||
{
|
{
|
||||||
private static readonly string URL = "https://wiki.leagueoflegends.com/en-us/rest.php/v1/page/Module:ChampionData%2Fdata";
|
private static readonly string ARAMONLY_URL = "https://www.aramonly.com/page-data/aram-changes/page-data.json";
|
||||||
|
|
||||||
private ARAMBalanceLookup _champions = [];
|
private ARAMBalanceLookup _champions = [];
|
||||||
|
|
||||||
static ARAMBalanceService()
|
|
||||||
{
|
|
||||||
UserData.RegisterType<WikiChampion>();
|
|
||||||
UserData.RegisterType<WikiChampionStats>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task EnsureIsLoadedAsync()
|
public async Task EnsureIsLoadedAsync()
|
||||||
{
|
{
|
||||||
if (_champions is not { Count: > 0 })
|
if (_champions is not { Count: > 0 })
|
||||||
@@ -39,71 +29,139 @@ public class ARAMBalanceService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Command curl = Cli.Wrap("curl")
|
await FetchFromAramonlyAsync();
|
||||||
.WithArguments(URL);
|
|
||||||
BufferedCommandResult result = await curl.ExecuteBufferedAsync();
|
|
||||||
|
|
||||||
string json = result.StandardOutput;
|
|
||||||
JsonDocument jsonDocument = JsonDocument.Parse(json);
|
|
||||||
string lua = jsonDocument.RootElement.GetProperty("source").GetString() ?? string.Empty;
|
|
||||||
|
|
||||||
DynValue champs = Script.RunString(lua);
|
|
||||||
if (champs.Type == DataType.Table)
|
|
||||||
{
|
|
||||||
Dictionary<string, WikiChampion> nameDictionary = [];
|
|
||||||
foreach (TablePair kv in champs.Table.Pairs)
|
|
||||||
{
|
|
||||||
if (kv.Key.Type is not DataType.String || kv.Value.Type is not DataType.Table)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
string key = kv.Key.String;
|
|
||||||
Table championTable = kv.Value.Table;
|
|
||||||
DynValue idValue = championTable.Get("id");
|
|
||||||
DynValue statsValue = championTable.Get("stats");
|
|
||||||
|
|
||||||
Dictionary<string, double> aramStats = [];
|
|
||||||
|
|
||||||
if (statsValue.Type is DataType.Table)
|
|
||||||
{
|
|
||||||
DynValue aramValue = statsValue.Table.Get("aram");
|
|
||||||
|
|
||||||
if (aramValue.Type is DataType.Table)
|
|
||||||
{
|
|
||||||
foreach (TablePair aramKv in aramValue.Table.Pairs)
|
|
||||||
{
|
|
||||||
if (aramKv.Key.Type is DataType.String && aramKv.Value.Type is DataType.Number)
|
|
||||||
{
|
|
||||||
aramStats[aramKv.Key.String] = aramKv.Value.Number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WikiChampion champ = new(idValue.Type is DataType.Number ? (int)idValue.Number : -1, new(aramStats));
|
|
||||||
nameDictionary.Add(key, champ);
|
|
||||||
}
|
|
||||||
|
|
||||||
_champions = [];
|
|
||||||
|
|
||||||
foreach (KeyValuePair<string, WikiChampion> kv in nameDictionary)
|
|
||||||
{
|
|
||||||
if (!_champions.TryGetValue(kv.Value.Id, out Dictionary<string, double>? value))
|
|
||||||
{
|
|
||||||
_champions[kv.Value.Id] = new(kv.Value.Stats.Aram);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
kv.Value.Stats.Aram.ToList().ForEach(kv => value.Add(kv.Key, kv.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ResourceService.SetARAMBalanceLookup(_champions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, double> GetAramStats(int championId)
|
private async Task<bool> FetchFromAramonlyAsync()
|
||||||
|
{
|
||||||
|
using HttpClient _client = new();
|
||||||
|
using HttpResponseMessage response = await _client.GetAsync(ARAMONLY_URL);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = await response.Content.ReadAsStringAsync();
|
||||||
|
AramonlyComResponse? result = JsonSerializer.Deserialize<AramonlyComResponse?>(json);
|
||||||
|
AramChampion?[] nodes = result?.Result?.Data?.AllAramModifiersJson?.Nodes ?? [];
|
||||||
|
if (nodes is not { Length: > 0 })
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_champions = [];
|
||||||
|
foreach (AramChampion? node in nodes)
|
||||||
|
{
|
||||||
|
if (node is null || !node.HasValue || !node.Value.Champion.HasValue)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
string name = node.Value.Champion.Value.Name ?? string.Empty;
|
||||||
|
if (name == string.Empty)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_champions.Add(name, node.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceService.SetARAMBalanceLookup(_champions);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AramChampion? GetAramChampion(string championName)
|
||||||
{
|
{
|
||||||
EnsureIsLoadedAsync().Wait();
|
EnsureIsLoadedAsync().Wait();
|
||||||
return _champions.TryGetValue(championId, out Dictionary<string, double>? stats) ? stats : [];
|
return _champions.TryGetValue(championName, out AramChampion aramChampion) ? aramChampion : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record struct AramonlyComResponse([property: JsonPropertyName("result")] AramonlyComResult? Result);
|
||||||
|
public record struct AramonlyComResult([property: JsonPropertyName("data")] AramonlyComData? Data);
|
||||||
|
|
||||||
|
public record struct AramonlyComData(
|
||||||
|
[property: JsonPropertyName("allAramModifiersJson")] AramonlyComModifiersJson? AllAramModifiersJson,
|
||||||
|
[property: JsonPropertyName("patchVersionJson")] PatchVersionJson? PatchVersionJson
|
||||||
|
);
|
||||||
|
|
||||||
|
public record struct AramonlyComModifiersJson([property: JsonPropertyName("nodes")] AramChampion?[]? Nodes);
|
||||||
|
|
||||||
|
public record struct AramChampion(
|
||||||
|
[property: JsonPropertyName("timestamp")] DateTime? Timestamp,
|
||||||
|
[property: JsonPropertyName("champion")] Champion? Champion,
|
||||||
|
[property: JsonPropertyName("aramDamageDealt")] double? AramDamageDealt,
|
||||||
|
[property: JsonPropertyName("aramDamageTaken")] double? AramDamageTaken,
|
||||||
|
[property: JsonPropertyName("aramHealing")] double? AramHealing,
|
||||||
|
[property: JsonPropertyName("aramShielding")] double? AramShielding,
|
||||||
|
[property: JsonPropertyName("aramTenacity")] double? AramTenacity,
|
||||||
|
[property: JsonPropertyName("aramAbilityHaste")] int? AramAbilityHaste,
|
||||||
|
[property: JsonPropertyName("aramAttackSpeed")] double? AramAttackSpeed,
|
||||||
|
[property: JsonPropertyName("aramEnergyRegen")] double? AramEnergyRegen
|
||||||
|
)
|
||||||
|
{
|
||||||
|
public readonly void ToDisplayString(StringBuilder sb)
|
||||||
|
{
|
||||||
|
// Damage Dealt
|
||||||
|
if (AramDamageDealt is not null and not 0 and not 1)
|
||||||
|
{
|
||||||
|
double value = (double)AramDamageDealt;
|
||||||
|
sb.AppendLine($"Dmg Dealt: {value - 1:+#0%;-#0%}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Damage Taken
|
||||||
|
if (AramDamageTaken is not null and not 0 and not 1)
|
||||||
|
{
|
||||||
|
double value = (double)AramDamageTaken;
|
||||||
|
sb.AppendLine($"Dmg Taken: {value - 1:+#0%;-#0%}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Healing
|
||||||
|
if (AramHealing is not null and not 0 and not 1)
|
||||||
|
{
|
||||||
|
double value = (double)AramHealing;
|
||||||
|
sb.AppendLine($"Healing: {value - 1:+#0%;-#0%}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shielding
|
||||||
|
if (AramShielding is not null and not 0 and not 1)
|
||||||
|
{
|
||||||
|
double value = (double)AramShielding;
|
||||||
|
sb.AppendLine($"Shielding: {value - 1:+#0%;-#0%}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tenacity
|
||||||
|
if (AramTenacity is not null and not 0 and not 1)
|
||||||
|
{
|
||||||
|
double value = (double)AramTenacity;
|
||||||
|
sb.AppendLine($"Tenacity: {value - 1:+#0%;-#0%}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ability Haste (Raw value)
|
||||||
|
if (AramAbilityHaste is not null and not 0 and not 1)
|
||||||
|
{
|
||||||
|
double value = (double)AramAbilityHaste;
|
||||||
|
sb.AppendLine($"Ability Haste: {value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total AS (Raw value)
|
||||||
|
if (AramAttackSpeed is not null and not 0 and not 1)
|
||||||
|
{
|
||||||
|
double value = (double)AramAttackSpeed;
|
||||||
|
sb.AppendLine($"Total AS: {value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Energy Regen (Percentage)
|
||||||
|
if (AramEnergyRegen is not null and not 0 and not 1)
|
||||||
|
{
|
||||||
|
double value = (double)AramEnergyRegen;
|
||||||
|
sb.AppendLine($"Energy Regen: {value - 1:+#0%;-#0%}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record struct Champion(
|
||||||
|
[property: JsonPropertyName("name")] string? Name,
|
||||||
|
[property: JsonPropertyName("sanitizedName")] string? SanitizedName
|
||||||
|
);
|
||||||
|
|
||||||
|
public record struct PatchVersionJson(
|
||||||
|
[property: JsonPropertyName("patchVersion")] string? PatchVersion
|
||||||
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,12 +49,15 @@ public class LcuWebsocket : IDisposable
|
|||||||
{
|
{
|
||||||
ProcessInfo = ProcessFinder.GetProcessInfo();
|
ProcessInfo = ProcessFinder.GetProcessInfo();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(500));
|
||||||
|
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!ProcessFinder.IsPortOpen(ProcessInfo))
|
if (!ProcessFinder.IsPortOpen(ProcessInfo))
|
||||||
{
|
{
|
||||||
|
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||||
throw new InvalidOperationException("Failed to connect to LCUx process port.");
|
throw new InvalidOperationException("Failed to connect to LCUx process port.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +70,6 @@ public class LcuWebsocket : IDisposable
|
|||||||
{
|
{
|
||||||
await _socket.ConnectAsync(uri, CancellationToken.None);
|
await _socket.ConnectAsync(uri, CancellationToken.None);
|
||||||
|
|
||||||
Connected?.Invoke(this, EventArgs.Empty);
|
|
||||||
|
|
||||||
foreach (string eventName in SUBSCRIBE_EVENTS)
|
foreach (string eventName in SUBSCRIBE_EVENTS)
|
||||||
{
|
{
|
||||||
string message = $"[{OPCODE_SUBSCRIBE}, \"{eventName}\"]";
|
string message = $"[{OPCODE_SUBSCRIBE}, \"{eventName}\"]";
|
||||||
@@ -77,6 +78,8 @@ public class LcuWebsocket : IDisposable
|
|||||||
await _socket.SendAsync(memory, WebSocketMessageType.Text, true, CancellationToken.None);
|
await _socket.SendAsync(memory, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connected?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
while (_socket.State is WebSocketState.Open)
|
while (_socket.State is WebSocketState.Open)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CliWrap" Version="3.10.0" />
|
<PackageReference Include="CliWrap" Version="3.10.1" />
|
||||||
<PackageReference Include="MoonSharp" Version="2.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using LeagueAPI.ARAM;
|
||||||
|
|
||||||
namespace LeagueAPI.Models.DDragon.Champions;
|
namespace LeagueAPI.Models.DDragon.Champions;
|
||||||
|
|
||||||
@@ -41,5 +42,5 @@ public record ChampionData
|
|||||||
public ChampionDataStats? Stats { get; init; }
|
public ChampionDataStats? Stats { get; init; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Dictionary<string, double> AramBalance { get; set; } = [];
|
public AramChampion? AramBalance { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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