From 6a99392e34a1d6c6047ee5320226157ca7d733d7 Mon Sep 17 00:00:00 2001 From: "tom.hempel" Date: Wed, 22 Oct 2025 13:04:15 +0200 Subject: [PATCH] added peer discovery settings to NetworkConfig and updated UDP broadcasting scripts to handle peer discovery events --- .../Multiplayer/ConvaiSimpleUDPAudioSender.cs | 33 ++ .../Multiplayer/ConvaiUDPSpeechSender.cs | 33 ++ .../Scripts/Multiplayer/UDPPeerDiscovery.cs | 460 ++++++++++++++++++ .../Multiplayer/UDPPeerDiscovery.cs.meta | 12 + .../Assets/Scripts/NetworkConfig.asset | 4 +- Unity-Master/Assets/Scripts/NetworkConfig.cs | 13 + .../Assets/Scripts/UDPAvatarBroadcaster.cs | 33 ++ .../Scripts/UDPAvatarBroadcasterAgent.cs | 33 ++ 8 files changed, 619 insertions(+), 2 deletions(-) create mode 100644 Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs create mode 100644 Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs.meta diff --git a/Unity-Master/Assets/Scripts/Multiplayer/ConvaiSimpleUDPAudioSender.cs b/Unity-Master/Assets/Scripts/Multiplayer/ConvaiSimpleUDPAudioSender.cs index 910f0a1..9f41b27 100644 --- a/Unity-Master/Assets/Scripts/Multiplayer/ConvaiSimpleUDPAudioSender.cs +++ b/Unity-Master/Assets/Scripts/Multiplayer/ConvaiSimpleUDPAudioSender.cs @@ -88,6 +88,14 @@ namespace Convai.Scripts.Runtime.Multiplayer { targetIP = cfg.ipAddress; targetPort = cfg.port; + + // Subscribe to peer discovery if enabled + if (cfg.useAutoDiscovery && UDPPeerDiscovery.Instance != null) + { + UDPPeerDiscovery.Instance.OnPeerDiscovered += HandlePeerDiscovered; + UDPPeerDiscovery.Instance.OnPeerLost += HandlePeerLost; + ConvaiLogger.Info("Audio sender subscribed to peer discovery", ConvaiLogger.LogCategory.Character); + } } else { @@ -130,6 +138,13 @@ namespace Convai.Scripts.Runtime.Multiplayer private void OnDestroy() { + // Unsubscribe from peer discovery + if (UDPPeerDiscovery.Instance != null) + { + UDPPeerDiscovery.Instance.OnPeerDiscovered -= HandlePeerDiscovered; + UDPPeerDiscovery.Instance.OnPeerLost -= HandlePeerLost; + } + StopRecording(); _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); @@ -143,6 +158,24 @@ namespace Convai.Scripts.Runtime.Multiplayer _udpClient?.Close(); } + private void HandlePeerDiscovered(string peerIP) + { + targetIP = peerIP; + _targetEndPoint = new IPEndPoint(IPAddress.Parse(peerIP), targetPort); + ConvaiLogger.Info($"🎤 Audio sender now targeting peer at {peerIP}:{targetPort}", ConvaiLogger.LogCategory.Character); + } + + private void HandlePeerLost() + { + var cfg = NetworkConfig.Instance; + if (cfg != null) + { + targetIP = cfg.fallbackBroadcastIP; + _targetEndPoint = new IPEndPoint(IPAddress.Parse(targetIP), targetPort); + ConvaiLogger.Warn($"🎤 Audio sender falling back to broadcast: {targetIP}", ConvaiLogger.LogCategory.Character); + } + } + private void InitializeNetwork() { try diff --git a/Unity-Master/Assets/Scripts/Multiplayer/ConvaiUDPSpeechSender.cs b/Unity-Master/Assets/Scripts/Multiplayer/ConvaiUDPSpeechSender.cs index 759077a..6496f74 100644 --- a/Unity-Master/Assets/Scripts/Multiplayer/ConvaiUDPSpeechSender.cs +++ b/Unity-Master/Assets/Scripts/Multiplayer/ConvaiUDPSpeechSender.cs @@ -59,6 +59,14 @@ namespace Convai.Scripts.Runtime.Multiplayer { targetIP = cfg.ipAddress; targetPort = cfg.port; + + // Subscribe to peer discovery if enabled + if (cfg.useAutoDiscovery && UDPPeerDiscovery.Instance != null) + { + UDPPeerDiscovery.Instance.OnPeerDiscovered += HandlePeerDiscovered; + UDPPeerDiscovery.Instance.OnPeerLost += HandlePeerLost; + ConvaiLogger.Info("Speech sender subscribed to peer discovery", ConvaiLogger.LogCategory.Character); + } } else { @@ -73,10 +81,35 @@ namespace Convai.Scripts.Runtime.Multiplayer private void OnDestroy() { + // Unsubscribe from peer discovery + if (UDPPeerDiscovery.Instance != null) + { + UDPPeerDiscovery.Instance.OnPeerDiscovered -= HandlePeerDiscovered; + UDPPeerDiscovery.Instance.OnPeerLost -= HandlePeerLost; + } + CleanupNPCSubscriptions(); CleanupNetwork(); } + private void HandlePeerDiscovered(string peerIP) + { + targetIP = peerIP; + _targetEndPoint = new IPEndPoint(IPAddress.Parse(peerIP), targetPort); + ConvaiLogger.Info($"🔊 Speech sender now targeting peer at {peerIP}:{targetPort}", ConvaiLogger.LogCategory.Character); + } + + private void HandlePeerLost() + { + var cfg = NetworkConfig.Instance; + if (cfg != null) + { + targetIP = cfg.fallbackBroadcastIP; + _targetEndPoint = new IPEndPoint(IPAddress.Parse(targetIP), targetPort); + ConvaiLogger.Warn($"🔊 Speech sender falling back to broadcast: {targetIP}", ConvaiLogger.LogCategory.Character); + } + } + private void InitializeNetwork() { try diff --git a/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs b/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs new file mode 100644 index 0000000..aa486f9 --- /dev/null +++ b/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs @@ -0,0 +1,460 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Convai.Scripts.Runtime.LoggerSystem; +using UnityEngine; + +namespace Convai.Scripts.Runtime.Multiplayer +{ + /// + /// UDP Peer Discovery - Finds other Quest Pro on the same hotspot network + /// Solves AP isolation problem by discovering peer IP and switching to direct unicast + /// + public class UDPPeerDiscovery : MonoBehaviour + { + [Header("Player Configuration")] + [SerializeField] private byte localPlayerID = 1; // 1 or 2 - must be unique per Quest Pro + [SerializeField] private bool enableDebugLogging = true; + + [Header("Discovery Settings")] + [SerializeField] private float discoveryBroadcastInterval = 2.0f; // Broadcast every 2 seconds + [SerializeField] private float heartbeatInterval = 5.0f; // Heartbeat every 5 seconds + [SerializeField] private float peerTimeoutSeconds = 15.0f; // Consider peer lost after 15s + + // Events + public event Action OnPeerDiscovered; // Fires when peer IP is found + public event Action OnPeerLost; // Fires when peer connection is lost + public event Action OnConnectionStateChanged; + + // Network components + private UdpClient _udpClient; + private bool _isRunning = false; + private int _listenPort; + private CancellationTokenSource _cancellationTokenSource; + + // Peer state + private string _peerIP = ""; + private byte _peerPlayerID = 0; + private DateTime _lastPeerPacketTime; + private ConnectionState _connectionState = ConnectionState.Disconnected; + + // Discovery protocol constants + private const uint DISCOVERY_MAGIC = 0x44495343; // "DISC" in hex + private const byte PACKET_TYPE_REQUEST = 0x01; + private const byte PACKET_TYPE_RESPONSE = 0x02; + private const byte PACKET_TYPE_HEARTBEAT = 0x03; + + // Singleton for easy access + private static UDPPeerDiscovery _instance; + public static UDPPeerDiscovery Instance => _instance; + + public enum ConnectionState + { + Disconnected, + Discovering, + Connected, + Lost + } + + // Public properties + public string PeerIP => _peerIP; + public byte LocalPlayerID => localPlayerID; + public byte PeerPlayerID => _peerPlayerID; + public ConnectionState CurrentState => _connectionState; + public bool IsConnected => _connectionState == ConnectionState.Connected; + + private void Awake() + { + // Singleton pattern + if (_instance != null && _instance != this) + { + Destroy(gameObject); + return; + } + _instance = this; + } + + private void Start() + { + // Get network config + var cfg = NetworkConfig.Instance; + if (cfg != null) + { + _listenPort = cfg.port; + } + else + { + ConvaiLogger.Error("NetworkConfig not found! Using default port 1221", ConvaiLogger.LogCategory.Character); + _listenPort = 1221; + } + + _cancellationTokenSource = new CancellationTokenSource(); + StartDiscovery(); + } + + private void OnDestroy() + { + StopDiscovery(); + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + + if (_instance == this) + _instance = null; + } + + private void Update() + { + // Check for peer timeout + if (_connectionState == ConnectionState.Connected) + { + TimeSpan timeSinceLastPacket = DateTime.UtcNow - _lastPeerPacketTime; + if (timeSinceLastPacket.TotalSeconds > peerTimeoutSeconds) + { + HandlePeerLost(); + } + } + } + + private void StartDiscovery() + { + if (_isRunning) return; + + try + { + // Create UDP client with port reuse for shared port + _udpClient = new UdpClient(); + _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, _listenPort)); + _udpClient.EnableBroadcast = true; + + _isRunning = true; + SetConnectionState(ConnectionState.Discovering); + + ConvaiLogger.Info($"🔍 Peer Discovery started - Player {localPlayerID} on port {_listenPort}", ConvaiLogger.LogCategory.Character); + + // Start listening for discovery packets + _ = ListenForDiscoveryPackets(_cancellationTokenSource.Token); + + // Start broadcasting discovery requests + _ = BroadcastDiscoveryRequests(_cancellationTokenSource.Token); + + // Start heartbeat when connected + _ = SendHeartbeats(_cancellationTokenSource.Token); + } + catch (Exception ex) + { + ConvaiLogger.Error($"Failed to start peer discovery: {ex.Message}", ConvaiLogger.LogCategory.Character); + } + } + + private void StopDiscovery() + { + if (!_isRunning) return; + + _isRunning = false; + _udpClient?.Close(); + _udpClient?.Dispose(); + _udpClient = null; + + SetConnectionState(ConnectionState.Disconnected); + ConvaiLogger.Info("Peer Discovery stopped", ConvaiLogger.LogCategory.Character); + } + + private async Task ListenForDiscoveryPackets(CancellationToken cancellationToken) + { + while (_isRunning && !cancellationToken.IsCancellationRequested) + { + try + { + var result = await _udpClient.ReceiveAsync(); + await ProcessDiscoveryPacket(result.Buffer, result.RemoteEndPoint); + } + catch (ObjectDisposedException) + { + // Normal when stopping + break; + } + catch (Exception ex) + { + if (_isRunning) + { + ConvaiLogger.Error($"Error receiving discovery packet: {ex.Message}", ConvaiLogger.LogCategory.Character); + } + } + } + } + + private async Task BroadcastDiscoveryRequests(CancellationToken cancellationToken) + { + while (_isRunning && !cancellationToken.IsCancellationRequested) + { + try + { + // Only broadcast if we're still discovering + if (_connectionState == ConnectionState.Discovering || _connectionState == ConnectionState.Lost) + { + await SendDiscoveryRequest(); + } + + await Task.Delay((int)(discoveryBroadcastInterval * 1000), cancellationToken); + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + ConvaiLogger.Error($"Error broadcasting discovery: {ex.Message}", ConvaiLogger.LogCategory.Character); + } + } + } + + private async Task SendHeartbeats(CancellationToken cancellationToken) + { + while (_isRunning && !cancellationToken.IsCancellationRequested) + { + try + { + await Task.Delay((int)(heartbeatInterval * 1000), cancellationToken); + + // Only send heartbeat if connected + if (_connectionState == ConnectionState.Connected && !string.IsNullOrEmpty(_peerIP)) + { + await SendHeartbeat(); + } + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + ConvaiLogger.Error($"Error sending heartbeat: {ex.Message}", ConvaiLogger.LogCategory.Character); + } + } + } + + private async Task SendDiscoveryRequest() + { + try + { + byte[] packet = CreateDiscoveryPacket(PACKET_TYPE_REQUEST); + + // Broadcast to subnet (will be blocked by AP isolation but we try anyway) + var broadcastEndPoint = new IPEndPoint(IPAddress.Broadcast, _listenPort); + await _udpClient.SendAsync(packet, packet.Length, broadcastEndPoint); + + if (enableDebugLogging && UnityEngine.Random.value < 0.1f) // Log 10% of broadcasts to reduce spam + { + ConvaiLogger.DebugLog($"🔍 Broadcasting discovery request (Player {localPlayerID})", ConvaiLogger.LogCategory.Character); + } + } + catch (Exception ex) + { + ConvaiLogger.Error($"Failed to send discovery request: {ex.Message}", ConvaiLogger.LogCategory.Character); + } + } + + private async Task SendDiscoveryResponse(IPEndPoint targetEndPoint) + { + try + { + byte[] packet = CreateDiscoveryPacket(PACKET_TYPE_RESPONSE); + await _udpClient.SendAsync(packet, packet.Length, targetEndPoint); + + if (enableDebugLogging) + { + ConvaiLogger.DebugLog($"📤 Sent discovery response to {targetEndPoint.Address}", ConvaiLogger.LogCategory.Character); + } + } + catch (Exception ex) + { + ConvaiLogger.Error($"Failed to send discovery response: {ex.Message}", ConvaiLogger.LogCategory.Character); + } + } + + private async Task SendHeartbeat() + { + try + { + if (string.IsNullOrEmpty(_peerIP)) return; + + byte[] packet = CreateDiscoveryPacket(PACKET_TYPE_HEARTBEAT); + var peerEndPoint = new IPEndPoint(IPAddress.Parse(_peerIP), _listenPort); + await _udpClient.SendAsync(packet, packet.Length, peerEndPoint); + } + catch (Exception ex) + { + ConvaiLogger.Warn($"Failed to send heartbeat: {ex.Message}", ConvaiLogger.LogCategory.Character); + } + } + + private byte[] CreateDiscoveryPacket(byte packetType) + { + byte[] packet = new byte[14]; + int offset = 0; + + // Magic number + BitConverter.GetBytes(DISCOVERY_MAGIC).CopyTo(packet, offset); + offset += 4; + + // Packet type + packet[offset] = packetType; + offset += 1; + + // Player ID + packet[offset] = localPlayerID; + offset += 1; + + // Timestamp + long timestamp = DateTime.UtcNow.Ticks; + BitConverter.GetBytes(timestamp).CopyTo(packet, offset); + + return packet; + } + + private async Task ProcessDiscoveryPacket(byte[] data, IPEndPoint senderEndPoint) + { + try + { + // Check if this is a discovery packet + if (data.Length < 14) return; + + // Verify magic number + uint magic = BitConverter.ToUInt32(data, 0); + if (magic != DISCOVERY_MAGIC) return; + + // Parse packet + byte packetType = data[4]; + byte senderPlayerID = data[5]; + long timestamp = BitConverter.ToInt64(data, 6); + + // Ignore packets from ourselves + if (senderPlayerID == localPlayerID) return; + + string senderIP = senderEndPoint.Address.ToString(); + + if (enableDebugLogging) + { + string typeStr = packetType switch + { + PACKET_TYPE_REQUEST => "REQUEST", + PACKET_TYPE_RESPONSE => "RESPONSE", + PACKET_TYPE_HEARTBEAT => "HEARTBEAT", + _ => "UNKNOWN" + }; + ConvaiLogger.DebugLog($"📥 Received {typeStr} from Player {senderPlayerID} at {senderIP}", ConvaiLogger.LogCategory.Character); + } + + // Update peer info + _lastPeerPacketTime = DateTime.UtcNow; + + switch (packetType) + { + case PACKET_TYPE_REQUEST: + // Respond to discovery request + await SendDiscoveryResponse(senderEndPoint); + + // If we weren't connected before, this is a new peer + if (_connectionState != ConnectionState.Connected) + { + HandlePeerDiscovered(senderIP, senderPlayerID); + } + break; + + case PACKET_TYPE_RESPONSE: + // Discovery response received - we found our peer! + if (_connectionState != ConnectionState.Connected) + { + HandlePeerDiscovered(senderIP, senderPlayerID); + } + break; + + case PACKET_TYPE_HEARTBEAT: + // Heartbeat keeps connection alive + if (_connectionState == ConnectionState.Connected && _peerIP == senderIP) + { + // Connection still alive + } + else if (_connectionState != ConnectionState.Connected) + { + // Reconnected + HandlePeerDiscovered(senderIP, senderPlayerID); + } + break; + } + } + catch (Exception ex) + { + ConvaiLogger.Error($"Error processing discovery packet: {ex.Message}", ConvaiLogger.LogCategory.Character); + } + } + + private void HandlePeerDiscovered(string peerIP, byte peerPlayerID) + { + _peerIP = peerIP; + _peerPlayerID = peerPlayerID; + _lastPeerPacketTime = DateTime.UtcNow; + + SetConnectionState(ConnectionState.Connected); + + ConvaiLogger.Info($"✅ Peer discovered! Player {peerPlayerID} at {peerIP}", ConvaiLogger.LogCategory.Character); + + // Notify listeners + OnPeerDiscovered?.Invoke(peerIP); + } + + private void HandlePeerLost() + { + ConvaiLogger.Warn($"⚠️ Peer connection lost (Player {_peerPlayerID} at {_peerIP})", ConvaiLogger.LogCategory.Character); + + string lostPeerIP = _peerIP; + _peerIP = ""; + _peerPlayerID = 0; + + SetConnectionState(ConnectionState.Lost); + + // Notify listeners + OnPeerLost?.Invoke(); + + // Restart discovery + SetConnectionState(ConnectionState.Discovering); + } + + private void SetConnectionState(ConnectionState newState) + { + if (_connectionState != newState) + { + _connectionState = newState; + OnConnectionStateChanged?.Invoke(newState); + + if (enableDebugLogging) + { + ConvaiLogger.Info($"🔄 Connection state changed to: {newState}", ConvaiLogger.LogCategory.Character); + } + } + } + + // Public methods for manual control + public void RestartDiscovery() + { + ConvaiLogger.Info("Manually restarting peer discovery", ConvaiLogger.LogCategory.Character); + _peerIP = ""; + _peerPlayerID = 0; + SetConnectionState(ConnectionState.Discovering); + } + + public void ShowStatus() + { + ConvaiLogger.Info($"=== Peer Discovery Status ===", ConvaiLogger.LogCategory.Character); + ConvaiLogger.Info($"Local Player ID: {localPlayerID}", ConvaiLogger.LogCategory.Character); + ConvaiLogger.Info($"Connection State: {_connectionState}", ConvaiLogger.LogCategory.Character); + ConvaiLogger.Info($"Peer IP: {(_peerIP ?? "None")}", ConvaiLogger.LogCategory.Character); + ConvaiLogger.Info($"Peer Player ID: {_peerPlayerID}", ConvaiLogger.LogCategory.Character); + ConvaiLogger.Info($"Listen Port: {_listenPort}", ConvaiLogger.LogCategory.Character); + } + } +} + diff --git a/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs.meta b/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs.meta new file mode 100644 index 0000000..6a6ef96 --- /dev/null +++ b/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a8b9c1d2e3f4a5b6c7d8e9f0a1b2c3d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Unity-Master/Assets/Scripts/NetworkConfig.asset b/Unity-Master/Assets/Scripts/NetworkConfig.asset index 1e5c4a8..358fcda 100644 --- a/Unity-Master/Assets/Scripts/NetworkConfig.asset +++ b/Unity-Master/Assets/Scripts/NetworkConfig.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17852fdd649ea85c2e4ac65f7d4426459d7466e27b5a807f5663811bee8b3c6b -size 443 +oid sha256:54d451cef255fb6db3a3a443957065843bf5aa42025cebc34890aa3933c4e5d9 +size 559 diff --git a/Unity-Master/Assets/Scripts/NetworkConfig.cs b/Unity-Master/Assets/Scripts/NetworkConfig.cs index 8b35c98..b9681ce 100644 --- a/Unity-Master/Assets/Scripts/NetworkConfig.cs +++ b/Unity-Master/Assets/Scripts/NetworkConfig.cs @@ -6,6 +6,19 @@ public class NetworkConfig : ScriptableObject [Header("Global Network Settings")] public string ipAddress = "127.0.0.1"; public int port = 1221; // Single port for all UDP communication + + [Header("Peer Discovery Settings")] + [Tooltip("Enable auto-discovery to find peer Quest Pro on same network")] + public bool useAutoDiscovery = true; + + [Tooltip("Fallback broadcast IP if discovery fails (default: 255.255.255.255)")] + public string fallbackBroadcastIP = "255.255.255.255"; + + [Tooltip("How often to broadcast discovery requests (seconds)")] + public float discoveryIntervalSeconds = 2.0f; + + [Tooltip("Time before considering peer lost (seconds)")] + public float peerTimeoutSeconds = 15.0f; private static NetworkConfig _instance; public static NetworkConfig Instance diff --git a/Unity-Master/Assets/Scripts/UDPAvatarBroadcaster.cs b/Unity-Master/Assets/Scripts/UDPAvatarBroadcaster.cs index 7343f97..229ee79 100644 --- a/Unity-Master/Assets/Scripts/UDPAvatarBroadcaster.cs +++ b/Unity-Master/Assets/Scripts/UDPAvatarBroadcaster.cs @@ -92,6 +92,14 @@ public class UDPAvatarBroadcaster : MonoBehaviour { broadcastAddress = cfg.ipAddress; broadcastPort = cfg.port; + + // Subscribe to peer discovery if enabled + if (cfg.useAutoDiscovery && Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance != null) + { + Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerDiscovered += HandlePeerDiscovered; + Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerLost += HandlePeerLost; + Debug.Log("Avatar broadcaster subscribed to peer discovery"); + } } else { @@ -444,6 +452,13 @@ public class UDPAvatarBroadcaster : MonoBehaviour void OnDestroy() { + // Unsubscribe from peer discovery + if (Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance != null) + { + Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerDiscovered -= HandlePeerDiscovered; + Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerLost -= HandlePeerLost; + } + if (udpClient != null) { udpClient.Close(); @@ -451,6 +466,24 @@ public class UDPAvatarBroadcaster : MonoBehaviour } } + private void HandlePeerDiscovered(string peerIP) + { + broadcastAddress = peerIP; + broadcastEndPoint = new IPEndPoint(IPAddress.Parse(peerIP), broadcastPort); + Debug.Log($"👤 Avatar broadcaster now targeting peer at {peerIP}:{broadcastPort}"); + } + + private void HandlePeerLost() + { + var cfg = NetworkConfig.Instance; + if (cfg != null) + { + broadcastAddress = cfg.fallbackBroadcastIP; + broadcastEndPoint = new IPEndPoint(IPAddress.Parse(broadcastAddress), broadcastPort); + Debug.LogWarning($"👤 Avatar broadcaster falling back to broadcast: {broadcastAddress}"); + } + } + void OnApplicationPause(bool pauseStatus) { enableBroadcast = !pauseStatus; diff --git a/Unity-Master/Assets/Scripts/UDPAvatarBroadcasterAgent.cs b/Unity-Master/Assets/Scripts/UDPAvatarBroadcasterAgent.cs index eef2dad..24de793 100644 --- a/Unity-Master/Assets/Scripts/UDPAvatarBroadcasterAgent.cs +++ b/Unity-Master/Assets/Scripts/UDPAvatarBroadcasterAgent.cs @@ -95,6 +95,14 @@ public class UDPAvatarBroadcasterAgent : MonoBehaviour { broadcastAddress = cfg.ipAddress; broadcastPort = cfg.port; + + // Subscribe to peer discovery if enabled + if (cfg.useAutoDiscovery && Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance != null) + { + Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerDiscovered += HandlePeerDiscovered; + Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerLost += HandlePeerLost; + Debug.Log("Avatar broadcaster agent subscribed to peer discovery"); + } } else { @@ -447,6 +455,13 @@ public class UDPAvatarBroadcasterAgent : MonoBehaviour void OnDestroy() { + // Unsubscribe from peer discovery + if (Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance != null) + { + Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerDiscovered -= HandlePeerDiscovered; + Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerLost -= HandlePeerLost; + } + if (udpClient != null) { udpClient.Close(); @@ -454,6 +469,24 @@ public class UDPAvatarBroadcasterAgent : MonoBehaviour } } + private void HandlePeerDiscovered(string peerIP) + { + broadcastAddress = peerIP; + broadcastEndPoint = new IPEndPoint(IPAddress.Parse(peerIP), broadcastPort); + Debug.Log($"👤 Avatar broadcaster agent now targeting peer at {peerIP}:{broadcastPort}"); + } + + private void HandlePeerLost() + { + var cfg = NetworkConfig.Instance; + if (cfg != null) + { + broadcastAddress = cfg.fallbackBroadcastIP; + broadcastEndPoint = new IPEndPoint(IPAddress.Parse(broadcastAddress), broadcastPort); + Debug.LogWarning($"👤 Avatar broadcaster agent falling back to broadcast: {broadcastAddress}"); + } + } + void OnApplicationPause(bool pauseStatus) { enableBroadcast = !pauseStatus;