using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.AspNetCore.SignalR; using Prospect.Server.Api.Hubs; 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; public class OnSquadMatchmakingSuccessMessage { [JsonPropertyName("success")] public bool Success { get; set; } [JsonPropertyName("sessionId")] public string SessionID { get; set; } [JsonPropertyName("squadId")] public string SquadID { get; set; } } [CloudScriptFunction("EnterMatchmakingMatch")] public class EnterMatchmakingMatchFunction : ICloudScriptFunction { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHubContext _hubContext; private readonly UserDataService _userDataService; private readonly TitleDataService _titleDataService; private readonly SignalRConnectionRegistry _registry; public EnterMatchmakingMatchFunction(IHubContext hubContext, IHttpContextAccessor httpContextAccessor, UserDataService userDataService, TitleDataService titleDataService, SignalRConnectionRegistry registry) { _httpContextAccessor = httpContextAccessor; _hubContext = hubContext; _userDataService = userDataService; _titleDataService = titleDataService; _registry = registry; } public async Task ExecuteAsync(FYEnterMatchAzureFunction 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 titleData = _titleDataService.Find(new List{"Contracts"}); var contracts = JsonSerializer.Deserialize>(titleData["Contracts"]); var userData = await _userDataService.FindAsync( userId, userId, new List{"ContractsActive", "Inventory"} ); var inventory = JsonSerializer.Deserialize>(userData["Inventory"].Value); var contractsActive = JsonSerializer.Deserialize(userData["ContractsActive"].Value); // Compute delivery quest progress before deploy. // This ensures that the quest progress will be shown on the planet. // The station doesn't seem to care about delivery quests progress and calculates progress // based on actual items in stash. And so does the "ClaimActiveContract" function. foreach (var contractActive in contractsActive.Contracts) { if (!contracts.TryGetValue(contractActive.ContractID, out var contract)) { continue; } for (var i = 0; i < contract.Objectives.Length; i++) { var objective = contract.Objectives[i]; // Kill objectives: the client-hosted raid has no dedicated game server // to report per-kill progress to the backend, so kills would never be // saved. Auto-credit them on deploy so the objective can be completed // (mirrors the player-kill auto-credit done in ActivateContract). if (objective.Type == EYContractObjectiveType.Kills) { contractActive.Progress[i] = objective.MaxProgress; continue; } if (objective.Type != EYContractObjectiveType.OwnNumOfItem) { continue; } int remaining = objective.MaxProgress; foreach (var item in inventory) { if (item.BaseItemId != objective.ItemToOwn) { continue; } remaining -= item.Amount; if (remaining <= 0) { remaining = 0; break; } } contractActive.Progress[i] = objective.MaxProgress - remaining; } } await _userDataService.UpdateAsync( userId, userId, new Dictionary{ ["ContractsActive"] = JsonSerializer.Serialize(contractsActive), } ); // Send the "matchmaking succeeded / travel to match" signal only to the deploying // player (via their mapped SignalR connection) — broadcasting to Clients.All made // every connected player travel too. Fall back to broadcast only if the connection // isn't mapped yet. var deployMessage = new OnSquadMatchmakingSuccessMessage { Success = true, SessionID = request.MapName, // TODO: pass real squad session info SquadID = request.SquadId, }; var connectionId = _registry.GetConnection(userId); if (connectionId != null) await _hubContext.Clients.Client(connectionId).SendAsync("OnSquadMatchmakingSuccess", deployMessage); else await _hubContext.Clients.All.SendAsync("OnSquadMatchmakingSuccess", deployMessage); return new FYEnterMatchmakingResult { Success = true, ErrorMessage = "", SingleplayerStation = false, NumAttempts = 1, Blocker = 0, IsMatchTravel = true, SessionId = "", // TODO: Not sure how this affects match travel }; } }