refactor(pack): migrate PackController to IGameCalendarService
Swaps all four DateTime.UtcNow.Date same-day checks in PackController for _calendar.ResetReady() — daily-single open, /pack/info daily-single gate, FreePacks /pack/info visibility, FreePacks /pack/open quota. Removes the TODO(daily-reset) comments; ToDto promoted from static to instance so it can see _calendar. Also fills in GameCalendarConfig.ShippedDefaults() (required by the [ConfigSection] discovery in SVSimDbContext.EnsureSeedDataAsync — bootstrap throws without it). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@ public class PackController : SVSimController
|
||||
private readonly SVSimDbContext _db;
|
||||
private readonly IInventoryService _inv;
|
||||
private readonly IGachaPointService _gachaPoint;
|
||||
private readonly IGameCalendarService _calendar;
|
||||
|
||||
public PackController(
|
||||
IPackRepository packs,
|
||||
@@ -43,7 +44,8 @@ public class PackController : SVSimController
|
||||
IRandom rng,
|
||||
SVSimDbContext db,
|
||||
IInventoryService inv,
|
||||
IGachaPointService gachaPoint)
|
||||
IGachaPointService gachaPoint,
|
||||
IGameCalendarService calendar)
|
||||
{
|
||||
_packs = packs;
|
||||
_opener = opener;
|
||||
@@ -53,6 +55,7 @@ public class PackController : SVSimController
|
||||
_db = db;
|
||||
_inv = inv;
|
||||
_gachaPoint = gachaPoint;
|
||||
_calendar = calendar;
|
||||
}
|
||||
|
||||
[HttpPost("info")]
|
||||
@@ -120,7 +123,7 @@ public class PackController : SVSimController
|
||||
};
|
||||
}
|
||||
|
||||
private static PackConfigDto ToDto(
|
||||
private PackConfigDto ToDto(
|
||||
PackConfigEntry p,
|
||||
IReadOnlyDictionary<int, ViewerPackOpenCount> openCounts,
|
||||
IReadOnlyDictionary<long, int> ownedItemsByItemId,
|
||||
@@ -137,21 +140,18 @@ public class PackController : SVSimController
|
||||
// stays visible after the first successful open and a second click 400s with
|
||||
// `daily_free_already_claimed`. Keep the child entry itself so the CRYSTAL_MULTI
|
||||
// full-price button still activates.
|
||||
// TODO(daily-reset): using UTC midnight; migrate to the JST-boundary DailyReset service
|
||||
// when it lands (see docs/plans/daily-reset-service.md).
|
||||
var today = DateTime.UtcNow.Date;
|
||||
bool dailyClaimedToday = oc?.LastDailyFreeAt is DateTime lastDaily && lastDaily.Date == today;
|
||||
bool dailyClaimedToday = !_calendar.ResetReady(oc?.LastDailyFreeAt);
|
||||
|
||||
// Drop type_detail=10 (FREE_PACKS) children whose daily quota for THIS viewer is spent.
|
||||
// Mirrors prod behavior: post-claim /pack/info simply omits the free child from
|
||||
// child_gacha_info (verified in traffic_event_crate_free_pack.ndjson lines 28→32).
|
||||
// Today's claim count >= DailyFreeGachaCount and same UTC date => hide.
|
||||
// Reset happens at the daily boundary — new day resets ClaimCount effectively.
|
||||
bool ChildAvailable(PackChildGachaEntry c)
|
||||
{
|
||||
if (c.TypeDetail != CardPackType.FreePacks) return true;
|
||||
if (c.FreeGachaCampaignId is not int campaignId) return true;
|
||||
if (!freeClaimsByCampaignId.TryGetValue(campaignId, out var claim)) return true;
|
||||
if (claim.LastClaimedAt.Date != today) return true;
|
||||
if (_calendar.ResetReady(claim.LastClaimedAt)) return true;
|
||||
int dailyCap = c.DailyFreeGachaCount > 0 ? c.DailyFreeGachaCount : 1;
|
||||
return claim.ClaimCount < dailyCap;
|
||||
}
|
||||
@@ -461,11 +461,8 @@ public class PackController : SVSimController
|
||||
{
|
||||
// DAILY is the once-per-day half-off crystal single-pack (GachaUI.cs:1046 →
|
||||
// CheckBuyPackWithCrystal, decompile-confirmed). Currency is Crystal, NOT Rupee.
|
||||
// TODO(daily-reset): using UTC midnight; migrate to the JST-boundary
|
||||
// DailyReset service when it lands (see docs/plans/daily-reset-service.md).
|
||||
var now = DateTime.UtcNow;
|
||||
var existing = viewer.PackOpenCounts.FirstOrDefault(p => p.PackId == pack.Id);
|
||||
if (existing?.LastDailyFreeAt is DateTime last && last.Date == now.Date)
|
||||
if (!_calendar.ResetReady(existing?.LastDailyFreeAt))
|
||||
return BadRequest(new { error = "daily_free_already_claimed" });
|
||||
|
||||
long cost = (long)child.Cost * packNumber;
|
||||
@@ -490,9 +487,9 @@ public class PackController : SVSimController
|
||||
return StatusCode(StatusCodes.Status501NotImplemented, new { error = "free_pack_missing_campaign_id" });
|
||||
|
||||
int dailyCap = child.DailyFreeGachaCount > 0 ? child.DailyFreeGachaCount : 1;
|
||||
var today = DateTime.UtcNow.Date;
|
||||
var existing = viewer.FreePackClaims.FirstOrDefault(c => c.FreeGachaCampaignId == campaignId);
|
||||
if (existing is not null && existing.LastClaimedAt.Date == today && existing.ClaimCount >= dailyCap)
|
||||
bool resetSinceLastClaim = existing is null || _calendar.ResetReady(existing.LastClaimedAt);
|
||||
if (existing is not null && !resetSinceLastClaim && existing.ClaimCount >= dailyCap)
|
||||
return BadRequest(new { error = "free_pack_already_claimed_today" });
|
||||
|
||||
// pack_number is forced to 1 — free-pack metadata never authorizes multi-opens.
|
||||
@@ -508,7 +505,7 @@ public class PackController : SVSimController
|
||||
LastClaimedAt = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
else if (existing.LastClaimedAt.Date != today)
|
||||
else if (resetSinceLastClaim)
|
||||
{
|
||||
existing.ClaimCount = 1;
|
||||
existing.LastClaimedAt = DateTime.UtcNow;
|
||||
|
||||
Reference in New Issue
Block a user