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:
parent
599245b1ca
commit
33ac43e1b5
@ -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
|
||||
{
|
||||
|
64
NadekoBot.Core/Modules/Administration/ModuleCommands.cs
Normal file
64
NadekoBot.Core/Modules/Administration/ModuleCommands.cs
Normal 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:");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -2,9 +2,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,34 +130,30 @@ 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();
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
21
NadekoBot.Modules.Gambling/Services/AnimalRaceService.cs
Normal file
21
NadekoBot.Modules.Gambling/Services/AnimalRaceService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
277
NadekoBot.Modules.Games/Common/TicTacToe.cs
Normal file
277
NadekoBot.Modules.Games/Common/TicTacToe.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ 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();
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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,6 +43,15 @@ namespace NadekoBot.Modules.Games.Services
|
||||
|
||||
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,
|
||||
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)
|
||||
{
|
||||
TypingArticles.Add(new TypingArticle
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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>();
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -40,7 +42,11 @@ 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)
|
||||
|
@ -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 () =>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
@ -242,18 +244,19 @@ namespace NadekoBot.Modules.Xp.Services
|
||||
}
|
||||
}, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user