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 System.Threading.Tasks;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Modules.CustomReactions.Services;
namespace NadekoBot.Common.TypeReaders namespace NadekoBot.Common.TypeReaders
{ {
@ -28,38 +29,38 @@ namespace NadekoBot.Common.TypeReaders
} }
} }
//todo dependency on the module //todo dependency on the module
//public class CommandOrCrTypeReader : CommandTypeReader public class CommandOrCrTypeReader : CommandTypeReader
//{ {
// public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services) public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider services)
// { {
// input = input.ToUpperInvariant(); input = input.ToUpperInvariant();
// var _crs = ((INServiceProvider)services).GetService<CustomReactionsService>(); var _crs = ((INServiceProvider)services).GetService<CustomReactionsService>();
// if (_crs.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input)) if (_crs.GlobalReactions.Any(x => x.Trigger.ToUpperInvariant() == input))
// { {
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input)); return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
// } }
// var guild = context.Guild; var guild = context.Guild;
// if (guild != null) if (guild != null)
// { {
// if (_crs.GuildReactions.TryGetValue(guild.Id, out var crs)) if (_crs.GuildReactions.TryGetValue(guild.Id, out var crs))
// { {
// if (crs.Concat(_crs.GlobalReactions).Any(x => x.Trigger.ToUpperInvariant() == input)) if (crs.Concat(_crs.GlobalReactions).Any(x => x.Trigger.ToUpperInvariant() == input))
// { {
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input)); return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input));
// } }
// } }
// } }
// var cmd = await base.Read(context, input, services); var cmd = await base.Read(context, input, services);
// if (cmd.IsSuccess) if (cmd.IsSuccess)
// { {
// return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name)); return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name));
// } }
// return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found."); return TypeReaderResult.FromError(CommandError.ParseFailed, "No such command or cr found.");
// } }
//} }
public class CommandOrCrInfo 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] [OwnerOnly]
public async Task StartupCommandAdd([Remainder] string cmdText) public async Task StartupCommandAdd([Remainder] string cmdText)
{ {
//todo don't let .die be a startup command
var guser = ((IGuildUser)Context.User); var guser = ((IGuildUser)Context.User);
var cmd = new StartupCommand() 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> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>

View File

@ -1,4 +1,6 @@
namespace NadekoBot.Services using System.Threading.Tasks;
namespace NadekoBot.Services
{ {
/// <summary> /// <summary>
/// All services must implement this interface in order to be auto-discovered by the DI system /// 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) if (shardId < 0)
throw new ArgumentOutOfRangeException(nameof(shardId)); 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(); LogSetup.SetupLogger();
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
TerribleElevatedPermissionCheck(); TerribleElevatedPermissionCheck();
@ -142,19 +131,17 @@ namespace NadekoBot
//var localization = new Localization(_botConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db); //var localization = new Localization(_botConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db);
//initialize Services //initialize Services
Services = new NServiceProvider.ServiceProviderBuilder() Services = new NServiceProvider()
.AddManual<IBotCredentials>(Credentials) .AddManual<IBotCredentials>(Credentials)
.AddManual(_db) .AddManual(_db)
.AddManual(Client) .AddManual(Client)
.AddManual(CommandService) .AddManual(CommandService)
.AddManual(botConfigProvider) .AddManual(botConfigProvider)
//.AddManual<ILocalization>(localization)
.AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this .AddManual<IEnumerable<GuildConfig>>(AllGuildConfigs) //todo wrap this
.AddManual<NadekoBot>(this) .AddManual<NadekoBot>(this)
.AddManual<IUnitOfWork>(uow) .AddManual<IUnitOfWork>(uow)
.AddManual<IDataCache>(new RedisCache(Client.CurrentUser.Id)) .AddManual<IDataCache>(new RedisCache(Client.CurrentUser.Id));
.LoadFrom(Assembly.GetEntryAssembly()) Services.LoadFrom(Assembly.GetAssembly(typeof(CommandHandler)));
.Build();
var commandHandler = Services.GetService<CommandHandler>(); var commandHandler = Services.GetService<CommandHandler>();
commandHandler.AddServices(Services); commandHandler.AddServices(Services);
@ -163,12 +150,13 @@ namespace NadekoBot
CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader()); CommandService.AddTypeReader<PermissionAction>(new PermissionActionTypeReader());
CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader()); CommandService.AddTypeReader<CommandInfo>(new CommandTypeReader());
//todo module dependency //todo module dependency
//CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader()); CommandService.AddTypeReader<CommandOrCrInfo>(new CommandOrCrTypeReader());
CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(CommandService)); CommandService.AddTypeReader<ModuleInfo>(new ModuleTypeReader(CommandService));
CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(CommandService)); CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(CommandService));
CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client)); CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client));
//CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader()); //CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader());
} }
Services.Unload(typeof(IUnitOfWork)); // unload it after the startup
} }
private async Task LoginAsync(string token) private async Task LoginAsync(string token)
@ -225,8 +213,8 @@ namespace NadekoBot
public async Task RunAsync(params string[] args) public async Task RunAsync(params string[] args)
{ {
if(Client.ShardId == 0) if (Client.ShardId == 0)
_log.Info("Starting NadekoBot v" + StatsService.BotVersion); _log.Info("Starting NadekoBot v" + StatsService.BotVersion);
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
@ -255,7 +243,7 @@ namespace NadekoBot
#endif #endif
//unload modules which are not available on the public bot //unload modules which are not available on the public bot
if(isPublicNadeko) if (isPublicNadeko)
CommandService CommandService
.Modules .Modules
.ToArray() .ToArray()
@ -372,5 +360,80 @@ namespace NadekoBot
var sub = Services.GetService<IDataCache>().Redis.GetSubscriber(); var sub = Services.GetService<IDataCache>().Redis.GetSubscriber();
return sub.PublishAsync(Client.CurrentUser.Id + "_status.game_set", JsonConvert.SerializeObject(obj)); 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;
using System.Collections; using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Reflection; using System.Reflection;
@ -17,57 +16,83 @@ namespace NadekoBot.Services
public interface INServiceProvider : IServiceProvider, IEnumerable<object> public interface INServiceProvider : IServiceProvider, IEnumerable<object>
{ {
T GetService<T>(); T GetService<T>();
IEnumerable<Type> LoadFrom(Assembly assembly);
INServiceProvider AddManual<T>(T obj);
object Unload(Type t);
} }
public class NServiceProvider : INServiceProvider 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>(); _log = LogManager.GetCurrentClassLogger();
private readonly Logger _log; }
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); allTypes = assembly.GetTypes();
return this;
} }
catch (ReflectionTypeLoadException ex)
public NServiceProvider Build()
{ {
return new NServiceProvider(_dict); Console.WriteLine(ex.LoaderExceptions[0]);
return Enumerable.Empty<Type>();
} }
var services = new Queue<Type>(allTypes
public ServiceProviderBuilder LoadFrom(Assembly assembly) .Where(x => x.GetInterfaces().Contains(typeof(INService))
{ && !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract
var allTypes = assembly.GetTypes();
var services = new Queue<Type>(allTypes
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& !x.GetTypeInfo().IsInterface && !x.GetTypeInfo().IsAbstract
#if GLOBAL_NADEKO #if GLOBAL_NADEKO
&& x.GetTypeInfo().GetCustomAttribute<NoPublicBot>() == null && x.GetTypeInfo().GetCustomAttribute<NoPublicBot>() == null
#endif #endif
) )
.ToArray()); .ToArray());
var interfaces = new HashSet<Type>(allTypes addedTypes.AddRange(services);
.Where(x => x.GetInterfaces().Contains(typeof(INService))
&& x.GetTypeInfo().IsInterface));
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 sw = Stopwatch.StartNew();
var swInstance = new Stopwatch(); var swInstance = new Stopwatch();
while (services.Count > 0) while (services.Count > 0)
{ {
var type = services.Dequeue(); //get a type i need to make an instance of 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; continue;
var ctor = type.GetConstructors()[0]; var ctor = type.GetConstructors()[0];
@ -79,7 +104,7 @@ namespace NadekoBot.Services
var args = new List<object>(argTypes.Length); var args = new List<object>(argTypes.Length);
foreach (var arg in argTypes) //get constructor arguments from the dictionary of already instantiated types 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); args.Add(argObj);
else //if i failed getting it, add it to the end, and break 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) if (args.Count != argTypes.Length)
continue; continue;
// _log.Info("Loading " + type.Name);
swInstance.Restart(); swInstance.Restart();
var instance = ctor.Invoke(args.ToArray()); var instance = ctor.Invoke(args.ToArray());
swInstance.Stop(); swInstance.Stop();
@ -105,34 +130,30 @@ namespace NadekoBot.Services
_log.Info($"{type.Name} took {swInstance.Elapsed.TotalSeconds:F2}s to load."); _log.Info($"{type.Name} took {swInstance.Elapsed.TotalSeconds:F2}s to load.");
var interfaceType = interfaces.FirstOrDefault(x => instance.GetType().GetInterfaces().Contains(x)); var interfaceType = interfaces.FirstOrDefault(x => instance.GetType().GetInterfaces().Contains(x));
if (interfaceType != null) if (interfaceType != null)
_dict.TryAdd(interfaceType, instance); {
addedTypes.Add(interfaceType);
_services.TryAdd(interfaceType, instance);
}
_dict.TryAdd(type, instance); _services.TryAdd(type, instance);
} }
sw.Stop(); sw.Stop();
_log.Info($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s"); _log.Info($"All services loaded in {sw.Elapsed.TotalSeconds:F2}s");
return this;
} }
return addedTypes;
} }
private readonly ImmutableDictionary<Type, object> _services; public object Unload(Type t)
private NServiceProvider() { }
public NServiceProvider(IDictionary<Type, object> services)
{ {
this._services = services.ToImmutableDictionary(); lock (_locker)
} {
if (_services.TryGetValue(t, out var obj))
public T GetService<T>() {
{ _services.Remove(t);
return (T)((IServiceProvider)(this)).GetService(typeof(T)); return obj;
} }
}
object IServiceProvider.GetService(Type serviceType) return null;
{
_services.TryGetValue(serviceType, out var toReturn);
return toReturn;
} }
IEnumerator IEnumerable.GetEnumerator() => _services.Values.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _services.Values.GetEnumerator();

