diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4fff02 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +_out/ diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/ClaimActiveContract.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/ClaimActiveContract.cs index df111ad..5796102 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/ClaimActiveContract.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/ClaimActiveContract.cs @@ -131,6 +131,12 @@ public class ClaimActiveContract : ICloudScriptFunction _logger; private readonly IHttpContextAccessor _httpContextAccessor; private readonly UserDataService _userDataService; + private readonly TitleDataService _titleDataService; - public CompleteInventoryUpdateFunction(ILogger logger, IHttpContextAccessor httpContextAccessor, UserDataService userDataService) + public CompleteInventoryUpdateFunction(ILogger logger, IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService) { _logger = logger; _httpContextAccessor = httpContextAccessor; _userDataService = userDataService; + _titleDataService = titleDataService; } public async Task ExecuteAsync(CompleteInventoryUpdateRequest request) @@ -64,6 +67,42 @@ public class CompleteInventoryUpdateFunction : ICloudScriptFunction{"Inventory"}); var inventory = JsonSerializer.Deserialize>(userData["Inventory"].Value); + // Insurance payout: this request also reports the end-of-match inventory, so items + // the player lost (death / left behind) arrive in ItemsToRemove. Any of those that + // were insured on deploy (EnterMatchmakingStation) are recovered back to the stash — + // the premium was already paid up-front — and the insurance is consumed (one raid). + // Recovery is only performed if the item's policy declares a positive Payout. + var recoveredInsured = new HashSet(); + if (request.ItemsToRemove is { Count: > 0 }) + { + try + { + var td = _titleDataService.Find(new List { "InventoryInsurance" }); + if (td.TryGetValue("InventoryInsurance", out var rawPolicies) && !string.IsNullOrEmpty(rawPolicies)) + { + var policies = JsonSerializer.Deserialize>(rawPolicies); + foreach (var item in inventory) + { + if (!request.ItemsToRemove.Contains(item.ItemId)) continue; + if (string.IsNullOrEmpty(item.Insurance) || item.InsuranceOwnerPlayfabId != userId) continue; + if (policies == null || !policies.TryGetValue(item.Insurance, out var policy) || policy.Payout <= 0) continue; + recoveredInsured.Add(item.ItemId); + item.Insurance = ""; // consumed for this raid + item.InsuranceOwnerPlayfabId = ""; + } + if (recoveredInsured.Count > 0) + { + _logger.LogInformation("Recovered {Count} insured item(s) for user {User}", recoveredInsured.Count, userId); + } + } + } + catch (Exception e) + { + // Never fail the inventory commit because of insurance handling. + _logger.LogWarning(e, "Failed to process insurance payout"); + } + } + // TODO: Optimize // TODO: Check deleted items to see if other stacks/mods were updated correctly // Process inventory update. The player may: @@ -74,7 +113,8 @@ public class CompleteInventoryUpdateFunction : ICloudScriptFunction(inventory.Count); foreach (var item in inventory) { - if (!request.ItemsToRemove.Contains(item.ItemId)) { + // Keep items that weren't removed, plus insured items recovered above. + if (!request.ItemsToRemove.Contains(item.ItemId) || recoveredInsured.Contains(item.ItemId)) { newInventory.Add(item); } } diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/EnterMatchmakingMatch.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/EnterMatchmakingMatch.cs index 427736a..6625fd3 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/EnterMatchmakingMatch.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/EnterMatchmakingMatch.cs @@ -61,6 +61,14 @@ public class EnterMatchmakingMatchFunction : ICloudScriptFunction { - public Task ExecuteAsync(FYEnterMatchAzureFunction request) + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly UserDataService _userDataService; + private readonly TitleDataService _titleDataService; + private readonly ILogger _logger; + + public EnterMatchmakingStationFunction( + IHttpContextAccessor httpContextAccessor, + UserDataService userDataService, + TitleDataService titleDataService, + ILogger logger) { - return Task.FromResult(new FYEnterMatchmakingResult + _httpContextAccessor = httpContextAccessor; + _userDataService = userDataService; + _titleDataService = titleDataService; + _logger = logger; + } + + public async Task ExecuteAsync(FYEnterMatchAzureFunction request) + { + // Au déploiement, le client peut demander d'assurer une partie de l'inventaire. + var insurance = request.PurchaseInventoryInsurances; + var context = _httpContextAccessor.HttpContext; + if (context != null && insurance?.ItemIds is { Count: > 0 }) + { + try + { + await ApplyInsuranceAsync(context.User.FindAuthUserId(), insurance); + } + catch (Exception e) + { + // Ne jamais bloquer le déploiement à cause de l'assurance. + _logger.LogWarning(e, "Failed to apply inventory insurance on deploy"); + } + } + + return new FYEnterMatchmakingResult { Success = true, ErrorMessage = "", SingleplayerStation = true, // NOTE: This will always travel to single player station IsMatchTravel = false, - }); + }; } -} \ No newline at end of file + + // Marque les items choisis comme assurés (Insurance + propriétaire) et débite la + // prime en InsuranceCurrency selon le coût de la police (TitleData InventoryInsurance). + private async Task ApplyInsuranceAsync(string userId, FYPurchaseInventoryInsuranceRequest req) + { + var titleData = _titleDataService.Find(new List { "InventoryInsurance" }); + var policies = JsonSerializer.Deserialize>(titleData["InventoryInsurance"]); + var insuranceId = string.IsNullOrEmpty(req.InsuranceId) ? "Default" : req.InsuranceId; + if (policies == null || !policies.TryGetValue(insuranceId, out var policy)) + { + _logger.LogWarning("Unknown insurance policy {InsuranceId}", insuranceId); + return; + } + + var userData = await _userDataService.FindAsync(userId, userId, new List { "Inventory", "Balance" }); + var inventory = JsonSerializer.Deserialize>(userData["Inventory"].Value); + var balance = JsonSerializer.Deserialize(userData["Balance"].Value); + + var toInsure = new HashSet(req.ItemIds); + var insuredCount = 0; + foreach (var item in inventory) + { + if (!toInsure.Contains(item.ItemId)) continue; + item.Insurance = insuranceId; + item.InsuranceOwnerPlayfabId = userId; + insuredCount++; + } + if (insuredCount == 0) return; + + // Prime = coût de la police × nombre d'items assurés (arrondi au supérieur), + // plafonnée au solde disponible pour ne jamais passer en négatif. + var premium = (int)Math.Ceiling(policy.Cost * insuredCount); + premium = Math.Min(premium, balance.InsuranceCurrency); + balance.InsuranceCurrency -= premium; + + await _userDataService.UpdateAsync(userId, userId, new Dictionary + { + ["Inventory"] = JsonSerializer.Serialize(inventory), + ["Balance"] = JsonSerializer.Serialize(balance), + }); + _logger.LogInformation("Insured {Count} item(s) under policy {Policy} for user {User} (premium {Premium})", + insuredCount, insuranceId, userId, premium); + } +} diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/PurchaseWeaponShopItem.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/PurchaseWeaponShopItem.cs index f450267..8df29de 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/PurchaseWeaponShopItem.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/PurchaseWeaponShopItem.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Prospect.Server.Api.Models.Data; using Prospect.Server.Api.Services.Auth.Extensions; using Prospect.Server.Api.Services.CloudScript; +using Prospect.Server.Api.Services.CloudScript.Functions; using Prospect.Server.Api.Services.UserData; public class PurchaseWeaponShopItemRequest @@ -137,7 +138,7 @@ public class PurchaseWeaponShopItemFunction : ICloudScriptFunction{"Inventory", "Balance"}); + var userData = await _userDataService.FindAsync(userId, userId, new List{"Inventory", "Balance", "ContractsOneTimeCompleted"}); var inventory = JsonSerializer.Deserialize>(userData["Inventory"].Value); var balance = JsonSerializer.Deserialize(userData["Balance"].Value); var blueprintsData = _titleDataService.Find(new List{"Blueprints"}); @@ -153,7 +154,21 @@ public class PurchaseWeaponShopItemFunction : ICloudScriptFunction(userData["ContractsOneTimeCompleted"].Value); + if (completed?.ContractsIDs == null || !completed.ContractsIDs.Contains(blueprintData.UnlockData.ContractLockPurchase)) + { + return new PurchaseWeaponShopItemResponse + { + UserID = userId, + Error = "Item is locked: complete the required contract first", + }; + } + } // Validate price var shopCraftingData = blueprintData.ItemShopsCraftingData[request.ShopItemBelongsTo]; diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestClaimFortunaPassRewards.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestClaimFortunaPassRewards.cs index fa7604b..59529d7 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestClaimFortunaPassRewards.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestClaimFortunaPassRewards.cs @@ -1,5 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Prospect.Server.Api.Models.Data; using Prospect.Server.Api.Services.Auth.Extensions; using Prospect.Server.Api.Services.CloudScript; using Prospect.Server.Api.Services.UserData; @@ -9,6 +10,15 @@ public class FYFortunaPassClaimedRewards { public List RewardsIDs { get; set; } }; +// Récompense d'un palier de la passe (catalogue optionnel TitleData "FortunaPass2_Rewards"). +// itemId "SoftCurrency"/"Aurum"/"InsuranceCurrency" = monnaie, sinon item d'inventaire. +public class FortunaPassRewardDef { + [JsonPropertyName("itemId")] + public string ItemId { get; set; } + [JsonPropertyName("amount")] + public int Amount { get; set; } +} + public class RequestClaimFortunaPassRewardsRequest { [JsonPropertyName("rewardsIds")] @@ -34,11 +44,15 @@ public class RequestClaimFortunaPassRewardsFunction : ICloudScriptFunction _logger; - public RequestClaimFortunaPassRewardsFunction(IHttpContextAccessor httpContextAccessor, UserDataService userDataService) + public RequestClaimFortunaPassRewardsFunction(IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService, ILogger logger) { _httpContextAccessor = httpContextAccessor; _userDataService = userDataService; + _titleDataService = titleDataService; + _logger = logger; } public async Task ExecuteAsync(RequestClaimFortunaPassRewardsRequest request) @@ -49,28 +63,103 @@ public class RequestClaimFortunaPassRewardsFunction : ICloudScriptFunction{"FortunaPass2_ClaimedRewards"}); - var claimedRewards = JsonSerializer.Deserialize(userData["FortunaPass2_ClaimedRewards"].Value); - foreach (var rewardId in request.RewardsIDs) { + + // Clé de la saison courante (déterminée par la config de build) — évite d'écraser + // la progression de l'autre saison (l'ancien code écrasait S2 ET S3). +#if SEASON_3_RELEASE || SEASON_3_DEBUG + const string claimedKey = "FortunaPass3_ClaimedRewards"; + const string rewardsCatalogKey = "FortunaPass3_Rewards"; +#else + const string claimedKey = "FortunaPass2_ClaimedRewards"; + const string rewardsCatalogKey = "FortunaPass2_Rewards"; +#endif + + var userData = await _userDataService.FindAsync(userId, userId, new List{ claimedKey, "Inventory", "Balance" }); + var claimedRewards = JsonSerializer.Deserialize(userData[claimedKey].Value) + ?? new FYFortunaPassClaimedRewards { RewardsIDs = new List() }; + claimedRewards.RewardsIDs ??= new List(); + + // Catalogue des récompenses (optionnel) : rewardId -> {itemId, amount}. + Dictionary catalog = null; + try + { + var td = _titleDataService.Find(new List { rewardsCatalogKey }); + if (td.TryGetValue(rewardsCatalogKey, out var raw) && !string.IsNullOrWhiteSpace(raw)) + catalog = JsonSerializer.Deserialize>(raw); + } + catch { /* pas de catalogue → on enregistre seulement la réclamation */ } + + var inventory = JsonSerializer.Deserialize>(userData["Inventory"].Value) ?? new List(); + var balance = JsonSerializer.Deserialize(userData["Balance"].Value); + var grantedItems = new List(); + var changedCurrencies = new List(); + + foreach (var rewardId in request.RewardsIDs) + { + // Ne pas re-créditer un palier déjà réclamé. + if (claimedRewards.RewardsIDs.Contains(rewardId)) continue; + + if (catalog == null || !catalog.TryGetValue(rewardId, out var def) || def == null) + { + // Le catalogue des paliers est défini côté client (DataTable). On journalise + // les IDs inconnus pour pouvoir construire un vrai catalogue FortunaPass{2,3}_Rewards + // qui matche. IMPORTANT : ne PAS marquer le palier comme réclamé si on ne peut rien + // accorder — sinon il serait brûlé définitivement et inclaimable une fois le + // catalogue ajouté. + _logger.LogInformation("FortunaPass claim: aucun catalogue pour le reward '{RewardId}' (catalogue {State}) — non accordé", rewardId, catalog == null ? "absent" : "présent"); + continue; + } + claimedRewards.RewardsIDs.Add(rewardId); + _logger.LogInformation("FortunaPass claim: octroi reward '{RewardId}' -> {ItemId} x{Amount}", rewardId, def.ItemId, def.Amount); + + switch (def.ItemId) + { + case "SoftCurrency": + balance.SoftCurrency += def.Amount; + changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "SoftCurrency", Amount = balance.SoftCurrency }); + break; + case "Aurum": + balance.HardCurrency += def.Amount; + changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "Aurum", Amount = balance.HardCurrency }); + break; + case "InsuranceCurrency": + balance.InsuranceCurrency += def.Amount; + changedCurrencies.Add(new FYCurrencyItem { CurrencyName = "InsuranceCurrency", Amount = balance.InsuranceCurrency }); + break; + default: + var item = new FYCustomItemInfo { + ItemId = Guid.NewGuid().ToString(), + Amount = def.Amount <= 0 ? 1 : def.Amount, + BaseItemId = def.ItemId, + Durability = 0, + Insurance = "", + InsuranceOwnerPlayfabId = "", + ModData = new FYModItems { M = [] }, + Origin = new FYItemOriginBackend { G = "", P = "", T = "" }, + InsuredAttachmentId = "", + PrimaryVanityId = 0, + SecondaryVanityId = 0, + RolledPerks = [], + }; + inventory.Add(item); + grantedItems.Add(item); + break; + } } - var output = JsonSerializer.Serialize(claimedRewards); - await _userDataService.UpdateAsync( - userId, userId, - new Dictionary{ - ["FortunaPass2_ClaimedRewards"] = output, - ["FortunaPass3_ClaimedRewards"] = output, // TODO: Separate logic for S2 and S3 - } - ); + var changes = new Dictionary { [claimedKey] = JsonSerializer.Serialize(claimedRewards) }; + if (grantedItems.Count > 0) changes["Inventory"] = JsonSerializer.Serialize(inventory); + if (changedCurrencies.Count > 0) changes["Balance"] = JsonSerializer.Serialize(balance); + await _userDataService.UpdateAsync(userId, userId, changes); return new RequestClaimFortunaPassRewardsResponse { UserId = userId, ErrorType = 3, // EYFortunaPassToastReponseType::Ok Error = "", - GrantedItems = [], - ChangedCurrencies = [], + GrantedItems = grantedItems.ToArray(), + ChangedCurrencies = changedCurrencies.ToArray(), }; } } diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestFactionProgression.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestFactionProgression.cs index 4f4a667..16912b8 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestFactionProgression.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestFactionProgression.cs @@ -1,5 +1,7 @@ -using Prospect.Server.Api.Services.Auth.Extensions; +using System.Text.Json; +using Prospect.Server.Api.Services.Auth.Extensions; using Prospect.Server.Api.Services.CloudScript.Models; +using Prospect.Server.Api.Services.UserData; namespace Prospect.Server.Api.Services.CloudScript.Functions; @@ -7,42 +9,43 @@ namespace Prospect.Server.Api.Services.CloudScript.Functions; public class RequestFactionProgression : ICloudScriptFunction { private readonly IHttpContextAccessor _httpContextAccessor; + private readonly UserDataService _userDataService; - public RequestFactionProgression(IHttpContextAccessor httpContextAccessor) + public RequestFactionProgression(IHttpContextAccessor httpContextAccessor, UserDataService userDataService) { _httpContextAccessor = httpContextAccessor; + _userDataService = userDataService; } - - public Task ExecuteAsync(FYQueryFactionProgressionRequest request) + + public async Task ExecuteAsync(FYQueryFactionProgressionRequest request) { var context = _httpContextAccessor.HttpContext; if (context == null) { - return Task.FromResult(null); + return null; } - - return Task.FromResult(new + var userId = context.User.FindAuthUserId(); + + var userData = await _userDataService.FindAsync( + userId, userId, + new List { "FactionProgressionICA", "FactionProgressionKorolev", "FactionProgressionOsiris" } + ); + + int Progression(string faction) => + userData.TryGetValue("FactionProgression" + faction, out var v) && v.Value != null + ? JsonSerializer.Deserialize(v.Value) + : 0; + + return new { - factions = new [] + factions = new[] { - new - { - factionId = "ICA", - currentProgression = 0 - }, - new - { - factionId = "Korolev", - currentProgression = 0 - }, - new - { - factionId = "Osiris", - currentProgression = 0 - }, + new { factionId = "ICA", currentProgression = Progression("ICA") }, + new { factionId = "Korolev", currentProgression = Progression("Korolev") }, + new { factionId = "Osiris", currentProgression = Progression("Osiris") }, }, - userId = context.User.FindAuthUserId(), + userId, error = "" - }); + }; } -} \ No newline at end of file +} diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestPlayerContracts.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestPlayerContracts.cs index c3fd37f..c2963b9 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestPlayerContracts.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestPlayerContracts.cs @@ -1,100 +1,119 @@ -using Prospect.Server.Api.Services.Auth.Extensions; +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 { - private readonly IHttpContextAccessor _httpContextAccessor; + // Board slots are ordered Easy / Medium / Hard (same convention as ClaimActiveContract). + private static readonly string[] Difficulties = ["Easy", "Medium", "Hard"]; + private const int RefreshHours = 12; - public RequestPlayerContracts(IHttpContextAccessor httpContextAccessor) + 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 Task ExecuteAsync(FYGetPlayerContractsRequest request) + public async Task 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(); - return Task.FromResult(new FYGetPlayerContractsResult + var userData = await _userDataService.FindAsync( + userId, userId, + new List + { + "ContractsActive", "JobBoardsData", + "FactionProgressionKorolev", "FactionProgressionICA", "FactionProgressionOsiris", + } + ); + + var contractsActive = JsonSerializer.Deserialize(userData["ContractsActive"].Value)!; + var jobBoardsData = JsonSerializer.Deserialize(userData["JobBoardsData"].Value)!; + + var titleData = _titleDataService.Find(new List { "Contracts", "Jobs", "LevelData" }); + var contracts = JsonSerializer.Deserialize>(titleData["Contracts"])!; + var jobs = JsonSerializer.Deserialize>>(titleData["Jobs"])!; + var levelsData = JsonSerializer.Deserialize>(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)) { - UserId = context.User.FindAuthUserId(), + 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(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 { ["JobBoardsData"] = JsonSerializer.Serialize(jobBoardsData) } + ); + } + + return new FYGetPlayerContractsResult + { + UserId = userId, Error = null, - ActiveContracts = new List(), + ActiveContracts = contractsActive.Contracts, FactionsContracts = new FYFactionsContractsData { - Boards = new List - { - new FYFactionContractsData - { - FactionId = "ICA", - Contracts = new List - { - new FYFactionContractData - { - ContractId = "NEW-Easy-ICA-Gather-1", - }, - new FYFactionContractData - { - ContractId = "NEW-Medium-ICA-Uplink-1", - }, - new FYFactionContractData - { - ContractId = "NEW-Hard-ICA-Uplink-1", - } - } - }, - new FYFactionContractsData - { - FactionId = "Korolev", - Contracts = new List - { - new FYFactionContractData - { - ContractId = "NEW-Easy-KOR-Mine-4", - }, - new FYFactionContractData - { - ContractId = "NEW-Medium-KOR-Mine-1", - }, - new FYFactionContractData - { - ContractId = "NEW-Hard-KOR-PvP-6", - } - } - }, - new FYFactionContractsData - { - FactionId = "Osiris", - Contracts = new List - { - new FYFactionContractData - { - ContractId = "NEW-Easy-Osiris-Brightcaps-1", - }, - new FYFactionContractData - { - ContractId = "NEW-Medium-Osiris-Gather-1", - }, - new FYFactionContractData - { - ContractId = "NEW-Hard-Osiris-Gather-7", - } - } - } - }, - LastBoardRefreshTimeUtc = new FYTimestamp - { - Seconds = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds() - } + Boards = jobBoardsData.Boards, + LastBoardRefreshTimeUtc = jobBoardsData.LastBoardRefreshTimeUtc ?? new FYTimestamp { Seconds = now }, }, - RefreshHours24UtcFromBackend = 12 - }); + RefreshHours24UtcFromBackend = RefreshHours, + }; } -} \ No newline at end of file +} diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestStoreRotationData.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestStoreRotationData.cs index 64d4598..1af67ae 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestStoreRotationData.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/RequestStoreRotationData.cs @@ -35,6 +35,11 @@ public class RequestStoreRotationDataFunction : ICloudScriptFunction ExecuteAsync(RequestStoreRotationDataRequest request) { var context = _httpContextAccessor.HttpContext; @@ -43,18 +48,33 @@ public class RequestStoreRotationDataFunction : ICloudScriptFunction{"TechTreeNodeData", "Balance", "Inventory"} + new List{"TechTreeNodeData", "Balance", "Inventory", "PlayerQuartersLevel"} ); var userTechTree = JsonSerializer.Deserialize(userData["TechTreeNodeData"].Value); @@ -79,7 +79,15 @@ public class StartTechTreeNodeUpgradeClient : ICloudScriptFunction(userData["PlayerQuartersLevel"].Value); + if (playerQuarters != null && playerQuarters.Level < techTreeNodeData.PlayerQuarterLevelRequired) { + return new FYStartTechTreeNodeUpgradeClientResult + { + UserID = userId, + Error = $"Requires player quarters level {techTreeNodeData.PlayerQuarterLevelRequired}", + }; + } // Validate price // TODO: Refactor diff --git a/src/Prospect.Server.Api/Services/CloudScript/Models/Data/FYPurchaseInventoryInsuranceRequest.cs b/src/Prospect.Server.Api/Services/CloudScript/Models/Data/FYPurchaseInventoryInsuranceRequest.cs index 608da97..a5ea0c0 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Models/Data/FYPurchaseInventoryInsuranceRequest.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Models/Data/FYPurchaseInventoryInsuranceRequest.cs @@ -1,7 +1,14 @@ -namespace Prospect.Server.Api.Services.CloudScript.Models.Data; +using System.Text.Json.Serialization; +namespace Prospect.Server.Api.Services.CloudScript.Models.Data; + +// Envoyé par le client au déploiement (dans FYEnterMatchAzureFunction) : quels items +// de l'inventaire assurer, avec quelle police (voir TitleData "InventoryInsurance"). public class FYPurchaseInventoryInsuranceRequest { - // m_insuranceId - // m_itemIds -} \ No newline at end of file + [JsonPropertyName("insuranceId")] + public string InsuranceId { get; set; } + + [JsonPropertyName("itemIds")] + public List ItemIds { get; set; } +}