Files
SVSimServer/SVSim.EmulatedEntrypoint/Controllers/PracticeController.cs

98 lines
3.9 KiB
C#

using Microsoft.AspNetCore.Mvc;
using SVSim.Database.Enums;
using SVSim.EmulatedEntrypoint.Models.Dtos;
using SVSim.Database.Repositories.Deck;
using SVSim.Database.Repositories.Globals;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Common;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Practice;
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Practice;
namespace SVSim.EmulatedEntrypoint.Controllers;
public class PracticeController : SVSimController
{
private readonly IDeckRepository _deckRepository;
private readonly IGlobalsRepository _globalsRepository;
public PracticeController(IDeckRepository deckRepository, IGlobalsRepository globalsRepository)
{
_deckRepository = deckRepository;
_globalsRepository = globalsRepository;
}
/// <summary>
/// /practice/info — returns the AI opponent catalog. Response data is a JSON array
/// directly (not wrapped in an object), per spec. Backed by PracticeOpponents table,
/// seeded by SVSim.Bootstrap from seeds/practice-opponents.json.
/// </summary>
[HttpPost("info")]
public async Task<List<PracticeOpponent>> Info(BaseRequest request)
{
var rows = await _globalsRepository.GetPracticeOpponents();
return rows.Select(e => new PracticeOpponent
{
PracticeId = e.PracticeId,
TextId = e.TextId,
ClassId = e.ClassId,
CharaId = e.CharaId,
DegreeId = e.DegreeId,
AiDeckLevel = e.AiDeckLevel,
AiLogicLevel = e.AiLogicLevel,
AiMaxLife = e.AiMaxLife,
Battle3dFieldId = e.Battle3dFieldId,
IsMaintenance = e.IsMaintenance,
IsCampaignPractice = e.IsCampaignPractice,
}).ToList();
}
/// <summary>
/// /practice/deck_list — returns viewer's decks scoped by format (always Format.All
/// per spec, server can ignore the request field). Fetched via IDeckRepository so the
/// DeckCard.Card navigation is Included; going through the heavier viewer-graph query
/// drops that ThenInclude and ships 40 zeros instead of real card ids, which then
/// NREs the client's SBattleLoad.InitPlayer (CardCreator returns null on id=0).
/// </summary>
[HttpPost("deck_list")]
public async Task<ActionResult<PracticeDeckListResponse>> DeckList(DeckFormatRequest request)
{
if (!TryGetViewerId(out long viewerId)) return Unauthorized();
var byFormat = await _deckRepository.GetDecksByFormats(viewerId, new[] { Format.Rotation, Format.Unlimited });
return new PracticeDeckListResponse
{
MaintenanceCardList = new List<long>(),
UserDeckRotation = byFormat[Format.Rotation].Select(d => new UserDeck(d)).ToList(),
UserDeckUnlimited = byFormat[Format.Unlimited].Select(d => new UserDeck(d)).ToList(),
};
}
/// <summary>
/// /practice/start — server is essentially a no-op for practice. Spec: empty body
/// response is fine; client tolerates missing mission_parameter.
/// </summary>
[HttpPost("start")]
public Task<PracticeStartResponse> Start(BaseRequest request)
{
return Task.FromResult(new PracticeStartResponse());
}
/// <summary>
/// /practice/finish — accept the recovery_data blob without validation; return zero
/// XP / no rewards. Class XP bookkeeping is deferred until a per-class XP store exists.
/// </summary>
[HttpPost("finish")]
public Task<PracticeFinishResponse> Finish(PracticeFinishRequest request)
{
return Task.FromResult(new PracticeFinishResponse
{
GetClassExperience = 0,
ClassExperience = 0,
ClassLevel = 1,
AchievedInfo = new Dictionary<string, object>(),
RewardList = new List<Models.Dtos.Common.Reward>()
});
}
}