diff --git a/Assets/Scripts/EOSVoiceChat.cs b/Assets/Scripts/EOSVoiceChat.cs index 5f645d9..d39bbb8 100644 --- a/Assets/Scripts/EOSVoiceChat.cs +++ b/Assets/Scripts/EOSVoiceChat.cs @@ -69,59 +69,68 @@ public class EOSVoiceChat: IDisposable DisconnectChat(); // Leave any currently connected chat lobby var connectArgs = new ChatConnectArgs(chatLobbyName, maxChatPlayers); + + var createSearchOptions = new CreateLobbySearchOptions { MaxResults = 1 }; + lobbyInterface.CreateLobbySearch(ref createSearchOptions, out var searchHandle); - lobbyInterface.CreateLobbySearch(new CreateLobbySearchOptions { MaxResults = 1 }, out var searchHandle); - searchHandle.SetLobbyId(new LobbySearchSetLobbyIdOptions { LobbyId = chatLobbyName }); + var setLobbyIdOptions = new LobbySearchSetLobbyIdOptions { LobbyId = chatLobbyName }; + searchHandle.SetLobbyId(ref setLobbyIdOptions); var localUserId = localUserProvider.Invoke(); if (localUserId == null) return; + + var searchOptions = new LobbySearchFindOptions { LocalUserId = localUserId }; + searchHandle.Find(ref searchOptions, null, LobbySearchOnFindCallback); - searchHandle.Find(new LobbySearchFindOptions { LocalUserId = localUserId }, null, findData => + void LobbySearchOnFindCallback(ref LobbySearchFindCallbackInfo findData) { switch (findData.ResultCode) { case Result.Success: - searchHandle.CopySearchResultByIndex(new LobbySearchCopySearchResultByIndexOptions { LobbyIndex = 0 }, out var lobbyDetails); + var copyOptions = new LobbySearchCopySearchResultByIndexOptions { LobbyIndex = 0 }; + searchHandle.CopySearchResultByIndex(ref copyOptions, out var lobbyDetails); Debug.Log("Found existing chat lobby, joining..."); + var joinLobbyOptions = new JoinLobbyOptions + { + LocalUserId = localUserId, + LobbyDetailsHandle = lobbyDetails, + PresenceEnabled = false, + }; lobbyInterface.JoinLobby ( - new JoinLobbyOptions - { - LocalUserId = localUserId, - LobbyDetailsHandle = lobbyDetails, - PresenceEnabled = false, - }, + ref joinLobbyOptions, connectArgs, HandleLobbyJoined ); break; default: Debug.Log($"Creating new chat lobby..."); + var createLobbyOptions = new CreateLobbyOptions + { + LocalUserId = localUserId, + AllowInvites = false, + PermissionLevel = LobbyPermissionLevel.Publicadvertised, + PresenceEnabled = false, + MaxLobbyMembers = maxChatPlayers, + DisableHostMigration = false, + LobbyId = chatLobbyName, + BucketId = Application.productName, // TODO: do we need anything more specific than this? + EnableRTCRoom = true, + }; lobbyInterface.CreateLobby ( - new CreateLobbyOptions - { - LocalUserId = localUserId, - AllowInvites = false, - PermissionLevel = LobbyPermissionLevel.Publicadvertised, - PresenceEnabled = false, - MaxLobbyMembers = maxChatPlayers, - DisableHostMigration = false, - LobbyId = chatLobbyName, - BucketId = Application.productName, // TODO: do we need anything more specific than this? - EnableRTCRoom = true, - }, + ref createLobbyOptions, connectArgs, HandleLobbyCreated ); break; } - }); + } } - private void HandleLobbyCreated(CreateLobbyCallbackInfo data) + private void HandleLobbyCreated(ref CreateLobbyCallbackInfo data) { var connectArgs = (ChatConnectArgs)data.ClientData; switch (data.ResultCode) @@ -153,9 +162,8 @@ public class EOSVoiceChat: IDisposable } } - private void HandleLobbyJoined(JoinLobbyCallbackInfo data) + private void HandleLobbyJoined(ref JoinLobbyCallbackInfo data) { - var connectArgs = (ChatConnectArgs)data.ClientData; switch (data.ResultCode) { case Result.Success: @@ -180,10 +188,13 @@ public class EOSVoiceChat: IDisposable private void UpdateRTCRoomName() { rtcRoomName = null; - - var result = lobbyInterface.GetRTCRoomName( - new GetRTCRoomNameOptions { LocalUserId = localUserProvider.Invoke(), LobbyId = connectedLobbyId }, - out string roomName); + + var getRoomNameOptions = new GetRTCRoomNameOptions + { + LocalUserId = localUserProvider.Invoke(), + LobbyId = connectedLobbyId + }; + var result = lobbyInterface.GetRTCRoomName(ref getRoomNameOptions, out Utf8String roomName); if (result != Result.Success) { @@ -201,17 +212,15 @@ public class EOSVoiceChat: IDisposable // Unsubscribing first means we don't get a Disconnected notification for intentionally leaving the lobby UnsubscribeFromRoomNotifications(); + + void OnLeaveLobbyCallback(ref LeaveLobbyCallbackInfo data) { } - lobbyInterface.LeaveLobby - ( - new LeaveLobbyOptions - { - LocalUserId = localUserProvider.Invoke(), - LobbyId = connectedLobbyId, - }, - null, - data => { } - ); + var leaveLobbyOptions = new LeaveLobbyOptions + { + LocalUserId = localUserProvider.Invoke(), + LobbyId = connectedLobbyId, + }; + lobbyInterface.LeaveLobby(ref leaveLobbyOptions, null, OnLeaveLobbyCallback); connectedLobbyId = null; rtcRoomName = null; @@ -228,12 +237,15 @@ public class EOSVoiceChat: IDisposable if (!IsConnected) return; - audioInterface.UpdateSending(new UpdateSendingOptions + void OnUpdateSendingCallback(ref UpdateSendingCallbackInfo data) { } + + var updateSendingOptions = new UpdateSendingOptions { LocalUserId = localUserProvider.Invoke(), RoomName = rtcRoomName, AudioStatus = muted ? RTCAudioStatus.Disabled : RTCAudioStatus.Enabled, - }, null, data => { }); + }; + audioInterface.UpdateSending(ref updateSendingOptions, null, OnUpdateSendingCallback); } /// @@ -244,14 +256,17 @@ public class EOSVoiceChat: IDisposable { if (!IsConnected) return; + + void OnUpdateReceivingCallback(ref UpdateReceivingCallbackInfo data) { } - audioInterface.UpdateReceiving(new UpdateReceivingOptions + var updateReceivingOptions = new UpdateReceivingOptions { LocalUserId = localUserProvider.Invoke(), ParticipantId = remoteUser, RoomName = rtcRoomName, AudioEnabled = !muted, - }, null, data => { }); + }; + audioInterface.UpdateReceiving(ref updateReceivingOptions, null, OnUpdateReceivingCallback); } /// @@ -263,16 +278,19 @@ public class EOSVoiceChat: IDisposable if (!IsConnected) return; + void OnUpdateReceivingCallback(ref UpdateReceivingCallbackInfo data) { } + var localUserId = localUserProvider.Invoke(); foreach (var remoteProductUser in chatUsers.Keys) { - audioInterface.UpdateReceiving(new UpdateReceivingOptions + var updateReceivingOptions = new UpdateReceivingOptions { LocalUserId = localUserId, ParticipantId = remoteProductUser, RoomName = rtcRoomName, AudioEnabled = false, - }, null, data => { }); + }; + audioInterface.UpdateReceiving(ref updateReceivingOptions, null, OnUpdateReceivingCallback); } } @@ -282,12 +300,13 @@ public class EOSVoiceChat: IDisposable /// public void SetOutputVolume(float volume) { - audioInterface.SetAudioOutputSettings(new SetAudioOutputSettingsOptions + var setAudioOptions = new SetAudioOutputSettingsOptions { LocalUserId = localUserProvider.Invoke(), - DeviceId = null, // Default output device + DeviceId = null, // Default output device Volume = volume * 50f, - }); + }; + audioInterface.SetAudioOutputSettings(ref setAudioOptions); } /// @@ -308,12 +327,13 @@ public class EOSVoiceChat: IDisposable { if (!onAudioDevicesChangedCallbackId.HasValue) { + var addNotifyOptions = new AddNotifyAudioDevicesChangedOptions(); onAudioDevicesChangedCallbackId = audioInterface.AddNotifyAudioDevicesChanged( - new AddNotifyAudioDevicesChangedOptions(), null, - HandleAudioDevicesChanged); + ref addNotifyOptions, null, HandleAudioDevicesChanged); // Call the event handler once to query the audio devices in their initial state - HandleAudioDevicesChanged(null); + AudioDevicesChangedCallbackInfo callbackInfo; + HandleAudioDevicesChanged(ref callbackInfo); } } @@ -326,38 +346,40 @@ public class EOSVoiceChat: IDisposable } } - private void HandleAudioDevicesChanged(AudioDevicesChangedCallbackInfo data) + private void HandleAudioDevicesChanged(ref AudioDevicesChangedCallbackInfo data) { defaultInputDeviceId = null; var sb = new System.Text.StringBuilder(); // Update the default input device, so we know whether we can actually talk or not - uint inputDevicesCount = audioInterface.GetAudioInputDevicesCount(new GetAudioInputDevicesCountOptions()); + var getInputDevicesOptions = new GetAudioInputDevicesCountOptions(); + uint inputDevicesCount = audioInterface.GetAudioInputDevicesCount(ref getInputDevicesOptions); sb.AppendLine($"Found {inputDevicesCount} audio input device(s):"); for (uint inputDeviceIndex = 0; inputDeviceIndex < inputDevicesCount; ++inputDeviceIndex) { - var inputDeviceInfo = audioInterface.GetAudioInputDeviceByIndex( - new GetAudioInputDeviceByIndexOptions { DeviceInfoIndex = inputDeviceIndex }); - - sb.AppendLine($"Input device {inputDeviceIndex}: ID = {inputDeviceInfo.DeviceId}, Name = {inputDeviceInfo.DeviceName}, Default = {inputDeviceInfo.DefaultDevice}"); + var getDeviceOptions = new GetAudioInputDeviceByIndexOptions { DeviceInfoIndex = inputDeviceIndex }; + var inputDeviceInfo = audioInterface.GetAudioInputDeviceByIndex(ref getDeviceOptions); + + sb.AppendLine($"Input device {inputDeviceIndex}: ID = {inputDeviceInfo?.DeviceId}, Name = {inputDeviceInfo?.DeviceName}, Default = {inputDeviceInfo?.DefaultDevice}"); - if (inputDeviceInfo.DefaultDevice) + if (inputDeviceInfo?.DefaultDevice ?? false) { - defaultInputDeviceId = inputDeviceInfo.DeviceId; + defaultInputDeviceId = inputDeviceInfo.Value.DeviceId; } } - uint outputDevicesCount = audioInterface.GetAudioOutputDevicesCount(new GetAudioOutputDevicesCountOptions()); + var getOutputDevicesOptions = new GetAudioOutputDevicesCountOptions(); + uint outputDevicesCount = audioInterface.GetAudioOutputDevicesCount(ref getOutputDevicesOptions); sb.AppendLine($"Found {outputDevicesCount} audio output device(s):"); for (uint outputDeviceIndex = 0; outputDeviceIndex < outputDevicesCount; ++outputDeviceIndex) { - var outputDeviceInfo = audioInterface.GetAudioOutputDeviceByIndex( - new GetAudioOutputDeviceByIndexOptions { DeviceInfoIndex = outputDeviceIndex }); + var getDeviceOptions = new GetAudioOutputDeviceByIndexOptions { DeviceInfoIndex = outputDeviceIndex }; + var outputDeviceInfo = audioInterface.GetAudioOutputDeviceByIndex(ref getDeviceOptions); - sb.AppendLine($"Output device {outputDeviceIndex}: ID = {outputDeviceInfo.DeviceId}, Name = {outputDeviceInfo.DeviceName}, Default = {outputDeviceInfo.DefaultDevice}"); + sb.AppendLine($"Output device {outputDeviceIndex}: ID = {outputDeviceInfo?.DeviceId}, Name = {outputDeviceInfo?.DeviceName}, Default = {outputDeviceInfo?.DefaultDevice}"); } Debug.Log(sb); @@ -369,13 +391,10 @@ public class EOSVoiceChat: IDisposable if (!onConnectionChangedCallbackId.HasValue) { + var addNotifyOptions = new AddNotifyRTCRoomConnectionChangedOptions(); onConnectionChangedCallbackId = lobbyInterface.AddNotifyRTCRoomConnectionChanged ( - new AddNotifyRTCRoomConnectionChangedOptions - { - LocalUserId = localUserId, - LobbyId = connectedLobbyId, - }, + ref addNotifyOptions, null, HandleConnectionChanged ); @@ -383,13 +402,14 @@ public class EOSVoiceChat: IDisposable if (!onParticipantStatusChangedCallbackId.HasValue) { + var addNotifyOptions = new AddNotifyParticipantStatusChangedOptions + { + LocalUserId = localUserId, + RoomName = rtcRoomName, + }; onParticipantStatusChangedCallbackId = rtcInterface.AddNotifyParticipantStatusChanged ( - new AddNotifyParticipantStatusChangedOptions - { - LocalUserId = localUserId, - RoomName = rtcRoomName, - }, + ref addNotifyOptions, null, HandleParticipantStatusChanged ); @@ -397,13 +417,14 @@ public class EOSVoiceChat: IDisposable if (!onParticipantUpdatedCallbackId.HasValue) { + var addNotifyOptions = new AddNotifyParticipantUpdatedOptions + { + LocalUserId = localUserId, + RoomName = rtcRoomName, + }; onParticipantUpdatedCallbackId = audioInterface.AddNotifyParticipantUpdated ( - new AddNotifyParticipantUpdatedOptions - { - LocalUserId = localUserId, - RoomName = rtcRoomName, - }, + ref addNotifyOptions, null, HandleParticipantUpdated ); @@ -431,8 +452,11 @@ public class EOSVoiceChat: IDisposable } } - private void HandleConnectionChanged(RTCRoomConnectionChangedCallbackInfo data) + private void HandleConnectionChanged(ref RTCRoomConnectionChangedCallbackInfo data) { + if (data.LobbyId != connectedLobbyId) + return; + // Note: reconnecting is handled automatically by the RTC lobby system if (!data.IsConnected && (data.DisconnectReason == Result.UserKicked || data.DisconnectReason == Result.UserBanned)) @@ -442,7 +466,7 @@ public class EOSVoiceChat: IDisposable } } - private void HandleParticipantStatusChanged(ParticipantStatusChangedCallbackInfo data) + private void HandleParticipantStatusChanged(ref ParticipantStatusChangedCallbackInfo data) { switch (data.ParticipantStatus) { @@ -461,7 +485,7 @@ public class EOSVoiceChat: IDisposable } } - private void HandleParticipantUpdated(ParticipantUpdatedCallbackInfo data) + private void HandleParticipantUpdated(ref ParticipantUpdatedCallbackInfo data) { if (chatUsers.TryGetValue(data.ParticipantId, out var chatUser)) { diff --git a/Assets/Scripts/EpicVoiceChatTest.cs b/Assets/Scripts/EpicVoiceChatTest.cs index 6d1678a..e6ffc8d 100644 --- a/Assets/Scripts/EpicVoiceChatTest.cs +++ b/Assets/Scripts/EpicVoiceChatTest.cs @@ -38,11 +38,10 @@ public class EpicVoiceChatTest : MonoBehaviour // This also allows multiple clients to connect to the same lobby without having to communicate the ID among each other. private const string DebugLobbyId = "a0f6a51f-6b61-4c95-9ed8-a1d508fe4eb6"; - [SerializeField] - private string devAuthAddress; - + [SerializeField] private string devAuthAddress; + private readonly StringBuilder status = new StringBuilder("Status:\n"); - + private PlatformInterface platformInterface; private AuthInterface authInterface; private ConnectInterface connectInterface; @@ -64,7 +63,7 @@ public class EpicVoiceChatTest : MonoBehaviour #else Path.Combine(Application.dataPath, @"Plugins\x86_64\xaudio2_9redist.dll"); #endif - + IEnumerator Start() { #if UNITY_EDITOR && UNITY_STANDALONE @@ -76,7 +75,7 @@ public class EpicVoiceChatTest : MonoBehaviour Debug.LogError($"Error initializing game runtime, hresult = 0x{hresult:X}"); yield break; } - + Debug.Log("GXDK game runtime initialized."); hresult = SDK.XBL.XblInitialize(GameCoreSettings.SCID); @@ -125,8 +124,8 @@ public class EpicVoiceChatTest : MonoBehaviour #if !UNITY_STANDALONE EOSNativeHelper.GetMemoryFunctions(out var allocFunc, out var reallocFunc, out var releaseFunc); #endif - - var result = PlatformInterface.Initialize(new InitializeOptions + + var initOptions = new InitializeOptions { ProductName = "WW1Test", ProductVersion = "1.0.0.0", @@ -136,18 +135,19 @@ public class EpicVoiceChatTest : MonoBehaviour ReallocateMemoryFunction = reallocFunc, ReleaseMemoryFunction = releaseFunc, #endif - }); - + }; + var result = PlatformInterface.Initialize(ref initOptions); + if (result != Result.Success) { Debug.LogError("Failed to initialize EOS, result = " + result); status.AppendLine("EOS initialization failed..."); yield break; } - + LoggingInterface.SetLogLevel(LogCategory.AllCategories, LogLevel.Warning); LoggingInterface.SetCallback(OnEOSLogMessage); - + #if UNITY_STANDALONE_WIN Debug.Log("XAudio library path: " + XAudio29DllPath); @@ -184,25 +184,25 @@ public class EpicVoiceChatTest : MonoBehaviour ProductId = "b21a28c2c5404c8099d72f5a28c59c16", SandboxId = "b630f2c3933a4838a971ce53d5b0db3b", DeploymentId = "9a36f589572c492fbee14bd299173c12", - + ClientCredentials = new ClientCredentials { ClientId = "xyza7891UOFoUhfvfbKgO2xRCIiuAIjH", ClientSecret = "NArwIQT1laFfsS7fdcN1MKDdgwy490w9MBJsAlHN4QI", }, - + Flags = PlatformFlags.DisableOverlay, - + CacheDirectory = null, EncryptionKey = null, - + IsServer = false, - + RTCOptions = new RTCOptions(), }; #endif - - platformInterface = PlatformInterface.Create(options); + + platformInterface = PlatformInterface.Create(ref options); if (platformInterface == null) { @@ -210,32 +210,36 @@ public class EpicVoiceChatTest : MonoBehaviour status.AppendLine("Failed to create EOS platform interface"); yield break; } - + Debug.Log("EOS platform interface successfully created!"); status.AppendLine("EOS platform interface created"); authInterface = platformInterface.GetAuthInterface(); status.AppendLine("Auth interface: " + authInterface); - + connectInterface = platformInterface.GetConnectInterface(); status.AppendLine("Connect interface: " + connectInterface); lobbyInterface = platformInterface.GetLobbyInterface(); status.AppendLine("Lobby interface: " + lobbyInterface); - - rtcInterface = platformInterface.GetRTCInterface(); // Real-time communication, needed for audio + + rtcInterface = platformInterface.GetRTCInterface(); // Real-time communication, needed for audio status.AppendLine("RTC interface: " + rtcInterface); - + audioInterface = rtcInterface.GetAudioInterface(); status.AppendLine("Audio interface: " + audioInterface); - + voiceChat = new EOSVoiceChat(lobbyInterface, rtcInterface, audioInterface, () => localProductUserId); voiceChat.OnChatConnected += () => status.AppendLine("Chat lobby successfully connected!"); voiceChat.OnChatConnectionFailed += () => status.AppendLine("Chat lobby connection failed..."); voiceChat.OnChatDisconnected += () => status.AppendLine("Chat lobby disconnected"); - voiceChat.OnChatUserJoined += userId => status.AppendLine($"Chat user {userId} joined"); + voiceChat.OnChatUserJoined += userId => + { + status.AppendLine($"Chat user {userId} joined"); + HandleChatUserJoined(userId); + }; voiceChat.OnChatUserLeft += userId => status.AppendLine($"Chat user {userId} left"); - + #if UNITY_GAMECORE SDK.XUserAddAsync(XUserAddOptions.AddDefaultUserAllowingUI, (hr, userHandle) => { @@ -246,21 +250,25 @@ public class EpicVoiceChatTest : MonoBehaviour return; } - status.AppendLine("Xbox user added"); + SDK.XUserGetId(userHandle, out ulong xuid); + Debug.Log($"Xbox user added with XUID: {xuid}"); + status.AppendLine($"Xbox user added"); + SDK.XUserGetTokenAndSignatureUtf16Async(userHandle, XUserGetTokenAndSignatureOptions.None, "GET", "https://api.epicgames.dev/", null, null, HandleTokenAndSignature); }); - + yield return new WaitUntil(() => !string.IsNullOrEmpty(xstsToken)); - connectInterface.Login(new Epic.OnlineServices.Connect.LoginOptions + var loginOptions = new Epic.OnlineServices.Connect.LoginOptions { Credentials = new Epic.OnlineServices.Connect.Credentials { Type = ExternalCredentialType.XblXstsToken, Token = xstsToken, }, - }, null, HandleConnectResult); + }; + connectInterface.Login(ref loginOptions, null, HandleConnectResult); #elif UNITY_PS4 || UNITY_PS5 var loggedInUser = PSInput.RefreshUsersDetails(0); @@ -292,23 +300,25 @@ public class EpicVoiceChatTest : MonoBehaviour yield return new WaitUntil(() => !string.IsNullOrEmpty(psnIdToken)); - connectInterface.Login(new Epic.OnlineServices.Connect.LoginOptions + var loginOptions = new Epic.OnlineServices.Connect.LoginOptions { Credentials = new Epic.OnlineServices.Connect.Credentials { Type = ExternalCredentialType.PsnIdToken, Token = psnIdToken, }, - }, null, HandleConnectResult); + }; + connectInterface.Login(ref loginOptions, null, HandleConnectResult); #else - authInterface.Login(new LoginOptions + var loginOptions = new LoginOptions { Credentials = GetEpicCredentials(), ScopeFlags = AuthScopeFlags.BasicProfile, - }, null, HandleLoginResult); + }; + authInterface.Login(ref loginOptions, null, HandleLoginResult); #endif } - + #if UNITY_GAMECORE private void HandleTokenAndSignature(int hresult, XUserGetTokenAndSignatureUtf16Data tokenAndSignature) { @@ -318,13 +328,13 @@ public class EpicVoiceChatTest : MonoBehaviour status.AppendLine("Xbox Live authentication failed..."); return; } - + Debug.Log($"Xbox Live authenticated, XSTS token = {tokenAndSignature.Token}"); status.AppendLine("Xbox Live successfully authenticated"); xstsToken = tokenAndSignature.Token; } #endif - + #if UNITY_STANDALONE private Credentials GetEpicCredentials() // This is platform-specific (actually it's not, we can skip this on consoles) { @@ -344,7 +354,7 @@ public class EpicVoiceChatTest : MonoBehaviour }; } - private void HandleLoginResult(LoginCallbackInfo data) + private void HandleLoginResult(ref LoginCallbackInfo data) { switch (data.ResultCode) { @@ -352,18 +362,27 @@ public class EpicVoiceChatTest : MonoBehaviour localEpicAccountId = data.LocalUserId; Debug.Log("EOS login successful: " + localEpicAccountId); status.AppendLine("EOS login successful: " + localEpicAccountId); + + var copyTokenOptions = new CopyUserAuthTokenOptions(); + authInterface.CopyUserAuthToken(ref copyTokenOptions, localEpicAccountId, out Token? token); + if (!token.HasValue) + { + Debug.Log("EOS login failed, invalid token!"); + status.AppendLine("EOS login failed, invalid token!"); + break; + } - authInterface.CopyUserAuthToken(new CopyUserAuthTokenOptions(), localEpicAccountId, out Token token); - Debug.Log($"User auth access token: {token.AccessToken}"); - - connectInterface.Login(new Epic.OnlineServices.Connect.LoginOptions + Debug.Log($"User auth access token: {token.Value.AccessToken}"); + + var loginOptions = new Epic.OnlineServices.Connect.LoginOptions { Credentials = new Epic.OnlineServices.Connect.Credentials { Type = ExternalCredentialType.Epic, // Can be XSTS or PSN ID as well, platform-specific - Token = token.AccessToken, + Token = token.Value.AccessToken, }, - }, null, HandleConnectResult); + }; + connectInterface.Login(ref loginOptions, null, HandleConnectResult); break; default: Debug.Log("EOS login failed, result code = " + data.ResultCode); @@ -373,7 +392,7 @@ public class EpicVoiceChatTest : MonoBehaviour } #endif - private void HandleConnectResult(Epic.OnlineServices.Connect.LoginCallbackInfo data) + private void HandleConnectResult(ref Epic.OnlineServices.Connect.LoginCallbackInfo data) { switch (data.ResultCode) { @@ -381,18 +400,16 @@ public class EpicVoiceChatTest : MonoBehaviour localProductUserId = data.LocalUserId; Debug.Log("Connect successful: " + localProductUserId); status.AppendLine("Connect successful: " + localProductUserId); - + CreateOrJoinVoiceLobby(); break; case Result.InvalidUser: Debug.Log("Invalid user, creating user..."); status.AppendLine("Invalid user, creating user..."); - - connectInterface.CreateUser(new CreateUserOptions - { - ContinuanceToken = data.ContinuanceToken, - }, null, HandleUserCreated); - + + var createUserOptions = new CreateUserOptions { ContinuanceToken = data.ContinuanceToken }; + connectInterface.CreateUser(ref createUserOptions, null, HandleUserCreated); + break; default: Debug.Log("Connect failed, result code = " + data.ResultCode); @@ -401,7 +418,7 @@ public class EpicVoiceChatTest : MonoBehaviour } } - private void HandleUserCreated(CreateUserCallbackInfo data) + private void HandleUserCreated(ref CreateUserCallbackInfo data) { switch (data.ResultCode) { @@ -409,7 +426,7 @@ public class EpicVoiceChatTest : MonoBehaviour localProductUserId = data.LocalUserId; Debug.Log("User creation successful: " + localProductUserId); status.AppendLine("User creation successful: " + localProductUserId); - + CreateOrJoinVoiceLobby(); break; default: @@ -428,7 +445,7 @@ public class EpicVoiceChatTest : MonoBehaviour { if (platformInterface != null) platformInterface.Tick(); - + #if UNITY_GAMECORE SDK.XTaskQueueDispatch(); #elif UNITY_PS4 || UNITY_PS5 @@ -449,9 +466,9 @@ public class EpicVoiceChatTest : MonoBehaviour platformInterface.Release(); platformInterface = null; } - + PlatformInterface.Shutdown(); - + #if UNITY_EDITOR && UNITY_STANDALONE UnloadLibrary(); #endif @@ -464,7 +481,8 @@ public class EpicVoiceChatTest : MonoBehaviour { // EOS SDK 1.13+ uses dynamic library binding in the Editor but does not provide any system functions to actually load dynamic libraries, // so we need to provide those ourselves. - eosLbraryHandle = EOSNativeHelper.LoadLibrary($@"Assets\Plugins\EpicOnlineServices\Bin\{Config.LibraryName}.dll"); + eosLbraryHandle = + EOSNativeHelper.LoadLibrary($@"Assets\Plugins\EpicOnlineServices\Bin\{Config.LibraryName}.dll"); if (eosLbraryHandle == IntPtr.Zero) { throw new Exception("Could not load EOS library!"); @@ -490,7 +508,7 @@ public class EpicVoiceChatTest : MonoBehaviour } #endif - private void OnEOSLogMessage(LogMessage message) + private void OnEOSLogMessage(ref LogMessage message) { switch (message.Level) { @@ -515,7 +533,93 @@ public class EpicVoiceChatTest : MonoBehaviour float screenScale = screenHeight / 720f; GUI.matrix = Matrix4x4.Scale(new Vector3(screenScale, screenScale, 1)); - + GUILayout.Label(status.ToString()); } + + private void HandleChatUserJoined(ProductUserId productUserId) + { + if (connectInterface == null || localProductUserId == null) + return; + + Debug.Log($"Chat product user ID {productUserId} joined, querying account mapping..."); + + // We need to know how to map from remote account GUIDs to Epic's ProductUserId so we can query other players' chat status + var queryMappingsOptions = new QueryProductUserIdMappingsOptions + { + LocalUserId = localProductUserId, + ProductUserIds = new[] { productUserId }, + }; + connectInterface.QueryProductUserIdMappings(ref queryMappingsOptions, productUserId, HandleProductUserIdMappings); + + var queryExternalOptions = new QueryExternalAccountMappingsOptions + { + LocalUserId = localProductUserId, + AccountIdType = ExternalAccountType.Xbl, + ExternalAccountIds = new Utf8String[] { "2814644373145756", "2814654765419620" }, + }; + connectInterface.QueryExternalAccountMappings(ref queryExternalOptions, null, HandleExternalAccountMappings); + } + + private void HandleExternalAccountMappings(ref QueryExternalAccountMappingsCallbackInfo data) + { + switch (data.ResultCode) + { + case Result.Success: + foreach (var accountId in + new[] {"2814644373145756", "2814654765419620"}) + { + var getMappingOptions = new GetExternalAccountMappingsOptions + { + AccountIdType = ExternalAccountType.Xbl, + LocalUserId = localProductUserId, + TargetExternalUserId = accountId + }; + var productUserId = connectInterface.GetExternalAccountMapping(ref getMappingOptions); + + if (productUserId != null) + { + Debug.Log($"Mapped XBL account ID {accountId} to product user ID: {productUserId}"); + } + else + { + Debug.Log($"Failed to map XBL account ID {accountId} to any product user ID"); + } + } + break; + default: + Debug.LogError($"External account mapping query failed with result code: {data.ResultCode}"); + break; + } + } + + private readonly Dictionary steamAccountMappings = new Dictionary(); + private readonly Dictionary epicAccountMappings = new Dictionary(); + private readonly Dictionary xblAccountMappings = new Dictionary(); + private readonly Dictionary psnAccountMappings = new Dictionary(); + + private void HandleProductUserIdMappings(ref QueryProductUserIdMappingsCallbackInfo data) + { + switch (data.ResultCode) + { + case Result.Success: + var productUserId = (ProductUserId)data.ClientData; + + var copyOptions = new CopyProductUserInfoOptions { TargetUserId = productUserId }; + var result = connectInterface.CopyProductUserInfo(ref copyOptions, out var externalAccountInfo); + + if (result == Result.Success) + { + Debug.Log($"External account info for product user {productUserId}: type = {externalAccountInfo?.AccountIdType}, account id = {externalAccountInfo?.AccountId}, display name = {externalAccountInfo?.DisplayName}"); + } + else + { + Debug.LogError($"Failed to query external account info for product user {productUserId}"); + } + break; + default: + Debug.LogError($"Product user ID mapping query failed with result code: {data.ResultCode}"); + break; + } + } }