Gambling moved to a separate project. Project builds

This commit is contained in:
Kwoth
2023-03-18 18:36:04 +01:00
parent db2328cdaf
commit 09171fb10a
361 changed files with 532 additions and 476 deletions

View File

@@ -1,155 +0,0 @@
#nullable disable
using Nadeko.Common;
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
using NadekoBot.Modules.Games.Common;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
public sealed class AnimalRace : IDisposable
{
public enum Phase
{
WaitingForPlayers,
Running,
Ended
}
public event Func<AnimalRace, Task> OnStarted = delegate { return Task.CompletedTask; };
public event Func<AnimalRace, Task> OnStartingFailed = delegate { return Task.CompletedTask; };
public event Func<AnimalRace, Task> OnStateUpdate = delegate { return Task.CompletedTask; };
public event Func<AnimalRace, Task> OnEnded = delegate { return Task.CompletedTask; };
public Phase CurrentPhase { get; private set; } = Phase.WaitingForPlayers;
public IReadOnlyCollection<AnimalRacingUser> Users
=> _users.ToList();
public List<AnimalRacingUser> FinishedUsers { get; } = new();
public int MaxUsers { get; }
private readonly SemaphoreSlim _locker = new(1, 1);
private readonly HashSet<AnimalRacingUser> _users = new();
private readonly ICurrencyService _currency;
private readonly RaceOptions _options;
private readonly Queue<RaceAnimal> _animalsQueue;
public AnimalRace(RaceOptions options, ICurrencyService currency, IEnumerable<RaceAnimal> availableAnimals)
{
_currency = currency;
_options = options;
_animalsQueue = new(availableAnimals);
MaxUsers = _animalsQueue.Count;
if (_animalsQueue.Count == 0)
CurrentPhase = Phase.Ended;
}
public void Initialize() //lame name
=> _ = Task.Run(async () =>
{
await Task.Delay(_options.StartTime * 1000);
await _locker.WaitAsync();
try
{
if (CurrentPhase != Phase.WaitingForPlayers)
return;
await Start();
}
finally { _locker.Release(); }
});
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
{
if (bet < 0)
throw new ArgumentOutOfRangeException(nameof(bet));
var user = new AnimalRacingUser(userName, userId, bet);
await _locker.WaitAsync();
try
{
if (_users.Count == MaxUsers)
throw new AnimalRaceFullException();
if (CurrentPhase != Phase.WaitingForPlayers)
throw new AlreadyStartedException();
if (!await _currency.RemoveAsync(userId, bet, new("animalrace", "bet")))
throw new NotEnoughFundsException();
if (_users.Contains(user))
throw new AlreadyJoinedException();
var animal = _animalsQueue.Dequeue();
user.Animal = animal;
_users.Add(user);
if (_animalsQueue.Count == 0) //start if no more spots left
await Start();
return user;
}
finally { _locker.Release(); }
}
private async Task Start()
{
CurrentPhase = Phase.Running;
if (_users.Count <= 1)
{
foreach (var user in _users)
{
if (user.Bet > 0)
await _currency.AddAsync(user.UserId, user.Bet, new("animalrace", "refund"));
}
_ = OnStartingFailed?.Invoke(this);
CurrentPhase = Phase.Ended;
return;
}
_ = OnStarted?.Invoke(this);
_ = Task.Run(async () =>
{
var rng = new NadekoRandom();
while (!_users.All(x => x.Progress >= 60))
{
foreach (var user in _users)
{
user.Progress += rng.Next(1, 11);
if (user.Progress >= 60)
user.Progress = 60;
}
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x)).Shuffle();
FinishedUsers.AddRange(finished);
_ = OnStateUpdate?.Invoke(this);
await Task.Delay(2500);
}
if (FinishedUsers[0].Bet > 0)
{
await _currency.AddAsync(FinishedUsers[0].UserId,
FinishedUsers[0].Bet * (_users.Count - 1),
new("animalrace", "win"));
}
_ = OnEnded?.Invoke(this);
});
}
public void Dispose()
{
CurrentPhase = Phase.Ended;
OnStarted = null;
OnEnded = null;
OnStartingFailed = null;
OnStateUpdate = null;
_locker.Dispose();
_users.Clear();
}
}

View File

@@ -1,9 +0,0 @@
#nullable disable
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
namespace NadekoBot.Modules.Gambling.Services;
public class AnimalRaceService : INService
{
public ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new();
}

View File

