Pack logic cleanup

This commit is contained in:
gamer147
2026-05-24 09:27:10 -04:00
parent 79209bd70b
commit d9ef9fe1fc
33 changed files with 71175 additions and 245 deletions

View File

@@ -0,0 +1,10 @@
using Microsoft.EntityFrameworkCore;
namespace SVSim.Database.Models.Config;
[Owned]
public class ChallengeConfig
{
public bool UseTwoPickPremiumCard { get; set; }
public long TwoPickSleeveId { get; set; }
}

View File

@@ -0,0 +1,12 @@
using Microsoft.EntityFrameworkCore;
namespace SVSim.Database.Models.Config;
/// <summary>Per-viewer-registration default currency grants.</summary>
[Owned]
public class DefaultGrantsConfig
{
public ulong Crystals { get; set; } = 50000;
public ulong Rupees { get; set; } = 50000;
public ulong Ether { get; set; } = 50000;
}

View File

@@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
namespace SVSim.Database.Models.Config;
/// <summary>
/// Default cosmetic loadout ids for a newly-registered viewer. These used to be FK columns;
/// they're now untyped longs in the jsonb tree. Validation would live in a future config-editing
/// UI (see project-wide TODO(config-validation)).
/// </summary>
[Owned]
public class DefaultLoadoutConfig
{
public int DegreeId { get; set; } = 300003;
public int EmblemId { get; set; } = 100000000;
public int MyPageBackgroundId { get; set; } = 100000000;
public int SleeveId { get; set; } = 3000011;
}

View File

@@ -0,0 +1,47 @@
using Microsoft.EntityFrameworkCore;
namespace SVSim.Database.Models.Config;
/// <summary>
/// Tunables for pack-opening RNG. Defaults reproduce the original Shadowverse Classic rates
/// exactly so the cutover from hardcoded magic numbers is zero-behavior-change.
/// </summary>
[Owned]
public class PackRateConfig
{
/// <summary>
/// Per-card-slot probability of upgrading any drawn card to its foil/animated twin.
/// Applied AFTER rarity selection — independent of rarity, slot position, and pack category.
/// Default 0.08 (8%). Cards without a foil twin in master data keep the non-foil silently.
/// </summary>
public double AnimatedRate { get; set; } = 0.08;
/// <summary>
/// Global default rarity weights, used for any slot that has no entry in
/// <see cref="PerSlot"/>. Defaults match SV Classic main-slot. Weights sum to 0.9994;
/// the 0.06% slack absorbs into Bronze via the PickRarity catch-all band.
/// </summary>
public SlotRarityWeights Default { get; set; } = new()
{
Bronze = 0.6744, Silver = 0.25, Gold = 0.06, Legendary = 0.015,
};
/// <summary>
/// Per-slot overrides (1-based slot index) applied to all packs. A missing slot falls back
/// to <see cref="Default"/>. Each entry is a FULL OVERRIDE, not a delta — if you change
/// <see cref="Default"/>, existing PerSlot entries do NOT auto-recompute. The slot-8 default
/// expresses the SV Classic "Silver-or-better guarantee" as data (Bronze=0) instead of a
/// separate code path.
/// </summary>
/// <summary>
/// Per-slot overrides keyed by 1-based slot index (stored as a list for EF Core 8 json
/// compatibility — Dictionary&lt;string,T&gt; of complex owned types is not supported).
/// Look up by <see cref="SlotRarityWeights.Slot"/>. A missing slot falls back to
/// <see cref="Default"/>. Slot-8 entry expresses the SV Classic "Silver-or-better
/// guarantee" as data (Bronze=0).
/// </summary>
public List<SlotRarityWeights> PerSlot { get; set; } =
[
new() { Slot = "8", Bronze = 0, Silver = 0.7692, Gold = 0.1846, Legendary = 0.0462 },
];
}

View File

@@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
namespace SVSim.Database.Models.Config;
[Owned]
public class PlayerConfig
{
public int MaxFriends { get; set; } = 20;
}

View File

@@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
namespace SVSim.Database.Models.Config;
/// <summary>
/// Time-varying season/rotation state, populated by GlobalsImporter from prod captures.
/// </summary>
[Owned]
public class RotationConfig
{
public string TsRotationId { get; set; } = "";
public bool IsBattlePassPeriod { get; set; }
public bool IsBeginnerMission { get; set; }
public int CardSetIdForResourceDlView { get; set; }
}

View File