View File

@ -2,6 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,6 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

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

View File

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

View File

@ -2,6 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <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> </PropertyGroup>
<ItemGroup> <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 file = _images.SlotEmojis[numbers[i]].ToStream())
using (var randomImage = ImageSharp.Image.Load(file)) 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 fs = _images.SlotNumbers[digit].ToStream())
using (var img = ImageSharp.Image.Load(fs)) 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++; n++;
} while ((printWon /= 10) != 0); } while ((printWon /= 10) != 0);
@ -202,7 +202,7 @@ namespace NadekoBot.Modules.Gambling
using (var fs = _images.SlotNumbers[digit].ToStream()) using (var fs = _images.SlotNumbers[digit].ToStream())
using (var img = ImageSharp.Image.Load(fs)) 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++; n++;
} while ((printAmount /= 10) != 0); } while ((printAmount /= 10) != 0);

View File

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

View File

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

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

View File

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

View File

@ -2,6 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

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

View File

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

View File

@ -17,10 +17,15 @@ using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Impl; using NadekoBot.Services.Impl;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog; 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 namespace NadekoBot.Modules.Games.Services
{ {
public class GamesService : INService public class GamesService : INService, IUnloadableService
{ {
private readonly IBotConfigProvider _bc; private readonly IBotConfigProvider _bc;
@ -38,6 +43,15 @@ namespace NadekoBot.Modules.Games.Services
public List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>(); public List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
//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, public GamesService(CommandHandler cmd, IBotConfigProvider bc, IEnumerable<GuildConfig> gcs,
NadekoStrings strings, IImagesService images, CommandHandler cmdHandler) NadekoStrings strings, IImagesService images, CommandHandler cmdHandler)
{ {
@ -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) public void AddTypingArticle(IUser user, string text)
{ {
TypingArticles.Add(new TypingArticle TypingArticles.Add(new TypingArticle

View File

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

View File

@ -3,23 +3,21 @@ using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using System; using System;
using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Common.Attributes; using NadekoBot.Common.Attributes;
using NadekoBot.Services.Impl; using NadekoBot.Services.Impl;
using NadekoBot.Modules.Games.Services;
using NadekoBot.Modules.Games.Common;
namespace NadekoBot.Modules.Games namespace NadekoBot.Modules.Games
{ {
public partial class Games public partial class Games
{ {
[Group] [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 SemaphoreSlim _sem = new SemaphoreSlim(1, 1);
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
@ -37,7 +35,7 @@ namespace NadekoBot.Modules.Games
await _sem.WaitAsync(1000); await _sem.WaitAsync(1000);
try try
{ {
if (_games.TryGetValue(channel.Id, out TicTacToe game)) if (_service.TicTacToeGames.TryGetValue(channel.Id, out TicTacToe game))
{ {
var _ = Task.Run(async () => var _ = Task.Run(async () =>
{ {
@ -45,13 +43,13 @@ namespace NadekoBot.Modules.Games
}); });
return; return;
} }
game = new TicTacToe(base._strings, (DiscordSocketClient)this._client, channel, (IGuildUser)Context.User); game = new TicTacToe(base._strings, this._client, channel, (IGuildUser)Context.User);
_games.Add(channel.Id, game); _service.TicTacToeGames.Add(channel.Id, game);
await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false); await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false);
game.OnEnded += (g) => game.OnEnded += (g) =>
{ {
_games.Remove(channel.Id); _service.TicTacToeGames.Remove(channel.Id);
}; };
} }
finally 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 Discord.WebSocket;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using System.Collections.Concurrent;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Common.Attributes; using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Games.Common.Trivia; using NadekoBot.Modules.Games.Common.Trivia;
using NadekoBot.Modules.Games.Services;
namespace NadekoBot.Modules.Games namespace NadekoBot.Modules.Games
{ {
public partial class Games public partial class Games
{ {
//todo move games to service, unload
[Group] [Group]
public class TriviaCommands : NadekoSubmodule public class TriviaCommands : NadekoSubmodule<GamesService>
{ {
private readonly CurrencyService _cs; private readonly CurrencyService _cs;
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly IBotConfigProvider _bc; private readonly IBotConfigProvider _bc;
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
public TriviaCommands(DiscordSocketClient client, IBotConfigProvider bc, CurrencyService cs) public TriviaCommands(DiscordSocketClient client, IBotConfigProvider bc, CurrencyService cs)
{ {
_cs = cs; _cs = cs;
@ -48,7 +47,7 @@ namespace NadekoBot.Modules.Games
var isPokemon = additionalArgs.Contains("pokemon"); var isPokemon = additionalArgs.Contains("pokemon");
var trivia = new TriviaGame(_strings, _client, _bc, _cs, channel.Guild, channel, showHints, winReq, isPokemon); 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 try
{ {
@ -56,7 +55,7 @@ namespace NadekoBot.Modules.Games
} }
finally finally
{ {
RunningTrivias.TryRemove(channel.Guild.Id, out trivia); _service.RunningTrivias.TryRemove(channel.Guild.Id, out trivia);
await trivia.EnsureStopped().ConfigureAwait(false); await trivia.EnsureStopped().ConfigureAwait(false);
} }
return; return;
@ -72,8 +71,7 @@ namespace NadekoBot.Modules.Games
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
TriviaGame trivia; if (_service.RunningTrivias.TryGetValue(channel.Guild.Id, out TriviaGame trivia))
if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia))
{ {
await channel.SendConfirmAsync(GetText("leaderboard"), trivia.GetLeaderboard()).ConfigureAwait(false); await channel.SendConfirmAsync(GetText("leaderboard"), trivia.GetLeaderboard()).ConfigureAwait(false);
return; return;
@ -88,8 +86,7 @@ namespace NadekoBot.Modules.Games
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
TriviaGame trivia; if (_service.RunningTrivias.TryGetValue(channel.Guild.Id, out TriviaGame trivia))
if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia))
{ {
await trivia.StopGame().ConfigureAwait(false); await trivia.StopGame().ConfigureAwait(false);
return; return;

View File

@ -30,22 +30,15 @@ namespace NadekoBot.Modules.Music
private readonly IGoogleApiService _google; private readonly IGoogleApiService _google;
private readonly DbService _db; private readonly DbService _db;
public Music(DiscordSocketClient client, IBotCredentials creds, IGoogleApiService google, public Music(DiscordSocketClient client,
IBotCredentials creds,
IGoogleApiService google,
DbService db) DbService db)
{ {
_client = client; _client = client;
_creds = creds; _creds = creds;
_google = google; _google = google;
_db = db; _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 //todo changing server region is bugged again

View File

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

View File

@ -18,7 +18,7 @@ using NadekoBot.Modules.Music.Common.SongResolver;
namespace NadekoBot.Modules.Music.Services namespace NadekoBot.Modules.Music.Services
{ {
public class MusicService : INService public class MusicService : INService, IUnloadableService
{ {
public const string MusicDataPath = "data/musicdata"; public const string MusicDataPath = "data/musicdata";
@ -47,13 +47,25 @@ namespace NadekoBot.Modules.Music.Services
_creds = creds; _creds = creds;
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_client.LeftGuild += _client_LeftGuild;
try { Directory.Delete(MusicDataPath, true); } catch { } try { Directory.Delete(MusicDataPath, true); } catch { }
_defaultVolumes = new ConcurrentDictionary<ulong, float>(gcs.ToDictionary(x => x.GuildId, x => x.DefaultMusicVolume)); _defaultVolumes = new ConcurrentDictionary<ulong, float>(gcs.ToDictionary(x => x.GuildId, x => x.DefaultMusicVolume));
Directory.CreateDirectory(MusicDataPath); 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) 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 // thanks to halitalf for adding autoboob and autobutt features :D
public class NSFW : NadekoTopLevelModule<SearchesService> 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> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentDictionary<ulong, Timer> _autoBoobTimers = 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>(); private static readonly ConcurrentDictionary<ulong, Timer> _autoButtTimers = new ConcurrentDictionary<ulong, Timer>();

View File

@ -2,6 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,6 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,6 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <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> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

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

View File

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

View File

@ -15,7 +15,7 @@ using NLog;
namespace NadekoBot.Modules.Utility.Services namespace NadekoBot.Modules.Utility.Services
{ {
public class PatreonRewardsService : INService public class PatreonRewardsService : INService, IUnloadableService
{ {
private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
@ -33,15 +33,16 @@ namespace NadekoBot.Modules.Utility.Services
private readonly string cacheFileName = "./patreon-rewards.json"; 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) DiscordSocketClient client)
{ {
_log = LogManager.GetCurrentClassLogger();
_creds = creds; _creds = creds;
_db = db; _db = db;
_currency = currency; _currency = currency;
if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken)) if (string.IsNullOrWhiteSpace(creds.PatreonAccessToken))
return; return;
_log = LogManager.GetCurrentClassLogger();
Updater = new Timer(async (load) => await RefreshPledges((bool)load), Updater = new Timer(async (load) => await RefreshPledges((bool)load),
client.ShardId == 0, client.ShardId == 0 ? TimeSpan.Zero : TimeSpan.FromMinutes(2), Interval); client.ShardId == 0, client.ShardId == 0 ? TimeSpan.Zero : TimeSpan.FromMinutes(2), Interval);
} }
@ -171,5 +172,11 @@ namespace NadekoBot.Modules.Utility.Services
claimLockJustInCase.Release(); 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.Common.Replacements;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Impl; using NadekoBot.Services.Impl;
using NLog; using NLog;
using System.Collections.Generic;
namespace NadekoBot.Modules.Utility.Services namespace NadekoBot.Modules.Utility.Services
{ {
@ -29,8 +29,10 @@ namespace NadekoBot.Modules.Utility.Services
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private readonly DbService _db; private readonly DbService _db;
public RemindService(DiscordSocketClient client, IBotConfigProvider config, DbService db, public RemindService(DiscordSocketClient client,
StartingGuildsService guilds, IUnitOfWork uow) IBotConfigProvider config,
DbService db,
StartingGuildsService guilds)
{ {
_config = config; _config = config;
_client = client; _client = client;
@ -40,7 +42,11 @@ namespace NadekoBot.Modules.Utility.Services
cancelSource = new CancellationTokenSource(); cancelSource = new CancellationTokenSource();
cancelAllToken = cancelSource.Token; 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; RemindMessageFormat = _config.BotConfig.RemindMessageFormat;
foreach (var r in reminders) foreach (var r in reminders)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@ using ImageSharp.Drawing.Brushes;
namespace NadekoBot.Modules.Xp.Services 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 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 private readonly ConcurrentQueue<UserCacheItem> _addMessageXp
= new ConcurrentQueue<UserCacheItem>(); = 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 readonly HttpClient http = new HttpClient();
private FontFamily _usernameFontFamily; private FontFamily _usernameFontFamily;
private FontFamily _clubFontFamily; private FontFamily _clubFontFamily;
@ -115,7 +117,7 @@ namespace NadekoBot.Modules.Xp.Services
_cmd.OnMessageNoTrigger += _cmd_OnMessageNoTrigger; _cmd.OnMessageNoTrigger += _cmd_OnMessageNoTrigger;
updateXpTimer = new Timer(async _ => _updateXpTimer = new Timer(async _ =>
{ {
try try
{ {
@ -242,18 +244,19 @@ namespace NadekoBot.Modules.Xp.Services
} }
}, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5)); }, 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, //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. //i'll check in the db too.
var clearRewardTimer = Task.Run(async () => _clearRewardTimer = Task.Run(async () =>
{ {
while (true) while (!token.IsCancellationRequested)
{ {
_rewardedUsers.Clear(); _rewardedUsers.Clear();
await Task.Delay(TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout)); await Task.Delay(TimeSpan.FromMinutes(_bc.BotConfig.XpMinutesTimeout));
} }
}); }, token);
} }
public IEnumerable<XpRoleReward> GetRoleRewards(ulong id) public IEnumerable<XpRoleReward> GetRoleRewards(ulong id)
@ -751,5 +754,17 @@ namespace NadekoBot.Modules.Xp.Services
return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight); 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;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Common;
using NadekoBot.Common.Attributes; using NadekoBot.Common.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Xp.Common; using NadekoBot.Modules.Xp.Common;

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26730.3 VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}"
EndProject EndProject
@ -13,25 +13,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot", "src\NadekoBot\NadekoBot.csproj", "{45EC1473-C678-4857-A544-07DFE0D0B478}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot", "src\NadekoBot\NadekoBot.csproj", "{45EC1473-C678-4857-A544-07DFE0D0B478}"
EndProject 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot.Core", "NadekoBot.Core\NadekoBot.Core.csproj", "{A6CCEFBD-DCF2-482C-9643-47664683548F}"
EndProject 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 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 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 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 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 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 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution 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}.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.ActiveCfg = Release|Any CPU
{45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{A6CCEFBD-DCF2-482C-9643-47664683548F}.Debug|Any CPU.Build.0 = 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 {A6CCEFBD-DCF2-482C-9643-47664683548F}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
@ -112,7 +104,6 @@ Global
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} {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} {A6CCEFBD-DCF2-482C-9643-47664683548F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{41A2DEBA-E8AE-4EC8-A58F-01C4B6E599E5} = {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} {07606931-CB55-4D20-8369-4E086B00EC52} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}

View File

@ -4,16 +4,13 @@
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion> <RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<OutputType>exe</OutputType> <OutputType>exe</OutputType>
<AssetTargetFallback>$(AssetTargetFallback);dnxcore50;portable-net45+win8+wpa81</AssetTargetFallback>
<ApplicationIcon>nadeko_icon.ico</ApplicationIcon> <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> <LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Version)' == '' "> <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=" '$(VersionSuffix)' != '' ">$(VersionPrefix).$(VersionSuffix)</Version>
<Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version> <Version Condition=" '$(Version)' == '' ">$(VersionPrefix)</Version>
</PropertyGroup> </PropertyGroup>
@ -23,6 +20,12 @@
<None Update="credentials_example.json"> <None Update="credentials_example.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </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"> <None Update="libsodium.dll;opus.dll;libsodium.so;libopus.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
@ -66,6 +69,5 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\NadekoBot.Core\NadekoBot.Core.csproj" /> <ProjectReference Include="..\..\NadekoBot.Core\NadekoBot.Core.csproj" />
<ProjectReference Include="..\..\NadekoBot.Modules.CustomReactions\NadekoBot.Modules.CustomReactions.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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