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
src/EllieBot
Migrations
PostgreSql
Sqlite
Modules
Gambling
Games
Fish
Hangman
NCanvas
Quests
Quest.csQuestCommands.csQuestEvent.csQuestEventType.csQuestIds.cs
QuestModels
BankerQuest.csBetFlowersQuest.csBetQuest.csCatchFishQuest.csCatchQualityQuest.csCatchTrashQuest.csCheckLeaderboardsQuest.csGiftWaifuQuest.csGiveFlowersQuest.csHangmanWinQuest.csIQuest.csJoinAnimalRaceQuest.csPlantPickQuest.csSetPixelsQuest.csWellInformed.cs
QuestService.csdb
Owner
_common
Currency
Services/Currency
strings
|
@ -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
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlContext))]
|
||||
[Migration("20250323022235_init")]
|
||||
[Migration("20250328080459_init")]
|
||||
partial class init
|
||||
{
|
||||
/// <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
|
||||
{
|
||||
[DbContext(typeof(SqliteContext))]
|
||||
[Migration("20250323022218_init")]
|
||||
[Migration("20250328080413_init")]
|
||||
partial class init
|
||||
{
|
||||
/// <inheritdoc />
|
|
@ -1,6 +1,7 @@
|
|||
#nullable disable
|
||||
using EllieBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
using EllieBot.Modules.Games.Common;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
|
||||
namespace EllieBot.Modules.Gambling.Common.AnimalRacing;
|
||||
|
||||
|
@ -35,12 +36,18 @@ public sealed class AnimalRace : IDisposable
|
|||
private readonly ICurrencyService _currency;
|
||||
private readonly RaceOptions _options;
|
||||
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;
|
||||
_options = options;
|
||||
_animalsQueue = new(availableAnimals);
|
||||
_quests = quests;
|
||||
MaxUsers = _animalsQueue.Count;
|
||||
|
||||
if (_animalsQueue.Count == 0)
|
||||
|
@ -60,7 +67,10 @@ public sealed class AnimalRace : IDisposable
|
|||
|
||||
await Start();
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
});
|
||||
|
||||
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
|
||||
|
@ -93,7 +103,10 @@ public sealed class AnimalRace : IDisposable
|
|||
|
||||
return user;
|
||||
}
|
||||
finally { _locker.Release(); }
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Start()
|
||||
|
@ -104,7 +117,9 @@ public sealed class AnimalRace : IDisposable
|
|||
foreach (var user in _users)
|
||||
{
|
||||
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);
|
||||
|
@ -112,6 +127,11 @@ public sealed class AnimalRace : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
foreach (var user in _users)
|
||||
{
|
||||
await _quests.ReportActionAsync(user.UserId, QuestEventType.RaceJoined);
|
||||
}
|
||||
|
||||
_ = OnStarted?.Invoke(this);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ using EllieBot.Modules.Gambling.Common;
|
|||
using EllieBot.Modules.Gambling.Common.AnimalRacing;
|
||||
using EllieBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
||||
using EllieBot.Modules.Gambling.Services;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
using EllieBot.Modules.Games.Services;
|
||||
|
||||
namespace EllieBot.Modules.Gambling;
|
||||
|
@ -17,6 +18,7 @@ public partial class Gambling
|
|||
private readonly ICurrencyService _cs;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly GamesConfigService _gamesConf;
|
||||
private readonly QuestService _quests;
|
||||
|
||||
private IUserMessage raceMessage;
|
||||
|
||||
|
@ -24,12 +26,14 @@ public partial class Gambling
|
|||
ICurrencyService cs,
|
||||
DiscordSocketClient client,
|
||||
GamblingConfigService gamblingConf,
|
||||
GamesConfigService gamesConf)
|
||||
GamesConfigService gamesConf,
|
||||
QuestService quests)
|
||||
: base(gamblingConf)
|
||||
{
|
||||
_cs = cs;
|
||||
_client = client;
|
||||
_gamesConf = gamesConf;
|
||||
_quests = quests;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@ -39,11 +43,11 @@ public partial class Gambling
|
|||
{
|
||||
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))
|
||||
return Response()
|
||||
.Error(GetText(strs.animal_race), GetText(strs.animal_race_already_started))
|
||||
.SendAsync();
|
||||
.Error(GetText(strs.animal_race), GetText(strs.animal_race_already_started))
|
||||
.SendAsync();
|
||||
|
||||
ar.Initialize();
|
||||
|
||||
|
@ -61,7 +65,9 @@ public partial class Gambling
|
|||
raceMessage = null;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
@ -74,22 +80,22 @@ public partial class Gambling
|
|||
if (race.FinishedUsers[0].Bet > 0)
|
||||
{
|
||||
return Response()
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(GetText(strs.animal_race_won_money(
|
||||
Format.Bold(winner.Username),
|
||||
winner.Animal.Icon,
|
||||
N(race.FinishedUsers[0].Bet * race.Multi))))
|
||||
.WithFooter($"x{race.Multi:F2}"))
|
||||
.SendAsync();
|
||||
.Embed(CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(GetText(strs.animal_race_won_money(
|
||||
Format.Bold(winner.Username),
|
||||
winner.Animal.Icon,
|
||||
N(race.FinishedUsers[0].Bet * race.Multi))))
|
||||
.WithFooter($"x{race.Multi:F2}"))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
ar.Dispose();
|
||||
return Response()
|
||||
.Confirm(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)))
|
||||
.SendAsync();
|
||||
.Confirm(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
ar.OnStartingFailed += Ar_OnStartingFailed;
|
||||
|
@ -99,10 +105,10 @@ public partial class Gambling
|
|||
_client.MessageReceived += ClientMessageReceived;
|
||||
|
||||
return Response()
|
||||
.Confirm(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_starting(options.StartTime)),
|
||||
footer: GetText(strs.animal_race_join_instr(prefix)))
|
||||
.SendAsync();
|
||||
.Confirm(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_starting(options.StartTime)),
|
||||
footer: GetText(strs.animal_race_join_instr(prefix)))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
private Task Ar_OnStarted(AnimalRace race)
|
||||
|
@ -110,9 +116,9 @@ public partial class Gambling
|
|||
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_starting_with_x(race.Users.Count)))
|
||||
.SendAsync();
|
||||
.Confirm(GetText(strs.animal_race),
|
||||
GetText(strs.animal_race_starting_with_x(race.Users.Count)))
|
||||
.SendAsync();
|
||||
}
|
||||
|
||||
private async Task Ar_OnStateUpdate(AnimalRace race)
|
||||
|
@ -133,10 +139,10 @@ public partial class Gambling
|
|||
else
|
||||
{
|
||||
await msg.ModifyAsync(x => x.Embed = CreateEmbed()
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
.WithTitle(GetText(strs.animal_race))
|
||||
.WithDescription(text)
|
||||
.WithOkColor()
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,15 +172,15 @@ public partial class Gambling
|
|||
if (amount > 0)
|
||||
{
|
||||
await Response()
|
||||
.Confirm(GetText(strs.animal_race_join_bet(ctx.User.Mention,
|
||||
user.Animal.Icon,
|
||||
amount + CurrencySign)))
|
||||
.SendAsync();
|
||||
.Confirm(GetText(strs.animal_race_join_bet(ctx.User.Mention,
|
||||
user.Animal.Icon,
|
||||
amount + CurrencySign)))
|
||||
.SendAsync();
|
||||
}
|
||||
else
|
||||
await Response()
|
||||
.Confirm(strs.animal_race_join(ctx.User.Mention, user.Animal.Icon))
|
||||
.SendAsync();
|
||||
.Confirm(strs.animal_race_join(ctx.User.Mention, user.Animal.Icon))
|
||||
.SendAsync();
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
|
||||
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)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||
|
@ -37,7 +32,7 @@ public sealed class BankService : IBankService, IEService
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> TakeAsync(ulong userId, long amount)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
|
||||
|
@ -50,7 +45,7 @@ public sealed class BankService : IBankService, IEService
|
|||
{
|
||||
Balance = old.Balance - amount
|
||||
});
|
||||
|
||||
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
|
@ -63,20 +58,28 @@ public sealed class BankService : IBankService, IEService
|
|||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await ctx.Set<BankUser>()
|
||||
.ToLinqToDBTable()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
UserId = userId,
|
||||
Balance = amount
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
Balance = old.Balance + amount
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
UserId = userId
|
||||
});
|
||||
.ToLinqToDBTable()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
UserId = userId,
|
||||
Balance = amount
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
Balance = old.Balance + amount
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
UserId = userId
|
||||
});
|
||||
|
||||
await quests.ReportActionAsync(userId,
|
||||
QuestEventType.BankAction,
|
||||
new()
|
||||
{
|
||||
{ "type", "deposit" },
|
||||
{ "amount", amount.ToString() }
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -87,12 +90,12 @@ public sealed class BankService : IBankService, IEService
|
|||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var rows = await ctx.Set<BankUser>()
|
||||
.ToLinqToDBTable()
|
||||
.Where(x => x.UserId == userId && x.Balance >= amount)
|
||||
.UpdateAsync((old) => new()
|
||||
{
|
||||
Balance = old.Balance - amount
|
||||
});
|
||||
.ToLinqToDBTable()
|
||||
.Where(x => x.UserId == userId && x.Balance >= amount)
|
||||
.UpdateAsync((old) => new()
|
||||
{
|
||||
Balance = old.Balance - amount
|
||||
});
|
||||
|
||||
if (rows > 0)
|
||||
{
|
||||
|
@ -106,10 +109,11 @@ public sealed class BankService : IBankService, IEService
|
|||
public async Task<long> GetBalanceAsync(ulong userId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return (await ctx.Set<BankUser>()
|
||||
.ToLinqToDBTable()
|
||||
.FirstOrDefaultAsync(x => x.UserId == userId))
|
||||
?.Balance
|
||||
?? 0;
|
||||
var res = (await ctx.Set<BankUser>()
|
||||
.ToLinqToDBTable()
|
||||
.FirstOrDefaultAsync(x => x.UserId == userId))
|
||||
?.Balance
|
||||
?? 0;
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ using System.Text;
|
|||
using EllieBot.Modules.Gambling.Rps;
|
||||
using EllieBot.Common.TypeReaders;
|
||||
using EllieBot.Modules.Games;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
using EllieBot.Modules.Patronage;
|
||||
|
||||
namespace EllieBot.Modules.Gambling;
|
||||
|
@ -36,6 +37,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||
private readonly IBotCache _cache;
|
||||
private readonly CaptchaService _captchaService;
|
||||
private readonly VoteRewardService _vrs;
|
||||
private readonly QuestService _quests;
|
||||
|
||||
public Gambling(
|
||||
IGamblingService gs,
|
||||
|
@ -52,7 +54,8 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||
RakebackService rb,
|
||||
IBotCache cache,
|
||||
CaptchaService captchaService,
|
||||
VoteRewardService vrs)
|
||||
VoteRewardService vrs,
|
||||
QuestService quests)
|
||||
: base(configService)
|
||||
{
|
||||
_gs = gs;
|
||||
|
@ -68,6 +71,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||
_ps = patronage;
|
||||
_rng = new EllieRandom();
|
||||
_vrs = vrs;
|
||||
_quests = quests;
|
||||
|
||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||
_enUsCulture.NumberDecimalDigits = 0;
|
||||
|
|
|
@ -6,6 +6,7 @@ using EllieBot.Common.ModuleBehaviors;
|
|||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Gambling.Common;
|
||||
using EllieBot.Modules.Gambling.Common.Connect4;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
using EllieBot.Modules.Patronage;
|
||||
|
||||
namespace EllieBot.Modules.Gambling.Services;
|
||||
|
@ -19,6 +20,7 @@ public class GamblingService : IEService, IReadyExecutor
|
|||
private readonly IBotCache _cache;
|
||||
private readonly GamblingConfigService _gcs;
|
||||
private readonly IPatronageService _ps;
|
||||
private readonly QuestService _quests;
|
||||
private readonly EllieRandom _rng;
|
||||
|
||||
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
||||
|
@ -28,13 +30,15 @@ public class GamblingService : IEService, IReadyExecutor
|
|||
DiscordSocketClient client,
|
||||
IBotCache cache,
|
||||
GamblingConfigService gcs,
|
||||
IPatronageService ps)
|
||||
IPatronageService ps,
|
||||
QuestService quests)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
_cache = cache;
|
||||
_gcs = gcs;
|
||||
_ps = ps;
|
||||
_quests = quests;
|
||||
_rng = new EllieRandom();
|
||||
}
|
||||
|
||||
|
@ -230,10 +234,15 @@ public class GamblingService : IEService, IReadyExecutor
|
|||
if (booster)
|
||||
originalAmount += gcsData.BoostBonus.BaseTimelyBonus;
|
||||
|
||||
var hasCompletedDailies = await _quests.UserCompletedDailies(userId);
|
||||
|
||||
if (hasCompletedDailies)
|
||||
originalAmount = (long)(1.5 * originalAmount);
|
||||
|
||||
var patron = await _ps.GetPatronAsync(userId);
|
||||
var percentBonus = (_ps.PercentBonus(patron) / 100f);
|
||||
|
||||
originalAmount += (int)(originalAmount * percentBonus);
|
||||
originalAmount += (long)(originalAmount * percentBonus);
|
||||
|
||||
var msg = $"**{N(originalAmount)}** base reward\n\n";
|
||||
if (boostGuilds.Count > 0)
|
||||
|
@ -252,6 +261,15 @@ public class GamblingService : IEService, IReadyExecutor
|
|||
else
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using LinqToDB.EntityFrameworkCore;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.Fonts.Unicode;
|
||||
using SixLabors.ImageSharp;
|
||||
|
@ -15,67 +16,47 @@ using Image = SixLabors.ImageSharp.Image;
|
|||
|
||||
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
|
||||
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 = [];
|
||||
|
||||
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)
|
||||
=> PotentialFlowerGeneration(msg);
|
||||
|
||||
private string GetText(ulong gid, LocStr str)
|
||||
=> _strings.GetText(str, gid);
|
||||
=> strings.GetText(str, gid);
|
||||
|
||||
public async Task<bool> ToggleCurrencyGeneration(ulong gid, ulong cid)
|
||||
{
|
||||
bool enabled;
|
||||
await using var uow = _db.GetDbContext();
|
||||
await using var uow = db.GetDbContext();
|
||||
|
||||
if (_generationChannels.Add(cid))
|
||||
{
|
||||
await uow.GetTable<GCChannelId>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
ChannelId = cid,
|
||||
GuildId = gid
|
||||
}, (x) => new()
|
||||
},
|
||||
(x) => new()
|
||||
{
|
||||
ChannelId = cid,
|
||||
GuildId = gid
|
||||
}, () => new()
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
ChannelId = cid,
|
||||
GuildId = gid
|
||||
|
@ -87,8 +68,8 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
else
|
||||
{
|
||||
await uow.GetTable<GCChannelId>()
|
||||
.Where(x => x.ChannelId == cid && x.GuildId == gid)
|
||||
.DeleteAsync();
|
||||
.Where(x => x.ChannelId == cid && x.GuildId == gid)
|
||||
.DeleteAsync();
|
||||
|
||||
_generationChannels.TryRemove(cid);
|
||||
enabled = false;
|
||||
|
@ -99,9 +80,9 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
|
||||
public async Task<IReadOnlyCollection<GCChannelId>> GetAllGeneratingChannels()
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await using var uow = db.GetDbContext();
|
||||
return await uow.GetTable<GCChannelId>()
|
||||
.ToListAsyncLinqToDB();
|
||||
.ToListAsyncLinqToDB();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -111,7 +92,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
/// <returns>Stream of the currency image</returns>
|
||||
public async Task<(Stream, string)> GetRandomCurrencyImageAsync(string pass)
|
||||
{
|
||||
var curImg = await _images.GetCurrencyImageAsync();
|
||||
var curImg = await images.GetCurrencyImageAsync();
|
||||
|
||||
if (curImg is null)
|
||||
return (new MemoryStream(), null);
|
||||
|
@ -142,7 +123,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
pass = pass.TrimTo(10, true).ToLowerInvariant();
|
||||
using var img = Image.Load<Rgba32>(curImg);
|
||||
// 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 =>
|
||||
{
|
||||
// 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
|
||||
x.DrawText(new RichTextOptions(font)
|
||||
{
|
||||
Origin = new(0, 0),
|
||||
TextRuns =
|
||||
{
|
||||
Origin = new(0, 0),
|
||||
TextRuns =
|
||||
[
|
||||
strikeoutRun
|
||||
]
|
||||
},
|
||||
},
|
||||
pass,
|
||||
new SolidBrush(Color.White));
|
||||
});
|
||||
|
@ -200,7 +181,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
{
|
||||
try
|
||||
{
|
||||
var config = _gss.Data;
|
||||
var config = gss.Data;
|
||||
var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue.ToBinary());
|
||||
var rng = new EllieRandom();
|
||||
|
||||
|
@ -219,7 +200,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
|
||||
if (dropAmount > 0)
|
||||
{
|
||||
var prefix = _cmdHandler.GetPrefix(channel.Guild.Id);
|
||||
var prefix = cmdHandler.GetPrefix(channel.Guild.Id);
|
||||
var toSend = dropAmount == 1
|
||||
? 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));
|
||||
|
||||
var pw = config.Generation.HasPassword ? _gs.GeneratePassword().ToUpperInvariant() : null;
|
||||
var pw = config.Generation.HasPassword ? gs.GeneratePassword().ToUpperInvariant() : null;
|
||||
|
||||
IUserMessage sent;
|
||||
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
|
||||
|
@ -238,7 +219,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
|
||||
var res = await AddPlantToDatabase(channel.GuildId,
|
||||
channel.Id,
|
||||
_client.CurrentUser.Id,
|
||||
client.CurrentUser.Id,
|
||||
sent.Id,
|
||||
dropAmount,
|
||||
pw,
|
||||
|
@ -261,12 +242,12 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
public async Task<long> PickAsync(
|
||||
ulong gid,
|
||||
ITextChannel ch,
|
||||
ulong uid,
|
||||
ulong userId,
|
||||
string pass)
|
||||
{
|
||||
long amount;
|
||||
ulong[] ids;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
await using (var uow = db.GetDbContext())
|
||||
{
|
||||
// this method will sum all plants with that password,
|
||||
// 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();
|
||||
// gets all plants in this channel with the same password
|
||||
var entries = await uow.GetTable<PlantedCurrency>()
|
||||
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
||||
.DeleteWithOutputAsync();
|
||||
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
|
||||
.DeleteWithOutputAsync();
|
||||
|
||||
if (!entries.Any())
|
||||
return 0;
|
||||
|
@ -285,14 +266,24 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
}
|
||||
|
||||
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
|
||||
{
|
||||
_ = ch.DeleteMessagesAsync(ids);
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// return the amount of currency the user picked
|
||||
return amount;
|
||||
|
@ -308,8 +299,8 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
try
|
||||
{
|
||||
// get the text
|
||||
var prefix = _cmdHandler.GetPrefix(gid);
|
||||
var msgToSend = GetText(gid, strs.planted(Format.Bold(user), amount + _gss.Data.Currency.Sign));
|
||||
var prefix = cmdHandler.GetPrefix(gid);
|
||||
var msgToSend = GetText(gid, strs.planted(Format.Bold(user), amount + gss.Data.Currency.Sign));
|
||||
|
||||
if (amount > 1)
|
||||
msgToSend += " " + GetText(gid, strs.pick_pl(prefix));
|
||||
|
@ -337,7 +328,7 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
public async Task<bool> PlantAsync(
|
||||
ulong gid,
|
||||
ITextChannel ch,
|
||||
ulong uid,
|
||||
ulong userId,
|
||||
string user,
|
||||
long amount,
|
||||
string pass)
|
||||
|
@ -349,19 +340,20 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
return false;
|
||||
|
||||
// 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
|
||||
var msgId = await SendPlantMessageAsync(gid, ch, user, amount, pass);
|
||||
if (msgId is null)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
@ -379,43 +371,42 @@ public class PlantPickService : IEService, IExecNoCommand, IReadyExecutor
|
|||
string pass,
|
||||
bool auto = false)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await using var uow = db.GetDbContext();
|
||||
|
||||
PlantedCurrency[] deleted = [];
|
||||
if (!string.IsNullOrWhiteSpace(pass) && auto)
|
||||
{
|
||||
deleted = await uow.GetTable<PlantedCurrency>()
|
||||
.Where(x => x.GuildId == gid
|
||||
&& x.ChannelId == cid
|
||||
&& x.Password != null
|
||||
&& x.Password.Length == pass.Length)
|
||||
.DeleteWithOutputAsync();
|
||||
.Where(x => x.GuildId == gid
|
||||
&& x.ChannelId == cid
|
||||
&& x.Password != null
|
||||
&& x.Password.Length == pass.Length)
|
||||
.DeleteWithOutputAsync();
|
||||
}
|
||||
|
||||
var totalDeletedAmount = deleted.Length == 0 ? 0 : deleted.Sum(x => x.Amount);
|
||||
|
||||
await uow.GetTable<PlantedCurrency>()
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
Amount = totalDeletedAmount + amount,
|
||||
GuildId = gid,
|
||||
ChannelId = cid,
|
||||
Password = pass,
|
||||
UserId = uid,
|
||||
MessageId = mid,
|
||||
});
|
||||
.InsertAsync(() => new()
|
||||
{
|
||||
Amount = totalDeletedAmount + amount,
|
||||
GuildId = gid,
|
||||
ChannelId = cid,
|
||||
Password = pass,
|
||||
UserId = uid,
|
||||
MessageId = mid,
|
||||
});
|
||||
|
||||
return (totalDeletedAmount + amount, deleted.Select(x => x.MessageId).ToArray());
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await using var uow = db.GetDbContext();
|
||||
_generationChannels = (await uow.GetTable<GCChannelId>()
|
||||
.Select(x => x.ChannelId)
|
||||
.ToListAsyncLinqToDB())
|
||||
.Select(x => x.ChannelId)
|
||||
.ToListAsyncLinqToDB())
|
||||
.ToHashSet()
|
||||
.ToConcurrentSet();
|
||||
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using EllieBot.Common.ModuleBehaviors;
|
|||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Gambling.Common;
|
||||
using EllieBot.Modules.Gambling.Common.Waifu;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
|
||||
namespace EllieBot.Modules.Gambling.Services;
|
||||
|
||||
|
@ -15,23 +16,23 @@ public class WaifuService : IEService, IReadyExecutor
|
|||
private readonly ICurrencyService _cs;
|
||||
private readonly IBotCache _cache;
|
||||
private readonly GamblingConfigService _gss;
|
||||
private readonly IBotCreds _creds;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly QuestService _quests;
|
||||
|
||||
public WaifuService(
|
||||
DbService db,
|
||||
ICurrencyService cs,
|
||||
IBotCache cache,
|
||||
GamblingConfigService gss,
|
||||
IBotCreds creds,
|
||||
DiscordSocketClient client)
|
||||
DiscordSocketClient client,
|
||||
QuestService quests)
|
||||
{
|
||||
_db = db;
|
||||
_cs = cs;
|
||||
_cache = cache;
|
||||
_gss = gss;
|
||||
_creds = creds;
|
||||
_client = client;
|
||||
_quests = quests;
|
||||
}
|
||||
|
||||
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);
|
||||
else
|
||||
w.Price += totalValue / 2;
|
||||
|
||||
await _quests.ReportActionAsync(from.Id, QuestEventType.WaifuGiftSent);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ using LinqToDB;
|
|||
using LinqToDB.EntityFrameworkCore;
|
||||
using EllieBot.Modules.Administration;
|
||||
using EllieBot.Modules.Administration.Services;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
|
||||
namespace EllieBot.Modules.Games.Fish;
|
||||
|
||||
|
@ -11,7 +12,8 @@ public sealed class FishService(
|
|||
FishConfigService fcs,
|
||||
IBotCache cache,
|
||||
DbService db,
|
||||
INotifySubscriber notify
|
||||
INotifySubscriber notify,
|
||||
QuestService quests
|
||||
)
|
||||
: 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Modules.Games.Services;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
|
||||
namespace EllieBot.Modules.Games.Hangman;
|
||||
|
||||
|
@ -13,6 +14,7 @@ public sealed class HangmanService : IHangmanService, IExecNoCommand
|
|||
private readonly GamesConfigService _gcs;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly IMemoryCache _cdCache;
|
||||
private readonly QuestService _quests;
|
||||
private readonly object _locker = new();
|
||||
|
||||
public HangmanService(
|
||||
|
@ -20,13 +22,15 @@ public sealed class HangmanService : IHangmanService, IExecNoCommand
|
|||
IMessageSenderService sender,
|
||||
GamesConfigService gcs,
|
||||
ICurrencyService cs,
|
||||
IMemoryCache cdCache)
|
||||
IMemoryCache cdCache,
|
||||
QuestService quests)
|
||||
{
|
||||
_source = source;
|
||||
_sender = sender;
|
||||
_gcs = gcs;
|
||||
_cs = cs;
|
||||
_cdCache = cdCache;
|
||||
_quests = quests;
|
||||
}
|
||||
|
||||
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)
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using LinqToDB.Data;
|
|||
using LinqToDB.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
using SixLabors.ImageSharp.ColorSpaces;
|
||||
using SixLabors.ImageSharp.ColorSpaces.Conversion;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
@ -17,6 +18,7 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
|||
private readonly IBotCache _cache;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly QuestService _quests;
|
||||
|
||||
public const int CANVAS_WIDTH = 500;
|
||||
public const int CANVAS_HEIGHT = 350;
|
||||
|
@ -26,12 +28,14 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
|||
DbService db,
|
||||
IBotCache cache,
|
||||
DiscordSocketClient client,
|
||||
ICurrencyService cs)
|
||||
ICurrencyService cs,
|
||||
QuestService quests)
|
||||
{
|
||||
_db = db;
|
||||
_cache = cache;
|
||||
_client = client;
|
||||
_cs = cs;
|
||||
_quests = quests;
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
|
@ -59,23 +63,23 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
|||
}
|
||||
|
||||
await uow.GetTable<NCPixel>()
|
||||
.BulkCopyAsync(toAdd.Select(x =>
|
||||
{
|
||||
var clr = ColorSpaceConverter.ToRgb(new Hsv(((float)Random.Shared.NextDouble() * 360),
|
||||
(float)(0.5 + (Random.Shared.NextDouble() * 0.49)),
|
||||
(float)(0.4 + (Random.Shared.NextDouble() / 5 + (x % 100 * 0.2)))))
|
||||
.ToVector3();
|
||||
.BulkCopyAsync(toAdd.Select(x =>
|
||||
{
|
||||
var clr = ColorSpaceConverter.ToRgb(new Hsv(((float)Random.Shared.NextDouble() * 360),
|
||||
(float)(0.5 + (Random.Shared.NextDouble() * 0.49)),
|
||||
(float)(0.4 + (Random.Shared.NextDouble() / 5 + (x % 100 * 0.2)))))
|
||||
.ToVector3();
|
||||
|
||||
var packed = new Rgba32(clr).PackedValue;
|
||||
return new NCPixel()
|
||||
{
|
||||
Color = packed,
|
||||
Price = 1,
|
||||
Position = x,
|
||||
Text = "",
|
||||
OwnerId = 0
|
||||
};
|
||||
}));
|
||||
var packed = new Rgba32(clr).PackedValue;
|
||||
return new NCPixel()
|
||||
{
|
||||
Color = packed,
|
||||
Price = 1,
|
||||
Position = x,
|
||||
Text = "",
|
||||
OwnerId = 0
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
@ -83,9 +87,9 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
|||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var colors = await uow.GetTable<NCPixel>()
|
||||
.OrderBy(x => x.Position)
|
||||
.Select(x => x.Color)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
.OrderBy(x => x.Position)
|
||||
.Select(x => x.Color)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
@ -121,15 +125,15 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
|||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var updates = await uow.GetTable<NCPixel>()
|
||||
.Where(x => x.Position == position && x.Price <= price)
|
||||
.UpdateAsync(old => new NCPixel()
|
||||
{
|
||||
Position = position,
|
||||
Color = color,
|
||||
Text = text,
|
||||
OwnerId = userId,
|
||||
Price = price + 1
|
||||
});
|
||||
.Where(x => x.Position == position && x.Price <= price)
|
||||
.UpdateAsync(old => new NCPixel()
|
||||
{
|
||||
Position = position,
|
||||
Color = color,
|
||||
Text = text,
|
||||
OwnerId = userId,
|
||||
Price = price + 1
|
||||
});
|
||||
success = updates > 0;
|
||||
}
|
||||
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"));
|
||||
}
|
||||
else
|
||||
{
|
||||
await _quests.ReportActionAsync(userId, QuestEventType.PixelSet);
|
||||
}
|
||||
|
||||
return success ? SetPixelResult.Success : SetPixelResult.InsufficientPayment;
|
||||
}
|
||||
|
@ -152,14 +160,14 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
|||
await using var uow = _db.GetDbContext();
|
||||
await uow.GetTable<NCPixel>().DeleteAsync();
|
||||
await uow.GetTable<NCPixel>()
|
||||
.BulkCopyAsync(colors.Select((x, i) => new NCPixel()
|
||||
{
|
||||
Color = x,
|
||||
Price = INITIAL_PRICE,
|
||||
Position = i,
|
||||
Text = "",
|
||||
OwnerId = 0
|
||||
}));
|
||||
.BulkCopyAsync(colors.Select((x, i) => new NCPixel()
|
||||
{
|
||||
Color = x,
|
||||
Price = INITIAL_PRICE,
|
||||
Position = i,
|
||||
Text = "",
|
||||
OwnerId = 0
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -190,12 +198,12 @@ public sealed class NCanvasService : INCanvasService, IReadyExecutor, IEService
|
|||
|
||||
await using var uow = _db.GetDbContext();
|
||||
return await uow.GetTable<NCPixel>()
|
||||
.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)
|
||||
.OrderBy(x => x.Position)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
.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)
|
||||
.OrderBy(x => x.Position)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private static CancellationTokenSource _cts = null;
|
||||
private static CancellationTokenSource? _cts = null;
|
||||
|
||||
[Cmd]
|
||||
public async Task MassPing()
|
||||
|
@ -22,6 +22,8 @@ public class Owner(VoteRewardService vrs) : EllieModule
|
|||
await t.CancelAsync();
|
||||
}
|
||||
|
||||
_cts = new();
|
||||
|
||||
try
|
||||
{
|
||||
var users = await ctx.Guild.GetUsersAsync().Fmap(u => u.Where(x => !x.IsBot).ToArray());
|
||||
|
|
|
@ -38,7 +38,7 @@ public interface ICurrencyService
|
|||
IUser user,
|
||||
long amount,
|
||||
TxData? txData);
|
||||
|
||||
|
||||
Task<IReadOnlyList<DiscordUser>> GetTopRichest(ulong ignoreId, int page = 0, int perPage = 9);
|
||||
|
||||
Task<IReadOnlyList<CurrencyTransaction>> GetTransactionsAsync(
|
||||
|
@ -47,4 +47,14 @@ public interface ICurrencyService
|
|||
int perPage = 15);
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
@ -53,16 +44,16 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
|||
{
|
||||
if (type == CurrencyType.Default)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await using var ctx = db.GetDbContext();
|
||||
await ctx
|
||||
.GetTable<DiscordUser>()
|
||||
.Where(x => userIds.Contains(x.UserId))
|
||||
.UpdateAsync(du => new()
|
||||
{
|
||||
CurrencyAmount = du.CurrencyAmount >= amount
|
||||
? du.CurrencyAmount - amount
|
||||
: 0
|
||||
});
|
||||
.GetTable<DiscordUser>()
|
||||
.Where(x => userIds.Contains(x.UserId))
|
||||
.UpdateAsync(du => new()
|
||||
{
|
||||
CurrencyAmount = du.CurrencyAmount >= amount
|
||||
? du.CurrencyAmount - amount
|
||||
: 0
|
||||
});
|
||||
await ctx.SaveChangesAsync();
|
||||
return;
|
||||
}
|
||||
|
@ -77,7 +68,7 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
|||
{
|
||||
var wallet = await GetWalletAsync(userId);
|
||||
await wallet.Add(amount, txData);
|
||||
await _txTracker.TrackAdd(userId, amount, txData);
|
||||
await txTracker.TrackAdd(userId, amount, txData);
|
||||
}
|
||||
|
||||
public async Task AddAsync(
|
||||
|
@ -97,7 +88,7 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
|||
var wallet = await GetWalletAsync(userId);
|
||||
var result = await wallet.Take(amount, txData);
|
||||
if (result)
|
||||
await _txTracker.TrackRemove(userId, amount, txData);
|
||||
await txTracker.TrackRemove(userId, amount, txData);
|
||||
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)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await using var uow = db.GetDbContext();
|
||||
return await uow.Set<DiscordUser>().GetTopRichest(ignoreId, page, perPage);
|
||||
}
|
||||
|
||||
|
@ -118,23 +109,63 @@ public sealed class CurrencyService : ICurrencyService, IEService
|
|||
int page,
|
||||
int perPage = 15)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await using var uow = db.GetDbContext();
|
||||
|
||||
var trs = await uow.GetTable<CurrencyTransaction>()
|
||||
.Where(x => x.UserId == userId)
|
||||
.OrderByDescending(x => x.DateAdded)
|
||||
.Skip(perPage * page)
|
||||
.Take(perPage)
|
||||
.ToListAsyncLinqToDB();
|
||||
.Where(x => x.UserId == userId)
|
||||
.OrderByDescending(x => x.DateAdded)
|
||||
.Skip(perPage * page)
|
||||
.Take(perPage)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
return trs;
|
||||
}
|
||||
|
||||
public async Task<int> GetTransactionsCountAsync(ulong userId)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await using var uow = db.GetDbContext();
|
||||
return await uow.GetTable<CurrencyTransaction>()
|
||||
.Where(x => x.UserId == userId)
|
||||
.CountAsyncLinqToDB();
|
||||
.Where(x => x.UserId == userId)
|
||||
.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 async Task<long> GetBalanceAsync(this ICurrencyService cs, ulong userId)
|
||||
{
|
||||
var wallet = await cs.GetWalletAsync(userId);
|
||||
return await wallet.GetBalance();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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 EllieBot.Modules.Administration;
|
||||
using EllieBot.Modules.Gambling.Services;
|
||||
using EllieBot.Modules.Games.Quests;
|
||||
|
||||
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[]
|
||||
{
|
||||
|
@ -21,17 +26,6 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
private NonBlocking.ConcurrentDictionary<string, (decimal Bet, decimal PaidOut)> globalStats = 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()
|
||||
=> await Task.WhenAll(RunUserStatsCollector(), RunBetStatsCollector());
|
||||
|
||||
|
@ -40,7 +34,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
|
||||
while (await timer.WaitForNextTickAsync())
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await using var ctx = db.GetDbContext();
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -51,22 +45,22 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
if (globalStats.TryRemove(key, out var stat))
|
||||
{
|
||||
await ctx.GetTable<GamblingStats>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
Feature = key,
|
||||
Bet = stat.Bet,
|
||||
PaidOut = stat.PaidOut,
|
||||
DateAdded = DateTime.UtcNow
|
||||
},
|
||||
old => new()
|
||||
{
|
||||
Bet = old.Bet + stat.Bet,
|
||||
PaidOut = old.PaidOut + stat.PaidOut,
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
Feature = key
|
||||
});
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
Feature = key,
|
||||
Bet = stat.Bet,
|
||||
PaidOut = stat.PaidOut,
|
||||
DateAdded = DateTime.UtcNow
|
||||
},
|
||||
old => new()
|
||||
{
|
||||
Bet = old.Bet + stat.Bet,
|
||||
PaidOut = old.PaidOut + stat.PaidOut,
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
Feature = key
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,68 +94,68 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
|
||||
// update userstats
|
||||
foreach (var (k, x) in users.GroupBy(x => (x.UserId, x.Game))
|
||||
.ToDictionary(x => x.Key,
|
||||
x => x.Aggregate((a, b) => new()
|
||||
{
|
||||
WinCount = a.WinCount + b.WinCount,
|
||||
LoseCount = a.LoseCount + b.LoseCount,
|
||||
TotalBet = a.TotalBet + b.TotalBet,
|
||||
PaidOut = a.PaidOut + b.PaidOut,
|
||||
MaxBet = Math.Max(a.MaxBet, b.MaxBet),
|
||||
MaxWin = Math.Max(a.MaxWin, b.MaxWin),
|
||||
})))
|
||||
.ToDictionary(x => x.Key,
|
||||
x => x.Aggregate((a, b) => new()
|
||||
{
|
||||
WinCount = a.WinCount + b.WinCount,
|
||||
LoseCount = a.LoseCount + b.LoseCount,
|
||||
TotalBet = a.TotalBet + b.TotalBet,
|
||||
PaidOut = a.PaidOut + b.PaidOut,
|
||||
MaxBet = Math.Max(a.MaxBet, b.MaxBet),
|
||||
MaxWin = Math.Max(a.MaxWin, b.MaxWin),
|
||||
})))
|
||||
{
|
||||
rakebacks.TryAdd(k.UserId, 0m);
|
||||
rakebacks[k.UserId] += x.TotalBet * GetHouseEdge(k.Game) * BASE_RAKEBACK;
|
||||
|
||||
|
||||
// bulk upsert in the future
|
||||
await using var uow = _db.GetDbContext();
|
||||
await using var uow = db.GetDbContext();
|
||||
await uow.GetTable<UserBetStats>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
UserId = k.UserId,
|
||||
Game = k.Game,
|
||||
WinCount = x.WinCount,
|
||||
LoseCount = Math.Max(0, x.LoseCount),
|
||||
TotalBet = x.TotalBet,
|
||||
PaidOut = x.PaidOut,
|
||||
MaxBet = x.MaxBet,
|
||||
MaxWin = x.MaxWin
|
||||
},
|
||||
o => new()
|
||||
{
|
||||
WinCount = o.WinCount + x.WinCount,
|
||||
LoseCount = Math.Max(0, o.LoseCount + x.LoseCount),
|
||||
TotalBet = o.TotalBet + x.TotalBet,
|
||||
PaidOut = o.PaidOut + x.PaidOut,
|
||||
MaxBet = Math.Max(o.MaxBet, x.MaxBet),
|
||||
MaxWin = Math.Max(o.MaxWin, x.MaxWin),
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
UserId = k.UserId,
|
||||
Game = k.Game
|
||||
});
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
UserId = k.UserId,
|
||||
Game = k.Game,
|
||||
WinCount = x.WinCount,
|
||||
LoseCount = Math.Max(0, x.LoseCount),
|
||||
TotalBet = x.TotalBet,
|
||||
PaidOut = x.PaidOut,
|
||||
MaxBet = x.MaxBet,
|
||||
MaxWin = x.MaxWin
|
||||
},
|
||||
o => new()
|
||||
{
|
||||
WinCount = o.WinCount + x.WinCount,
|
||||
LoseCount = Math.Max(0, o.LoseCount + x.LoseCount),
|
||||
TotalBet = o.TotalBet + x.TotalBet,
|
||||
PaidOut = o.PaidOut + x.PaidOut,
|
||||
MaxBet = Math.Max(o.MaxBet, x.MaxBet),
|
||||
MaxWin = Math.Max(o.MaxWin, x.MaxWin),
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
UserId = k.UserId,
|
||||
Game = k.Game
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var (k, v) in rakebacks)
|
||||
{
|
||||
await _db.GetDbContext()
|
||||
.GetTable<Rakeback>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
UserId = k,
|
||||
Amount = v
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
Amount = old.Amount + v
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
UserId = k
|
||||
});
|
||||
await db.GetDbContext()
|
||||
.GetTable<Rakeback>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
UserId = k,
|
||||
Amount = v
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
Amount = old.Amount + v
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
UserId = k
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -173,10 +167,10 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
|
||||
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)
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
|
||||
if (_gamblingTypes.Contains(txData.Type))
|
||||
{
|
||||
|
@ -188,7 +182,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
var mType = GetGameType(txData.Type);
|
||||
|
||||
if (mType is not { } type)
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
|
||||
// var bigWin = _gcs.Data.BigWin;
|
||||
// if (bigWin > 0 && amount >= bigWin)
|
||||
|
@ -211,7 +205,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
MaxBet = 0,
|
||||
MaxWin = amount,
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (txData.Type == "animalrace")
|
||||
|
@ -230,7 +224,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
MaxWin = 0,
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,14 +239,12 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
MaxBet = 0,
|
||||
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)
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
|
||||
if (_gamblingTypes.Contains(txData.Type))
|
||||
{
|
||||
|
@ -264,7 +256,7 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
var mType = GetGameType(txData.Type);
|
||||
|
||||
if (mType is not { } type)
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
|
||||
userStats.Add(new UserBetStats()
|
||||
{
|
||||
|
@ -278,7 +270,14 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
MaxWin = 0
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
await quests.ReportActionAsync(userId,
|
||||
QuestEventType.BetPlaced,
|
||||
new()
|
||||
{
|
||||
{ "type", txData.Type },
|
||||
{ "amount", amount.ToString() }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static GamblingGame? GetGameType(string game)
|
||||
|
@ -296,26 +295,26 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
|
||||
public async Task<IReadOnlyCollection<GamblingStats>> GetAllAsync()
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await using var ctx = db.GetDbContext();
|
||||
return await ctx.Set<GamblingStats>()
|
||||
.ToListAsyncEF();
|
||||
.ToListAsyncEF();
|
||||
}
|
||||
|
||||
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)
|
||||
return await ctx
|
||||
.GetTable<UserBetStats>()
|
||||
.Where(x => x.UserId == userId)
|
||||
.ToListAsync();
|
||||
.GetTable<UserBetStats>()
|
||||
.Where(x => x.UserId == userId)
|
||||
.ToListAsync();
|
||||
|
||||
return await ctx
|
||||
.GetTable<UserBetStats>()
|
||||
.Where(x => x.UserId == userId && x.Game == game)
|
||||
.ToListAsync();
|
||||
.GetTable<UserBetStats>()
|
||||
.Where(x => x.UserId == userId && x.Game == game)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public decimal GetHouseEdge(GamblingGame game)
|
||||
|
@ -330,8 +329,6 @@ public sealed class GamblingTxTracker : ITxTracker, IEService, IReadyExecutor
|
|||
GamblingGame.Race => 0.06m,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
public sealed class UserBetStats
|
||||
|
|
|
@ -374,7 +374,6 @@ quoteadd:
|
|||
- qa
|
||||
- qadd
|
||||
- quadd
|
||||
- .
|
||||
quoteedit:
|
||||
- quoteedit
|
||||
- qe
|
||||
|
@ -384,7 +383,6 @@ quoteprint:
|
|||
- quoteprint
|
||||
- qp
|
||||
- qup
|
||||
- ..
|
||||
- qprint
|
||||
quoteshow:
|
||||
- quoteshow
|
||||
|
@ -1658,4 +1656,8 @@ votefeed:
|
|||
vote:
|
||||
- vote
|
||||
massping:
|
||||
- massping
|
||||
- massping
|
||||
questlog:
|
||||
- questlog
|
||||
- qlog
|
||||
- myquests
|
|
@ -5206,5 +5206,12 @@ massping:
|
|||
Run again to cancel.
|
||||
ex:
|
||||
- ''
|
||||
params:
|
||||
- { }
|
||||
questlog:
|
||||
desc: |-
|
||||
Shows your active quests and progress.
|
||||
ex:
|
||||
- ''
|
||||
params:
|
||||
- { }
|
|
@ -1244,5 +1244,9 @@
|
|||
"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_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