mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Applied codestyle to all .cs files
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
using System.Collections.Immutable;
|
||||
using NadekoBot.Modules.Games.Common.Acrophobia;
|
||||
using NadekoBot.Modules.Games.Services;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace NadekoBot.Modules.Games;
|
||||
|
||||
@@ -15,7 +15,8 @@ public partial class Games
|
||||
public AcropobiaCommands(DiscordSocketClient client)
|
||||
=> _client = client;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptions(typeof(AcrophobiaGame.Options))]
|
||||
public async Task Acrophobia(params string[] args)
|
||||
@@ -25,7 +26,6 @@ public partial class Games
|
||||
|
||||
var game = new AcrophobiaGame(options);
|
||||
if (_service.AcrophobiaGames.TryAdd(channel.Id, game))
|
||||
{
|
||||
try
|
||||
{
|
||||
game.OnStarted += Game_OnStarted;
|
||||
@@ -41,11 +41,8 @@ public partial class Games
|
||||
_service.AcrophobiaGames.TryRemove(channel.Id, out game);
|
||||
game.Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.acro_running);
|
||||
}
|
||||
|
||||
Task _client_MessageReceived(SocketMessage msg)
|
||||
{
|
||||
@@ -69,44 +66,52 @@ public partial class Games
|
||||
|
||||
private Task Game_OnStarted(AcrophobiaGame game)
|
||||
{
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.acrophobia))
|
||||
.WithDescription(GetText(strs.acro_started(Format.Bold(string.Join(".", game.StartingLetters)))))
|
||||
.WithFooter(GetText(strs.acro_started_footer(game.Opts.SubmissionTime)));
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.acrophobia))
|
||||
.WithDescription(
|
||||
GetText(strs.acro_started(Format.Bold(string.Join(".", game.StartingLetters)))))
|
||||
.WithFooter(GetText(strs.acro_started_footer(game.Opts.SubmissionTime)));
|
||||
|
||||
return ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
private Task Game_OnUserVoted(string user)
|
||||
=> SendConfirmAsync(
|
||||
GetText(strs.acrophobia),
|
||||
GetText(strs.acro_vote_cast(Format.Bold(user))));
|
||||
=> SendConfirmAsync(GetText(strs.acrophobia), GetText(strs.acro_vote_cast(Format.Bold(user))));
|
||||
|
||||
private async Task Game_OnVotingStarted(AcrophobiaGame game, ImmutableArray<KeyValuePair<AcrophobiaUser, int>> submissions)
|
||||
private async Task Game_OnVotingStarted(
|
||||
AcrophobiaGame game,
|
||||
ImmutableArray<KeyValuePair<AcrophobiaUser, int>> submissions)
|
||||
{
|
||||
if (submissions.Length == 0)
|
||||
{
|
||||
await SendErrorAsync(GetText(strs.acrophobia), GetText(strs.acro_ended_no_sub));
|
||||
return;
|
||||
}
|
||||
|
||||
if (submissions.Length == 1)
|
||||
{
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithDescription(GetText(strs.acro_winner_only(Format.Bold(submissions.First().Key.UserName))))
|
||||
.WithFooter(submissions.First().Key.Input));
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(
|
||||
strs.acro_winner_only(
|
||||
Format.Bold(submissions.First().Key.UserName))))
|
||||
.WithFooter(submissions.First().Key.Input));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var i = 0;
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.acrophobia) + " - " + GetText(strs.submissions_closed))
|
||||
.WithDescription(GetText(strs.acro_nym_was(Format.Bold(string.Join(".", game.StartingLetters)) + "\n" +
|
||||
$@"--
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.acrophobia) + " - " + GetText(strs.submissions_closed))
|
||||
.WithDescription(GetText(strs.acro_nym_was(
|
||||
Format.Bold(string.Join(".", game.StartingLetters))
|
||||
+ "\n"
|
||||
+ $@"--
|
||||
{submissions.Aggregate("", (agg, cur) => agg + $"`{++i}.` **{cur.Key.Input}**\n")}
|
||||
--")))
|
||||
.WithFooter(GetText(strs.acro_vote));
|
||||
.WithFooter(GetText(strs.acro_vote));
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
@@ -118,15 +123,17 @@ public partial class Games
|
||||
await SendErrorAsync(GetText(strs.acrophobia), GetText(strs.acro_no_votes_cast));
|
||||
return;
|
||||
}
|
||||
|
||||
var table = votes.OrderByDescending(v => v.Value);
|
||||
var winner = table.First();
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.acrophobia))
|
||||
.WithDescription(GetText(strs.acro_winner(Format.Bold(winner.Key.UserName),
|
||||
Format.Bold(winner.Value.ToString()))))
|
||||
.WithFooter(winner.Key.Input);
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.acrophobia))
|
||||
.WithDescription(GetText(strs.acro_winner(Format.Bold(winner.Key.UserName),
|
||||
Format.Bold(winner.Value.ToString()))))
|
||||
.WithFooter(winner.Key.Input);
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,7 +15,8 @@ public partial class Games
|
||||
=> _db = db;
|
||||
|
||||
[NoPublicBot]
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async Task Cleverbot()
|
||||
@@ -29,6 +30,7 @@ public partial class Games
|
||||
uow.GuildConfigs.SetCleverbotEnabled(ctx.Guild.Id, false);
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.cleverbot_disabled);
|
||||
return;
|
||||
}
|
||||
@@ -44,6 +46,4 @@ public partial class Games
|
||||
await ReplyConfirmLocalizedAsync(strs.cleverbot_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -1,28 +1,11 @@
|
||||
#nullable disable
|
||||
using System.Collections.Immutable;
|
||||
using CommandLine;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Common.Acrophobia;
|
||||
|
||||
public sealed class AcrophobiaGame : IDisposable
|
||||
{
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
[Option('s', "submission-time", Required = false, Default = 60, HelpText = "Time after which the submissions are closed and voting starts.")]
|
||||
public int SubmissionTime { get; set; } = 60;
|
||||
|
||||
[Option('v', "vote-time", Required = false, Default = 60, HelpText = "Time after which the voting is closed and the winner is declared.")]
|
||||
public int VoteTime { get; set; } = 30;
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (SubmissionTime is < 15 or > 300)
|
||||
SubmissionTime = 60;
|
||||
if (VoteTime is < 15 or > 120)
|
||||
VoteTime = 30;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Phase
|
||||
{
|
||||
Submission,
|
||||
@@ -39,19 +22,26 @@ public sealed class AcrophobiaGame : IDisposable
|
||||
Failed
|
||||
}
|
||||
|
||||
public event Func<AcrophobiaGame, Task> OnStarted = delegate { return Task.CompletedTask; };
|
||||
|
||||
public event Func<AcrophobiaGame, ImmutableArray<KeyValuePair<AcrophobiaUser, int>>, Task> OnVotingStarted =
|
||||
delegate { return Task.CompletedTask; };
|
||||
|
||||
public event Func<string, Task> OnUserVoted = delegate { return Task.CompletedTask; };
|
||||
|
||||
public event Func<AcrophobiaGame, ImmutableArray<KeyValuePair<AcrophobiaUser, int>>, Task> OnEnded = delegate
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
public Phase CurrentPhase { get; private set; } = Phase.Submission;
|
||||
public ImmutableArray<char> StartingLetters { get; private set; }
|
||||
public Options Opts { get; }
|
||||
|
||||
private readonly Dictionary<AcrophobiaUser, int> submissions = new();
|
||||
private readonly SemaphoreSlim locker = new(1, 1);
|
||||
public Options Opts { get; }
|
||||
private readonly NadekoRandom _rng;
|
||||
|
||||
public event Func<AcrophobiaGame, Task> OnStarted = delegate { return Task.CompletedTask; };
|
||||
public event Func<AcrophobiaGame, ImmutableArray<KeyValuePair<AcrophobiaUser, int>>, Task> OnVotingStarted = delegate { return Task.CompletedTask; };
|
||||
public event Func<string, Task> OnUserVoted = delegate { return Task.CompletedTask; };
|
||||
public event Func<AcrophobiaGame, ImmutableArray<KeyValuePair<AcrophobiaUser, int>>, Task> OnEnded = delegate { return Task.CompletedTask; };
|
||||
|
||||
private readonly HashSet<ulong> _usersWhoVoted = new();
|
||||
|
||||
public AcrophobiaGame(Options options)
|
||||
@@ -74,6 +64,7 @@ public sealed class AcrophobiaGame : IDisposable
|
||||
await OnVotingStarted(this, ImmutableArray.Create<KeyValuePair<AcrophobiaUser, int>>());
|
||||
return;
|
||||
}
|
||||
|
||||
if (submissions.Count == 1)
|
||||
{
|
||||
CurrentPhase = Phase.Ended;
|
||||
@@ -108,6 +99,7 @@ public sealed class AcrophobiaGame : IDisposable
|
||||
var randChar = (char)_rng.Next(65, 91);
|
||||
lettersArr[i] = randChar == 'X' ? (char)_rng.Next(65, 88) : randChar;
|
||||
}
|
||||
|
||||
StartingLetters = lettersArr.ToImmutableArray();
|
||||
}
|
||||
|
||||
@@ -137,9 +129,8 @@ public sealed class AcrophobiaGame : IDisposable
|
||||
++submissions[toVoteFor];
|
||||
var _ = Task.Run(() => OnUserVoted(userName));
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
@@ -154,14 +145,16 @@ public sealed class AcrophobiaGame : IDisposable
|
||||
|
||||
var inputWords = input.Split(' ');
|
||||
|
||||
if (inputWords.Length != StartingLetters.Length) // number of words must be the same as the number of the starting letters
|
||||
if (inputWords.Length
|
||||
!= StartingLetters.Length) // number of words must be the same as the number of the starting letters
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < StartingLetters.Length; i++)
|
||||
{
|
||||
var letter = StartingLetters[i];
|
||||
|
||||
if (!inputWords[i].StartsWith(letter.ToString(), StringComparison.InvariantCulture)) // all first letters must match
|
||||
if (!inputWords[i]
|
||||
.StartsWith(letter.ToString(), StringComparison.InvariantCulture)) // all first letters must match
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -170,7 +163,7 @@ public sealed class AcrophobiaGame : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.CurrentPhase = Phase.Ended;
|
||||
CurrentPhase = Phase.Ended;
|
||||
OnStarted = null;
|
||||
OnEnded = null;
|
||||
OnUserVoted = null;
|
||||
@@ -179,4 +172,29 @@ public sealed class AcrophobiaGame : IDisposable
|
||||
submissions.Clear();
|
||||
locker.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
[Option('s',
|
||||
"submission-time",
|
||||
Required = false,
|
||||
Default = 60,
|
||||
HelpText = "Time after which the submissions are closed and voting starts.")]
|
||||
public int SubmissionTime { get; set; } = 60;
|
||||
|
||||
[Option('v',
|
||||
"vote-time",
|
||||
Required = false,
|
||||
Default = 60,
|
||||
HelpText = "Time after which the voting is closed and the winner is declared.")]
|
||||
public int VoteTime { get; set; } = 30;
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (SubmissionTime is < 15 or > 300)
|
||||
SubmissionTime = 60;
|
||||
if (VoteTime is < 15 or > 120)
|
||||
VoteTime = 30;
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,16 +9,14 @@ public class AcrophobiaUser
|
||||
|
||||
public AcrophobiaUser(ulong userId, string userName, string input)
|
||||
{
|
||||
this.UserName = userName;
|
||||
this.UserId = userId;
|
||||
this.Input = input;
|
||||
UserName = userName;
|
||||
UserId = userId;
|
||||
Input = input;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
=> UserId.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> obj is AcrophobiaUser x
|
||||
? x.UserId == this.UserId
|
||||
: false;
|
||||
}
|
||||
=> obj is AcrophobiaUser x ? x.UserId == UserId : false;
|
||||
}
|
@@ -5,4 +5,4 @@ public class ChatterBotResponse
|
||||
{
|
||||
public string Convo_id { get; set; }
|
||||
public string BotSay { get; set; }
|
||||
}
|
||||
}
|
@@ -7,6 +7,13 @@ public class ChatterBotSession : IChatterBotSession
|
||||
{
|
||||
private static NadekoRandom Rng { get; } = new();
|
||||
|
||||
private string ApiEndpoint
|
||||
=> "http://api.program-o.com/v2/chatbot/"
|
||||
+ $"?bot_id={_botId}&"
|
||||
+ "say={0}&"
|
||||
+ $"convo_id=nadekobot_{_chatterBotId}&"
|
||||
+ "format=json";
|
||||
|
||||
private readonly string _chatterBotId;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly int _botId = 6;
|
||||
@@ -17,12 +24,6 @@ public class ChatterBotSession : IChatterBotSession
|
||||
_httpFactory = httpFactory;
|
||||
}
|
||||
|
||||
private string ApiEndpoint => "http://api.program-o.com/v2/chatbot/" +
|
||||
$"?bot_id={_botId}&" +
|
||||
"say={0}&" +
|
||||
$"convo_id=nadekobot_{_chatterBotId}&" +
|
||||
"format=json";
|
||||
|
||||
public async Task<string> Think(string message)
|
||||
{
|
||||
using var http = _httpFactory.CreateClient();
|
||||
@@ -30,4 +31,4 @@ public class ChatterBotSession : IChatterBotSession
|
||||
var cbr = JsonConvert.DeserializeObject<ChatterBotResponse>(res);
|
||||
return cbr.BotSay.Replace("<br/>", "\n", StringComparison.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,4 +17,4 @@ public class CleverbotIOAskResponse
|
||||
{
|
||||
public string Status { get; set; }
|
||||
public string Response { get; set; }
|
||||
}
|
||||
}
|
@@ -4,4 +4,4 @@ namespace NadekoBot.Modules.Games.Common.ChatterBot;
|
||||
public interface IChatterBotSession
|
||||
{
|
||||
Task<string> Think(string input);
|
||||
}
|
||||
}
|
@@ -5,19 +5,17 @@ namespace NadekoBot.Modules.Games.Common.ChatterBot;
|
||||
|
||||
public class OfficialCleverbotSession : IChatterBotSession
|
||||
{
|
||||
private string QueryString
|
||||
=> $"https://www.cleverbot.com/getreply?key={_apiKey}" + "&wrapper=nadekobot" + "&input={0}" + "&cs={1}";
|
||||
|
||||
private readonly string _apiKey;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private string _cs = null;
|
||||
|
||||
private string QueryString => $"https://www.cleverbot.com/getreply?key={_apiKey}" +
|
||||
"&wrapper=nadekobot" +
|
||||
"&input={0}" +
|
||||
"&cs={1}";
|
||||
private string _cs;
|
||||
|
||||
public OfficialCleverbotSession(string apiKey, IHttpClientFactory factory)
|
||||
{
|
||||
this._apiKey = apiKey;
|
||||
this._httpFactory = factory;
|
||||
_apiKey = apiKey;
|
||||
_httpFactory = factory;
|
||||
}
|
||||
|
||||
public async Task<string> Think(string input)
|
||||
@@ -47,14 +45,14 @@ public class CleverbotIOSession : IChatterBotSession
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly AsyncLazy<string> _nick;
|
||||
|
||||
private readonly string _createEndpoint = $"https://cleverbot.io/1.0/create";
|
||||
private readonly string _askEndpoint = $"https://cleverbot.io/1.0/ask";
|
||||
private readonly string _createEndpoint = "https://cleverbot.io/1.0/create";
|
||||
private readonly string _askEndpoint = "https://cleverbot.io/1.0/ask";
|
||||
|
||||
public CleverbotIOSession(string user, string key, IHttpClientFactory factory)
|
||||
{
|
||||
this._key = key;
|
||||
this._user = user;
|
||||
this._httpFactory = factory;
|
||||
_key = key;
|
||||
_user = user;
|
||||
_httpFactory = factory;
|
||||
|
||||
_nick = new(GetNick);
|
||||
}
|
||||
@@ -64,8 +62,7 @@ public class CleverbotIOSession : IChatterBotSession
|
||||
using var _http = _httpFactory.CreateClient();
|
||||
using var msg = new FormUrlEncodedContent(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("user", _user),
|
||||
new KeyValuePair<string, string>("key", _key),
|
||||
new KeyValuePair<string, string>("user", _user), new KeyValuePair<string, string>("key", _key)
|
||||
});
|
||||
using var data = await _http.PostAsync(_createEndpoint, msg);
|
||||
var str = await data.Content.ReadAsStringAsync();
|
||||
@@ -81,10 +78,8 @@ public class CleverbotIOSession : IChatterBotSession
|
||||
using var _http = _httpFactory.CreateClient();
|
||||
using var msg = new FormUrlEncodedContent(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("user", _user),
|
||||
new KeyValuePair<string, string>("key", _key),
|
||||
new KeyValuePair<string, string>("nick", await _nick),
|
||||
new KeyValuePair<string, string>("text", input),
|
||||
new KeyValuePair<string, string>("user", _user), new KeyValuePair<string, string>("key", _key),
|
||||
new KeyValuePair<string, string>("nick", await _nick), new KeyValuePair<string, string>("text", input)
|
||||
});
|
||||
using var data = await _http.PostAsync(_askEndpoint, msg);
|
||||
var str = await data.Content.ReadAsStringAsync();
|
||||
@@ -94,4 +89,4 @@ public class CleverbotIOSession : IChatterBotSession
|
||||
|
||||
return obj.Response;
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,17 +11,10 @@ public sealed partial class GamesConfig : ICloneable<GamesConfig>
|
||||
public int Version { get; set; }
|
||||
|
||||
[Comment("Hangman related settings (.hangman command)")]
|
||||
public HangmanConfig Hangman { get; set; } = new()
|
||||
{
|
||||
CurrencyReward = 0
|
||||
};
|
||||
|
||||
public HangmanConfig Hangman { get; set; } = new() { CurrencyReward = 0 };
|
||||
|
||||
[Comment("Trivia related settings (.t command)")]
|
||||
public TriviaConfig Trivia { get; set; } = new()
|
||||
{
|
||||
CurrencyReward = 0,
|
||||
MinimumWinReq = 1,
|
||||
};
|
||||
public TriviaConfig Trivia { get; set; } = new() { CurrencyReward = 0, MinimumWinReq = 1 };
|
||||
|
||||
[Comment("List of responses for the .8ball command. A random one will be selected every time")]
|
||||
public List<string> EightBallResponses { get; set; } = new()
|
||||
@@ -76,7 +69,7 @@ public sealed partial class HangmanConfig
|
||||
public sealed partial class TriviaConfig
|
||||
{
|
||||
[Comment("The amount of currency awarded to the winner of the trivia game.")]
|
||||
public long CurrencyReward { get; set; } = 0;
|
||||
public long CurrencyReward { get; set; }
|
||||
|
||||
[Comment(@"Users won't be able to start trivia games which have
|
||||
a smaller win requirement than the one specified by this setting.")]
|
||||
@@ -88,4 +81,4 @@ public sealed partial class RaceAnimal
|
||||
{
|
||||
public string Icon { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
@@ -1,24 +1,29 @@
|
||||
#nullable disable
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Common;
|
||||
|
||||
public class GirlRating
|
||||
{
|
||||
private readonly IImageCache _images;
|
||||
|
||||
public double Crazy { get; }
|
||||
public double Hot { get; }
|
||||
public int Roll { get; }
|
||||
public string Advice { get; }
|
||||
|
||||
public AsyncLazy<Stream> Stream { get; }
|
||||
private readonly IImageCache _images;
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public AsyncLazy<Stream> Stream { get; }
|
||||
|
||||
public GirlRating(IImageCache images, IHttpClientFactory factory, double crazy, double hot, int roll, string advice)
|
||||
public GirlRating(
|
||||
IImageCache images,
|
||||
IHttpClientFactory factory,
|
||||
double crazy,
|
||||
double hot,
|
||||
int roll,
|
||||
string advice)
|
||||
{
|
||||
_images = images;
|
||||
Crazy = crazy;
|
||||
@@ -26,7 +31,7 @@ public class GirlRating
|
||||
Roll = roll;
|
||||
Advice = advice; // convenient to have it here, even though atm there are only few different ones.
|
||||
_httpFactory = factory;
|
||||
|
||||
|
||||
Stream = new(() =>
|
||||
{
|
||||
try
|
||||
@@ -64,4 +69,4 @@ public class GirlRating
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using NadekoBot.Common.Yml;
|
||||
using NadekoBot.Common.Yml;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Hangman;
|
||||
|
||||
@@ -24,7 +24,6 @@ public sealed class DefaultHangmanSource : IHangmanSource
|
||||
|
||||
var qs = new Dictionary<string, HangmanTerm[]>();
|
||||
foreach (var file in Directory.EnumerateFiles("data/hangman/", "*.yml"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = Yaml.Deserializer.Deserialize<HangmanTerm[]>(File.ReadAllText(file));
|
||||
@@ -34,14 +33,13 @@ public sealed class DefaultHangmanSource : IHangmanSource
|
||||
{
|
||||
Log.Error(ex, "Loading {HangmanFile} failed.", file);
|
||||
}
|
||||
}
|
||||
|
||||
_terms = qs;
|
||||
|
||||
|
||||
Log.Information("Loaded {HangmanCategoryCount} hangman categories.", qs.Count);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<string> GetCategories()
|
||||
public IReadOnlyCollection<string> GetCategories()
|
||||
=> _terms.Keys.ToList();
|
||||
|
||||
public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term)
|
||||
@@ -61,4 +59,4 @@ public sealed class DefaultHangmanSource : IHangmanSource
|
||||
term = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,22 +5,12 @@ namespace NadekoBot.Modules.Games.Hangman;
|
||||
|
||||
public sealed class HangmanGame
|
||||
{
|
||||
public enum Phase { Running, Ended }
|
||||
public enum GuessResult { NoAction, AlreadyTried, Incorrect, Guess, Win }
|
||||
|
||||
public record State(
|
||||
int Errors,
|
||||
Phase Phase,
|
||||
string Word,
|
||||
GuessResult GuessResult,
|
||||
List<char> missedLetters,
|
||||
string ImageUrl)
|
||||
{
|
||||
public bool Failed => Errors > 5;
|
||||
}
|
||||
public enum Phase { Running, Ended }
|
||||
|
||||
private Phase CurrentPhase { get; set; }
|
||||
|
||||
|
||||
private readonly HashSet<char> _incorrect = new();
|
||||
private readonly HashSet<char> _correct = new();
|
||||
private readonly HashSet<char> _remaining = new();
|
||||
@@ -32,25 +22,17 @@ public sealed class HangmanGame
|
||||
{
|
||||
_word = term.Word;
|
||||
_imageUrl = term.ImageUrl;
|
||||
|
||||
_remaining = _word
|
||||
.ToLowerInvariant()
|
||||
.Where(x => x.IsLetter())
|
||||
.Select(char.ToLowerInvariant)
|
||||
.ToHashSet();
|
||||
|
||||
_remaining = _word.ToLowerInvariant().Where(x => x.IsLetter()).Select(char.ToLowerInvariant).ToHashSet();
|
||||
}
|
||||
|
||||
public State GetState(GuessResult guessResult = GuessResult.NoAction)
|
||||
=> new(_incorrect.Count,
|
||||
CurrentPhase,
|
||||
CurrentPhase == Phase.Ended
|
||||
? _word
|
||||
: GetScrambledWord(),
|
||||
CurrentPhase == Phase.Ended ? _word : GetScrambledWord(),
|
||||
guessResult,
|
||||
_incorrect.ToList(),
|
||||
CurrentPhase == Phase.Ended
|
||||
? _imageUrl
|
||||
: string.Empty);
|
||||
CurrentPhase == Phase.Ended ? _imageUrl : string.Empty);
|
||||
|
||||
private string GetScrambledWord()
|
||||
{
|
||||
@@ -59,11 +41,11 @@ public sealed class HangmanGame
|
||||
{
|
||||
var ch = _word[i];
|
||||
if (ch == ' ')
|
||||
output[i*2] = ' ';
|
||||
output[i * 2] = ' ';
|
||||
if (!ch.IsLetter() || !_remaining.Contains(char.ToLowerInvariant(ch)))
|
||||
output[i*2] = ch;
|
||||
output[i * 2] = ch;
|
||||
else
|
||||
output[i*2] = '_';
|
||||
output[i * 2] = '_';
|
||||
|
||||
output[(i * 2) + 1] = ' ';
|
||||
}
|
||||
@@ -74,7 +56,7 @@ public sealed class HangmanGame
|
||||
public State Guess(string guess)
|
||||
{
|
||||
if (CurrentPhase != Phase.Running)
|
||||
return GetState(GuessResult.NoAction);
|
||||
return GetState();
|
||||
|
||||
guess = guess.Trim();
|
||||
if (guess.Length > 1)
|
||||
@@ -85,12 +67,12 @@ public sealed class HangmanGame
|
||||
return GetState(GuessResult.Win);
|
||||
}
|
||||
|
||||
return GetState(GuessResult.NoAction);
|
||||
return GetState();
|
||||
}
|
||||
|
||||
var charGuess = guess[0];
|
||||
if (!char.IsLetter(charGuess))
|
||||
return GetState(GuessResult.NoAction);
|
||||
return GetState();
|
||||
|
||||
if (_incorrect.Contains(charGuess) || _correct.Contains(charGuess))
|
||||
return GetState(GuessResult.AlreadyTried);
|
||||
@@ -116,4 +98,16 @@ public sealed class HangmanGame
|
||||
|
||||
return GetState(GuessResult.Incorrect);
|
||||
}
|
||||
}
|
||||
|
||||
public record State(
|
||||
int Errors,
|
||||
Phase Phase,
|
||||
string Word,
|
||||
GuessResult GuessResult,
|
||||
List<char> missedLetters,
|
||||
string ImageUrl)
|
||||
{
|
||||
public bool Failed
|
||||
=> Errors > 5;
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Modules.Games.Services;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Hangman;
|
||||
|
||||
@@ -15,8 +15,12 @@ public sealed class HangmanService : IHangmanService, ILateExecutor
|
||||
private readonly IMemoryCache _cdCache;
|
||||
private readonly object _locker = new();
|
||||
|
||||
public HangmanService(IHangmanSource source, IEmbedBuilderService eb, GamesConfigService gcs,
|
||||
ICurrencyService cs, IMemoryCache cdCache)
|
||||
public HangmanService(
|
||||
IHangmanSource source,
|
||||
IEmbedBuilderService eb,
|
||||
GamesConfigService gcs,
|
||||
ICurrencyService cs,
|
||||
IMemoryCache cdCache)
|
||||
{
|
||||
_source = source;
|
||||
_eb = eb;
|
||||
@@ -25,10 +29,7 @@ public sealed class HangmanService : IHangmanService, ILateExecutor
|
||||
_cdCache = cdCache;
|
||||
}
|
||||
|
||||
public bool StartHangman(
|
||||
ulong channelId,
|
||||
string? category,
|
||||
[NotNullWhen(true)] out HangmanGame.State? state)
|
||||
public bool StartHangman(ulong channelId, string? category, [NotNullWhen(true)] out HangmanGame.State? state)
|
||||
{
|
||||
state = null;
|
||||
if (!_source.GetTerm(category, out var term))
|
||||
@@ -53,10 +54,7 @@ public sealed class HangmanService : IHangmanService, ILateExecutor
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (_hangmanGames.TryRemove(channelId, out var game))
|
||||
{
|
||||
return new(true);
|
||||
}
|
||||
if (_hangmanGames.TryRemove(channelId, out var game)) return new(true);
|
||||
}
|
||||
|
||||
return new(false);
|
||||
@@ -74,7 +72,7 @@ public sealed class HangmanService : IHangmanService, ILateExecutor
|
||||
|
||||
if (_cdCache.TryGetValue(msg.Author.Id, out _))
|
||||
return;
|
||||
|
||||
|
||||
HangmanGame.State state;
|
||||
long rew = 0;
|
||||
lock (_locker)
|
||||
@@ -88,13 +86,10 @@ public sealed class HangmanService : IHangmanService, ILateExecutor
|
||||
return;
|
||||
|
||||
if (state.GuessResult is HangmanGame.GuessResult.Incorrect or HangmanGame.GuessResult.AlreadyTried)
|
||||
{
|
||||
_cdCache.Set(msg.Author.Id, string.Empty, new MemoryCacheEntryOptions()
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3)
|
||||
});
|
||||
}
|
||||
|
||||
_cdCache.Set(msg.Author.Id,
|
||||
string.Empty,
|
||||
new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3) });
|
||||
|
||||
if (state.Phase == HangmanGame.Phase.Ended)
|
||||
if (_hangmanGames.TryRemove(msg.Channel.Id, out _))
|
||||
rew = _gcs.Data.Hangman.CurrencyReward;
|
||||
@@ -106,32 +101,28 @@ public sealed class HangmanService : IHangmanService, ILateExecutor
|
||||
await SendState((ITextChannel)msg.Channel, msg.Author, msg.Content, state);
|
||||
}
|
||||
}
|
||||
|
||||
private Task<IUserMessage> SendState(ITextChannel channel, IUser user, string content, HangmanGame.State state)
|
||||
|
||||
private Task<IUserMessage> SendState(
|
||||
ITextChannel channel,
|
||||
IUser user,
|
||||
string content,
|
||||
HangmanGame.State state)
|
||||
{
|
||||
var embed = Games.HangmanCommands.GetEmbed(_eb, state);
|
||||
if (state.GuessResult == HangmanGame.GuessResult.Guess)
|
||||
embed.WithDescription($"{user} guessed the letter {content}!")
|
||||
.WithOkColor();
|
||||
embed.WithDescription($"{user} guessed the letter {content}!").WithOkColor();
|
||||
else if (state.GuessResult == HangmanGame.GuessResult.Incorrect && state.Failed)
|
||||
embed.WithDescription($"{user} Letter {content} doesn't exist! Game over!")
|
||||
.WithErrorColor();
|
||||
embed.WithDescription($"{user} Letter {content} doesn't exist! Game over!").WithErrorColor();
|
||||
else if (state.GuessResult == HangmanGame.GuessResult.Incorrect)
|
||||
embed.WithDescription($"{user} Letter {content} doesn't exist!")
|
||||
.WithErrorColor();
|
||||
embed.WithDescription($"{user} Letter {content} doesn't exist!").WithErrorColor();
|
||||
else if (state.GuessResult == HangmanGame.GuessResult.AlreadyTried)
|
||||
embed.WithDescription($"{user} Letter {content} has already been used.")
|
||||
.WithPendingColor();
|
||||
embed.WithDescription($"{user} Letter {content} has already been used.").WithPendingColor();
|
||||
else if (state.GuessResult == HangmanGame.GuessResult.Win)
|
||||
embed.WithDescription($"{user} won!")
|
||||
.WithOkColor();
|
||||
embed.WithDescription($"{user} won!").WithOkColor();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(state.ImageUrl)
|
||||
&& Uri.IsWellFormedUriString(state.ImageUrl, UriKind.Absolute))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(state.ImageUrl) && Uri.IsWellFormedUriString(state.ImageUrl, UriKind.Absolute))
|
||||
embed.WithImageUrl(state.ImageUrl);
|
||||
}
|
||||
|
||||
return channel.EmbedAsync(embed);
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,4 +5,4 @@ public sealed class HangmanTerm
|
||||
{
|
||||
public string Word { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
}
|
||||
}
|
@@ -7,4 +7,4 @@ public interface IHangmanService
|
||||
bool StartHangman(ulong channelId, string? category, [NotNullWhen(true)] out HangmanGame.State? hangmanController);
|
||||
ValueTask<bool> StopHangman(ulong channelId);
|
||||
IReadOnlyCollection<string> GetHangmanTypes();
|
||||
}
|
||||
}
|
@@ -7,4 +7,4 @@ public interface IHangmanSource : INService
|
||||
public IReadOnlyCollection<string> GetCategories();
|
||||
public void Reload();
|
||||
public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term);
|
||||
}
|
||||
}
|
@@ -10,11 +10,11 @@ public sealed class NunchiGame : IDisposable
|
||||
Joining,
|
||||
Playing,
|
||||
WaitingForNextRound,
|
||||
Ended,
|
||||
Ended
|
||||
}
|
||||
|
||||
public int CurrentNumber { get; private set; } = new NadekoRandom().Next(0, 100);
|
||||
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
||||
private const int _killTimeout = 20 * 1000;
|
||||
private const int _nextRoundTimeout = 5 * 1000;
|
||||
|
||||
public event Func<NunchiGame, Task> OnGameStarted;
|
||||
public event Func<NunchiGame, int, Task> OnRoundStarted;
|
||||
@@ -22,16 +22,19 @@ public sealed class NunchiGame : IDisposable
|
||||
public event Func<NunchiGame, (ulong Id, string Name)?, Task> OnRoundEnded; // tuple of the user who failed
|
||||
public event Func<NunchiGame, string, Task> OnGameEnded; // name of the user who won
|
||||
|
||||
public int CurrentNumber { get; private set; } = new NadekoRandom().Next(0, 100);
|
||||
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
||||
|
||||
public ImmutableArray<(ulong Id, string Name)> Participants
|
||||
=> _participants.ToImmutableArray();
|
||||
|
||||
public int ParticipantCount
|
||||
=> _participants.Count;
|
||||
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
|
||||
private HashSet<(ulong Id, string Name)> _participants = new();
|
||||
private readonly HashSet<(ulong Id, string Name)> _passed = new();
|
||||
|
||||
public ImmutableArray<(ulong Id, string Name)> Participants => _participants.ToImmutableArray();
|
||||
public int ParticipantCount => _participants.Count;
|
||||
|
||||
private const int _killTimeout = 20 * 1000;
|
||||
private const int _nextRoundTimeout = 5 * 1000;
|
||||
private Timer _killTimer;
|
||||
|
||||
public NunchiGame(ulong creatorId, string creatorName)
|
||||
@@ -64,19 +67,22 @@ public sealed class NunchiGame : IDisposable
|
||||
}
|
||||
|
||||
_killTimer = new(async state =>
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (CurrentPhase != Phase.Playing)
|
||||
return;
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (CurrentPhase != Phase.Playing)
|
||||
return;
|
||||
|
||||
//if some players took too long to type a number, boot them all out and start a new round
|
||||
_participants = new HashSet<(ulong, string)>(_passed);
|
||||
EndRound();
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
}, null, _killTimeout, _killTimeout);
|
||||
//if some players took too long to type a number, boot them all out and start a new round
|
||||
_participants = new HashSet<(ulong, string)>(_passed);
|
||||
EndRound();
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
},
|
||||
null,
|
||||
_killTimeout,
|
||||
_killTimeout);
|
||||
|
||||
CurrentPhase = Phase.Playing;
|
||||
var _ = OnGameStarted?.Invoke(this);
|
||||
@@ -145,7 +151,7 @@ public sealed class NunchiGame : IDisposable
|
||||
_killTimer.Change(_killTimeout, _killTimeout);
|
||||
CurrentNumber = new NadekoRandom().Next(0, 100); // reset the counter
|
||||
_passed.Clear(); // reset all users who passed (new round starts)
|
||||
if(failure != null)
|
||||
if (failure != null)
|
||||
_participants.Remove(failure.Value); // remove the dude who failed from the list of players
|
||||
|
||||
var __ = OnRoundEnded?.Invoke(this, failure);
|
||||
@@ -156,6 +162,7 @@ public sealed class NunchiGame : IDisposable
|
||||
var _ = OnGameEnded?.Invoke(this, _participants.Count > 0 ? _participants.First().Name : null);
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentPhase = Phase.WaitingForNextRound;
|
||||
var throwawayDelay = Task.Run(async () =>
|
||||
{
|
||||
@@ -163,7 +170,6 @@ public sealed class NunchiGame : IDisposable
|
||||
CurrentPhase = Phase.Playing;
|
||||
var ___ = OnRoundStarted?.Invoke(this, CurrentNumber);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -174,4 +180,4 @@ public sealed class NunchiGame : IDisposable
|
||||
OnRoundStarted = null;
|
||||
OnUserGuessed = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,11 +5,10 @@ namespace NadekoBot.Modules.Games.Common;
|
||||
|
||||
public class PollRunner
|
||||
{
|
||||
public event Func<IUserMessage, IGuildUser, Task> OnVoted;
|
||||
public Poll Poll { get; }
|
||||
private readonly DbService _db;
|
||||
|
||||
public event Func<IUserMessage, IGuildUser, Task> OnVoted;
|
||||
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
|
||||
public PollRunner(DbService db, Poll poll)
|
||||
@@ -40,11 +39,7 @@ public class PollRunner
|
||||
if (usr is null)
|
||||
return false;
|
||||
|
||||
voteObj = new()
|
||||
{
|
||||
UserId = msg.Author.Id,
|
||||
VoteIndex = vote,
|
||||
};
|
||||
voteObj = new() { UserId = msg.Author.Id, VoteIndex = vote };
|
||||
if (!Poll.Votes.Add(voteObj))
|
||||
return false;
|
||||
|
||||
@@ -61,4 +56,4 @@ public class PollRunner
|
||||
|
||||
public void End()
|
||||
=> OnVoted = null;
|
||||
}
|
||||
}
|
@@ -1,30 +1,12 @@
|
||||
#nullable disable
|
||||
using System.Text;
|
||||
using CommandLine;
|
||||
using System.Text;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Common;
|
||||
|
||||
public class TicTacToe
|
||||
{
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (TurnTimer is < 5 or > 60)
|
||||
TurnTimer = 15;
|
||||
}
|
||||
|
||||
[Option('t', "turn-timer", Required = false, Default = 15, HelpText = "Turn time in seconds. Default 15.")]
|
||||
public int TurnTimer { get; set; } = 15;
|
||||
}
|
||||
|
||||
private enum Phase
|
||||
{
|
||||
Starting,
|
||||
Started,
|
||||
Ended
|
||||
}
|
||||
|
||||
public event Action<TicTacToe> OnEnded;
|
||||
private readonly ITextChannel _channel;
|
||||
private readonly IGuildUser[] _users;
|
||||
private readonly int?[,] _state;
|
||||
@@ -34,9 +16,10 @@ public class TicTacToe
|
||||
|
||||
private IGuildUser _winner;
|
||||
|
||||
private readonly string[] _numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
|
||||
|
||||
public event Action<TicTacToe> OnEnded;
|
||||
private readonly string[] _numbers =
|
||||
{
|
||||
":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:"
|
||||
};
|
||||
|
||||
private IUserMessage _previousMessage;
|
||||
private Timer _timeoutTimer;
|
||||
@@ -45,8 +28,13 @@ public class TicTacToe
|
||||
private readonly Options _options;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public TicTacToe(IBotStrings strings, DiscordSocketClient client, ITextChannel channel,
|
||||
IGuildUser firstUser, Options options, IEmbedBuilderService eb)
|
||||
public TicTacToe(
|
||||
IBotStrings strings,
|
||||
DiscordSocketClient client,
|
||||
ITextChannel channel,
|
||||
IGuildUser firstUser,
|
||||
Options options,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_channel = channel;
|
||||
_strings = strings;
|
||||
@@ -55,11 +43,7 @@ public class TicTacToe
|
||||
_eb = eb;
|
||||
|
||||
_users = new[] { firstUser, null };
|
||||
_state = new int?[,] {
|
||||
{ null, null, null },
|
||||
{ null, null, null },
|
||||
{ null, null, null },
|
||||
};
|
||||
_state = new int?[,] { { null, null, null }, { null, null, null }, { null, null, null } };
|
||||
|
||||
_phase = Phase.Starting;
|
||||
_moveLock = new(1, 1);
|
||||
@@ -79,6 +63,7 @@ public class TicTacToe
|
||||
if (j < _state.GetLength(1) - 1)
|
||||
sb.Append("┃");
|
||||
}
|
||||
|
||||
if (i < _state.GetLength(0) - 1)
|
||||
sb.AppendLine("\n──────────");
|
||||
}
|
||||
@@ -89,9 +74,9 @@ public class TicTacToe
|
||||
public IEmbedBuilder GetEmbed(string title = null)
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(Environment.NewLine + GetState())
|
||||
.WithAuthor(GetText(strs.vs(_users[0], _users[1])));
|
||||
.WithOkColor()
|
||||
.WithDescription(Environment.NewLine + GetState())
|
||||
.WithAuthor(GetText(strs.vs(_users[0], _users[1])));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
embed.WithTitle(title);
|
||||
@@ -104,7 +89,9 @@ public class TicTacToe
|
||||
embed.WithFooter(GetText(strs.ttt_users_move(_users[_curUserIndex])));
|
||||
}
|
||||
else
|
||||
{
|
||||
embed.WithFooter(GetText(strs.ttt_has_won(_winner)));
|
||||
}
|
||||
|
||||
return embed;
|
||||
}
|
||||
@@ -133,7 +120,8 @@ public class TicTacToe
|
||||
await _channel.SendErrorAsync(_eb, user.Mention + GetText(strs.ttt_already_running));
|
||||
return;
|
||||
}
|
||||
else if (_users[0] == user)
|
||||
|
||||
if (_users[0] == user)
|
||||
{
|
||||
await _channel.SendErrorAsync(_eb, user.Mention + GetText(strs.ttt_against_yourself));
|
||||
return;
|
||||
@@ -144,35 +132,38 @@ public class TicTacToe
|
||||
_phase = Phase.Started;
|
||||
|
||||
_timeoutTimer = new(async _ =>
|
||||
{
|
||||
await _moveLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (_phase == Phase.Ended)
|
||||
return;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
if (_users[1] != null)
|
||||
await _moveLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
_winner = _users[_curUserIndex ^= 1];
|
||||
var del = _previousMessage?.DeleteAsync();
|
||||
try
|
||||
{
|
||||
await _channel.EmbedAsync(GetEmbed(GetText(strs.ttt_time_expired)));
|
||||
if (del != null)
|
||||
await del;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
if (_phase == Phase.Ended)
|
||||
return;
|
||||
|
||||
OnEnded?.Invoke(this);
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_moveLock.Release();
|
||||
}
|
||||
}, null, _options.TurnTimer * 1000, Timeout.Infinite);
|
||||
_phase = Phase.Ended;
|
||||
if (_users[1] != null)
|
||||
{
|
||||
_winner = _users[_curUserIndex ^= 1];
|
||||
var del = _previousMessage?.DeleteAsync();
|
||||
try
|
||||
{
|
||||
await _channel.EmbedAsync(GetEmbed(GetText(strs.ttt_time_expired)));
|
||||
if (del != null)
|
||||
await del;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
OnEnded?.Invoke(this);
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_moveLock.Release();
|
||||
}
|
||||
},
|
||||
null,
|
||||
_options.TurnTimer * 1000,
|
||||
Timeout.Infinite);
|
||||
|
||||
_client.MessageReceived += Client_MessageReceived;
|
||||
|
||||
@@ -183,13 +174,9 @@ public class TicTacToe
|
||||
private bool IsDraw()
|
||||
{
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
if (_state[i, j] is null)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (var j = 0; j < 3; j++)
|
||||
if (_state[i, j] is null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -204,10 +191,10 @@ public class TicTacToe
|
||||
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] is null)
|
||||
if (int.TryParse(msg.Content, out var index)
|
||||
&& --index >= 0
|
||||
&& index <= 9
|
||||
&& _state[index / 3, index % 3] is null)
|
||||
{
|
||||
_state[index / 3, index % 3] = _curUserIndex;
|
||||
|
||||
@@ -220,7 +207,8 @@ public class TicTacToe
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
else if (_state[0, index % 3] == _state[1, index % 3] && _state[1, index % 3] == _state[2, index % 3])
|
||||
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;
|
||||
@@ -228,7 +216,9 @@ public class TicTacToe
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
else if (_curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
|
||||
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;
|
||||
@@ -236,7 +226,9 @@ public class TicTacToe
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
else if (_curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
|
||||
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;
|
||||
@@ -244,6 +236,7 @@ public class TicTacToe
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
|
||||
var reason = string.Empty;
|
||||
|
||||
if (_phase == Phase.Ended) // if user won, stop receiving moves
|
||||
@@ -265,9 +258,17 @@ public class TicTacToe
|
||||
{
|
||||
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 { }
|
||||
try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); }
|
||||
catch { }
|
||||
|
||||
try { await del1; }
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
if (del2 != null) await del2;
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
_curUserIndex ^= 1;
|
||||
|
||||
@@ -282,4 +283,23 @@ public class TicTacToe
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
[Option('t', "turn-timer", Required = false, Default = 15, HelpText = "Turn time in seconds. Default 15.")]
|
||||
public int TurnTimer { get; set; } = 15;
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (TurnTimer is < 5 or > 60)
|
||||
TurnTimer = 15;
|
||||
}
|
||||
}
|
||||
|
||||
private enum Phase
|
||||
{
|
||||
Starting,
|
||||
Started,
|
||||
Ended
|
||||
}
|
||||
}
|
@@ -1,23 +1,14 @@
|
||||
#nullable disable
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Common.Trivia;
|
||||
|
||||
public class TriviaGame
|
||||
{
|
||||
private readonly SemaphoreSlim _guessLock = new(1, 1);
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly GamesConfig _config;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly TriviaOptions _options;
|
||||
|
||||
public IGuild Guild { get; }
|
||||
public ITextChannel Channel { get; }
|
||||
|
||||
private CancellationTokenSource _triviaCancelSource;
|
||||
|
||||
public TriviaQuestion CurrentQuestion { get; private set; }
|
||||
public HashSet<TriviaQuestion> OldQuestions { get; } = new();
|
||||
|
||||
@@ -25,15 +16,32 @@ public class TriviaGame
|
||||
|
||||
public bool GameActive { get; private set; }
|
||||
public bool ShouldStopGame { get; private set; }
|
||||
private readonly SemaphoreSlim _guessLock = new(1, 1);
|
||||
private readonly IDataCache _cache;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly GamesConfig _config;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly TriviaOptions _options;
|
||||
|
||||
private CancellationTokenSource _triviaCancelSource;
|
||||
|
||||
private readonly TriviaQuestionPool _questionPool;
|
||||
private int _timeoutCount = 0;
|
||||
private int _timeoutCount;
|
||||
private readonly string _quitCommand;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public TriviaGame(IBotStrings strings, DiscordSocketClient client, GamesConfig config,
|
||||
IDataCache cache, ICurrencyService cs, IGuild guild, ITextChannel channel,
|
||||
TriviaOptions options, string quitCommand, IEmbedBuilderService eb)
|
||||
public TriviaGame(
|
||||
IBotStrings strings,
|
||||
DiscordSocketClient client,
|
||||
GamesConfig config,
|
||||
IDataCache cache,
|
||||
ICurrencyService cs,
|
||||
IGuild guild,
|
||||
ITextChannel channel,
|
||||
TriviaOptions options,
|
||||
string quitCommand,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_cache = cache;
|
||||
_questionPool = new(_cache);
|
||||
@@ -63,31 +71,36 @@ public class TriviaGame
|
||||
|
||||
// load question
|
||||
CurrentQuestion = _questionPool.GetRandomQuestion(OldQuestions, _options.IsPokemon);
|
||||
if (string.IsNullOrWhiteSpace(CurrentQuestion?.Answer) || string.IsNullOrWhiteSpace(CurrentQuestion.Question))
|
||||
if (string.IsNullOrWhiteSpace(CurrentQuestion?.Answer)
|
||||
|| string.IsNullOrWhiteSpace(CurrentQuestion.Question))
|
||||
{
|
||||
await Channel.SendErrorAsync(_eb, GetText(strs.trivia_game), GetText(strs.failed_loading_question));
|
||||
return;
|
||||
}
|
||||
|
||||
OldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again
|
||||
|
||||
IEmbedBuilder questionEmbed;
|
||||
IUserMessage questionMessage;
|
||||
try
|
||||
{
|
||||
questionEmbed = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.trivia_game))
|
||||
.AddField(GetText(strs.category), CurrentQuestion.Category)
|
||||
.AddField(GetText(strs.question), CurrentQuestion.Question);
|
||||
questionEmbed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.trivia_game))
|
||||
.AddField(GetText(strs.category), CurrentQuestion.Category)
|
||||
.AddField(GetText(strs.question), CurrentQuestion.Question);
|
||||
|
||||
if (showHowToQuit)
|
||||
questionEmbed.WithFooter(GetText(strs.trivia_quit(_quitCommand)));
|
||||
|
||||
|
||||
if (Uri.IsWellFormedUriString(CurrentQuestion.ImageUrl, UriKind.Absolute))
|
||||
questionEmbed.WithImageUrl(CurrentQuestion.ImageUrl);
|
||||
|
||||
questionMessage = await Channel.EmbedAsync(questionEmbed);
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode is System.Net.HttpStatusCode.NotFound or System.Net.HttpStatusCode.Forbidden or System.Net.HttpStatusCode.BadRequest)
|
||||
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound
|
||||
or HttpStatusCode.Forbidden
|
||||
or HttpStatusCode.BadRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -112,9 +125,11 @@ public class TriviaGame
|
||||
if (!_options.NoHint)
|
||||
try
|
||||
{
|
||||
await questionMessage.ModifyAsync(m => m.Embed = questionEmbed.WithFooter(CurrentQuestion.GetHint()).Build());
|
||||
await questionMessage.ModifyAsync(m
|
||||
=> m.Embed = questionEmbed.WithFooter(CurrentQuestion.GetHint()).Build());
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode is System.Net.HttpStatusCode.NotFound or System.Net.HttpStatusCode.Forbidden)
|
||||
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound
|
||||
or HttpStatusCode.Forbidden)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -122,7 +137,6 @@ public class TriviaGame
|
||||
|
||||
//timeout
|
||||
await Task.Delay(_options.QuestionTimer * 1000 / 2, _triviaCancelSource.Token);
|
||||
|
||||
}
|
||||
catch (TaskCanceledException) { _timeoutCount = 0; } //means someone guessed the answer
|
||||
}
|
||||
@@ -131,13 +145,14 @@ public class TriviaGame
|
||||
GameActive = false;
|
||||
_client.MessageReceived -= PotentialGuess;
|
||||
}
|
||||
|
||||
if (!_triviaCancelSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var embed = _eb.Create().WithErrorColor()
|
||||
.WithTitle(GetText(strs.trivia_game))
|
||||
.WithDescription(GetText(strs.trivia_times_up(Format.Bold(CurrentQuestion.Answer))));
|
||||
var embed = _eb.Create()
|
||||
.WithErrorColor()
|
||||
.WithTitle(GetText(strs.trivia_game))
|
||||
.WithDescription(GetText(strs.trivia_times_up(Format.Bold(CurrentQuestion.Answer))));
|
||||
if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute))
|
||||
embed.WithImageUrl(CurrentQuestion.AnswerImageUrl);
|
||||
|
||||
@@ -150,7 +165,7 @@ public class TriviaGame
|
||||
{
|
||||
Log.Warning(ex, "Error sending trivia time's up message");
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
@@ -159,10 +174,11 @@ public class TriviaGame
|
||||
{
|
||||
ShouldStopGame = true;
|
||||
|
||||
await Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithAuthor("Trivia Game Ended")
|
||||
.WithTitle("Final Results")
|
||||
.WithDescription(GetLeaderboard()));
|
||||
await Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithAuthor("Trivia Game Ended")
|
||||
.WithTitle("Final Results")
|
||||
.WithDescription(GetLeaderboard()));
|
||||
}
|
||||
|
||||
public async Task StopGame()
|
||||
@@ -170,19 +186,14 @@ public class TriviaGame
|
||||
var old = ShouldStopGame;
|
||||
ShouldStopGame = true;
|
||||
if (!old)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Channel.SendConfirmAsync(_eb,
|
||||
GetText(strs.trivia_game),
|
||||
GetText(strs.trivia_stopping));
|
||||
|
||||
await Channel.SendConfirmAsync(_eb, GetText(strs.trivia_game), GetText(strs.trivia_stopping));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Error sending trivia stopping message");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task PotentialGuess(SocketMessage imsg)
|
||||
@@ -205,13 +216,16 @@ public class TriviaGame
|
||||
await _guessLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (GameActive && CurrentQuestion.IsAnswerCorrect(umsg.Content) && !_triviaCancelSource.IsCancellationRequested)
|
||||
if (GameActive
|
||||
&& CurrentQuestion.IsAnswerCorrect(umsg.Content)
|
||||
&& !_triviaCancelSource.IsCancellationRequested)
|
||||
{
|
||||
Users.AddOrUpdate(guildUser, 1, (gu, old) => ++old);
|
||||
guess = true;
|
||||
}
|
||||
}
|
||||
finally { _guessLock.Release(); }
|
||||
|
||||
if (!guess) return;
|
||||
_triviaCancelSource.Cancel();
|
||||
|
||||
@@ -221,11 +235,11 @@ public class TriviaGame
|
||||
ShouldStopGame = true;
|
||||
try
|
||||
{
|
||||
var embedS = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.trivia_game))
|
||||
.WithDescription(GetText(strs.trivia_win(
|
||||
guildUser.Mention,
|
||||
Format.Bold(CurrentQuestion.Answer))));
|
||||
var embedS = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.trivia_game))
|
||||
.WithDescription(GetText(strs.trivia_win(guildUser.Mention,
|
||||
Format.Bold(CurrentQuestion.Answer))));
|
||||
if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute))
|
||||
embedS.WithImageUrl(CurrentQuestion.AnswerImageUrl);
|
||||
await Channel.EmbedAsync(embedS);
|
||||
@@ -234,14 +248,18 @@ public class TriviaGame
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
var reward = _config.Trivia.CurrencyReward;
|
||||
if (reward > 0)
|
||||
await _cs.AddAsync(guildUser, "Won trivia", reward, true);
|
||||
return;
|
||||
}
|
||||
var embed = _eb.Create().WithOkColor()
|
||||
.WithTitle(GetText(strs.trivia_game))
|
||||
.WithDescription(GetText(strs.trivia_guess(guildUser.Mention, Format.Bold(CurrentQuestion.Answer))));
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.trivia_game))
|
||||
.WithDescription(GetText(strs.trivia_guess(guildUser.Mention,
|
||||
Format.Bold(CurrentQuestion.Answer))));
|
||||
if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute))
|
||||
embed.WithImageUrl(CurrentQuestion.AnswerImageUrl);
|
||||
await Channel.EmbedAsync(embed);
|
||||
@@ -259,10 +277,8 @@ public class TriviaGame
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var kvp in Users.OrderByDescending(kvp => kvp.Value))
|
||||
{
|
||||
sb.AppendLine(GetText(strs.trivia_points(Format.Bold(kvp.Key.ToString()), kvp.Value)));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,13 +7,29 @@ public class TriviaOptions : INadekoCommandOptions
|
||||
{
|
||||
[Option('p', "pokemon", Required = false, Default = false, HelpText = "Whether it's 'Who's that pokemon?' trivia.")]
|
||||
public bool IsPokemon { get; set; } = false;
|
||||
|
||||
[Option("nohint", Required = false, Default = false, HelpText = "Don't show any hints.")]
|
||||
public bool NoHint { get; set; } = false;
|
||||
[Option('w', "win-req", Required = false, Default = 10, HelpText = "Winning requirement. Set 0 for an infinite game. Default 10.")]
|
||||
|
||||
[Option('w',
|
||||
"win-req",
|
||||
Required = false,
|
||||
Default = 10,
|
||||
HelpText = "Winning requirement. Set 0 for an infinite game. Default 10.")]
|
||||
public int WinRequirement { get; set; } = 10;
|
||||
[Option('q', "question-timer", Required = false, Default = 30, HelpText = "How long until the question ends. Default 30.")]
|
||||
|
||||
[Option('q',
|
||||
"question-timer",
|
||||
Required = false,
|
||||
Default = 30,
|
||||
HelpText = "How long until the question ends. Default 30.")]
|
||||
public int QuestionTimer { get; set; } = 30;
|
||||
[Option('t', "timeout", Required = false, Default = 10, HelpText = "Number of questions of inactivity in order stop. Set 0 for never. Default 10.")]
|
||||
|
||||
[Option('t',
|
||||
"timeout",
|
||||
Required = false,
|
||||
Default = 10,
|
||||
HelpText = "Number of questions of inactivity in order stop. Set 0 for never. Default 10.")]
|
||||
public int Timeout { get; set; } = 10;
|
||||
|
||||
public void NormalizeOptions()
|
||||
@@ -24,6 +40,5 @@ public class TriviaOptions : INadekoCommandOptions
|
||||
QuestionTimer = 30;
|
||||
if (Timeout is < 0 or > 20)
|
||||
Timeout = 10;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,46 +6,47 @@ namespace NadekoBot.Modules.Games.Common.Trivia;
|
||||
|
||||
public class TriviaQuestion
|
||||
{
|
||||
public const int maxStringLength = 22;
|
||||
|
||||
//represents the min size to judge levDistance with
|
||||
private static readonly HashSet<Tuple<int, int>> strictness = new()
|
||||
{
|
||||
new(9, 0),
|
||||
new(14, 1),
|
||||
new(19, 2),
|
||||
new(22, 3),
|
||||
new(9, 0), new(14, 1), new(19, 2), new(22, 3)
|
||||
};
|
||||
public const int maxStringLength = 22;
|
||||
|
||||
public string Category { get; set; }
|
||||
public string Question { get; set; }
|
||||
public string ImageUrl { get; set; }
|
||||
public string AnswerImageUrl { get; set; }
|
||||
public string Answer { get; set; }
|
||||
private string _cleanAnswer;
|
||||
public string CleanAnswer => _cleanAnswer ?? (_cleanAnswer = Clean(Answer));
|
||||
|
||||
public TriviaQuestion(string q, string a, string c, string img = null, string answerImage = null)
|
||||
public string CleanAnswer
|
||||
=> _cleanAnswer ?? (_cleanAnswer = Clean(Answer));
|
||||
|
||||
private string _cleanAnswer;
|
||||
|
||||
public TriviaQuestion(
|
||||
string q,
|
||||
string a,
|
||||
string c,
|
||||
string img = null,
|
||||
string answerImage = null)
|
||||
{
|
||||
this.Question = q;
|
||||
this.Answer = a;
|
||||
this.Category = c;
|
||||
this.ImageUrl = img;
|
||||
this.AnswerImageUrl = answerImage ?? img;
|
||||
Question = q;
|
||||
Answer = a;
|
||||
Category = c;
|
||||
ImageUrl = img;
|
||||
AnswerImageUrl = answerImage ?? img;
|
||||
}
|
||||
|
||||
public string GetHint() => Scramble(Answer);
|
||||
public string GetHint()
|
||||
=> Scramble(Answer);
|
||||
|
||||
public bool IsAnswerCorrect(string guess)
|
||||
{
|
||||
if (Answer.Equals(guess, StringComparison.InvariantCulture))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Answer.Equals(guess, StringComparison.InvariantCulture)) return true;
|
||||
var cleanGuess = Clean(guess);
|
||||
if (CleanAnswer.Equals(cleanGuess, StringComparison.InvariantCulture))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (CleanAnswer.Equals(cleanGuess, StringComparison.InvariantCulture)) return true;
|
||||
|
||||
var levDistanceClean = CleanAnswer.LevenshteinDistance(cleanGuess);
|
||||
var levDistanceNormal = Answer.LevenshteinDistance(guess);
|
||||
@@ -56,15 +57,13 @@ public class TriviaQuestion
|
||||
private static bool JudgeGuess(int guessLength, int answerLength, int levDistance)
|
||||
{
|
||||
foreach (var level in strictness)
|
||||
{
|
||||
if (guessLength <= level.Item1 || answerLength <= level.Item1)
|
||||
{
|
||||
if (levDistance <= level.Item2)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -102,6 +101,8 @@ public class TriviaQuestion
|
||||
if (letters[i] != ' ')
|
||||
letters[i] = '_';
|
||||
}
|
||||
return string.Join(" ", new string(letters).Replace(" ", " \u2000", StringComparison.InvariantCulture).AsEnumerable());
|
||||
|
||||
return string.Join(" ",
|
||||
new string(letters).Replace(" ", " \u2000", StringComparison.InvariantCulture).AsEnumerable());
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,14 +3,17 @@ namespace NadekoBot.Modules.Games.Common.Trivia;
|
||||
|
||||
public class TriviaQuestionPool
|
||||
{
|
||||
private TriviaQuestion[] Pool
|
||||
=> _cache.LocalData.TriviaQuestions;
|
||||
|
||||
private IReadOnlyDictionary<int, string> Map
|
||||
=> _cache.LocalData.PokemonMap;
|
||||
|
||||
private readonly IDataCache _cache;
|
||||
private readonly int maxPokemonId;
|
||||
|
||||
private readonly NadekoRandom _rng = new();
|
||||
|
||||
private TriviaQuestion[] Pool => _cache.LocalData.TriviaQuestions;
|
||||
private IReadOnlyDictionary<int, string> Map => _cache.LocalData.PokemonMap;
|
||||
|
||||
public TriviaQuestionPool(IDataCache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
@@ -31,9 +34,10 @@ public class TriviaQuestionPool
|
||||
$@"https://nadeko.bot/images/pokemon/shadows/{num}.png",
|
||||
$@"https://nadeko.bot/images/pokemon/real/{num}.png");
|
||||
}
|
||||
|
||||
TriviaQuestion randomQuestion;
|
||||
while (exclude.Contains(randomQuestion = Pool[_rng.Next(0, Pool.Length)])) ;
|
||||
|
||||
return randomQuestion;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,4 +6,4 @@ public class TypingArticle
|
||||
public string Source { get; set; }
|
||||
public string Extra { get; set; }
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
@@ -1,24 +1,12 @@
|
||||
#nullable disable
|
||||
using System.Diagnostics;
|
||||
using NadekoBot.Modules.Games.Services;
|
||||
using CommandLine;
|
||||
using NadekoBot.Modules.Games.Services;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Common;
|
||||
|
||||
public class TypingGame
|
||||
{
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
[Option('s', "start-time", Default = 5, Required = false, HelpText = "How long does it take for the race to start. Default 5.")]
|
||||
public int StartTime { get; set; } = 5;
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (StartTime is < 3 or > 30)
|
||||
StartTime = 5;
|
||||
}
|
||||
}
|
||||
|
||||
public const float WORD_VALUE = 4.5f;
|
||||
public ITextChannel Channel { get; }
|
||||
public string CurrentSentence { get; private set; }
|
||||
@@ -31,8 +19,13 @@ public class TypingGame
|
||||
private readonly Options _options;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public TypingGame(GamesService games, DiscordSocketClient client, ITextChannel channel,
|
||||
string prefix, Options options, IEmbedBuilderService eb)
|
||||
public TypingGame(
|
||||
GamesService games,
|
||||
DiscordSocketClient client,
|
||||
ITextChannel channel,
|
||||
string prefix,
|
||||
Options options,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_games = games;
|
||||
_client = client;
|
||||
@@ -40,7 +33,7 @@ public class TypingGame
|
||||
_options = options;
|
||||
_eb = eb;
|
||||
|
||||
this.Channel = channel;
|
||||
Channel = channel;
|
||||
IsActive = false;
|
||||
sw = new();
|
||||
finishedUserIds = new();
|
||||
@@ -80,19 +73,19 @@ public class TypingGame
|
||||
|
||||
var time = _options.StartTime;
|
||||
|
||||
var msg = await Channel.SendMessageAsync($"Starting new typing contest in **{time}**...", options: new()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysRetry
|
||||
});
|
||||
var msg = await Channel.SendMessageAsync($"Starting new typing contest in **{time}**...",
|
||||
options: new() { RetryMode = RetryMode.AlwaysRetry });
|
||||
|
||||
do
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
time -= 2;
|
||||
try { await msg.ModifyAsync(m => m.Content = $"Starting new typing contest in **{time}**.."); } catch { }
|
||||
try { await msg.ModifyAsync(m => m.Content = $"Starting new typing contest in **{time}**.."); }
|
||||
catch { }
|
||||
} while (time > 2);
|
||||
|
||||
await msg.ModifyAsync(m => {
|
||||
await msg.ModifyAsync(m =>
|
||||
{
|
||||
m.Content = CurrentSentence.Replace(" ", " \x200B", StringComparison.InvariantCulture);
|
||||
});
|
||||
sw.Start();
|
||||
@@ -105,7 +98,6 @@ public class TypingGame
|
||||
if (!IsActive)
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
@@ -118,9 +110,7 @@ public class TypingGame
|
||||
{
|
||||
if (_games.TypingArticles.Any())
|
||||
return _games.TypingArticles[new NadekoRandom().Next(0, _games.TypingArticles.Count)].Text;
|
||||
else
|
||||
return $"No typing articles found. Use {_prefix}typeadd command to add a new article for typing.";
|
||||
|
||||
return $"No typing articles found. Use {_prefix}typeadd command to add a new article for typing.";
|
||||
}
|
||||
|
||||
private void HandleAnswers()
|
||||
@@ -137,7 +127,7 @@ public class TypingGame
|
||||
if (imsg is not SocketUserMessage msg)
|
||||
return;
|
||||
|
||||
if (this.Channel is null || this.Channel.Id != msg.Channel.Id) return;
|
||||
if (Channel is null || Channel.Id != msg.Channel.Id) return;
|
||||
|
||||
var guess = msg.Content;
|
||||
|
||||
@@ -148,18 +138,17 @@ public class TypingGame
|
||||
var elapsed = sw.Elapsed;
|
||||
var wpm = CurrentSentence.Length / WORD_VALUE / elapsed.TotalSeconds * 60;
|
||||
finishedUserIds.Add(msg.Author.Id);
|
||||
await this.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithTitle($"{msg.Author} finished the race!")
|
||||
.AddField("Place", $"#{finishedUserIds.Count}", true)
|
||||
.AddField("WPM", $"{wpm:F1} *[{elapsed.TotalSeconds:F2}sec]*", true)
|
||||
.AddField("Errors", distance.ToString(), true));
|
||||
|
||||
await Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle($"{msg.Author} finished the race!")
|
||||
.AddField("Place", $"#{finishedUserIds.Count}", true)
|
||||
.AddField("WPM", $"{wpm:F1} *[{elapsed.TotalSeconds:F2}sec]*", true)
|
||||
.AddField("Errors", distance.ToString(), true));
|
||||
|
||||
if (finishedUserIds.Count % 4 == 0)
|
||||
{
|
||||
await this.Channel.SendConfirmAsync(_eb,
|
||||
$":exclamation: A lot of people finished, here is the text for those still typing:" +
|
||||
$"\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B", StringComparison.InvariantCulture)).SanitizeMentions(true)}**");
|
||||
}
|
||||
await Channel.SendConfirmAsync(_eb,
|
||||
":exclamation: A lot of people finished, here is the text for those still typing:"
|
||||
+ $"\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B", StringComparison.InvariantCulture)).SanitizeMentions(true)}**");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -170,6 +159,22 @@ public class TypingGame
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static bool Judge(int errors, int textLength) => errors <= textLength / 25;
|
||||
private static bool Judge(int errors, int textLength)
|
||||
=> errors <= textLength / 25;
|
||||
|
||||
}
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
[Option('s',
|
||||
"start-time",
|
||||
Default = 5,
|
||||
Required = false,
|
||||
HelpText = "How long does it take for the race to start. Default 5.")]
|
||||
public int StartTime { get; set; } = 5;
|
||||
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
if (StartTime is < 3 or > 30)
|
||||
StartTime = 5;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,2 +1 @@
|
||||
#nullable disable
|
||||
|
||||
#nullable disable
|
@@ -20,7 +20,8 @@ public partial class Games : NadekoModule<GamesService>
|
||||
_httpFactory = factory;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Choose([Leftover] string list = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(list))
|
||||
@@ -32,20 +33,23 @@ public partial class Games : NadekoModule<GamesService>
|
||||
await SendConfirmAsync("🤔", listArr[rng.Next(0, listArr.Length)]);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task EightBall([Leftover] string question = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(question))
|
||||
return;
|
||||
|
||||
var res = _service.GetEightballResponse(ctx.User.Id, question);
|
||||
await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
|
||||
.WithDescription(ctx.User.ToString())
|
||||
.AddField("❓ " + GetText(strs.question), question, false)
|
||||
.AddField("🎱 " + GetText(strs._8ball), res, false));
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(ctx.User.ToString())
|
||||
.AddField("❓ " + GetText(strs.question), question)
|
||||
.AddField("🎱 " + GetText(strs._8ball), res));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task RateGirl([Leftover] IGuildUser usr)
|
||||
{
|
||||
@@ -64,16 +68,17 @@ public partial class Games : NadekoModule<GamesService>
|
||||
originalStream.Position = 0;
|
||||
originalStream.CopyTo(imgStream);
|
||||
}
|
||||
|
||||
imgStream.Position = 0;
|
||||
await ctx.Channel.SendFileAsync(stream: imgStream,
|
||||
filename: $"girl_{usr}.png",
|
||||
text: Format.Bold($"{ctx.User.Mention} Girl Rating For {usr}"),
|
||||
await ctx.Channel.SendFileAsync(imgStream,
|
||||
$"girl_{usr}.png",
|
||||
Format.Bold($"{ctx.User.Mention} Girl Rating For {usr}"),
|
||||
embed: _eb.Create()
|
||||
.WithOkColor()
|
||||
.AddField("Hot", gr.Hot.ToString("F2"), true)
|
||||
.AddField("Crazy", gr.Crazy.ToString("F2"), true)
|
||||
.AddField("Advice", gr.Advice, false)
|
||||
.Build());
|
||||
.WithOkColor()
|
||||
.AddField("Hot", gr.Hot.ToString("F2"), true)
|
||||
.AddField("Crazy", gr.Crazy.ToString("F2"), true)
|
||||
.AddField("Advice", gr.Advice)
|
||||
.Build());
|
||||
}
|
||||
|
||||
private double NextDouble(double x, double y)
|
||||
@@ -136,13 +141,13 @@ public partial class Games : NadekoModule<GamesService>
|
||||
return new(_images, _httpFactory, crazy, hot, roll, advice);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
public async Task Linux(string guhnoo, string loonix)
|
||||
=> await SendConfirmAsync(
|
||||
$@"I'd just like to interject for moment. What you're refering to as {loonix}, is in fact, {guhnoo}/{loonix}, or as I've recently taken to calling it, {guhnoo} plus {loonix}. {loonix} is not an operating system unto itself, but rather another free component of a fully functioning {guhnoo} system made useful by the {guhnoo} corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX.
|
||||
|
||||
Many computer users run a modified version of the {guhnoo} system every day, without realizing it. Through a peculiar turn of events, the version of {guhnoo} which is widely used today is often called {loonix}, and many of its users are not aware that it is basically the {guhnoo} system, developed by the {guhnoo} Project.
|
||||
|
||||
There really is a {loonix}, and these people are using it, but it is just a part of the system they use. {loonix} is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. {loonix} is normally used in combination with the {guhnoo} operating system: the whole system is basically {guhnoo} with {loonix} added, or {guhnoo}/{loonix}. All the so-called {loonix} distributions are really distributions of {guhnoo}/{loonix}."
|
||||
);
|
||||
}
|
||||
There really is a {loonix}, and these people are using it, but it is just a part of the system they use. {loonix} is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. {loonix} is normally used in combination with the {guhnoo} operating system: the whole system is basically {guhnoo} with {loonix} added, or {guhnoo}/{loonix}. All the so-called {loonix} distributions are really distributions of {guhnoo}/{loonix}.");
|
||||
}
|
@@ -7,12 +7,11 @@ public partial class Games
|
||||
[Group]
|
||||
public class HangmanCommands : NadekoSubmodule<IHangmanService>
|
||||
{
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Hangmanlist()
|
||||
=> await SendConfirmAsync(
|
||||
GetText(strs.hangman_types(Prefix)),
|
||||
_service.GetHangmanTypes().Join('\n'));
|
||||
=> await SendConfirmAsync(GetText(strs.hangman_types(Prefix)), _service.GetHangmanTypes().Join('\n'));
|
||||
|
||||
private static string Draw(HangmanGame.State state)
|
||||
=> $@". ┌─────┐
|
||||
@@ -27,28 +26,26 @@ public partial class Games
|
||||
{
|
||||
if (state.Phase == HangmanGame.Phase.Running)
|
||||
return eb.Create()
|
||||
.WithOkColor()
|
||||
.AddField("Hangman", Draw(state))
|
||||
.AddField("Guess", Format.Code(state.Word))
|
||||
.WithFooter(state.missedLetters.Join(' '));
|
||||
|
||||
.WithOkColor()
|
||||
.AddField("Hangman", Draw(state))
|
||||
.AddField("Guess", Format.Code(state.Word))
|
||||
.WithFooter(state.missedLetters.Join(' '));
|
||||
|
||||
if (state.Phase == HangmanGame.Phase.Ended && state.Failed)
|
||||
return eb.Create()
|
||||
.WithErrorColor()
|
||||
.AddField("Hangman", Draw(state))
|
||||
.AddField("Guess", Format.Code(state.Word))
|
||||
.WithFooter(state.missedLetters.Join(' '));
|
||||
else
|
||||
{
|
||||
return eb.Create()
|
||||
.WithOkColor()
|
||||
.AddField("Hangman", Draw(state))
|
||||
.AddField("Guess", Format.Code(state.Word))
|
||||
.WithFooter(state.missedLetters.Join(' '));
|
||||
}
|
||||
.WithErrorColor()
|
||||
.AddField("Hangman", Draw(state))
|
||||
.AddField("Guess", Format.Code(state.Word))
|
||||
.WithFooter(state.missedLetters.Join(' '));
|
||||
return eb.Create()
|
||||
.WithOkColor()
|
||||
.AddField("Hangman", Draw(state))
|
||||
.AddField("Guess", Format.Code(state.Word))
|
||||
.WithFooter(state.missedLetters.Join(' '));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Hangman([Leftover] string? type = null)
|
||||
{
|
||||
@@ -63,14 +60,12 @@ public partial class Games
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task HangmanStop()
|
||||
{
|
||||
if (await _service.StopHangman(ctx.Channel.Id))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.hangman_stopped);
|
||||
}
|
||||
if (await _service.StopHangman(ctx.Channel.Id)) await ReplyConfirmLocalizedAsync(strs.hangman_stopped);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,7 +14,8 @@ public partial class Games
|
||||
public NunchiCommands(DiscordSocketClient client)
|
||||
=> _client = client;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Nunchi()
|
||||
{
|
||||
@@ -26,18 +27,17 @@ public partial class Games
|
||||
{
|
||||
// join it
|
||||
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
|
||||
{
|
||||
// if you failed joining, that means game is running or just ended
|
||||
// await ReplyErrorLocalized("nunchi_already_started");
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.nunchi_joined(nunchi.ParticipantCount));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try { await ConfirmLocalizedAsync(strs.nunchi_created); } catch { }
|
||||
try { await ConfirmLocalizedAsync(strs.nunchi_created); }
|
||||
catch { }
|
||||
|
||||
nunchi.OnGameEnded += Nunchi_OnGameEnded;
|
||||
//nunchi.OnGameStarted += Nunchi_OnGameStarted;
|
||||
@@ -45,7 +45,7 @@ public partial class Games
|
||||
nunchi.OnUserGuessed += Nunchi_OnUserGuessed;
|
||||
nunchi.OnRoundStarted += Nunchi_OnRoundStarted;
|
||||
_client.MessageReceived += _client_MessageReceived;
|
||||
|
||||
|
||||
var success = await nunchi.Initialize();
|
||||
if (!success)
|
||||
{
|
||||
@@ -84,14 +84,12 @@ public partial class Games
|
||||
|
||||
if (arg2 is null)
|
||||
return ConfirmLocalizedAsync(strs.nunchi_ended_no_winner);
|
||||
else
|
||||
return ConfirmLocalizedAsync(strs.nunchi_ended(Format.Bold(arg2)));
|
||||
return ConfirmLocalizedAsync(strs.nunchi_ended(Format.Bold(arg2)));
|
||||
}
|
||||
}
|
||||
|
||||
private Task Nunchi_OnRoundStarted(NunchiGame arg, int cur)
|
||||
=> ConfirmLocalizedAsync(strs.nunchi_round_started(
|
||||
Format.Bold(arg.ParticipantCount.ToString()),
|
||||
=> ConfirmLocalizedAsync(strs.nunchi_round_started(Format.Bold(arg.ParticipantCount.ToString()),
|
||||
Format.Bold(cur.ToString())));
|
||||
|
||||
private Task Nunchi_OnUserGuessed(NunchiGame arg)
|
||||
@@ -99,14 +97,16 @@ public partial class Games
|
||||
|
||||
private Task Nunchi_OnRoundEnded(NunchiGame arg1, (ulong Id, string Name)? arg2)
|
||||
{
|
||||
if(arg2.HasValue)
|
||||
if (arg2.HasValue)
|
||||
return ConfirmLocalizedAsync(strs.nunchi_round_ended(Format.Bold(arg2.Value.Name)));
|
||||
else
|
||||
return ConfirmLocalizedAsync(strs.nunchi_round_ended_boot(
|
||||
Format.Bold("\n" + string.Join("\n, ", arg1.Participants.Select(x => x.Name))))); // this won't work if there are too many users
|
||||
return ConfirmLocalizedAsync(strs.nunchi_round_ended_boot(
|
||||
Format.Bold("\n"
|
||||
+ string.Join("\n, ",
|
||||
arg1.Participants.Select(x
|
||||
=> x.Name))))); // this won't work if there are too many users
|
||||
}
|
||||
|
||||
private Task Nunchi_OnGameStarted(NunchiGame arg)
|
||||
=> ConfirmLocalizedAsync(strs.nunchi_started(Format.Bold(arg.ParticipantCount.ToString())));
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,7 +15,8 @@ public partial class Games
|
||||
public PollCommands(DiscordSocketClient client)
|
||||
=> _client = client;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Poll([Leftover] string arg)
|
||||
@@ -23,31 +24,28 @@ public partial class Games
|
||||
if (string.IsNullOrWhiteSpace(arg))
|
||||
return;
|
||||
|
||||
var poll = _service.CreatePoll(ctx.Guild.Id,
|
||||
ctx.Channel.Id, arg);
|
||||
if(poll is null)
|
||||
var poll = _service.CreatePoll(ctx.Guild.Id, ctx.Channel.Id, arg);
|
||||
if (poll is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.poll_invalid_input);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_service.StartPoll(poll))
|
||||
{
|
||||
await ctx.Channel
|
||||
.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.poll_created(ctx.User.ToString())))
|
||||
.WithDescription(
|
||||
Format.Bold(poll.Question) + "\n\n" +
|
||||
string.Join("\n", poll.Answers
|
||||
.Select(x => $"`{x.Index + 1}.` {Format.Bold(x.Text)}"))));
|
||||
}
|
||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.poll_created(ctx.User.ToString())))
|
||||
.WithDescription(Format.Bold(poll.Question)
|
||||
+ "\n\n"
|
||||
+ string.Join("\n",
|
||||
poll.Answers.Select(x
|
||||
=> $"`{x.Index + 1}.` {Format.Bold(x.Text)}"))));
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.poll_already_running);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task PollStats()
|
||||
@@ -58,7 +56,8 @@ public partial class Games
|
||||
await ctx.Channel.EmbedAsync(GetStats(pr.Poll, GetText(strs.current_poll_results)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Pollend()
|
||||
@@ -75,39 +74,32 @@ public partial class Games
|
||||
|
||||
public IEmbedBuilder GetStats(Poll poll, string title)
|
||||
{
|
||||
var results = poll.Votes.GroupBy(kvp => kvp.VoteIndex)
|
||||
.ToDictionary(x => x.Key, x => x.Sum(kvp => 1));
|
||||
var results = poll.Votes.GroupBy(kvp => kvp.VoteIndex).ToDictionary(x => x.Key, x => x.Sum(kvp => 1));
|
||||
|
||||
var totalVotesCast = results.Sum(x => x.Value);
|
||||
|
||||
var eb = _eb.Create().WithTitle(title);
|
||||
|
||||
var sb = new StringBuilder()
|
||||
.AppendLine(Format.Bold(poll.Question))
|
||||
.AppendLine();
|
||||
var sb = new StringBuilder().AppendLine(Format.Bold(poll.Question)).AppendLine();
|
||||
|
||||
var stats = poll.Answers
|
||||
.Select(x =>
|
||||
{
|
||||
results.TryGetValue(x.Index, out var votes);
|
||||
var stats = poll.Answers.Select(x =>
|
||||
{
|
||||
results.TryGetValue(x.Index, out var votes);
|
||||
|
||||
return (x.Index, votes, x.Text);
|
||||
})
|
||||
.OrderByDescending(x => x.votes)
|
||||
.ToArray();
|
||||
return (x.Index, votes, x.Text);
|
||||
})
|
||||
.OrderByDescending(x => x.votes)
|
||||
.ToArray();
|
||||
|
||||
for (var i = 0; i < stats.Length; i++)
|
||||
{
|
||||
var (Index, votes, Text) = stats[i];
|
||||
sb.AppendLine(GetText(strs.poll_result(
|
||||
Index + 1,
|
||||
Format.Bold(Text),
|
||||
Format.Bold(votes.ToString()))));
|
||||
sb.AppendLine(GetText(strs.poll_result(Index + 1, Format.Bold(Text), Format.Bold(votes.ToString()))));
|
||||
}
|
||||
|
||||
return eb.WithDescription(sb.ToString())
|
||||
.WithFooter(GetText(strs.x_votes_cast(totalVotesCast)))
|
||||
.WithOkColor();
|
||||
.WithFooter(GetText(strs.x_votes_cast(totalVotesCast)))
|
||||
.WithOkColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,13 +1,18 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Modules.Games.Common.ChatterBot;
|
||||
using NadekoBot.Modules.Permissions.Common;
|
||||
using NadekoBot.Modules.Permissions.Services;
|
||||
using NadekoBot.Modules.Games.Common.ChatterBot;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Services;
|
||||
|
||||
public class ChatterBotService : IEarlyBehavior
|
||||
{
|
||||
public ConcurrentDictionary<ulong, Lazy<IChatterBotSession>> ChatterBotGuilds { get; }
|
||||
|
||||
public int Priority
|
||||
=> 1;
|
||||
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly PermissionService _perms;
|
||||
private readonly CommandHandler _cmd;
|
||||
@@ -16,13 +21,15 @@ public class ChatterBotService : IEarlyBehavior
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
|
||||
public ConcurrentDictionary<ulong, Lazy<IChatterBotSession>> ChatterBotGuilds { get; }
|
||||
|
||||
public int Priority => 1;
|
||||
|
||||
public ChatterBotService(DiscordSocketClient client, PermissionService perms,
|
||||
Bot bot, CommandHandler cmd, IBotStrings strings, IHttpClientFactory factory,
|
||||
IBotCredentials creds, IEmbedBuilderService eb)
|
||||
public ChatterBotService(
|
||||
DiscordSocketClient client,
|
||||
PermissionService perms,
|
||||
Bot bot,
|
||||
CommandHandler cmd,
|
||||
IBotStrings strings,
|
||||
IHttpClientFactory factory,
|
||||
IBotCredentials creds,
|
||||
IEmbedBuilderService eb)
|
||||
{
|
||||
_client = client;
|
||||
_perms = perms;
|
||||
@@ -32,18 +39,16 @@ public class ChatterBotService : IEarlyBehavior
|
||||
_eb = eb;
|
||||
_httpFactory = factory;
|
||||
|
||||
ChatterBotGuilds = new(
|
||||
bot.AllGuildConfigs
|
||||
.Where(gc => gc.CleverbotEnabled)
|
||||
.ToDictionary(gc => gc.GuildId, gc => new Lazy<IChatterBotSession>(() => CreateSession(), true)));
|
||||
ChatterBotGuilds = new(bot.AllGuildConfigs.Where(gc => gc.CleverbotEnabled)
|
||||
.ToDictionary(gc => gc.GuildId,
|
||||
gc => new Lazy<IChatterBotSession>(() => CreateSession(), true)));
|
||||
}
|
||||
|
||||
public IChatterBotSession CreateSession()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_creds.CleverbotApiKey))
|
||||
return new OfficialCleverbotSession(_creds.CleverbotApiKey, _httpFactory);
|
||||
else
|
||||
return new CleverbotIOSession("GAh3wUfzDCpDpdpT", "RStKgqn7tcO9blbrv4KbXM8NDlb7H37C", _httpFactory);
|
||||
return new CleverbotIOSession("GAh3wUfzDCpDpdpT", "RStKgqn7tcO9blbrv4KbXM8NDlb7H37C", _httpFactory);
|
||||
}
|
||||
|
||||
public string PrepareMessage(IUserMessage msg, out IChatterBotSession cleverbot)
|
||||
@@ -64,17 +69,11 @@ public class ChatterBotService : IEarlyBehavior
|
||||
var nickMention = $"<@!{nadekoId}> ";
|
||||
string message;
|
||||
if (msg.Content.StartsWith(normalMention, StringComparison.InvariantCulture))
|
||||
{
|
||||
message = msg.Content[normalMention.Length..].Trim();
|
||||
}
|
||||
else if (msg.Content.StartsWith(nickMention, StringComparison.InvariantCulture))
|
||||
{
|
||||
message = msg.Content[nickMention.Length..].Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
@@ -92,6 +91,7 @@ public class ChatterBotService : IEarlyBehavior
|
||||
{
|
||||
await channel.SendConfirmAsync(_eb, response.SanitizeMentions(true)); // try twice :\
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -106,19 +106,19 @@ public class ChatterBotService : IEarlyBehavior
|
||||
return false;
|
||||
|
||||
var pc = _perms.GetCacheFor(guild.Id);
|
||||
if (!pc.Permissions.CheckPermissions(usrMsg,
|
||||
"cleverbot",
|
||||
"Games".ToLowerInvariant(),
|
||||
out var index))
|
||||
if (!pc.Permissions.CheckPermissions(usrMsg, "cleverbot", "Games".ToLowerInvariant(), out var index))
|
||||
{
|
||||
if (pc.Verbose)
|
||||
{
|
||||
var returnMsg = _strings.GetText(strs.perm_prevent(index + 1,
|
||||
Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(sg), sg))));
|
||||
|
||||
try { await usrMsg.Channel.SendErrorAsync(_eb, returnMsg); } catch { }
|
||||
|
||||
try { await usrMsg.Channel.SendErrorAsync(_eb, returnMsg); }
|
||||
catch { }
|
||||
|
||||
Log.Information(returnMsg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -135,8 +135,9 @@ Message: {usrMsg.Content}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex,"Error in cleverbot");
|
||||
Log.Warning(ex, "Error in cleverbot");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,35 +6,39 @@ namespace NadekoBot.Modules.Games.Services;
|
||||
|
||||
public sealed class GamesConfigService : ConfigServiceBase<GamesConfig>
|
||||
{
|
||||
public override string Name { get; } = "games";
|
||||
private const string FilePath = "data/games.yml";
|
||||
private static readonly TypedKey<GamesConfig> changeKey = new("config.games.updated");
|
||||
public override string Name { get; } = "games";
|
||||
|
||||
public GamesConfigService(IConfigSeria serializer, IPubSub pubSub)
|
||||
: base(FilePath, serializer, pubSub, changeKey)
|
||||
{
|
||||
AddParsedProp("trivia.min_win_req", gs => gs.Trivia.MinimumWinReq, int.TryParse,
|
||||
ConfigPrinters.ToString, val => val > 0);
|
||||
AddParsedProp("trivia.currency_reward", gs => gs.Trivia.CurrencyReward, long.TryParse,
|
||||
ConfigPrinters.ToString, val => val >= 0);
|
||||
AddParsedProp("hangman.currency_reward", gs => gs.Hangman.CurrencyReward, long.TryParse,
|
||||
ConfigPrinters.ToString, val => val >= 0);
|
||||
|
||||
AddParsedProp("trivia.min_win_req",
|
||||
gs => gs.Trivia.MinimumWinReq,
|
||||
int.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val > 0);
|
||||
AddParsedProp("trivia.currency_reward",
|
||||
gs => gs.Trivia.CurrencyReward,
|
||||
long.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 0);
|
||||
AddParsedProp("hangman.currency_reward",
|
||||
gs => gs.Hangman.CurrencyReward,
|
||||
long.TryParse,
|
||||
ConfigPrinters.ToString,
|
||||
val => val >= 0);
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
|
||||
private void Migrate()
|
||||
{
|
||||
if (data.Version < 1)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 1;
|
||||
c.Hangman = new()
|
||||
{
|
||||
CurrencyReward = 0
|
||||
};
|
||||
c.Hangman = new() { CurrencyReward = 0 };
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,27 +1,21 @@
|
||||
#nullable disable
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NadekoBot.Modules.Games.Common;
|
||||
using NadekoBot.Modules.Games.Common.Acrophobia;
|
||||
using NadekoBot.Modules.Games.Common.Nunchi;
|
||||
using NadekoBot.Modules.Games.Common.Trivia;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Services;
|
||||
|
||||
public class GamesService : INService
|
||||
{
|
||||
private readonly GamesConfigService _gamesConfig;
|
||||
private const string TypingArticlesPath = "data/typing_articles3.json";
|
||||
|
||||
public ConcurrentDictionary<ulong, GirlRating> GirlRatings { get; } = new();
|
||||
|
||||
public IReadOnlyList<string> EightBallResponses => _gamesConfig.Data.EightBallResponses;
|
||||
|
||||
private readonly Timer _t;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IMemoryCache _8BallCache;
|
||||
private readonly Random _rng;
|
||||
|
||||
private const string TypingArticlesPath = "data/typing_articles3.json";
|
||||
public IReadOnlyList<string> EightBallResponses
|
||||
=> _gamesConfig.Data.EightBallResponses;
|
||||
|
||||
public List<TypingArticle> TypingArticles { get; } = new();
|
||||
|
||||
@@ -33,36 +27,30 @@ public class GamesService : INService
|
||||
public ConcurrentDictionary<ulong, NunchiGame> NunchiGames { get; } = new();
|
||||
|
||||
public AsyncLazy<RatingTexts> Ratings { get; }
|
||||
private readonly GamesConfigService _gamesConfig;
|
||||
|
||||
public class RatingTexts
|
||||
{
|
||||
public string Nog { get; set; }
|
||||
public string Tra { get; set; }
|
||||
public string Fun { get; set; }
|
||||
public string Uni { get; set; }
|
||||
public string Wif { get; set; }
|
||||
public string Dat { get; set; }
|
||||
public string Dan { get; set; }
|
||||
}
|
||||
private readonly Timer _t;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IMemoryCache _8BallCache;
|
||||
private readonly Random _rng;
|
||||
|
||||
public GamesService(GamesConfigService gamesConfig, IHttpClientFactory httpFactory)
|
||||
{
|
||||
_gamesConfig = gamesConfig;
|
||||
_httpFactory = httpFactory;
|
||||
_8BallCache = new MemoryCache(new MemoryCacheOptions()
|
||||
{
|
||||
SizeLimit = 500_000
|
||||
});
|
||||
_8BallCache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 500_000 });
|
||||
|
||||
Ratings = new(GetRatingTexts);
|
||||
_rng = new NadekoRandom();
|
||||
|
||||
//girl ratings
|
||||
_t = new(_ =>
|
||||
{
|
||||
GirlRatings.Clear();
|
||||
|
||||
}, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1));
|
||||
{
|
||||
GirlRatings.Clear();
|
||||
},
|
||||
null,
|
||||
TimeSpan.FromDays(1),
|
||||
TimeSpan.FromDays(1));
|
||||
|
||||
try
|
||||
{
|
||||
@@ -78,7 +66,8 @@ public class GamesService : INService
|
||||
private async Task<RatingTexts> GetRatingTexts()
|
||||
{
|
||||
using var http = _httpFactory.CreateClient();
|
||||
var text = await http.GetStringAsync("https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/rates.json");
|
||||
var text = await http.GetStringAsync(
|
||||
"https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/rates.json");
|
||||
return JsonConvert.DeserializeObject<RatingTexts>(text);
|
||||
}
|
||||
|
||||
@@ -88,19 +77,21 @@ public class GamesService : INService
|
||||
{
|
||||
Source = user.ToString(),
|
||||
Extra = $"Text added on {DateTime.UtcNow} by {user}.",
|
||||
Text = text.SanitizeMentions(true),
|
||||
Text = text.SanitizeMentions(true)
|
||||
});
|
||||
|
||||
File.WriteAllText(TypingArticlesPath, JsonConvert.SerializeObject(TypingArticles));
|
||||
}
|
||||
|
||||
public string GetEightballResponse(ulong userId, string question)
|
||||
=> _8BallCache.GetOrCreate($"8ball:{userId}:{question}", e =>
|
||||
{
|
||||
e.Size = question.Length;
|
||||
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12);
|
||||
return EightBallResponses[_rng.Next(0, EightBallResponses.Count)];;
|
||||
});
|
||||
=> _8BallCache.GetOrCreate($"8ball:{userId}:{question}",
|
||||
e =>
|
||||
{
|
||||
e.Size = question.Length;
|
||||
e.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12);
|
||||
return EightBallResponses[_rng.Next(0, EightBallResponses.Count)];
|
||||
;
|
||||
});
|
||||
|
||||
public TypingArticle RemoveTypingArticle(int index)
|
||||
{
|
||||
@@ -110,8 +101,19 @@ public class GamesService : INService
|
||||
|
||||
var removed = articles[index];
|
||||
TypingArticles.RemoveAt(index);
|
||||
|
||||
|
||||
File.WriteAllText(TypingArticlesPath, JsonConvert.SerializeObject(articles));
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
public class RatingTexts
|
||||
{
|
||||
public string Nog { get; set; }
|
||||
public string Tra { get; set; }
|
||||
public string Fun { get; set; }
|
||||
public string Uni { get; set; }
|
||||
public string Wif { get; set; }
|
||||
public string Dat { get; set; }
|
||||
public string Dan { get; set; }
|
||||
}
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Modules.Games.Common;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Games.Common;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Services;
|
||||
|
||||
@@ -11,7 +11,8 @@ public class PollService : IEarlyBehavior
|
||||
{
|
||||
public ConcurrentDictionary<ulong, PollRunner> ActivePolls { get; } = new();
|
||||
|
||||
public int Priority => 5;
|
||||
public int Priority
|
||||
=> 5;
|
||||
|
||||
private readonly DbService _db;
|
||||
private readonly IBotStrings _strs;
|
||||
@@ -25,13 +26,14 @@ public class PollService : IEarlyBehavior
|
||||
|
||||
using var uow = db.GetDbContext();
|
||||
ActivePolls = uow.Poll.GetAllPolls()
|
||||
.ToDictionary(x => x.GuildId, x =>
|
||||
{
|
||||
var pr = new PollRunner(db, x);
|
||||
pr.OnVoted += Pr_OnVoted;
|
||||
return pr;
|
||||
})
|
||||
.ToConcurrent();
|
||||
.ToDictionary(x => x.GuildId,
|
||||
x =>
|
||||
{
|
||||
var pr = new PollRunner(db, x);
|
||||
pr.OnVoted += Pr_OnVoted;
|
||||
return pr;
|
||||
})
|
||||
.ToConcurrent();
|
||||
}
|
||||
|
||||
public Poll CreatePoll(ulong guildId, ulong channelId, string input)
|
||||
@@ -42,8 +44,7 @@ public class PollService : IEarlyBehavior
|
||||
if (data.Length < 3)
|
||||
return null;
|
||||
|
||||
var col = new IndexedCollection<PollAnswer>(data.Skip(1)
|
||||
.Select(x => new PollAnswer() { Text = x }));
|
||||
var col = new IndexedCollection<PollAnswer>(data.Skip(1).Select(x => new PollAnswer { Text = x }));
|
||||
|
||||
return new()
|
||||
{
|
||||
@@ -57,7 +58,7 @@ public class PollService : IEarlyBehavior
|
||||
|
||||
public bool StartPoll(Poll p)
|
||||
{
|
||||
var pr = new PollRunner(_db, p);
|
||||
var pr = new PollRunner(_db, p);
|
||||
if (ActivePolls.TryAdd(p.GuildId, pr))
|
||||
{
|
||||
using (var uow = _db.GetDbContext())
|
||||
@@ -69,6 +70,7 @@ public class PollService : IEarlyBehavior
|
||||
pr.OnVoted += Pr_OnVoted;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -77,22 +79,24 @@ public class PollService : IEarlyBehavior
|
||||
if (ActivePolls.TryRemove(guildId, out var pr))
|
||||
{
|
||||
pr.OnVoted -= Pr_OnVoted;
|
||||
|
||||
|
||||
using var uow = _db.GetDbContext();
|
||||
uow.RemovePoll(pr.Poll.Id);
|
||||
uow.SaveChanges();
|
||||
|
||||
|
||||
return pr.Poll;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task Pr_OnVoted(IUserMessage msg, IGuildUser usr)
|
||||
{
|
||||
var toDelete = await msg.Channel.SendConfirmAsync(_eb,
|
||||
_strs.GetText(strs.poll_voted(Format.Bold(usr.ToString())), usr.GuildId));
|
||||
var toDelete = await msg.Channel.SendConfirmAsync(_eb,
|
||||
_strs.GetText(strs.poll_voted(Format.Bold(usr.ToString())), usr.GuildId));
|
||||
toDelete.DeleteAfter(5);
|
||||
try { await msg.DeleteAsync(); } catch { }
|
||||
try { await msg.DeleteAsync(); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
public async Task<bool> RunBehavior(IGuild guild, IUserMessage msg)
|
||||
@@ -108,14 +112,12 @@ public class PollService : IEarlyBehavior
|
||||
var voted = await poll.TryVote(msg);
|
||||
|
||||
if (voted)
|
||||
{
|
||||
Log.Information("User {UserName} [{UserId}] voted in a poll on {GuildName} [{GuildId}] server",
|
||||
msg.Author.ToString(),
|
||||
msg.Author.Id,
|
||||
guild.Name,
|
||||
guild.Id);
|
||||
}
|
||||
|
||||
|
||||
return voted;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -125,4 +127,4 @@ public class PollService : IEarlyBehavior
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,7 +18,8 @@ public partial class Games
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptionsAttribute(typeof(TypingGame.Options))]
|
||||
public async Task TypeStart(params string[] args)
|
||||
@@ -26,19 +27,17 @@ public partial class Games
|
||||
var (options, _) = OptionsParser.ParseFrom(new TypingGame.Options(), args);
|
||||
var channel = (ITextChannel)ctx.Channel;
|
||||
|
||||
var game = _service.RunningContests.GetOrAdd(ctx.Guild.Id, id => new(_games, _client, channel, Prefix, options, _eb));
|
||||
var game = _service.RunningContests.GetOrAdd(ctx.Guild.Id,
|
||||
id => new(_games, _client, channel, Prefix, options, _eb));
|
||||
|
||||
if (game.IsActive)
|
||||
{
|
||||
await SendErrorAsync($"Contest already running in {game.Channel.Mention} channel.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await game.Start();
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task TypeStop()
|
||||
{
|
||||
@@ -47,12 +46,13 @@ public partial class Games
|
||||
await game.Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await SendErrorAsync("No contest to stop on this channel.");
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task Typeadd([Leftover] string text)
|
||||
@@ -60,12 +60,13 @@ public partial class Games
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return;
|
||||
|
||||
_games.AddTypingArticle(ctx.User, text);
|
||||
_games.AddTypingArticle(ctx.User, text);
|
||||
|
||||
await SendConfirmAsync("Added new article for typing game.");
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Typelist(int page = 1)
|
||||
{
|
||||
@@ -79,28 +80,28 @@ public partial class Games
|
||||
await SendErrorAsync($"{ctx.User.Mention} `No articles found on that page.`");
|
||||
return;
|
||||
}
|
||||
|
||||
var i = (page - 1) * 15;
|
||||
await SendConfirmAsync("List of articles for Type Race", string.Join("\n", articles.Select(a => $"`#{++i}` - {a.Text.TrimTo(50)}")));
|
||||
await SendConfirmAsync("List of articles for Type Race",
|
||||
string.Join("\n", articles.Select(a => $"`#{++i}` - {a.Text.TrimTo(50)}")));
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task Typedel(int index)
|
||||
{
|
||||
var removed = _service.RemoveTypingArticle(--index);
|
||||
|
||||
if (removed is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (removed is null) return;
|
||||
|
||||
var embed = _eb.Create()
|
||||
.WithTitle($"Removed typing article #{index + 1}")
|
||||
.WithDescription(removed.Text.TrimTo(50))
|
||||
.WithOkColor();
|
||||
.WithTitle($"Removed typing article #{index + 1}")
|
||||
.WithDescription(removed.Text.TrimTo(50))
|
||||
.WithOkColor();
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,7 +15,8 @@ public partial class Games
|
||||
public TicTacToeCommands(DiscordSocketClient client)
|
||||
=> _client = client;
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NadekoOptions(typeof(TicTacToe.Options))]
|
||||
public async Task TicTacToe(params string[] args)
|
||||
@@ -34,7 +35,8 @@ public partial class Games
|
||||
});
|
||||
return;
|
||||
}
|
||||
game = new(base.Strings, this._client, channel, (IGuildUser)ctx.User, options, _eb);
|
||||
|
||||
game = new(Strings, _client, channel, (IGuildUser)ctx.User, options, _eb);
|
||||
_service.TicTacToeGames.Add(channel.Id, game);
|
||||
await ReplyConfirmLocalizedAsync(strs.ttt_created);
|
||||
|
||||
@@ -50,4 +52,4 @@ public partial class Games
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,7 +14,10 @@ public partial class Games
|
||||
private readonly GamesConfigService _gamesConfig;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public TriviaCommands(DiscordSocketClient client, IDataCache cache, ICurrencyService cs,
|
||||
public TriviaCommands(
|
||||
DiscordSocketClient client,
|
||||
IDataCache cache,
|
||||
ICurrencyService cs,
|
||||
GamesConfigService gamesConfig)
|
||||
{
|
||||
_cache = cache;
|
||||
@@ -23,7 +26,8 @@ public partial class Games
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
[NadekoOptionsAttribute(typeof(TriviaOptions))]
|
||||
@@ -37,13 +41,18 @@ public partial class Games
|
||||
var (opts, _) = OptionsParser.ParseFrom(new TriviaOptions(), args);
|
||||
|
||||
var config = _gamesConfig.Data;
|
||||
if (config.Trivia.MinimumWinReq > 0 && config.Trivia.MinimumWinReq > opts.WinRequirement)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (config.Trivia.MinimumWinReq > 0 && config.Trivia.MinimumWinReq > opts.WinRequirement) return;
|
||||
|
||||
var trivia = new TriviaGame(Strings, _client, config, _cache, _cs, channel.Guild, channel, opts,
|
||||
Prefix + "tq", _eb);
|
||||
var trivia = new TriviaGame(Strings,
|
||||
_client,
|
||||
config,
|
||||
_cache,
|
||||
_cs,
|
||||
channel.Guild,
|
||||
channel,
|
||||
opts,
|
||||
Prefix + "tq",
|
||||
_eb);
|
||||
if (_service.RunningTrivias.TryAdd(channel.Guild.Id, trivia))
|
||||
{
|
||||
try
|
||||
@@ -55,13 +64,15 @@ public partial class Games
|
||||
_service.RunningTrivias.TryRemove(channel.Guild.Id, out trivia);
|
||||
await trivia.EnsureStopped();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await SendErrorAsync(GetText(strs.trivia_already_running) + "\n" + trivia.CurrentQuestion);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Tl()
|
||||
{
|
||||
@@ -74,7 +85,8 @@ public partial class Games
|
||||
await ReplyErrorLocalizedAsync(strs.trivia_none);
|
||||
}
|
||||
|
||||
[NadekoCommand, Aliases]
|
||||
[NadekoCommand]
|
||||
[Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Tq()
|
||||
{
|
||||
@@ -89,4 +101,4 @@ public partial class Games
|
||||
await ReplyErrorLocalizedAsync(strs.trivia_none);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user