Files
Vanadium-2023-2025-Leak-Server/Controllers/Player/MatchController.cs
T
skidfire fe3468c65d yikes,
2026-09-06 19:59:27 -04:00

2049 lines
88 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Vanadium.Auth;
using Vanadium.Classes;
using Vanadium.Classes.DBs;
using Vanadium.Classes.DBs.DBClasses;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
using static Vanadium.Classes.DBs.DBClasses.RoomDBClasses;
using Vanadium.Classes.WebSocket;
using Vanadium.Utils.NotiController;
using Vanadium.Controllers;
using System.Collections.Concurrent;
namespace Vanadium.Controllers
{
[ApiController]
[Route("/match")]
public partial class MatchController : ControllerBase
{
private static readonly ConcurrentDictionary<long, (long RoomId, DateTime MatchmadeAt)> _authorizedRoomSessions = new();
private static readonly ConcurrentDictionary<(long, long), (int Count, DateTime Window)> _inviteRateLimit = new();
private static readonly string PhotonChatAppId = "9c035c47-8725-43d4-8c53-ab45ed381511";
private static readonly Random _photonRng = new Random();
public static long EnsurePlayerDorm(FullPlayer player)
{
long dormRoomId = player.Player.PlayerExtra.DormRoomId;
if (dormRoomId > 0 && RoomDB.DoesPlayerDormExist(player.PlayerId))
return dormRoomId;
var dormRoom = RoomDB.GetPlayerDormRoom(player.PlayerId);
if (dormRoom != null)
{
dormRoomId = dormRoom.RoomId;
}
else
{
dormRoomId = RoomDB.CloneDormRoom(player.PlayerId);
}
PlayerDB.UpdateDormRoomId(player.PlayerId, dormRoomId);
player.Player.PlayerExtra.DormRoomId = dormRoomId;
return dormRoomId;
}
private static void AuthorizeRoomSession(long playerId, long roomId)
{
_authorizedRoomSessions[playerId] = (roomId, DateTime.UtcNow);
}
private static void ClearRoomSession(long playerId)
{
_authorizedRoomSessions.TryRemove(playerId, out _);
}
private static bool IsRoomSessionAuthorized(long playerId, long roomId)
{
if (!_authorizedRoomSessions.TryGetValue(playerId, out var session))
return false;
return session.RoomId == roomId && (DateTime.UtcNow - session.MatchmadeAt).TotalHours < 12;
}
private static void KickPlayer(long playerId)
{
}
private static bool IsBannedFromRoom(Room? room, long playerId)
{
return room?.Roles?.Any(r => r.AccountId == playerId && r.Role == Role.Banned) ?? false;
}
[HttpGet("player")]
public async Task<IActionResult> GetPlayerHeartbeatBulk([FromQuery] List<long> id)
{
var playerId = AuthStuff.GetPlayerId(Request);
if (playerId == null)
return Unauthorized("");
return Ok(PlayerDB.GetPlayerHeartbeatsBulk(id));
}
[HttpPost("roominstance/{roomInstanceId}/reportjoinresult")]
public async Task<IActionResult> ReportJoinResultForRoomInstanceId(ulong roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
long resolvedAccountId = player.PlayerId;
long newLevel = player.Player.Level;
long newXP = player.Player.XP;
_ = NotiController.SendEvent(resolvedAccountId, "PlayerProgressionXPUpdate", new
{
PlayerId = resolvedAccountId,
Level = newLevel,
XP = newXP
});
_ = NotiController.SendEvent(resolvedAccountId, "PlayerProgressionLevelUpdate", new
{
PlayerId = resolvedAccountId,
Level = newLevel,
XP = newXP
});
return Ok();
}
[HttpPut("roominstance/{roomInstanceId}/markprivate")]
public async Task<IActionResult> MarkRoomInstancePrivate(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
Sessions.MarkInstancePrivate(roomInstanceId);
Sessions.FakeInstanceFull(roomInstanceId);
var playersInInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.ToList();
foreach (var fp in playersInInstance)
{
if (fp.Player?.PlayerExtra?.Heartbeat?.roomInstance == null) continue;
fp.Player.PlayerExtra.Heartbeat.roomInstance.isPrivate = true;
PlayerDB.Players.Update(fp);
}
var outsiderIds = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.isOnline == true
&& p.Player.PlayerExtra.Heartbeat.roomInstance?.roomInstanceId != roomInstanceId)
.Select(p => p.PlayerId)
.ToList();
foreach (var fp in playersInInstance)
{
if (fp.Player?.PlayerExtra?.Heartbeat == null) continue;
var hbJson = Newtonsoft.Json.JsonConvert.SerializeObject(new WebsocketEvents.Response
{
Id = NotificationsController.EventTypes.PresenceUpdate,
Msg = fp.Player.PlayerExtra.Heartbeat
});
await NotificationsController.SendToPlayer(fp.PlayerId, hbJson);
await NotificationsController.SendToPlayers(outsiderIds, hbJson);
}
return Ok(new StorefrontsDBClasses.SuccessResult());
}
private static HashSet<long> RecRoomPlusCheckWhitelisgt()
{
var path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "RRPlus.txt");
if (!System.IO.File.Exists(path))
return new HashSet<long>();
var ids = new HashSet<long>();
foreach (var line in System.IO.File.ReadAllLines(path))
{
if (long.TryParse(line.Trim(), out var id))
ids.Add(id);
}
return ids;
}
[HttpGet("rooms/requiring/developer")]
public async Task<IActionResult> RoomsRequiringDeveloper()
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
return Ok(ServerConfig.Bracket);
}
[HttpGet("rooms/requiring/rrplus")]
public async Task<IActionResult> RoomsRequiringRRPlus()
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
return Ok(ServerConfig.Bracket);
}
[HttpPut("player/statusvisibility")]
public IActionResult setStatusVisibilityShit()
{
return Ok(new { success = true });
}
[HttpPut("player/photonregionpings")]
public IActionResult setPhotonRegionShit()
{
return Ok(new { success = true });
}
[HttpGet("room/{roomId}/instances")] // ripped from other code only for reference? i only needed the response data
public async Task<IActionResult> GetRoomInstances(long roomId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var room = RoomDB.GetRoom(roomId);
if (room == null) return NotFound("");
var currentPlayer = PlayerDB.GetCurrentPlayer((long)id);
bool isDeveloper = currentPlayer?.PlayerRoles?.Any(r =>
r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool hasRoomModRole = room.CreatorAccountId == (long)id || (room.Roles?.Any(r =>
r.AccountId == (long)id &&
(int)r.Role >= (int)Role.Moderator) ?? false);
if (!isDeveloper && !hasRoomModRole)
return StatusCode(403, "Permission Denied");
var allPlayers = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance != null
&& p.Player.PlayerExtra.Heartbeat.isOnline)
.ToList();
var instanceGroups = allPlayers
.Where(p => p.Player.PlayerExtra.Heartbeat.roomInstance.roomId == roomId)
.GroupBy(p => p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId)
.ToList();
if (!instanceGroups.Any())
return Ok(Array.Empty<object>());
bool canViewAll = isDeveloper || room.CreatorAccountId == (long)id || (room.Roles?.Any(r =>
r.AccountId == (long)id &&
(int)r.Role >= (int)Role.Moderator) ?? false);
var result = instanceGroups
.Where(g =>
{
var sample = g.First();
var instance = sample.Player.PlayerExtra.Heartbeat.roomInstance;
return canViewAll || instance.isPrivate != true;
})
.OrderByDescending(g => g.Count())
.Select(g =>
{
var sample = g.First().Player.PlayerExtra.Heartbeat.roomInstance;
return new Dictionary<string, object>
{
["RoomInstanceId"] = sample.roomInstanceId,
["RoomId"] = roomId,
["SubRoomId"] = sample.subRoomId,
["IsFull"] = Sessions.IsInstanceActuallyFull(sample.roomInstanceId),
["MaxCapacity"] = sample.maxCapacity,
["IsPrivate"] = sample.isPrivate,
["PlayerIds"] = g.Select(p => p.PlayerId).ToList()
};
})
.ToList();
return Ok(result);
}
[HttpPost("player/exclusivelogin")]
public async Task<IActionResult> PlayerExclusiveLogin()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
await NotificationsController.SendPlayerOnline(id.Value, new MatchDBClasses.PlayerOnlineDTO() { SenderIsMyFavoriteFriend = true });
return Ok();
}
[HttpPost("player/login")]
public async Task<IActionResult> PlayerLogin()
{
var id = AuthStuff.GetPlayerId(Request);
if (id != null)
{
ClearRoomSession(id.Value);
PlayerDB.UpdateLastLoginAt(id.Value);
PlayerDB.UpdatePlayerHeartbeat(id.Value, null, online: true);
var someRequiredSetings = new[]
{
("Recroom.AccountCreation.HasStarted", "True"),
("Recroom.AccountCreation.HasChosenUsername", "False"),
("Recroom.AccountCreation.HasCreatedPassword", "True"),
("Recroom.AccountCreation.HasFinished", "False"),
("TUTORIAL_COMPLETE_MASK", "77")
};
var existingSettings = PlayerDB.GetPlayerSettings(id.Value);
foreach (var (key, value) in someRequiredSetings)
{
if (!existingSettings.Any(s => s.Key == key))
PlayerDB.SetPlayerSetting(key, value, id.Value);
}
}
return Ok();
}
[HttpPost("player/logout")]
public async Task<IActionResult> PlayerLogout()
{
var id = AuthStuff.GetPlayerId(Request);
if (id != null)
{
ClearRoomSession(id.Value);
PlayerDB.UpdatePlayerHeartbeat(id.Value, null, online: false);
}
return Ok();
}
[HttpPost("player/heartbeat")]
public async Task<IActionResult> PostPlayerHeartbeat()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var playerLock = PlayerDB.GetPlayerLock(id.Value);
await playerLock.WaitAsync();
FullPlayer player;
Heartbeat hb;
try
{
player = PlayerDB.Players.FindById(id.Value);
if (player?.Player?.PlayerExtra == null)
return Unauthorized("");
hb = player.Player.PlayerExtra.Heartbeat ?? new Heartbeat();
if (hb.roomInstance != null)
{
long claimedRoomId = hb.roomInstance.roomId;
if (!IsRoomSessionAuthorized(id.Value, claimedRoomId))
{
}
}
hb.playerId = id.Value;
hb.isOnline = true;
hb.errorCode = 0;
hb.photonAuthToken = null;
hb.appVersion = player.Player.PlayerExtra.Heartbeat?.appVersion ?? hb.appVersion;
if (Request.HasFormContentType)
{
var form = await Request.ReadFormAsync();
if (form.TryGetValue("DeviceClass", out var dc) && int.TryParse(dc, out var dcVal))
hb.deviceClass = (DeviceClasses)dcVal;
if (form.TryGetValue("StatusVisibility", out var sv) && int.TryParse(sv, out var svVal))
hb.statusVisibility = (StatusVisibility)svVal;
if (form.TryGetValue("VRMovementMode", out var vrm) && int.TryParse(vrm, out var vrmVal))
hb.vrMovementMode = vrmVal;
}
player.Player.PlayerExtra.Heartbeat = hb;
PlayerDB.Players.Update(player);
await Services.ToxModService.EnsureCurrent(player);
}
finally
{
playerLock.Release();
}
/*var (xpAwarded, leveledUp, newLevel, newXP) = PlayerDB.AwardHeartbeatXP(id.Value);
if (xpAwarded)
{
var progressionPayload = Newtonsoft.Json.JsonConvert.SerializeObject(
WebsocketEvents.CreateLevelUpdateResponse(id.Value, newLevel, newXP));
await NotificationsController.SendToPlayer(id.Value, progressionPayload);
}*/
return Ok(hb);
}
[HttpPost("matchmake/none")]
public async Task<IActionResult> MatchmakeNone()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
ClearRoomSession(id.Value);
var player = PlayerDB.GetCurrentPlayer(id.Value);
if (player != null)
EnsurePlayerDorm(player);
var heartbeat = PlayerDB.GetPlayerHeartbeat(id.Value);
if (heartbeat != null)
await NotificationsController.RefreshHeartbeat(id.Value, heartbeat);
return Ok(heartbeat);
}
[HttpPost("goto/room/{roomName}")]
public async Task<IActionResult> MatchmakeByRoomName(string roomName)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
if (roomName.Equals("DormRoom", StringComparison.OrdinalIgnoreCase))
{
long dormRoomId = EnsurePlayerDorm(player);
AuthorizeRoomSession(player.PlayerId, dormRoomId);
RoomDB.IncrementRoomVisitCount(dormRoomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, dormRoomId);
var dormHeartbeat = Sessions.CreateDorm(player.PlayerId, player.Player.Username, dormRoomId);
if (PlayerDB.IsBanned(player.PlayerId))
dormHeartbeat.roomInstance.maxCapacity = 1;
await NotificationsController.RefreshHeartbeat(player.PlayerId, dormHeartbeat);
return Ok(dormHeartbeat);
}
var room = RoomDB.GetRoomByName(roomName);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
long roomId = room.RoomId;
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.RoomIsPrivate,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
var (joinMode, autoFollow, _, additionalPlayerIds) = await ParseMatchmakeForm(Request);
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
null,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, 0, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/dorm")]
public async Task<IActionResult> MatchmakeDorm()
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
long dormRoomId = EnsurePlayerDorm(player);
AuthorizeRoomSession(player.PlayerId, dormRoomId);
RoomDB.IncrementRoomVisitCount(dormRoomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, dormRoomId);
var heartbeat = Sessions.CreateDorm(player.PlayerId, player.Player.Username, dormRoomId);
if (PlayerDB.IsBanned(player.PlayerId))
heartbeat.roomInstance.maxCapacity = 1;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/event/{eventId}")]
public async Task<IActionResult> MatchmakeEvent(long eventId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
var ev = Vanadium.Classes.DBs.EventDB.GetPlayerEventById(eventId);
if (ev == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchGame,
isOnline = true,
playerId = player.PlayerId
});
long roomId = ev.RoomId;
long? subRoomId = ev.SubRoomId;
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
var (joinMode, autoFollow, _, additionalPlayerIds) = await ParseMatchmakeForm(Request);
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
if (heartbeat?.roomInstance != null)
heartbeat.roomInstance.eventId = eventId;
var fp = PlayerDB.Players.FindById(player.PlayerId);
if (fp?.Player?.PlayerExtra != null)
{
fp.Player.PlayerExtra.Heartbeat = heartbeat;
PlayerDB.Players.Update(fp);
}
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId ?? 0, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/club/{clubId}")]
public async Task<IActionResult> MatchmakeToClub(long clubId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
return StatusCode(403);
var targetClub = Classes.DBs.ClubsDB.GetClub(clubId);
if (targetClub == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchClub,
isOnline = true,
playerId = player.PlayerId
});
long roomId = targetClub.ClubhouseRoomId ?? 0;
if (roomId <= 0)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.ClubHasNoClubhouse,
isOnline = true,
playerId = player.PlayerId
});
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.ClubHasNoClubhouse,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromClub,
isOnline = true,
playerId = player.PlayerId
});
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomId == roomId)
.Select(p => p.Player!.PlayerExtra!.Heartbeat!.roomInstance!)
.GroupBy(ri => ri.roomInstanceId)
.Select(g => g.First())
.OrderByDescending(ri => Sessions.CountPlayersInInstance(ri.roomInstanceId))
.FirstOrDefault();
if (targetInstance != null)
{
if (currentInstanceHb?.roomInstance?.roomInstanceId == targetInstance.roomInstanceId)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AlreadyInTargetInstance,
isOnline = true,
playerId = player.PlayerId
});
if (targetInstance.isFull || Sessions.CountPlayersInInstance(targetInstance.roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.InsufficientSpace,
isOnline = true,
playerId = player.PlayerId
});
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
if (heartbeat == null)
return StatusCode(500);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(heartbeat);
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var createdHeartbeat = Sessions.CreateRoom(player.PlayerId, roomId, joinMode: 0, additionalPlayersAutoFollow: false, roomNameOverride: targetClub.Name + " Clubhouse");
if (createdHeartbeat == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.ClubHasNoClubhouse,
isOnline = true,
playerId = player.PlayerId
});
createdHeartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, createdHeartbeat);
return Ok(createdHeartbeat);
}
private static async Task ShovePlayersToInstance(List<long> playerIds, long roomId, long subRoomId, Heartbeat leaderHeartbeat)
{
foreach (var pid in playerIds)
{
var fp = PlayerDB.Players.FindById(pid);
if (fp == null) continue;
var hb = Sessions.CreateRoom(pid, roomId, subRoomId, joinMode: 0, additionalPlayersAutoFollow: false);
if (leaderHeartbeat.roomInstance != null && hb.roomInstance != null)
{
hb.roomInstance.photonRoomId = leaderHeartbeat.roomInstance.photonRoomId;
hb.roomInstance.photonRegion = leaderHeartbeat.roomInstance.photonRegion;
hb.roomInstance.roomInstanceId = leaderHeartbeat.roomInstance.roomInstanceId;
}
fp.Player.PlayerExtra ??= new PlayerExtra();
fp.Player.PlayerExtra.Heartbeat = hb;
PlayerDB.Players.Update(fp);
await NotificationsController.RefreshHeartbeat(pid, hb);
}
}
private static async Task<(int joinMode, bool autoFollow, bool bypassMovement, List<long> additionalPlayerIds)> ParseMatchmakeForm(HttpRequest request)
{
int joinMode = 0;
bool autoFollow = false;
bool bypassMovement = false;
var additionalPlayerIds = new List<long>();
if (request.HasFormContentType)
{
var form = await request.ReadFormAsync();
if (form.TryGetValue("JoinMode", out var joinModeStr))
int.TryParse(joinModeStr, out joinMode);
if (form.TryGetValue("AdditionalPlayersAutoFollow", out var autoFollowStr))
autoFollow = autoFollowStr.ToString().Equals("True", StringComparison.OrdinalIgnoreCase);
if (form.TryGetValue("BypassMovementModeRestriction", out var bypassStr))
bypassMovement = bypassStr.ToString().Equals("True", StringComparison.OrdinalIgnoreCase);
var additionalKey = form.Keys.FirstOrDefault(k => k.Equals("AdditionalPlayerIds", StringComparison.OrdinalIgnoreCase));
if (additionalKey != null)
{
foreach (var val in form[additionalKey])
{
if (long.TryParse(val?.Trim(), out var pid))
additionalPlayerIds.Add(pid);
}
}
}
return (joinMode, autoFollow, bypassMovement, additionalPlayerIds);
}
[HttpPost("player/notifydisconnect")]
public async Task<IActionResult> NotifyDisconnect()
{
return Ok();
}
[HttpPost("matchmake/instance/{roomInstanceId}")]
public async Task<IActionResult> MatchmakeInstance(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (targetInstance == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchGame,
isOnline = true,
playerId = player.PlayerId
});
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.InsufficientSpace,
isOnline = true,
playerId = player.PlayerId
});
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AlreadyInTargetInstance,
isOnline = true,
playerId = player.PlayerId
});
AuthorizeRoomSession(player.PlayerId, targetInstance.roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
var (_, autoFollowInstance, _, additionalPlayerIdsInstance) = await ParseMatchmakeForm(Request);
if (additionalPlayerIdsInstance.Count > 0 && autoFollowInstance)
await ShovePlayersToInstance(additionalPlayerIdsInstance, targetInstance.roomId, targetInstance.subRoomId, heartbeat);
else if (additionalPlayerIdsInstance.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIdsInstance, targetInstance.roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/v2/instance/{roomInstanceId}")]
public async Task<IActionResult> MatchmakeInstanceV2(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new { success = false, error_id = "Matchmaking.Banned", error = "You are banned.", value = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
}});
}
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (targetInstance == null)
return Ok(new { success = false, error_id = "Matchmaking.NoSuchGame", error = "Instance not found.", value = new Heartbeat { errorCode = MatchmakingErrorCode.NoSuchGame, isOnline = true, playerId = player.PlayerId } });
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new { success = false, error_id = "Matchmaking.InsufficientSpace", error = "Instance is full.", value = new Heartbeat { errorCode = MatchmakingErrorCode.InsufficientSpace, isOnline = true, playerId = player.PlayerId } });
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new { success = false, error_id = "Matchmaking.AlreadyInTargetInstance", error = "Already in this instance.", value = new Heartbeat { errorCode = MatchmakingErrorCode.AlreadyInTargetInstance, isOnline = true, playerId = player.PlayerId } });
AuthorizeRoomSession(player.PlayerId, targetInstance.roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
var (_, autoFollowInstance, _, additionalPlayerIdsInstance) = await ParseMatchmakeForm(Request);
if (additionalPlayerIdsInstance.Count > 0 && autoFollowInstance)
await ShovePlayersToInstance(additionalPlayerIdsInstance, targetInstance.roomId, targetInstance.subRoomId, heartbeat);
else if (additionalPlayerIdsInstance.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIdsInstance, targetInstance.roomId, heartbeat);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = heartbeat });
}
[HttpPost("matchmake/room/{roomId}")]
public async Task<IActionResult> MatchmakeRoomRoomId(long roomId, [FromQuery] long? subRoomId = null)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
var bannedHeartbeat = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
};
return Ok(bannedHeartbeat);
}
var (joinMode, autoFollow, bypassMovement, additionalPlayerIds) = await ParseMatchmakeForm(Request);
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
if (AuthStuff.GetAndEnsureAppVersion(Request) != "20250724" && room.Tags.Any(x => x.Tag.Equals("2025", StringComparison.OrdinalIgnoreCase)))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.UpdateRequired,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
if (room != null)
{
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.RoomIsPrivate,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
}
var currentHb = player.Player?.PlayerExtra?.Heartbeat;
if (bypassMovement && joinMode == 0 && currentHb?.roomInstance != null && currentHb.roomInstance.roomId == roomId && !currentHb.roomInstance.isPrivate)
{
long currentInstanceId = currentHb.roomInstance.roomInstanceId;
var otherInstance = PlayerDB.Players
.FindAll()
.Where(p =>
p.PlayerId != player.PlayerId &&
p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomId == roomId &&
!p.Player.PlayerExtra.Heartbeat.roomInstance.isPrivate &&
p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId != currentInstanceId &&
Sessions.CountPlayersInInstance(p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId) < p.Player.PlayerExtra.Heartbeat.roomInstance.maxCapacity)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (otherInstance != null)
{
var hb = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, otherInstance);
hb.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, hb);
return Ok(hb);
}
joinMode = 1;
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId ?? 0, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/v2/room/{roomId}")]
public async Task<IActionResult> MatchmakeRoomRoomIdV2(long roomId, [FromQuery] long? subRoomId = null)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new { success = false, error_id = "Matchmaking.Banned", error = "You are banned.", value = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
}});
}
var (joinMode, autoFollow, bypassMovement, additionalPlayerIds) = await ParseMatchmakeForm(Request);
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new { success = false, error_id = "Matchmaking.NoSuchRoom", error = "Room not found.", value = new Heartbeat { errorCode = MatchmakingErrorCode.NoSuchRoom, isOnline = true, playerId = player.PlayerId } });
if (AuthStuff.GetAndEnsureAppVersion(Request) != "20250724" && room.Tags.Any(x => x.Tag.Equals("2025", StringComparison.OrdinalIgnoreCase)))
return Ok(new { success = false, error_id = "Matchmaking.UpdateRequired", error = "Update required.", value = new Heartbeat { errorCode = MatchmakingErrorCode.UpdateRequired, isOnline = true, playerId = player.PlayerId } });
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new { success = false, error_id = "Matchmaking.BannedFromRoom", error = "You are banned from this room.", value = new Heartbeat { errorCode = MatchmakingErrorCode.BannedFromRoom, isOnline = true, playerId = player.PlayerId } });
if (room != null)
{
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new { success = false, error_id = "Matchmaking.AccountDoesNotExist", error = "Room has no owner.", value = new Heartbeat { errorCode = MatchmakingErrorCode.AccountDoesNotExist, isOnline = true, playerId = player.PlayerId } });
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new { success = false, error_id = "Matchmaking.DeveloperOnly", error = "This room is developer only.", value = new Heartbeat { errorCode = MatchmakingErrorCode.DeveloperOnly, isOnline = true, playerId = player.PlayerId } });
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new { success = false, error_id = "Matchmaking.RoomIsPrivate", error = "This room is private.", value = new Heartbeat { errorCode = MatchmakingErrorCode.RoomIsPrivate, isOnline = true, playerId = player.PlayerId } });
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new { success = false, error_id = "Matchmaking.JuniorNotAllowed", error = "Juniors are not allowed in this room.", value = new Heartbeat { errorCode = MatchmakingErrorCode.JuniorNotAllowed, isOnline = true, playerId = player.PlayerId } });
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new { success = false, error_id = "Matchmaking.LevelTooLow", error = "Your level is too low.", value = new Heartbeat { errorCode = MatchmakingErrorCode.LevelTooLow, isOnline = true, playerId = player.PlayerId } });
}
}
var currentHb = player.Player?.PlayerExtra?.Heartbeat;
if (bypassMovement && joinMode == 0 && currentHb?.roomInstance != null && currentHb.roomInstance.roomId == roomId && !currentHb.roomInstance.isPrivate)
{
long currentInstanceId = currentHb.roomInstance.roomInstanceId;
var otherInstance = PlayerDB.Players
.FindAll()
.Where(p =>
p.PlayerId != player.PlayerId &&
p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomId == roomId &&
!p.Player.PlayerExtra.Heartbeat.roomInstance.isPrivate &&
p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId != currentInstanceId &&
Sessions.CountPlayersInInstance(p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId) < p.Player.PlayerExtra.Heartbeat.roomInstance.maxCapacity)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (otherInstance != null)
{
var hb = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, otherInstance);
hb.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, hb);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = hb });
}
joinMode = 1;
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId ?? 0, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = heartbeat });
}
[Route("player/avoidjuniors")]
public IActionResult avoidjuniours()
{
return Ok(false);
}
[HttpPost("matchmake/room/{roomId}/{subRoomId}")]
public async Task<IActionResult> MatchmakeRoomWithSubRoom(long roomId, long subRoomId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
var bannedHeartbeat = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
};
return Ok(bannedHeartbeat);
}
var (joinMode, autoFollow, bypassMovement, additionalPlayerIds) = await ParseMatchmakeForm(Request);
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
if (room != null)
{
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.RoomIsPrivate,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/v2/room/{roomId}/{subRoomId}")]
public async Task<IActionResult> MatchmakeRoomWithSubRoomV2(long roomId, long subRoomId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new { success = false, error_id = "Matchmaking.Banned", error = "You are banned.", value = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
}});
}
var (joinMode, autoFollow, bypassMovement, additionalPlayerIds) = await ParseMatchmakeForm(Request);
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new { success = false, error_id = "Matchmaking.NoSuchRoom", error = "Room not found.", value = new Heartbeat { errorCode = MatchmakingErrorCode.NoSuchRoom, isOnline = true, playerId = player.PlayerId } });
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new { success = false, error_id = "Matchmaking.BannedFromRoom", error = "You are banned from this room.", value = new Heartbeat { errorCode = MatchmakingErrorCode.BannedFromRoom, isOnline = true, playerId = player.PlayerId } });
if (room != null)
{
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new { success = false, error_id = "Matchmaking.AccountDoesNotExist", error = "Room has no owner.", value = new Heartbeat { errorCode = MatchmakingErrorCode.AccountDoesNotExist, isOnline = true, playerId = player.PlayerId } });
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new { success = false, error_id = "Matchmaking.DeveloperOnly", error = "This room is developer only.", value = new Heartbeat { errorCode = MatchmakingErrorCode.DeveloperOnly, isOnline = true, playerId = player.PlayerId } });
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new { success = false, error_id = "Matchmaking.RoomIsPrivate", error = "This room is private.", value = new Heartbeat { errorCode = MatchmakingErrorCode.RoomIsPrivate, isOnline = true, playerId = player.PlayerId } });
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new { success = false, error_id = "Matchmaking.JuniorNotAllowed", error = "Juniors are not allowed in this room.", value = new Heartbeat { errorCode = MatchmakingErrorCode.JuniorNotAllowed, isOnline = true, playerId = player.PlayerId } });
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new { success = false, error_id = "Matchmaking.LevelTooLow", error = "Your level is too low.", value = new Heartbeat { errorCode = MatchmakingErrorCode.LevelTooLow, isOnline = true, playerId = player.PlayerId } });
}
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = heartbeat });
}
/* 20230616 & 20250724.01 photons */
private static readonly string[] _fallbackRealtime = new[]
{
"616d6ba8-edb1-47f8-a0de-31c8ce426685",
"a7c732cd-5cea-4839-b0b3-ba8e9af14b06",
"90d94f73-9797-409d-84ab-ed3992177612"
};
private static readonly string[] _fallbackVoice = new[]
{
"b3a7f09a-b167-4ba1-87bb-2603da5a39be",
"7e71f40c-d1fa-4e48-8851-981402e630e5",
"3e010413-c8c8-48df-95cf-bf7f2bf430e6"
};
[HttpGet("player/connection-info")]
public async Task<IActionResult> GetPlayerConnectionInfo([FromQuery] long? roomInstanceId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
if (roomInstanceId == null || roomInstanceId <= 0)
{
return Ok(new
{
success = true,
error = (string?)null,
value = new
{
PhotonRealtimeAppId = _fallbackRealtime[_photonRng.Next(_fallbackRealtime.Length)],
PhotonChatAppId,
PhotonVoiceAppId = _fallbackVoice[_photonRng.Next(_fallbackVoice.Length)],
experiments = new { shouldUseGameServerNetworking = false }
}
});
}
var dbEntry = InstanceDB.Get(roomInstanceId.Value);
if (dbEntry == null)
{
var liveInstance = PlayerDB.Players.FindAll()
.Select(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance)
.FirstOrDefault(ri => ri?.roomInstanceId == roomInstanceId.Value);
if (liveInstance != null)
{
dbEntry = InstanceDB.Upsert(liveInstance, liveInstance.isPrivate);
}
}
if (dbEntry == null)
{
return Ok(new
{
success = true,
error = (string?)null,
value = new
{
PhotonRealtimeAppId = _fallbackRealtime[_photonRng.Next(_fallbackRealtime.Length)],
PhotonChatAppId,
PhotonVoiceAppId = _fallbackVoice[_photonRng.Next(_fallbackVoice.Length)],
experiments = new { shouldUseGameServerNetworking = false }
}
});
}
if (string.IsNullOrEmpty(dbEntry.PhotonRealtimeAppId) || string.IsNullOrEmpty(dbEntry.PhotonVoiceAppId))
{
var (rt, vo) = InstanceDB.AssignPhotonIds(roomInstanceId.Value);
dbEntry.PhotonRealtimeAppId = rt;
dbEntry.PhotonVoiceAppId = vo;
}
var liveInst = PlayerDB.Players.FindAll()
.Select(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance)
.FirstOrDefault(ri => ri?.roomInstanceId == roomInstanceId.Value);
return Ok(new
{
success = true,
error = (string?)null,
value = new
{
PhotonAuthToken = "",
PhotonRealtimeAppId = dbEntry.PhotonRealtimeAppId,
PhotonChatAppId,
PhotonVoiceAppId = dbEntry.PhotonVoiceAppId,
PhotonRegion = liveInst?.photonRegion ?? dbEntry.PhotonRegion ?? "us",
PhotonRoomId = liveInst?.photonRoomId ?? dbEntry.PhotonRoomId,
VoiceConnectionInfo = (string?)null,
VoiceServerId = (string?)null,
experiments = new { shouldUseGameServerNetworking = false }
}
});
}
[HttpPost("invite")]
public async Task<IActionResult> Invite([FromForm] long playerId, [FromForm] long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (playerId <= 0 || roomInstanceId <= 0)
return BadRequest();
var targetPlayer = PlayerDB.Players.FindById(playerId);
if (targetPlayer == null)
return NotFound("");
var rel = FriendsDB.GetRelationships(player.PlayerId).FirstOrDefault(r => r.OtherPlayerID == playerId);
if (rel?.RelationshipType != FriendsDBClasses.RelationshipType.Friend)
return BadRequest();
var heartbeat = PlayerDB.GetPlayerHeartbeat(player.PlayerId);
if (heartbeat?.roomInstance == null || heartbeat.roomInstance.roomInstanceId != roomInstanceId)
return BadRequest();
var key = (player.PlayerId, playerId);
var now = DateTime.UtcNow;
_inviteRateLimit.AddOrUpdate(key, _ => (1, now), (_, e) =>
(now - e.Window).TotalSeconds >= 15 ? (1, now) : (e.Count + 1, e.Window));
if (_inviteRateLimit.TryGetValue(key, out var rate) && rate.Count > 3 && (now - rate.Window).TotalSeconds < 15)
return BadRequest();
var invite = new MatchDBClasses.InviteDTO
{
InviteId = roomInstanceId,
Name = heartbeat.roomInstance.Name ?? "Unknown Room",
InviteMode = 0
};
await NotificationsController.SendInvite(playerId, player.PlayerId, heartbeat.roomInstance.roomId, invite);
return Ok();
}
//[HttpPost("matchmake/instance/{roomInstanceId}")] // added back because some bitch deleted it
[HttpPost("matchmake/invite/{roomInstanceId}")] // ez
//[HttpPost("matchmake/v2/instance/{roomInstanceId}")]
public async Task<IActionResult> MatchmakeToInviteInstance(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (targetInstance == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchGame,
isOnline = true,
playerId = player.PlayerId
});
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.InsufficientSpace,
isOnline = true,
playerId = player.PlayerId
});
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AlreadyInTargetInstance,
isOnline = true,
playerId = player.PlayerId
});
AuthorizeRoomSession(player.PlayerId, targetInstance.roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/v2/invite/{roomInstanceId}")]
public async Task<IActionResult> MatchmakeToInviteInstanceV2(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new { success = false, error_id = "Matchmaking.Banned", error = "You are banned.", value = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
}});
}
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (targetInstance == null)
return Ok(new { success = false, error_id = "Matchmaking.NoSuchGame", error = "Instance not found.", value = new Heartbeat { errorCode = MatchmakingErrorCode.NoSuchGame, isOnline = true, playerId = player.PlayerId } });
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new { success = false, error_id = "Matchmaking.InsufficientSpace", error = "Instance is full.", value = new Heartbeat { errorCode = MatchmakingErrorCode.InsufficientSpace, isOnline = true, playerId = player.PlayerId } });
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new { success = false, error_id = "Matchmaking.AlreadyInTargetInstance", error = "Already in this instance.", value = new Heartbeat { errorCode = MatchmakingErrorCode.AlreadyInTargetInstance, isOnline = true, playerId = player.PlayerId } });
AuthorizeRoomSession(player.PlayerId, targetInstance.roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = heartbeat });
}
[HttpPost("matchmake/player/{targetPlayerId}")]
public async Task<IActionResult> MatchmakeToPlayer(long targetPlayerId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
var targetPlayer = PlayerDB.Players.FindById(targetPlayerId);
if (targetPlayer == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
var targetHb = targetPlayer.Player?.PlayerExtra?.Heartbeat;
if (targetHb == null || !targetHb.isOnline || targetHb.roomInstance == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.PlayerNotOnline,
isOnline = true,
playerId = player.PlayerId
});
var targetInstance = targetHb.roomInstance;
long roomId = targetInstance.roomId;
long roomInstanceId = targetInstance.roomInstanceId;
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.RoomIsPrivate,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.InsufficientSpace,
isOnline = true,
playerId = player.PlayerId
});
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AlreadyInTargetInstance,
isOnline = true,
playerId = player.PlayerId
});
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(heartbeat);
}
[HttpDelete("invite/{inviteId}")]
public async Task<IActionResult> DeclineInvite(long inviteId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
var messages = PlayerDB.GetMessages(player.PlayerId);
var invite = messages.FirstOrDefault(m => m.Id == inviteId);
var invitedata = new MatchDBClasses.InviteDTO
{
InviteId = inviteId,
Name = "Unknown Room",
InviteMode = 0
};
if (invite != null)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(new
{
Id = "2",
Msg = new
{
FromPlayerId = player.PlayerId,
Id = PlayerDB.GenerateRandomMessageId(),
SentTime = DateTime.UtcNow,
Type = MessageType.GameInviteDeclined,
Data = invitedata,
RoomId = invite.RoomId,
PlayerEventId = (long?)null
}
});
await NotificationsController.SendToPlayer(invite.FromPlayerId, json);
PlayerDB.DeleteMessage(player.PlayerId, inviteId);
}
return Ok();
}
}
}