From 961a42a8c304b643c0eac9e7866b8787a579ee64 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Tue, 26 Jul 2016 14:10:19 +0200 Subject: [PATCH] clash of clans saving started, needs more work --- .../Modules/ClashOfClans/ClashOfClans.cs | 116 +++++----- .../ClashOfClans/ClashOfClansModule.cs | 216 +++++++++++++----- 2 files changed, 212 insertions(+), 120 deletions(-) diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs b/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs index 668afd73..b3a59feb 100644 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs +++ b/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs @@ -4,6 +4,9 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Collections.Generic; +using Newtonsoft.Json; +//using Manatee.Json.Serialization; namespace NadekoBot.Classes.ClashOfClans { @@ -11,13 +14,14 @@ namespace NadekoBot.Classes.ClashOfClans { One, Two, Three } + [System.Serializable] internal class Caller { public string CallUser { get; } - public DateTime TimeAdded { get; private set; } + public DateTime TimeAdded { get; set; } - public bool BaseDestroyed { get; internal set; } + public bool BaseDestroyed { get; set; } public int Stars { get; set; } = 3; @@ -47,94 +51,76 @@ namespace NadekoBot.Classes.ClashOfClans public string EnemyClan { get; } public int Size { get; } - private Caller[] bases { get; } - private CancellationTokenSource[] baseCancelTokens; - private CancellationTokenSource endTokenSource { get; } = new CancellationTokenSource(); - public event Action OnUserTimeExpired = delegate { }; - public event Action OnWarEnded = delegate { }; + public Caller[] Bases { get; } public bool Started { get; set; } = false; + public DateTime StartedAt { get; private set; } + public bool Ended { get; private set; } = false; - public ClashWar(string enemyClan, int size, CommandEventArgs e) + public ulong ServerId { get; set; } + public ulong ChannelId { get; set; } + + [JsonIgnore] + public Discord.Channel Channel { get; internal set; } + + /// + /// This init is purely for the deserialization + /// + public ClashWar() { } + + public ClashWar(string enemyClan, int size, ulong serverId, ulong channelId) { this.EnemyClan = enemyClan; this.Size = size; - this.bases = new Caller[size]; - this.baseCancelTokens = new CancellationTokenSource[size]; + this.Bases = new Caller[size]; + this.ServerId = serverId; + this.ChannelId = channelId; + this.Channel = NadekoBot.Client.Servers.FirstOrDefault(s => s.Id == serverId)?.TextChannels.FirstOrDefault(c => c.Id == channelId); } internal void End() { - if (endTokenSource.Token.IsCancellationRequested) return; - endTokenSource.Cancel(); - OnWarEnded(); + Ended = true; } internal void Call(string u, int baseNumber) { - if (baseNumber < 0 || baseNumber >= bases.Length) + if (baseNumber < 0 || baseNumber >= Bases.Length) throw new ArgumentException("Invalid base number"); - if (bases[baseNumber] != null) + if (Bases[baseNumber] != null) throw new ArgumentException("That base is already claimed."); - for (var i = 0; i < bases.Length; i++) + for (var i = 0; i < Bases.Length; i++) { - if (bases[i]?.BaseDestroyed == false && bases[i]?.CallUser == u) - throw new ArgumentException($"@{u} You already claimed a base #{i + 1}. You can't claim a new one."); + if (Bases[i]?.BaseDestroyed == false && Bases[i]?.CallUser == u) + throw new ArgumentException($"@{u} You already claimed base #{i + 1}. You can't claim a new one."); } - bases[baseNumber] = new Caller(u.Trim(), DateTime.Now, false); + Bases[baseNumber] = new Caller(u.Trim(), DateTime.Now, false); } - internal async Task Start() + internal void Start() { if (Started) throw new InvalidOperationException(); - try + Started = true; + StartedAt = DateTime.Now; + foreach (var b in Bases.Where(b => b != null)) { - Started = true; - foreach (var b in bases.Where(b => b != null)) - { - b.ResetTime(); - } -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(async () => await ClearArray()).ConfigureAwait(false); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - await Task.Delay(new TimeSpan(24, 0, 0), endTokenSource.Token).ConfigureAwait(false); - } - catch { } - finally - { - End(); + b.ResetTime(); } } + internal int Uncall(string user) { user = user.Trim(); - for (var i = 0; i < bases.Length; i++) + for (var i = 0; i < Bases.Length; i++) { - if (bases[i]?.CallUser != user) continue; - bases[i] = null; + if (Bases[i]?.CallUser != user) continue; + Bases[i] = null; return i; } throw new InvalidOperationException("You are not participating in that war."); } - private async Task ClearArray() - { - while (!endTokenSource.IsCancellationRequested) - { - await Task.Delay(5000).ConfigureAwait(false); - for (var i = 0; i < bases.Length; i++) - { - if (bases[i] == null) continue; - if (!bases[i].BaseDestroyed && DateTime.Now - bases[i].TimeAdded >= callExpire) - { - OnUserTimeExpired(bases[i].CallUser); - bases[i] = null; - } - } - } - } - public string ShortPrint() => $"`{EnemyClan}` ({Size} v {Size})"; @@ -145,22 +131,22 @@ namespace NadekoBot.Classes.ClashOfClans sb.AppendLine($"🔰**WAR AGAINST `{EnemyClan}` ({Size} v {Size}) INFO:**"); if (!Started) sb.AppendLine("`not started`"); - for (var i = 0; i < bases.Length; i++) + for (var i = 0; i < Bases.Length; i++) { - if (bases[i] == null) + if (Bases[i] == null) { sb.AppendLine($"`{i + 1}.` ❌*unclaimed*"); } else { - if (bases[i].BaseDestroyed) + if (Bases[i].BaseDestroyed) { - sb.AppendLine($"`{i + 1}.` ✅ `{bases[i].CallUser}` {new string('⭐', bases[i].Stars)}"); + sb.AppendLine($"`{i + 1}.` ✅ `{Bases[i].CallUser}` {new string('⭐', Bases[i].Stars)}"); } else { - var left = Started ? callExpire - (DateTime.Now - bases[i].TimeAdded) : callExpire; - sb.AppendLine($"`{i + 1}.` ✅ `{bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left"); + var left = Started ? callExpire - (DateTime.Now - Bases[i].TimeAdded) : callExpire; + sb.AppendLine($"`{i + 1}.` ✅ `{Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left"); } } @@ -171,11 +157,11 @@ namespace NadekoBot.Classes.ClashOfClans internal int FinishClaim(string user, int stars = 3) { user = user.Trim(); - for (var i = 0; i < bases.Length; i++) + for (var i = 0; i < Bases.Length; i++) { - if (bases[i]?.BaseDestroyed != false || bases[i]?.CallUser != user) continue; - bases[i].BaseDestroyed = true; - bases[i].Stars = stars; + if (Bases[i]?.BaseDestroyed != false || Bases[i]?.CallUser != user) continue; + Bases[i].BaseDestroyed = true; + Bases[i].Stars = stars; return i; } throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base."); diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs b/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs index c5ba20fc..a07fd822 100644 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs +++ b/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs @@ -7,7 +7,9 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using NadekoBot.Modules.Permissions.Classes; -using System.Threading; +using System.Linq; +using System.IO; +using Newtonsoft.Json; namespace NadekoBot.Modules.ClashOfClans { @@ -15,65 +17,166 @@ namespace NadekoBot.Modules.ClashOfClans { public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.ClashOfClans; - public static ConcurrentDictionary> ClashWars { get; } = new ConcurrentDictionary>(); + public static ConcurrentDictionary> ClashWars { get; set; } = new ConcurrentDictionary>(); + private readonly object writeLock = new object(); + + public ClashOfClansModule() + { + NadekoBot.OnReady += () => Task.Run(async () => + { + if (File.Exists("data/clashofclans/wars.json")) + { + try + { + var content = File.ReadAllText("data/clashofclans/wars.json"); + + var dict = JsonConvert.DeserializeObject>>(content); + + foreach (var cw in dict) + { + cw.Value.ForEach(war => + { + war.Channel = NadekoBot.Client.GetServer(war.ServerId)?.GetChannel(war.ChannelId); + if (war.Channel == null) + { + cw.Value.Remove(war); + } + } + ); + } + //urgh + ClashWars = new ConcurrentDictionary>(dict); + } + catch (Exception e) + { + Console.WriteLine("Could not load coc wars: " + e.Message); + } + + + } + //Can't this be disabled if the modules is disabled too :) + var callExpire = new TimeSpan(2, 0, 0); + var warExpire = new TimeSpan(23, 0, 0); + while (true) + { + try + { + var hash = ClashWars.GetHashCode(); + foreach (var cw in ClashWars) + { + foreach (var war in cw.Value) + { + await CheckWar(callExpire, war); + } + List newVal = new List(); + foreach (var w in cw.Value) + { + if (!w.Ended && (DateTime.Now - w.StartedAt <= warExpire)) + { + newVal.Add(w); + } + } + //var newVal = cw.Value.Where(w => !(w.Ended || DateTime.Now - w.StartedAt >= warExpire)).ToList(); + foreach (var exWar in cw.Value.Except(newVal)) + { + await exWar.Channel.SendMessage($"War against {exWar.EnemyClan} ({exWar.Size}v{exWar.Size})has ended."); + } + ClashWars.AddOrUpdate(cw.Key, newVal, (x, s) => newVal); + if (cw.Value.Count == 0) + { + List obj; + ClashWars.TryRemove(cw.Key, out obj); + } + } + if (hash != ClashWars.GetHashCode()) //something changed + { + Save(); + } + + + } + catch { } + await Task.Delay(5000); + } + }); + } + + private static void Save() + { + try + { + Directory.CreateDirectory("data/clashofclans"); + File.WriteAllText("data/clashofclans/wars.json", JsonConvert.SerializeObject(ClashWars, Formatting.Indented)); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } + + private static async Task CheckWar(TimeSpan callExpire, ClashWar war) + { + var Bases = war.Bases; + for (var i = 0; i < Bases.Length; i++) + { + if (Bases[i] == null) continue; + if (!Bases[i].BaseDestroyed && DateTime.Now - Bases[i].TimeAdded >= callExpire) + { + await war.Channel.SendMessage($"❗🔰**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false); + Bases[i] = null; + } + } + } + + + + + + + #region commands public override void Install(ModuleManager manager) { manager.CreateCommands("", cgb => { - cgb.AddCheck(PermissionChecker.Instance); + cgb.AddCheck(PermissionChecker.Instance); - cgb.CreateCommand(Prefix + "createwar") - .Alias(Prefix + "cw") - .Description($"Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. |{Prefix}cw 15 The Enemy Clan") - .Parameter("size") - .Parameter("enemy_clan", ParameterType.Unparsed) - .Do(async e => - { - if (!e.User.ServerPermissions.ManageChannels) - return; - List wars; - if (!ClashWars.TryGetValue(e.Server.Id, out wars)) - { - wars = new List(); - if (!ClashWars.TryAdd(e.Server.Id, wars)) - return; - } - var enemyClan = e.GetArg("enemy_clan"); - if (string.IsNullOrWhiteSpace(enemyClan)) - { - return; - } - int size; - if (!int.TryParse(e.GetArg("size"), out size) || size < 10 || size > 50 || size % 5 != 0) - { - await e.Channel.SendMessage("💢🔰 Not a Valid war size").ConfigureAwait(false); - return; - } - var cw = new ClashWar(enemyClan, size, e); + cgb.CreateCommand(Prefix + "createwar") + .Alias(Prefix + "cw") + .Description($"Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. |{Prefix}cw 15 The Enemy Clan") + .Parameter("size") + .Parameter("enemy_clan", ParameterType.Unparsed) + .Do(async e => + { + if (!e.User.ServerPermissions.ManageChannels) + return; + var enemyClan = e.GetArg("enemy_clan"); + if (string.IsNullOrWhiteSpace(enemyClan)) + { + return; + } + int size; + if (!int.TryParse(e.GetArg("size"), out size) || size < 10 || size > 50 || size % 5 != 0) + { + await e.Channel.SendMessage("💢🔰 Not a Valid war size").ConfigureAwait(false); + return; + } + List wars; + if (!ClashWars.TryGetValue(e.Server.Id, out wars)) + { + wars = new List(); + if (!ClashWars.TryAdd(e.Server.Id, wars)) + return; + } + + + var cw = new ClashWar(enemyClan, size, e.Server.Id, e.Channel.Id); //cw.Start(); + wars.Add(cw); - cw.OnUserTimeExpired += async (u) => - { - try - { - await - e.Channel.SendMessage( - $"❗🔰**Claim from @{u} for a war against {cw.ShortPrint()} has expired.**") - .ConfigureAwait(false); - } - catch { } - }; - cw.OnWarEnded += async () => - { - try - { - await e.Channel.SendMessage($"❗🔰**War against {cw.ShortPrint()} ended.**").ConfigureAwait(false); - } - catch { } - }; - await e.Channel.SendMessage($"❗🔰**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false); + await e.Channel.SendMessage($"❗🔰**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false); + Save(); //war with the index X started. }); @@ -92,13 +195,12 @@ namespace NadekoBot.Modules.ClashOfClans var war = warsInfo.Item1[warsInfo.Item2]; try { - var startTask = war.Start(); + war.Start(); await e.Channel.SendMessage($"🔰**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false); - await startTask.ConfigureAwait(false); } catch { - await e.Channel.SendMessage($"🔰**WAR AGAINST {war.ShortPrint()} IS ALREADY STARTED**").ConfigureAwait(false); + await e.Channel.SendMessage($"🔰**WAR AGAINST {war.ShortPrint()} HAS ALREADY STARTED**").ConfigureAwait(false); } }); @@ -205,7 +307,7 @@ namespace NadekoBot.Modules.ClashOfClans cgb.CreateCommand(Prefix + "unclaim") .Alias(Prefix + "uncall") .Alias(Prefix + "uc") - .Description($"Removes your claim from a certain war. Optional second argument denotes a person in whos place to unclaim | {Prefix}uc [war_number] [optional_other_name]") + .Description($"Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | {Prefix}uc [war_number] [optional_other_name]") .Parameter("number", ParameterType.Required) .Parameter("other_name", ParameterType.Unparsed) .Do(async e => @@ -245,12 +347,16 @@ namespace NadekoBot.Modules.ClashOfClans return; } warsInfo.Item1[warsInfo.Item2].End(); + await e.Channel.SendMessage($"❗🔰**War against {warsInfo.Item1[warsInfo.Item2].ShortPrint()} ended.**").ConfigureAwait(false); var size = warsInfo.Item1[warsInfo.Item2].Size; warsInfo.Item1.RemoveAt(warsInfo.Item2); }); }); + } + #endregion + private async Task FinishClaim(CommandEventArgs e, int stars = 3) {