Merge remote-tracking branch 'refs/remotes/Kwoth/dev' into dev

This commit is contained in:
samvaio 2016-12-15 15:53:05 +05:30
commit f09b9cf194
13 changed files with 3615 additions and 10 deletions

View File

@ -43,6 +43,7 @@ There are currently three different placeholders which we will look at, with mor
|`%mention`|The `%mention%` placeholder is triggered when you type `@BotName` - It's important to note that if you've given the bot a custom nickname, this trigger won't work!|```.acr "Hello %mention%" I, %mention%, also say hello!```|Input: "Hello @BotName" Output: "I, @BotName, also say hello!"|
|`%user%`|The `%user%` placeholder mentions the person who said the command|`.acr "Who am I?" You are %user%!`|Input: "Who am I?" Output: "You are @Username!"|
|`%rng%`|The `%rng%` placeholder generates a random number between 0 and 10. You can also specify a custom range (%rng1-100%) even with negative numbers: `%rng-9--1%` (from -9 to -1) . |`.acr "Random number" %rng%`|Input: "Random number" Output: "2"|
|`%rnduser%`|The `%rnduser%` placeholder mentions a random user from the server. |`.acr "Random user" %rnduser%`|Input: "Random number" Output: @SomeUser|
|`%target%`|The `%target%` placeholder is used to make Nadeko Mention another person or phrase, it is only supported as part of the response|`.acr "Say this: " %target%`|Input: "Say this: I, @BotName, am a parrot!". Output: "I, @BotName, am a parrot!".|
Thanks to Nekai for being creative. <3

View File

