mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-26 07:28:45 -07:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 24cc13084a | |||
| 193d9b343c |
@@ -167,6 +167,11 @@ public static class DorkNetRouteOwnership
|
|||||||
public static IReadOnlyList<string> PublicSubdomains =>
|
public static IReadOnlyList<string> PublicSubdomains =>
|
||||||
ServiceRouteGroups
|
ServiceRouteGroups
|
||||||
.SelectMany(group => group.HostSubdomains)
|
.SelectMany(group => group.HostSubdomains)
|
||||||
|
// api.* is a shared public host: it owns no service by host and is
|
||||||
|
// dispatched by path (see ResolvePublicService's path-prefix pass),
|
||||||
|
// so it never appears in a group's HostSubdomains. It must still be
|
||||||
|
// whitelisted here or HostFiltering rejects every api.{apex} request.
|
||||||
|
.Append("api")
|
||||||
.Append(AdminHost)
|
.Append(AdminHost)
|
||||||
.Append("feed")
|
.Append("feed")
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ namespace DorkNet.Server.Controllers.Match;
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
public class MatchPlayerController(
|
public class MatchPlayerController(
|
||||||
PlayerPresenceService presence,
|
PlayerPresenceService presence,
|
||||||
NotificationService notifications,
|
|
||||||
RoomService rooms,
|
RoomService rooms,
|
||||||
PrivateInstanceService privateInstances,
|
PrivateInstanceService privateInstances,
|
||||||
IConfiguration config,
|
IConfiguration config,
|
||||||
@@ -37,7 +36,7 @@ public class MatchPlayerController(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpPost("/player/login")]
|
[HttpPost("/player/login")]
|
||||||
[Consumes("application/x-www-form-urlencoded", "multipart/form-data")]
|
[Consumes("application/x-www-form-urlencoded", "multipart/form-data")]
|
||||||
public async Task<IActionResult> PlayerLogin([FromForm(Name = "LoginLock")] string? loginLock)
|
public IActionResult PlayerLogin([FromForm(Name = "LoginLock")] string? loginLock)
|
||||||
{
|
{
|
||||||
var playerId = TryGetCurrentPlayerId();
|
var playerId = TryGetCurrentPlayerId();
|
||||||
if (playerId != 0 && !string.IsNullOrWhiteSpace(loginLock))
|
if (playerId != 0 && !string.IsNullOrWhiteSpace(loginLock))
|
||||||
@@ -46,17 +45,15 @@ public class MatchPlayerController(
|
|||||||
if (replaced)
|
if (replaced)
|
||||||
{
|
{
|
||||||
log.LogWarning(
|
log.LogWarning(
|
||||||
"[player-login] concurrent session for player={PlayerId}; kicking old session(s) via ModerationKick push",
|
"[player-login] concurrent session marker replaced for player={PlayerId}; stale SignalR connections will be evicted on hub connect",
|
||||||
playerId);
|
playerId);
|
||||||
// The new session hasn't opened its SignalR socket yet
|
// Do not broadcast ModerationKick to the whole player group
|
||||||
// (negotiate fires AFTER /player/login on the watch's
|
// here. During reconnects the active watch can already have a
|
||||||
// boot sequence — verified in the live trace), so the
|
// SignalR connection in that group, and the 2020 client crashes
|
||||||
// ModerationKick push hits only the OLDER session's
|
// hard if it receives a malformed/stale moderation payload.
|
||||||
// existing connections. They'll show the "you've been
|
// NotifyHub.OnConnectedAsync snapshots existing connection ids
|
||||||
// kicked" dialog and gracefully disconnect via the
|
// before adding the new connection and sends a targeted kick to
|
||||||
// watch's LocalModerationKickSelf path.
|
// those ids only.
|
||||||
await notifications.KickStaleSession(playerId,
|
|
||||||
"Your account was signed in on another device.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(Array.Empty<object>());
|
return Ok(Array.Empty<object>());
|
||||||
|
|||||||
@@ -64,7 +64,12 @@ public class GameSessionService(DorkNetDbContext db)
|
|||||||
// in, so we save once with a placeholder, then patch and save
|
// in, so we save once with a placeholder, then patch and save
|
||||||
// again. Two SaveChangesAsync calls is cheap at our QPS.
|
// again. Two SaveChangesAsync calls is cheap at our QPS.
|
||||||
PhotonRoomName = "pending",
|
PhotonRoomName = "pending",
|
||||||
MaxCapacity = 8,
|
// Track the room's "Max Player Count in This Subroom" slider
|
||||||
|
// instead of a hardcoded 8, so matchmade joiners fill the room to
|
||||||
|
// the host's chosen limit rather than fragmenting into a new
|
||||||
|
// instance every 8 players. (Existing sessions keep whatever cap
|
||||||
|
// they were created with.)
|
||||||
|
MaxCapacity = await ResolveRoomMaxCapacityAsync(effectiveRoom),
|
||||||
PlayerCount = 1,
|
PlayerCount = 1,
|
||||||
CreatedAt = DateTime.UtcNow,
|
CreatedAt = DateTime.UtcNow,
|
||||||
};
|
};
|
||||||
@@ -75,6 +80,28 @@ public class GameSessionService(DorkNetDbContext db)
|
|||||||
return ToDto(session);
|
return ToDto(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default session cap when we can't resolve a room-specific value (system
|
||||||
|
// rooms like the dorm use a GUID roomId that doesn't map to a RoomEntity).
|
||||||
|
private const int DefaultMaxCapacity = 8;
|
||||||
|
|
||||||
|
/// <summary>Resolve a new session's capacity from the room's entry
|
||||||
|
/// subroom (lowest <c>OrderIndex</c>) <see cref="RoomSceneEntity.MaxPlayers"/>
|
||||||
|
/// — the value the in-game "Max Player Count in This Subroom" slider
|
||||||
|
/// writes. Matchmade sessions land in the entry subroom, so its cap is the
|
||||||
|
/// right ceiling. GameSession roomIds are the room's numeric Id as a
|
||||||
|
/// string for user rooms; non-numeric (dorm GUID, etc.) → default. Clamped
|
||||||
|
/// to a sane Photon ceiling.</summary>
|
||||||
|
private async Task<int> ResolveRoomMaxCapacityAsync(string roomId)
|
||||||
|
{
|
||||||
|
if (!long.TryParse(roomId, out var roomLongId)) return DefaultMaxCapacity;
|
||||||
|
var max = await db.RoomScenes
|
||||||
|
.Where(s => s.RoomId == roomLongId)
|
||||||
|
.OrderBy(s => s.OrderIndex)
|
||||||
|
.Select(s => (int?)s.MaxPlayers)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
return max is int v && v > 0 ? Math.Clamp(v, 1, 80) : DefaultMaxCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<GameSession?> GetByIdAsync(long id)
|
public async Task<GameSession?> GetByIdAsync(long id)
|
||||||
{
|
{
|
||||||
var s = await db.GameSessions.AsNoTracking()
|
var s = await db.GameSessions.AsNoTracking()
|
||||||
|
|||||||
Reference in New Issue
Block a user