diff --git a/SVSim.Database/Models/Config/BattleXpConfig.cs b/SVSim.Database/Models/Config/BattleXpConfig.cs new file mode 100644 index 00000000..0a83c413 --- /dev/null +++ b/SVSim.Database/Models/Config/BattleXpConfig.cs @@ -0,0 +1,28 @@ +namespace SVSim.Database.Models.Config; + +/// +/// Class XP awarded on battle finish. / +/// are the global defaults; each per-mode nullable slot overrides them for that mode +/// when set. Story is clear-only (no loss variant) — +/// falls back to when null. +/// +[ConfigSection("BattleXp")] +public class BattleXpConfig +{ + public int XpPerWin { get; set; } = 100; + public int XpPerLoss { get; set; } = 25; + + public int? PracticeXpPerWin { get; set; } + public int? PracticeXpPerLoss { get; set; } + public int? RankXpPerWin { get; set; } + public int? RankXpPerLoss { get; set; } + public int? FreeXpPerWin { get; set; } + public int? FreeXpPerLoss { get; set; } + public int? ArenaTwoPickXpPerWin { get; set; } + public int? ArenaTwoPickXpPerLoss { get; set; } + public int? ColosseumXpPerWin { get; set; } + public int? ColosseumXpPerLoss { get; set; } + public int? StoryXpPerClear { get; set; } + + public static BattleXpConfig ShippedDefaults() => new(); +} diff --git a/SVSim.Database/Repositories/Viewer/IViewerRepository.cs b/SVSim.Database/Repositories/Viewer/IViewerRepository.cs index da2e98ca..ea93d8ad 100644 --- a/SVSim.Database/Repositories/Viewer/IViewerRepository.cs +++ b/SVSim.Database/Repositories/Viewer/IViewerRepository.cs @@ -51,6 +51,14 @@ public interface IViewerRepository /// Task LoadForMatchContextAsync(long viewerId); + /// + /// Focused load for class-XP grants: viewer with owned Classes collection and + /// each ViewerClassData.Class nav ref included. Tracked (not AsNoTracking) so + /// the caller can mutate Exp/Level and SaveChangesAsync. Returns + /// null if the viewer does not exist. + /// + Task LoadForBattleXpGrantAsync(long viewerId, CancellationToken ct = default); + /// Sets Viewer.GuildId to . No-op if the viewer does not exist. Task SetGuildIdAsync(long viewerId, int guildId, CancellationToken ct = default); diff --git a/SVSim.Database/Repositories/Viewer/ViewerRepository.cs b/SVSim.Database/Repositories/Viewer/ViewerRepository.cs index 9b7b4e24..ed06edef 100644 --- a/SVSim.Database/Repositories/Viewer/ViewerRepository.cs +++ b/SVSim.Database/Repositories/Viewer/ViewerRepository.cs @@ -273,6 +273,14 @@ public class ViewerRepository : IViewerRepository .FirstOrDefaultAsync(v => v.Id == viewerId); } + public Task LoadForBattleXpGrantAsync(long viewerId, CancellationToken ct = default) + { + return _dbContext.Set() + .Include(v => v.Classes) + .ThenInclude(c => c.Class) + .FirstOrDefaultAsync(v => v.Id == viewerId, ct); + } + public async Task SetGuildIdAsync(long viewerId, int guildId, CancellationToken ct = default) { var viewer = await _dbContext.Set() diff --git a/SVSim.Database/Services/BattleXp/BattleXpMode.cs b/SVSim.Database/Services/BattleXp/BattleXpMode.cs new file mode 100644 index 00000000..5ab2c06d --- /dev/null +++ b/SVSim.Database/Services/BattleXp/BattleXpMode.cs @@ -0,0 +1,11 @@ +namespace SVSim.Database.Services.BattleXp; + +public enum BattleXpMode +{ + Practice, + Rank, + Free, + ArenaTwoPick, + Colosseum, + Story, +}