Compare commits

..

2 Commits

Author SHA1 Message Date
SacredFloof 61eee88078 ARAM balance data from aramonly.com 2026-04-29 01:21:18 +02:00
SacredFloof 076c0a9a8d accept matches
TODO: make it a button
2026-04-29 01:20:37 +02:00
6 changed files with 160 additions and 124 deletions
+2
View File
@@ -10,6 +10,7 @@
Height="480" Height="480"
Width="700" Width="700"
Background="#0a1a2a" Background="#0a1a2a"
Topmost="True"
> >
<Window.DataContext> <Window.DataContext>
<vm:MainViewModel /> <vm:MainViewModel />
@@ -60,6 +61,7 @@
<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 />
+2 -39
View File
@@ -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();
} }
+18 -6
View File
@@ -39,8 +39,9 @@ public partial class MainViewModel : ObservableObject, IDisposable
[ObservableProperty] [ObservableProperty]
public partial string ConnectionStatus { get; private set; } = "Not connected."; public partial string ConnectionStatus { get; private set; } = "Not connected.";
[ObservableProperty]
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 = [];
private readonly APIClient _client = new(); private readonly APIClient _client = new();
@@ -93,9 +94,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":
@@ -163,13 +175,13 @@ public partial class MainViewModel : ObservableObject, IDisposable
foreach (int championId in championIds) foreach (int championId in championIds)
{ {
ChampionData? championData = await _client.GetChampionByIdAsync(championId); ChampionData? championData = await _client.GetChampionByIdAsync(championId);
if (championData is null) if (championData is null || championData.Name is null)
{ {
continue; continue;
} }
string imagePath = await ResourceService.GetChampionIconPathAsync(championId); string imagePath = await ResourceService.GetChampionIconPathAsync(championId);
ChampionViewModel vm = new(championData with { AramBalance = _aramBalanceService.GetAramStats(championData.Id) }) ChampionViewModel vm = new(championData with { AramBalance = _aramBalanceService.GetAramChampion(championData.Name) })
{ {
IsNeededForChallenge = _needChampionIds.Contains(championData.Id), IsNeededForChallenge = _needChampionIds.Contains(championData.Id),
ImagePath = imagePath, ImagePath = imagePath,
+135 -77
View File
@@ -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 FetchFromAramonly();
.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> FetchFromAramonly()
{
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 -1
View File
@@ -49,7 +49,7 @@ public class LcuWebsocket : IDisposable
{ {
ProcessInfo = ProcessFinder.GetProcessInfo(); ProcessInfo = ProcessFinder.GetProcessInfo();
} }
catch (Exception ex) catch
{ {
return; return;
} }
@@ -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; }
} }