Packages can be loaded/unloaded. IUnloadableService interface added whose method Unload, if service implements it, will be called when the module is unloaded.

This commit is contained in:
Master Kwoth 2017-10-05 00:51:12 +02:00
parent 599245b1ca
commit 33ac43e1b5
74 changed files with 866 additions and 520 deletions

View File

@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks;
using Discord.Commands;
using NadekoBot.Services;
using NadekoBot.Modules.CustomReactions.Services;
namespace NadekoBot.Common.TypeReaders
{
@ -28,38 +29,38 @@ namespace NadekoBot.Common.TypeReaders
}
}
//todo dependency on the module
//public class CommandOrCrTypeReader : CommandTypeReader
//{
// public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
// {
// input = input.ToUpperInvariant();
public class CommandOrCrTypeReader : CommandTypeReader
{
public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
{
input = input.ToUpperInvariant();
// var _crs = ((INServiceProvider)services).GetService<CustomReactionsService>();
var _crs = ((INServiceProvider)services).GetService<CustomReactionsService>();
// if (_crs.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input))
// {
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
// }
// var guild = context.Guild;
// if (guild != null)
// {
// if (_crs.GuildReactions.TryGetValue(guild.Id, out var crs))
// {
// if (crs.Concat(_crs.GlobalReactions).Any(x => x.Trigger.ToUpperInvariant() == input))
// {
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
// }
// }
// }
if (_crs.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input))
{
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
}
var guild = context.Guild;
if (guild != null)
{
if (_crs.GuildReactions.TryGetValue(guild.Id, out var crs))
{
if (crs.Concat(_crs.GlobalReactions).Any(x => x.Trigger.ToUpperInvariant() == input))
{
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
}
}
}
// var cmd = await base.Read(context, input, services);
// if (cmd.IsSuccess)
// {
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name));
// }
// return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
// }
//}
var cmd = await base.Read(context, input, services);
if (cmd.IsSuccess)
{
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name));
}
return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
}
}
public class CommandOrCrInfo
{

View File

@ -0,0 +1,64 @@
using Discord.Commands;
using NadekoBot.Common.Attributes;
using NadekoBot.Core.Modules.Administration.Services;
using NadekoBot.Extensions;
using System;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
{
public partial class Administration
{
[Group]
public class PackagesCommands : NadekoSubmodule<PackagesService>
{
private readonly NadekoBot _bot;
public PackagesCommands(NadekoBot bot)
{
_bot = bot;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task PackageList()
{
_service.ReloadAvailablePackages();
await Context.Channel.SendConfirmAsync(string.Join("\n", _service.Packages));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task PackageUnload(string name)
{
if (name.Contains(":") || name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
return;
name = name.ToTitleCase();
var package = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory,
"modules",
$"NadekoBot.Modules.{name}",
$"NadekoBot.Modules.{name}.dll"));
await _bot.UnloadPackage(name).ConfigureAwait(false);
await ReplyAsync(":ok:");
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task PackageLoad(string name)
{
if (name.Contains(".") || name.Contains("\\") || name.Contains("/") || name.Contains("~"))
return;
name = name.ToTitleCase();
await _bot.LoadPackage(name);
await ReplyAsync(":ok:");
}
}
}
}

View File

@ -48,6 +48,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task StartupCommandAdd([Remainder] string cmdText)
{
//todo don't let .die be a startup command
var guser = ((IGuildUser)Context.User);
var cmd = new StartupCommand()
{

View File

@ -0,0 +1,31 @@
using NadekoBot.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace NadekoBot.Core.Modules.Administration.Services
{
public class PackagesService : INService
{
public IEnumerable<string> Packages { get; private set; }
public PackagesService()
{
ReloadAvailablePackages();
}
public void ReloadAvailablePackages()
{
Packages = Directory.GetDirectories(Path.Combine(AppContext.BaseDirectory, "modules\\"), "NadekoBot.Modules.*", SearchOption.AllDirectories)
.SelectMany(x => Directory.GetFiles(x, "NadekoBot.Modules.*.dll"))
.Select(x => Path.GetFileNameWithoutExtension(x))
.Select(x =>
{
var m = Regex.Match(x, @"NadekoBot\.Modules\.(?<name>.*)");
return m.Groups["name"].Value;
});
}
}
}

View File

@ -2,9 +2,6 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<LangVersion>latest</LangVersion>
</PropertyGroup>

View File

@ -1,4 +1,6 @@
namespace NadekoBot.Services
using System.Threading.Tasks;
namespace NadekoBot.Services
{
/// <summary>
/// All services must implement this interface in order to be auto-discovered by the DI system
@ -7,4 +9,12 @@
{
}
/// <summary>
/// All services which require cleanup after they are unloaded must implement this interface
/// </summary>
public interface IUnloadableService
{
Task Unload();
}
}

View File

@ -61,17 +61,6 @@ namespace NadekoBot
if (shardId < 0)
throw new ArgumentOutOfRangeException(nameof(shardId));
//var obj = JsonConvert.DeserializeObject<Dictionary<string, CommandData2>>(File.ReadAllText("./data/command_strings.json"))
// .ToDictionary(x => x.Key, x => new CommandData2
// {
// Cmd = x.Value.Cmd,
// Desc = x.Value.Desc,
// Usage = x.Value.Usage.Select(y => y.Substring(1, y.Length - 2)).ToArray(),
// });
//File.WriteAllText("./data/command_strings.json", JsonConvert.SerializeObject(obj, Formatting.Indented));
LogSetup.SetupLogger();
_log = LogManager.GetCurrentClassLogger();
TerribleElevatedPermissionCheck();
@ -142,19 +131,17 @@ namespace NadekoBot
//var localization = new Localization(_botConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db);
//initialize Services
Services = new NServiceProvider.ServiceProviderBuilder()
Services = new NServiceProvider()
.AddManual<IBotCredentials>(Credentials)
.AddManual(_db)
.AddManual(Client)
.AddManual(CommandService)
.AddManual(botConfigProvider)
//.AddManual<ILocalization>(localization)
.AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this
.AddManual<NadekoBot>(this)
.AddManual<IUnitOfWork>(uow)
.AddManual<IDataCache>(new RedisCache(Client.CurrentUser.Id))
.LoadFrom(Assembly.GetEntryAssembly())
.Build();
.AddManual<IDataCache>(new RedisCache(Client.CurrentUser.Id));
Services.LoadFrom(Assembly.GetAssembly(typeof(CommandHandler)));
var commandHandler = Services.GetService<CommandHandler>();
commandHandler.AddServices(Services);
@ -163,12 +150,13 @@ namespace NadekoBot
CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader());
CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader());
//todo module dependency
//CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader());
CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader());
CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(CommandService));
CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(CommandService));
CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client));
//CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader());
}
Services.Unload(typeof(IUnitOfWork)); // unload it after the startup
}
private async Task LoginAsync(string token)
@ -193,7 +181,7 @@ namespace NadekoBot
}
finally
{
}
});
return Task.CompletedTask;
@ -225,8 +213,8 @@ namespace NadekoBot
public async Task RunAsync(params string[] args)
{
if(Client.ShardId == 0)
_log.Info("Starting NadekoBot v" + StatsService.BotVersion);
if (Client.ShardId == 0)
_log.Info("Starting NadekoBot v" + StatsService.BotVersion);
var sw = Stopwatch.StartNew();
@ -255,7 +243,7 @@ namespace NadekoBot
#endif
//unload modules which are not available on the public bot
if(isPublicNadeko)
if (isPublicNadeko)
CommandService
.Modules
.ToArray()
@ -372,5 +360,80 @@ namespace NadekoBot
var sub = Services.GetService<IDataCache>().Redis.GetSubscriber();
return sub.PublishAsync(Client.CurrentUser.Id + "_status.game_set", JsonConvert.SerializeObject(obj));
}
private readonly Dictionary<string, IEnumerable<ModuleInfo>> _packageModules = new Dictionary<string, IEnumerable<ModuleInfo>>();
private readonly Dictionary<string, IEnumerable<Type>> _packageTypes = new Dictionary<string, IEnumerable<Type>>();
private readonly SemaphoreSlim _packageLocker = new SemaphoreSlim(1, 1);
/// <summary>
/// Unloads a package
/// </summary>
/// <param name="name">Package name. Case sensitive.</param>
/// <returns>Whether the unload is successful.</returns>
public async Task<bool> UnloadPackage(string name)
{
await _packageLocker.WaitAsync().ConfigureAwait(false);
try
{
if (!_packageModules.TryGetValue(name, out var modules))
return false;
var i = 0;
foreach (var m in modules)
{
await CommandService.RemoveModuleAsync(m).ConfigureAwait(false);
i++;
}
_log.Info("Unloaded {0} modules.", i);
if (_packageTypes.TryGetValue(name, out var types))
{
i = 0;
foreach (var t in types)
{
var obj = Services.Unload(t);
if (obj is IUnloadableService s)
await s.Unload().ConfigureAwait(false);
i++;
}
_log.Info("Unloaded {0} types.", i);
}
return true;
}
finally
{
_packageLocker.Release();
}
}
/// <summary>
/// Loads a package
/// </summary>
/// <param name="name">Name of the package to load. Case sensitive.</param>
/// <returns>Whether the load is successful.</returns>
public async Task<bool> LoadPackage(string name)
{
await _packageLocker.WaitAsync().ConfigureAwait(false);
try
{
if (_packageModules.ContainsKey(name))
return false;
var package = Assembly.LoadFile(Path.Combine(AppContext.BaseDirectory,
"modules",
$"NadekoBot.Modules.{name}",
$"NadekoBot.Modules.{name}.dll"));
var types = Services.LoadFrom(package);
var added = await CommandService.AddModulesAsync(package).ConfigureAwait(false);
_log.Info("Loaded {0} modules and {1} types.", added.Count(), types.Count());
_packageModules.Add(name, added);
_packageTypes.Add(name, types);
return true;
}
finally
{
_packageLocker.Release();
}
}
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection;
@ -17,57 +16,83 @@ namespace NadekoBot.Services
public interface INServiceProvider : IServiceProvider, IEnumerable<object>
{
T GetService<T>();
IEnumerable<Type> LoadFrom(Assembly assembly);
INServiceProvider AddManual<T>(T obj);
object Unload(Type t);
}
public class NServiceProvider : INServiceProvider
{
public class ServiceProviderBuilder
private readonly object _locker = new object();
private readonly Logger _log;
public readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
public IReadOnlyDictionary<Type, object> Services => _services;
public NServiceProvider()
{
private ConcurrentDictionary<Type, object> _dict = new ConcurrentDictionary<Type, object>();
private readonly Logger _log;
_log = LogManager.GetCurrentClassLogger();
}
public ServiceProviderBuilder()
public T GetService<T>()
{
return (T)((IServiceProvider)(this)).GetService(typeof(T));
}
object IServiceProvider.GetService(Type serviceType)
{
_services.TryGetValue(serviceType, out var toReturn);
return toReturn;
}
public INServiceProvider AddManual<T>(T obj)
{
lock (_locker)
{
_log = LogManager.GetCurrentClassLogger();
_services.TryAdd(typeof(T), obj);
}
return this;
}
public ServiceProviderBuilder AddManual<T>(T obj)
public IEnumerable<Type> LoadFrom(Assembly assembly)
{
List<Type> addedTypes = new List<Type>();
Type[] allTypes;
try
{
_dict.TryAdd(typeof(T), obj);
return this;
allTypes = assembly.GetTypes();
}
public NServiceProvider Build()
catch (ReflectionTypeLoadException ex)
{
return new NServiceProvider(_dict);
Console.WriteLine(ex.LoaderExceptions[0]);
return Enumerable.Empty<Type>();
}
public ServiceProviderBuilder LoadFrom(Assembly assembly)
{
var allTypes = assembly.GetTypes();
var services = new Queue<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract
var services = new Queue<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract
#if GLOBAL_NADEKO
&& x.GetTypeInfo().GetCustomAttribute<NoPublicBot>() == null
&& x.GetTypeInfo().GetCustomAttribute<NoPublicBot>() == null
#endif
)
.ToArray());
.ToArray());
var interfaces = new HashSet<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& x.GetTypeInfo().IsInterface));
addedTypes.AddRange(services);
var alreadyFailed = new Dictionary<Type, int>();
var interfaces = new HashSet<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& x.GetTypeInfo().IsInterface));
var alreadyFailed = new Dictionary<Type, int>();
lock (_locker)
{
var sw = Stopwatch.StartNew();
var swInstance = new Stopwatch();
while (services.Count > 0)
{
var type = services.Dequeue(); //get a type i need to make an instance of
if (_dict.TryGetValue(type, out _)) // if that type is already instantiated, skip
if (_services.TryGetValue(type, out _)) // if that type is already instantiated, skip
continue;
var ctor = type.GetConstructors()[0];
@ -79,7 +104,7 @@ namespace NadekoBot.Services
var args = new List<object>(argTypes.Length);
foreach (var arg in argTypes) //get constructor arguments from the dictionary of already instantiated types
{
if (_dict.TryGetValue(arg, out var argObj)) //if i got current one, add it to the list of instances and move on
if (_services.TryGetValue(arg, out var argObj)) //if i got current one, add it to the list of instances and move on
args.Add(argObj);
else //if i failed getting it, add it to the end, and break
{
@ -97,7 +122,7 @@ namespace NadekoBot.Services
}
if (args.Count != argTypes.Length)
continue;
// _log.Info("Loading " + type.Name);
swInstance.Restart();
var instance = ctor.Invoke(args.ToArray());
swInstance.Stop();
@ -105,38 +130,34 @@ namespace NadekoBot.Services
_log.Info($"{type.Name} took {swInstance.Elapsed.TotalSeconds:F2}s to load.");
var interfaceType = interfaces.FirstOrDefault(x => instance.GetType().GetInterfaces().Contains(x));
if (interfaceType != null)
_dict.TryAdd(interfaceType, instance);
{
addedTypes.Add(interfaceType);
_services.TryAdd(interfaceType, instance);
}
_dict.TryAdd(type, instance);
_services.TryAdd(type, instance);
}
sw.Stop();
_log.Info($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
return this;
}
return addedTypes;
}
private readonly ImmutableDictionary<Type, object> _services;
private NServiceProvider() { }
public NServiceProvider(IDictionary<Type, object> services)
public object Unload(Type t)
{
this._services = services.ToImmutableDictionary();
}
public T GetService<T>()
{
return (T)((IServiceProvider)(this)).GetService(typeof(T));
}
object IServiceProvider.GetService(Type serviceType)
{
_services.TryGetValue(serviceType, out var toReturn);
return toReturn;
lock (_locker)
{
if (_services.TryGetValue(t, out var obj))
{
_services.Remove(t);
return obj;
}
}
return null;
}
IEnumerator IEnumerable.GetEnumerator() => _services.Values.GetEnumerator();
public IEnumerator<object> GetEnumerator() => _services.Values.GetEnumerator();
}
}
}