@@ -1,184 +0,0 @@
#nullable disable
using Nadeko.Common;
using NadekoBot.Common.TypeReaders;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Common.AnimalRacing;
using NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
using NadekoBot.Modules.Gambling.Services;
using NadekoBot.Modules.Games.Services;
namespace NadekoBot.Modules.Gambling;
// wth is this, needs full rewrite
public partial class Gambling
{
[Group]
public partial class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
{
private readonly ICurrencyService _cs;
private readonly DiscordSocketClient _client;
private readonly GamesConfigService _gamesConf;
private IUserMessage raceMessage;
public AnimalRacingCommands(
ICurrencyService cs,
DiscordSocketClient client,
GamblingConfigService gamblingConf,
GamesConfigService gamesConf)
: base(gamblingConf)
{
_cs = cs;
_client = client;
_gamesConf = gamesConf;
}
[Cmd]
[RequireContext(ContextType.Guild)]
[NadekoOptions<RaceOptions>]
public Task Race(params string[] args)
{
var (options, _) = OptionsParser.ParseFrom(new RaceOptions(), args);
var ar = new AnimalRace(options, _cs, _gamesConf.Data.RaceAnimals.Shuffle());
if (!_service.AnimalRaces.TryAdd(ctx.Guild.Id, ar))
return SendErrorAsync(GetText(strs.animal_race), GetText(strs.animal_race_already_started));
ar.Initialize();
var count = 0;
Task ClientMessageReceived(SocketMessage arg)
{
_ = Task.Run(() =>
{
try
{
if (arg.Channel.Id == ctx.Channel.Id)
{
if (ar.CurrentPhase == AnimalRace.Phase.Running && ++count % 9 == 0)
raceMessage = null;
}
}
catch { }
});
return Task.CompletedTask;
}
Task ArOnEnded(AnimalRace race)
{
_client.MessageReceived -= ClientMessageReceived;
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
var winner = race.FinishedUsers[0];
if (race.FinishedUsers[0].Bet > 0)
{
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_won_money(Format.Bold(winner.Username),
winner.Animal.Icon,
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)));
}
ar.Dispose();
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)));
}
ar.OnStartingFailed += Ar_OnStartingFailed;
ar.OnStateUpdate += Ar_OnStateUpdate;
ar.OnEnded += ArOnEnded;
ar.OnStarted += Ar_OnStarted;
_client.MessageReceived += ClientMessageReceived;
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_starting(options.StartTime)),
footer: GetText(strs.animal_race_join_instr(prefix)));
}
private Task Ar_OnStarted(AnimalRace race)
{
if (race.Users.Count == race.MaxUsers)
return SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full));
return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_starting_with_x(race.Users.Count)));
}
private async Task Ar_OnStateUpdate(AnimalRace race)
{
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
{string.Join("\n", race.Users.Select(p =>
{
var index = race.FinishedUsers.IndexOf(p);
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
}))}
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
var msg = raceMessage;
if (msg is null)
raceMessage = await SendConfirmAsync(text);
else
{
await msg.ModifyAsync(x => x.Embed = _eb.Create()
.WithTitle(GetText(strs.animal_race))
.WithDescription(text)
.WithOkColor()
.Build());
}
}
private Task Ar_OnStartingFailed(AnimalRace race)
{
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
race.Dispose();
return ReplyErrorLocalizedAsync(strs.animal_race_failed);
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task JoinRace([OverrideTypeReader(typeof(BalanceTypeReader))] long amount = default)
{
if (!await CheckBetOptional(amount))
return;
if (!_service.AnimalRaces.TryGetValue(ctx.Guild.Id, out var ar))
{
await ReplyErrorLocalizedAsync(strs.race_not_exist);
return;
}
try
{
var user = await ar.JoinRace(ctx.User.Id, ctx.User.ToString(), amount);
if (amount > 0)
{
await SendConfirmAsync(GetText(strs.animal_race_join_bet(ctx.User.Mention,
user.Animal.Icon,
amount + CurrencySign)));
}
else
await SendConfirmAsync(GetText(strs.animal_race_join(ctx.User.Mention, user.Animal.Icon)));
}
catch (ArgumentOutOfRangeException)
{
//ignore if user inputed an invalid amount
}
catch (AlreadyJoinedException)
{
// just ignore this
}
catch (AlreadyStartedException)
{
//ignore
}
catch (AnimalRaceFullException)
{
await SendConfirmAsync(GetText(strs.animal_race), GetText(strs.animal_race_full));
}
catch (NotEnoughFundsException)
{
await SendErrorAsync(GetText(strs.not_enough(CurrencySign)));
}
}
}
}

View File

@@ -1,26 +0,0 @@
#nullable disable
using NadekoBot.Modules.Games.Common;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
public class AnimalRacingUser
{
public long Bet { get; }
public string Username { get; }
public ulong UserId { get; }
public RaceAnimal Animal { get; set; }
public int Progress { get; set; }
public AnimalRacingUser(string username, ulong userId, long bet)
{
Bet = bet;
Username = username;
UserId = userId;
}
public override bool Equals(object obj)
=> obj is AnimalRacingUser x ? x.UserId == UserId : false;
public override int GetHashCode()
=> UserId.GetHashCode();
}

View File

@@ -1,19 +0,0 @@
#nullable disable
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
public class AlreadyJoinedException : Exception
{
public AlreadyJoinedException()
{
}
public AlreadyJoinedException(string message)
: base(message)
{
}
public AlreadyJoinedException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -1,19 +0,0 @@
#nullable disable
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
public class AlreadyStartedException : Exception
{
public AlreadyStartedException()
{
}
public AlreadyStartedException(string message)
: base(message)
{
}
public AlreadyStartedException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -1,19 +0,0 @@
#nullable disable
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
public class AnimalRaceFullException : Exception
{
public AnimalRaceFullException()
{
}
public AnimalRaceFullException(string message)
: base(message)
{
}
public AnimalRaceFullException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -1,19 +0,0 @@
#nullable disable
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
public class NotEnoughFundsException : Exception
{
public NotEnoughFundsException()
{
}
public NotEnoughFundsException(string message)
: base(message)
{
}
public NotEnoughFundsException(string message, Exception innerException)
: base(message, innerException)
{
}
}

View File

@@ -1,16 +0,0 @@
#nullable disable
using CommandLine;
namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
public class RaceOptions : INadekoCommandOptions
{
[Option('s', "start-time", Default = 20, Required = false)]
public int StartTime { get; set; } = 20;
public void NormalizeOptions()
{
if (StartTime is < 10 or > 120)
StartTime = 20;
}
}