Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Pythonic-Rainbow authored Mar 12, 2024
2 parents 02aaaf5 + e231b84 commit b05a159
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 162 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/bot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup .NET
id: setup-dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
dotnet-version: 8.0.101
- name: Restore
run: dotnet restore
- name: Inspect
Expand All @@ -50,4 +51,5 @@ jobs:
workingDirectory: './Bot'
solutionPath: './Bot.sln'
noBuild: true
minimumSeverity: 'warning'
minimumSeverity: 'warning'
dotnetVersion: ${{ steps.setup-dotnet.outputs.dotnet-version }}
173 changes: 130 additions & 43 deletions Bot/Clash/Coc.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,36 @@
using System.Diagnostics.CodeAnalysis;
using ClashOfClans;
using ClashOfClans.Core;
using ClashOfClans.Models;
using ClashOfClans.Search;
using Hyperstellar.Discord;
using Hyperstellar.Sql;

namespace Hyperstellar.Clash;

internal static class Coc
{
private class RaidAttackerComparer : IEqualityComparer<ClanCapitalRaidSeasonAttacker>

Check warning on line 13 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[CA1852] Type 'RaidAttackerComparer' can be sealed because it has no subtypes in its containing assembly and is not externally visible" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(13,268)
{
public bool Equals(ClanCapitalRaidSeasonAttacker? x, ClanCapitalRaidSeasonAttacker? y) => x!.Tag == y!.Tag;
public int GetHashCode([DisallowNull] ClanCapitalRaidSeasonAttacker obj) => obj.Tag.GetHashCode();

Check warning on line 16 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[RedundantNullableFlowAttribute] The nullability attribute has no effect and can be safely removed" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(16,494)
}

private const string ClanId = "#2QU2UCJJC"; // 2G8LP8PVV
private static readonly ClashOfClansClient s_client = new(Secrets.s_coc);
private static bool s_inMaintenance;
private static ClashOfClansException? s_exception;
private static ClanCapitalRaidSeason s_raidSeason;
internal static ClanUtil Clan { get; private set; } = new();
internal static event Action<ClanMember>? s_eventMemberJoined;
internal static event Action<ClanMember, string?>? s_eventMemberLeft;
internal static event Func<Dictionary<string, DonationTuple>, Task>? EventDonated;
internal static event Func<Dictionary<string, DonationTuple>, Task>? EventDonatedFold;
internal static event Action<ClanMember, Main> EventMemberJoined;
internal static event Action<ClanMember, string?> EventMemberLeft;
internal static event Action<ClanCapitalRaidSeason> EventInitRaid;
internal static event Action<ClanCapitalRaidSeason> EventRaidCompleted;
internal static event Func<Dictionary<string, DonationTuple>, Task> EventDonated;
internal static event Func<Dictionary<string, DonationTuple>, Task> EventDonatedFold;

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
static Coc() => Dc.EventBotReady += BotReadyAsync;

private static async Task BotReadyAsync()
{
while (true)
{
try
{
await PollAsync();
s_inMaintenance = false;
await Task.Delay(10000);
}
catch (ClashOfClansException ex)
{
if (ex.Error.Reason == "inMaintenance")
{
if (!s_inMaintenance)
{
s_inMaintenance = true;
await Dc.SendLogAsync(ex.Error.Message);
}
await Task.Delay(60000);
}
else
{
await Dc.ExceptionAsync(ex);
}
}
catch (Exception ex)
{
await Dc.ExceptionAsync(ex);
}
}
}
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

private static void CheckMembersJoined(ClanUtil clan)
{
Expand All @@ -66,7 +46,9 @@ private static void CheckMembersJoined(ClanUtil clan)

foreach (ClanMember m in clan._joiningMembers.Values)
{
s_eventMemberJoined!(m);
Main main = new(m.Tag);
EventMemberJoined!(m, main);

Check warning on line 50 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[RedundantSuppressNullableWarningExpression] The nullable warning suppression expression is redundant" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(50,2179)
main.Insert();
}
}

Expand All @@ -81,6 +63,8 @@ private static void CheckMembersLeft(ClanUtil clan)
{
IEnumerable<Alt> alts = new Member(id).GetAltsByMain();
string? altId = null;
Main main = Db.GetMain(id)!;
main.Delete();
if (alts.Any())
{
Alt alt = alts.First();
Expand All @@ -90,8 +74,13 @@ private static void CheckMembersLeft(ClanUtil clan)
alts.ElementAt(i).UpdateMain(alt.AltId);
}
alt.Delete();
// Maybe adapt this in the future if need to modify attributes when replacing main
main.MainId = altId;
main.Insert();
}
s_eventMemberLeft!(member, altId); // This is before Db.DelMem below so that we can remap Donation to new mainId
// This is before Db.DelMem below so that we can remap Donation to new mainId
// ^ No longer true because the remap is done ABOVE now but I'll still leave this comment
EventMemberLeft!(member, altId);

Check warning on line 83 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[RedundantSuppressNullableWarningExpression] The nullable warning suppression expression is redundant" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(83,3335)
}

string[] members = [.. clan._leavingMembers.Keys];
Expand All @@ -100,6 +89,33 @@ private static void CheckMembersLeft(ClanUtil clan)
Console.WriteLine($"{membersMsg} left");
}

