clash of clans saving started, needs more work

This commit is contained in:
Kwoth 2016-07-26 14:10:19 +02:00
parent 0521a71fa8
commit 961a42a8c3
2 changed files with 212 additions and 120 deletions

View File

@ -4,6 +4,9 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic;
using Newtonsoft.Json;
//using Manatee.Json.Serialization;
namespace NadekoBot.Classes.ClashOfClans namespace NadekoBot.Classes.ClashOfClans
{ {
@ -11,13 +14,14 @@ namespace NadekoBot.Classes.ClashOfClans
{ {
One, Two, Three One, Two, Three
} }
[System.Serializable]
internal class Caller internal class Caller
{ {
public string CallUser { get; } 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; public int Stars { get; set; } = 3;
@ -47,94 +51,76 @@ namespace NadekoBot.Classes.ClashOfClans
public string EnemyClan { get; } public string EnemyClan { get; }
public int Size { get; } public int Size { get; }
private Caller[] bases { get; } public Caller[] Bases { get; }
private CancellationTokenSource[] baseCancelTokens;
private CancellationTokenSource endTokenSource { get; } = new CancellationTokenSource();
public event Action<string> OnUserTimeExpired = delegate { };
public event Action OnWarEnded = delegate { };
public bool Started { get; set; } = false; 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; }
/// <summary>
/// This init is purely for the deserialization
/// </summary>
public ClashWar() { }
public ClashWar(string enemyClan, int size, ulong serverId, ulong channelId)
{ {
this.EnemyClan = enemyClan; this.EnemyClan = enemyClan;
this.Size = size; this.Size = size;
this.bases = new Caller[size]; this.Bases = new Caller[size];
this.baseCancelTokens = new CancellationTokenSource[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() internal void End()
{ {
if (endTokenSource.Token.IsCancellationRequested) return; Ended = true;
endTokenSource.Cancel();
OnWarEnded();
} }
internal void Call(string u, int baseNumber) 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"); throw new ArgumentException("Invalid base number");
if (bases[baseNumber] != null) if (Bases[baseNumber] != null)
throw new ArgumentException("That base is already claimed."); 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) 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."); 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) if (Started)
throw new InvalidOperationException(); throw new InvalidOperationException();
try
{
Started = true; Started = true;
foreach (var b in bases.Where(b => b != null)) StartedAt = DateTime.Now;
foreach (var b in Bases.Where(b => b != null))
{ {
b.ResetTime(); 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();
}
} }
internal int Uncall(string user) internal int Uncall(string user)
{ {
user = user.Trim(); 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; if (Bases[i]?.CallUser != user) continue;
bases[i] = null; Bases[i] = null;
return i; return i;
} }
throw new InvalidOperationException("You are not participating in that war."); 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() => public string ShortPrint() =>
$"`{EnemyClan}` ({Size} v {Size})"; $"`{EnemyClan}` ({Size} v {Size})";
@ -145,22 +131,22 @@ namespace NadekoBot.Classes.ClashOfClans
sb.AppendLine($"🔰**WAR AGAINST `{EnemyClan}` ({Size} v {Size}) INFO:**"); sb.AppendLine($"🔰**WAR AGAINST `{EnemyClan}` ({Size} v {Size}) INFO:**");
if (!Started) if (!Started)
sb.AppendLine("`not 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*"); sb.AppendLine($"`{i + 1}.` ❌*unclaimed*");
} }
else 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 else
{ {
var left = Started ? callExpire - (DateTime.Now - bases[i].TimeAdded) : callExpire; 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"); 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) internal int FinishClaim(string user, int stars = 3)
{ {
user = user.Trim(); 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; if (Bases[i]?.BaseDestroyed != false || Bases[i]?.CallUser != user) continue;
bases[i].BaseDestroyed = true; Bases[i].BaseDestroyed = true;
bases[i].Stars = stars; Bases[i].Stars = stars;
return i; return i;
} }
throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base."); throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base.");

View File

@ -7,7 +7,9 @@ using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Modules.Permissions.Classes; using NadekoBot.Modules.Permissions.Classes;
using System.Threading; using System.Linq;
using System.IO;
using Newtonsoft.Json;
namespace NadekoBot.Modules.ClashOfClans namespace NadekoBot.Modules.ClashOfClans
{ {
@ -15,8 +17,124 @@ namespace NadekoBot.Modules.ClashOfClans
{ {
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.ClashOfClans; public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.ClashOfClans;
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; } = new ConcurrentDictionary<ulong, List<ClashWar>>(); public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>();
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<Dictionary<ulong, List<ClashWar>>>(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<ulong, List<ClashWar>>(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<ClashWar> newVal = new List<ClashWar>();
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<ClashWar> 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) public override void Install(ModuleManager manager)
{ {
manager.CreateCommands("", cgb => manager.CreateCommands("", cgb =>
@ -33,13 +151,6 @@ namespace NadekoBot.Modules.ClashOfClans
{ {
if (!e.User.ServerPermissions.ManageChannels) if (!e.User.ServerPermissions.ManageChannels)
return; return;
List<ClashWar> wars;
if (!ClashWars.TryGetValue(e.Server.Id, out wars))
{
wars = new List<ClashWar>();
if (!ClashWars.TryAdd(e.Server.Id, wars))
return;
}
var enemyClan = e.GetArg("enemy_clan"); var enemyClan = e.GetArg("enemy_clan");
if (string.IsNullOrWhiteSpace(enemyClan)) if (string.IsNullOrWhiteSpace(enemyClan))
{ {
@ -51,29 +162,21 @@ namespace NadekoBot.Modules.ClashOfClans
await e.Channel.SendMessage("💢🔰 Not a Valid war size").ConfigureAwait(false); await e.Channel.SendMessage("💢🔰 Not a Valid war size").ConfigureAwait(false);
return; return;
} }
var cw = new ClashWar(enemyClan, size, e); List<ClashWar> wars;
if (!ClashWars.TryGetValue(e.Server.Id, out wars))
{
wars = new List<ClashWar>();
if (!ClashWars.TryAdd(e.Server.Id, wars))
return;
}
var cw = new ClashWar(enemyClan, size, e.Server.Id, e.Channel.Id);
//cw.Start(); //cw.Start();
wars.Add(cw); 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. //war with the index X started.
}); });
@ -92,13 +195,12 @@ namespace NadekoBot.Modules.ClashOfClans
var war = warsInfo.Item1[warsInfo.Item2]; var war = warsInfo.Item1[warsInfo.Item2];
try try
{ {
var startTask = war.Start(); war.Start();
await e.Channel.SendMessage($"🔰**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false); await e.Channel.SendMessage($"🔰**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false);
await startTask.ConfigureAwait(false);
} }
catch 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") cgb.CreateCommand(Prefix + "unclaim")
.Alias(Prefix + "uncall") .Alias(Prefix + "uncall")
.Alias(Prefix + "uc") .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("number", ParameterType.Required)
.Parameter("other_name", ParameterType.Unparsed) .Parameter("other_name", ParameterType.Unparsed)
.Do(async e => .Do(async e =>
@ -245,12 +347,16 @@ namespace NadekoBot.Modules.ClashOfClans
return; return;
} }
warsInfo.Item1[warsInfo.Item2].End(); 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; var size = warsInfo.Item1[warsInfo.Item2].Size;
warsInfo.Item1.RemoveAt(warsInfo.Item2); warsInfo.Item1.RemoveAt(warsInfo.Item2);
}); });
}); });
} }
#endregion
private async Task FinishClaim(CommandEventArgs e, int stars = 3) private async Task FinishClaim(CommandEventArgs e, int stars = 3)
{ {