#if FISHNET
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using Adrenak.BRW;
using FishNet;
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Transporting;
namespace Adrenak.UniVoice.Networks
{
///
/// This is an implementation of the interface for FishNet.
/// It uses the FishNet to send and receive UniVoice audio data to and from clients.
///
public class FishNetServer : IAudioServer
{
private const string TAG = "[FishNetServer]";
public event Action OnServerStart;
public event Action OnServerStop;
public event Action OnClientVoiceSettingsUpdated;
public List ClientIDs { get; private set; }
public Dictionary ClientVoiceSettings { get; private set; }
private NetworkManager _networkManager;
private List _startedTransports = new();
public FishNetServer()
{
ClientIDs = new List();
ClientVoiceSettings = new Dictionary();
_networkManager = InstanceFinder.NetworkManager;
_networkManager.ServerManager.OnServerConnectionState += OnServerConnectionStateChanged;
_networkManager.ServerManager.OnRemoteConnectionState += OnServerRemoteConnectionStateChanged;
_networkManager.ClientManager.OnClientConnectionState += OnClientConnectionStateChanged;
_networkManager.ServerManager.RegisterBroadcast(OnReceivedMessage, false);
}
public void Dispose()
{
if (_networkManager)
{
_networkManager.ServerManager.OnServerConnectionState -= OnServerConnectionStateChanged;
_networkManager.ServerManager.OnRemoteConnectionState -= OnServerRemoteConnectionStateChanged;
_networkManager.ClientManager.OnClientConnectionState -= OnClientConnectionStateChanged;
_networkManager.ServerManager.UnregisterBroadcast(OnReceivedMessage);
}
OnServerShutdown();
}
private void OnServerStarted()
{
OnServerStart?.Invoke();
}
private void OnServerShutdown()
{
ClientIDs.Clear();
ClientVoiceSettings.Clear();
OnServerStop?.Invoke();
}
private void OnServerRemoteConnectionStateChanged(NetworkConnection connection, RemoteConnectionStateArgs args)
{
if (args.ConnectionState == RemoteConnectionState.Started)
{
OnServerConnected(connection.ClientId);
}
else if (args.ConnectionState == RemoteConnectionState.Stopped)
{
OnServerDisconnected(connection.ClientId);
}
}
private void OnServerConnectionStateChanged(ServerConnectionStateArgs args)
{
// Connection can change for each transport, so we need to track them
if (args.ConnectionState == LocalConnectionState.Started)
{
var wasStarted = _startedTransports.Count != 0;
_startedTransports.Add(args.TransportIndex);
if (!wasStarted)
OnServerStarted();
}
else if (args.ConnectionState == LocalConnectionState.Stopped)
{
_startedTransports.Remove(args.TransportIndex);
if(_startedTransports.Count == 0)
OnServerShutdown();
}
}
private void OnClientConnectionStateChanged(ClientConnectionStateArgs args)
{
if (args.ConnectionState == LocalConnectionState.Started)
{
OnServerConnected(0);
}
else if (args.ConnectionState == LocalConnectionState.Stopped)
{
OnServerDisconnected(0);
}
}
private void OnReceivedMessage(NetworkConnection connection, FishNetBroadcast message, Channel channel)
{
var clientId = connection.ClientId;
var reader = new BytesReader(message.data);
var tag = reader.ReadString();
if (tag.Equals(FishNetBroadcastTags.AUDIO_FRAME))
{
// We start with all the peers except the one that's
// sent the audio
var peersToForwardAudioTo = ClientIDs
.Where(x => x != clientId);
// Check the voice settings of the sender and eliminate any peers the sender
// may have deafened
if (ClientVoiceSettings.TryGetValue(clientId, out var senderSettings))
{
// If the client sending the audio has deafened everyone,
// we simply return. Sender's audio should not be forwarded to anyone.
if (senderSettings.deafenAll)
return;
// Filter the recipient list by removing all peers that the sender has
// deafened using ID
peersToForwardAudioTo = peersToForwardAudioTo
.Where(x => !senderSettings.deafenedPeers.Contains(x));
// Further filter the recipient list by removing peers that the sender has
// deafened using tags
peersToForwardAudioTo = peersToForwardAudioTo.Where(peer =>
{
// Get the voice settings of the peer
if (ClientVoiceSettings.TryGetValue(peer, out var peerVoiceSettings))
{
// Check if sender has not deafened peer using tag
var hasDeafenedPeer = senderSettings.deafenedTags.Intersect(peerVoiceSettings.myTags).Any();
return !hasDeafenedPeer;
}
// If peer doesn't have voice settings, we can keep the peer in the list
return true;
});
}
// We iterate through each recipient peer that the sender wants to send audio to, checking if
// they have muted the sender, before forwarding the audio to them.
foreach (var recipient in peersToForwardAudioTo) {
// Get the settings of a potential recipient
if (ClientVoiceSettings.TryGetValue(recipient, out var recipientSettings)) {
// If a peer has muted everyone, don't send audio
if (recipientSettings.muteAll)
continue;
// If the peers has muted the sender using ID, skip sending audio
if (recipientSettings.mutedPeers.Contains(clientId))
continue;
// If the peer has muted the sender using tag, skip sending audio
if (senderSettings != null && recipientSettings.mutedTags.Intersect(senderSettings.myTags).Any())
continue;
}
SendToClient(recipient, message.data, Channel.Unreliable);
}
}
else if (tag.Equals(FishNetBroadcastTags.VOICE_SETTINGS)) {
// We create the VoiceSettings object by reading from the reader
// and update the peer voice settings map
var muteAll = reader.ReadInt() == 1;
var mutedPeers = reader.ReadIntArray().ToList();
var deafenAll = reader.ReadInt() == 1;
var deafenedPeers = reader.ReadIntArray().ToList();
var myTags = reader.ReadStringArray().ToList();
var mutedTags = reader.ReadStringArray().ToList();
var deafenedTags = reader.ReadStringArray().ToList();
var voiceSettings = new VoiceSettings {
muteAll = muteAll,
mutedPeers = mutedPeers,
deafenAll = deafenAll,
deafenedPeers = deafenedPeers,
myTags = myTags,
mutedTags = mutedTags,
deafenedTags = deafenedTags
};
ClientVoiceSettings[clientId] = voiceSettings;
OnClientVoiceSettingsUpdated?.Invoke();
}
}
private void OnServerConnected(int connId)
{
if (ClientIDs.Contains(connId))
return;
ClientIDs.Add(connId);
Debug.unityLogger.Log(LogType.Log, TAG, $"Client {connId} connected. IDs now: {string.Join(", ", ClientIDs)}");
}
private void OnServerDisconnected(int connId)
{
ClientIDs.Remove(connId);
Debug.unityLogger.Log(LogType.Log, TAG, $"Client {connId} disconnected. IDs now: {string.Join(", ", ClientIDs)}");
}
private void SendToClient(int clientConnId, byte[] bytes, Channel channel)
{
if (!TryGetConnectionToClient(clientConnId, out var connection))
return;
var message = new FishNetBroadcast {data = bytes};
_networkManager.ServerManager.Broadcast(connection, message, false, channel);
}
private bool TryGetConnectionToClient(int desiredClientID, out NetworkConnection resultConnection)
{
resultConnection = null;
foreach (var (clientID, conn) in _networkManager.ServerManager.Clients)
{
if (clientID == desiredClientID)
{
resultConnection = conn;
return true;
}
}
return false;
}
}
}
#endif