@@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore;
namespace SVSim.Database.Models.Config;
/// <summary>
/// Per-rarity weights for a single pack slot. Sum should be ≤ 1.0;
/// remainder absorbs into Bronze via the PickRarity catch-all band.
/// <para>
/// <see cref="Slot"/> is the 1-based slot index as a string (e.g. "8") and is used as the
/// lookup key in <see cref="PackRateConfig.PerSlot"/>. It is empty/null for the global
/// <see cref="PackRateConfig.Default"/> entry, which has no slot affiliation.
/// </para>
/// </summary>
[Owned]
public class SlotRarityWeights
{
/// <summary>1-based slot index (as a string) for entries in PerSlot. Null/empty for the Default entry.</summary>
public string? Slot { get; set; }
public double Bronze { get; set; }
public double Silver { get; set; }
public double Gold { get; set; }
public double Legendary { get; set; }
}

View File

@@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore;
using SVSim.Database.Models.Config;
namespace SVSim.Database.Models;
/// <summary>
/// The root of <see cref="GameConfiguration.Config"/>, stored as a single jsonb column.
/// Each sub-object defaults to its own initialiser, so `new GameConfigRoot()` is fully populated
/// with the canonical SV Classic / DCGEngine defaults.
/// </summary>
[Owned]
public class GameConfigRoot
{
public DefaultGrantsConfig DefaultGrants { get; set; } = new();
public PlayerConfig Player { get; set; } = new();
public DefaultLoadoutConfig DefaultLoadout { get; set; } = new();
public ChallengeConfig Challenge { get; set; } = new();
public RotationConfig Rotation { get; set; } = new();
public PackRateConfig PackRates { get; set; } = new();
}

View File

@@ -2,56 +2,13 @@ using SVSim.Database.Common;
namespace SVSim.Database.Models;
/// <summary>
/// Server-wide tunable config and captured-from-prod state. Singleton (Id = "default") with
/// all data living in a single typed jsonb column. See <see cref="GameConfigRoot"/> for the
/// schema. Pre-refactor this entity had ~14 flat columns plus 4 FK navs — see migration
/// `RefactorGameConfigurationToJsonb` for the cutover.
/// </summary>
public class GameConfiguration : BaseEntity<string>
{
public ulong DefaultCrystals { get; set; }
public ulong DefaultRupees { get; set; }
public ulong DefaultEther { get; set; }
public int MaxFriends { get; set; }
#region Time-varying globals populated by SVSim.Bootstrap.GlobalsImporter
/// <summary>Current "Take Two Special" rotation ID, e.g. "10015". Points into MyRotationSettingEntry.</summary>
public string TsRotationId { get; set; } = string.Empty;
public bool ChallengeUseTwoPickPremiumCard { get; set; }
public long ChallengeTwoPickSleeveId { get; set; }
/// <summary>
/// Bool on the wire (prod sends true/false); local previously sent int. Fixes the
/// type-mismatch noted in seed-data-strategy-2026-05-23.md crash audit.
/// </summary>
public bool IsBattlePassPeriod { get; set; }
public bool IsBeginnerMission { get; set; }
public int CardSetIdForResourceDlView { get; set; }
#endregion
#region Foreign Keys
public int DefaultDegreeId { get; set; }
public int DefaultEmblemId { get; set; }
public int DefaultMyPageBackgroundId { get; set; }
public int DefaultSleeveId { get; set; }
#endregion
#region Navigation Properties
public DegreeEntry DefaultDegree { get; set; } = new DegreeEntry();
public EmblemEntry DefaultEmblem { get; set; } = new EmblemEntry();
public MyPageBackgroundEntry DefaultMyPageBackground { get; set; } = new MyPageBackgroundEntry();
public SleeveEntry DefaultSleeve { get; set; } = new SleeveEntry();
#endregion
public GameConfigRoot Config { get; set; } = new();
}

View File

@@ -32,6 +32,14 @@ public class ShadowverseCardEntry : BaseEntity<long>
/// </summary>
public Rarity Rarity { get; set; }
/// <summary>
/// True for foil/animated card rows (cards.json `is_foil=1`). Foils live in the same
/// CardSet as their non-foil twin (twin's card_id = this.Id - 1). Excluded from pack
/// draw pools by DbCardPoolProvider; reached via the per-card animated-upgrade roll
/// in PackOpenService.
/// </summary>
public bool IsFoil { get; set; }
#region Owned
/// <summary>