mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 07:29:00 -07:00
611 lines
18 KiB
C#
611 lines
18 KiB
C#
using System.Text;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API.Notifications;
|
|
using RadicadeServer.Auth;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.Extensions;
|
|
using RadicadeServer.RecNet.Accounts;
|
|
using RadicadeServer.RecNet.Enums.Messages;
|
|
using RadicadeServer.RecNet.Enums.Players;
|
|
using RadicadeServer.RecNet.Notifications;
|
|
using RadicadeServer.RecNet.Rooms;
|
|
using RadicadeServer.Utils;
|
|
|
|
namespace RadicadeServer.API.Matchmaking
|
|
{
|
|
[Route("matchmaking/player")]
|
|
public class MatchmakingPlayerAPI : Controller
|
|
{
|
|
private readonly RecDBContext db;
|
|
|
|
public MatchmakingPlayerAPI(RecDBContext context)
|
|
{
|
|
db = context;
|
|
}
|
|
|
|
[HttpPut("photonregionpings")]
|
|
public IActionResult SendPhotonRegionPings()
|
|
{
|
|
return Ok(new { });
|
|
}
|
|
|
|
[HttpPut("gameserverregionpings")]
|
|
public IActionResult SendGameServerRegionPings()
|
|
{
|
|
return Ok(new { });
|
|
}
|
|
|
|
[HttpGet("qos")]
|
|
public IActionResult GetQos()
|
|
{
|
|
return Ok(new[]
|
|
{
|
|
new { id = "us-west1", address = "34.169.254.144:50000" },
|
|
new { id = "europe-west1", address = "35.205.141.119:50000" },
|
|
new { id = "asia-northeast1", address = "35.200.67.228:50000" },
|
|
new { id = "us-east1", address = "34.73.244.122:50000" },
|
|
new { id = "us-central1", address = "34.69.179.51:50000" },
|
|
new { id = "northamerica-northeast1", address = "34.152.4.100:50000" }
|
|
});
|
|
}
|
|
|
|
[HttpGet("connection-info"), Authorize]
|
|
public async Task<IActionResult> GetConnectionInfo([FromQuery] long? roomInstanceId = null)
|
|
{
|
|
long? nullableUserId = User.GetUserId();
|
|
if (!nullableUserId.HasValue)
|
|
{
|
|
return Unauthorized();
|
|
}
|
|
long userId = nullableUserId.Value;
|
|
|
|
string photonRoomId = string.Empty;
|
|
var presence = await db.Presences.AsNoTracking()
|
|
.FirstOrDefaultAsync(x => x.playerId == userId);
|
|
if (presence?.roomInstance.HasValue == true)
|
|
{
|
|
var instance = await db.RoomInstances.AsNoTracking()
|
|
.FirstOrDefaultAsync(x => x.roomInstanceId == presence.roomInstance.Value);
|
|
photonRoomId = instance?.photonRoomId ?? string.Empty;
|
|
}
|
|
if (string.IsNullOrEmpty(photonRoomId) && roomInstanceId.HasValue)
|
|
{
|
|
var requested = await db.RoomInstances.AsNoTracking()
|
|
.FirstOrDefaultAsync(x => x.roomInstanceId == roomInstanceId.Value);
|
|
photonRoomId = requested?.photonRoomId ?? string.Empty;
|
|
}
|
|
|
|
var platformId = User.GetPlatId() ?? string.Empty;
|
|
var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
|
|
var key = Encoding.ASCII.GetBytes(DBConfig.JwtKey);
|
|
var now = DateTime.UtcNow;
|
|
var photonToken = tokenHandler.WriteToken(tokenHandler.CreateToken(
|
|
new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor
|
|
{
|
|
Subject = new System.Security.Claims.ClaimsIdentity(new[]
|
|
{
|
|
new System.Security.Claims.Claim("sub", userId.ToString()),
|
|
new System.Security.Claims.Claim("rn.platid", platformId),
|
|
new System.Security.Claims.Claim("rn.plat", User.FindFirst("rn.plat")?.Value ?? "0"),
|
|
new System.Security.Claims.Claim("rn.deviceclass", User.FindFirst("rn.deviceclass")?.Value ?? "0"),
|
|
new System.Security.Claims.Claim("rn.env", "prod")
|
|
}),
|
|
Issuer = DBConfig.TokenIssuer,
|
|
Expires = now.AddHours(1),
|
|
SigningCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(
|
|
new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(key),
|
|
Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256Signature)
|
|
}));
|
|
|
|
return Ok(new
|
|
{
|
|
success = true,
|
|
value = new
|
|
{
|
|
photonAuthToken = photonToken,
|
|
photonRealtimeAppId = DBConfig.PhotonRealtimeAppId,
|
|
photonVoiceAppId = DBConfig.PhotonVoiceAppId,
|
|
photonChatAppId = DBConfig.PhotonChatAppId,
|
|
photonRegion = DBConfig.PhotonRegion,
|
|
photonRoomId,
|
|
voiceConnectionInfo = string.Empty,
|
|
voiceServerId = string.Empty,
|
|
experiments = new
|
|
{
|
|
networkTransformSyncInterval = 10.0,
|
|
shouldUseUnreliableOnChange = false,
|
|
shouldAvoidDiscontinuityRPCs = true,
|
|
shouldAvoidRedundantDiscontinuity = false,
|
|
r2RuntimeStaticBaking = true,
|
|
r2AutoEmbodiment = true,
|
|
r2RuntimeStaticBakingMinShapeThreshold = 1,
|
|
r2UseCheapReplicas = true,
|
|
shouldUseGameServerNetworking = false
|
|
}
|
|
},
|
|
error = (string?)null
|
|
});
|
|
}
|
|
|
|
[HttpGet("avoidjuniors"), Authorize]
|
|
public IActionResult GetAvoidJuniors()
|
|
{
|
|
return Ok(false);
|
|
}
|
|
|
|
[HttpPut("avoidjuniors"), Authorize]
|
|
public IActionResult SetAvoidJuniors()
|
|
{
|
|
return Ok(false);
|
|
}
|
|
|
|
[HttpPut("statusvisibility"), Authorize]
|
|
public async Task<IActionResult> SetStatusVisibility(
|
|
StatusVisibility statusVisibility)
|
|
{
|
|
long? nullableUserId = User.GetUserId();
|
|
|
|
if (!nullableUserId.HasValue)
|
|
{
|
|
return Unauthorized("Missing user ID.");
|
|
}
|
|
|
|
long userId = nullableUserId.Value;
|
|
|
|
var presence = await db.Presences
|
|
.FirstOrDefaultAsync(x => x.playerId == userId);
|
|
|
|
if (presence == null)
|
|
{
|
|
return NotFound("Player presence does not exist.");
|
|
}
|
|
|
|
presence.statusVisibility = statusVisibility;
|
|
|
|
await db.SaveChangesAsync();
|
|
await Notify.UpdateSubscriptions(userId);
|
|
|
|
return Ok(new { });
|
|
}
|
|
|
|
[HttpPost("heartbeat"), Authorize]
|
|
public async Task<IActionResult> Heartbeat()
|
|
{
|
|
long? nullableUserId = User.GetUserId();
|
|
|
|
if (!nullableUserId.HasValue)
|
|
{
|
|
return Unauthorized("Missing user ID.");
|
|
}
|
|
|
|
long userId = nullableUserId.Value;
|
|
|
|
try
|
|
{
|
|
return await HeartbeatCore(userId);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
|
|
return Ok(new PlayerHeartbeat
|
|
{
|
|
playerId = userId,
|
|
isOnline = true,
|
|
deviceClass = DeviceClass.Screen,
|
|
statusVisibility = StatusVisibility.Public,
|
|
roomInstance = null
|
|
});
|
|
}
|
|
}
|
|
|
|
private async Task<IActionResult> HeartbeatCore(long userId)
|
|
{
|
|
var presence = await db.Presences
|
|
.FirstOrDefaultAsync(x => x.playerId == userId);
|
|
|
|
if (presence == null)
|
|
{
|
|
|
|
presence = new RealPresence { playerId = userId };
|
|
db.Presences.Add(presence);
|
|
|
|
await MatchmakingUtils.EnsureDormPresenceAsync(db, userId, presence);
|
|
}
|
|
|
|
RoomInstance? roomInstance = null;
|
|
|
|
if (presence.roomInstance.HasValue)
|
|
{
|
|
long roomInstanceId = presence.roomInstance.Value;
|
|
|
|
roomInstance = await db.RoomInstances
|
|
.FirstOrDefaultAsync(
|
|
x => x.roomInstanceId == roomInstanceId
|
|
);
|
|
|
|
if (roomInstance == null)
|
|
{
|
|
presence.roomInstance = null;
|
|
MatchmakingUtils.ClearInRoom(userId);
|
|
}
|
|
}
|
|
|
|
if (roomInstance != null)
|
|
{
|
|
roomInstance.eventId ??= 0;
|
|
roomInstance.clubId ??= 0;
|
|
roomInstance.roomCode ??= string.Empty;
|
|
roomInstance.location ??= string.Empty;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (roomInstance != null)
|
|
{
|
|
var currentRoom = await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomInstance.roomId);
|
|
bool isOwnDorm = currentRoom != null && currentRoom.IsDorm && currentRoom.CreatorAccountId == userId;
|
|
if (!isOwnDorm)
|
|
{
|
|
var ban = await db.ResolveBanAsync(userId, Request.GetClientIp());
|
|
|
|
if (ban != null && !await db.IsDeveloper(userId))
|
|
{
|
|
await PlayerUtils.EjectToDormAsync(db, userId);
|
|
roomInstance = presence.roomInstance.HasValue
|
|
? await db.RoomInstances.FirstOrDefaultAsync(x => x.roomInstanceId == presence.roomInstance.Value)
|
|
: null;
|
|
|
|
await Notify.SendNotify(userId, new Notification
|
|
{
|
|
Id = NotificationType.ModerationKick,
|
|
Msg = await PlayerUtils.BuildBanBlockDetailsAsync(db, ban, userId)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
presence.isOnline = true;
|
|
presence.deviceClass = DeviceClass.Screen;
|
|
|
|
await db.SaveChangesAsync();
|
|
|
|
if (roomInstance != null)
|
|
{
|
|
try
|
|
{
|
|
var xpRoom = await db.Rooms.AsNoTracking()
|
|
.FirstOrDefaultAsync(x => x.RoomId == roomInstance.roomId);
|
|
bool publicRoom = xpRoom != null && !xpRoom.IsDorm &&
|
|
xpRoom.Accessibility == RadicadeServer.RecNet.Enums.Accessibility.Public;
|
|
await RoomProgressionService.AwardRoomXpAsync(db, userId, roomInstance.roomId, publicRoom);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
|
|
var heartbeat = new PlayerHeartbeat
|
|
{
|
|
deviceClass = presence.deviceClass,
|
|
isOnline = true,
|
|
playerId = userId,
|
|
roomInstance = roomInstance,
|
|
statusVisibility = presence.statusVisibility
|
|
};
|
|
|
|
if (roomInstance != null)
|
|
{
|
|
try
|
|
{
|
|
var presenceUpdate =
|
|
await Presence.CreateAsync(db, presence, roomInstance);
|
|
|
|
await Notify.SendNotify(
|
|
userId,
|
|
new Notification
|
|
{
|
|
Id = "PresenceUpdate",
|
|
Msg = presenceUpdate
|
|
}
|
|
);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
await Notify.UpdateSubscriptions(userId);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
|
|
return Ok(heartbeat);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[HttpPost("notifydisconnect"), Authorize]
|
|
public async Task<IActionResult> NotifyDisconnect(
|
|
[FromForm(Name = "playerId")] long playerId = 0,
|
|
[FromForm(Name = "loginLock")] string? loginLock = null)
|
|
{
|
|
var requesterId = User.GetUserId();
|
|
if (!requesterId.HasValue) return Unauthorized();
|
|
|
|
long targetId = playerId > 0 ? playerId : 0;
|
|
|
|
|
|
|
|
if (targetId == 0 && !string.IsNullOrWhiteSpace(loginLock))
|
|
{
|
|
var byLock = await db.Presences.FirstOrDefaultAsync(x => x.LoginLock == loginLock);
|
|
if (byLock != null) targetId = byLock.playerId;
|
|
}
|
|
|
|
if (targetId == 0) targetId = requesterId.Value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var ban = await db.ResolveBanAsync(targetId, Request.GetClientIp());
|
|
if (ban == null) return Ok();
|
|
|
|
|
|
|
|
await PlayerUtils.EjectToDormAsync(db, targetId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (targetId != requesterId.Value)
|
|
{
|
|
await Notify.SendNotify(targetId, new Notification
|
|
{
|
|
Id = NotificationType.ModerationKick,
|
|
Msg = await PlayerUtils.BuildBanBlockDetailsAsync(db, ban, targetId)
|
|
});
|
|
}
|
|
|
|
return Ok();
|
|
}
|
|
|
|
[HttpPost("login"), Authorize]
|
|
public async Task<IActionResult> Login(
|
|
[FromForm(Name = "LoginLock")]
|
|
string? loginLock = null,
|
|
|
|
[FromForm(Name = "identification")]
|
|
string? identification = null,
|
|
|
|
[FromForm(Name = "JoinMode")]
|
|
int joinMode = 0,
|
|
|
|
[FromForm(Name = "BypassMovementModeRestriction")]
|
|
bool bypassMovementModeRestriction = false)
|
|
{
|
|
long? nullableUserId = User.GetUserId();
|
|
|
|
if (!nullableUserId.HasValue)
|
|
{
|
|
|
|
return Unauthorized(new
|
|
{
|
|
error = "invalid_token",
|
|
error_description = "Missing user ID."
|
|
});
|
|
}
|
|
|
|
long userId = nullableUserId.Value;
|
|
|
|
string? platformId = User.GetPlatId();
|
|
|
|
if (string.IsNullOrWhiteSpace(platformId))
|
|
{
|
|
|
|
return Unauthorized(new
|
|
{
|
|
error = "invalid_token",
|
|
error_description = "Missing platform ID."
|
|
});
|
|
}
|
|
|
|
bool isWhitelisted;
|
|
|
|
try
|
|
{
|
|
isWhitelisted =
|
|
await AuthShared.IsWhitelisted(platformId);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
|
|
return StatusCode(
|
|
StatusCodes.Status500InternalServerError,
|
|
new
|
|
{
|
|
error = "server_error",
|
|
error_description =
|
|
"Whitelist check failed."
|
|
}
|
|
);
|
|
}
|
|
|
|
if (!isWhitelisted)
|
|
{
|
|
|
|
return Unauthorized(new
|
|
{
|
|
error = "not_whitelisted",
|
|
error_description =
|
|
"This account is not whitelisted."
|
|
});
|
|
}
|
|
|
|
var presence = await db.Presences
|
|
.FirstOrDefaultAsync(
|
|
x => x.playerId == userId
|
|
);
|
|
|
|
if (presence == null)
|
|
{
|
|
|
|
presence = new RealPresence
|
|
{
|
|
playerId = userId
|
|
};
|
|
|
|
db.Presences.Add(presence);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(loginLock))
|
|
{
|
|
presence.LoginLock = loginLock;
|
|
}
|
|
|
|
presence.isOnline = true;
|
|
presence.deviceClass = DeviceClass.Screen;
|
|
|
|
|
|
|
|
|
|
|
|
if (!presence.roomInstance.HasValue)
|
|
{
|
|
await MatchmakingUtils.EnsureDormPresenceAsync(
|
|
db,
|
|
userId,
|
|
presence);
|
|
}
|
|
else
|
|
{
|
|
presence.isOnline = true;
|
|
}
|
|
|
|
if (presence.roomInstance.HasValue)
|
|
{
|
|
MatchmakingUtils.MarkInRoom(userId);
|
|
}
|
|
else
|
|
{
|
|
MatchmakingUtils.ClearInRoom(userId);
|
|
}
|
|
|
|
await db.SaveChangesAsync();
|
|
|
|
try
|
|
{
|
|
await Notify.UpdateSubscriptions(userId);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
|
|
return Ok(new
|
|
{
|
|
success = true,
|
|
playerId = userId,
|
|
accountId = userId,
|
|
platformId,
|
|
loginLock = presence.LoginLock ??
|
|
string.Empty
|
|
});
|
|
}
|
|
|
|
[HttpPost("exclusivelogin"), Authorize]
|
|
public Task<IActionResult> ExclusiveLogin(
|
|
[FromForm(Name = "LoginLock")]
|
|
string? loginLock = null,
|
|
|
|
[FromForm(Name = "identification")]
|
|
string? identification = null,
|
|
|
|
[FromForm(Name = "JoinMode")]
|
|
int joinMode = 0,
|
|
|
|
[FromForm(Name = "BypassMovementModeRestriction")]
|
|
bool bypassMovementModeRestriction = false)
|
|
{
|
|
return Login(
|
|
loginLock,
|
|
identification,
|
|
joinMode,
|
|
bypassMovementModeRestriction);
|
|
}
|
|
|
|
[HttpPost("logout"), Authorize]
|
|
public async Task<IActionResult> Logout(
|
|
[FromForm(Name = "LoginLock")]
|
|
string? loginLock = null)
|
|
{
|
|
long? nullableUserId = User.GetUserId();
|
|
|
|
if (!nullableUserId.HasValue)
|
|
{
|
|
return Ok();
|
|
}
|
|
|
|
long userId = nullableUserId.Value;
|
|
|
|
var presence = await db.Presences
|
|
.FirstOrDefaultAsync(
|
|
x => x.playerId == userId
|
|
);
|
|
|
|
if (presence == null)
|
|
{
|
|
MatchmakingUtils.ClearInRoom(userId);
|
|
return Ok();
|
|
}
|
|
|
|
presence.LoginLock = null;
|
|
presence.isOnline = false;
|
|
presence.roomInstance = null;
|
|
|
|
MatchmakingUtils.ClearInRoom(userId);
|
|
|
|
await db.SaveChangesAsync();
|
|
|
|
var subscriberUpdate = Notify.UpdateSubscriptions(userId);
|
|
try
|
|
{
|
|
await Notify.SendNotify(userId, new Notification
|
|
{
|
|
Id = "PresenceUpdate",
|
|
Msg = await Presence.CreateAsync(db, presence, null)
|
|
});
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
|
|
await subscriberUpdate;
|
|
|
|
return Ok();
|
|
}
|
|
}
|
|
}
|