diff --git a/Assets/Readme.asset b/Assets/Readme.asset index a2ac61c4e..c4a66551a 100644 --- a/Assets/Readme.asset +++ b/Assets/Readme.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06fc8bc2a87fcd9d1d85ca6da32ef8176f27214124adfd90ae77e294d022a3a5 -size 2542 +oid sha256:bc84032ccd602f06302a2b509412634709d51faaeace1f426b041cd6a863f592 +size 2533 diff --git a/Assets/Scripts/UnityServices/Lobbies/LobbyAPIInterface.cs b/Assets/Scripts/UnityServices/Lobbies/LobbyAPIInterface.cs index 08d3d0471..0b163be1b 100644 --- a/Assets/Scripts/UnityServices/Lobbies/LobbyAPIInterface.cs +++ b/Assets/Scripts/UnityServices/Lobbies/LobbyAPIInterface.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Unity.BossRoom.Infrastructure; using Unity.Services.Lobbies; using Unity.Services.Lobbies.Models; using UnityEngine; -using VContainer; namespace Unity.BossRoom.UnityServices.Lobbies { @@ -16,15 +14,11 @@ public class LobbyAPIInterface { const int k_MaxLobbiesToShow = 16; // If more are necessary, consider retrieving paginated results or using filters. - readonly IPublisher m_UnityServiceErrorMessagePublisher; readonly List m_Filters; readonly List m_Order; - [Inject] - public LobbyAPIInterface(IPublisher unityServiceErrorMessagePublisher) + public LobbyAPIInterface() { - m_UnityServiceErrorMessagePublisher = unityServiceErrorMessagePublisher; - // Filter for open lobbies only m_Filters = new List() { @@ -43,34 +37,6 @@ public LobbyAPIInterface(IPublisher unityServiceErrorM }; } - async Task ExceptionHandling(Task task) - { - try - { - return await task; - } - catch (Exception e) - { - var reason = $"{e.Message} ({e.InnerException?.Message})"; // Lobby error type, then HTTP error type. - m_UnityServiceErrorMessagePublisher.Publish(new UnityServiceErrorMessage("Lobby Error", reason, UnityServiceErrorMessage.Service.Lobby, e)); - throw; - } - } - - async Task ExceptionHandling(Task task) - { - try - { - await task; - } - catch (Exception e) - { - var reason = $"{e.Message} ({e.InnerException?.Message})"; // Lobby error type, then HTTP error type. - m_UnityServiceErrorMessagePublisher.Publish(new UnityServiceErrorMessage("Lobby Error", reason, UnityServiceErrorMessage.Service.Lobby, e)); - throw; - } - } - public async Task CreateLobby(string requesterUasId, string lobbyName, int maxPlayers, bool isPrivate, Dictionary hostUserData, Dictionary lobbyData) { CreateLobbyOptions createOptions = new CreateLobbyOptions @@ -80,24 +46,24 @@ public async Task CreateLobby(string requesterUasId, string lobbyName, in Data = lobbyData }; - return await ExceptionHandling(LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions)); + return await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, createOptions); } public async Task DeleteLobby(string lobbyId) { - await ExceptionHandling(LobbyService.Instance.DeleteLobbyAsync(lobbyId)); + await LobbyService.Instance.DeleteLobbyAsync(lobbyId); } public async Task JoinLobbyByCode(string requesterUasId, string lobbyCode, Dictionary localUserData) { JoinLobbyByCodeOptions joinOptions = new JoinLobbyByCodeOptions { Player = new Player(id: requesterUasId, data: localUserData) }; - return await ExceptionHandling(LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions)); + return await LobbyService.Instance.JoinLobbyByCodeAsync(lobbyCode, joinOptions); } public async Task JoinLobbyById(string requesterUasId, string lobbyId, Dictionary localUserData) { JoinLobbyByIdOptions joinOptions = new JoinLobbyByIdOptions { Player = new Player(id: requesterUasId, data: localUserData) }; - return await ExceptionHandling(LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions)); + return await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId, joinOptions); } public async Task QuickJoinLobby(string requesterUasId, Dictionary localUserData) @@ -108,19 +74,19 @@ public async Task QuickJoinLobby(string requesterUasId, Dictionary ReconnectToLobby(string lobbyId) { - return await ExceptionHandling(LobbyService.Instance.ReconnectToLobbyAsync(lobbyId)); + return await LobbyService.Instance.ReconnectToLobbyAsync(lobbyId); } public async Task RemovePlayerFromLobby(string requesterUasId, string lobbyId) { try { - await ExceptionHandling(LobbyService.Instance.RemovePlayerAsync(lobbyId, requesterUasId)); + await LobbyService.Instance.RemovePlayerAsync(lobbyId, requesterUasId); } catch (LobbyServiceException e) when (e is { Reason: LobbyExceptionReason.PlayerNotFound }) @@ -138,18 +104,18 @@ public async Task QueryAllLobbies() Order = m_Order }; - return await ExceptionHandling(LobbyService.Instance.QueryLobbiesAsync(queryOptions)); + return await LobbyService.Instance.QueryLobbiesAsync(queryOptions); } public async Task GetLobby(string lobbyId) { - return await ExceptionHandling(LobbyService.Instance.GetLobbyAsync(lobbyId)); + return await LobbyService.Instance.GetLobbyAsync(lobbyId); } public async Task UpdateLobby(string lobbyId, Dictionary data, bool shouldLock) { UpdateLobbyOptions updateOptions = new UpdateLobbyOptions { Data = data, IsLocked = shouldLock }; - return await ExceptionHandling(LobbyService.Instance.UpdateLobbyAsync(lobbyId, updateOptions)); + return await LobbyService.Instance.UpdateLobbyAsync(lobbyId, updateOptions); } public async Task UpdatePlayer(string lobbyId, string playerId, Dictionary data, string allocationId, string connectionInfo) @@ -160,12 +126,12 @@ public async Task UpdatePlayer(string lobbyId, string playerId, Dictionar AllocationId = allocationId, ConnectionInfo = connectionInfo }; - return await ExceptionHandling(LobbyService.Instance.UpdatePlayerAsync(lobbyId, playerId, updateOptions)); + return await LobbyService.Instance.UpdatePlayerAsync(lobbyId, playerId, updateOptions); } public async void SendHeartbeatPing(string lobbyId) { - await ExceptionHandling(LobbyService.Instance.SendHeartbeatPingAsync(lobbyId)); + await LobbyService.Instance.SendHeartbeatPingAsync(lobbyId); } } } diff --git a/Assets/Scripts/UnityServices/Lobbies/LobbyServiceFacade.cs b/Assets/Scripts/UnityServices/Lobbies/LobbyServiceFacade.cs index 382af59db..9b3eea329 100644 --- a/Assets/Scripts/UnityServices/Lobbies/LobbyServiceFacade.cs +++ b/Assets/Scripts/UnityServices/Lobbies/LobbyServiceFacade.cs @@ -91,9 +91,18 @@ public Task EndTracking() { CurrentUnityLobby = null; - if (!string.IsNullOrEmpty(m_LocalLobby?.LobbyID)) + var lobbyId = m_LocalLobby?.LobbyID; + + if (!string.IsNullOrEmpty(lobbyId)) { - task = LeaveLobbyAsync(m_LocalLobby?.LobbyID); + if (m_LocalUser.IsHost) + { + task = DeleteLobbyAsync(lobbyId); + } + else + { + task = LeaveLobbyAsync(lobbyId); + } } m_LocalUser.ResetState(); @@ -146,6 +155,10 @@ async void UpdateLobby(float unused) { m_RateLimitQuery.PutOnCooldown(); } + else if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost) // If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here. + { + PublishError(e); + } } } @@ -171,6 +184,10 @@ async void UpdateLobby(float unused) { m_RateLimitHost.PutOnCooldown(); } + else + { + PublishError(e); + } } return (false, null); @@ -207,6 +224,10 @@ async void UpdateLobby(float unused) { m_RateLimitJoin.PutOnCooldown(); } + else + { + PublishError(e); + } } return (false, null); @@ -234,6 +255,10 @@ async void UpdateLobby(float unused) { m_RateLimitQuickJoin.PutOnCooldown(); } + else + { + PublishError(e); + } } return (false, null); @@ -261,12 +286,29 @@ public async Task RetrieveAndPublishLobbyListAsync() { m_RateLimitQuery.PutOnCooldown(); } + else + { + PublishError(e); + } } } public async Task ReconnectToLobbyAsync(string lobbyId) { - return await m_LobbyApiInterface.ReconnectToLobby(lobbyId); + try + { + return await m_LobbyApiInterface.ReconnectToLobby(lobbyId); + } + catch (LobbyServiceException e) + { + // If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here. + if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost) + { + PublishError(e); + } + } + + return null; } /// @@ -280,9 +322,12 @@ public async Task LeaveLobbyAsync(string lobbyId) await m_LobbyApiInterface.RemovePlayerFromLobby(uasId, lobbyId); } catch (LobbyServiceException e) - when (e is { Reason: LobbyExceptionReason.LobbyNotFound }) { - // If Lobby is not found, it has already been deleted. No need to throw here. + // If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here. + if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost) + { + PublishError(e); + } } } @@ -291,7 +336,14 @@ public async void RemovePlayerFromLobbyAsync(string uasId, string lobbyId) { if (m_LocalUser.IsHost) { - await m_LobbyApiInterface.RemovePlayerFromLobby(uasId, lobbyId); + try + { + await m_LobbyApiInterface.RemovePlayerFromLobby(uasId, lobbyId); + } + catch (LobbyServiceException e) + { + PublishError(e); + } } else { @@ -299,11 +351,18 @@ public async void RemovePlayerFromLobbyAsync(string uasId, string lobbyId) } } - public async void DeleteLobbyAsync(string lobbyId) + public async Task DeleteLobbyAsync(string lobbyId) { if (m_LocalUser.IsHost) { - await m_LobbyApiInterface.DeleteLobby(lobbyId); + try + { + await m_LobbyApiInterface.DeleteLobby(lobbyId); + } + catch (LobbyServiceException e) + { + PublishError(e); + } } else { @@ -336,6 +395,10 @@ public async Task UpdatePlayerDataAsync(Dictionary dat { m_RateLimitQuery.PutOnCooldown(); } + else if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost) // If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here. + { + PublishError(e); + } } } @@ -359,6 +422,10 @@ public async Task UpdatePlayerRelayInfoAsync(string allocationId, string connect { m_RateLimitQuery.PutOnCooldown(); } + else + { + PublishError(e); + } //todo - retry logic? SDK is supposed to handle this eventually } @@ -406,6 +473,10 @@ public async Task UpdateLobbyDataAsync(Dictionary data) { m_RateLimitQuery.PutOnCooldown(); } + else + { + PublishError(e); + } } } @@ -418,8 +489,25 @@ public void DoLobbyHeartbeat(float dt) if (m_HeartbeatTime > k_HeartbeatPeriod) { m_HeartbeatTime -= k_HeartbeatPeriod; - m_LobbyApiInterface.SendHeartbeatPing(CurrentUnityLobby.Id); + try + { + m_LobbyApiInterface.SendHeartbeatPing(CurrentUnityLobby.Id); + } + catch (LobbyServiceException e) + { + // If Lobby is not found and if we are not the host, it has already been deleted. No need to publish the error here. + if (e.Reason != LobbyExceptionReason.LobbyNotFound && !m_LocalUser.IsHost) + { + PublishError(e); + } + } } } + + void PublishError(LobbyServiceException e) + { + var reason = $"{e.Message} ({e.InnerException?.Message})"; // Lobby error type, then HTTP error type. + m_UnityServiceErrorMessagePub.Publish(new UnityServiceErrorMessage("Lobby Error", reason, UnityServiceErrorMessage.Service.Lobby, e)); + } } } diff --git a/Assets/Scripts/VisualEffects/SpecialFXGraphic.cs b/Assets/Scripts/VisualEffects/SpecialFXGraphic.cs index 8b924929a..baab237c5 100644 --- a/Assets/Scripts/VisualEffects/SpecialFXGraphic.cs +++ b/Assets/Scripts/VisualEffects/SpecialFXGraphic.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using UnityEngine; @@ -47,14 +48,22 @@ public class SpecialFXGraphic : MonoBehaviour [Tooltip("After Shutdown, how long before we self-destruct? 0 means no self destruct. -1 means self-destruct after ALL particles have disappeared")] private float m_PostShutdownSelfDestructTime = -1; + [SerializeField] + [Tooltip("If this graphic should keep its spawn rotation during its lifetime.")] + bool m_StayAtSpawnRotation; + // track when Shutdown() is called so we don't try to do it twice private bool m_IsShutdown = false; // we keep a reference to our self-destruction coroutine in case we need to abort it prematurely private Coroutine coroWaitForSelfDestruct = null; + Quaternion m_StartRotation; + private void Start() { + m_StartRotation = transform.rotation; + if (m_AutoShutdownTime != -1) { coroWaitForSelfDestruct = StartCoroutine(CoroWaitForSelfDestruct()); @@ -124,6 +133,13 @@ private IEnumerator CoroWaitForSelfDestruct() } } + void Update() + { + if (m_StayAtSpawnRotation) + { + transform.rotation = m_StartRotation; + } + } } diff --git a/Assets/VFX/Prefabs/Shared Hero FX/FX_Buff.prefab b/Assets/VFX/Prefabs/Shared Hero FX/FX_Buff.prefab index 885634c4a..ba7fd80d5 100644 --- a/Assets/VFX/Prefabs/Shared Hero FX/FX_Buff.prefab +++ b/Assets/VFX/Prefabs/Shared Hero FX/FX_Buff.prefab @@ -106,7 +106,7 @@ ParticleSystem: m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 - moveWithTransform: 1 + moveWithTransform: 0 moveWithCustomTransform: {fileID: 0} scalingMode: 1 randomSeed: 5677 @@ -4925,11 +4925,15 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 8e80714725b908940a76375b52b8d53b, type: 3} - m_Name: - m_EditorClassIdentifier: - m_ParticleSystemsToTurnOffOnShutdown: [] + m_Name: + m_EditorClassIdentifier: + m_ParticleSystemsToTurnOffOnShutdown: + - {fileID: 5082887311387214394} + - {fileID: 5082887310581284565} + - {fileID: 5082887311598866503} m_AutoShutdownTime: 3 m_PostShutdownSelfDestructTime: -1 + m_StayAtSpawnRotation: 1 --- !u!1 &5082887311387214391 GameObject: m_ObjectHideFlags: 0 @@ -9915,7 +9919,7 @@ ParticleSystem: m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 - moveWithTransform: 1 + moveWithTransform: 0 moveWithCustomTransform: {fileID: 0} scalingMode: 1 randomSeed: 5678 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1379e0599..fe5002a78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [unreleased] - yyyy-mm-dd + +## [2.0.3] - 2022-12-05 + +### Changed +* Hosts now delete their lobby when shutting down instead of only leaving it (#772) Since Boss Room doesn't support host migration, there is no need to keep the lobby alive after the host shuts down. This also changes how LobbyServiceExceptions are handled to prevent popup messages on clients trying to leave a lobby that is already deleted, following the best practices outlined in this doc : https://docs.unity.com/lobby/delete-a-lobby.html + +### Fixed +* Mage's heal FX plays out on itself and on targets. Added ability for SpecialFXGraphic components to remain at spawn rotation (#771) + ## [2.0.2] - 2022-11-01 ### Fixed * Bumped Unity editor version to fix android build error (#779) diff --git a/Packages/com.unity.multiplayer.samples.coop/CHANGELOG.md b/Packages/com.unity.multiplayer.samples.coop/CHANGELOG.md index b7d9c2ca8..72256e5b1 100644 --- a/Packages/com.unity.multiplayer.samples.coop/CHANGELOG.md +++ b/Packages/com.unity.multiplayer.samples.coop/CHANGELOG.md @@ -1,5 +1,14 @@ # Multiplayer Samples Co-op Changelog +## [unreleased] - yyyy-mm-dd +### Changed +* + +## [1.5.0] - 2022-12-05 + +### Changed +* ClientNetworkAnimator component has been added to the Samples Utilities Package. This allows for authority on Animators to be passed onto clients, meaning animations will be client-driven. + ## [1.4.1] - 2022-10-25 ### Changed diff --git a/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkAnimator.cs b/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkAnimator.cs new file mode 100644 index 000000000..4034548ca --- /dev/null +++ b/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkAnimator.cs @@ -0,0 +1,22 @@ +using Unity.Netcode.Components; +using UnityEngine; + +namespace Unity.Multiplayer.Samples.Utilities.ClientAuthority +{ + /// + /// Used for syncing an animator with client side changes. This includes host. Pure server as owner isn't supported + /// by this. Please use NetworkAnimator for animations that'll always be owned by the server. + /// + [DisallowMultipleComponent] + public class ClientNetworkAnimator : NetworkAnimator + { + /// + /// Used to determine who can write to this animator. Owner client only. + /// This imposes state to the server. This is putting trust on your clients. Make sure no security-sensitive features use this animator. + /// + protected override bool OnIsServerAuthoritative() + { + return false; + } + } +} diff --git a/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkAnimator.cs.meta b/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkAnimator.cs.meta new file mode 100644 index 000000000..f7cb92ffa --- /dev/null +++ b/Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkAnimator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ad8deae8091111f4eb30a56c1f4e0d3e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.multiplayer.samples.coop/package.json b/Packages/com.unity.multiplayer.samples.coop/package.json index 997343db4..abb8c5cbe 100644 --- a/Packages/com.unity.multiplayer.samples.coop/package.json +++ b/Packages/com.unity.multiplayer.samples.coop/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.multiplayer.samples.coop", "displayName": "Multiplayer Samples Utilities", - "version": "1.4.1", + "version": "1.5.0", "type": "template", "host": "hub", "unity": "2020.3", diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 11078ae70..7d8d102da 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8945219216adb46586a4f9a1bbba347ad520e68e530042124625a4c891ebbf44 +oid sha256:4c052cbb3d9a0b5dd3ba096d6a72f5bcb5e31ece58ffa09df84ea4d8ebb70f26 size 25551 diff --git a/README.md b/README.md index e8f874928..c9b88dedb 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ Running the game over internet currently requires setting up a relay. * Spawner for in-scene objects - [Packages/com.unity.multiplayer.samples.coop/Utilities/Net/NetworkObjectSpawner.cs ](Packages/com.unity.multiplayer.samples.coop/Utilities/Net/NetworkObjectSpawner.cs) * Session manager for reconnection - [Packages/com.unity.multiplayer.samples.coop/Utilities/Net/SessionManager.cs ](Packages/com.unity.multiplayer.samples.coop/Utilities/Net/SessionManager.cs) * Relay utils - [Packages/com.unity.multiplayer.samples.coop/Utilities/Net/UnityRelayUtilities.cs ](Packages/com.unity.multiplayer.samples.coop/Utilities/Net/UnityRelayUtilities.cs) -* Client authority - [Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkTransform.cs ](Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/ClientNetworkTransform.cs) +* Client authority - [Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/](Packages/com.unity.multiplayer.samples.coop/Utilities/Net/ClientAuthority/) * Scene utils with synced loading screens - [Packages/com.unity.multiplayer.samples.coop/Utilities/SceneManagement/ ](Packages/com.unity.multiplayer.samples.coop/Utilities/SceneManagement/) * RNSM custom config - [Packages/com.unity.multiplayer.samples.coop/Utilities/Net/RNSM/CustomNetStatsMonitorConfiguration.asset ](Packages/com.unity.multiplayer.samples.coop/Utilities/Net/RNSM/CustomNetStatsMonitorConfiguration.asset) * ParrelSync - [ Packages/manifest.json ](Packages/manifest.json)