Build & Deploy / build (push) Successful in 21s
- RequestPlayerContracts: renvoie les vrais contrats actifs + job boards du joueur (peuplement initial des boards vides) au lieu d'une liste hardcodée. - RequestFactionProgression: renvoie la réputation réelle des 3 factions (au lieu de 0). - StartTechTreeNodeUpgradeClient: dependency check PlayerQuarterLevelRequired. - PurchaseWeaponShopItem: critère de déblocage par contrat (UnlockData.ContractLockPurchase). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
5.3 KiB
C#
120 lines
5.3 KiB
C#
using System.Text.Json;
|
|
using Prospect.Server.Api.Models.Data;
|
|
using Prospect.Server.Api.Services.Auth.Extensions;
|
|
using Prospect.Server.Api.Services.CloudScript.Models;
|
|
using Prospect.Server.Api.Services.CloudScript.Models.Data;
|
|
using Prospect.Server.Api.Services.UserData;
|
|
|
|
namespace Prospect.Server.Api.Services.CloudScript.Functions;
|
|
|
|
[CloudScriptFunction("RequestPlayerContracts")]
|
|
public class RequestPlayerContracts : ICloudScriptFunction<FYGetPlayerContractsRequest, FYGetPlayerContractsResult>
|
|
{
|
|
// Board slots are ordered Easy / Medium / Hard (same convention as ClaimActiveContract).
|
|
private static readonly string[] Difficulties = ["Easy", "Medium", "Hard"];
|
|
private const int RefreshHours = 12;
|
|
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
private readonly UserDataService _userDataService;
|
|
private readonly TitleDataService _titleDataService;
|
|
private readonly Random _rnd = new();
|
|
|
|
public RequestPlayerContracts(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService)
|
|
{
|
|
_httpContextAccessor = httpContextAccessor;
|
|
_userDataService = userDataService;
|
|
_titleDataService = titleDataService;
|
|
}
|
|
|
|
public async Task<FYGetPlayerContractsResult> ExecuteAsync(FYGetPlayerContractsRequest request)
|
|
{
|
|
var context = _httpContextAccessor.HttpContext;
|
|
if (context == null)
|
|
{
|
|
throw new CloudScriptException("CloudScript was not called within a http request");
|
|
}
|
|
var userId = context.User.FindAuthUserId();
|
|
|
|
var userData = await _userDataService.FindAsync(
|
|
userId, userId,
|
|
new List<string>
|
|
{
|
|
"ContractsActive", "JobBoardsData",
|
|
"FactionProgressionKorolev", "FactionProgressionICA", "FactionProgressionOsiris",
|
|
}
|
|
);
|
|
|
|
var contractsActive = JsonSerializer.Deserialize<FYGetActiveContractsResult>(userData["ContractsActive"].Value)!;
|
|
var jobBoardsData = JsonSerializer.Deserialize<JobBoardsData>(userData["JobBoardsData"].Value)!;
|
|
|
|
var titleData = _titleDataService.Find(new List<string> { "Contracts", "Jobs", "LevelData" });
|
|
var contracts = JsonSerializer.Deserialize<Dictionary<string, TitleDataContractInfo>>(titleData["Contracts"])!;
|
|
var jobs = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, string[]>>>(titleData["Jobs"])!;
|
|
var levelsData = JsonSerializer.Deserialize<Dictionary<string, int[]>>(titleData["LevelData"])!;
|
|
|
|
var now = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
|
var lastRefresh = jobBoardsData.LastBoardRefreshTimeUtc?.Seconds ?? 0;
|
|
|
|
// Initial fill only: populate boards that were seeded empty / never refreshed.
|
|
// The periodic rotation is owned by RequestActiveObjectivesAndBoardsData, so we
|
|
// don't refresh on a timer here to avoid two competing refresh policies.
|
|
var boardsEmpty = jobBoardsData.Boards == null
|
|
|| jobBoardsData.Boards.Any(b => b.Contracts == null
|
|
|| b.Contracts.Any(c => string.IsNullOrEmpty(c.ContractId)));
|
|
|
|
if (jobBoardsData.Boards != null && (lastRefresh == 0 || boardsEmpty))
|
|
{
|
|
foreach (var board in jobBoardsData.Boards)
|
|
{
|
|
if (board.Contracts == null || !jobs.TryGetValue(board.FactionId, out var factionJobs))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var factionKey = "FactionProgression" + board.FactionId;
|
|
var factionProgression = userData.TryGetValue(factionKey, out var fp)
|
|
? JsonSerializer.Deserialize<int>(fp.Value)
|
|
: 0;
|
|
|
|
var level = levelsData.TryGetValue(board.FactionId, out var levels)
|
|
? levels.Length - Array.FindIndex(levels, xp => factionProgression >= xp)
|
|
: 0;
|
|
|
|
for (var idx = 0; idx < board.Contracts.Count && idx < Difficulties.Length; idx++)
|
|
{
|
|
if (!factionJobs.TryGetValue(Difficulties[idx], out var difficultyJobs))
|
|
{
|
|
continue;
|
|
}
|
|
var available = Array.FindAll(difficultyJobs,
|
|
jobId => contracts.TryGetValue(jobId, out var c) && level >= c.UnlockData.Level);
|
|
if (available.Length > 0)
|
|
{
|
|
board.Contracts[idx].ContractId = available[_rnd.Next(available.Length)];
|
|
}
|
|
}
|
|
}
|
|
|
|
jobBoardsData.LastBoardRefreshTimeUtc = new FYTimestamp { Seconds = now };
|
|
|
|
await _userDataService.UpdateAsync(
|
|
userId, userId,
|
|
new Dictionary<string, string> { ["JobBoardsData"] = JsonSerializer.Serialize(jobBoardsData) }
|
|
);
|
|
}
|
|
|
|
return new FYGetPlayerContractsResult
|
|
{
|
|
UserId = userId,
|
|
Error = null,
|
|
ActiveContracts = contractsActive.Contracts,
|
|
FactionsContracts = new FYFactionsContractsData
|
|
{
|
|
Boards = jobBoardsData.Boards,
|
|
LastBoardRefreshTimeUtc = jobBoardsData.LastBoardRefreshTimeUtc ?? new FYTimestamp { Seconds = now },
|
|
},
|
|
RefreshHours24UtcFromBackend = RefreshHours,
|
|
};
|
|
}
|
|
}
|