Readded CoC saving, hopefuly works
This commit is contained in:
parent
d1e6501b62
commit
26f79063c9
@ -8,14 +8,58 @@ using NadekoBot.Services.Database.Impl;
|
|||||||
namespace NadekoBot.Migrations
|
namespace NadekoBot.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(NadekoSqliteContext))]
|
[DbContext(typeof(NadekoSqliteContext))]
|
||||||
[Migration("20160825131849_FirstMigration")]
|
[Migration("20160825172257_first")]
|
||||||
partial class FirstMigration
|
partial class first
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "1.0.0-rtm-21431");
|
.HasAnnotation("ProductVersion", "1.0.0-rtm-21431");
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<bool>("BaseDestroyed");
|
||||||
|
|
||||||
|
b.Property<string>("CallUser");
|
||||||
|
|
||||||
|
b.Property<int>("ClashWarId");
|
||||||
|
|
||||||
|
b.Property<int>("Stars");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TimeAdded");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ClashWarId");
|
||||||
|
|
||||||
|
b.ToTable("ClashCallers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId");
|
||||||
|
|
||||||
|
b.Property<string>("EnemyClan");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId");
|
||||||
|
|
||||||
|
b.Property<int>("Size");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartedAt");
|
||||||
|
|
||||||
|
b.Property<int>("WarState");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ClashOfClans");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@ -98,6 +142,14 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.ToTable("Quotes");
|
b.ToTable("Quotes");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar")
|
||||||
|
.WithMany("Bases")
|
||||||
|
.HasForeignKey("ClashWarId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,10 +4,28 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
|
|
||||||
namespace NadekoBot.Migrations
|
namespace NadekoBot.Migrations
|
||||||
{
|
{
|
||||||
public partial class FirstMigration : Migration
|
public partial class first : Migration
|
||||||
{
|
{
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ClashOfClans",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(nullable: false)
|
||||||
|
.Annotation("Autoincrement", true),
|
||||||
|
ChannelId = table.Column<ulong>(nullable: false),
|
||||||
|
EnemyClan = table.Column<string>(nullable: true),
|
||||||
|
GuildId = table.Column<ulong>(nullable: false),
|
||||||
|
Size = table.Column<int>(nullable: false),
|
||||||
|
StartedAt = table.Column<DateTime>(nullable: false),
|
||||||
|
WarState = table.Column<int>(nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ClashOfClans", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Donators",
|
name: "Donators",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@ -66,6 +84,34 @@ namespace NadekoBot.Migrations
|
|||||||
table.PrimaryKey("PK_Quotes", x => x.Id);
|
table.PrimaryKey("PK_Quotes", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ClashCallers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(nullable: false)
|
||||||
|
.Annotation("Autoincrement", true),
|
||||||
|
BaseDestroyed = table.Column<bool>(nullable: false),
|
||||||
|
CallUser = table.Column<string>(nullable: true),
|
||||||
|
ClashWarId = table.Column<int>(nullable: false),
|
||||||
|
Stars = table.Column<int>(nullable: false),
|
||||||
|
TimeAdded = table.Column<DateTime>(nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ClashCallers", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ClashCallers_ClashOfClans_ClashWarId",
|
||||||
|
column: x => x.ClashWarId,
|
||||||
|
principalTable: "ClashOfClans",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ClashCallers_ClashWarId",
|
||||||
|
table: "ClashCallers",
|
||||||
|
column: "ClashWarId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Donators_UserId",
|
name: "IX_Donators_UserId",
|
||||||
table: "Donators",
|
table: "Donators",
|
||||||
@ -81,6 +127,9 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ClashCallers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Donators");
|
name: "Donators");
|
||||||
|
|
||||||
@ -89,6 +138,9 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Quotes");
|
name: "Quotes");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ClashOfClans");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,6 +15,50 @@ namespace NadekoBot.Migrations
|
|||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "1.0.0-rtm-21431");
|
.HasAnnotation("ProductVersion", "1.0.0-rtm-21431");
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<bool>("BaseDestroyed");
|
||||||
|
|
||||||
|
b.Property<string>("CallUser");
|
||||||
|
|
||||||
|
b.Property<int>("ClashWarId");
|
||||||
|
|
||||||
|
b.Property<int>("Stars");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TimeAdded");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ClashWarId");
|
||||||
|
|
||||||
|
b.ToTable("ClashCallers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId");
|
||||||
|
|
||||||
|
b.Property<string>("EnemyClan");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId");
|
||||||
|
|
||||||
|
b.Property<int>("Size");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartedAt");
|
||||||
|
|
||||||
|
b.Property<int>("WarState");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ClashOfClans");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@ -97,6 +141,14 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.ToTable("Quotes");
|
b.ToTable("Quotes");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar")
|
||||||
|
.WithMany("Bases")
|
||||||
|
.HasForeignKey("ClashWarId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ using Discord;
|
|||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using NadekoBot.Attributes;
|
using NadekoBot.Attributes;
|
||||||
using NadekoBot.Classes;
|
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Attributes;
|
using NadekoBot.Attributes;
|
||||||
using NadekoBot.Classes;
|
|
||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Classes.ClashOfClans;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -9,23 +8,43 @@ using Discord;
|
|||||||
using NadekoBot.Services;
|
using NadekoBot.Services;
|
||||||
using NadekoBot.Attributes;
|
using NadekoBot.Attributes;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using System.Linq;
|
||||||
|
using NadekoBot.Services.Database;
|
||||||
|
|
||||||
//todo DB
|
//todo DB
|
||||||
namespace NadekoBot.Modules.ClashOfClans
|
namespace NadekoBot.Modules.ClashOfClans
|
||||||
{
|
{
|
||||||
[Module(",",AppendSpace = false)]
|
[Module(",", AppendSpace = false)]
|
||||||
public class ClashOfClans : DiscordModule
|
public class ClashOfClans : DiscordModule
|
||||||
{
|
{
|
||||||
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>();
|
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>();
|
||||||
|
|
||||||
public ClashOfClans(ILocalization loc, CommandService cmds, IBotConfiguration config, DiscordSocketClient client) : base(loc, cmds, config, client)
|
public ClashOfClans(ILocalization loc, CommandService cmds, IBotConfiguration config, DiscordSocketClient client) : base(loc, cmds, config, client)
|
||||||
{
|
{
|
||||||
|
using (var uow = DbHandler.UnitOfWork())
|
||||||
|
{
|
||||||
|
ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
|
||||||
|
uow.ClashOfClans
|
||||||
|
.GetAll()
|
||||||
|
.Select(cw => {
|
||||||
|
cw.Channel = NadekoBot.Client.GetGuilds()
|
||||||
|
.FirstOrDefault(s => s.Id == cw.GuildId)?
|
||||||
|
.GetChannels()
|
||||||
|
.FirstOrDefault(c => c.Id == cw.ChannelId)
|
||||||
|
as ITextChannel;
|
||||||
|
cw.Bases.Capacity = cw.Size;
|
||||||
|
return cw;
|
||||||
|
})
|
||||||
|
.GroupBy(cw => cw.GuildId)
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task CheckWar(TimeSpan callExpire, ClashWar war)
|
private static async Task CheckWar(TimeSpan callExpire, ClashWar war)
|
||||||
{
|
{
|
||||||
var Bases = war.Bases;
|
var Bases = war.Bases;
|
||||||
for (var i = 0; i < Bases.Length; i++)
|
for (var i = 0; i < Bases.Capacity; i++)
|
||||||
{
|
{
|
||||||
if (Bases[i] == null) continue;
|
if (Bases[i] == null) continue;
|
||||||
if (!Bases[i].BaseDestroyed && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire)
|
if (!Bases[i].BaseDestroyed && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire)
|
||||||
@ -62,7 +81,7 @@ namespace NadekoBot.Modules.ClashOfClans
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var cw = new ClashWar(enemyClan, size, channel.Guild.Id, imsg.Channel.Id);
|
var cw = await CreateWar(enemyClan, size, channel.Guild.Id, imsg.Channel.Id);
|
||||||
//cw.Start();
|
//cw.Start();
|
||||||
|
|
||||||
wars.Add(cw);
|
wars.Add(cw);
|
||||||
@ -94,6 +113,7 @@ namespace NadekoBot.Modules.ClashOfClans
|
|||||||
{
|
{
|
||||||
await channel.SendMessageAsync($"🔰**WAR AGAINST {war.ShortPrint()} HAS ALREADY STARTED**").ConfigureAwait(false);
|
await channel.SendMessageAsync($"🔰**WAR AGAINST {war.ShortPrint()} HAS ALREADY STARTED**").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
SaveWar(war);
|
||||||
}
|
}
|
||||||
|
|
||||||
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
|
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
|
||||||
@ -136,7 +156,7 @@ namespace NadekoBot.Modules.ClashOfClans
|
|||||||
await channel.SendMessageAsync("💢🔰 **That war does not exist.**").ConfigureAwait(false);
|
await channel.SendMessageAsync("💢🔰 **That war does not exist.**").ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await channel.SendMessageAsync(warsInfo.Item1[warsInfo.Item2].ToString()).ConfigureAwait(false);
|
await channel.SendMessageAsync(warsInfo.Item1[warsInfo.Item2].ToPrettyString()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
|
[LocalizedCommand, LocalizedDescription, LocalizedSummary]
|
||||||
@ -158,6 +178,7 @@ namespace NadekoBot.Modules.ClashOfClans
|
|||||||
{
|
{
|
||||||
var war = warsInfo.Item1[warsInfo.Item2];
|
var war = warsInfo.Item1[warsInfo.Item2];
|
||||||
war.Call(usr, baseNumber - 1);
|
war.Call(usr, baseNumber - 1);
|
||||||
|
SaveWar(war);
|
||||||
await channel.SendMessageAsync($"🔰**{usr}** claimed a base #{baseNumber} for a war against {war.ShortPrint()}").ConfigureAwait(false);
|
await channel.SendMessageAsync($"🔰**{usr}** claimed a base #{baseNumber} for a war against {war.ShortPrint()}").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -202,7 +223,9 @@ namespace NadekoBot.Modules.ClashOfClans
|
|||||||
await channel.SendMessageAsync("💢🔰 That war does not exist.").ConfigureAwait(false);
|
await channel.SendMessageAsync("💢🔰 That war does not exist.").ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
warsInfo.Item1[warsInfo.Item2].End();
|
var war = warsInfo.Item1[warsInfo.Item2];
|
||||||
|
war.End();
|
||||||
|
SaveWar(war);
|
||||||
await channel.SendMessageAsync($"❗🔰**War against {warsInfo.Item1[warsInfo.Item2].ShortPrint()} ended.**").ConfigureAwait(false);
|
await channel.SendMessageAsync($"❗🔰**War against {warsInfo.Item1[warsInfo.Item2].ShortPrint()} ended.**").ConfigureAwait(false);
|
||||||
|
|
||||||
var size = warsInfo.Item1[warsInfo.Item2].Size;
|
var size = warsInfo.Item1[warsInfo.Item2].Size;
|
||||||
@ -229,6 +252,7 @@ namespace NadekoBot.Modules.ClashOfClans
|
|||||||
{
|
{
|
||||||
var war = warsInfo.Item1[warsInfo.Item2];
|
var war = warsInfo.Item1[warsInfo.Item2];
|
||||||
var baseNumber = war.Uncall(usr);
|
var baseNumber = war.Uncall(usr);
|
||||||
|
SaveWar(war);
|
||||||
await channel.SendMessageAsync($"🔰 @{usr} has **UNCLAIMED** a base #{baseNumber + 1} from a war against {war.ShortPrint()}").ConfigureAwait(false);
|
await channel.SendMessageAsync($"🔰 @{usr} has **UNCLAIMED** a base #{baseNumber + 1} from a war against {war.ShortPrint()}").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -255,6 +279,7 @@ namespace NadekoBot.Modules.ClashOfClans
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var baseNum = war.FinishClaim(usr, stars);
|
var baseNum = war.FinishClaim(usr, stars);
|
||||||
|
SaveWar(war);
|
||||||
await channel.SendMessageAsync($"❗🔰{imsg.Author.Mention} **DESTROYED** a base #{baseNum + 1} in a war against {war.ShortPrint()}").ConfigureAwait(false);
|
await channel.SendMessageAsync($"❗🔰{imsg.Author.Mention} **DESTROYED** a base #{baseNum + 1} in a war against {war.ShortPrint()}").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -282,5 +307,48 @@ namespace NadekoBot.Modules.ClashOfClans
|
|||||||
//get the actual war
|
//get the actual war
|
||||||
return new Tuple<List<ClashWar>, int>(wars, num);
|
return new Tuple<List<ClashWar>, int>(wars, num);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<ClashWar> CreateWar(string enemyClan, int size, ulong serverId, ulong channelId)
|
||||||
|
{
|
||||||
|
using (var uow = DbHandler.UnitOfWork())
|
||||||
|
{
|
||||||
|
var cw = new ClashWar
|
||||||
|
{
|
||||||
|
EnemyClan = enemyClan,
|
||||||
|
Size = size,
|
||||||
|
Bases = new List<ClashCaller>(size),
|
||||||
|
GuildId = serverId,
|
||||||
|
ChannelId = channelId,
|
||||||
|
Channel = NadekoBot.Client.GetGuilds()
|
||||||
|
.FirstOrDefault(s => s.Id == serverId)?
|
||||||
|
.GetChannels()
|
||||||
|
.FirstOrDefault(c => c.Id == channelId)
|
||||||
|
as ITextChannel
|
||||||
|
};
|
||||||
|
uow.ClashOfClans.Add(cw);
|
||||||
|
await uow.CompleteAsync();
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveWar(ClashWar cw)
|
||||||
|
{
|
||||||
|
if (cw.WarState == ClashWar.StateOfWar.Ended)
|
||||||
|
{
|
||||||
|
using (var uow = DbHandler.UnitOfWork())
|
||||||
|
{
|
||||||
|
uow.ClashOfClans.Remove(cw);
|
||||||
|
uow.CompleteAsync();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
using (var uow = DbHandler.UnitOfWork())
|
||||||
|
{
|
||||||
|
uow.ClashOfClans.Update(cw);
|
||||||
|
uow.CompleteAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,183 +0,0 @@
|
|||||||
using Discord;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NadekoBot.Classes.ClashOfClans
|
|
||||||
{
|
|
||||||
public class ClashWar
|
|
||||||
{
|
|
||||||
public enum DestroyStars
|
|
||||||
{
|
|
||||||
One, Two, Three
|
|
||||||
}
|
|
||||||
public enum StateOfWar
|
|
||||||
{
|
|
||||||
Started, Ended, Created
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Caller
|
|
||||||
{
|
|
||||||
public string CallUser { get; set; }
|
|
||||||
|
|
||||||
public DateTime TimeAdded { get; set; }
|
|
||||||
|
|
||||||
public bool BaseDestroyed { get; set; }
|
|
||||||
|
|
||||||
public int Stars { get; set; } = 3;
|
|
||||||
|
|
||||||
public Caller() { }
|
|
||||||
|
|
||||||
public Caller(string callUser, DateTime timeAdded, bool baseDestroyed)
|
|
||||||
{
|
|
||||||
CallUser = callUser;
|
|
||||||
TimeAdded = timeAdded;
|
|
||||||
BaseDestroyed = baseDestroyed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetTime()
|
|
||||||
{
|
|
||||||
TimeAdded = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Destroy()
|
|
||||||
{
|
|
||||||
BaseDestroyed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private static TimeSpan callExpire => new TimeSpan(2, 0, 0);
|
|
||||||
|
|
||||||
public string EnemyClan { get; set; }
|
|
||||||
public int Size { get; set; }
|
|
||||||
|
|
||||||
public Caller[] Bases { get; set; }
|
|
||||||
public StateOfWar WarState { get; set; } = StateOfWar.Created;
|
|
||||||
//public bool Started { get; set; } = false;
|
|
||||||
public DateTime StartedAt { get; set; }
|
|
||||||
//public bool Ended { get; private set; } = false;
|
|
||||||
|
|
||||||
public ulong ServerId { get; set; }
|
|
||||||
public ulong ChannelId { get; set; }
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public ITextChannel 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.ServerId = serverId;
|
|
||||||
this.ChannelId = channelId;
|
|
||||||
this.Channel = NadekoBot.Client.GetGuildsAsync() //nice api you got here volt,
|
|
||||||
.GetAwaiter() //especially like how getguildsasync isn't async at all internally.
|
|
||||||
.GetResult() //But hey, lib has to be async kek
|
|
||||||
.FirstOrDefault(s => s.Id == serverId)? // srsly
|
|
||||||
.GetChannelsAsync() //wtf is this
|
|
||||||
.GetAwaiter() // oh i know, its the implementation detail
|
|
||||||
.GetResult() // and makes library look consistent
|
|
||||||
.FirstOrDefault(c => c.Id == channelId) // its not common sense to make library work like this.
|
|
||||||
as ITextChannel; // oh and don't forget to cast it to this arbitrary bullshit
|
|
||||||
}
|
|
||||||
|
|
||||||
public void End()
|
|
||||||
{
|
|
||||||
//Ended = true;
|
|
||||||
WarState = StateOfWar.Ended;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Call(string u, int baseNumber)
|
|
||||||
{
|
|
||||||
if (baseNumber < 0 || baseNumber >= Bases.Length)
|
|
||||||
throw new ArgumentException("Invalid base number");
|
|
||||||
if (Bases[baseNumber] != null)
|
|
||||||
throw new ArgumentException("That base is already claimed.");
|
|
||||||
for (var i = 0; i < Bases.Length; i++)
|
|
||||||
{
|
|
||||||
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.UtcNow, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
if (WarState == StateOfWar.Started)
|
|
||||||
throw new InvalidOperationException("War already started");
|
|
||||||
//if (Started)
|
|
||||||
// throw new InvalidOperationException();
|
|
||||||
//Started = true;
|
|
||||||
WarState = StateOfWar.Started;
|
|
||||||
StartedAt = DateTime.UtcNow;
|
|
||||||
foreach (var b in Bases.Where(b => b != null))
|
|
||||||
{
|
|
||||||
b.ResetTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Uncall(string user)
|
|
||||||
{
|
|
||||||
user = user.Trim();
|
|
||||||
for (var i = 0; i < Bases.Length; i++)
|
|
||||||
{
|
|
||||||
if (Bases[i]?.CallUser != user) continue;
|
|
||||||
Bases[i] = null;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("You are not participating in that war.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ShortPrint() =>
|
|
||||||
$"`{EnemyClan}` ({Size} v {Size})";
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.AppendLine($"🔰**WAR AGAINST `{EnemyClan}` ({Size} v {Size}) INFO:**");
|
|
||||||
if (WarState == StateOfWar.Created)
|
|
||||||
sb.AppendLine("`not started`");
|
|
||||||
for (var i = 0; i < Bases.Length; i++)
|
|
||||||
{
|
|
||||||
if (Bases[i] == null)
|
|
||||||
{
|
|
||||||
sb.AppendLine($"`{i + 1}.` ❌*unclaimed*");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Bases[i].BaseDestroyed)
|
|
||||||
{
|
|
||||||
sb.AppendLine($"`{i + 1}.` ✅ `{Bases[i].CallUser}` {new string('⭐', Bases[i].Stars)}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var left = (WarState == StateOfWar.Started) ? callExpire - (DateTime.UtcNow - Bases[i].TimeAdded) : callExpire;
|
|
||||||
sb.AppendLine($"`{i + 1}.` ✅ `{Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int FinishClaim(string user, int stars = 3)
|
|
||||||
{
|
|
||||||
user = user.Trim();
|
|
||||||
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;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
124
src/NadekoBot/Modules/ClashOfClans/Extensions.cs
Normal file
124
src/NadekoBot/Modules/ClashOfClans/Extensions.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static NadekoBot.Services.Database.Models.ClashWar;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.ClashOfClans
|
||||||
|
{
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static void ResetTime(this ClashCaller c)
|
||||||
|
{
|
||||||
|
c.TimeAdded = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Destroy(this ClashCaller c)
|
||||||
|
{
|
||||||
|
c.BaseDestroyed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void End(this ClashWar cw)
|
||||||
|
{
|
||||||
|
//Ended = true;
|
||||||
|
cw.WarState = StateOfWar.Ended;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Call(this ClashWar cw, string u, int baseNumber)
|
||||||
|
{
|
||||||
|
if (baseNumber < 0 || baseNumber >= cw.Bases.Capacity)
|
||||||
|
throw new ArgumentException("Invalid base number");
|
||||||
|
if (cw.Bases[baseNumber] != null)
|
||||||
|
throw new ArgumentException("That base is already claimed.");
|
||||||
|
for (var i = 0; i < cw.Bases.Capacity; i++)
|
||||||
|
{
|
||||||
|
if (cw.Bases[i]?.BaseDestroyed == false && cw.Bases[i]?.CallUser == u)
|
||||||
|
throw new ArgumentException($"@{u} You already claimed base #{i + 1}. You can't claim a new one.");
|
||||||
|
}
|
||||||
|
|
||||||
|
cw.Bases[baseNumber] = new ClashCaller() {
|
||||||
|
CallUser = u.Trim(),
|
||||||
|
TimeAdded = DateTime.UtcNow,
|
||||||
|
BaseDestroyed = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Start(this ClashWar cw)
|
||||||
|
{
|
||||||
|
if (cw.WarState == StateOfWar.Started)
|
||||||
|
throw new InvalidOperationException("War already started");
|
||||||
|
//if (Started)
|
||||||
|
// throw new InvalidOperationException();
|
||||||
|
//Started = true;
|
||||||
|
cw.WarState = StateOfWar.Started;
|
||||||
|
cw.StartedAt = DateTime.UtcNow;
|
||||||
|
foreach (var b in cw.Bases.Where(b => b != null))
|
||||||
|
{
|
||||||
|
b.ResetTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int Uncall(this ClashWar cw, string user)
|
||||||
|
{
|
||||||
|
user = user.Trim();
|
||||||
|
for (var i = 0; i < cw.Bases.Capacity; i++)
|
||||||
|
{
|
||||||
|
if (cw.Bases[i]?.CallUser != user) continue;
|
||||||
|
cw.Bases[i] = null;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException("You are not participating in that war.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ShortPrint(this ClashWar cw) =>
|
||||||
|
$"`{cw.EnemyClan}` ({cw.Size} v {cw.Size})";
|
||||||
|
|
||||||
|
public static string ToPrettyString(this ClashWar cw)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine($"🔰**WAR AGAINST `{cw.EnemyClan}` ({cw.Size} v {cw.Size}) INFO:**");
|
||||||
|
if (cw.WarState == StateOfWar.Created)
|
||||||
|
sb.AppendLine("`not started`");
|
||||||
|
var twoHours = new TimeSpan(2, 0, 0);
|
||||||
|
for (var i = 0; i < cw.Bases.Capacity; i++)
|
||||||
|
{
|
||||||
|
if (cw.Bases[i] == null)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"`{i + 1}.` ❌*unclaimed*");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (cw.Bases[i].BaseDestroyed)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {new string('⭐', cw.Bases[i].Stars)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var left = (cw.WarState == StateOfWar.Started) ? twoHours - (DateTime.UtcNow - cw.Bases[i].TimeAdded) : twoHours;
|
||||||
|
sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int FinishClaim(this ClashWar cw, string user, int stars = 3)
|
||||||
|
{
|
||||||
|
user = user.Trim();
|
||||||
|
for (var i = 0; i < cw.Bases.Capacity; i++)
|
||||||
|
{
|
||||||
|
if (cw.Bases[i]?.BaseDestroyed != false || cw.Bases[i]?.CallUser != user) continue;
|
||||||
|
cw.Bases[i].BaseDestroyed = true;
|
||||||
|
cw.Bases[i].Stars = stars;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Attributes;
|
using NadekoBot.Attributes;
|
||||||
using NadekoBot.Classes;
|
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Attributes;
|
using NadekoBot.Attributes;
|
||||||
using NadekoBot.Classes;
|
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Discord.Audio;
|
using Discord.Audio;
|
||||||
using NadekoBot.Classes;
|
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using NadekoBot.Classes;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Commands;
|
using Discord.Commands;
|
||||||
using NadekoBot.Attributes;
|
using NadekoBot.Attributes;
|
||||||
using NadekoBot.Classes;
|
|
||||||
using NadekoBot.Extensions;
|
using NadekoBot.Extensions;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
@ -12,6 +12,8 @@ namespace NadekoBot.Services.Database
|
|||||||
IQuoteRepository Quotes { get; }
|
IQuoteRepository Quotes { get; }
|
||||||
IConfigRepository GuildConfigs { get; }
|
IConfigRepository GuildConfigs { get; }
|
||||||
IDonatorsRepository Donators { get; }
|
IDonatorsRepository Donators { get; }
|
||||||
|
IClashOfClansRepository ClashOfClans { get; }
|
||||||
|
|
||||||
int Complete();
|
int Complete();
|
||||||
Task<int> CompleteAsync();
|
Task<int> CompleteAsync();
|
||||||
}
|
}
|
||||||
|
25
src/NadekoBot/Services/Database/Models/ClashCaller.cs
Normal file
25
src/NadekoBot/Services/Database/Models/ClashCaller.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services.Database.Models
|
||||||
|
{
|
||||||
|
public class ClashCaller : DbEntity
|
||||||
|
{
|
||||||
|
public string CallUser { get; set; }
|
||||||
|
|
||||||
|
public DateTime TimeAdded { get; set; }
|
||||||
|
|
||||||
|
public bool BaseDestroyed { get; set; }
|
||||||
|
|
||||||
|
public int Stars { get; set; } = 3;
|
||||||
|
|
||||||
|
public int ClashWarId { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(ClashWarId))]
|
||||||
|
public ClashWar ClashWar { get; set; }
|
||||||
|
}
|
||||||
|
}
|
36
src/NadekoBot/Services/Database/Models/ClashWar.cs
Normal file
36
src/NadekoBot/Services/Database/Models/ClashWar.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services.Database.Models
|
||||||
|
{
|
||||||
|
public class ClashWar : DbEntity
|
||||||
|
{
|
||||||
|
public enum DestroyStars
|
||||||
|
{
|
||||||
|
One, Two, Three
|
||||||
|
}
|
||||||
|
public enum StateOfWar
|
||||||
|
{
|
||||||
|
Started, Ended, Created
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EnemyClan { get; set; }
|
||||||
|
public int Size { get; set; }
|
||||||
|
public StateOfWar WarState { get; set; } = StateOfWar.Created;
|
||||||
|
public DateTime StartedAt { get; set; }
|
||||||
|
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
|
public ITextChannel Channel { get; internal set; }
|
||||||
|
|
||||||
|
public List<ClashCaller> Bases { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,8 @@ namespace NadekoBot.Services.Database
|
|||||||
public DbSet<Quote> Quotes { get; set; }
|
public DbSet<Quote> Quotes { get; set; }
|
||||||
public DbSet<Donator> Donators { get; set; }
|
public DbSet<Donator> Donators { get; set; }
|
||||||
public DbSet<GuildConfig> GuildConfigs { get; set; }
|
public DbSet<GuildConfig> GuildConfigs { get; set; }
|
||||||
|
public DbSet<ClashWar> ClashOfClans { get; set; }
|
||||||
|
public DbSet<ClashCaller> ClashCallers { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@ -40,6 +42,15 @@ namespace NadekoBot.Services.Database
|
|||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region ClashOfClans
|
||||||
|
|
||||||
|
var callersEntity = modelBuilder.Entity<ClashCaller>();
|
||||||
|
callersEntity
|
||||||
|
.HasOne(c => c.ClashWar)
|
||||||
|
.WithMany(c => c.Bases);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
protected abstract override void OnConfiguring(DbContextOptionsBuilder optionsBuilder);
|
protected abstract override void OnConfiguring(DbContextOptionsBuilder optionsBuilder);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services.Database.Repositories
|
||||||
|
{
|
||||||
|
public interface IClashOfClansRepository : IRepository<ClashWar>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace NadekoBot.Services.Database.Repositories.Impl
|
||||||
|
{
|
||||||
|
public class ClashOfClansRepository : Repository<ClashWar>, IClashOfClansRepository
|
||||||
|
{
|
||||||
|
public ClashOfClansRepository(DbContext context) : base(context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,9 @@ namespace NadekoBot.Services.Database
|
|||||||
private IDonatorsRepository _donators;
|
private IDonatorsRepository _donators;
|
||||||
public IDonatorsRepository Donators => _donators ?? (_donators = new DonatorsRepository(_context));
|
public IDonatorsRepository Donators => _donators ?? (_donators = new DonatorsRepository(_context));
|
||||||
|
|
||||||
|
private IClashOfClansRepository _clashOfClans;
|
||||||
|
public IClashOfClansRepository ClashOfClans => _clashOfClans ?? (_clashOfClans = new ClashOfClansRepository(_context));
|
||||||
|
|
||||||
public UnitOfWork(NadekoContext context)
|
public UnitOfWork(NadekoContext context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
|
Loading…
Reference in New Issue
Block a user