@ -10,12 +10,14 @@ using NadekoBot.Extensions;
namespace NadekoBot.Modules.CustomReactions
{
[NadekoModule("CustomReactions",".")]
[NadekoModule("CustomReactions", ".")]
public class CustomReactions : DiscordModule
{
public static ConcurrentHashSet<CustomReaction> GlobalReactions { get; } = new ConcurrentHashSet<CustomReaction>();
public static ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>> GuildReactions { get; } = new ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>>();
public static ConcurrentDictionary<string, uint> ReactionStats { get; } = new ConcurrentDictionary<string, uint>();
static CustomReactions()
{
using (var uow = DbHandler.UnitOfWork())
@ -29,6 +31,8 @@ namespace NadekoBot.Modules.CustomReactions
{
}
public void ClearStats() => ReactionStats.Clear();
public static async Task<bool> TryExecuteCustomReaction(IUserMessage umsg)
{
var channel = umsg.Channel as ITextChannel;
@ -40,15 +44,18 @@ namespace NadekoBot.Modules.CustomReactions
GuildReactions.TryGetValue(channel.Guild.Id, out reactions);
if (reactions != null && reactions.Any())
{
var reaction = reactions.Where(cr => {
var reaction = reactions.Where(cr =>
{
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant();
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
}).Shuffle().FirstOrDefault();
if (reaction != null)
{
if(reaction.Response != "-")
if (reaction.Response != "-")
try { await channel.SendMessageAsync(reaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { }
ReactionStats.AddOrUpdate(reaction.Trigger, 1, (k, old) => ++old);
return true;
}
}
@ -62,6 +69,7 @@ namespace NadekoBot.Modules.CustomReactions
if (greaction != null)
{
try { await channel.SendMessageAsync(greaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { }
ReactionStats.AddOrUpdate(greaction.Trigger, 1, (k, old) => ++old);
return true;
}
return false;
@ -190,9 +198,9 @@ namespace NadekoBot.Modules.CustomReactions
if (customReactions == null || !customReactions.Any())
await imsg.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
else
await imsg.Channel.SendConfirmAsync($"Page {page} of custom reactions (grouped):",
await imsg.Channel.SendConfirmAsync($"Page {page} of custom reactions (grouped):",
string.Join("\r\n", customReactions
.GroupBy(cr=>cr.Trigger)
.GroupBy(cr => cr.Trigger)
.OrderBy(cr => cr.Key)
.Skip((page - 1) * 20)
.Take(20)
@ -220,7 +228,7 @@ namespace NadekoBot.Modules.CustomReactions
await imsg.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
.WithDescription($"#{id}")
.AddField(efb => efb.WithName("Trigger").WithValue(found.Trigger))
.AddField(efb => efb.WithName("Response").WithValue(found.Response + "\n```css\n" + found.Response + "```" ))
.AddField(efb => efb.WithName("Response").WithValue(found.Response + "\n```css\n" + found.Response + "```"))
.Build()).ConfigureAwait(false);
}
}
@ -256,7 +264,7 @@ namespace NadekoBot.Modules.CustomReactions
GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CustomReaction>()).RemoveWhere(cr => cr.Id == toDelete.Id);
success = true;
}
if(success)
if (success)
await uow.CompleteAsync().ConfigureAwait(false);
}
@ -265,5 +273,41 @@ namespace NadekoBot.Modules.CustomReactions
else
await imsg.Channel.SendErrorAsync("Failed to find that custom reaction.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task CrStatsClear(IUserMessage imsg, string trigger = null)
{
if (string.IsNullOrWhiteSpace(trigger))
{
ClearStats();
await imsg.Channel.SendConfirmAsync($"Custom reaction stats cleared.").ConfigureAwait(false);
}
else
{
uint throwaway;
if (ReactionStats.TryRemove(trigger, out throwaway))
{
await imsg.Channel.SendConfirmAsync($"Stats cleared for `{trigger}` custom reaction.").ConfigureAwait(false);
}
else
{
await imsg.Channel.SendErrorAsync("No stats for that trigger found, no action taken.").ConfigureAwait(false);
}
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task CrStats(IUserMessage imsg, int page = 1)
{
if (page < 1)
return;
await imsg.Channel.EmbedAsync(ReactionStats.OrderByDescending(x => x.Value)
.Skip((page - 1)*9)
.Take(9)
.Aggregate(new EmbedBuilder().WithColor(NadekoBot.OkColor).WithTitle($"Custom Reaction stats page #{page}"),
(agg, cur) => agg.AddField(efb => efb.WithName(cur.Key).WithValue(cur.Value.ToString()).WithIsInline(true)))
.Build())
.ConfigureAwait(false);
}
}
}

View File

@ -1,8 +1,10 @@
using Discord;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace NadekoBot.Modules.CustomReactions
@ -18,6 +20,15 @@ namespace NadekoBot.Modules.CustomReactions
{
{"%mention%", (ctx) => { return $"<@{NadekoBot.Client.GetCurrentUser().Id}>"; } },
{"%user%", (ctx) => { return ctx.Author.Mention; } },
{"%rnduser%", (ctx) => {
var ch = ctx.Channel as ITextChannel;
if(ch == null)
return "";
var usrs = (ch.Guild.GetUsersAsync().GetAwaiter().GetResult());
return usrs.Skip(new NadekoRandom().Next(0,usrs.Count-1)).Shuffle().FirstOrDefault()?.Mention ?? "";
} }
//{"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } }
};

View File

@ -0,0 +1,206 @@
using Discord;
using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands.Hangman
{
public class HangmanModel
{
public List<HangmanObject> All { get; set; }
public List<HangmanObject> Animals { get; set; }
public List<HangmanObject> Countries { get; set; }
public List<HangmanObject> Movies { get; set; }
public List<HangmanObject> Things { get; set; }
}
public class HangmanTermPool
{
public enum HangmanTermType
{
All,
Animals,
Countries,
Movies,
Things
}
const string termsPath = "data/hangman.json";
public static HangmanModel data { get; }
static HangmanTermPool()
{
try
{
data = JsonConvert.DeserializeObject<HangmanModel>(File.ReadAllText(termsPath));
data.All = data.Animals.Concat(data.Countries)
.Concat(data.Movies)
.Concat(data.Things)
.ToList();
}
catch (Exception ex) {
Console.WriteLine(ex);
}
}
public static HangmanObject GetTerm(HangmanTermType type)
{
var rng = new NadekoRandom();
switch (type)
{
case HangmanTermType.Animals:
return data.Animals[rng.Next(0, data.Animals.Count)];
case HangmanTermType.Countries:
return data.Countries[rng.Next(0, data.Countries.Count)];
case HangmanTermType.Movies:
return data.Movies[rng.Next(0, data.Movies.Count)];
case HangmanTermType.Things:
return data.Things[rng.Next(0, data.Things.Count)];
default:
return data.All[rng.Next(0, data.All.Count)];
}
}
}
public class HangmanGame
{
public IMessageChannel GameChannel { get; }
public HashSet<char> Guesses { get; } = new HashSet<char>();
public HangmanObject Term { get; private set; }
public uint Errors { get; private set; } = 0;
public uint MaxErrors { get; } = 6;
public uint MessagesSinceLastPost { get; private set; } = 0;
public string ScrambledWord => "`" + String.Concat(Term.Word.Select(c =>
{
if (!(char.IsLetter(c) || char.IsDigit(c)))
return $" {c}";
c = char.ToUpperInvariant(c);
if (c == ' ')
return " ";
return Guesses.Contains(c) ? $" {c}" : " _";
})) + "`";
public bool GuessedAll => Guesses.IsSupersetOf(Term.Word.ToUpperInvariant()
.Where(c => char.IsLetter(c) || char.IsDigit(c)));
public HangmanTermPool.HangmanTermType TermType { get; }
public event Action<HangmanGame> OnEnded;
public HangmanGame(IMessageChannel channel, HangmanTermPool.HangmanTermType type)
{
this.GameChannel = channel;
this.TermType = type;
}
public void Start()
{
this.Term = HangmanTermPool.GetTerm(TermType);
// start listening for answers when game starts
NadekoBot.Client.MessageReceived += PotentialGuess;
}
public async Task End()
{
NadekoBot.Client.MessageReceived -= PotentialGuess;
OnEnded(this);
var toSend = "Game ended. You **" + (Errors >= MaxErrors ? "LOSE" : "WIN") + "**!\n" + GetHangman();
var embed = new EmbedBuilder().WithTitle("Hangman Game")
.WithDescription(toSend)
.AddField(efb => efb.WithName("It was").WithValue(Term.Word))
.WithImage(eib => eib.WithUrl(Term.ImageUrl));
if (Errors >= MaxErrors)
await GameChannel.EmbedAsync(embed.WithColor(NadekoBot.ErrorColor).Build()).ConfigureAwait(false);
else
await GameChannel.EmbedAsync(embed.WithColor(NadekoBot.OkColor).Build()).ConfigureAwait(false);
}
private Task PotentialGuess(IMessage msg)
{
if (msg.Channel != GameChannel)
return Task.CompletedTask; // message's channel has to be the same as game's
if (msg.Content.Length != 1) // message must be 1 char long
{
if (++MessagesSinceLastPost > 10)
{
MessagesSinceLastPost = 0;
Task.Run(async () =>
{
try { await GameChannel.SendConfirmAsync("Hangman Game", ScrambledWord + "\n" + GetHangman()).ConfigureAwait(false); } catch { }
});
}
return Task.CompletedTask;
}
if (!(char.IsLetter(msg.Content[0]) || char.IsDigit(msg.Content[0])))// and a letter or a digit
return Task.CompletedTask;
var guess = char.ToUpperInvariant(msg.Content[0]);
// todo hmmmm
// how do i want to limit the users on guessing?
// one guess every 5 seconds if wrong?
Task.Run(async () =>
{
try
{
if (Guesses.Contains(guess))
{
++Errors;
if (Errors < MaxErrors)
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author.Mention} Letter `{guess}` has already been used.\n" + ScrambledWord + "\n" + GetHangman()).ConfigureAwait(false);
else
await End().ConfigureAwait(false);
return;
}
Guesses.Add(guess);
if (Term.Word.ToUpperInvariant().Contains(guess))
{
if (GuessedAll)
{
try { await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author.Mention} guessed a letter `{guess}`!").ConfigureAwait(false); } catch { }
await End().ConfigureAwait(false);
return;
}
try { await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author.Mention} guessed a letter `{guess}`!\n" + ScrambledWord + "\n" + GetHangman()).ConfigureAwait(false); } catch { }
}
else
{
++Errors;
if (Errors < MaxErrors)
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author.Mention} Letter `{guess}` does not exist.\n" + ScrambledWord + "\n" + GetHangman()).ConfigureAwait(false);
else
await End().ConfigureAwait(false);
}
}
catch { }
});
return Task.CompletedTask;
}
public string GetHangman()
{
return
$@"\_\_\_\_\_\_\_\_\_
| |
| |
{(Errors > 0 ? "😲" : " ")} |
{(Errors > 1 ? "/" : " ")} {(Errors > 2 ? "|" : " ")} {(Errors > 3 ? "\\" : " ")} |
{(Errors > 4 ? "/" : " ")} {(Errors > 5 ? "\\" : " ")} |
/-\";
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands.Hangman
{
public class HangmanObject
{
public string Word { get; set; }
public string ImageUrl { get; set; }
}
}

View File

@ -0,0 +1,66 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Commands.Hangman;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class HangmanCommands
{
private static Logger _log { get; }
//channelId, game
public static ConcurrentDictionary<ulong, HangmanGame> HangmanGames { get; } = new ConcurrentDictionary<ulong, HangmanGame>();
static HangmanCommands()
{
_log = LogManager.GetCurrentClassLogger();
}
string typesStr { get; } = "";
public HangmanCommands()
{
typesStr = $"`List of \"{NadekoBot.ModulePrefixes[typeof(Games).Name]}hangman\" term types:`\n" + String.Join(", ", Enum.GetNames(typeof(HangmanTermPool.HangmanTermType)));
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Hangmanlist(IUserMessage imsg)
{
await imsg.Channel.SendConfirmAsync(typesStr);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Hangman(IUserMessage imsg, HangmanTermPool.HangmanTermType type = HangmanTermPool.HangmanTermType.All)
{
var hm = new HangmanGame(imsg.Channel, type);
if (!HangmanGames.TryAdd(imsg.Channel.Id, hm))
{
await imsg.Channel.SendErrorAsync("Hangman game already running on this channel.").ConfigureAwait(false);
return;
}
hm.OnEnded += (g) =>
{
HangmanGame throwaway;
HangmanGames.TryRemove(g.GameChannel.Id, out throwaway);
};
hm.Start();
await imsg.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman() + "\n" + hm.ScrambledWord);
}
}
}
}

View File

@ -20,9 +20,9 @@ namespace NadekoBot.Modules.Searches
private static Dictionary<string, SearchPokemon> pokemons { get; } = new Dictionary<string, SearchPokemon>();
private static Dictionary<string, SearchPokemonAbility> pokemonAbilities { get; } = new Dictionary<string, SearchPokemonAbility>();
public const string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities.json";
public const string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json";
public const string PokemonListFile = "data/pokemon/pokemon_list.json";
public const string PokemonListFile = "data/pokemon/pokemon_list7.json";
private static Logger _log { get; }
static PokemonSearchCommands()

View File

@ -17,6 +17,7 @@ using NadekoBot.TypeReaders;
using System.Collections.Concurrent;
using NadekoBot.Modules.Music;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Games.Commands.Hangman;
namespace NadekoBot
{

View File

@ -1868,6 +1868,60 @@ namespace NadekoBot.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to crstats.
/// </summary>
public static string crstats_cmd {
get {
return ResourceManager.GetString("crstats_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shows a list of custom reactions and the number of times they have been executed. Paginated with 10 per page. Use `{0}crstatsclear` to reset the counters..
/// </summary>
public static string crstats_desc {
get {
return ResourceManager.GetString("crstats_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}crstats` or `{0}crstats 3`.
/// </summary>
public static string crstats_usage {
get {
return ResourceManager.GetString("crstats_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to crstatsclear.
/// </summary>
public static string crstatsclear_cmd {
get {
return ResourceManager.GetString("crstatsclear_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Resets the counters on `{0}crstats`. You can specify a trigger to clear stats only for that trigger..
/// </summary>
public static string crstatsclear_desc {
get {
return ResourceManager.GetString("crstatsclear_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}crstatsclear` or `{0}crstatsclear rng`.
/// </summary>
public static string crstatsclear_usage {
get {
return ResourceManager.GetString("crstatsclear_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to danbooru.
/// </summary>
@ -2840,6 +2894,60 @@ namespace NadekoBot.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to hangman.
/// </summary>
public static string hangman_cmd {
get {
return ResourceManager.GetString("hangman_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Starts a game of hangman in the channel. Use `{0}hangmanlist` to see a list of available term types. Defaults to &apos;all&apos;..
/// </summary>
public static string hangman_desc {
get {
return ResourceManager.GetString("hangman_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}hangman` or `{0}hangman movies`.
/// </summary>
public static string hangman_usage {
get {
return ResourceManager.GetString("hangman_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to hangmanlist.
/// </summary>
public static string hangmanlist_cmd {
get {
return ResourceManager.GetString("hangmanlist_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shows a list of hangman term types..
/// </summary>
public static string hangmanlist_desc {
get {
return ResourceManager.GetString("hangmanlist_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0} hangmanlist`.
/// </summary>
public static string hangmanlist_usage {
get {
return ResourceManager.GetString("hangmanlist_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to #.
/// </summary>

View File

@ -2745,4 +2745,40 @@
<data name="type_usage" xml:space="preserve">
<value>`{0}type @someone`</value>
</data>
<data name="hangmanlist_cmd" xml:space="preserve">
<value>hangmanlist</value>
</data>
<data name="hangmanlist_desc" xml:space="preserve">
<value>Shows a list of hangman term types.</value>
</data>
<data name="hangmanlist_usage" xml:space="preserve">
<value>`{0} hangmanlist`</value>
</data>
<data name="hangman_cmd" xml:space="preserve">
<value>hangman</value>
</data>
<data name="hangman_desc" xml:space="preserve">
<value>Starts a game of hangman in the channel. Use `{0}hangmanlist` to see a list of available term types. Defaults to 'all'.</value>
</data>
<data name="hangman_usage" xml:space="preserve">
<value>`{0}hangman` or `{0}hangman movies`</value>
</data>
<data name="crstatsclear_cmd" xml:space="preserve">
<value>crstatsclear</value>
</data>
<data name="crstatsclear_desc" xml:space="preserve">
<value>Resets the counters on `{0}crstats`. You can specify a trigger to clear stats only for that trigger.</value>
</data>
<data name="crstatsclear_usage" xml:space="preserve">
<value>`{0}crstatsclear` or `{0}crstatsclear rng`</value>
</data>
<data name="crstats_cmd" xml:space="preserve">
<value>crstats</value>
</data>
<data name="crstats_desc" xml:space="preserve">
<value>Shows a list of custom reactions and the number of times they have been executed. Paginated with 10 per page. Use `{0}crstatsclear` to reset the counters.</value>
</data>
<data name="crstats_usage" xml:space="preserve">
<value>`{0}crstats` or `{0}crstats 3`</value>
</data>
</root>

File diff suppressed because it is too large Load Diff