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.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<string> 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; }
/// <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.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;
foreach (var b in bases.Where(b => b != null))
StartedAt = DateTime.Now;
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();
}
}
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.");

View File

@ -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,8 +17,124 @@ namespace NadekoBot.Modules.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)
{
manager.CreateCommands("", cgb =>
@ -33,13 +151,6 @@ namespace NadekoBot.Modules.ClashOfClans
{
if (!e.User.ServerPermissions.ManageChannels)
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");
if (string.IsNullOrWhiteSpace(enemyClan))
{
@ -51,29 +162,21 @@ namespace NadekoBot.Modules.ClashOfClans
await e.Channel.SendMessage("💢🔰 Not a Valid war size").ConfigureAwait(false);
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();
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);
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)
{