Compare commits
3 Commits
61eee88078
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
9f6105b970
|
|||
|
06d5113711
|
|||
|
b6a4903847
|
@@ -15,7 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
xmlns:vm="clr-namespace:ARAMUtility.ViewModel"
|
||||
mc:Ignorable="d"
|
||||
Title="{Binding Title}"
|
||||
Height="480"
|
||||
Height="500"
|
||||
Width="700"
|
||||
Background="#0a1a2a"
|
||||
Topmost="True"
|
||||
@@ -67,27 +67,11 @@
|
||||
<Separator />
|
||||
<MenuItem Header="Quit" Command="{Binding QuitCommand}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{Binding ConnectionStatus}" Command="{Binding ConnectCommand}" />
|
||||
<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>
|
||||
<MenuItem Header="Reconnect" Command="{Binding ConnectCommand}" Visibility="{Binding ReconnectButtonVisibility, Mode=OneWay}" />
|
||||
</Menu>
|
||||
<StatusBar DockPanel.Dock="Bottom">
|
||||
<StatusBarItem Content="{Binding Status}" />
|
||||
</StatusBar>
|
||||
<Viewbox Stretch="Uniform" StretchDirection="DownOnly">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
@@ -11,6 +11,10 @@ public partial class MainWindow : Window
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
#if DEBUG
|
||||
Topmost = false;
|
||||
#endif
|
||||
|
||||
if (DataContext is MainViewModel viewModel)
|
||||
{
|
||||
Loaded += viewModel.OnInit;
|
||||
|
||||
@@ -9,7 +9,9 @@ using LeagueAPI;
|
||||
using LeagueAPI.ARAM;
|
||||
using LeagueAPI.Models.ChampSelect;
|
||||
using LeagueAPI.Models.DDragon.Champions;
|
||||
using LeagueAPI.Models.GameClient;
|
||||
using LeagueAPI.Models.ReadyCheck;
|
||||
using LeagueAPI.Utils;
|
||||
|
||||
namespace ARAMUtility.ViewModel;
|
||||
|
||||
@@ -18,6 +20,13 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
||||
private const int TEAM_CHAMPIONS_MAX = 5;
|
||||
private const int BENCH_CHAMPIONS_MAX = 10;
|
||||
|
||||
public enum ConnectionStatus
|
||||
{
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected,
|
||||
}
|
||||
|
||||
private bool _isDisposed;
|
||||
private readonly Lock _syncRoot = new();
|
||||
private readonly SemaphoreSlim _championUpdateSemaphore = new(1, 1);
|
||||
@@ -36,24 +45,27 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
||||
[ObservableProperty]
|
||||
public partial bool IsDisconnected { get; private set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string ConnectionStatus { get; private set; } = "Not connected.";
|
||||
[ObservableProperty, NotifyPropertyChangedFor(nameof(ReconnectButtonVisibility))]
|
||||
public partial ConnectionStatus Status { get; private set; } = ConnectionStatus.Disconnected;
|
||||
public Visibility ReconnectButtonVisibility => Status == ConnectionStatus.Disconnected ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool AutoAccept { get; set; } = true;
|
||||
|
||||
|
||||
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();
|
||||
private readonly ProcessWatcher _leagueWatcher = new();
|
||||
private readonly CachingHttpClient _localCachingClient = new(insecure: true);
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
_lcuWebsocket.Connecting += (_, _) => UpdateConnectionStatus(false, "Connecting ...");
|
||||
_lcuWebsocket.Connected += (_, _) => UpdateConnectionStatus(false, "Connected");
|
||||
_lcuWebsocket.Disconnected += (_, _) => UpdateConnectionStatus(true, "Reconnect?");
|
||||
_lcuWebsocket.Connecting += (_, _) => UpdateConnectionStatus(false, ConnectionStatus.Connecting);
|
||||
_lcuWebsocket.Connected += (_, _) => UpdateConnectionStatus(false, ConnectionStatus.Connected);
|
||||
_lcuWebsocket.Disconnected += (_, _) => UpdateConnectionStatus(true, ConnectionStatus.Disconnected);
|
||||
|
||||
_lcuWebsocket.LcuApiEvent += OnLcuApiEvent;
|
||||
#if DEBUG
|
||||
@@ -63,6 +75,8 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
||||
};
|
||||
#endif
|
||||
_lcuWebsocketTask = _lcuWebsocket.Connect();
|
||||
|
||||
_leagueWatcher.LeagueOfLegendsExeFound += UpdateChampionListsFromGame;
|
||||
}
|
||||
|
||||
internal async void OnInit(object? sender, RoutedEventArgs e)
|
||||
@@ -75,16 +89,27 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
_allChampions = championDictionary;
|
||||
}
|
||||
await UpdateNeedChampionIdsAsync();
|
||||
await FillChampionLists();
|
||||
try
|
||||
{
|
||||
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(() =>
|
||||
{
|
||||
IsDisconnected = isConnected;
|
||||
ConnectionStatus = statusMessage;
|
||||
Status = status;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -133,14 +158,17 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
||||
private async Task FillChampionLists()
|
||||
{
|
||||
string defaultImagePath = await ResourceService.GetChampionIconPathAsync(-1);
|
||||
while (TeamChampions.Count < TEAM_CHAMPIONS_MAX)
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
TeamChampions.Add(GetEmptyChampionViewModel());
|
||||
}
|
||||
while (BenchChampions.Count < BENCH_CHAMPIONS_MAX)
|
||||
{
|
||||
BenchChampions.Add(GetEmptyChampionViewModel());
|
||||
}
|
||||
while (TeamChampions.Count < TEAM_CHAMPIONS_MAX)
|
||||
{
|
||||
TeamChampions.Add(GetEmptyChampionViewModel());
|
||||
}
|
||||
while (BenchChampions.Count < BENCH_CHAMPIONS_MAX)
|
||||
{
|
||||
BenchChampions.Add(GetEmptyChampionViewModel());
|
||||
}
|
||||
});
|
||||
|
||||
ChampionViewModel GetEmptyChampionViewModel()
|
||||
{
|
||||
@@ -166,32 +194,77 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
||||
|
||||
await FillChampionLists();
|
||||
_championUpdateSemaphore.Release();
|
||||
}
|
||||
|
||||
async Task UpdateChampions(ObservableCollection<ChampionViewModel> viewModel, IEnumerable<int> championIds)
|
||||
private async Task UpdateChampions(ObservableCollection<ChampionViewModel> viewModel, IEnumerable<int> championIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
viewModel.Clear();
|
||||
await _aramBalanceService.EnsureIsLoadedAsync();
|
||||
|
||||
foreach (int championId in championIds)
|
||||
Dispatcher.Invoke(() => viewModel.Clear());
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ChampionData? championData = await _client.GetChampionByIdAsync(championId);
|
||||
if (championData is null || championData.Name is null)
|
||||
while (viewModel.Count > 0)
|
||||
{
|
||||
continue;
|
||||
viewModel.RemoveAt(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
await _aramBalanceService.EnsureIsLoadedAsync();
|
||||
|
||||
string imagePath = await ResourceService.GetChampionIconPathAsync(championId);
|
||||
ChampionViewModel vm = new(championData with { AramBalance = _aramBalanceService.GetAramChampion(championData.Name) })
|
||||
{
|
||||
IsNeededForChallenge = _needChampionIds.Contains(championData.Id),
|
||||
ImagePath = imagePath,
|
||||
};
|
||||
viewModel.Add(vm);
|
||||
foreach (int championId in championIds)
|
||||
{
|
||||
ChampionData? championData = await _client.GetChampionByIdAsync(championId);
|
||||
if (championData is null || championData.Name is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (_lcuWebsocketTask is null || _lcuWebsocketTask.IsCompleted)
|
||||
@@ -219,7 +292,7 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
||||
await _aramBalanceService.ReloadAsync(force: true);
|
||||
MessageBox.Show("Reloaded ARAM balance data.", "Info", MessageBoxButton.OK, MessageBoxImage.Exclamation);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Quit()
|
||||
{
|
||||
@@ -234,8 +307,9 @@ public partial class MainViewModel : ObservableObject, IDisposable
|
||||
if (disposing)
|
||||
{
|
||||
_allChampions = [];
|
||||
_client.Dispose();
|
||||
_lcuWebsocket.Dispose();
|
||||
_client?.Dispose();
|
||||
_lcuWebsocket?.Dispose();
|
||||
_localCachingClient?.Dispose();
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
+17
-3
@@ -24,6 +24,8 @@ public class APIClient : IDisposable
|
||||
|
||||
private readonly Dictionary<string, ChampionResponse> _championResponseCache = [];
|
||||
|
||||
private readonly CachingHttpClient _ddragonClient = new();
|
||||
|
||||
public APIClient()
|
||||
{
|
||||
_lcuHttpClient = new(new LcuHttpClientHandler());
|
||||
@@ -81,12 +83,12 @@ public class APIClient : IDisposable
|
||||
}
|
||||
|
||||
#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);
|
||||
return JsonSerializer.Deserialize<T>(json);
|
||||
@@ -135,6 +137,17 @@ public class APIClient : IDisposable
|
||||
|
||||
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
|
||||
|
||||
#region IDisposable
|
||||
@@ -145,6 +158,7 @@ public class APIClient : IDisposable
|
||||
if (disposing)
|
||||
{
|
||||
_lcuHttpClient?.Dispose();
|
||||
_ddragonClient?.Dispose();
|
||||
}
|
||||
_championResponseCache?.Clear();
|
||||
|
||||
|
||||
@@ -29,10 +29,10 @@ public class ARAMBalanceService
|
||||
return;
|
||||
}
|
||||
|
||||
await FetchFromAramonly();
|
||||
await FetchFromAramonlyAsync();
|
||||
}
|
||||
|
||||
private async Task<bool> FetchFromAramonly()
|
||||
private async Task<bool> FetchFromAramonlyAsync()
|
||||
{
|
||||
using HttpClient _client = new();
|
||||
using HttpResponseMessage response = await _client.GetAsync(ARAMONLY_URL);
|
||||
|
||||
@@ -1,18 +1,77 @@
|
||||
namespace LeagueAPI;
|
||||
|
||||
public class HttpCache : Dictionary<string, string> { }
|
||||
|
||||
public static class CachingHttpClient
|
||||
public class HttpCache : Dictionary<string, InvalidatableCacheObject<string>> { }
|
||||
public record struct InvalidatableCacheObject<T>(
|
||||
T Value,
|
||||
DateTime? InvalidateTime = null
|
||||
)
|
||||
{
|
||||
private static HttpCache _cache = ResourceService.GetHttpCache() ?? [];
|
||||
private static HttpClient _client = new();
|
||||
|
||||
public static async Task<string> GetStringAsync(string requestUri)
|
||||
public readonly bool IsValid()
|
||||
{
|
||||
if (_cache.TryGetValue(requestUri, out string? response))
|
||||
{
|
||||
return response;
|
||||
}
|
||||
return await _client.GetStringAsync(requestUri);
|
||||
return !InvalidateTime.HasValue || InvalidateTime < DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -51,10 +51,13 @@ public class LcuWebsocket : IDisposable
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(500));
|
||||
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
return;
|
||||
}
|
||||
if (!ProcessFinder.IsPortOpen(ProcessInfo))
|
||||
{
|
||||
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
throw new InvalidOperationException("Failed to connect to LCUx process port.");
|
||||
}
|
||||
|
||||
@@ -67,8 +70,6 @@ public class LcuWebsocket : IDisposable
|
||||
{
|
||||
await _socket.ConnectAsync(uri, CancellationToken.None);
|
||||
|
||||
Connected?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
foreach (string eventName in SUBSCRIBE_EVENTS)
|
||||
{
|
||||
string message = $"[{OPCODE_SUBSCRIBE}, \"{eventName}\"]";
|
||||
@@ -77,6 +78,8 @@ public class LcuWebsocket : IDisposable
|
||||
await _socket.SendAsync(memory, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
}
|
||||
|
||||
Connected?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
while (_socket.State is WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliWrap" Version="3.10.0" />
|
||||
<PackageReference Include="MoonSharp" Version="2.0.0" />
|
||||
<PackageReference Include="CliWrap" Version="3.10.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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