Files
skidfire fe3468c65d yikes,
2026-09-06 19:59:27 -04:00

379 lines
16 KiB
C#

using Vanadium.Classes.DBs;
using Vanadium.Classes.DBs.DBClasses;
using static Vanadium.Classes.DBs.DBClasses.InstanceDBClasses;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
using static Vanadium.Classes.DBs.DBClasses.RoomDBClasses;
using static Vanadium.Classes.ServerConfig;
namespace Vanadium.Classes
{
public class Sessions // yo
{
private static readonly Random _rng = new();
private static readonly object _lock = new();
private static RoomInstance BuildInstance(string roomName, string dataBlob, SubRooms sub, long roomId, bool isPrivate)
{
long id = _rng.NextInt64(100_000_000_000L, long.MaxValue);
var ri = new RoomInstance
{
Name = $"^{roomName}",
dataBlob = dataBlob,
isPrivate = isPrivate,
location = sub.UnitySceneId,
maxCapacity = sub.MaxPlayers,
photonRegion = "us",
photonRegionId = "us",
photonRoomId = $"{roomName}-{id}-room",
roomId = roomId,
roomInstanceId = id,
subRoomId = sub.SubRoomId
};
InstanceDB.Upsert(ri, isPrivate);
return ri;
}
private static SubRooms? ResolveSubRoom(Room roomData, long? subRoomId)
{
if (subRoomId.HasValue)
return roomData.SubRooms?.FirstOrDefault(s => s.SubRoomId == subRoomId.Value);
var publicSubs = roomData.SubRooms?
.Where(s => s.Accessibility == RoomAccessibility.Public)
.ToList();
if (publicSubs?.Count > 0)
return publicSubs[Random.Shared.Next(publicSubs.Count)];
return roomData.SubRooms?.FirstOrDefault();
}
private static void AutoFollowPlayers(long playerId, RoomInstance instance)
{
foreach (var other in PlayerDB.Players.FindAll()
.Where(p => p.PlayerId != playerId &&
p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == instance.roomInstanceId))
PlayerDB.UpdatePlayerHeartbeat(other.PlayerId, instance);
}
private static Heartbeat? ApplyPlayerId(long playerId, Heartbeat? hb)
{
if (hb != null) hb.playerId = playerId;
return hb;
}
private static HashSet<long> GetFriendIds(long playerId)
=> FriendsDB.GetRelationships(playerId)
.Where(r => r.RelationshipType == FriendsDBClasses.RelationshipType.Friend)
.Select(r => r.OtherPlayerID)
.ToHashSet();
private static void Prune(long roomId)
{
foreach (var entry in InstanceDB.GetAllForRoom(roomId))
{
if (InstanceDB.CountPlayers(entry.RoomInstanceId) == 0)
InstanceDB.Delete(entry.RoomInstanceId);
}
}
private static RoomInstance? FindBestPublicInstance(long playerId, long roomId, long? subRoomId, long? excludeInstanceId = null)
{
var friendIds = GetFriendIds(playerId);
var allPlayers = PlayerDB.Players.FindAll()
.Where(p =>
p.Player?.PlayerExtra?.Heartbeat?.isOnline == true &&
p.Player.PlayerExtra.Heartbeat.roomInstance != null &&
p.Player.PlayerExtra.Heartbeat.roomInstance.roomId == roomId &&
!p.Player.PlayerExtra.Heartbeat.roomInstance.isPrivate &&
!p.Player.PlayerExtra.Heartbeat.roomInstance.isFull &&
(excludeInstanceId == null || p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId != excludeInstanceId))
.ToList();
var candidates = allPlayers
.Select(p => p.Player!.PlayerExtra!.Heartbeat!.roomInstance!)
.Where(ri => !subRoomId.HasValue || ri.subRoomId == subRoomId.Value)
.GroupBy(ri => ri.roomInstanceId)
.Select(g => g.First())
.Where(ri => InstanceDB.CountPlayers(ri.roomInstanceId) < ri.maxCapacity)
.ToList();
var friendInstance = candidates
.Where(ri => allPlayers.Any(p =>
friendIds.Contains(p.PlayerId) &&
p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == ri.roomInstanceId))
.OrderByDescending(ri => InstanceDB.CountPlayers(ri.roomInstanceId))
.FirstOrDefault();
return friendInstance ?? candidates
.OrderByDescending(ri => InstanceDB.CountPlayers(ri.roomInstanceId))
.FirstOrDefault();
}
public static void MarkInstanceInactive(long roomInstanceId)
{
lock (_lock)
InstanceDB.Delete(roomInstanceId);
}
public static async Task MarkInstancePrivate(long roomInstanceId)
{
lock (_lock)
InstanceDB.MarkPrivate(roomInstanceId);
foreach (var p in PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId))
{
var hb = p.Player!.PlayerExtra!.Heartbeat!;
hb.playerId = p.PlayerId;
hb.roomInstance!.isPrivate = true;
await Controllers.NotificationsController.RefreshHeartbeat(p.PlayerId, hb);
}
}
public static void FakeInstanceFull(long roomInstanceId)
{
lock (_lock)
InstanceDB.MarkFull(roomInstanceId, true);
}
public static async Task<(int instancesRefreshed, int playersMoved)> RefreshPublicInstances(long roomId, long subRoomId)
{
var roomData = RoomDB.GetRoom(roomId);
if (roomData == null) return (0, 0);
var sub = roomData.SubRooms?.FirstOrDefault(s => s.SubRoomId == subRoomId);
if (sub == null) return (0, 0);
var moved = new List<(long playerId, Heartbeat hb)>();
int refreshed = 0;
lock (_lock)
{
var occupants = PlayerDB.Players.FindAll()
.Where(p =>
p.Player?.PlayerExtra?.Heartbeat?.isOnline == true &&
p.Player.PlayerExtra.Heartbeat.roomInstance != null &&
p.Player.PlayerExtra.Heartbeat.roomInstance.roomId == roomId &&
p.Player.PlayerExtra.Heartbeat.roomInstance.subRoomId == subRoomId &&
!p.Player.PlayerExtra.Heartbeat.roomInstance.isPrivate)
.ToList();
foreach (var group in occupants.GroupBy(p => p.Player!.PlayerExtra!.Heartbeat!.roomInstance!.roomInstanceId))
{
var fresh = BuildInstance(roomData.Name ?? "UnknownRoom", sub.DataBlob ?? "", sub, roomId, isPrivate: false);
foreach (var p in group)
{
var hb = PlayerDB.UpdatePlayerHeartbeat(p.PlayerId, fresh);
if (hb == null) continue;
hb.playerId = p.PlayerId;
moved.Add((p.PlayerId, hb));
}
InstanceDB.Delete(group.Key);
refreshed++;
}
Prune(roomId);
}
foreach (var (playerId, hb) in moved)
await Controllers.NotificationsController.RefreshHeartbeat(playerId, hb);
return (refreshed, moved.Count);
}
public static async Task<int> SetInstancesPrivacy(long roomId, long subRoomId, bool isPrivate)
{
var updated = new List<(long playerId, Heartbeat hb)>();
HashSet<long> instanceIds;
lock (_lock)
{
var occupants = PlayerDB.Players.FindAll()
.Where(p =>
p.Player?.PlayerExtra?.Heartbeat?.isOnline == true &&
p.Player.PlayerExtra.Heartbeat.roomInstance != null &&
p.Player.PlayerExtra.Heartbeat.roomInstance.roomId == roomId &&
p.Player.PlayerExtra.Heartbeat.roomInstance.subRoomId == subRoomId &&
p.Player.PlayerExtra.Heartbeat.roomInstance.isPrivate != isPrivate)
.ToList();
instanceIds = occupants
.Select(p => p.Player!.PlayerExtra!.Heartbeat!.roomInstance!.roomInstanceId)
.ToHashSet();
foreach (var instanceId in instanceIds)
{
if (isPrivate) InstanceDB.MarkPrivate(instanceId);
else InstanceDB.MarkPublic(instanceId);
}
foreach (var p in occupants)
{
var hb = p.Player!.PlayerExtra!.Heartbeat!;
hb.playerId = p.PlayerId;
hb.roomInstance!.isPrivate = isPrivate;
updated.Add((p.PlayerId, hb));
}
}
foreach (var (playerId, hb) in updated)
await Controllers.NotificationsController.RefreshHeartbeat(playerId, hb);
return instanceIds.Count;
}
public static bool IsInstanceActuallyFull(long roomInstanceId)
=> InstanceDB.IsFull(roomInstanceId);
public static int CountPlayersInInstance(long roomInstanceId)
=> InstanceDB.CountPlayers(roomInstanceId);
public static Heartbeat? CreateRoom(
long playerId,
long roomId,
long? subRoomId = null,
string? dataBlobOverride = null,
int joinMode = 0,
bool additionalPlayersAutoFollow = false,
string roomNameOverride = "") // todo remove ^
{
var roomData = RoomDB.GetRoom(roomId);
if (roomData == null) return null;
bool isPrivateInstance = joinMode == 2;
bool isPrivateRoom = roomData.Accessibility == RoomAccessibility.Private;
lock (_lock)
{
Prune(roomId);
if (isPrivateInstance)
{
var sub = ResolveSubRoom(roomData, subRoomId);
if (sub == null) return null;
var solo = BuildInstance(roomData.Name ?? "UnknownRoom", dataBlobOverride ?? sub.DataBlob ?? "", sub, roomId, isPrivate: true);
return ApplyPlayerId(playerId, PlayerDB.UpdatePlayerHeartbeat(playerId, solo));
}
if (isPrivateRoom)
{
var sub = ResolveSubRoom(roomData, subRoomId);
if (sub == null) return null;
var existing = InstanceDB.GetActivePrivate(roomId, sub.SubRoomId);
if (existing != null)
{
int count = InstanceDB.CountPlayers(existing.RoomInstanceId);
if (!existing.IsFull && count < existing.MaxCapacity)
{
var ri = existing.ToRoomInstance();
if (additionalPlayersAutoFollow) AutoFollowPlayers(playerId, ri);
return ApplyPlayerId(playerId, PlayerDB.UpdatePlayerHeartbeat(playerId, ri));
}
}
var liveInstance = PlayerDB.Players.FindAll()
.Where(p =>
p.Player?.PlayerExtra?.Heartbeat?.isOnline == true &&
p.Player.PlayerExtra.Heartbeat.roomInstance?.roomId == roomId &&
p.Player.PlayerExtra.Heartbeat.roomInstance.subRoomId == sub.SubRoomId)
.Select(p => p.Player!.PlayerExtra!.Heartbeat!.roomInstance!)
.GroupBy(ri => ri.roomInstanceId)
.Select(g => g.First())
.Where(ri => !ri.isFull && InstanceDB.CountPlayers(ri.roomInstanceId) < ri.maxCapacity)
.FirstOrDefault();
if (liveInstance != null)
{
InstanceDB.Upsert(liveInstance, isPrivate: true);
if (additionalPlayersAutoFollow) AutoFollowPlayers(playerId, liveInstance);
return ApplyPlayerId(playerId, PlayerDB.UpdatePlayerHeartbeat(playerId, liveInstance));
}
var newInstance = BuildInstance(roomData.Name ?? "UnknownRoom", dataBlobOverride ?? sub.DataBlob ?? "", sub, roomId, isPrivate: true);
return ApplyPlayerId(playerId, PlayerDB.UpdatePlayerHeartbeat(playerId, newInstance));
}
if (joinMode == 1)
{
var currentHb = PlayerDB.GetPlayerHeartbeat(playerId);
long? currentInstanceId = currentHb?.roomInstance?.roomInstanceId;
var different = FindBestPublicInstance(playerId, roomId, subRoomId, excludeInstanceId: currentInstanceId);
if (different != null)
{
if (additionalPlayersAutoFollow) AutoFollowPlayers(playerId, different);
return ApplyPlayerId(playerId, PlayerDB.UpdatePlayerHeartbeat(playerId, different));
}
var sub = ResolveSubRoom(roomData, subRoomId);
if (sub == null) return null;
var fresh = BuildInstance(roomData.Name ?? "UnknownRoom", dataBlobOverride ?? sub.DataBlob ?? "", sub, roomId, isPrivate: false);
return ApplyPlayerId(playerId, PlayerDB.UpdatePlayerHeartbeat(playerId, fresh));
}
var best = FindBestPublicInstance(playerId, roomId, subRoomId);
if (best != null)
{
if (additionalPlayersAutoFollow) AutoFollowPlayers(playerId, best);
return ApplyPlayerId(playerId, PlayerDB.UpdatePlayerHeartbeat(playerId, best));
}
var resolvedSub = ResolveSubRoom(roomData, subRoomId);
if (resolvedSub == null) return null;
var created = BuildInstance(roomData.Name ?? "UnknownRoom", dataBlobOverride ?? resolvedSub.DataBlob ?? "", resolvedSub, roomId, isPrivate: false);
return ApplyPlayerId(playerId, PlayerDB.UpdatePlayerHeartbeat(playerId, created));
}
}
public static Heartbeat CreateDorm(long playerId, string username, long dormRoomId)
{
var dorm = RoomDB.GetRoom(dormRoomId);
var firstSub = dorm?.SubRooms?.FirstOrDefault();
long subRoomId = firstSub?.SubRoomId ?? 0;
lock (_lock)
{
Prune(dormRoomId);
var existing = InstanceDB.GetActivePrivate(dormRoomId, subRoomId);
if (existing != null)
{
int count = InstanceDB.CountPlayers(existing.RoomInstanceId);
if (!existing.IsFull && count < existing.MaxCapacity)
{
var existingRi = existing.ToRoomInstance();
var existingHb = PlayerDB.UpdatePlayerHeartbeat(playerId, existingRi);
existingHb.playerId = playerId;
return existingHb;
}
}
long instanceId = _rng.NextInt64(100_000_000_000L, long.MaxValue);
var session = new RoomInstance
{
isPrivate = true,
location = "76d98498-60a1-430c-ab76-b54a29b7a163",
maxCapacity = 4,
Name = $"@{username}'s Dorm",
photonRegion = "us",
photonRegionId = "us",
photonRoomId = $"DormRoom-{instanceId}-room",
roomId = dormRoomId,
roomInstanceId = instanceId,
subRoomId = subRoomId,
dataBlob = firstSub?.DataBlob ?? ""
};
InstanceDB.Upsert(session, isPrivate: true);
var hb = PlayerDB.UpdatePlayerHeartbeat(playerId, session);
hb.playerId = playerId;
return hb;
}
}
}
}