View File

@ -2,6 +2,13 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>

View File

@ -2,6 +2,13 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>

View File

@ -4,27 +4,24 @@ using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
using NadekoBot.Modules.Gambling.Services;
namespace NadekoBot.Modules.Gambling
{
public partial class Gambling
{
[Group]
public class AnimalRacingCommands : NadekoSubmodule
public class AnimalRacingCommands : NadekoSubmodule<AnimalRaceService>
{
private readonly IBotConfigProvider _bc;
private readonly CurrencyService _cs;
private readonly DiscordSocketClient _client;
public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
public AnimalRacingCommands(IBotConfigProvider bc, CurrencyService cs, DiscordSocketClient client)
{
_bc = bc;
@ -39,7 +36,7 @@ namespace NadekoBot.Modules.Gambling
public Task Race()
{
var ar = new AnimalRace(_cs, _bc.BotConfig.RaceAnimals.Shuffle().ToArray());
if (!AnimalRaces.TryAdd(Context.Guild.Id, ar))
if (!_service.AnimalRaces.TryAdd(Context.Guild.Id, ar))
return Context.Channel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_already_started"));
ar.Initialize();
@ -66,7 +63,7 @@ namespace NadekoBot.Modules.Gambling
Task Ar_OnEnded(AnimalRace race)
{
_client.MessageReceived -= _client_MessageReceived;
AnimalRaces.TryRemove(Context.Guild.Id, out _);
_service.AnimalRaces.TryRemove(Context.Guild.Id, out _);
var winner = race.FinishedUsers[0];
if (race.FinishedUsers[0].Bet > 0)
{
@ -126,7 +123,7 @@ namespace NadekoBot.Modules.Gambling
private Task Ar_OnStartingFailed(AnimalRace race)
{
AnimalRaces.TryRemove(Context.Guild.Id, out _);
_service.AnimalRaces.TryRemove(Context.Guild.Id, out _);
return ReplyErrorLocalized("animal_race_failed");
}
@ -134,7 +131,7 @@ namespace NadekoBot.Modules.Gambling
[RequireContext(ContextType.Guild)]
public async Task JoinRace(int amount = 0)
{
if (!AnimalRaces.TryGetValue(Context.Guild.Id, out var ar))
if (!_service.AnimalRaces.TryGetValue(Context.Guild.Id, out var ar))
{
await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false);
return;

View File

@ -17,6 +17,7 @@ using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Gambling
{
//todo mess, needs unload thing too - refactor
public partial class Gambling
{
[Group]
@ -274,7 +275,6 @@ namespace NadekoBot.Modules.Gambling
_log.Warn("Stopping flower reaction event because it expired.");
await End();
}
}
}

View File

@ -2,6 +2,13 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>

View File

@ -0,0 +1,21 @@
using System.Threading.Tasks;
using NadekoBot.Services;
using System.Collections.Concurrent;
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
namespace NadekoBot.Modules.Gambling.Services
{
public class AnimalRaceService : INService, IUnloadableService
{
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
public Task Unload()
{
foreach (var kvp in AnimalRaces)
{
try { kvp.Value.Dispose(); } catch { }
}
return Task.CompletedTask;
}
}
}

View File

@ -176,7 +176,7 @@ namespace NadekoBot.Modules.Gambling
using (var file = _images.SlotEmojis[numbers[i]].ToStream())
using (var randomImage = ImageSharp.Image.Load(file))
{
bgImage.DrawImage(randomImage, 100, default(Size), new Point(95 + 142 * i, 330));
bgImage.DrawImage(randomImage, 100, default, new Point(95 + 142 * i, 330));
}
}
@ -189,7 +189,7 @@ namespace NadekoBot.Modules.Gambling
using (var fs = _images.SlotNumbers[digit].ToStream())
using (var img = ImageSharp.Image.Load(fs))
{
bgImage.DrawImage(img, 100, default(Size), new Point(230 - n * 16, 462));
bgImage.DrawImage(img, 100, default, new Point(230 - n * 16, 462));
}
n++;
} while ((printWon /= 10) != 0);
@ -202,7 +202,7 @@ namespace NadekoBot.Modules.Gambling
using (var fs = _images.SlotNumbers[digit].ToStream())
using (var img = ImageSharp.Image.Load(fs))
{
bgImage.DrawImage(img, 100, default(Size), new Point(395 - n * 16, 462));
bgImage.DrawImage(img, 100, default, new Point(395 - n * 16, 462));
}
n++;
} while ((printAmount /= 10) != 0);

View File

@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Gambling
Depraved,
Harlot
}
//todo unclaimed waifus should lose 5% of their value a day
[Group]
public class WaifuClaimCommands : NadekoSubmodule
{

View File

@ -2,26 +2,23 @@
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Extensions;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Games.Common.Acrophobia;
using NadekoBot.Modules.Games.Services;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class AcropobiaCommands : NadekoSubmodule
public class AcropobiaCommands : NadekoSubmodule<GamesService>
{
private readonly DiscordSocketClient _client;
//channelId, game
public static ConcurrentDictionary<ulong, Acrophobia> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, Acrophobia>();
public AcropobiaCommands(DiscordSocketClient client)
{
_client = client;
@ -36,7 +33,7 @@ namespace NadekoBot.Modules.Games
var channel = (ITextChannel)Context.Channel;
var game = new Acrophobia(submissionTime);
if (AcrophobiaGames.TryAdd(channel.Id, game))
if (_service.AcrophobiaGames.TryAdd(channel.Id, game))
{
try
{
@ -50,7 +47,7 @@ namespace NadekoBot.Modules.Games
finally
{
_client.MessageReceived -= _client_MessageReceived;
AcrophobiaGames.TryRemove(channel.Id, out game);
_service.AcrophobiaGames.TryRemove(channel.Id, out game);
game.Dispose();
}
}

View File

@ -0,0 +1,277 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services.Impl;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Common
{
public class TicTacToe
{
enum Phase
{
Starting,
Started,
Ended
}
private readonly ITextChannel _channel;
private readonly IGuildUser[] _users;
private readonly int?[,] _state;
private Phase _phase;
private int _curUserIndex;
private readonly SemaphoreSlim _moveLock;
private IGuildUser _winner;
private readonly string[] _numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
public Action<TicTacToe> OnEnded;
private IUserMessage _previousMessage;
private Timer _timeoutTimer;
private readonly NadekoStrings _strings;
private readonly DiscordSocketClient _client;
public TicTacToe(NadekoStrings strings, DiscordSocketClient client, ITextChannel channel, IGuildUser firstUser)
{
_channel = channel;
_strings = strings;
_client = client;
_users = new[] { firstUser, null };
_state = new int?[,] {
{ null, null, null },
{ null, null, null },
{ null, null, null },
};
_phase = Phase.Starting;
_moveLock = new SemaphoreSlim(1, 1);
}
private string GetText(string key, params object[] replacements) =>
_strings.GetText(key,
_channel.GuildId,
typeof(Games).Name.ToLowerInvariant(),
replacements);
public string GetState()
{
var sb = new StringBuilder();
for (var i = 0; i < _state.GetLength(0); i++)
{
for (var j = 0; j < _state.GetLength(1); j++)
{
sb.Append(_state[i, j] == null ? _numbers[i * 3 + j] : GetIcon(_state[i, j]));
if (j < _state.GetLength(1) - 1)
sb.Append("┃");
}
if (i < _state.GetLength(0) - 1)
sb.AppendLine("\n──────────");
}
return sb.ToString();
}
public EmbedBuilder GetEmbed(string title = null)
{
var embed = new EmbedBuilder()
.WithOkColor()
.WithDescription(Environment.NewLine + GetState())
.WithAuthor(eab => eab.WithName(GetText("vs", _users[0], _users[1])));
if (!string.IsNullOrWhiteSpace(title))
embed.WithTitle(title);
if (_winner == null)
{
if (_phase == Phase.Ended)
embed.WithFooter(efb => efb.WithText(GetText("ttt_no_moves")));
else
embed.WithFooter(efb => efb.WithText(GetText("ttt_users_move", _users[_curUserIndex])));
}
else
embed.WithFooter(efb => efb.WithText(GetText("ttt_has_won", _winner)));
return embed;
}
private static string GetIcon(int? val)
{
switch (val)
{
case 0:
return "❌";
case 1:
return "⭕";
case 2:
return "❎";
case 3:
return "🅾";
default:
return "⬛";
}
}
public async Task Start(IGuildUser user)
{
if (_phase == Phase.Started || _phase == Phase.Ended)
{
await _channel.SendErrorAsync(user.Mention + GetText("ttt_already_running")).ConfigureAwait(false);
return;
}
else if (_users[0] == user)
{
await _channel.SendErrorAsync(user.Mention + GetText("ttt_against_yourself")).ConfigureAwait(false);
return;
}
_users[1] = user;
_phase = Phase.Started;
_timeoutTimer = new Timer(async (_) =>
{
await _moveLock.WaitAsync();
try
{
if (_phase == Phase.Ended)
return;
_phase = Phase.Ended;
if (_users[1] != null)
{
_winner = _users[_curUserIndex ^= 1];
var del = _previousMessage?.DeleteAsync();
try
{
await _channel.EmbedAsync(GetEmbed(GetText("ttt_time_expired"))).ConfigureAwait(false);
if (del != null)
await del.ConfigureAwait(false);
}
catch { }
}
OnEnded?.Invoke(this);
}
catch { }
finally
{
_moveLock.Release();
}
}, null, 15000, Timeout.Infinite);
_client.MessageReceived += Client_MessageReceived;
_previousMessage = await _channel.EmbedAsync(GetEmbed(GetText("game_started"))).ConfigureAwait(false);
}
private bool IsDraw()
{
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (_state[i, j] == null)
return false;
}
}
return true;
}
private Task Client_MessageReceived(SocketMessage msg)
{
var _ = Task.Run(async () =>
{
await _moveLock.WaitAsync().ConfigureAwait(false);
try
{
var curUser = _users[_curUserIndex];
if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id)
return;
if (int.TryParse(msg.Content, out var index) &&
--index >= 0 &&
index <= 9 &&
_state[index / 3, index % 3] == null)
{
_state[index / 3, index % 3] = _curUserIndex;
// i'm lazy
if (_state[index / 3, 0] == _state[index / 3, 1] && _state[index / 3, 1] == _state[index / 3, 2])
{
_state[index / 3, 0] = _curUserIndex + 2;
_state[index / 3, 1] = _curUserIndex + 2;
_state[index / 3, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_state[0, index % 3] == _state[1, index % 3] && _state[1, index % 3] == _state[2, index % 3])
{
_state[0, index % 3] = _curUserIndex + 2;
_state[1, index % 3] = _curUserIndex + 2;
_state[2, index % 3] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
{
_state[0, 0] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
{
_state[0, 2] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 0] = _curUserIndex + 2;
_phase = Phase.Ended;
}
var reason = "";
if (_phase == Phase.Ended) // if user won, stop receiving moves
{
reason = GetText("ttt_matched_three");
_winner = _users[_curUserIndex];
_client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
}
else if (IsDraw())
{
reason = GetText("ttt_a_draw");
_phase = Phase.Ended;
_client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
}
var sendstate = Task.Run(async () =>
{
var del1 = msg.DeleteAsync();
var del2 = _previousMessage?.DeleteAsync();
try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } catch { }
try { await del1; } catch { }
try { if (del2 != null) await del2; } catch { }
});
_curUserIndex ^= 1;
_timeoutTimer.Change(15000, Timeout.Infinite);
}
}
finally
{
_moveLock.Release();
}
});
return Task.CompletedTask;
}
}
}

View File

@ -1,10 +1,10 @@
using Discord;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Common.Connect4;
using System.Collections.Concurrent;
using NadekoBot.Modules.Games.Services;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -13,12 +13,11 @@ namespace NadekoBot.Modules.Games
{
public partial class Games
{
public class Connect4Commands : NadekoSubmodule
[Group]
public class Connect4Commands : NadekoSubmodule<GamesService>
{
public static ConcurrentDictionary<ulong, Connect4Game> Games = new ConcurrentDictionary<ulong, Connect4Game>();
private readonly DiscordSocketClient _client;
//private readonly string[] numbers = new string[] { "⓪", " ①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨" };
private readonly string[] numbers = new string[] { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:"};
public Connect4Commands(DiscordSocketClient client)
@ -32,7 +31,7 @@ namespace NadekoBot.Modules.Games
{
var newGame = new Connect4Game(Context.User.Id, Context.User.ToString());
Connect4Game game;
if ((game = Games.GetOrAdd(Context.Channel.Id, newGame)) != newGame)
if ((game = _service.Connect4Games.GetOrAdd(Context.Channel.Id, newGame)) != newGame)
{
if (game.CurrentPhase != Connect4Game.Phase.Joining)
return;
@ -84,7 +83,7 @@ namespace NadekoBot.Modules.Games
Task Game_OnGameFailedToStart(Connect4Game arg)
{
if (Games.TryRemove(Context.Channel.Id, out var toDispose))
if (_service.Connect4Games.TryRemove(Context.Channel.Id, out var toDispose))
{
_client.MessageReceived -= _client_MessageReceived;
toDispose.Dispose();
@ -94,7 +93,7 @@ namespace NadekoBot.Modules.Games
Task Game_OnGameEnded(Connect4Game arg, Connect4Game.Result result)
{
if (Games.TryRemove(Context.Channel.Id, out var toDispose))
if (_service.Connect4Games.TryRemove(Context.Channel.Id, out var toDispose))
{
_client.MessageReceived -= _client_MessageReceived;
toDispose.Dispose();

View File

@ -1,19 +1,19 @@
using Discord.Commands;
using NadekoBot.Extensions;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Games.Common.Hangman;
using NadekoBot.Modules.Games.Services;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class HangmanCommands : NadekoSubmodule
public class HangmanCommands : NadekoSubmodule<GamesService>
{
private readonly DiscordSocketClient _client;
@ -22,9 +22,6 @@ namespace NadekoBot.Modules.Games
_client = client;
}
//channelId, game
public static ConcurrentDictionary<ulong, Hangman> HangmanGames { get; } = new ConcurrentDictionary<ulong, Hangman>();
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Hangmanlist()
@ -38,7 +35,7 @@ namespace NadekoBot.Modules.Games
{
var hm = new Hangman(type);
if (!HangmanGames.TryAdd(Context.Channel.Id, hm))
if (!_service.HangmanGames.TryAdd(Context.Channel.Id, hm))
{
hm.Dispose();
await ReplyErrorLocalized("hangman_running").ConfigureAwait(false);
@ -61,7 +58,7 @@ namespace NadekoBot.Modules.Games
await hm.EndedTask.ConfigureAwait(false);
_client.MessageReceived -= _client_MessageReceived;
HangmanGames.TryRemove(Context.Channel.Id, out _);
_service.HangmanGames.TryRemove(Context.Channel.Id, out _);
hm.Dispose();
Task _client_MessageReceived(SocketMessage msg)
@ -127,7 +124,7 @@ namespace NadekoBot.Modules.Games
[RequireContext(ContextType.Guild)]
public async Task HangmanStop()
{
if (HangmanGames.TryRemove(Context.Channel.Id, out var removed))
if (_service.HangmanGames.TryRemove(Context.Channel.Id, out var removed))
{
await removed.Stop().ConfigureAwait(false);
await ReplyConfirmLocalized("hangman_stopped").ConfigureAwait(false);

View File

@ -2,8 +2,15 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="2.0.0-alpha-build-00832" />
</ItemGroup>

View File

@ -3,8 +3,8 @@ using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Games.Common.Nunchi;
using NadekoBot.Modules.Games.Services;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
@ -12,9 +12,9 @@ namespace NadekoBot.Modules.Games
{
public partial class Games
{
public class NunchiCommands : NadekoSubmodule
[Group]
public class NunchiCommands : NadekoSubmodule<GamesService>
{
public static readonly ConcurrentDictionary<ulong, Nunchi> Games = new ConcurrentDictionary<ulong, Common.Nunchi.Nunchi>();
private readonly DiscordSocketClient _client;
public NunchiCommands(DiscordSocketClient client)
@ -30,7 +30,7 @@ namespace NadekoBot.Modules.Games
Nunchi nunchi;
//if a game was already active
if ((nunchi = Games.GetOrAdd(Context.Guild.Id, newNunchi)) != newNunchi)
if ((nunchi = _service.NunchiGames.GetOrAdd(Context.Guild.Id, newNunchi)) != newNunchi)
{
// join it
if (!await nunchi.Join(Context.User.Id, Context.User.ToString()))
@ -57,7 +57,7 @@ namespace NadekoBot.Modules.Games
var success = await nunchi.Initialize().ConfigureAwait(false);
if (!success)
{
if (Games.TryRemove(Context.Guild.Id, out var game))
if (_service.NunchiGames.TryRemove(Context.Guild.Id, out var game))
game.Dispose();
await ConfirmLocalized("nunchi_failed_to_start").ConfigureAwait(false);
}
@ -85,7 +85,7 @@ namespace NadekoBot.Modules.Games
Task Nunchi_OnGameEnded(Nunchi arg1, string arg2)
{
if (Games.TryRemove(Context.Guild.Id, out var game))
if (_service.NunchiGames.TryRemove(Context.Guild.Id, out var game))
{
_client.MessageReceived -= _client_MessageReceived;
game.Dispose();

View File

@ -22,19 +22,17 @@ namespace NadekoBot.Modules.Games
/// https://discord.gg/0TYNJfCU4De7YIk8
/// </summary>
[Group]
public class PlantPickCommands : NadekoSubmodule
public class PlantPickCommands : NadekoSubmodule<GamesService>
{
private readonly CurrencyService _cs;
private readonly IBotConfigProvider _bc;
private readonly GamesService _games;
private readonly DbService _db;
public PlantPickCommands(IBotConfigProvider bc, CurrencyService cs, GamesService games,
public PlantPickCommands(IBotConfigProvider bc, CurrencyService cs,
DbService db)
{
_bc = bc;
_cs = cs;
_games = games;
_db = db;
}
@ -49,7 +47,7 @@ namespace NadekoBot.Modules.Games
try { await Context.Message.DeleteAsync().ConfigureAwait(false); } catch { }
if (!_games.PlantedFlowers.TryRemove(channel.Id, out List<IUserMessage> msgs))
if (!_service.PlantedFlowers.TryRemove(channel.Id, out List<IUserMessage> msgs))
return;
await Task.WhenAll(msgs.Where(m => m != null).Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false);
@ -74,7 +72,7 @@ namespace NadekoBot.Modules.Games
return;
}
var imgData = _games.GetRandomCurrencyImage();
var imgData = _service.GetRandomCurrencyImage();
var msgToSend = GetText("planted",
Format.Bold(Context.User.ToString()),
@ -95,7 +93,7 @@ namespace NadekoBot.Modules.Games
var msgs = new IUserMessage[amount];
msgs[0] = msg;
_games.PlantedFlowers.AddOrUpdate(Context.Channel.Id, msgs.ToList(), (id, old) =>
_service.PlantedFlowers.AddOrUpdate(Context.Channel.Id, msgs.ToList(), (id, old) =>
{
old.AddRange(msgs);
return old;
@ -121,13 +119,13 @@ namespace NadekoBot.Modules.Games
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
{
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
_games.GenerationChannels.Add(channel.Id);
_service.GenerationChannels.Add(channel.Id);
enabled = true;
}
else
{
guildConfig.GenerateCurrencyChannelIds.Remove(toAdd);
_games.GenerationChannels.TryRemove(channel.Id);
_service.GenerationChannels.TryRemove(channel.Id);
enabled = false;
}
await uow.CompleteAsync();

View File

@ -17,10 +17,15 @@ using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Impl;
using Newtonsoft.Json;
using NLog;
using NadekoBot.Modules.Games.Common.Acrophobia;
using NadekoBot.Modules.Games.Common.Connect4;
using NadekoBot.Modules.Games.Common.Hangman;
using NadekoBot.Modules.Games.Common.Trivia;
using NadekoBot.Modules.Games.Common.Nunchi;
namespace NadekoBot.Modules.Games.Services
{
public class GamesService : INService
public class GamesService : INService, IUnloadableService
{
private readonly IBotConfigProvider _bc;
@ -38,7 +43,16 @@ namespace NadekoBot.Modules.Games.Services
public List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
public GamesService(CommandHandler cmd, IBotConfigProvider bc, IEnumerable<GuildConfig> gcs,
//channelId, game
public ConcurrentDictionary<ulong, Acrophobia> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, Acrophobia>();
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new ConcurrentDictionary<ulong, Connect4Game>();
public ConcurrentDictionary<ulong, Hangman> HangmanGames { get; } = new ConcurrentDictionary<ulong, Hangman>();
public ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
public Dictionary<ulong, TicTacToe> TicTacToeGames { get; } = new Dictionary<ulong, TicTacToe>();
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new ConcurrentDictionary<ulong, TypingGame>();
public ConcurrentDictionary<ulong, Nunchi> NunchiGames { get; } = new ConcurrentDictionary<ulong, Common.Nunchi.Nunchi>();
public GamesService(CommandHandler cmd, IBotConfigProvider bc, IEnumerable<GuildConfig> gcs,
NadekoStrings strings, IImagesService images, CommandHandler cmdHandler)
{
_bc = bc;
@ -74,6 +88,34 @@ namespace NadekoBot.Modules.Games.Services
}
}
public async Task Unload()
{
_t.Change(Timeout.Infinite, Timeout.Infinite);
_cmd.OnMessageNoTrigger -= PotentialFlowerGeneration;
AcrophobiaGames.ForEach(x => x.Value.Dispose());
AcrophobiaGames.Clear();
Connect4Games.ForEach(x => x.Value.Dispose());
Connect4Games.Clear();
HangmanGames.ForEach(x => x.Value.Dispose());
HangmanGames.Clear();
await Task.WhenAll(RunningTrivias.Select(x => x.Value.StopGame()));
RunningTrivias.Clear();
TicTacToeGames.Clear();
await Task.WhenAll(RunningContests.Select(x => x.Value.Stop()))
.ConfigureAwait(false);
RunningContests.Clear();
NunchiGames.ForEach(x => x.Value.Dispose());
NunchiGames.Clear();
}
private void DisposeElems(IEnumerable<IDisposable> xs)
{
xs.ForEach(x => x.Dispose());
}
public void AddTypingArticle(IUser user, string text)
{
TypingArticles.Add(new TypingArticle

View File

@ -3,7 +3,6 @@ using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Extensions;
using Newtonsoft.Json;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -16,9 +15,8 @@ namespace NadekoBot.Modules.Games
public partial class Games
{
[Group]
public class SpeedTypingCommands : NadekoSubmodule
public class SpeedTypingCommands : NadekoSubmodule<GamesService>
{
public static ConcurrentDictionary<ulong, TypingGame> RunningContests = new ConcurrentDictionary<ulong, TypingGame>();
private readonly GamesService _games;
private readonly DiscordSocketClient _client;
@ -34,7 +32,7 @@ namespace NadekoBot.Modules.Games
{
var channel = (ITextChannel)Context.Channel;
var game = RunningContests.GetOrAdd(channel.Guild.Id, id => new TypingGame(_games, _client, channel, Prefix));
var game = _service.RunningContests.GetOrAdd(channel.Guild.Id, id => new TypingGame(_games, _client, channel, Prefix));
if (game.IsActive)
{
@ -54,8 +52,7 @@ namespace NadekoBot.Modules.Games
public async Task TypeStop()
{
var channel = (ITextChannel)Context.Channel;
TypingGame game;
if (RunningContests.TryRemove(channel.Guild.Id, out game))
if (_service.RunningContests.TryRemove(channel.Guild.Id, out TypingGame game))
{
await game.Stop().ConfigureAwait(false);
return;

View File

@ -3,23 +3,21 @@ using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Services.Impl;
using NadekoBot.Modules.Games.Services;
using NadekoBot.Modules.Games.Common;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class TicTacToeCommands : NadekoSubmodule
public class TicTacToeCommands : NadekoSubmodule<GamesService>
{
//channelId/game
private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>();
private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1);
private readonly DiscordSocketClient _client;
@ -37,7 +35,7 @@ namespace NadekoBot.Modules.Games
await _sem.WaitAsync(1000);
try
{
if (_games.TryGetValue(channel.Id, out TicTacToe game))
if (_service.TicTacToeGames.TryGetValue(channel.Id, out TicTacToe game))
{
var _ = Task.Run(async () =>
{
@ -45,13 +43,13 @@ namespace NadekoBot.Modules.Games
});
return;
}
game = new TicTacToe(base._strings, (DiscordSocketClient)this._client, channel, (IGuildUser)Context.User);
_games.Add(channel.Id, game);
game = new TicTacToe(base._strings, this._client, channel, (IGuildUser)Context.User);
_service.TicTacToeGames.Add(channel.Id, game);
await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false);
game.OnEnded += (g) =>
{
_games.Remove(channel.Id);
_service.TicTacToeGames.Remove(channel.Id);
};
}
finally
@ -60,271 +58,5 @@ namespace NadekoBot.Modules.Games
}
}
}
public class TicTacToe
{
enum Phase
{
Starting,
Started,
Ended
}
private readonly ITextChannel _channel;
private readonly IGuildUser[] _users;
private readonly int?[,] _state;
private Phase _phase;
private int _curUserIndex;
private readonly SemaphoreSlim _moveLock;
private IGuildUser _winner;
private readonly string[] _numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
public Action<TicTacToe> OnEnded;
private IUserMessage _previousMessage;
private Timer _timeoutTimer;
private readonly NadekoStrings _strings;
private readonly DiscordSocketClient _client;
public TicTacToe(NadekoStrings strings, DiscordSocketClient client, ITextChannel channel, IGuildUser firstUser)
{
_channel = channel;
_strings = strings;
_client = client;
_users = new[] { firstUser, null };
_state = new int?[,] {
{ null, null, null },
{ null, null, null },
{ null, null, null },
};
_phase = Phase.Starting;
_moveLock = new SemaphoreSlim(1, 1);
}
private string GetText(string key, params object[] replacements) =>
_strings.GetText(key,
_channel.GuildId,
typeof(Games).Name.ToLowerInvariant(),
replacements);
public string GetState()
{
var sb = new StringBuilder();
for (var i = 0; i < _state.GetLength(0); i++)
{
for (var j = 0; j < _state.GetLength(1); j++)
{
sb.Append(_state[i, j] == null ? _numbers[i * 3 + j] : GetIcon(_state[i, j]));
if (j < _state.GetLength(1) - 1)
sb.Append("┃");
}
if (i < _state.GetLength(0) - 1)
sb.AppendLine("\n──────────");
}
return sb.ToString();
}
public EmbedBuilder GetEmbed(string title = null)
{
var embed = new EmbedBuilder()
.WithOkColor()
.WithDescription(Environment.NewLine + GetState())
.WithAuthor(eab => eab.WithName(GetText("vs", _users[0], _users[1])));
if (!string.IsNullOrWhiteSpace(title))
embed.WithTitle(title);
if (_winner == null)
{
if (_phase == Phase.Ended)
embed.WithFooter(efb => efb.WithText(GetText("ttt_no_moves")));
else
embed.WithFooter(efb => efb.WithText(GetText("ttt_users_move", _users[_curUserIndex])));
}
else
embed.WithFooter(efb => efb.WithText(GetText("ttt_has_won", _winner)));
return embed;
}
private static string GetIcon(int? val)
{
switch (val)
{
case 0:
return "❌";
case 1:
return "⭕";
case 2:
return "❎";
case 3:
return "🅾";
default:
return "⬛";
}
}
public async Task Start(IGuildUser user)
{
if (_phase == Phase.Started || _phase == Phase.Ended)
{
await _channel.SendErrorAsync(user.Mention + GetText("ttt_already_running")).ConfigureAwait(false);
return;
}
else if (_users[0] == user)
{
await _channel.SendErrorAsync(user.Mention + GetText("ttt_against_yourself")).ConfigureAwait(false);
return;
}
_users[1] = user;
_phase = Phase.Started;
_timeoutTimer = new Timer(async (_) =>
{
await _moveLock.WaitAsync();
try
{
if (_phase == Phase.Ended)
return;
_phase = Phase.Ended;
if (_users[1] != null)
{
_winner = _users[_curUserIndex ^= 1];
var del = _previousMessage?.DeleteAsync();
try
{
await _channel.EmbedAsync(GetEmbed(GetText("ttt_time_expired"))).ConfigureAwait(false);
if (del != null)
await del.ConfigureAwait(false);
}
catch { }
}
OnEnded?.Invoke(this);
}
catch { }
finally
{
_moveLock.Release();
}
}, null, 15000, Timeout.Infinite);
_client.MessageReceived += Client_MessageReceived;
_previousMessage = await _channel.EmbedAsync(GetEmbed(GetText("game_started"))).ConfigureAwait(false);
}
private bool IsDraw()
{
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (_state[i, j] == null)
return false;
}
}
return true;
}
private Task Client_MessageReceived(SocketMessage msg)
{
var _ = Task.Run(async () =>
{
await _moveLock.WaitAsync().ConfigureAwait(false);
try
{
var curUser = _users[_curUserIndex];
if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id)
return;
if (int.TryParse(msg.Content, out var index) &&
--index >= 0 &&
index <= 9 &&
_state[index / 3, index % 3] == null)
{
_state[index / 3, index % 3] = _curUserIndex;
// i'm lazy
if (_state[index / 3, 0] == _state[index / 3, 1] && _state[index / 3, 1] == _state[index / 3, 2])
{
_state[index / 3, 0] = _curUserIndex + 2;
_state[index / 3, 1] = _curUserIndex + 2;
_state[index / 3, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_state[0, index % 3] == _state[1, index % 3] && _state[1, index % 3] == _state[2, index % 3])
{
_state[0, index % 3] = _curUserIndex + 2;
_state[1, index % 3] = _curUserIndex + 2;
_state[2, index % 3] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
{
_state[0, 0] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
{
_state[0, 2] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 0] = _curUserIndex + 2;
_phase = Phase.Ended;
}
var reason = "";
if (_phase == Phase.Ended) // if user won, stop receiving moves
{
reason = GetText("ttt_matched_three");
_winner = _users[_curUserIndex];
_client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
}
else if (IsDraw())
{
reason = GetText("ttt_a_draw");
_phase = Phase.Ended;
_client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
}
var sendstate = Task.Run(async () =>
{
var del1 = msg.DeleteAsync();
var del2 = _previousMessage?.DeleteAsync();
try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } catch { }
try { await del1; } catch { }
try { if (del2 != null) await del2; } catch { }
});
_curUserIndex ^= 1;
_timeoutTimer.Change(15000, Timeout.Infinite);
}
}
finally
{
_moveLock.Release();
}
});
return Task.CompletedTask;
}
}
}
}

View File

@ -3,24 +3,23 @@ using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Games.Common.Trivia;
using NadekoBot.Modules.Games.Services;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
//todo move games to service, unload
[Group]
public class TriviaCommands : NadekoSubmodule
public class TriviaCommands : NadekoSubmodule<GamesService>
{
private readonly CurrencyService _cs;
private readonly DiscordSocketClient _client;
private readonly IBotConfigProvider _bc;
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
public TriviaCommands(DiscordSocketClient client, IBotConfigProvider bc, CurrencyService cs)
{
_cs = cs;
@ -48,7 +47,7 @@ namespace NadekoBot.Modules.Games
var isPokemon = additionalArgs.Contains("pokemon");
var trivia = new TriviaGame(_strings, _client, _bc, _cs, channel.Guild, channel, showHints, winReq, isPokemon);
if (RunningTrivias.TryAdd(channel.Guild.Id, trivia))
if (_service.RunningTrivias.TryAdd(channel.Guild.Id, trivia))
{
try
{
@ -56,7 +55,7 @@ namespace NadekoBot.Modules.Games
}
finally
{
RunningTrivias.TryRemove(channel.Guild.Id, out trivia);
_service.RunningTrivias.TryRemove(channel.Guild.Id, out trivia);
await trivia.EnsureStopped().ConfigureAwait(false);
}
return;
@ -72,8 +71,7 @@ namespace NadekoBot.Modules.Games
{
var channel = (ITextChannel)Context.Channel;
TriviaGame trivia;
if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia))
if (_service.RunningTrivias.TryGetValue(channel.Guild.Id, out TriviaGame trivia))
{
await channel.SendConfirmAsync(GetText("leaderboard"), trivia.GetLeaderboard()).ConfigureAwait(false);
return;
@ -88,8 +86,7 @@ namespace NadekoBot.Modules.Games
{
var channel = (ITextChannel)Context.Channel;
TriviaGame trivia;
if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia))
if (_service.RunningTrivias.TryGetValue(channel.Guild.Id, out TriviaGame trivia))
{
await trivia.StopGame().ConfigureAwait(false);
return;

View File

@ -30,22 +30,15 @@ namespace NadekoBot.Modules.Music
private readonly IGoogleApiService _google;
private readonly DbService _db;
public Music(DiscordSocketClient client, IBotCredentials creds, IGoogleApiService google,
public Music(DiscordSocketClient client,
IBotCredentials creds,
IGoogleApiService google,
DbService db)
{
_client = client;
_creds = creds;
_google = google;
_db = db;
//_client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
_client.LeftGuild += _client_LeftGuild;
}
private Task _client_LeftGuild(SocketGuild arg)
{
var t = _service.DestroyPlayer(arg.Id);
return Task.CompletedTask;
}
//todo changing server region is bugged again

View File

@ -2,10 +2,14 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>

View File

@ -18,7 +18,7 @@ using NadekoBot.Modules.Music.Common.SongResolver;
namespace NadekoBot.Modules.Music.Services
{
public class MusicService : INService
public class MusicService : INService, IUnloadableService
{
public const string MusicDataPath = "data/musicdata";
@ -47,13 +47,25 @@ namespace NadekoBot.Modules.Music.Services
_creds = creds;
_log = LogManager.GetCurrentClassLogger();
_client.LeftGuild += _client_LeftGuild;
try { Directory.Delete(MusicDataPath, true); } catch { }
_defaultVolumes = new ConcurrentDictionary<ulong, float>(gcs.ToDictionary(x => x.GuildId, x => x.DefaultMusicVolume));
Directory.CreateDirectory(MusicDataPath);
}
//_t = new Timer(_ => _log.Info(MusicPlayers.Count(x => x.Value.Current.Current != null)), null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
public Task Unload()
{
_client.LeftGuild -= _client_LeftGuild;
return Task.CompletedTask;
}
private Task _client_LeftGuild(SocketGuild arg)
{
var t = DestroyPlayer(arg.Id);
return Task.CompletedTask;
}
public float GetDefaultVolume(ulong guildId)

View File

@ -19,6 +19,7 @@ namespace NadekoBot.Modules.NSFW
// thanks to halitalf for adding autoboob and autobutt features :D
public class NSFW : NadekoTopLevelModule<SearchesService>
{
//todo clear when module unloaded
private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentDictionary<ulong, Timer> _autoBoobTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentDictionary<ulong, Timer> _autoButtTimers = new ConcurrentDictionary<ulong, Timer>();

View File

@ -2,6 +2,13 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>

View File

@ -2,6 +2,13 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>

View File

@ -2,6 +2,13 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>

View File

@ -13,7 +13,7 @@ using NLog;
namespace NadekoBot.Modules.Utility.Services
{
public class ConverterService : INService
public class ConverterService : INService, IUnloadableService
{
public List<ConvertUnit> Units { get; } = new List<ConvertUnit>();
private readonly Logger _log;
@ -122,6 +122,12 @@ namespace NadekoBot.Modules.Utility.Services
_log.Warn("Failed updating currency. Ignore this.");
}
}
public Task Unload()
{
_currencyUpdater.Change(Timeout.Infinite, Timeout.Infinite);
return Task.CompletedTask;
}
}
public class MeasurementUnit

View File

@ -33,7 +33,7 @@ namespace NadekoBot.Modules.Utility.Services
.Where(x => x.Guild != null)));
})
.Where(x => x.Item2 != null)
.ToDictionary(x => x.Item1, x => x.Item2));
.ToDictionary(x => x.GuildId, x => x.Item2));
RepeaterReady = true;
});
}

View File

@ -15,7 +15,7 @@ using NLog;
namespace NadekoBot.Modules.Utility.Services
{
public class PatreonRewardsService : INService
public class PatreonRewardsService : INService, IUnloadableService
{
private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
@ -33,15 +33,16 @@ namespace NadekoBot.Modules.Utility.Services
private readonly string cacheFileName = "./patreon-rewards.json";
public PatreonRewardsService(IBotCredentials creds, DbService db, CurrencyService currency,
public PatreonRewardsService(IBotCredentials creds, DbService db,
CurrencyService currency,
DiscordSocketClient client)
{
_log = LogManager.GetCurrentClassLogger();
_creds = creds;
_db = db;
_currency = currency;
if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken))
return;
_log = LogManager.GetCurrentClassLogger();
Updater = new Timer(async (load) => await RefreshPledges((bool)load),
client.ShardId == 0, client.ShardId == 0 ? TimeSpan.Zero : TimeSpan.FromMinutes(2), Interval);
}
@ -171,5 +172,11 @@ namespace NadekoBot.Modules.Utility.Services
claimLockJustInCase.Release();
}
}
public Task Unload()
{
Updater.Change(Timeout.Infinite, Timeout.Infinite);
return Task.CompletedTask;
}
}
}

View File

@ -8,10 +8,10 @@ using Discord.WebSocket;
using NadekoBot.Common.Replacements;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Impl;
using NLog;
using System.Collections.Generic;
namespace NadekoBot.Modules.Utility.Services
{
@ -29,8 +29,10 @@ namespace NadekoBot.Modules.Utility.Services
private readonly DiscordSocketClient _client;
private readonly DbService _db;
public RemindService(DiscordSocketClient client, IBotConfigProvider config, DbService db,
StartingGuildsService guilds, IUnitOfWork uow)
public RemindService(DiscordSocketClient client,
IBotConfigProvider config,
DbService db,
StartingGuildsService guilds)
{
_config = config;
_client = client;
@ -39,8 +41,12 @@ namespace NadekoBot.Modules.Utility.Services
cancelSource = new CancellationTokenSource();
cancelAllToken = cancelSource.Token;
var reminders = uow.Reminders.GetIncludedReminders(guilds).ToList();
List<Reminder> reminders;
using (var uow = _db.UnitOfWork)
{
reminders = uow.Reminders.GetIncludedReminders(guilds).ToList();
}
RemindMessageFormat = _config.BotConfig.RemindMessageFormat;
foreach (var r in reminders)

View File

@ -17,22 +17,24 @@ using Discord.Net;
namespace NadekoBot.Modules.Utility.Services
{
public class StreamRoleService : INService
public class StreamRoleService : INService, IUnloadableService
{
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly ConcurrentDictionary<ulong, StreamRoleSettings> guildSettings;
private readonly Logger _log;
public StreamRoleService(DiscordSocketClient client, DbService db, IEnumerable<GuildConfig> gcs)
{
this._db = db;
this._log = LogManager.GetCurrentClassLogger();
this._db = db;
this._client = client;
guildSettings = gcs.ToDictionary(x => x.GuildId, x => x.StreamRole)
.Where(x => x.Value != null && x.Value.Enabled)
.ToConcurrent();
client.GuildMemberUpdated += Client_GuildMemberUpdated;
_client.GuildMemberUpdated += Client_GuildMemberUpdated;
var _ = Task.Run(async () =>
{
@ -47,6 +49,12 @@ namespace NadekoBot.Modules.Utility.Services
});
}
public Task Unload()
{
_client.GuildMemberUpdated -= Client_GuildMemberUpdated;
return Task.CompletedTask;
}
private Task Client_GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after)
{
var _ = Task.Run(async () =>

View File

@ -11,7 +11,7 @@ using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Utility.Services
{
public class VerboseErrorsService : INService
public class VerboseErrorsService : INService, IUnloadableService
{
private readonly ConcurrentHashSet<ulong> guildsEnabled;
private readonly DbService _db;
@ -24,11 +24,17 @@ namespace NadekoBot.Modules.Utility.Services
_ch = ch;
_hs = hs;
ch.CommandErrored += LogVerboseError;
_ch.CommandErrored += LogVerboseError;
guildsEnabled = new ConcurrentHashSet<ulong>(gcs.Where(x => x.VerboseErrors).Select(x => x.GuildId));
}
public Task Unload()
{
_ch.CommandErrored -= LogVerboseError;
return Task.CompletedTask;
}
private async Task LogVerboseError(CommandInfo cmd, ITextChannel channel, string reason)
{
if (channel == null || !guildsEnabled.Contains(channel.GuildId))
@ -73,6 +79,5 @@ namespace NadekoBot.Modules.Utility.Services
return enabled;
}
}
}

View File

@ -99,8 +99,7 @@ namespace NadekoBot.Modules.Xp
return;
}
ClubInfo club;
if (!_service.GetClubByName(clubName, out club))
if (!_service.GetClubByName(clubName, out ClubInfo club))
{
await ReplyErrorLocalized("club_not_exists").ConfigureAwait(false);
return;

View File

@ -2,12 +2,19 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="2.0.0-alpha-build-00832" />
</ItemGroup>
<PropertyGroup>
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
</ItemGroup>

View File

@ -4,8 +4,6 @@ using NadekoBot.Services.Database.Models;
using Discord;
using NadekoBot.Modules.Xp.Common;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
namespace NadekoBot.Modules.Xp.Services
{

View File

@ -26,7 +26,7 @@ using ImageSharp.Drawing.Brushes;
namespace NadekoBot.Modules.Xp.Services
{
public class XpService : INService
public class XpService : INService, IUnloadableService
{
private enum NotifOf { Server, Global } // is it a server level-up or global level-up notification
@ -55,7 +55,9 @@ namespace NadekoBot.Modules.Xp.Services
private readonly ConcurrentQueue<UserCacheItem> _addMessageXp
= new ConcurrentQueue<UserCacheItem>();
private readonly Timer updateXpTimer;
private readonly Timer _updateXpTimer;
private readonly CancellationTokenSource _clearRewardTimerTokenSource;
private readonly Task _clearRewardTimer;
private readonly HttpClient http = new HttpClient();
private FontFamily _usernameFontFamily;
private FontFamily _clubFontFamily;
@ -115,7 +117,7 @@ namespace NadekoBot.Modules.Xp.Services
_cmd.OnMessageNoTrigger += _cmd_OnMessageNoTrigger;
updateXpTimer = new Timer(async _ =>
_updateXpTimer = new Timer(async _ =>
{
try
{
@ -241,19 +243,20 @@ namespace NadekoBot.Modules.Xp.Services
_log.Warn(ex);
}
}, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
_clearRewardTimerTokenSource = new CancellationTokenSource();
var token = _clearRewardTimerTokenSource.Token;
//just a first line, in order to prevent queries. But since other shards can try to do this too,
//i'll check in the db too.
var clearRewardTimer = Task.Run(async () =>
_clearRewardTimer = Task.Run(async () =>
{
while (true)
while (!token.IsCancellationRequested)
{
_rewardedUsers.Clear();
await Task.Delay(TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout));
}
});
}, token);
}
public IEnumerable<XpRoleReward> GetRoleRewards(ulong id)
@ -751,5 +754,17 @@ namespace NadekoBot.Modules.Xp.Services
return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
}
public Task Unload()
{
_cmd.OnMessageNoTrigger -= _cmd_OnMessageNoTrigger;
if (!_clearRewardTimerTokenSource.IsCancellationRequested)
_clearRewardTimerTokenSource.Cancel();
_updateXpTimer.Change(Timeout.Infinite, Timeout.Infinite);
_clearRewardTimerTokenSource.Dispose();
return Task.CompletedTask;
}
}
}

View File

@ -1,7 +1,6 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Common;
using NadekoBot.Common.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Common;

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.3
VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}"
EndProject
@ -13,25 +13,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot", "src\NadekoBot\NadekoBot.csproj", "{45EC1473-C678-4857-A544-07DFE0D0B478}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.CustomReactions", "NadekoBot.Modules.CustomReactions\NadekoBot.Modules.CustomReactions.csproj", "{60F1217E-5E01-42DC-9B86-0FE910EDE081}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Core", "NadekoBot.Core\NadekoBot.Core.csproj", "{A6CCEFBD-DCF2-482C-9643-47664683548F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Modules.Xp", "NadekoBot.Modules.Xp\NadekoBot.Modules.Xp.csproj", "{41A2DEBA-E8AE-4EC8-A58F-01C4B6E599E5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Xp", "NadekoBot.Modules.Xp\NadekoBot.Modules.Xp.csproj", "{41A2DEBA-E8AE-4EC8-A58F-01C4B6E599E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Modules.Utility", "NadekoBot.Modules.Utility\NadekoBot.Modules.Utility.csproj", "{07606931-CB55-4D20-8369-4E086B00EC52}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Utility", "NadekoBot.Modules.Utility\NadekoBot.Modules.Utility.csproj", "{07606931-CB55-4D20-8369-4E086B00EC52}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Modules.Searches", "NadekoBot.Module.Searches\NadekoBot.Modules.Searches.csproj", "{8BEE9984-3EB3-45BE-A5CF-0DB912626B81}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Searches", "NadekoBot.Module.Searches\NadekoBot.Modules.Searches.csproj", "{8BEE9984-3EB3-45BE-A5CF-0DB912626B81}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Modules.Nsfw", "NadekoBot.Modules.Nsfw\NadekoBot.Modules.Nsfw.csproj", "{75ED72EC-7AB3-4B12-A2DA-3655C740B356}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Nsfw", "NadekoBot.Modules.Nsfw\NadekoBot.Modules.Nsfw.csproj", "{75ED72EC-7AB3-4B12-A2DA-3655C740B356}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Modules.Games", "NadekoBot.Modules.Games\NadekoBot.Modules.Games.csproj", "{FF6BDE61-24B4-4DC2-99EE-409EA1650180}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Games", "NadekoBot.Modules.Games\NadekoBot.Modules.Games.csproj", "{FF6BDE61-24B4-4DC2-99EE-409EA1650180}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Modules.Gambling", "NadekoBot.Modules.Gambling\NadekoBot.Modules.Gambling.csproj", "{6436A700-694E-412C-92AE-B793FCD44E84}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Gambling", "NadekoBot.Modules.Gambling\NadekoBot.Modules.Gambling.csproj", "{6436A700-694E-412C-92AE-B793FCD44E84}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Modules.Pokemon", "NadekoBot.Modules.Pokemon\NadekoBot.Modules.Pokemon.csproj", "{30463C26-555B-4760-9459-A16DFA015DFA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Pokemon", "NadekoBot.Modules.Pokemon\NadekoBot.Modules.Pokemon.csproj", "{30463C26-555B-4760-9459-A16DFA015DFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Modules.Music", "NadekoBot.Modules.Music\NadekoBot.Modules.Music.csproj", "{674E28A6-30B1-413D-BBD3-E5F71614A00F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Modules.Music", "NadekoBot.Modules.Music\NadekoBot.Modules.Music.csproj", "{674E28A6-30B1-413D-BBD3-E5F71614A00F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -46,12 +44,6 @@ Global
{45EC1473-C678-4857-A544-07DFE0D0B478}.GlobalNadeko|Any CPU.Build.0 = Release|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.Build.0 = Release|Any CPU
{60F1217E-5E01-42DC-9B86-0FE910EDE081}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{60F1217E-5E01-42DC-9B86-0FE910EDE081}.Debug|Any CPU.Build.0 = Debug|Any CPU
{60F1217E-5E01-42DC-9B86-0FE910EDE081}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
{60F1217E-5E01-42DC-9B86-0FE910EDE081}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
{60F1217E-5E01-42DC-9B86-0FE910EDE081}.Release|Any CPU.ActiveCfg = Release|Any CPU
{60F1217E-5E01-42DC-9B86-0FE910EDE081}.Release|Any CPU.Build.0 = Release|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
@ -112,7 +104,6 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{60F1217E-5E01-42DC-9B86-0FE910EDE081} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{A6CCEFBD-DCF2-482C-9643-47664683548F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{41A2DEBA-E8AE-4EC8-A58F-01C4B6E599E5} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{07606931-CB55-4D20-8369-4E086B00EC52} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}

View File

@ -4,16 +4,13 @@
<TargetFramework>netcoreapp2.0</TargetFramework>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<OutputType>exe</OutputType>
<AssetTargetFallback>$(AssetTargetFallback);dnxcore50;portable-net45+win8+wpa81</AssetTargetFallback>
<ApplicationIcon>nadeko_icon.ico</ApplicationIcon>
<RuntimeIdentifiers>win7-x64<!--;ubuntu.14.04-x64;osx.10.10-x64 --></RuntimeIdentifiers>
<Configurations>Debug;Release;global_nadeko</Configurations>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Version)' == '' ">
<VersionPrefix Condition=" '$(VersionPrefix)' == '' ">1.9.1</VersionPrefix>
<VersionPrefix Condition=" '$(VersionPrefix)' == '' ">2.0.0</VersionPrefix>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionPrefix).$(VersionSuffix)</Version>
<Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version>
</PropertyGroup>
@ -23,6 +20,12 @@
<None Update="credentials_example.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="data\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="_strings\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="libsodium.dll;opus.dll;libsodium.so;libopus.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@ -66,6 +69,5 @@
<ItemGroup>
<ProjectReference Include="..\..\NadekoBot.Core\NadekoBot.Core.csproj" />
<ProjectReference Include="..\..\NadekoBot.Modules.CustomReactions\NadekoBot.Modules.CustomReactions.csproj" />
</ItemGroup>
</Project>

View File

@ -1,18 +1,20 @@
namespace NadekoBot
using System.Threading.Tasks;
namespace NadekoBot
{
public class Program
{
public static void Main(string[] args)
public static Task Main(string[] args)
{
if (args.Length == 3 && int.TryParse(args[0], out int shardId) && int.TryParse(args[1], out int parentProcessId))
{
int? port = null;
if (int.TryParse(args[2], out var outPort))
port = outPort;
new NadekoBot(shardId, parentProcessId, outPort).RunAndBlockAsync(args).GetAwaiter().GetResult();
return new NadekoBot(shardId, parentProcessId, outPort).RunAndBlockAsync(args);
}
else
new NadekoBot(0, 0).RunAndBlockAsync(args).GetAwaiter().GetResult();
return new NadekoBot(0, 0).RunAndBlockAsync(args);
}
}
}