private static async Task BotReadyAsync()
{
while (true)
{
try
{
await PollAsync();
s_exception = null;
await Task.Delay(10000);
}
catch (ClashOfClansException ex)
{
if (s_exception == null || s_exception.Error.Reason != ex.Error.Reason || s_exception.Error.Message != ex.Error.Message)
{
s_exception = ex;
await Dc.ExceptionAsync(ex);
}
await Task.Delay(60000);
}
catch (Exception ex)
{
await Dc.ExceptionAsync(ex);
await Task.Delay(60000);
}
}
}

private static async Task<Clan> GetClanAsync() => await s_client.Clans.GetClanAsync(ClanId);

private static async Task PollAsync()
Expand All @@ -112,6 +128,7 @@ private static async Task PollAsync()
}

ClanUtil clanUtil = ClanUtil.FromPoll(clan);

CheckMembersJoined(clanUtil);
CheckMembersLeft(clanUtil);
await Task.WhenAll([
Expand All @@ -120,6 +137,37 @@ await Task.WhenAll([
Clan = clanUtil;
}

private static async Task PollRaidAsync()
{
static async Task WaitRaidAsync()

Check notice on line 142 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[MoveLocalFunctionAfterJumpStatement] Put local function 'WaitRaidAsync' after 'return'" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(142,4935)
{
await Task.Delay(s_raidSeason.EndTime - DateTime.UtcNow);
s_raidSeason = await GetRaidSeasonAsync();
while (s_raidSeason.State != ClanCapitalRaidSeasonState.Ended)
{
await Task.Delay(20000);
s_raidSeason = await GetRaidSeasonAsync();
}
EventRaidCompleted(s_raidSeason);
}

// Check if there is an ongoing raid
if (s_raidSeason.EndTime > DateTime.UtcNow)
{
await WaitRaidAsync();
}
while (true)
{
await Task.Delay(60 * 60 * 1000); // 1 hour
ClanCapitalRaidSeason season = await GetRaidSeasonAsync();
if (season.StartTime != s_raidSeason.StartTime) // New season started
{
s_raidSeason = season;
await WaitRaidAsync();
}
}
}

private static async Task CheckDonationsAsync(ClanUtil clan)
{
Dictionary<string, DonationTuple> donDelta = [];
Expand Down Expand Up @@ -165,11 +213,11 @@ private static async Task CheckDonationsAsync(ClanUtil clan)
ICollection<Task> tasks = [];
if (donDelta.Count > 0)
{
tasks.Add(EventDonated!(donDelta));
tasks.Add(EventDonated(donDelta));
}
if (foldedDelta.Count > 0)
{
tasks.Add(EventDonatedFold!(foldedDelta));
tasks.Add(EventDonatedFold(foldedDelta));
}
await Task.WhenAll(tasks);
}
Expand All @@ -182,5 +230,44 @@ private static async Task CheckDonationsAsync(ClanUtil clan)

internal static ClanMember GetMember(string id) => Clan._members[id];

internal static async Task InitAsync() => Clan = ClanUtil.FromInit(await GetClanAsync());
internal static HashSet<ClanCapitalRaidSeasonAttacker> GetRaidAttackers(ClanCapitalRaidSeason season)
{
HashSet<ClanCapitalRaidSeasonAttacker> set = new(new RaidAttackerComparer());
foreach (ClanCapitalRaidSeasonAttackLogEntry capital in season.AttackLog)

Check notice on line 236 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator] Part of loop's body can be converted into LINQ-expression but another 'GetEnumerator' method will be used" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(236,8177)
{
foreach (ClanCapitalRaidSeasonDistrict district in capital.Districts)
{
if (district.Attacks != null)
{
foreach (ClanCapitalRaidSeasonAttack atk in district.Attacks)
{
set.Add(atk.Attacker);
}
}
}
}
return set;
}

internal static async Task<ClanCapitalRaidSeason> GetRaidSeasonAsync()

Check notice on line 252 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[MemberCanBePrivate.Global] Method 'GetRaidSeasonAsync' can be made private" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(252,8667)
{
Query query = new() { Limit = 1 };
ClanCapitalRaidSeasons seasons = (ClanCapitalRaidSeasons)await s_client.Clans.GetCapitalRaidSeasonsAsync(ClanId, query);
return seasons.First();
}

internal static async Task InitAsync()
{
static async Task InitClanAsync() { Clan = ClanUtil.FromInit(await GetClanAsync()); }

Check warning on line 261 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[IDE0061] Use expression body for local function" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(261,9012)

Check notice on line 261 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[MoveLocalFunctionAfterJumpStatement] Put local function 'InitClanAsync' after 'return'" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(261,9030)
static async Task InitRaidAsync()

Check notice on line 262 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[MoveLocalFunctionAfterJumpStatement] Put local function 'InitRaidAsync' after 'return'" on /home/runner/work/coc-clan/coc-clan/Bot/Clash/Coc.cs(262,9124)
{
ClanCapitalRaidSeason season = await GetRaidSeasonAsync();
// If last raid happened within a week, we count it as valid
EventInitRaid(season);
s_raidSeason = season;
_ = Task.Run(PollRaidAsync);
}

await Task.WhenAll([InitClanAsync(), InitRaidAsync()]);
}
}
Loading

0 comments on commit b05a159

Please sign in to comment.