server-master/srcs/_plugins/WingsEmu.Plugins.BasicImplementation/Managers/SessionManager.cs
2026-02-10 18:21:30 +01:00

451 lines
No EOL
13 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using PhoenixLib.Caching;
using PhoenixLib.Logging;
using WingsAPI.Communication;
using WingsAPI.Communication.DbServer.CharacterService;
using WingsAPI.Communication.Player;
using WingsAPI.Data.Character;
using WingsEmu.Game.Managers;
using WingsEmu.Game.Networking;
using WingsEmu.Game.Networking.Broadcasting;
using WingsEmu.Packets;
namespace WingsEmu.Plugins.BasicImplementations.Managers;
public class SessionManager : ISessionManager
{
private static readonly IPacketSerializer Serializer = new PacketSerializer();
private readonly ILongKeyCachedRepository<ClusterCharacterInfo> _characterCache;
private readonly ConcurrentDictionary<string, ClusterCharacterInfo> _characterCacheByName = new();
private readonly ICharacterService _characterService;
private readonly ConcurrentDictionary<long, bool> _isOnlineByCharacterId = new();
private readonly ConcurrentDictionary<string, bool> _isOnlineByCharacterName = new();
private readonly ReaderWriterLockSlim _lock = new();
private readonly ConcurrentQueue<string> _queue = new();
private readonly IServerManager _serverManager;
private readonly List<IClientSession> _sessionsAll = new();
private readonly ConcurrentDictionary<long, IClientSession> _sessionsByCharacterId = new();
private readonly ConcurrentDictionary<string, IClientSession> _sessionsByCharacterName = new();
public SessionManager(ILongKeyCachedRepository<ClusterCharacterInfo> characterCache, IServerManager serverManager, ICharacterService characterService)
{
_characterCache = characterCache;
_serverManager = serverManager;
_characterService = characterService;
}
private IReadOnlyDictionary<long, IClientSession> SessionsByCharacterId => _sessionsByCharacterId;
private IReadOnlyDictionary<string, IClientSession> SessionsByCharacterName => _sessionsByCharacterName;
public IReadOnlyList<IClientSession> Sessions
{
get
{
_lock.EnterReadLock();
try
{
return _sessionsAll.ToArray();
}
finally
{
_lock.ExitReadLock();
}
}
}
public int SessionsCount => _sessionsByCharacterId.Count;
public async ValueTask<ClusterCharacterInfo> GetOnlineCharacterById(long characterId)
{
return await _characterCache.GetOrSetAsync(characterId, () => FetchDelegate(characterId), TimeSpan.FromHours(2));
}
public ClusterCharacterInfo GetOnlineCharacterByName(string characterName) => _characterCacheByName.TryGetValue(characterName, out ClusterCharacterInfo characterInfo) ? characterInfo : null;
public bool IsOnline(string charName) => _isOnlineByCharacterName.ContainsKey(charName);
public bool IsOnline(long characterId) => _isOnlineByCharacterId.ContainsKey(characterId);
public void AddOnline(ClusterCharacterInfo clusterCharacterInfo)
{
long characterId = clusterCharacterInfo.Id;
string charName = clusterCharacterInfo.Name;
_isOnlineByCharacterId[characterId] = true;
_isOnlineByCharacterName[charName] = true;
_characterCache.Set(clusterCharacterInfo.Id, clusterCharacterInfo, TimeSpan.FromHours(2));
_characterCacheByName[charName] = clusterCharacterInfo;
}
public void RemoveOnline(string charName, long characterId)
{
_isOnlineByCharacterId.Remove(characterId, out _);
_isOnlineByCharacterName.Remove(charName, out _);
_characterCache.Remove(characterId);
_characterCacheByName.Remove(charName, out _);
}
public async Task DisconnectAllAsync()
{
foreach (IClientSession session in Sessions.ToArray())
{
if (session == null)
{
continue;
}
if (session.HasSelectedCharacter)
{
if (session.PlayerEntity.Hp < 1)
{
session.PlayerEntity.Hp = 1;
}
}
Log.Info($"[SESSION_DISCONNECT] {session.SessionId}:{session.PlayerEntity?.Name}");
session.ForceDisconnect();
}
}
public IClientSession GetSessionByCharacterName(string name) => SessionsByCharacterName.TryGetValue(name, out IClientSession session) ? session : null;
public async Task KickAsync(string characterName)
{
IClientSession session = GetSessionByCharacterName(characterName);
session?.ForceDisconnect();
}
public async Task KickAsync(long accountId)
{
IClientSession session = Sessions.FirstOrDefault(s => s.Account != null && s.Account.Id == accountId);
session?.ForceDisconnect();
}
public IClientSession GetSessionByCharacterId(long characterId) => SessionsByCharacterId.TryGetValue(characterId, out IClientSession session) ? session : null;
public void Broadcast(string packet)
{
_queue.Enqueue(packet);
}
public void Broadcast<T>(T packet, params IBroadcastRule[] rules) where T : IServerPacket
{
_lock.EnterReadLock();
try
{
foreach (IClientSession session in _sessionsAll)
{
if (session is null)
{
continue;
}
bool all = true;
foreach (IBroadcastRule x in rules)
{
if (!x.Match(session))
{
all = false;
break;
}
}
if (!rules.Any() || all)
{
session.SendPacket(packet);
}
}
}
finally
{
_lock.ExitReadLock();
}
}
public void Broadcast(string packet, params IBroadcastRule[] rules)
{
_lock.EnterReadLock();
try
{
foreach (IClientSession session in _sessionsAll)
{
if (session is null)
{
continue;
}
bool all = true;
foreach (IBroadcastRule x in rules)
{
if (!x.Match(session))
{
all = false;
break;
}
}
if (!rules.Any() || all)
{
session.SendPacket(packet);
}
}
}
finally
{
_lock.ExitReadLock();
}
}
public void Broadcast(IEnumerable<string> packets)
{
foreach (string packet in packets)
{
_queue.Enqueue(packet);
}
}
public void Broadcast(IEnumerable<string> packets, params IBroadcastRule[] rules)
{
_lock.EnterReadLock();
try
{
foreach (IClientSession session in _sessionsAll)
{
if (session is null)
{
continue;
}
bool all = true;
foreach (IBroadcastRule x in rules)
{
if (!x.Match(session))
{
all = false;
break;
}
}
if (!rules.Any() || all)
{
session.SendPackets(packets);
}
}
}
finally
{
_lock.ExitReadLock();
}
}
public void Broadcast(Func<IClientSession, string> generatePacketCallback)
{
_lock.EnterReadLock();
try
{
foreach (IClientSession session in _sessionsAll)
{
if (session is null)
{
continue;
}
session.SendPacket(generatePacketCallback(session));
}
}
finally
{
_lock.ExitReadLock();
}
}
public void Broadcast(Func<IClientSession, string> generatePacketCallback, params IBroadcastRule[] rules)
{
_lock.EnterReadLock();
try
{
foreach (IClientSession session in _sessionsAll)
{
if (session is null)
{
continue;
}
bool all = true;
foreach (IBroadcastRule x in rules)
{
if (x.Match(session))
{
continue;
}
all = false;
break;
}
if (!rules.Any() || all)
{
session.SendPacket(generatePacketCallback(session));
}
}
}
finally
{
_lock.ExitReadLock();
}
}
public async Task BroadcastAsync(Func<IClientSession, Task<string>> lambdaAsync)
{
foreach (IClientSession session in Sessions)
{
if (session is null)
{
continue;
}
session.SendPacket(await lambdaAsync(session));
}
}
public async Task BroadcastAsync(Func<IClientSession, Task<string>> generatePacketCallback, params IBroadcastRule[] rules)
{
foreach (IClientSession session in Sessions)
{
if (session is null)
{
continue;
}
bool all = true;
foreach (IBroadcastRule x in rules)
{
if (!x.Match(session))
{
all = false;
break;
}
}
if (!rules.Any() || all)
{
session.SendPacket(await generatePacketCallback(session));
}
}
}
public void Broadcast<T>(T packet) where T : IPacket
{
Broadcast(Serializer.Serialize(packet));
}
public virtual void RegisterSession(IClientSession session)
{
if (!session.HasSelectedCharacter)
{
return;
}
long id = session.PlayerEntity.Id;
string name = session.PlayerEntity.Name;
if (!_sessionsByCharacterId.TryGetValue(id, out _))
{
_sessionsByCharacterId.TryAdd(session.PlayerEntity.Id, session);
}
else
{
_sessionsByCharacterId.TryRemove(session.PlayerEntity.Id, out _);
_sessionsByCharacterId.TryAdd(session.PlayerEntity.Id, session);
}
if (!_sessionsByCharacterName.TryGetValue(name, out _))
{
_sessionsByCharacterName.TryAdd(session.PlayerEntity.Name, session);
}
else
{
_sessionsByCharacterName.TryRemove(session.PlayerEntity.Name, out _);
_sessionsByCharacterName.TryAdd(session.PlayerEntity.Name, session);
}
_lock.EnterWriteLock();
try
{
_sessionsAll.Add(session);
}
finally
{
_lock.ExitWriteLock();
}
_serverManager.TryStart();
}
public virtual void UnregisterSession(IClientSession session)
{
_sessionsByCharacterId.TryRemove(session.PlayerEntity.Id, out _);
_sessionsByCharacterName.TryRemove(session.PlayerEntity.Name, out _);
_lock.EnterWriteLock();
try
{
_sessionsAll.Remove(session);
}
finally
{
_lock.ExitWriteLock();
}
if (_sessionsByCharacterId.IsEmpty)
{
_serverManager.PutIdle();
}
}
private async Task<ClusterCharacterInfo> FetchDelegate(long characterId)
{
DbServerGetCharacterResponse response = null;
try
{
response = await _characterService.GetCharacterById(new DbServerGetCharacterByIdRequest
{
CharacterId = characterId
});
}
catch (Exception e)
{
Log.Error("[SESSION_MANAGER] Unexpected error: ", e);
}
if (response?.RpcResponseType == RpcResponseType.GENERIC_SERVER_ERROR)
{
Log.Error("[SESSION_MANAGER] FamilyManager", new InvalidOperationException($"Database corrupted: {characterId.ToString()} seems to have been removed but still used somewhere"));
}
if (response?.RpcResponseType != RpcResponseType.SUCCESS)
{
return null;
}
CharacterDTO characterDto = response.CharacterDto;
return new ClusterCharacterInfo
{
Id = characterId,
Name = characterDto.Name,
Gender = characterDto.Gender,
Class = characterDto.Class,
Level = characterDto.Level,
HeroLevel = characterDto.HeroLevel,
MorphId = null,
ChannelId = null
};
}
}