added a .questlog and a quest system
This commit is contained in:
parent
97fe14cf5a
commit
4c2b42ab7f
46 changed files with 1391 additions and 392 deletions
|
@ -0,0 +1,6 @@
|
||||||
|
START TRANSACTION;
|
||||||
|
INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
|
||||||
|
VALUES ('20250328075848_quests', '9.0.1');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
|
@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
namespace EllieBot.Migrations.PostgreSql
|
namespace EllieBot.Migrations.PostgreSql
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PostgreSqlContext))]
|
[DbContext(typeof(PostgreSqlContext))]
|
||||||
[Migration("20250323022235_init")]
|
[Migration("20250328080459_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
6
src/EllieBot/Migrations/Sqlite/20250328075818_quests.sql
Normal file
6
src/EllieBot/Migrations/Sqlite/20250328075818_quests.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||||
|
VALUES ('20250328075818_quests', '9.0.1');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
|
@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
namespace EllieBot.Migrations.Sqlite
|
namespace EllieBot.Migrations.Sqlite
|
||||||
{
|
{
|
||||||
[DbContext(typeof(SqliteContext))]
|
[DbContext(typeof(SqliteContext))]
|
||||||
[Migration("20250323022218_init")]
|
[Migration("20250328080413_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
|
@ -1,6 +1,7 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
using EllieBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||||
using EllieBot.Modules.Games.Common;
|
using EllieBot.Modules.Games.Common;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Common.AnimalRacing;
|
namespace EllieBot.Modules.Gambling.Common.AnimalRacing;
|
||||||
|
|
||||||
|
@ -35,12 +36,18 @@ public sealed class AnimalRace : IDisposable
|
||||||
private readonly ICurrencyService _currency;
|
private readonly ICurrencyService _currency;
|
||||||
private readonly RaceOptions _options;
|
private readonly RaceOptions _options;
|
||||||
private readonly Queue<RaceAnimal> _animalsQueue;
|
private readonly Queue<RaceAnimal> _animalsQueue;
|
||||||
|
private readonly QuestService _quests;
|
||||||
|
|
||||||
public AnimalRace(RaceOptions options, ICurrencyService currency, IEnumerable<RaceAnimal> availableAnimals)
|
public AnimalRace(
|
||||||
|
RaceOptions options,
|
||||||
|
ICurrencyService currency,
|
||||||
|
IEnumerable<RaceAnimal> availableAnimals,
|
||||||
|
QuestService quests)
|
||||||
{
|
{
|
||||||
_currency = currency;
|
_currency = currency;
|
||||||
_options = options;
|
_options = options;
|
||||||
_animalsQueue = new(availableAnimals);
|
_animalsQueue = new(availableAnimals);
|
||||||
|
_quests = quests;
|
||||||
MaxUsers = _animalsQueue.Count;
|
MaxUsers = _animalsQueue.Count;
|
||||||
|
|
||||||
if (_animalsQueue.Count == 0)
|
if (_animalsQueue.Count == 0)
|
||||||
|
@ -60,7 +67,10 @@ public sealed class AnimalRace : IDisposable
|
||||||
|
|
||||||
await Start();
|
await Start();
|
||||||
}
|
}
|
||||||
finally { _locker.Release(); }
|
finally
|
||||||
|
{
|
||||||
|
_locker.Release();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
|
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
|
||||||
|
@ -93,7 +103,10 @@ public sealed class AnimalRace : IDisposable
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
finally { _locker.Release(); }
|
finally
|
||||||
|
{
|
||||||
|
_locker.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Start()
|
private async Task Start()
|
||||||
|
@ -104,7 +117,9 @@ public sealed class AnimalRace : IDisposable
|
||||||
foreach (var user in _users)
|
foreach (var user in _users)
|
||||||
{
|
{
|
||||||
if (user.Bet > 0)
|
if (user.Bet > 0)
|
||||||
await _currency.AddAsync(user.UserId, (long)(user.Bet + BASE_MULTIPLIER), new("animalrace", "refund"));
|
await _currency.AddAsync(user.UserId,
|
||||||
|
(long)(user.Bet * BASE_MULTIPLIER),
|
||||||
|
new("animalrace", "refund"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = OnStartingFailed?.Invoke(this);
|
_ = OnStartingFailed?.Invoke(this);
|
||||||
|
@ -112,6 +127,11 @@ public sealed class AnimalRace : IDisposable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var user in _users)
|
||||||
|
{
|
||||||
|
await _quests.ReportActionAsync(user.UserId, QuestEventType.RaceJoined);
|
||||||
|
}
|
||||||
|
|
||||||
_ = OnStarted?.Invoke(this);
|
_ = OnStarted?.Invoke(this);
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,6 +4,7 @@ using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Common.AnimalRacing;
|
using EllieBot.Modules.Gambling.Common.AnimalRacing;
|
||||||
using EllieBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
using EllieBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
using EllieBot.Modules.Games.Services;
|
using EllieBot.Modules.Games.Services;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
@ -17,6 +18,7 @@ public partial class Gambling
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly GamesConfigService _gamesConf;
|
private readonly GamesConfigService _gamesConf;
|
||||||
|
private readonly QuestService _quests;
|
||||||
|
|
||||||
private IUserMessage raceMessage;
|
private IUserMessage raceMessage;
|
||||||
|
|
||||||
|
@ -24,12 +26,14 @@ public partial class Gambling
|
||||||
ICurrencyService cs,
|
ICurrencyService cs,
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
GamblingConfigService gamblingConf,
|
GamblingConfigService gamblingConf,
|
||||||
GamesConfigService gamesConf)
|
GamesConfigService gamesConf,
|
||||||
|
QuestService quests)
|
||||||
: base(gamblingConf)
|
: base(gamblingConf)
|
||||||
{
|
{
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
_client = client;
|
_client = client;
|
||||||
_gamesConf = gamesConf;
|
_gamesConf = gamesConf;
|
||||||
|
_quests = quests;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
@ -39,11 +43,11 @@ public partial class Gambling
|
||||||
{
|
{
|
||||||
var (options, _) = OptionsParser.ParseFrom(new RaceOptions(), args);
|
var (options, _) = OptionsParser.ParseFrom(new RaceOptions(), args);
|
||||||
|
|
||||||
var ar = new AnimalRace(options, _cs, _gamesConf.Data.RaceAnimals.Shuffle());
|
var ar = new AnimalRace(options, _cs, _gamesConf.Data.RaceAnimals.Shuffle(), _quests);
|
||||||
if (!_service.AnimalRaces.TryAdd(ctx.Guild.Id, ar))
|
if (!_service.AnimalRaces.TryAdd(ctx.Guild.Id, ar))
|
||||||
return Response()
|
return Response()
|
||||||
.Error(GetText(strs.animal_race), GetText(strs.animal_race_already_started))
|
.Error(GetText(strs.animal_race), GetText(strs.animal_race_already_started))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
|
|
||||||
ar.Initialize();
|
ar.Initialize();
|
||||||
|
|
||||||
|
@ -61,7 +65,9 @@ public partial class Gambling
|
||||||
raceMessage = null;
|
raceMessage = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
@ -74,22 +80,22 @@ public partial class Gambling
|
||||||
if (race.FinishedUsers[0].Bet > 0)
|
if (race.FinishedUsers[0].Bet > 0)
|
||||||
{
|
{
|
||||||
return Response()
|
return Response()
|
||||||
.Embed(CreateEmbed()
|
.Embed(CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.animal_race))
|
.WithTitle(GetText(strs.animal_race))
|
||||||
.WithDescription(GetText(strs.animal_race_won_money(
|
.WithDescription(GetText(strs.animal_race_won_money(
|
||||||
Format.Bold(winner.Username),
|
Format.Bold(winner.Username),
|
||||||
winner.Animal.Icon,
|
winner.Animal.Icon,
|
||||||
N(race.FinishedUsers[0].Bet * race.Multi))))
|
N(race.FinishedUsers[0].Bet * race.Multi))))
|
||||||
.WithFooter($"x{race.Multi:F2}"))
|
.WithFooter($"x{race.Multi:F2}"))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
ar.Dispose();
|
ar.Dispose();
|
||||||
return Response()
|
return Response()
|
||||||
.Confirm(GetText(strs.animal_race),
|
.Confirm(GetText(strs.animal_race),
|
||||||
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)))
|
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
ar.OnStartingFailed += Ar_OnStartingFailed;
|
ar.OnStartingFailed += Ar_OnStartingFailed;
|
||||||
|
@ -99,10 +105,10 @@ public partial class Gambling
|
||||||
_client.MessageReceived += ClientMessageReceived;
|
_client.MessageReceived += ClientMessageReceived;
|
||||||
|
|
||||||
return Response()
|
return Response()
|
||||||
.Confirm(GetText(strs.animal_race),
|
.Confirm(GetText(strs.animal_race),
|
||||||
GetText(strs.animal_race_starting(options.StartTime)),
|
GetText(strs.animal_race_starting(options.StartTime)),
|
||||||
footer: GetText(strs.animal_race_join_instr(prefix)))
|
footer: GetText(strs.animal_race_join_instr(prefix)))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task Ar_OnStarted(AnimalRace race)
|
private Task Ar_OnStarted(AnimalRace race)
|
||||||
|
@ -110,9 +116,9 @@ public partial class Gambling
|
||||||
if (race.Users.Count == race.MaxUsers)
|
if (race.Users.Count == race.MaxUsers)
|
||||||
return Response().Confirm(GetText(strs.animal_race), GetText(strs.animal_race_full)).SendAsync();
|
return Response().Confirm(GetText(strs.animal_race), GetText(strs.animal_race_full)).SendAsync();
|
||||||
return Response()
|
return Response()
|
||||||
.Confirm(GetText(strs.animal_race),
|
.Confirm(GetText(strs.animal_race),
|
||||||
GetText(strs.animal_race_starting_with_x(race.Users.Count)))
|
GetText(strs.animal_race_starting_with_x(race.Users.Count)))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||||
|
@ -133,10 +139,10 @@ public partial class Gambling
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await msg.ModifyAsync(x => x.Embed = CreateEmbed()
|
await msg.ModifyAsync(x => x.Embed = CreateEmbed()
|
||||||
.WithTitle(GetText(strs.animal_race))
|
.WithTitle(GetText(strs.animal_race))
|
||||||
.WithDescription(text)
|
.WithDescription(text)
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.Build());
|
.Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,15 +172,15 @@ public partial class Gambling
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(GetText(strs.animal_race_join_bet(ctx.User.Mention,
|
.Confirm(GetText(strs.animal_race_join_bet(ctx.User.Mention,
|
||||||
user.Animal.Icon,
|
user.Animal.Icon,
|
||||||
amount + CurrencySign)))
|
amount + CurrencySign)))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.animal_race_join(ctx.User.Mention, user.Animal.Icon))
|
.Confirm(strs.animal_race_join(ctx.User.Mention, user.Animal.Icon))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException)
|
catch (ArgumentOutOfRangeException)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Bank;
|
namespace EllieBot.Modules.Gambling.Bank;
|
||||||
|
|
||||||
public sealed class BankService : IBankService, IEService
|
public sealed class BankService(
|
||||||
|
ICurrencyService _cur,
|
||||||
|
DbService _db,
|
||||||
|
QuestService quests) : IBankService, IEService
|
||||||
{
|
{
|
||||||
private readonly ICurrencyService _cur;
|
|
||||||
private readonly DbService _db;
|
|
||||||
|
|
||||||
public BankService(ICurrencyService cur, DbService db)
|
|
||||||
{
|
|
||||||
_cur = cur;
|
|
||||||
_db = db;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> AwardAsync(ulong userId, long amount)
|
public async Task<bool> AwardAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||||
|
@ -37,7 +32,7 @@ public sealed class BankService : IBankService, IEService
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> TakeAsync(ulong userId, long amount)
|
public async Task<bool> TakeAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||||
|
@ -50,7 +45,7 @@ public sealed class BankService : IBankService, IEService
|
||||||
{
|
{
|
||||||
Balance = old.Balance - amount
|
Balance = old.Balance - amount
|
||||||
});
|
});
|
||||||
|
|
||||||
return rows > 0;
|
return rows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,20 +58,28 @@ public sealed class BankService : IBankService, IEService
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
await ctx.Set<BankUser>()
|
await ctx.Set<BankUser>()
|
||||||
.ToLinqToDBTable()
|
.ToLinqToDBTable()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Balance = amount
|
Balance = amount
|
||||||
},
|
},
|
||||||
(old) => new()
|
(old) => new()
|
||||||
{
|
{
|
||||||
Balance = old.Balance + amount
|
Balance = old.Balance + amount
|
||||||
},
|
},
|
||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
UserId = userId
|
UserId = userId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await quests.ReportActionAsync(userId,
|
||||||
|
QuestEventType.BankAction,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
{ "type", "deposit" },
|
||||||
|
{ "amount", amount.ToString() }
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -87,12 +90,12 @@ public sealed class BankService : IBankService, IEService
|
||||||
|
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
var rows = await ctx.Set<BankUser>()
|
var rows = await ctx.Set<BankUser>()
|
||||||
.ToLinqToDBTable()
|
.ToLinqToDBTable()
|
||||||
.Where(x => x.UserId == userId && x.Balance >= amount)
|
.Where(x => x.UserId == userId && x.Balance >= amount)
|
||||||
.UpdateAsync((old) => new()
|
.UpdateAsync((old) => new()
|
||||||
{
|
{
|
||||||
Balance = old.Balance - amount
|
Balance = old.Balance - amount
|
||||||
});
|
});
|
||||||
|
|
||||||
if (rows > 0)
|
if (rows > 0)
|
||||||
{
|
{
|
||||||
|
@ -106,10 +109,11 @@ public sealed class BankService : IBankService, IEService
|
||||||
public async Task<long> GetBalanceAsync(ulong userId)
|
public async Task<long> GetBalanceAsync(ulong userId)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return (await ctx.Set<BankUser>()
|
var res = (await ctx.Set<BankUser>()
|
||||||
.ToLinqToDBTable()
|
.ToLinqToDBTable()
|
||||||
.FirstOrDefaultAsync(x => x.UserId == userId))
|
.FirstOrDefaultAsync(x => x.UserId == userId))
|
||||||
?.Balance
|
?.Balance
|
||||||
?? 0;
|
?? 0;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@ using System.Text;
|
||||||
using EllieBot.Modules.Gambling.Rps;
|
using EllieBot.Modules.Gambling.Rps;
|
||||||
using EllieBot.Common.TypeReaders;
|
using EllieBot.Common.TypeReaders;
|
||||||
using EllieBot.Modules.Games;
|
using EllieBot.Modules.Games;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
@ -36,6 +37,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
private readonly IBotCache _cache;
|
private readonly IBotCache _cache;
|
||||||
private readonly CaptchaService _captchaService;
|
private readonly CaptchaService _captchaService;
|
||||||
private readonly VoteRewardService _vrs;
|
private readonly VoteRewardService _vrs;
|
||||||
|
private readonly QuestService _quests;
|
||||||
|
|
||||||
public Gambling(
|
public Gambling(
|
||||||
IGamblingService gs,
|
IGamblingService gs,
|
||||||
|
@ -52,7 +54,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
RakebackService rb,
|
RakebackService rb,
|
||||||
IBotCache cache,
|
IBotCache cache,
|
||||||
CaptchaService captchaService,
|
CaptchaService captchaService,
|
||||||
VoteRewardService vrs)
|
VoteRewardService vrs,
|
||||||
|
QuestService quests)
|
||||||
: base(configService)
|
: base(configService)
|
||||||
{
|
{
|
||||||
_gs = gs;
|
_gs = gs;
|
||||||
|
@ -68,6 +71,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||||
_ps = patronage;
|
_ps = patronage;
|
||||||
_rng = new EllieRandom();
|
_rng = new EllieRandom();
|
||||||
_vrs = vrs;
|
_vrs = vrs;
|
||||||
|
_quests = quests;
|
||||||
|
|
||||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||||
_enUsCulture.NumberDecimalDigits = 0;
|
_enUsCulture.NumberDecimalDigits = 0;
|
||||||
|
|
|
@ -6,6 +6,7 @@ using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Gambling.Common;
|
using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Common.Connect4;
|
using EllieBot.Modules.Gambling.Common.Connect4;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Services;
|
namespace EllieBot.Modules.Gambling.Services;
|
||||||
|
@ -19,6 +20,7 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
private readonly IBotCache _cache;
|
private readonly IBotCache _cache;
|
||||||
private readonly GamblingConfigService _gcs;
|
private readonly GamblingConfigService _gcs;
|
||||||
private readonly IPatronageService _ps;
|
private readonly IPatronageService _ps;
|
||||||
|
private readonly QuestService _quests;
|
||||||
private readonly EllieRandom _rng;
|
private readonly EllieRandom _rng;
|
||||||
|
|
||||||
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
||||||
|
@ -28,13 +30,15 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
IBotCache cache,
|
IBotCache cache,
|
||||||
GamblingConfigService gcs,
|
GamblingConfigService gcs,
|
||||||
IPatronageService ps)
|
IPatronageService ps,
|
||||||
|
QuestService quests)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_client = client;
|
_client = client;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_gcs = gcs;
|
_gcs = gcs;
|
||||||
_ps = ps;
|
_ps = ps;
|
||||||
|
_quests = quests;
|
||||||
_rng = new EllieRandom();
|
_rng = new EllieRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,10 +234,15 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
if (booster)
|
if (booster)
|
||||||
originalAmount += gcsData.BoostBonus.BaseTimelyBonus;
|
originalAmount += gcsData.BoostBonus.BaseTimelyBonus;
|
||||||
|
|
||||||
|
var hasCompletedDailies = await _quests.UserCompletedDailies(userId);
|
||||||
|
|
||||||
|
if (hasCompletedDailies)
|
||||||
|
originalAmount = (long)(1.5 * originalAmount);
|
||||||
|
|
||||||
var patron = await _ps.GetPatronAsync(userId);
|
var patron = await _ps.GetPatronAsync(userId);
|
||||||
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
||||||
|
|
||||||
originalAmount += (int)(originalAmount * percentBonus);
|
originalAmount += (long)(originalAmount * percentBonus);
|
||||||
|
|
||||||
var msg = $"**{N(originalAmount)}** base reward\n\n";
|
var msg = $"**{N(originalAmount)}** base reward\n\n";
|
||||||
if (boostGuilds.Count > 0)
|
if (boostGuilds.Count > 0)
|
||||||
|
@ -252,6 +261,15 @@ public class GamblingService : IEService, IReadyExecutor
|
||||||
else
|
else
|
||||||
msg += $"\\❌ *+0 bonus for the [Patreon](https://patreon.com/elliebot) pledge*\n";
|
msg += $"\\❌ *+0 bonus for the [Patreon](https://patreon.com/elliebot) pledge*\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasCompletedDailies)
|
||||||
|
{
|
||||||
|
msg += $"\\✅ *+50% bonus for completing daily quests*\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msg += $"\\❌ *+0 bonus for completing daily quests*\n";
|
||||||
|
}
|
||||||
|
|
||||||
return (originalAmount, msg);
|
return (originalAmount, msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using LinqToDB.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
using SixLabors.Fonts;
|
using SixLabors.Fonts;
|
||||||
using SixLabors.Fonts.Unicode;
|
using SixLabors.Fonts.Unicode;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
|
@ -15,67 +16,47 @@ using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Services;
|
namespace EllieBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
public class PlantPickService(
|
||||||
|
DbService db,
|
||||||
|
IBotStrings strings,
|
||||||
|
IImageCache images,
|
||||||
|
FontProvider fonts,
|
||||||
|
ICurrencyService cs,
|
||||||
|
CommandHandler cmdHandler,
|
||||||
|
DiscordSocketClient client,
|
||||||
|
GamblingConfigService gss,
|
||||||
|
GamblingService gs,
|
||||||
|
QuestService quests) : IEService, IExecNoCommand, IReadyExecutor
|
||||||
{
|
{
|
||||||
//channelId/last generation
|
//channelId/last generation
|
||||||
public ConcurrentDictionary<ulong, long> LastGenerations { get; } = new();
|
public ConcurrentDictionary<ulong, long> LastGenerations { get; } = new();
|
||||||
private readonly DbService _db;
|
|
||||||
private readonly IBotStrings _strings;
|
|
||||||
private readonly IImageCache _images;
|
|
||||||
private readonly FontProvider _fonts;
|
|
||||||
private readonly ICurrencyService _cs;
|
|
||||||
private readonly CommandHandler _cmdHandler;
|
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
private readonly GamblingConfigService _gss;
|
|
||||||
private readonly GamblingService _gs;
|
|
||||||
|
|
||||||
private ConcurrentHashSet<ulong> _generationChannels = [];
|
private ConcurrentHashSet<ulong> _generationChannels = [];
|
||||||
|
|
||||||
public PlantPickService(
|
|
||||||
DbService db,
|
|
||||||
IBotStrings strings,
|
|
||||||
IImageCache images,
|
|
||||||
FontProvider fonts,
|
|
||||||
ICurrencyService cs,
|
|
||||||
CommandHandler cmdHandler,
|
|
||||||
DiscordSocketClient client,
|
|
||||||
GamblingConfigService gss,
|
|
||||||
GamblingService gs)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
_strings = strings;
|
|
||||||
_images = images;
|
|
||||||
_fonts = fonts;
|
|
||||||
_cs = cs;
|
|
||||||
_cmdHandler = cmdHandler;
|
|
||||||
_client = client;
|
|
||||||
_gss = gss;
|
|
||||||
_gs = gs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
public Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||||
=> PotentialFlowerGeneration(msg);
|
=> PotentialFlowerGeneration(msg);
|
||||||
|
|
||||||
private string GetText(ulong gid, LocStr str)
|
private string GetText(ulong gid, LocStr str)
|
||||||
=> _strings.GetText(str, gid);
|
=> strings.GetText(str, gid);
|
||||||
|
|
||||||
public async Task<bool> ToggleCurrencyGeneration(ulong gid, ulong cid)
|
public async Task<bool> ToggleCurrencyGeneration(ulong gid, ulong cid)
|
||||||
{
|
{
|
||||||
bool enabled;
|
bool enabled;
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = db.GetDbContext();
|
||||||
|
|
||||||
if (_generationChannels.Add(cid))
|
if (_generationChannels.Add(cid))
|
||||||
{
|
{
|
||||||
await uow.GetTable<GCChannelId>()
|
await uow.GetTable<GCChannelId>()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
ChannelId = cid,
|
ChannelId = cid,
|
||||||
GuildId = gid
|
GuildId = gid
|
||||||
}, (x) => new()
|
},
|
||||||
|
(x) => new()
|
||||||
{
|
{
|
||||||
ChannelId = cid,
|
ChannelId = cid,
|
||||||
GuildId = gid
|
GuildId = gid
|
||||||
}, () => new()
|
},
|
||||||
|
() => new()
|
||||||
{
|
{
|
||||||
ChannelId = cid,
|
ChannelId = cid,
|
||||||
GuildId = gid
|
GuildId = gid
|
||||||
|
@ -87,8 +68,8 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await uow.GetTable<GCChannelId>()
|
await uow.GetTable<GCChannelId>()
|
||||||
.Where(x => x.ChannelId == cid && x.GuildId == gid)
|
.Where(x => x.ChannelId == cid && x.GuildId == gid)
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
|
|
||||||
_generationChannels.TryRemove(cid);
|
_generationChannels.TryRemove(cid);
|
||||||
enabled = false;
|
enabled = false;
|
||||||
|
@ -99,9 +80,9 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<GCChannelId>> GetAllGeneratingChannels()
|
public async Task<IReadOnlyCollection<GCChannelId>> GetAllGeneratingChannels()
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = db.GetDbContext();
|
||||||
return await uow.GetTable<GCChannelId>()
|
return await uow.GetTable<GCChannelId>()
|
||||||
.ToListAsyncLinqToDB();
|
.ToListAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -111,7 +92,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
/// <returns>Stream of the currency image</returns>
|
/// <returns>Stream of the currency image</returns>
|
||||||
public async Task<(Stream, string)> GetRandomCurrencyImageAsync(string pass)
|
public async Task<(Stream, string)> GetRandomCurrencyImageAsync(string pass)
|
||||||
{
|
{
|
||||||
var curImg = await _images.GetCurrencyImageAsync();
|
var curImg = await images.GetCurrencyImageAsync();
|
||||||
|
|
||||||
if (curImg is null)
|
if (curImg is null)
|
||||||
return (new MemoryStream(), null);
|
return (new MemoryStream(), null);
|
||||||
|
@ -142,7 +123,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
||||||
using var img = Image.Load<Rgba32>(curImg);
|
using var img = Image.Load<Rgba32>(curImg);
|
||||||
// choose font size based on the image height, so that it's visible
|
// choose font size based on the image height, so that it's visible
|
||||||
var font = _fonts.NotoSans.CreateFont(img.Height / 11.0f, FontStyle.Bold);
|
var font = fonts.NotoSans.CreateFont(img.Height / 11.0f, FontStyle.Bold);
|
||||||
img.Mutate(x =>
|
img.Mutate(x =>
|
||||||
{
|
{
|
||||||
// measure the size of the text to be drawing
|
// measure the size of the text to be drawing
|
||||||
|
@ -170,13 +151,13 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
|
|
||||||
// draw the password over the background
|
// draw the password over the background
|
||||||
x.DrawText(new RichTextOptions(font)
|
x.DrawText(new RichTextOptions(font)
|
||||||
{
|
{
|
||||||
Origin = new(0, 0),
|
Origin = new(0, 0),
|
||||||
TextRuns =
|
TextRuns =
|
||||||
[
|
[
|
||||||
strikeoutRun
|
strikeoutRun
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
pass,
|
pass,
|
||||||
new SolidBrush(Color.White));
|
new SolidBrush(Color.White));
|
||||||
});
|
});
|
||||||
|
@ -200,7 +181,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var config = _gss.Data;
|
var config = gss.Data;
|
||||||
var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue.ToBinary());
|
var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue.ToBinary());
|
||||||
var rng = new EllieRandom();
|
var rng = new EllieRandom();
|
||||||
|
|
||||||
|
@ -219,7 +200,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
|
|
||||||
if (dropAmount > 0)
|
if (dropAmount > 0)
|
||||||
{
|
{
|
||||||
var prefix = _cmdHandler.GetPrefix(channel.Guild.Id);
|
var prefix = cmdHandler.GetPrefix(channel.Guild.Id);
|
||||||
var toSend = dropAmount == 1
|
var toSend = dropAmount == 1
|
||||||
? GetText(channel.GuildId, strs.curgen_sn(config.Currency.Sign))
|
? GetText(channel.GuildId, strs.curgen_sn(config.Currency.Sign))
|
||||||
+ " "
|
+ " "
|
||||||
|
@ -228,7 +209,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
+ " "
|
+ " "
|
||||||
+ GetText(channel.GuildId, strs.pick_pl(prefix));
|
+ GetText(channel.GuildId, strs.pick_pl(prefix));
|
||||||
|
|
||||||
var pw = config.Generation.HasPassword ? _gs.GeneratePassword().ToUpperInvariant() : null;
|
var pw = config.Generation.HasPassword ? gs.GeneratePassword().ToUpperInvariant() : null;
|
||||||
|
|
||||||
IUserMessage sent;
|
IUserMessage sent;
|
||||||
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
||||||
|
@ -238,7 +219,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
|
|
||||||
var res = await AddPlantToDatabase(channel.GuildId,
|
var res = await AddPlantToDatabase(channel.GuildId,
|
||||||
channel.Id,
|
channel.Id,
|
||||||
_client.CurrentUser.Id,
|
client.CurrentUser.Id,
|
||||||
sent.Id,
|
sent.Id,
|
||||||
dropAmount,
|
dropAmount,
|
||||||
pw,
|
pw,
|
||||||
|
@ -261,12 +242,12 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
public async Task<long> PickAsync(
|
public async Task<long> PickAsync(
|
||||||
ulong gid,
|
ulong gid,
|
||||||
ITextChannel ch,
|
ITextChannel ch,
|
||||||
ulong uid,
|
ulong userId,
|
||||||
string pass)
|
string pass)
|
||||||
{
|
{
|
||||||
long amount;
|
long amount;
|
||||||
ulong[] ids;
|
ulong[] ids;
|
||||||
await using (var uow = _db.GetDbContext())
|
await using (var uow = db.GetDbContext())
|
||||||
{
|
{
|
||||||
// this method will sum all plants with that password,
|
// this method will sum all plants with that password,
|
||||||
// remove them, and get messageids of the removed plants
|
// remove them, and get messageids of the removed plants
|
||||||
|
@ -274,8 +255,8 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
pass = pass?.Trim().TrimTo(10, true)?.ToUpperInvariant();
|
pass = pass?.Trim().TrimTo(10, true)?.ToUpperInvariant();
|
||||||
// gets all plants in this channel with the same password
|
// gets all plants in this channel with the same password
|
||||||
var entries = await uow.GetTable<PlantedCurrency>()
|
var entries = await uow.GetTable<PlantedCurrency>()
|
||||||
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
||||||
.DeleteWithOutputAsync();
|
.DeleteWithOutputAsync();
|
||||||
|
|
||||||
if (!entries.Any())
|
if (!entries.Any())
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -285,14 +266,24 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
await _cs.AddAsync(uid, amount, new("currency", "collect"));
|
{
|
||||||
|
await cs.AddAsync(userId, amount, new("currency", "collect"));
|
||||||
|
await quests.ReportActionAsync(userId,
|
||||||
|
QuestEventType.PlantOrPick,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
{ "type", "pick" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = ch.DeleteMessagesAsync(ids);
|
_ = ch.DeleteMessagesAsync(ids);
|
||||||
}
|
}
|
||||||
catch { }
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
// return the amount of currency the user picked
|
// return the amount of currency the user picked
|
||||||
return amount;
|
return amount;
|
||||||
|
@ -308,8 +299,8 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// get the text
|
// get the text
|
||||||
var prefix = _cmdHandler.GetPrefix(gid);
|
var prefix = cmdHandler.GetPrefix(gid);
|
||||||
var msgToSend = GetText(gid, strs.planted(Format.Bold(user), amount + _gss.Data.Currency.Sign));
|
var msgToSend = GetText(gid, strs.planted(Format.Bold(user), amount + gss.Data.Currency.Sign));
|
||||||
|
|
||||||
if (amount > 1)
|
if (amount > 1)
|
||||||
msgToSend += " " + GetText(gid, strs.pick_pl(prefix));
|
msgToSend += " " + GetText(gid, strs.pick_pl(prefix));
|
||||||
|
@ -337,7 +328,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
public async Task<bool> PlantAsync(
|
public async Task<bool> PlantAsync(
|
||||||
ulong gid,
|
ulong gid,
|
||||||
ITextChannel ch,
|
ITextChannel ch,
|
||||||
ulong uid,
|
ulong userId,
|
||||||
string user,
|
string user,
|
||||||
long amount,
|
long amount,
|
||||||
string pass)
|
string pass)
|
||||||
|
@ -349,19 +340,20 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// remove currency from the user who's planting
|
// remove currency from the user who's planting
|
||||||
if (await _cs.RemoveAsync(uid, amount, new("put/collect", "put")))
|
if (await cs.RemoveAsync(userId, amount, new("put/collect", "put")))
|
||||||
{
|
{
|
||||||
// try to send the message with the currency image
|
// try to send the message with the currency image
|
||||||
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass);
|
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass);
|
||||||
if (msgId is null)
|
if (msgId is null)
|
||||||
{
|
{
|
||||||
// if it fails it will return null, if it returns null, refund
|
// if it fails it will return null, if it returns null, refund
|
||||||
await _cs.AddAsync(uid, amount, new("put/collect", "refund"));
|
await cs.AddAsync(userId, amount, new("put/collect", "refund"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it doesn't fail, put the plant in the database for other people to pick
|
// if it doesn't fail, put the plant in the database for other people to pick
|
||||||
await AddPlantToDatabase(gid, ch.Id, uid, msgId.Value, amount, pass);
|
await AddPlantToDatabase(gid, ch.Id, userId, msgId.Value, amount, pass);
|
||||||
|
await quests.ReportActionAsync(userId, QuestEventType.PlantOrPick, new() { { "type", "plant" } });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -379,43 +371,42 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
||||||
string pass,
|
string pass,
|
||||||
bool auto = false)
|
bool auto = false)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = db.GetDbContext();
|
||||||
|
|
||||||
PlantedCurrency[] deleted = [];
|
PlantedCurrency[] deleted = [];
|
||||||
if (!string.IsNullOrWhiteSpace(pass) && auto)
|
if (!string.IsNullOrWhiteSpace(pass) && auto)
|
||||||
{
|
{
|
||||||
deleted = await uow.GetTable<PlantedCurrency>()
|
deleted = await uow.GetTable<PlantedCurrency>()
|
||||||
.Where(x => x.GuildId == gid
|
.Where(x => x.GuildId == gid
|
||||||
&& x.ChannelId == cid
|
&& x.ChannelId == cid
|
||||||
&& x.Password != null
|
&& x.Password != null
|
||||||
&& x.Password.Length == pass.Length)
|
&& x.Password.Length == pass.Length)
|
||||||
.DeleteWithOutputAsync();
|
.DeleteWithOutputAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalDeletedAmount = deleted.Length == 0 ? 0 : deleted.Sum(x => x.Amount);
|
var totalDeletedAmount = deleted.Length == 0 ? 0 : deleted.Sum(x => x.Amount);
|
||||||
|
|
||||||
await uow.GetTable<PlantedCurrency>()
|
await uow.GetTable<PlantedCurrency>()
|
||||||
.InsertAsync(() => new()
|
.InsertAsync(() => new()
|
||||||
{
|
{
|
||||||
Amount = totalDeletedAmount + amount,
|
Amount = totalDeletedAmount + amount,
|
||||||
GuildId = gid,
|
GuildId = gid,
|
||||||
ChannelId = cid,
|
ChannelId = cid,
|
||||||
Password = pass,
|
Password = pass,
|
||||||
UserId = uid,
|
UserId = uid,
|
||||||
MessageId = mid,
|
MessageId = mid,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (totalDeletedAmount + amount, deleted.Select(x => x.MessageId).ToArray());
|
return (totalDeletedAmount + amount, deleted.Select(x => x.MessageId).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = db.GetDbContext();
|
||||||
_generationChannels = (await uow.GetTable<GCChannelId>()
|
_generationChannels = (await uow.GetTable<GCChannelId>()
|
||||||
.Select(x => x.ChannelId)
|
.Select(x => x.ChannelId)
|
||||||
.ToListAsyncLinqToDB())
|
.ToListAsyncLinqToDB())
|
||||||
.ToHashSet()
|
.ToHashSet()
|
||||||
.ToConcurrentSet();
|
.ToConcurrentSet();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,6 +6,7 @@ using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Gambling.Common;
|
using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Common.Waifu;
|
using EllieBot.Modules.Gambling.Common.Waifu;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Services;
|
namespace EllieBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
|
@ -15,23 +16,23 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly IBotCache _cache;
|
private readonly IBotCache _cache;
|
||||||
private readonly GamblingConfigService _gss;
|
private readonly GamblingConfigService _gss;
|
||||||
private readonly IBotCreds _creds;
|
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly QuestService _quests;
|
||||||
|
|
||||||
public WaifuService(
|
public WaifuService(
|
||||||
DbService db,
|
DbService db,
|
||||||
ICurrencyService cs,
|
ICurrencyService cs,
|
||||||
IBotCache cache,
|
IBotCache cache,
|
||||||
GamblingConfigService gss,
|
GamblingConfigService gss,
|
||||||
IBotCreds creds,
|
DiscordSocketClient client,
|
||||||
DiscordSocketClient client)
|
QuestService quests)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_gss = gss;
|
_gss = gss;
|
||||||
_creds = creds;
|
|
||||||
_client = client;
|
_client = client;
|
||||||
|
_quests = quests;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> WaifuTransfer(IUser owner, ulong waifuId, IUser newOwner)
|
public async Task<bool> WaifuTransfer(IUser owner, ulong waifuId, IUser newOwner)
|
||||||
|
@ -411,6 +412,8 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
w.Price += (long)(totalValue * _gss.Data.Waifu.Multipliers.GiftEffect);
|
w.Price += (long)(totalValue * _gss.Data.Waifu.Multipliers.GiftEffect);
|
||||||
else
|
else
|
||||||
w.Price += totalValue / 2;
|
w.Price += totalValue / 2;
|
||||||
|
|
||||||
|
await _quests.ReportActionAsync(from.Id, QuestEventType.WaifuGiftSent);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,6 +4,7 @@ using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Modules.Administration;
|
using EllieBot.Modules.Administration;
|
||||||
using EllieBot.Modules.Administration.Services;
|
using EllieBot.Modules.Administration.Services;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games.Fish;
|
namespace EllieBot.Modules.Games.Fish;
|
||||||
|
|
||||||
|
@ -11,7 +12,8 @@ public sealed class FishService(
|
||||||
FishConfigService fcs,
|
FishConfigService fcs,
|
||||||
IBotCache cache,
|
IBotCache cache,
|
||||||
DbService db,
|
DbService db,
|
||||||
INotifySubscriber notify
|
INotifySubscriber notify,
|
||||||
|
QuestService quests
|
||||||
)
|
)
|
||||||
: IEService
|
: IEService
|
||||||
{
|
{
|
||||||
|
@ -91,6 +93,15 @@ public sealed class FishService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await quests.ReportActionAsync(userId,
|
||||||
|
QuestEventType.FishCaught,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
{ "fish", result.Fish.Name },
|
||||||
|
{ "type", typeRoll < nothingChance + fishChance ? "fish" : "trash" },
|
||||||
|
{ "stars", result.Stars.ToString() }
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Modules.Games.Services;
|
using EllieBot.Modules.Games.Services;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games.Hangman;
|
namespace EllieBot.Modules.Games.Hangman;
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ public sealed class HangmanService : IHangmanService, IExecNoCommand
|
||||||
private readonly GamesConfigService _gcs;
|
private readonly GamesConfigService _gcs;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly IMemoryCache _cdCache;
|
private readonly IMemoryCache _cdCache;
|
||||||
|
private readonly QuestService _quests;
|
||||||
private readonly object _locker = new();
|
private readonly object _locker = new();
|
||||||
|
|
||||||
public HangmanService(
|
public HangmanService(
|
||||||
|
@ -20,13 +22,15 @@ public sealed class HangmanService : IHangmanService, IExecNoCommand
|
||||||
IMessageSenderService sender,
|
IMessageSenderService sender,
|
||||||
GamesConfigService gcs,
|
GamesConfigService gcs,
|
||||||
ICurrencyService cs,
|
ICurrencyService cs,
|
||||||
IMemoryCache cdCache)
|
IMemoryCache cdCache,
|
||||||
|
QuestService quests)
|
||||||
{
|
{
|
||||||
_source = source;
|
_source = source;
|
||||||
_sender = sender;
|
_sender = sender;
|
||||||
_gcs = gcs;
|
_gcs = gcs;
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
_cdCache = cdCache;
|
_cdCache = cdCache;
|
||||||
|
_quests = quests;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -104,6 +108,9 @@ public sealed class HangmanService : IHangmanService, IExecNoCommand
|
||||||
|
|
||||||
if (rew > 0)
|
if (rew > 0)
|
||||||
await _cs.AddAsync(msg.Author, rew, new("hangman", "win"));
|
await _cs.AddAsync(msg.Author, rew, new("hangman", "win"));
|
||||||
|
|
||||||
|
if (state.GuessResult == HangmanGame.GuessResult.Win)
|
||||||
|
await _quests.ReportActionAsync(msg.Author.Id, QuestEventType.GameWon, new() { { "game", "hangman" } });
|
||||||
|
|
||||||
await SendState((ITextChannel)msg.Channel, msg.Author, msg.Content, state);
|
await SendState((ITextChannel)msg.Channel, msg.Author, msg.Content, state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using LinqToDB.Data;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
using SixLabors.ImageSharp.ColorSpaces;
|
using SixLabors.ImageSharp.ColorSpaces;
|
||||||
using SixLabors.ImageSharp.ColorSpaces.Conversion;
|
using SixLabors.ImageSharp.ColorSpaces.Conversion;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
@ -17,6 +18,7 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
private readonly IBotCache _cache;
|
private readonly IBotCache _cache;
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
|
private readonly QuestService _quests;
|
||||||
|
|
||||||
public const int CANVAS_WIDTH = 500;
|
public const int CANVAS_WIDTH = 500;
|
||||||
public const int CANVAS_HEIGHT = 350;
|
public const int CANVAS_HEIGHT = 350;
|
||||||
|
@ -26,12 +28,14 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
DbService db,
|
DbService db,
|
||||||
IBotCache cache,
|
IBotCache cache,
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
ICurrencyService cs)
|
ICurrencyService cs,
|
||||||
|
QuestService quests)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_client = client;
|
_client = client;
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
|
_quests = quests;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
|
@ -59,23 +63,23 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
}
|
}
|
||||||
|
|
||||||
await uow.GetTable<NCPixel>()
|
await uow.GetTable<NCPixel>()
|
||||||
.BulkCopyAsync(toAdd.Select(x =>
|
.BulkCopyAsync(toAdd.Select(x =>
|
||||||
{
|
{
|
||||||
var clr = ColorSpaceConverter.ToRgb(new Hsv(((float)Random.Shared.NextDouble() * 360),
|
var clr = ColorSpaceConverter.ToRgb(new Hsv(((float)Random.Shared.NextDouble() * 360),
|
||||||
(float)(0.5 + (Random.Shared.NextDouble() * 0.49)),
|
(float)(0.5 + (Random.Shared.NextDouble() * 0.49)),
|
||||||
(float)(0.4 + (Random.Shared.NextDouble() / 5 + (x % 100 * 0.2)))))
|
(float)(0.4 + (Random.Shared.NextDouble() / 5 + (x % 100 * 0.2)))))
|
||||||
.ToVector3();
|
.ToVector3();
|
||||||
|
|
||||||
var packed = new Rgba32(clr).PackedValue;
|
var packed = new Rgba32(clr).PackedValue;
|
||||||
return new NCPixel()
|
return new NCPixel()
|
||||||
{
|
{
|
||||||
Color = packed,
|
Color = packed,
|
||||||
Price = 1,
|
Price = 1,
|
||||||
Position = x,
|
Position = x,
|
||||||
Text = "",
|
Text = "",
|
||||||
OwnerId = 0
|
OwnerId = 0
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,9 +87,9 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var colors = await uow.GetTable<NCPixel>()
|
var colors = await uow.GetTable<NCPixel>()
|
||||||
.OrderBy(x => x.Position)
|
.OrderBy(x => x.Position)
|
||||||
.Select(x => x.Color)
|
.Select(x => x.Color)
|
||||||
.ToArrayAsyncLinqToDB();
|
.ToArrayAsyncLinqToDB();
|
||||||
|
|
||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
|
@ -121,15 +125,15 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var updates = await uow.GetTable<NCPixel>()
|
var updates = await uow.GetTable<NCPixel>()
|
||||||
.Where(x => x.Position == position && x.Price <= price)
|
.Where(x => x.Position == position && x.Price <= price)
|
||||||
.UpdateAsync(old => new NCPixel()
|
.UpdateAsync(old => new NCPixel()
|
||||||
{
|
{
|
||||||
Position = position,
|
Position = position,
|
||||||
Color = color,
|
Color = color,
|
||||||
Text = text,
|
Text = text,
|
||||||
OwnerId = userId,
|
OwnerId = userId,
|
||||||
Price = price + 1
|
Price = price + 1
|
||||||
});
|
});
|
||||||
success = updates > 0;
|
success = updates > 0;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
@ -140,6 +144,10 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
{
|
{
|
||||||
await wallet.Add(price, new("canvas", "pixel-refund", $"Refund pixel {new kwum(position)} purchase"));
|
await wallet.Add(price, new("canvas", "pixel-refund", $"Refund pixel {new kwum(position)} purchase"));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _quests.ReportActionAsync(userId, QuestEventType.PixelSet);
|
||||||
|
}
|
||||||
|
|
||||||
return success ? SetPixelResult.Success : SetPixelResult.InsufficientPayment;
|
return success ? SetPixelResult.Success : SetPixelResult.InsufficientPayment;
|
||||||
}
|
}
|
||||||
|
@ -152,14 +160,14 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.GetTable<NCPixel>().DeleteAsync();
|
await uow.GetTable<NCPixel>().DeleteAsync();
|
||||||
await uow.GetTable<NCPixel>()
|
await uow.GetTable<NCPixel>()
|
||||||
.BulkCopyAsync(colors.Select((x, i) => new NCPixel()
|
.BulkCopyAsync(colors.Select((x, i) => new NCPixel()
|
||||||
{
|
{
|
||||||
Color = x,
|
Color = x,
|
||||||
Price = INITIAL_PRICE,
|
Price = INITIAL_PRICE,
|
||||||
Position = i,
|
Position = i,
|
||||||
Text = "",
|
Text = "",
|
||||||
OwnerId = 0
|
OwnerId = 0
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -190,12 +198,12 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
||||||
|
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
return await uow.GetTable<NCPixel>()
|
return await uow.GetTable<NCPixel>()
|
||||||
.Where(x => x.Position % CANVAS_WIDTH >= (position % CANVAS_WIDTH) - 2
|
.Where(x => x.Position % CANVAS_WIDTH >= (position % CANVAS_WIDTH) - 2
|
||||||
&& x.Position % CANVAS_WIDTH <= (position % CANVAS_WIDTH) + 2
|
&& x.Position % CANVAS_WIDTH <= (position % CANVAS_WIDTH) + 2
|
||||||
&& x.Position / CANVAS_WIDTH >= (position / CANVAS_WIDTH) - 2
|
&& x.Position / CANVAS_WIDTH >= (position / CANVAS_WIDTH) - 2
|
||||||
&& x.Position / CANVAS_WIDTH <= (position / CANVAS_WIDTH) + 2)
|
&& x.Position / CANVAS_WIDTH <= (position / CANVAS_WIDTH) + 2)
|
||||||
.OrderBy(x => x.Position)
|
.OrderBy(x => x.Position)
|
||||||
.ToArrayAsyncLinqToDB();
|
.ToArrayAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetHeight()
|
public int GetHeight()
|
||||||
|
|
9
src/EllieBot/Modules/Games/Quests/Quest.cs
Normal file
9
src/EllieBot/Modules/Games/Quests/Quest.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public record class Quest(
|
||||||
|
QuestIds Id,
|
||||||
|
string Name,
|
||||||
|
string Description,
|
||||||
|
QuestEventType TriggerEvent,
|
||||||
|
int RequiredAmount
|
||||||
|
);
|
38
src/EllieBot/Modules/Games/Quests/QuestCommands.cs
Normal file
38
src/EllieBot/Modules/Games/Quests/QuestCommands.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public class QuestCommands : EllieModule<QuestService>
|
||||||
|
{
|
||||||
|
[Cmd]
|
||||||
|
public async Task QuestLog()
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var quests = await _service.GetUserQuestsAsync(ctx.User.Id, now);
|
||||||
|
|
||||||
|
var embed = CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithTitle(GetText(strs.quest_log));
|
||||||
|
|
||||||
|
var allDone = quests.All(x => x.UserQuest.IsCompleted);
|
||||||
|
|
||||||
|
var tmrw = now.AddDays(1).Date;
|
||||||
|
var desc = GetText(strs.dailies_reset(TimestampTag.FromDateTime(tmrw, TimestampTagStyles.Relative)));
|
||||||
|
if (allDone)
|
||||||
|
desc = GetText(strs.dailies_done) + "\n" + desc;
|
||||||
|
|
||||||
|
embed.WithDescription(desc);
|
||||||
|
|
||||||
|
foreach (var res in quests)
|
||||||
|
{
|
||||||
|
if (res.Quest is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
embed.AddField(
|
||||||
|
(res.UserQuest.IsCompleted ? IQuest.COMPLETED : IQuest.INCOMPLETE) + " " + res.Quest.Name,
|
||||||
|
$"{res.Quest.Desc}\n\n" +
|
||||||
|
res.Quest.ToString(res.UserQuest.Progress),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Response().Embed(embed).SendAsync();
|
||||||
|
}
|
||||||
|
}
|
15
src/EllieBot/Modules/Games/Quests/QuestEvent.cs
Normal file
15
src/EllieBot/Modules/Games/Quests/QuestEvent.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public class QuestEvent
|
||||||
|
{
|
||||||
|
public QuestEventType EventType { get; }
|
||||||
|
public ulong UserId { get; }
|
||||||
|
public Dictionary<string, string> Metadata { get; }
|
||||||
|
|
||||||
|
public QuestEvent(QuestEventType eventType, ulong userId, Dictionary<string, string>? metadata = null)
|
||||||
|
{
|
||||||
|
EventType = eventType;
|
||||||
|
UserId = userId;
|
||||||
|
Metadata = metadata ?? new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
}
|
15
src/EllieBot/Modules/Games/Quests/QuestEventType.cs
Normal file
15
src/EllieBot/Modules/Games/Quests/QuestEventType.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public enum QuestEventType
|
||||||
|
{
|
||||||
|
CommandUsed,
|
||||||
|
GameWon,
|
||||||
|
BetPlaced,
|
||||||
|
FishCaught,
|
||||||
|
PixelSet,
|
||||||
|
RaceJoined,
|
||||||
|
BankAction,
|
||||||
|
PlantOrPick,
|
||||||
|
Give,
|
||||||
|
WaifuGiftSent
|
||||||
|
}
|
16
src/EllieBot/Modules/Games/Quests/QuestIds.cs
Normal file
16
src/EllieBot/Modules/Games/Quests/QuestIds.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public enum QuestIds
|
||||||
|
{
|
||||||
|
HangmanWin,
|
||||||
|
Bet,
|
||||||
|
WaifuGift,
|
||||||
|
CatchFish,
|
||||||
|
SetPixels,
|
||||||
|
JoinAnimalRace,
|
||||||
|
BankDeposit,
|
||||||
|
CheckBetting,
|
||||||
|
PlantPick,
|
||||||
|
GiveFlowers,
|
||||||
|
WellInformed
|
||||||
|
}
|
65
src/EllieBot/Modules/Games/Quests/QuestModels/BankerQuest.cs
Normal file
65
src/EllieBot/Modules/Games/Quests/QuestModels/BankerQuest.cs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class BankerQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.BankDeposit;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Banker";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Perform bank actions";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.BankAction;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 0b111;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (!metadata.TryGetValue("type", out var type))
|
||||||
|
return oldProgress;
|
||||||
|
|
||||||
|
var progress = oldProgress;
|
||||||
|
|
||||||
|
if (type == "balance")
|
||||||
|
progress |= 0b001;
|
||||||
|
else if (type == "deposit")
|
||||||
|
progress |= 0b010;
|
||||||
|
else if (type == "withdraw")
|
||||||
|
progress |= 0b100;
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString(long progress)
|
||||||
|
{
|
||||||
|
var msg = "";
|
||||||
|
|
||||||
|
var emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b001) == 0b001)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += emoji + " checked bank balance";
|
||||||
|
|
||||||
|
emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b010) == 0b010)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += "\n" + emoji + " made a deposit";
|
||||||
|
|
||||||
|
emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b100) == 0b100)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += "\n" + emoji + " made a withdrawal";
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class BetFlowersQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.Bet;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Flower Gambler";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Bet 300 flowers";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "flowers bet";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.BetPlaced;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 300;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (!metadata.TryGetValue("amount", out var amountStr)
|
||||||
|
|| !long.TryParse(amountStr, out var amount))
|
||||||
|
return oldProgress;
|
||||||
|
|
||||||
|
return oldProgress + amount;
|
||||||
|
}
|
||||||
|
}
|
27
src/EllieBot/Modules/Games/Quests/QuestModels/BetQuest.cs
Normal file
27
src/EllieBot/Modules/Games/Quests/QuestModels/BetQuest.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class BetQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.Bet;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "High Roller";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Place 10 bets";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "bets placed";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.BetPlaced;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 10;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
return oldProgress + 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class CatchFishQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.CatchFish;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Fisherman";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Catch 5 fish";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "fish caught";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.FishCaught;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 5;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (metadata.TryGetValue("type", out var type) && type == "fish")
|
||||||
|
return oldProgress + 1;
|
||||||
|
|
||||||
|
return oldProgress;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class CatchQualityQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.CatchFish;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Master Angler";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Catch a fish or an item rated 3 stars or above.";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "3+ star fish caught";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.FishCaught;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 1;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (metadata.TryGetValue("stars", out var quality)
|
||||||
|
&& int.TryParse(quality, out var q)
|
||||||
|
&& q >= 3)
|
||||||
|
return oldProgress + 1;
|
||||||
|
|
||||||
|
return oldProgress;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class CatchTrashQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.CatchFish;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Environmentalist";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Catch 5 trash items while fishing";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "items caught";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.FishCaught;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 5;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (metadata.TryGetValue("type", out var type) && type == "trash")
|
||||||
|
return oldProgress + 1;
|
||||||
|
|
||||||
|
return oldProgress;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class CheckLeaderboardsQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.CheckBetting;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Leaderboard Enthusiast";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Check lb, xplb and waifulb";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.CommandUsed;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 0b111;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (!metadata.TryGetValue("name", out var name))
|
||||||
|
return oldProgress;
|
||||||
|
|
||||||
|
var progress = oldProgress;
|
||||||
|
|
||||||
|
if (name == "leaderboard")
|
||||||
|
progress |= 0b001;
|
||||||
|
else if (name == "xpleaderboard")
|
||||||
|
progress |= 0b010;
|
||||||
|
else if (name == "waifulb")
|
||||||
|
progress |= 0b100;
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString(long progress)
|
||||||
|
{
|
||||||
|
var msg = "";
|
||||||
|
|
||||||
|
var emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b001) == 0b001)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += emoji + " flower lb seen\n";
|
||||||
|
|
||||||
|
emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b010) == 0b010)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += emoji + " xp lb seen\n";
|
||||||
|
|
||||||
|
emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b100) == 0b100)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += emoji + " waifu lb seen";
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class GiftWaifuQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.WaifuGift;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Generous Gifter";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Gift a waifu";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "waifus gifted";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.WaifuGiftSent;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 1;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
return oldProgress + 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class GiveFlowersQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.GiveFlowers;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Sharing is Caring";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Give 10 flowers to someone";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "flowers given";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.Give;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 10;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (!metadata.TryGetValue("amount", out var amountStr)
|
||||||
|
|| !long.TryParse(amountStr, out var amount))
|
||||||
|
return oldProgress;
|
||||||
|
|
||||||
|
return oldProgress + amount;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class HangmanWinQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.HangmanWin;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Hangman Champion";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Win a game of Hangman";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "hangman games won";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.GameWon;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 1;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (!metadata.TryGetValue("game", out var value))
|
||||||
|
return oldProgress;
|
||||||
|
|
||||||
|
return value == "hangman" ? oldProgress + 1 : oldProgress;
|
||||||
|
}
|
||||||
|
}
|
33
src/EllieBot/Modules/Games/Quests/QuestModels/IQuest.cs
Normal file
33
src/EllieBot/Modules/Games/Quests/QuestModels/IQuest.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public interface IQuest
|
||||||
|
{
|
||||||
|
QuestIds QuestId { get; }
|
||||||
|
string Name { get; }
|
||||||
|
string Desc { get; }
|
||||||
|
string ProgDesc { get; }
|
||||||
|
QuestEventType EventType { get; }
|
||||||
|
long RequiredAmount { get; }
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress);
|
||||||
|
|
||||||
|
public virtual string ToString(long progress)
|
||||||
|
=> GetEmoji(progress, RequiredAmount) + $" [{progress}/{RequiredAmount}] " + ProgDesc;
|
||||||
|
|
||||||
|
public static string GetEmoji(long progress, long requiredAmount)
|
||||||
|
=> progress >= requiredAmount
|
||||||
|
? COMPLETED
|
||||||
|
: INCOMPLETE;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Completed Emoji
|
||||||
|
/// </summary>
|
||||||
|
public const string COMPLETED = "\\✅";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Incomplete Emoji
|
||||||
|
/// </summary>
|
||||||
|
public const string INCOMPLETE = "\\❌";
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class JoinAnimalRaceQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.JoinAnimalRace;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Race Participant";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Join an animal race";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "races joined";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.RaceJoined;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 1;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
return oldProgress + 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class PlantPickQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.PlantPick;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Gardener";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "pick and plant";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.PlantOrPick;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 0b11;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (!metadata.TryGetValue("type", out var val))
|
||||||
|
return oldProgress;
|
||||||
|
|
||||||
|
if (val == "plant")
|
||||||
|
{
|
||||||
|
oldProgress |= 0b10;
|
||||||
|
return oldProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val == "pick")
|
||||||
|
{
|
||||||
|
oldProgress |= 0b01;
|
||||||
|
return oldProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString(long progress)
|
||||||
|
{
|
||||||
|
var msg = "";
|
||||||
|
|
||||||
|
var emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b01) == 0b01)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += emoji + " picked flowers\n";
|
||||||
|
|
||||||
|
emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b10) == 0b10)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += emoji + " planted flowers";
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class SetPixelsQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.SetPixels;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Pixel Artist";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Set 3 pixels";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "pixels set";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.PixelSet;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 3;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
return oldProgress + 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class WellInformedQuest : IQuest
|
||||||
|
{
|
||||||
|
public QuestIds QuestId
|
||||||
|
=> QuestIds.WellInformed;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Well Informed";
|
||||||
|
|
||||||
|
public string Desc
|
||||||
|
=> "Check your flower stats";
|
||||||
|
|
||||||
|
public string ProgDesc
|
||||||
|
=> "";
|
||||||
|
|
||||||
|
public QuestEventType EventType
|
||||||
|
=> QuestEventType.CommandUsed;
|
||||||
|
|
||||||
|
public long RequiredAmount
|
||||||
|
=> 0b111;
|
||||||
|
|
||||||
|
public long TryUpdateProgress(IDictionary<string, string> metadata, long oldProgress)
|
||||||
|
{
|
||||||
|
if (!metadata.TryGetValue("name", out var type))
|
||||||
|
return oldProgress;
|
||||||
|
|
||||||
|
var progress = oldProgress;
|
||||||
|
|
||||||
|
if (type == "cash")
|
||||||
|
progress |= 0b001;
|
||||||
|
else if (type == "rakeback")
|
||||||
|
progress |= 0b010;
|
||||||
|
else if (type == "betstats")
|
||||||
|
progress |= 0b100;
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString(long progress)
|
||||||
|
{
|
||||||
|
var msg = "";
|
||||||
|
|
||||||
|
var emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b001) == 0b001)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += emoji + " checked cash\n";
|
||||||
|
|
||||||
|
emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b010) == 0b010)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += emoji + " checked rakeback\n";
|
||||||
|
|
||||||
|
emoji = IQuest.INCOMPLETE;
|
||||||
|
if ((progress & 0b100) == 0b100)
|
||||||
|
emoji = IQuest.COMPLETED;
|
||||||
|
|
||||||
|
msg += emoji + " checked bet stats";
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
206
src/EllieBot/Modules/Games/Quests/QuestService.cs
Normal file
206
src/EllieBot/Modules/Games/Quests/QuestService.cs
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using Microsoft.CodeAnalysis.Operations;
|
||||||
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
public sealed class QuestService(
|
||||||
|
DbService db,
|
||||||
|
IBotCache botCache,
|
||||||
|
IMessageSenderService sender,
|
||||||
|
DiscordSocketClient client
|
||||||
|
) : IEService, IExecPreCommand
|
||||||
|
{
|
||||||
|
private readonly EllieRandom rng = new();
|
||||||
|
|
||||||
|
private readonly IQuest[] _availableQuests =
|
||||||
|
[
|
||||||
|
new HangmanWinQuest(),
|
||||||
|
new PlantPickQuest(),
|
||||||
|
new BetQuest(),
|
||||||
|
new BetFlowersQuest(),
|
||||||
|
new GiftWaifuQuest(),
|
||||||
|
new CatchFishQuest(),
|
||||||
|
new SetPixelsQuest(),
|
||||||
|
new JoinAnimalRaceQuest(),
|
||||||
|
new BankerQuest(),
|
||||||
|
new CheckLeaderboardsQuest(),
|
||||||
|
new WellInformedQuest(),
|
||||||
|
];
|
||||||
|
|
||||||
|
private const int MAX_QUESTS_PER_DAY = 3;
|
||||||
|
|
||||||
|
private TypedKey<bool> UserHasQuestsKey(ulong userId)
|
||||||
|
=> new($"daily:generated:{userId}");
|
||||||
|
|
||||||
|
private TypedKey<bool> UserCompletedDailiesKey(ulong userId)
|
||||||
|
=> new($"daily:completed:{userId}");
|
||||||
|
|
||||||
|
|
||||||
|
public Task ReportActionAsync(
|
||||||
|
ulong userId,
|
||||||
|
QuestEventType eventType,
|
||||||
|
Dictionary<string, string>? metadata = null)
|
||||||
|
{
|
||||||
|
// don't block any caller
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
Log.Information("Action reported by {UserId}: {EventType} {Metadata}",
|
||||||
|
userId,
|
||||||
|
eventType,
|
||||||
|
metadata.ToJson());
|
||||||
|
metadata ??= new();
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
|
var alreadyDone = await botCache.GetAsync(UserCompletedDailiesKey(userId));
|
||||||
|
if (alreadyDone.IsT0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var userQuests = await GetUserQuestsAsync(userId, now);
|
||||||
|
|
||||||
|
foreach (var (q, uq) in userQuests)
|
||||||
|
{
|
||||||
|
// deleted quest
|
||||||
|
if (q is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// user already completed or incorrect event
|
||||||
|
if (uq.IsCompleted || q.EventType != eventType)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var newProgress = q.TryUpdateProgress(metadata, uq.Progress);
|
||||||
|
|
||||||
|
// user already did that part of the quest
|
||||||
|
if (newProgress == uq.Progress)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var isCompleted = newProgress >= q.RequiredAmount;
|
||||||
|
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
await uow.GetTable<UserQuest>()
|
||||||
|
.Where(x => x.UserId == userId && x.QuestId == q.QuestId && x.QuestNumber == uq.QuestNumber)
|
||||||
|
.Set(x => x.Progress, newProgress)
|
||||||
|
.Set(x => x.IsCompleted, isCompleted)
|
||||||
|
.UpdateAsync();
|
||||||
|
|
||||||
|
uq.IsCompleted = isCompleted;
|
||||||
|
|
||||||
|
if (userQuests.All(x => x.UserQuest.IsCompleted))
|
||||||
|
{
|
||||||
|
var timeUntilTomorrow = now.Date.AddDays(1) - DateTime.UtcNow;
|
||||||
|
if (!await botCache.AddAsync(
|
||||||
|
UserCompletedDailiesKey(userId),
|
||||||
|
true,
|
||||||
|
expiry: timeUntilTomorrow))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var user = await client.GetUserAsync(userId);
|
||||||
|
await sender
|
||||||
|
.Response(user)
|
||||||
|
.Confirm(strs.dailies_done)
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// we don't really care if the user receives it
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<(IQuest? Quest, UserQuest UserQuest)>> GetUserQuestsAsync(
|
||||||
|
ulong userId,
|
||||||
|
DateTime now)
|
||||||
|
{
|
||||||
|
var today = now.Date;
|
||||||
|
await EnsureUserDailiesAsync(userId, today);
|
||||||
|
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
var quests = await uow.GetTable<UserQuest>()
|
||||||
|
.Where(x => x.UserId == userId && x.DateAssigned == today)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return quests
|
||||||
|
.Select(x => (_availableQuests.FirstOrDefault(q => q.QuestId == x.QuestId), x))
|
||||||
|
.Select(x => x!)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnsureUserDailiesAsync(ulong userId, DateTime date)
|
||||||
|
{
|
||||||
|
var today = date.Date;
|
||||||
|
var timeUntilTomorrow = today.AddDays(1) - DateTime.UtcNow;
|
||||||
|
if (!await botCache.AddAsync(UserHasQuestsKey(userId), true, expiry: timeUntilTomorrow))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
var newQuests = GenerateDailyQuestsAsync(userId);
|
||||||
|
for (var i = 0; i < MAX_QUESTS_PER_DAY; i++)
|
||||||
|
{
|
||||||
|
await uow.GetTable<UserQuest>()
|
||||||
|
.InsertOrUpdateAsync(() => new()
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
QuestNumber = i,
|
||||||
|
DateAssigned = today,
|
||||||
|
|
||||||
|
IsCompleted = false,
|
||||||
|
QuestId = newQuests[i].QuestId,
|
||||||
|
Progress = 0,
|
||||||
|
},
|
||||||
|
old => new()
|
||||||
|
{
|
||||||
|
},
|
||||||
|
() => new()
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
QuestNumber = i,
|
||||||
|
DateAssigned = today
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<IQuest> GenerateDailyQuestsAsync(ulong userId)
|
||||||
|
{
|
||||||
|
return _availableQuests
|
||||||
|
.ToList()
|
||||||
|
.Shuffle()
|
||||||
|
.Take(MAX_QUESTS_PER_DAY)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Priority
|
||||||
|
=> int.MinValue;
|
||||||
|
|
||||||
|
public async Task<bool> ExecPreCommandAsync(ICommandContext context, string moduleName, CommandInfo command)
|
||||||
|
{
|
||||||
|
var cmdName = command.Name.ToLowerInvariant();
|
||||||
|
|
||||||
|
await ReportActionAsync(
|
||||||
|
context.User.Id,
|
||||||
|
QuestEventType.CommandUsed,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
{ "name", cmdName }
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UserCompletedDailies(ulong userId)
|
||||||
|
{
|
||||||
|
var result = await botCache.GetAsync(UserCompletedDailiesKey(userId));
|
||||||
|
|
||||||
|
return result.IsT0;
|
||||||
|
}
|
||||||
|
}
|
21
src/EllieBot/Modules/Games/Quests/db/UserQuest.cs
Normal file
21
src/EllieBot/Modules/Games/Quests/db/UserQuest.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
|
public class UserQuest
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int QuestNumber { get; set; }
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
|
||||||
|
public QuestIds QuestId { get; set; }
|
||||||
|
|
||||||
|
public int Progress { get; set; }
|
||||||
|
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
|
||||||
|
public DateTime DateAssigned { get; set; }
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ public class Owner(VoteRewardService vrs) : EllieModule
|
||||||
await ctx.OkAsync();
|
await ctx.OkAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CancellationTokenSource _cts = null;
|
private static CancellationTokenSource? _cts = null;
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task MassPing()
|
public async Task MassPing()
|
||||||
|
@ -22,6 +22,8 @@ public class Owner(VoteRewardService vrs) : EllieModule
|
||||||
await t.CancelAsync();
|
await t.CancelAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_cts = new();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var users = await ctx.Guild.GetUsersAsync().Fmap(u => u.Where(x => !x.IsBot).ToArray());
|
var users = await ctx.Guild.GetUsersAsync().Fmap(u => u.Where(x => !x.IsBot).ToArray());
|
||||||
|
|
|
@ -38,7 +38,7 @@ public interface ICurrencyService
|
||||||
IUser user,
|
IUser user,
|
||||||
long amount,
|
long amount,
|
||||||
TxData? txData);
|
TxData? txData);
|
||||||
|
|
||||||
Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9);
|
Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9);
|
||||||
|
|
||||||
Task<IReadOnlyList<CurrencyTransaction>> GetTransactionsAsync(
|
Task<IReadOnlyList<CurrencyTransaction>> GetTransactionsAsync(
|
||||||
|
@ -47,4 +47,14 @@ public interface ICurrencyService
|
||||||
int perPage = 15);
|
int perPage = 15);
|
||||||
|
|
||||||
Task<int> GetTransactionsCountAsync(ulong userId);
|
Task<int> GetTransactionsCountAsync(ulong userId);
|
||||||
|
|
||||||
|
Task<bool> TransferAsync(
|
||||||
|
IMessageSenderService sender,
|
||||||
|
IUser from,
|
||||||
|
IUser to,
|
||||||
|
long amount,
|
||||||
|
string? note,
|
||||||
|
string formattedAmount);
|
||||||
|
|
||||||
|
Task<long> GetBalanceAsync(ulong userId);
|
||||||
}
|
}
|
|
@ -6,21 +6,12 @@ using EllieBot.Services.Currency;
|
||||||
|
|
||||||
namespace EllieBot.Services;
|
namespace EllieBot.Services;
|
||||||
|
|
||||||
public sealed class CurrencyService : ICurrencyService, IEService
|
public sealed class CurrencyService(DbService db, ITxTracker txTracker) : ICurrencyService, IEService
|
||||||
{
|
{
|
||||||
private readonly DbService _db;
|
|
||||||
private readonly ITxTracker _txTracker;
|
|
||||||
|
|
||||||
public CurrencyService(DbService db, ITxTracker txTracker)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
_txTracker = txTracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
|
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
|
||||||
{
|
{
|
||||||
if (type == CurrencyType.Default)
|
if (type == CurrencyType.Default)
|
||||||
return Task.FromResult<IWallet>(new DefaultWallet(userId, _db));
|
return Task.FromResult<IWallet>(new DefaultWallet(userId, db));
|
||||||
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(type));
|
throw new ArgumentOutOfRangeException(nameof(type));
|
||||||
}
|
}
|
||||||
|
@ -53,16 +44,16 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
{
|
{
|
||||||
if (type == CurrencyType.Default)
|
if (type == CurrencyType.Default)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = db.GetDbContext();
|
||||||
await ctx
|
await ctx
|
||||||
.GetTable<DiscordUser>()
|
.GetTable<DiscordUser>()
|
||||||
.Where(x => userIds.Contains(x.UserId))
|
.Where(x => userIds.Contains(x.UserId))
|
||||||
.UpdateAsync(du => new()
|
.UpdateAsync(du => new()
|
||||||
{
|
{
|
||||||
CurrencyAmount = du.CurrencyAmount >= amount
|
CurrencyAmount = du.CurrencyAmount >= amount
|
||||||
? du.CurrencyAmount - amount
|
? du.CurrencyAmount - amount
|
||||||
: 0
|
: 0
|
||||||
});
|
});
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -77,7 +68,7 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
{
|
{
|
||||||
var wallet = await GetWalletAsync(userId);
|
var wallet = await GetWalletAsync(userId);
|
||||||
await wallet.Add(amount, txData);
|
await wallet.Add(amount, txData);
|
||||||
await _txTracker.TrackAdd(userId, amount, txData);
|
await txTracker.TrackAdd(userId, amount, txData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddAsync(
|
public async Task AddAsync(
|
||||||
|
@ -97,7 +88,7 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
var wallet = await GetWalletAsync(userId);
|
var wallet = await GetWalletAsync(userId);
|
||||||
var result = await wallet.Take(amount, txData);
|
var result = await wallet.Take(amount, txData);
|
||||||
if (result)
|
if (result)
|
||||||
await _txTracker.TrackRemove(userId, amount, txData);
|
await txTracker.TrackRemove(userId, amount, txData);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +100,7 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
|
|
||||||
public async Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9)
|
public async Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = db.GetDbContext();
|
||||||
return await uow.Set<DiscordUser>().GetTopRichest(ignoreId, page, perPage);
|
return await uow.Set<DiscordUser>().GetTopRichest(ignoreId, page, perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,23 +109,63 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
||||||
int page,
|
int page,
|
||||||
int perPage = 15)
|
int perPage = 15)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = db.GetDbContext();
|
||||||
|
|
||||||
var trs = await uow.GetTable<CurrencyTransaction>()
|
var trs = await uow.GetTable<CurrencyTransaction>()
|
||||||
.Where(x => x.UserId == userId)
|
.Where(x => x.UserId == userId)
|
||||||
.OrderByDescending(x => x.DateAdded)
|
.OrderByDescending(x => x.DateAdded)
|
||||||
.Skip(perPage * page)
|
.Skip(perPage * page)
|
||||||
.Take(perPage)
|
.Take(perPage)
|
||||||
.ToListAsyncLinqToDB();
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
return trs;
|
return trs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> GetTransactionsCountAsync(ulong userId)
|
public async Task<int> GetTransactionsCountAsync(ulong userId)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = db.GetDbContext();
|
||||||
return await uow.GetTable<CurrencyTransaction>()
|
return await uow.GetTable<CurrencyTransaction>()
|
||||||
.Where(x => x.UserId == userId)
|
.Where(x => x.UserId == userId)
|
||||||
.CountAsyncLinqToDB();
|
.CountAsyncLinqToDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> TransferAsync(
|
||||||
|
IMessageSenderService sender,
|
||||||
|
IUser from,
|
||||||
|
IUser to,
|
||||||
|
long amount,
|
||||||
|
string note,
|
||||||
|
string formattedAmount)
|
||||||
|
{
|
||||||
|
var fromWallet = await GetWalletAsync(from.Id);
|
||||||
|
var toWallet = await GetWalletAsync(to.Id);
|
||||||
|
|
||||||
|
var extra = new TxData("gift", from.ToString()!, note, from.Id);
|
||||||
|
|
||||||
|
if (await fromWallet.Transfer(amount, toWallet, extra))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await sender.Response(to)
|
||||||
|
.Confirm(string.IsNullOrWhiteSpace(note)
|
||||||
|
? $"Received {formattedAmount} from {from} "
|
||||||
|
: $"Received {formattedAmount} from {from}: {note}")
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetBalanceAsync(ulong userId)
|
||||||
|
{
|
||||||
|
var wallet = await GetWalletAsync(userId);
|
||||||
|
return await wallet.GetBalance();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,45 +4,8 @@ namespace EllieBot.Services;
|
||||||
|
|
||||||
public static class CurrencyServiceExtensions
|
public static class CurrencyServiceExtensions
|
||||||
{
|
{
|
||||||
public static async Task<long> GetBalanceAsync(this ICurrencyService cs, ulong userId)
|
|
||||||
{
|
|
||||||
var wallet = await cs.GetWalletAsync(userId);
|
|
||||||
return await wallet.GetBalance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// FUTURE should be a transaction
|
// FUTURE should be a transaction
|
||||||
public static async Task<bool> TransferAsync(
|
|
||||||
this ICurrencyService cs,
|
|
||||||
IMessageSenderService sender,
|
|
||||||
IUser from,
|
|
||||||
IUser to,
|
|
||||||
long amount,
|
|
||||||
string? note,
|
|
||||||
string formattedAmount)
|
|
||||||
{
|
|
||||||
var fromWallet = await cs.GetWalletAsync(from.Id);
|
|
||||||
var toWallet = await cs.GetWalletAsync(to.Id);
|
|
||||||
|
|
||||||
var extra = new TxData("gift", from.ToString()!, note, from.Id);
|
|
||||||
|
|
||||||
if (await fromWallet.Transfer(amount, toWallet, extra))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await sender.Response(to)
|
|
||||||
.Confirm(string.IsNullOrWhiteSpace(note)
|
|
||||||
? $"Received {formattedAmount} from {from} "
|
|
||||||
: $"Received {formattedAmount} from {from}: {note}")
|
|
||||||
.SendAsync();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
//ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -8,10 +8,15 @@ using EllieBot.Modules.Gambling;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using EllieBot.Modules.Administration;
|
using EllieBot.Modules.Administration;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
|
using EllieBot.Modules.Games.Quests;
|
||||||
|
|
||||||
namespace EllieBot.Services;
|
namespace EllieBot.Services;
|
||||||
|
|
||||||
public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
public sealed class GamblingTxTracker(
|
||||||
|
DbService db,
|
||||||
|
QuestService quests
|
||||||
|
)
|
||||||
|
: ITxTracker, IEService, IReadyExecutor
|
||||||
{
|
{
|
||||||
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
|
private static readonly IReadOnlySet<string> _gamblingTypes = new HashSet<string>(new[]
|
||||||
{
|
{
|
||||||
|
@ -21,17 +26,6 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
private NonBlocking.ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> globalStats = new();
|
private NonBlocking.ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> globalStats = new();
|
||||||
private ConcurrentBag<UserBetStats> userStats = new();
|
private ConcurrentBag<UserBetStats> userStats = new();
|
||||||
|
|
||||||
private readonly DbService _db;
|
|
||||||
private readonly GamblingConfigService _gcs;
|
|
||||||
private readonly INotifySubscriber _notify;
|
|
||||||
|
|
||||||
public GamblingTxTracker(DbService db, GamblingConfigService gcs, INotifySubscriber notify)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
_gcs = gcs;
|
|
||||||
_notify = notify;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
=> await Task.WhenAll(RunUserStatsCollector(), RunBetStatsCollector());
|
=> await Task.WhenAll(RunUserStatsCollector(), RunBetStatsCollector());
|
||||||
|
|
||||||
|
@ -40,7 +34,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
||||||
while (await timer.WaitForNextTickAsync())
|
while (await timer.WaitForNextTickAsync())
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = db.GetDbContext();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -51,22 +45,22 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
if (globalStats.TryRemove(key, out var stat))
|
if (globalStats.TryRemove(key, out var stat))
|
||||||
{
|
{
|
||||||
await ctx.GetTable<GamblingStats>()
|
await ctx.GetTable<GamblingStats>()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
Feature = key,
|
Feature = key,
|
||||||
Bet = stat.Bet,
|
Bet = stat.Bet,
|
||||||
PaidOut = stat.PaidOut,
|
PaidOut = stat.PaidOut,
|
||||||
DateAdded = DateTime.UtcNow
|
DateAdded = DateTime.UtcNow
|
||||||
},
|
},
|
||||||
old => new()
|
old => new()
|
||||||
{
|
{
|
||||||
Bet = old.Bet + stat.Bet,
|
Bet = old.Bet + stat.Bet,
|
||||||
PaidOut = old.PaidOut + stat.PaidOut,
|
PaidOut = old.PaidOut + stat.PaidOut,
|
||||||
},
|
},
|
||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
Feature = key
|
Feature = key
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,68 +94,68 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
|
|
||||||
// update userstats
|
// update userstats
|
||||||
foreach (var (k, x) in users.GroupBy(x => (x.UserId, x.Game))
|
foreach (var (k, x) in users.GroupBy(x => (x.UserId, x.Game))
|
||||||
.ToDictionary(x => x.Key,
|
.ToDictionary(x => x.Key,
|
||||||
x => x.Aggregate((a, b) => new()
|
x => x.Aggregate((a, b) => new()
|
||||||
{
|
{
|
||||||
WinCount = a.WinCount + b.WinCount,
|
WinCount = a.WinCount + b.WinCount,
|
||||||
LoseCount = a.LoseCount + b.LoseCount,
|
LoseCount = a.LoseCount + b.LoseCount,
|
||||||
TotalBet = a.TotalBet + b.TotalBet,
|
TotalBet = a.TotalBet + b.TotalBet,
|
||||||
PaidOut = a.PaidOut + b.PaidOut,
|
PaidOut = a.PaidOut + b.PaidOut,
|
||||||
MaxBet = Math.Max(a.MaxBet, b.MaxBet),
|
MaxBet = Math.Max(a.MaxBet, b.MaxBet),
|
||||||
MaxWin = Math.Max(a.MaxWin, b.MaxWin),
|
MaxWin = Math.Max(a.MaxWin, b.MaxWin),
|
||||||
})))
|
})))
|
||||||
{
|
{
|
||||||
rakebacks.TryAdd(k.UserId, 0m);
|
rakebacks.TryAdd(k.UserId, 0m);
|
||||||
rakebacks[k.UserId] += x.TotalBet * GetHouseEdge(k.Game) * BASE_RAKEBACK;
|
rakebacks[k.UserId] += x.TotalBet * GetHouseEdge(k.Game) * BASE_RAKEBACK;
|
||||||
|
|
||||||
|
|
||||||
// bulk upsert in the future
|
// bulk upsert in the future
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = db.GetDbContext();
|
||||||
await uow.GetTable<UserBetStats>()
|
await uow.GetTable<UserBetStats>()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
UserId = k.UserId,
|
UserId = k.UserId,
|
||||||
Game = k.Game,
|
Game = k.Game,
|
||||||
WinCount = x.WinCount,
|
WinCount = x.WinCount,
|
||||||
LoseCount = Math.Max(0, x.LoseCount),
|
LoseCount = Math.Max(0, x.LoseCount),
|
||||||
TotalBet = x.TotalBet,
|
TotalBet = x.TotalBet,
|
||||||
PaidOut = x.PaidOut,
|
PaidOut = x.PaidOut,
|
||||||
MaxBet = x.MaxBet,
|
MaxBet = x.MaxBet,
|
||||||
MaxWin = x.MaxWin
|
MaxWin = x.MaxWin
|
||||||
},
|
},
|
||||||
o => new()
|
o => new()
|
||||||
{
|
{
|
||||||
WinCount = o.WinCount + x.WinCount,
|
WinCount = o.WinCount + x.WinCount,
|
||||||
LoseCount = Math.Max(0, o.LoseCount + x.LoseCount),
|
LoseCount = Math.Max(0, o.LoseCount + x.LoseCount),
|
||||||
TotalBet = o.TotalBet + x.TotalBet,
|
TotalBet = o.TotalBet + x.TotalBet,
|
||||||
PaidOut = o.PaidOut + x.PaidOut,
|
PaidOut = o.PaidOut + x.PaidOut,
|
||||||
MaxBet = Math.Max(o.MaxBet, x.MaxBet),
|
MaxBet = Math.Max(o.MaxBet, x.MaxBet),
|
||||||
MaxWin = Math.Max(o.MaxWin, x.MaxWin),
|
MaxWin = Math.Max(o.MaxWin, x.MaxWin),
|
||||||
},
|
},
|
||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
UserId = k.UserId,
|
UserId = k.UserId,
|
||||||
Game = k.Game
|
Game = k.Game
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (k, v) in rakebacks)
|
foreach (var (k, v) in rakebacks)
|
||||||
{
|
{
|
||||||
await _db.GetDbContext()
|
await db.GetDbContext()
|
||||||
.GetTable<Rakeback>()
|
.GetTable<Rakeback>()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
UserId = k,
|
UserId = k,
|
||||||
Amount = v
|
Amount = v
|
||||||
},
|
},
|
||||||
(old) => new()
|
(old) => new()
|
||||||
{
|
{
|
||||||
Amount = old.Amount + v
|
Amount = old.Amount + v
|
||||||
},
|
},
|
||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
UserId = k
|
UserId = k
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -173,10 +167,10 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
|
|
||||||
private const decimal BASE_RAKEBACK = 0.05m;
|
private const decimal BASE_RAKEBACK = 0.05m;
|
||||||
|
|
||||||
public Task TrackAdd(ulong userId, long amount, TxData? txData)
|
public async Task TrackAdd(ulong userId, long amount, TxData? txData)
|
||||||
{
|
{
|
||||||
if (txData is null)
|
if (txData is null)
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
|
|
||||||
if (_gamblingTypes.Contains(txData.Type))
|
if (_gamblingTypes.Contains(txData.Type))
|
||||||
{
|
{
|
||||||
|
@ -188,7 +182,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
var mType = GetGameType(txData.Type);
|
var mType = GetGameType(txData.Type);
|
||||||
|
|
||||||
if (mType is not { } type)
|
if (mType is not { } type)
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
|
|
||||||
// var bigWin = _gcs.Data.BigWin;
|
// var bigWin = _gcs.Data.BigWin;
|
||||||
// if (bigWin > 0 && amount >= bigWin)
|
// if (bigWin > 0 && amount >= bigWin)
|
||||||
|
@ -211,7 +205,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
MaxBet = 0,
|
MaxBet = 0,
|
||||||
MaxWin = amount,
|
MaxWin = amount,
|
||||||
});
|
});
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (txData.Type == "animalrace")
|
else if (txData.Type == "animalrace")
|
||||||
|
@ -230,7 +224,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
MaxWin = 0,
|
MaxWin = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,14 +239,12 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
MaxBet = 0,
|
MaxBet = 0,
|
||||||
MaxWin = amount,
|
MaxWin = amount,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task TrackRemove(ulong userId, long amount, TxData? txData)
|
public async Task TrackRemove(ulong userId, long amount, TxData? txData)
|
||||||
{
|
{
|
||||||
if (txData is null)
|
if (txData is null)
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
|
|
||||||
if (_gamblingTypes.Contains(txData.Type))
|
if (_gamblingTypes.Contains(txData.Type))
|
||||||
{
|
{
|
||||||
|
@ -264,7 +256,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
var mType = GetGameType(txData.Type);
|
var mType = GetGameType(txData.Type);
|
||||||
|
|
||||||
if (mType is not { } type)
|
if (mType is not { } type)
|
||||||
return Task.CompletedTask;
|
return;
|
||||||
|
|
||||||
userStats.Add(new UserBetStats()
|
userStats.Add(new UserBetStats()
|
||||||
{
|
{
|
||||||
|
@ -278,7 +270,14 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
MaxWin = 0
|
MaxWin = 0
|
||||||
});
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
await quests.ReportActionAsync(userId,
|
||||||
|
QuestEventType.BetPlaced,
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
{ "type", txData.Type },
|
||||||
|
{ "amount", amount.ToString() }
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GamblingGame? GetGameType(string game)
|
private static GamblingGame? GetGameType(string game)
|
||||||
|
@ -296,26 +295,26 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<GamblingStats>> GetAllAsync()
|
public async Task<IReadOnlyCollection<GamblingStats>> GetAllAsync()
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = db.GetDbContext();
|
||||||
return await ctx.Set<GamblingStats>()
|
return await ctx.Set<GamblingStats>()
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<UserBetStats>> GetUserStatsAsync(ulong userId, GamblingGame? game = null)
|
public async Task<List<UserBetStats>> GetUserStatsAsync(ulong userId, GamblingGame? game = null)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = db.GetDbContext();
|
||||||
|
|
||||||
|
|
||||||
if (game is null)
|
if (game is null)
|
||||||
return await ctx
|
return await ctx
|
||||||
.GetTable<UserBetStats>()
|
.GetTable<UserBetStats>()
|
||||||
.Where(x => x.UserId == userId)
|
.Where(x => x.UserId == userId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return await ctx
|
return await ctx
|
||||||
.GetTable<UserBetStats>()
|
.GetTable<UserBetStats>()
|
||||||
.Where(x => x.UserId == userId && x.Game == game)
|
.Where(x => x.UserId == userId && x.Game == game)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public decimal GetHouseEdge(GamblingGame game)
|
public decimal GetHouseEdge(GamblingGame game)
|
||||||
|
@ -330,8 +329,6 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
||||||
GamblingGame.Race => 0.06m,
|
GamblingGame.Race => 0.06m,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class UserBetStats
|
public sealed class UserBetStats
|
||||||
|
|
|
@ -374,7 +374,6 @@ quoteadd:
|
||||||
- qa
|
- qa
|
||||||
- qadd
|
- qadd
|
||||||
- quadd
|
- quadd
|
||||||
- .
|
|
||||||
quoteedit:
|
quoteedit:
|
||||||
- quoteedit
|
- quoteedit
|
||||||
- qe
|
- qe
|
||||||
|
@ -384,7 +383,6 @@ quoteprint:
|
||||||
- quoteprint
|
- quoteprint
|
||||||
- qp
|
- qp
|
||||||
- qup
|
- qup
|
||||||
- ..
|
|
||||||
- qprint
|
- qprint
|
||||||
quoteshow:
|
quoteshow:
|
||||||
- quoteshow
|
- quoteshow
|
||||||
|
@ -1658,4 +1656,8 @@ votefeed:
|
||||||
vote:
|
vote:
|
||||||
- vote
|
- vote
|
||||||
massping:
|
massping:
|
||||||
- massping
|
- massping
|
||||||
|
questlog:
|
||||||
|
- questlog
|
||||||
|
- qlog
|
||||||
|
- myquests
|
|
@ -5206,5 +5206,12 @@ massping:
|
||||||
Run again to cancel.
|
Run again to cancel.
|
||||||
ex:
|
ex:
|
||||||
- ''
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
||||||
|
questlog:
|
||||||
|
desc: |-
|
||||||
|
Shows your active quests and progress.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
params:
|
params:
|
||||||
- { }
|
- { }
|
|
@ -1244,5 +1244,9 @@
|
||||||
"notify_cant_set": "This event doesn't support origin channel, Please specify a channel",
|
"notify_cant_set": "This event doesn't support origin channel, Please specify a channel",
|
||||||
"vote_reward": "Thank you for voting! You've received {0}.",
|
"vote_reward": "Thank you for voting! You've received {0}.",
|
||||||
"vote_suggest": "Voting for the bot once every 6 hours will get you {0}!",
|
"vote_suggest": "Voting for the bot once every 6 hours will get you {0}!",
|
||||||
"vote_disabled": "Voting is disabled."
|
"vote_disabled": "Voting is disabled.",
|
||||||
|
"quest_log": "Quest Log",
|
||||||
|
"dailies_done": "You've completed your dailies!",
|
||||||
|
"dailies_reset": "Reset {0}",
|
||||||
|
"daily_completed": "You've completed a daily quest: {0}"
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue