Promotion: fix sauvegarde des missions en zone (raid client-hosted) #4
+23
-18
@@ -1,6 +1,9 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Prospect.Server.Api.Services.CloudScript;
|
using Prospect.Server.Api.Services.CloudScript;
|
||||||
using Prospect.Server.Api.Services.Auth.Extensions;
|
using Prospect.Server.Api.Services.Auth.Extensions;
|
||||||
|
using Prospect.Server.Api.Services.Database;
|
||||||
|
using Prospect.Server.Api.Services.Database.Models;
|
||||||
|
using Prospect.Server.Api.Services.Squad;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Prospect.Server.Api.Hubs;
|
using Prospect.Server.Api.Hubs;
|
||||||
|
|
||||||
@@ -28,12 +31,16 @@ public class SquadMemberReadyForMatchFunction : ICloudScriptFunction<SquadMember
|
|||||||
private readonly ILogger<SquadMemberReadyForMatchFunction> _logger;
|
private readonly ILogger<SquadMemberReadyForMatchFunction> _logger;
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
private readonly IHubContext<CycleHub> _hubContext;
|
private readonly IHubContext<CycleHub> _hubContext;
|
||||||
|
private readonly SquadService _squadService;
|
||||||
|
private readonly DbUserService _userService;
|
||||||
|
|
||||||
public SquadMemberReadyForMatchFunction(ILogger<SquadMemberReadyForMatchFunction> logger, IHttpContextAccessor httpContextAccessor, IHubContext<CycleHub> hubContext)
|
public SquadMemberReadyForMatchFunction(ILogger<SquadMemberReadyForMatchFunction> logger, IHttpContextAccessor httpContextAccessor, IHubContext<CycleHub> hubContext, SquadService squadService, DbUserService userService)
|
||||||
{
|
{
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_hubContext = hubContext;
|
_hubContext = hubContext;
|
||||||
|
_squadService = squadService;
|
||||||
|
_userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SquadMemberReadyForMatchResponse> ExecuteAsync(SquadMemberReadyForMatchRequest request)
|
public async Task<SquadMemberReadyForMatchResponse> ExecuteAsync(SquadMemberReadyForMatchRequest request)
|
||||||
@@ -44,27 +51,25 @@ public class SquadMemberReadyForMatchFunction : ICloudScriptFunction<SquadMember
|
|||||||
throw new CloudScriptException("CloudScript was not called within a http request");
|
throw new CloudScriptException("CloudScript was not called within a http request");
|
||||||
}
|
}
|
||||||
var userId = context.User.FindAuthUserId();
|
var userId = context.User.FindAuthUserId();
|
||||||
|
var squadId = string.IsNullOrEmpty(request.SquadID) ? userId : request.SquadID;
|
||||||
|
|
||||||
|
// Register the caller into the squad (party is formed client-side; each member's client
|
||||||
|
// calls this with the shared squadId) and record their readiness / selected map.
|
||||||
|
var displayName = (await _userService.FindByIdAsync(userId))?.DisplayName ?? "";
|
||||||
|
_squadService.JoinOrCreate(squadId, userId, displayName);
|
||||||
|
var ready = request.matchmakingSettings?.isReadyForMatch ?? false;
|
||||||
|
_squadService.SetReady(squadId, userId, ready, request.matchmakingSettings?.selectedMapName);
|
||||||
|
|
||||||
|
var squad = _squadService.Get(squadId)!;
|
||||||
|
var isSquadReady = _squadService.IsSquadReady(squadId);
|
||||||
|
_logger.LogInformation("SquadMemberReadyForMatch: {User} ready={Ready} in squad {Squad} ({Count} member(s), squadReady={SquadReady})",
|
||||||
|
userId, ready, squadId, squad.Members.Count, isSquadReady);
|
||||||
|
|
||||||
return new SquadMemberReadyForMatchResponse
|
return new SquadMemberReadyForMatchResponse
|
||||||
{
|
{
|
||||||
Result = 0, // EYSquadActionResult::OK
|
Result = 0, // EYSquadActionResult::OK
|
||||||
Squad = new FYPlayFabSquad {
|
Squad = _squadService.ToClientSquad(squad),
|
||||||
SquadID = "100", // TODO
|
IsSquadReadyForMatch = isSquadReady,
|
||||||
// Members = [
|
|
||||||
// new FYPlayFabSquadMember {
|
|
||||||
// Profile = new FYPlayFabPlayerProfile {
|
|
||||||
// PlayerId = userId,
|
|
||||||
// },
|
|
||||||
// onlineState = 0, // EYUserState::IN_STATION
|
|
||||||
// matchmakingSettings = new FYUserMatchmakingSettings {
|
|
||||||
// isReadyForMatch = true,
|
|
||||||
// isSecretLeader = true,
|
|
||||||
// selectedMapName = "Map01",
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
},
|
|
||||||
IsSquadReadyForMatch = true,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Prospect.Server.Api.Services.Auth.Extensions;
|
||||||
using Prospect.Server.Api.Services.CloudScript;
|
using Prospect.Server.Api.Services.CloudScript;
|
||||||
|
using Prospect.Server.Api.Services.Squad;
|
||||||
|
|
||||||
public class SquadMemberSelectedMapRequest
|
public class SquadMemberSelectedMapRequest
|
||||||
{
|
{
|
||||||
[JsonPropertyName("squadId")]
|
[JsonPropertyName("squadId")]
|
||||||
@@ -17,15 +20,26 @@ public class SquadMemberSelectedMapResponse
|
|||||||
public class SquadMemberSelectedMapFunction : ICloudScriptFunction<SquadMemberSelectedMapRequest, SquadMemberSelectedMapResponse>
|
public class SquadMemberSelectedMapFunction : ICloudScriptFunction<SquadMemberSelectedMapRequest, SquadMemberSelectedMapResponse>
|
||||||
{
|
{
|
||||||
private readonly ILogger<SquadMemberSelectedMapFunction> _logger;
|
private readonly ILogger<SquadMemberSelectedMapFunction> _logger;
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
private readonly SquadService _squadService;
|
||||||
|
|
||||||
public SquadMemberSelectedMapFunction(ILogger<SquadMemberSelectedMapFunction> logger)
|
public SquadMemberSelectedMapFunction(ILogger<SquadMemberSelectedMapFunction> logger, IHttpContextAccessor httpContextAccessor, SquadService squadService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_httpContextAccessor = httpContextAccessor;
|
||||||
|
_squadService = squadService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SquadMemberSelectedMapResponse> ExecuteAsync(SquadMemberSelectedMapRequest request)
|
public Task<SquadMemberSelectedMapResponse> ExecuteAsync(SquadMemberSelectedMapRequest request)
|
||||||
{
|
{
|
||||||
return new SquadMemberSelectedMapResponse
|
var context = _httpContextAccessor.HttpContext;
|
||||||
{};
|
if (context != null && !string.IsNullOrEmpty(request.SquadID))
|
||||||
|
{
|
||||||
|
var userId = context.User.FindAuthUserId();
|
||||||
|
_squadService.JoinOrCreate(request.SquadID, userId, "");
|
||||||
|
_squadService.SetMap(request.SquadID, userId, request.SelectedMapName ?? "");
|
||||||
|
_logger.LogInformation("SquadMemberSelectedMap: {User} -> {Map} (squad {Squad})", userId, request.SelectedMapName, request.SquadID);
|
||||||
|
}
|
||||||
|
return Task.FromResult(new SquadMemberSelectedMapResponse { });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Prospect.Server.Api.Services.Auth.Extensions;
|
||||||
using Prospect.Server.Api.Services.CloudScript;
|
using Prospect.Server.Api.Services.CloudScript;
|
||||||
|
using Prospect.Server.Api.Services.Database;
|
||||||
|
using Prospect.Server.Api.Services.Squad;
|
||||||
|
|
||||||
public class TryGetCompleteSquadInfoRequest
|
public class TryGetCompleteSquadInfoRequest
|
||||||
{
|
{
|
||||||
@@ -51,26 +54,30 @@ public class TryGetCompleteSquadInfoFunction : ICloudScriptFunction<TryGetComple
|
|||||||
{
|
{
|
||||||
private readonly ILogger<TryGetCompleteSquadInfoFunction> _logger;
|
private readonly ILogger<TryGetCompleteSquadInfoFunction> _logger;
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
private readonly SquadService _squadService;
|
||||||
|
|
||||||
public TryGetCompleteSquadInfoFunction(ILogger<TryGetCompleteSquadInfoFunction> logger, IHttpContextAccessor httpContextAccessor)
|
public TryGetCompleteSquadInfoFunction(ILogger<TryGetCompleteSquadInfoFunction> logger, IHttpContextAccessor httpContextAccessor, SquadService squadService)
|
||||||
{
|
{
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_squadService = squadService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<FYPlayFabSquad> ExecuteAsync(TryGetCompleteSquadInfoRequest request)
|
public Task<FYPlayFabSquad> ExecuteAsync(TryGetCompleteSquadInfoRequest request)
|
||||||
{
|
{
|
||||||
var context = _httpContextAccessor.HttpContext;
|
var context = _httpContextAccessor.HttpContext;
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
throw new CloudScriptException("CloudScript was not called within a http request");
|
throw new CloudScriptException("CloudScript was not called within a http request");
|
||||||
}
|
}
|
||||||
// var userId = context.User.FindAuthUserId();
|
var userId = context.User.FindAuthUserId();
|
||||||
|
var squad = _squadService.GetByUser(userId);
|
||||||
return new FYPlayFabSquad
|
if (squad == null)
|
||||||
{
|
{
|
||||||
// SquadID = "100",
|
// Not in a squad yet — return an empty squad (same as the previous stub).
|
||||||
// Members = [],
|
return Task.FromResult(new FYPlayFabSquad());
|
||||||
};
|
}
|
||||||
|
_logger.LogInformation("TryGetCompleteSquadInfo: user {User} in squad {Squad} ({Count} member(s))", userId, squad.SquadId, squad.Members.Count);
|
||||||
|
return Task.FromResult(_squadService.ToClientSquad(squad));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ public class DbUserService : BaseDbService<PlayFabUser>
|
|||||||
auth.Key == key)).FirstOrDefaultAsync();
|
auth.Key == key)).FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve a player by its PlayFab id (used to display squad member names).
|
||||||
|
public async Task<PlayFabUser> FindByIdAsync(string id)
|
||||||
|
{
|
||||||
|
return await Collection.Find(user => user.Id == id).FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<PlayFabUser> CreateAsync(PlayFabUserAuthType type, string key)
|
private async Task<PlayFabUser> CreateAsync(PlayFabUserAuthType type, string key)
|
||||||
{
|
{
|
||||||
var user = new PlayFabUser
|
var user = new PlayFabUser
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Prospect.Server.Api.Services.Squad;
|
||||||
|
|
||||||
|
// In-memory squad/lobby state. Squads are keyed by the squadId the client provides on the
|
||||||
|
// SquadMember* calls (the client forms the party and shares a squadId; each member's client
|
||||||
|
// then calls the backend with it). A member is (lazily) registered into that squad on any
|
||||||
|
// squad call, and the current squad for a user is tracked so TryGetCompleteSquadInfo — which
|
||||||
|
// carries no squadId — can still return it. State is process-local (fine for a small server).
|
||||||
|
public class SquadMemberInfo
|
||||||
|
{
|
||||||
|
public string UserId { get; set; } = "";
|
||||||
|
public string DisplayName { get; set; } = "";
|
||||||
|
public int OnlineState { get; set; }
|
||||||
|
public bool IsReady { get; set; }
|
||||||
|
public string SelectedMap { get; set; } = "";
|
||||||
|
public bool IsLeader { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SquadInfo
|
||||||
|
{
|
||||||
|
public string SquadId { get; set; } = "";
|
||||||
|
public ConcurrentDictionary<string, SquadMemberInfo> Members { get; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SquadService
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, SquadInfo> _squads = new();
|
||||||
|
private readonly ConcurrentDictionary<string, string> _userToSquad = new();
|
||||||
|
|
||||||
|
public SquadInfo JoinOrCreate(string squadId, string userId, string displayName)
|
||||||
|
{
|
||||||
|
var squad = _squads.GetOrAdd(squadId, id => new SquadInfo { SquadId = id });
|
||||||
|
var isFirst = squad.Members.IsEmpty;
|
||||||
|
var member = squad.Members.GetOrAdd(userId, uid => new SquadMemberInfo { UserId = uid, IsLeader = isFirst });
|
||||||
|
if (!string.IsNullOrEmpty(displayName)) member.DisplayName = displayName;
|
||||||
|
_userToSquad[userId] = squadId;
|
||||||
|
return squad;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SquadInfo? GetByUser(string userId)
|
||||||
|
=> _userToSquad.TryGetValue(userId, out var sid) && _squads.TryGetValue(sid, out var sq) ? sq : null;
|
||||||
|
|
||||||
|
public SquadInfo? Get(string squadId)
|
||||||
|
=> _squads.TryGetValue(squadId, out var sq) ? sq : null;
|
||||||
|
|
||||||
|
public void SetReady(string squadId, string userId, bool ready, string? selectedMap)
|
||||||
|
{
|
||||||
|
if (_squads.TryGetValue(squadId, out var sq) && sq.Members.TryGetValue(userId, out var m))
|
||||||
|
{
|
||||||
|
m.IsReady = ready;
|
||||||
|
if (!string.IsNullOrEmpty(selectedMap)) m.SelectedMap = selectedMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMap(string squadId, string userId, string map)
|
||||||
|
{
|
||||||
|
if (_squads.TryGetValue(squadId, out var sq) && sq.Members.TryGetValue(userId, out var m))
|
||||||
|
m.SelectedMap = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSquadReady(string squadId)
|
||||||
|
=> _squads.TryGetValue(squadId, out var sq) && !sq.Members.IsEmpty && sq.Members.Values.All(m => m.IsReady);
|
||||||
|
|
||||||
|
public void Leave(string userId)
|
||||||
|
{
|
||||||
|
if (_userToSquad.TryRemove(userId, out var sid) && _squads.TryGetValue(sid, out var sq))
|
||||||
|
{
|
||||||
|
sq.Members.TryRemove(userId, out _);
|
||||||
|
if (sq.Members.IsEmpty) _squads.TryRemove(sid, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the client-facing squad object (models live in the global namespace).
|
||||||
|
public FYPlayFabSquad ToClientSquad(SquadInfo squad)
|
||||||
|
{
|
||||||
|
return new FYPlayFabSquad
|
||||||
|
{
|
||||||
|
SquadID = squad.SquadId,
|
||||||
|
Members = squad.Members.Values.Select(m => new FYPlayFabSquadMember
|
||||||
|
{
|
||||||
|
Profile = new FYPlayFabPlayerProfile
|
||||||
|
{
|
||||||
|
PlayerId = m.UserId,
|
||||||
|
DisplayName = m.DisplayName,
|
||||||
|
AvatarUrl = "",
|
||||||
|
},
|
||||||
|
onlineState = m.OnlineState,
|
||||||
|
matchmakingSettings = new FYUserMatchmakingSettings
|
||||||
|
{
|
||||||
|
isReadyForMatch = m.IsReady,
|
||||||
|
selectedMapName = m.SelectedMap,
|
||||||
|
isSecretLeader = m.IsLeader,
|
||||||
|
},
|
||||||
|
mapRowNamesUnlocked = Array.Empty<string>(),
|
||||||
|
}).ToArray(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,8 @@ public class Startup
|
|||||||
|
|
||||||
services.AddSingleton<DbUserDataService>();
|
services.AddSingleton<DbUserDataService>();
|
||||||
|
|
||||||
|
services.AddSingleton<Prospect.Server.Api.Services.Squad.SquadService>();
|
||||||
|
|
||||||
services.AddHostedService<QosService>();
|
services.AddHostedService<QosService>();
|
||||||
services.AddSingleton<QosServer>();
|
services.AddSingleton<QosServer>();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user