├── .gitignore
├── Assets
└── FishNet
│ └── Plugins
│ └── Alven
│ ├── SessionManagement
│ ├── Runtime
│ │ ├── Managers
│ │ │ ├── ServerSessionManager.cs.meta
│ │ │ ├── Broadcasts.meta
│ │ │ ├── ClientSessionManager.cs.meta
│ │ │ ├── PlayerConnectionState.cs.meta
│ │ │ ├── LocalPlayerConnectionState.cs.meta
│ │ │ ├── PlayerConnectionStateArgs.cs.meta
│ │ │ ├── RemotePlayerConnectionStateArgs.cs.meta
│ │ │ ├── Broadcasts
│ │ │ │ ├── ConnectionStateBroadcast.cs.meta
│ │ │ │ ├── PlayerConnectedBroadcast.cs.meta
│ │ │ │ ├── PlayerConnectionChangeBroadcast.cs.meta
│ │ │ │ ├── PlayerConnectedBroadcast.cs
│ │ │ │ ├── ConnectionStateBroadcast.cs
│ │ │ │ └── PlayerConnectionChangeBroadcast.cs
│ │ │ ├── PlayerConnectionStateArgs.cs
│ │ │ ├── RemotePlayerConnectionStateArgs.cs
│ │ │ ├── LocalPlayerConnectionState.cs
│ │ │ ├── PlayerConnectionState.cs
│ │ │ ├── ClientSessionManager.cs
│ │ │ └── ServerSessionManager.cs
│ │ ├── SessionAuthenticators
│ │ │ ├── BasicSessionAuthenticator.cs.meta
│ │ │ ├── SessionAuthenticator.cs.meta
│ │ │ ├── UnitySessionAuthenticator.cs.meta
│ │ │ ├── SessionAuthenticator.cs
│ │ │ ├── BasicSessionAuthenticator.cs
│ │ │ └── UnitySessionAuthenticator.cs
│ │ ├── Managers.meta
│ │ ├── Components.meta
│ │ ├── Extensions.meta
│ │ ├── SessionPlayers.meta
│ │ ├── SessionAuthenticators.meta
│ │ ├── FishNet.Alven.SessionManagement.asmdef.meta
│ │ ├── Components
│ │ │ ├── NetworkSessionObject.cs.meta
│ │ │ ├── SessionPlayerSpawner.cs.meta
│ │ │ ├── NetworkSessionObject.cs
│ │ │ └── SessionPlayerSpawner.cs
│ │ ├── SessionPlayers
│ │ │ ├── SessionPlayer.cs.meta
│ │ │ ├── SessionPlayerSerializer.cs.meta
│ │ │ ├── SessionPlayerSerializer.cs
│ │ │ └── SessionPlayer.cs
│ │ ├── Extensions
│ │ │ ├── ServerManagerExtensions.cs.meta
│ │ │ ├── NetworkConnectionExtensions.cs.meta
│ │ │ ├── NetworkManagerSessionExtensions.cs.meta
│ │ │ ├── NetworkManagerSessionExtensions.cs
│ │ │ ├── NetworkConnectionExtensions.cs
│ │ │ └── ServerManagerExtensions.cs
│ │ └── FishNet.Alven.SessionManagement.asmdef
│ ├── package.json.meta
│ ├── Runtime.meta
│ └── package.json
│ └── SessionManagement.meta
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !Assets/FishNet/Plugins/Alven/SessionManagement/**
3 | !Assets/FishNet/Plugins/Alven/SessionManagement.meta
4 | !.gitignore
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/ServerSessionManager.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 689d6eed83c2418aabb20e997676ccfe
3 | timeCreated: 1695913165
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionAuthenticators/BasicSessionAuthenticator.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a1ea8452a71e11e428056a9c524c81d5
3 | timeCreated: 1704525663
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fc2fb28c6f10e3d43854a2128593f369
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7aee4ccbe50e5994ea21f36e360198cf
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1231e8f8d4ac077459371ade92632073
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b296c19c82e669a459f1be10628133e0
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Components.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7c3d7c9a45f042eca4827a63315a869b
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Extensions.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c2ffe2a5fa1e4694cbb2b301554f5e09
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionPlayers.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 59069b65c64bdb748b6602b50ccf0f0d
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/Broadcasts.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 76a5500fcde035b4089671fa479d8613
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionAuthenticators.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4023867515ccad34f86e3af7293fe9a2
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/FishNet.Alven.SessionManagement.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9bb85d1f15198e443be0a528717af368
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.alven.fishnet.session-management",
3 | "displayName": "Session Management for FishNet",
4 | "version": "1.0.0",
5 | "unity": "2020.3",
6 | "author": "Alven (https://github.com/ooonush)",
7 | "homepage": "https://github.com/ooonush/com.alven.fishnet.session-management"
8 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Components/NetworkSessionObject.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 92c3b9b9e632460f80d8b8a535e30789
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Components/SessionPlayerSpawner.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e5ffdd2f62234fd5b7b1baba653ec912
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/ClientSessionManager.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c25c28eaac047b6429686319f2d6b7b5
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/PlayerConnectionState.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 8e6ec5e29e7f7d743b450d1e960586c0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionPlayers/SessionPlayer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2a130a62bc9e15641b84f7da7dd16acb
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Extensions/ServerManagerExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1eeee0537f9c49f8a6c95f45f48d4f76
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/LocalPlayerConnectionState.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a1011917f68b4c6aae608a65a29eace8
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/PlayerConnectionStateArgs.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b49302a7d80741d79addc3153f3f6893
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Extensions/NetworkConnectionExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 49fc8b0b35db6a24caa524b1a7e5677d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/RemotePlayerConnectionStateArgs.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1efd1740dbaeb2249b20a8f19e4e43bd
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionPlayers/SessionPlayerSerializer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5c6c1b70fb18721409b9e9515d42d44a
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Extensions/NetworkManagerSessionExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 08bd6c5166e41d849a49479872983bd3
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/Broadcasts/ConnectionStateBroadcast.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 03f640c3b082a154d93ef3fa2a3a0a54
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/Broadcasts/PlayerConnectedBroadcast.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3d06fcfc43ffa6d46b84244d4b4f753e
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionAuthenticators/SessionAuthenticator.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1b50c1d08d020464d9471a30413b70f3
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/Broadcasts/PlayerConnectionChangeBroadcast.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6be79c7141db4534c8ed11fd5e028069
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionAuthenticators/UnitySessionAuthenticator.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: cdc34bf18fa364044bc7c0e915aab988
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/PlayerConnectionStateArgs.cs:
--------------------------------------------------------------------------------
1 | namespace FishNet.Alven.SessionManagement
2 | {
3 | public readonly struct PlayerConnectionStateArgs
4 | {
5 | public readonly LocalPlayerConnectionState State;
6 |
7 | internal PlayerConnectionStateArgs(LocalPlayerConnectionState state)
8 | {
9 | State = state;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/RemotePlayerConnectionStateArgs.cs:
--------------------------------------------------------------------------------
1 | namespace FishNet.Alven.SessionManagement
2 | {
3 | public readonly struct RemotePlayerConnectionStateArgs
4 | {
5 | public readonly PlayerConnectionState State;
6 | public readonly int ClientPlayerId;
7 |
8 | internal RemotePlayerConnectionStateArgs(PlayerConnectionState state, int clientPlayerId)
9 | {
10 | State = state;
11 | ClientPlayerId = clientPlayerId;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/Broadcasts/PlayerConnectedBroadcast.cs:
--------------------------------------------------------------------------------
1 | using FishNet.Broadcast;
2 |
3 | namespace FishNet.Alven.SessionManagement
4 | {
5 | internal readonly struct PlayerConnectedBroadcast : IBroadcast
6 | {
7 | public readonly SessionPlayer Player;
8 | public readonly bool IsReconnected;
9 |
10 | public PlayerConnectedBroadcast(SessionPlayer player, bool isReconnected)
11 | {
12 | Player = player;
13 | IsReconnected = isReconnected;
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/Broadcasts/ConnectionStateBroadcast.cs:
--------------------------------------------------------------------------------
1 | using FishNet.Broadcast;
2 |
3 | namespace FishNet.Alven.SessionManagement
4 | {
5 | internal readonly struct ConnectedPlayersBroadcast : IBroadcast
6 | {
7 | public readonly int[] ClientPlayerIds;
8 | public readonly int[] ConnectionIds;
9 |
10 | public ConnectedPlayersBroadcast(int[] clientPlayerIds, int[] connectionIds)
11 | {
12 | ClientPlayerIds = clientPlayerIds;
13 | ConnectionIds = connectionIds;
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/Broadcasts/PlayerConnectionChangeBroadcast.cs:
--------------------------------------------------------------------------------
1 | using FishNet.Broadcast;
2 |
3 | namespace FishNet.Alven.SessionManagement
4 | {
5 | internal readonly struct PlayerConnectionChangeBroadcast : IBroadcast
6 | {
7 | public readonly int ClientPlayerId;
8 | public readonly int ConnectionId;
9 | public readonly PlayerConnectionState State;
10 |
11 | public PlayerConnectionChangeBroadcast(int clientPlayerId, int connectionId, PlayerConnectionState state)
12 | {
13 | ClientPlayerId = clientPlayerId;
14 | ConnectionId = connectionId;
15 | State = state;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/LocalPlayerConnectionState.cs:
--------------------------------------------------------------------------------
1 | namespace FishNet.Alven.SessionManagement
2 | {
3 | public enum LocalPlayerConnectionState
4 | {
5 | ///
6 | /// The player has been connected first time.
7 | /// Is called after the player has been authenticated.
8 | ///
9 | Connected,
10 | ///
11 | /// The player has been reconnected to this session.
12 | /// Is called after the player has been authenticated.
13 | ///
14 | Reconnected,
15 | ///
16 | /// The player was disconnected. If a session was started on the server, he was disconnected temporarily and can reconnect.
17 | /// Otherwise he is disconnected permanently and next time he will connect as a new player with the same PlayerId.
18 | ///
19 | Disconnected
20 | }
21 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/PlayerConnectionState.cs:
--------------------------------------------------------------------------------
1 | namespace FishNet.Alven.SessionManagement
2 | {
3 | public enum PlayerConnectionState : byte
4 | {
5 | ///
6 | /// The player has been connected first time.
7 | /// Is called after the player has been authenticated.
8 | ///
9 | Connected,
10 | ///
11 | /// The player has been reconnected to this session.
12 | /// Is called after the player has been authenticated.
13 | ///
14 | Reconnected,
15 | ///
16 | /// The player has been permanently disconnected and cannot reconnect to this session.
17 | /// Next time it will connect as a new player with the same PlayerId.
18 | ///
19 | PermanentlyDisconnected,
20 | ///
21 | /// The player has been temporarily disconnected and can reconnect to this session using their own PlayerId.
22 | ///
23 | TemporarilyDisconnected
24 | }
25 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/FishNet.Alven.SessionManagement.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FishNet.Alven.SessionManagement",
3 | "rootNamespace": "FishNet.Alven.SessionManagement",
4 | "references": [
5 | "GUID:7c88a4a7926ee5145ad2dfa06f454c67",
6 | "GUID:5540e30183c82e84b954c033c388e06c",
7 | "GUID:fe25561d224ed4743af4c60938a59d0b",
8 | "GUID:894a6cc6ed5cd2645bb542978cbed6a9"
9 | ],
10 | "includePlatforms": [],
11 | "excludePlatforms": [],
12 | "allowUnsafeCode": false,
13 | "overrideReferences": false,
14 | "precompiledReferences": [],
15 | "autoReferenced": true,
16 | "defineConstraints": [],
17 | "versionDefines": [
18 | {
19 | "name": "com.veriorpies.parrelsync",
20 | "expression": "1.2",
21 | "define": "PARRELSYNC"
22 | },
23 | {
24 | "name": "com.unity.services.authentication",
25 | "expression": "",
26 | "define": "UNITY_SERVICES_AUTHENTICATION"
27 | }
28 | ],
29 | "noEngineReferences": false
30 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Extensions/NetworkManagerSessionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FishNet.Managing;
3 |
4 | namespace FishNet.Alven.SessionManagement
5 | {
6 | public static class NetworkManagerSessionExtensions
7 | {
8 | public static ServerSessionManager GetServerSessionManager(this NetworkManager networkManager)
9 | {
10 | if (!networkManager.HasInstance())
11 | {
12 | throw new InvalidOperationException("ServerSessionManager component is not found.");
13 | }
14 | return networkManager.GetInstance();
15 | }
16 |
17 | public static ClientSessionManager GetClientSessionManager(this NetworkManager networkManager)
18 | {
19 | if (!networkManager.HasInstance())
20 | {
21 | throw new InvalidOperationException("ClientSessionManager component is not found.");
22 | }
23 | return networkManager.GetInstance();
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Alven
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionAuthenticators/SessionAuthenticator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FishNet.Authenticating;
3 | using FishNet.Connection;
4 |
5 | namespace FishNet.Alven.SessionManagement
6 | {
7 | public abstract class SessionAuthenticator : Authenticator
8 | {
9 | public sealed override event Action OnAuthenticationResult;
10 |
11 | internal void InvokeAuthenticationResult(NetworkConnection connection, bool result)
12 | {
13 | OnAuthenticationResult?.Invoke(connection, result);
14 | }
15 |
16 | ///
17 | /// Called when connection authentication is passed.
18 | ///
19 | ///
20 | ///
21 | ///
22 | protected bool PassAuthentication(NetworkConnection connection, string playerId)
23 | {
24 | ServerSessionManager sessionManager = NetworkManager.GetServerSessionManager();
25 | return sessionManager.SetupPlayerConnection(this, playerId, connection);
26 | }
27 |
28 | ///
29 | /// Called when connection authentication is failed.
30 | ///
31 | ///
32 | ///
33 | protected void FailAuthentication(NetworkConnection connection)
34 | {
35 | OnAuthenticationResult?.Invoke(connection, false);
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Extensions/NetworkConnectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using FishNet.Connection;
2 | using FishNet.Managing;
3 |
4 | namespace FishNet.Alven.SessionManagement
5 | {
6 | public static class NetworkConnectionExtensions
7 | {
8 | ///
9 | /// SessionPlayer for this connection.
10 | ///
11 | ///
12 | ///
13 | public static SessionPlayer GetSessionPlayer(this NetworkConnection connection)
14 | {
15 | if (connection == null || !connection.IsValid)
16 | {
17 | return SessionPlayer.Empty;
18 | }
19 |
20 | if (connection.NetworkManager.ServerManager.Clients.TryGetValue(connection.ClientId, out NetworkConnection c))
21 | {
22 | if (ReferenceEquals(connection, c))
23 | {
24 | return connection.NetworkManager.GetServerSessionManager().GetPlayer(connection);
25 | }
26 | }
27 |
28 | if (connection.IsLocalClient)
29 | {
30 | return connection.NetworkManager.GetClientSessionManager().Player;
31 | }
32 |
33 | if (connection.NetworkManager.ClientManager.Clients.TryGetValue(connection.ClientId, out c))
34 | {
35 | if (ReferenceEquals(connection, c))
36 | {
37 | return connection.NetworkManager.GetClientSessionManager().GetPlayer(connection);
38 | }
39 | }
40 |
41 | connection.NetworkManager.LogError("Could not find session player for connection");
42 | return SessionPlayer.Empty;
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionAuthenticators/BasicSessionAuthenticator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UnityEngine;
3 | using FishNet.Broadcast;
4 | using FishNet.Connection;
5 | using FishNet.Managing;
6 | using FishNet.Transporting;
7 |
8 | namespace FishNet.Alven.SessionManagement
9 | {
10 | [AddComponentMenu("FishNet/Session Management/BasicSessionAuthenticator")]
11 | public sealed class BasicSessionAuthenticator : SessionAuthenticator
12 | {
13 | private struct PlayerIdBroadcast : IBroadcast
14 | {
15 | public string PlayerId;
16 | }
17 |
18 | private string PlayerIdKey => $"{_profile}.fishnet.alven.session-management.player-id";
19 | private string _profile = "default";
20 | private string _playerId;
21 |
22 | public override void InitializeOnce(NetworkManager networkManager)
23 | {
24 | base.InitializeOnce(networkManager);
25 |
26 | #if PARRELSYNC && UNITY_EDITOR
27 | if (ParrelSync.ClonesManager.IsClone())
28 | {
29 | _profile = $"Clone_{ParrelSync.ClonesManager.GetArgument()}_Profile";
30 | }
31 | #endif
32 |
33 | SignIn();
34 |
35 | networkManager.ClientManager.OnClientConnectionState += OnClientConnectionState;
36 | networkManager.ServerManager.RegisterBroadcast(OnPlayerIdBroadcast, false);
37 | }
38 |
39 | private void SignIn()
40 | {
41 | _playerId = PlayerPrefs.GetString(PlayerIdKey);
42 | if (string.IsNullOrEmpty(_playerId))
43 | {
44 | _playerId = Guid.NewGuid().ToString("N");
45 | PlayerPrefs.SetString(PlayerIdKey, _playerId);
46 | }
47 | NetworkManager.Log($"Player {_profile} with PlayerId {_playerId} signed in" );
48 | }
49 |
50 | private void OnPlayerIdBroadcast(NetworkConnection connection, PlayerIdBroadcast broadcast, Channel channel)
51 | {
52 | PassAuthentication(connection, broadcast.PlayerId);
53 | }
54 |
55 | private void OnClientConnectionState(ClientConnectionStateArgs args)
56 | {
57 | if (args.ConnectionState == LocalConnectionState.Started)
58 | {
59 | NetworkManager.ClientManager.Broadcast(new PlayerIdBroadcast { PlayerId = _playerId });
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionAuthenticators/UnitySessionAuthenticator.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_SERVICES_AUTHENTICATION
2 |
3 | using System.Threading.Tasks;
4 | using FishNet.Broadcast;
5 | using FishNet.Connection;
6 | using FishNet.Managing;
7 | using FishNet.Transporting;
8 | using Unity.Services.Authentication;
9 | using Unity.Services.Core;
10 | using UnityEngine;
11 |
12 | namespace FishNet.Alven.SessionManagement
13 | {
14 | [AddComponentMenu("FishNet/Session Management/UnitySessionAuthenticator")]
15 | public sealed class UnitySessionAuthenticator : SessionAuthenticator
16 | {
17 | private struct PlayerIdBroadcast : IBroadcast
18 | {
19 | public string PlayerId;
20 | }
21 |
22 | [Tooltip("Automatically call UnityServices.InitializeAsync() and sign in anonymously.")]
23 | public bool AutoSignIn = true;
24 |
25 | private Task _authenticationTask;
26 |
27 | public override void InitializeOnce(NetworkManager networkManager)
28 | {
29 | base.InitializeOnce(networkManager);
30 |
31 | if (AutoSignIn)
32 | {
33 | _authenticationTask = SignInAnonymouslyAsync();
34 | }
35 |
36 | networkManager.ClientManager.OnClientConnectionState += OnClientConnectionState;
37 | networkManager.ServerManager.RegisterBroadcast(OnPlayerIdBroadcast, false);
38 | }
39 |
40 | private async Task SignInAnonymouslyAsync()
41 | {
42 | await UnityServices.InitializeAsync();
43 |
44 | #if PARRELSYNC && UNITY_EDITOR
45 | if (ParrelSync.ClonesManager.IsClone())
46 | {
47 | var profile = $"parrelsync_clone_{ParrelSync.ClonesManager.GetArgument()}";
48 | AuthenticationService.Instance.SwitchProfile(profile);
49 | }
50 | #endif
51 |
52 | await AuthenticationService.Instance.SignInAnonymouslyAsync();
53 | string playerId = AuthenticationService.Instance.PlayerId;
54 | NetworkManager.Log($"Player {AuthenticationService.Instance.Profile} with PlayerId {playerId} signed in" );
55 | }
56 |
57 | private void OnPlayerIdBroadcast(NetworkConnection connection, PlayerIdBroadcast broadcast, Channel channel)
58 | {
59 | PassAuthentication(connection, broadcast.PlayerId);
60 | }
61 |
62 | private async void OnClientConnectionState(ClientConnectionStateArgs args)
63 | {
64 | if (args.ConnectionState != LocalConnectionState.Started) return;
65 |
66 | if (_authenticationTask != null)
67 | {
68 | await _authenticationTask;
69 | }
70 |
71 | string playerId = AuthenticationService.Instance.PlayerId;
72 | NetworkManager.ClientManager.Broadcast(new PlayerIdBroadcast { PlayerId = playerId });
73 | }
74 | }
75 | }
76 |
77 | #endif
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Extensions/ServerManagerExtensions.cs:
--------------------------------------------------------------------------------
1 | using FishNet.Managing;
2 | using FishNet.Managing.Server;
3 | using FishNet.Object;
4 | using UnityEngine.SceneManagement;
5 |
6 | namespace FishNet.Alven.SessionManagement
7 | {
8 | public static class ServerManagerExtensions
9 | {
10 | ///
11 | /// Spawns an object over the network. Can only be called on the server.
12 | ///
13 | /// NetworkPlayerObject instance to spawn.
14 | /// SessionPlayer to give ownership to.
15 | /// True to give ownership and add player to the Observers. False to give ownership without adding player to the Observers. This can be useful if you want to give ownership of the object that is in a scene that the player does not currently have loaded. .
16 | public static void Spawn(this ServerManager serverManager, NetworkSessionObject playerObject, SessionPlayer player, Scene scene = default, bool rebuildObservers = true)
17 | {
18 | playerObject.GivingOwnership = true;
19 | playerObject.IsSpawning = true;
20 | playerObject.Initialize(player);
21 |
22 | foreach (NetworkSessionObject childSessionObject in playerObject.ChildNetworkSessionObjects)
23 | {
24 | childSessionObject.GivingOwnership = true;
25 | childSessionObject.IsSpawning = true;
26 | childSessionObject.Initialize(player);
27 | }
28 |
29 | if (rebuildObservers)
30 | {
31 | serverManager.Spawn(playerObject.NetworkObject, player.NetworkConnection, scene);
32 | }
33 | else
34 | {
35 | serverManager.Spawn(playerObject.NetworkObject, scene: scene);
36 | }
37 |
38 | foreach (NetworkSessionObject child in playerObject.ChildNetworkSessionObjects)
39 | {
40 | child.GivingOwnership = false;
41 | child.IsSpawning = false;
42 | }
43 | playerObject.IsSpawning = false;
44 | playerObject.GivingOwnership = false;
45 | }
46 |
47 | ///
48 | /// Spawns an object over the network. Can only be called on the server.
49 | ///
50 | /// NetworkObject instance to spawn.
51 | /// SessionPlayer to give ownership to.
52 | /// True to give ownership and add player to the Observers. False to give ownership without adding player to the Observers. This can be useful if you want to give ownership of the object that is in a scene that the player does not currently have loaded. .
53 | public static void Spawn(this ServerManager serverManager, NetworkObject networkObject, SessionPlayer player, Scene scene = default, bool rebuildObservers = true)
54 | {
55 | var networkPlayerObject = networkObject.GetComponent();
56 | if (!networkPlayerObject)
57 | {
58 | serverManager.NetworkManager.LogWarning("NetworkObject does not have a NetworkPlayerObject component.");
59 | }
60 | else
61 | {
62 | Spawn(serverManager, networkPlayerObject, player, scene, rebuildObservers);
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionPlayers/SessionPlayerSerializer.cs:
--------------------------------------------------------------------------------
1 | using FishNet.Managing;
2 | using FishNet.Serializing;
3 |
4 | namespace FishNet.Alven.SessionManagement
5 | {
6 | public static class SessionPlayerSerializer
7 | {
8 | public static void WriteSessionPlayer(this Writer writer, SessionPlayer player)
9 | {
10 | writer.WriteInt32(player.NetworkConnection.ClientId);
11 | writer.WriteInt32(player.ClientPlayerId);
12 | }
13 |
14 | public static SessionPlayer ReadSessionPlayer(this Reader reader)
15 | {
16 | int connectionId = reader.ReadInt32();
17 | int clientPlayerId = reader.ReadInt32();
18 |
19 | if (clientPlayerId == SessionPlayer.UNSET_CLIENTID)
20 | {
21 | return SessionPlayer.Empty;
22 | }
23 | else
24 | {
25 | //Prefer server.
26 | NetworkManager networkManager = reader.NetworkManager;
27 | if (networkManager.IsServerStarted)
28 | {
29 | SessionPlayer result;
30 | if (networkManager.GetServerSessionManager().Players.TryGetValue(clientPlayerId, out result))
31 | {
32 | return result;
33 | }
34 | //If also client then try client side data.
35 | else if (networkManager.IsClientStarted)
36 | {
37 | //If found in client collection then return.
38 | if (networkManager.GetClientSessionManager().Players.TryGetValue(clientPlayerId, out result))
39 | return result;
40 | /* Otherwise make a new instance.
41 | * We do not know if this is for the server or client so
42 | * initialize it either way. Connections rarely come through
43 | * without being in server/client side collection. */
44 | else
45 | {
46 | return new SessionPlayer(networkManager, clientPlayerId, connectionId);
47 | }
48 | }
49 | //Only server and not found.
50 | else
51 | {
52 | networkManager.LogWarning($"Unable to find Session Player for read ClientPlayerId " + clientPlayerId + " An empty connection will be returned.");
53 | return SessionPlayer.Empty;
54 | }
55 | }
56 | //Try client side, will only be able to fetch against local connection.
57 | else
58 | {
59 | //If value is self then return self.
60 | if (clientPlayerId == networkManager.GetClientSessionManager().Player.ClientPlayerId)
61 | return networkManager.GetClientSessionManager().Player;
62 | //Try client side dictionary.
63 | else if (networkManager.GetClientSessionManager().Players
64 | .TryGetValue(clientPlayerId, out SessionPlayer result))
65 | return result;
66 | /* Otherwise make a new instance.
67 | * We do not know if this is for the server or client so
68 | * initialize it either way. Connections rarely come through
69 | * without being in server/client side collection. */
70 | else
71 | return new SessionPlayer(networkManager, clientPlayerId, connectionId);
72 | }
73 | }
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Components/NetworkSessionObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using FishNet.Connection;
3 | using FishNet.Object;
4 | using FishNet.Object.Synchronizing;
5 | using FishNet.Utility.Extension;
6 | using UnityEngine;
7 |
8 | namespace FishNet.Alven.SessionManagement
9 | {
10 | ///
11 | /// Component for managing session players network objects.
12 | ///
13 | [RequireComponent(typeof(NetworkObject))]
14 | public sealed class NetworkSessionObject : NetworkBehaviour
15 | {
16 | private readonly SyncVar _ownerPlayer = new SyncVar();
17 | ///
18 | /// Owner SessionPlayer of this object.
19 | ///
20 | public SessionPlayer OwnerPlayer
21 | {
22 | get => IsOffline ? null : _ownerPlayer.Value;
23 | set => _ownerPlayer.Value = value;
24 | }
25 |
26 | ///
27 | /// The local SessionPlayer of the client calling this method.
28 | ///
29 | public SessionPlayer LocalPlayer => ClientSessionManager.Player;
30 | public ServerSessionManager ServerSessionManager => NetworkManager.GetServerSessionManager();
31 | public ClientSessionManager ClientSessionManager => NetworkManager.GetClientSessionManager();
32 |
33 | ///
34 | /// Invoked on after ownership has changed.
35 | ///
36 | public event Action OnOwnershipPlayer;
37 |
38 | internal bool GivingOwnership;
39 | internal bool IsSpawning;
40 |
41 | [SerializeField, HideInInspector]
42 | internal NetworkSessionObject[] ChildNetworkSessionObjects;
43 |
44 | private void Awake()
45 | {
46 | _ownerPlayer.OnChange += OnOwnerPlayerChanged;
47 | }
48 |
49 | private void OnDestroy()
50 | {
51 | _ownerPlayer.OnChange -= OnOwnerPlayerChanged;
52 | }
53 |
54 | internal void Initialize(SessionPlayer ownerPlayer)
55 | {
56 | SetOwner(ownerPlayer);
57 | }
58 |
59 | private void SetOwner(SessionPlayer newOwner)
60 | {
61 | if (newOwner != null && newOwner.IsValid)
62 | {
63 | OwnerPlayer = newOwner;
64 | }
65 | else
66 | {
67 | OwnerPlayer = null;
68 | }
69 | }
70 |
71 | ///
72 | /// Gives ownership to newOwner.
73 | ///
74 | /// Session Player
75 | ///
76 | /// If True, the object will be transferred to the owner immediately.
77 | /// If player is not its observer, the observers are rebuilt.
78 | /// False if there is no need to rebuild the observers.
79 | /// The object will be transferred to the player's possession as soon as the player becomes an observer.
80 | [Server]
81 | public void GiveOwnershipPlayer(SessionPlayer newOwner, bool rebuildObservers = true)
82 | {
83 | if (rebuildObservers || NetworkObject.Observers.Contains(newOwner?.NetworkConnection))
84 | {
85 | GivingOwnership = true;
86 | if (newOwner != null && newOwner.IsValid)
87 | {
88 | OwnerPlayer = newOwner;
89 | if (newOwner.IsConnected)
90 | {
91 | GiveOwnership(newOwner.NetworkConnection);
92 | }
93 | else
94 | {
95 | RemoveOwnership();
96 | }
97 | }
98 | else
99 | {
100 | OwnerPlayer = null;
101 | }
102 |
103 | GivingOwnership = false;
104 | }
105 | else
106 | {
107 | SetOwner(newOwner);
108 | }
109 | }
110 |
111 | private void OnOwnerPlayerChanged(SessionPlayer prev, SessionPlayer next, bool asServer)
112 | {
113 | if (!NetworkManager.DoubleLogic(asServer) && (IsServerInitialized || next == null || next.IsLocalPlayer))
114 | {
115 | if (prev != null && prev.IsValid)
116 | {
117 | prev.RemoveObject(this);
118 | }
119 |
120 | if (next != null && next.IsValid)
121 | {
122 | next.AddObject(this);
123 | }
124 | }
125 | OnOwnershipPlayer?.Invoke(prev, next, asServer);
126 | }
127 |
128 | public override void OnStopNetwork()
129 | {
130 | OwnerPlayer?.RemoveObject(this);
131 | }
132 |
133 | public override void OnSpawnServer(NetworkConnection connection)
134 | {
135 | if (!IsSpawning && GivingOwnership) return;
136 |
137 | if (connection == OwnerPlayer?.NetworkConnection && Owner != connection)
138 | {
139 | GivingOwnership = true;
140 | GiveOwnership(connection);
141 | GivingOwnership = false;
142 | }
143 | }
144 |
145 | public override void OnOwnershipServer(NetworkConnection prevOwner)
146 | {
147 | if (GivingOwnership) return;
148 |
149 | OwnerPlayer = null;
150 | }
151 |
152 | public override void OnDespawnServer(NetworkConnection connection)
153 | {
154 | bool hasOwner = Owner != null && Owner.IsValid && Owner.Objects.Contains(NetworkObject);
155 |
156 | if (hasOwner && OwnerPlayer != null && OwnerPlayer.IsValid && OwnerPlayer.NetworkConnection == connection)
157 | {
158 | GivingOwnership = true;
159 | RemoveOwnership();
160 | GivingOwnership = false;
161 | }
162 | }
163 |
164 | protected override void OnValidate()
165 | {
166 | base.OnValidate();
167 |
168 | ChildNetworkSessionObjects = GetComponentsInChildren(true);
169 | }
170 | }
171 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/SessionPlayers/SessionPlayer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using FishNet.CodeGenerating;
4 | using FishNet.Connection;
5 | using FishNet.Managing;
6 |
7 | namespace FishNet.Alven.SessionManagement
8 | {
9 | [UseGlobalCustomSerializer]
10 | public sealed class SessionPlayer
11 | {
12 | internal const int UNSET_CLIENTID = -1;
13 | public static SessionPlayer Empty { get; private set; } = new SessionPlayer();
14 |
15 | ///
16 | /// Objects owned by this Session Player. Available to this connection and server.
17 | ///
18 | public IReadOnlyCollection Objects => _objects;
19 | public NetworkManager NetworkManager { get; private set; }
20 | ///
21 | /// Available to server and clients.
22 | ///
23 | public int ClientPlayerId { get; private set; } = UNSET_CLIENTID;
24 | ///
25 | /// Available to server.
26 | ///
27 | public string PlayerId { get; private set; }
28 | public bool IsLocalPlayer => ClientSessionManager.Player == this;
29 | public bool IsValid => ClientPlayerId != UNSET_CLIENTID;
30 | public bool IsConnected => NetworkConnection != null && NetworkConnection.IsActive;
31 | public NetworkConnection NetworkConnection
32 | {
33 | get
34 | {
35 | if (_connection != null && _connection.ClientId == ConnectionId)
36 | {
37 | return _connection;
38 | }
39 |
40 | if (_asServer)
41 | {
42 | if (NetworkManager.ServerManager.Clients.TryGetValue(ConnectionId, out _connection))
43 | {
44 | return _connection;
45 | }
46 | }
47 | else
48 | {
49 | // This is done to get rid of some of the problems of the initialization order of NetworkConnection and SessionPlayer.
50 | if (NetworkManager.ClientManager.Connection.ClientId == ConnectionId)
51 | {
52 | _connection = NetworkManager.ClientManager.Connection;
53 | return _connection;
54 | }
55 |
56 | if (NetworkManager.ClientManager.Clients.TryGetValue(ConnectionId, out _connection))
57 | {
58 | return _connection;
59 | }
60 | }
61 |
62 | return NetworkManager.EmptyConnection;
63 | }
64 | private set
65 | {
66 | _connection = value;
67 | ConnectionId = value == null ? NetworkConnection.UNSET_CLIENTID_VALUE : _connection.ClientId;
68 | }
69 | }
70 |
71 | ///
72 | /// Invokes when an NetworkPlayerObject is added for this player. Available to this connection and server.
73 | ///
74 | public event Action OnObjectAdded;
75 | ///
76 | /// Invokes when an NetworkPlayerObject is removed for this player. Available to this connection and server.
77 | ///
78 | public event Action OnObjectRemoved;
79 |
80 | internal bool IsConnectedFirstTime { get; set; } = true;
81 | internal int ConnectionId { get; private set; } = NetworkConnection.UNSET_CLIENTID_VALUE;
82 |
83 | private NetworkConnection _connection;
84 | private readonly HashSet _objects = new HashSet();
85 | private readonly bool _asServer;
86 | private ClientSessionManager ClientSessionManager =>
87 | NetworkManager == null ? null : NetworkManager.GetClientSessionManager();
88 |
89 | internal SessionPlayer(NetworkManager networkManager, int clientPlayerId, int connectionId, bool asServer = false)
90 | {
91 | NetworkManager = networkManager;
92 | ClientPlayerId = clientPlayerId;
93 | ConnectionId = connectionId;
94 | _asServer = asServer;
95 | }
96 |
97 | internal SessionPlayer(NetworkManager networkManager, int clientPlayerId, NetworkConnection connection, bool asServer = false)
98 | {
99 | NetworkManager = networkManager;
100 | ClientPlayerId = clientPlayerId;
101 | NetworkConnection = connection;
102 | _asServer = asServer;
103 | }
104 |
105 | internal SessionPlayer(NetworkManager networkManager, int clientPlayerId, NetworkConnection connection, string playerId, bool asServer = true)
106 | {
107 | NetworkManager = networkManager;
108 | ClientPlayerId = clientPlayerId;
109 | NetworkConnection = connection;
110 | PlayerId = playerId;
111 | _asServer = asServer;
112 | }
113 |
114 | public SessionPlayer()
115 | {
116 |
117 | }
118 |
119 | internal void Dispose()
120 | {
121 | NetworkManager = null;
122 | ConnectionId = NetworkConnection.UNSET_CLIENTID_VALUE;
123 | PlayerId = null;
124 | ClientPlayerId = UNSET_CLIENTID;
125 | _objects.Clear();
126 | _connection = null;
127 | }
128 |
129 | internal void SetupReconnection(NetworkConnection connection)
130 | {
131 | IsConnectedFirstTime = false;
132 | NetworkConnection = connection;
133 | }
134 |
135 | internal void SetupReconnection(int connectionId)
136 | {
137 | IsConnectedFirstTime = false;
138 | ConnectionId = connectionId;
139 | }
140 |
141 | internal void AddObject(NetworkSessionObject networkObject)
142 | {
143 | if (!IsValid) return;
144 |
145 | if (_objects.Add(networkObject))
146 | {
147 | OnObjectAdded?.Invoke(networkObject);
148 | }
149 | }
150 |
151 | internal void RemoveObject(NetworkSessionObject networkObject)
152 | {
153 | if (!IsValid)
154 | {
155 | _objects.Clear();
156 | return;
157 | }
158 |
159 | if (_objects.Remove(networkObject))
160 | {
161 | OnObjectRemoved?.Invoke(networkObject);
162 | }
163 | }
164 |
165 | public override bool Equals(object obj)
166 | {
167 | if (obj is SessionPlayer other)
168 | return Equals(other);
169 | return false;
170 | }
171 |
172 | public bool Equals(SessionPlayer other)
173 | {
174 | if (other is null) return false;
175 | //If either is -1 Id.
176 | if (ClientPlayerId == UNSET_CLIENTID || other.ClientPlayerId == UNSET_CLIENTID)
177 | return false;
178 | //Same object.
179 | if (ReferenceEquals(this, other)) return true;
180 |
181 | return ClientPlayerId == other.ClientPlayerId;
182 | }
183 |
184 | public override int GetHashCode() => ClientPlayerId;
185 |
186 | public static bool operator ==(SessionPlayer a, SessionPlayer b)
187 | {
188 | if (a is null && b is null)
189 | return true;
190 | if (a is null)
191 | return false;
192 |
193 | return !(b == null) && b.Equals(a);
194 | }
195 |
196 | public static bool operator !=(SessionPlayer a, SessionPlayer b) => !(a == b);
197 | }
198 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Components/SessionPlayerSpawner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using FishNet.Connection;
4 | using FishNet.Managing;
5 | using FishNet.Object;
6 | using UnityEngine;
7 |
8 | namespace FishNet.Alven.SessionManagement
9 | {
10 | ///
11 | /// Spawns a player object for session players when they connect.
12 | /// Must be placed on or beneath the NetworkManager object.
13 | ///
14 | [AddComponentMenu("FishNet/Component/SessionPlayerSpawner")]
15 | public class SessionPlayerSpawner : MonoBehaviour
16 | {
17 | #region Public.
18 | ///
19 | /// Called on the server when a player is spawned.
20 | ///
21 | public event Action OnSpawned;
22 | #endregion
23 |
24 | #region Serialized.
25 | ///
26 | /// Prefab to spawn for the session player.
27 | ///
28 | [Tooltip("Prefab to spawn for the session player.")]
29 | [SerializeField]
30 | private NetworkSessionObject _playerPrefab;
31 | ///
32 | /// Sets the PlayerPrefab to use.
33 | ///
34 | ///
35 | public void SetPlayerPrefab(NetworkSessionObject nob) => _playerPrefab = nob;
36 | ///
37 | /// True to add player to the active scene when no global scenes are specified through the SceneManager.
38 | ///
39 | [Tooltip("True to add player to the active scene when no global scenes are specified through the SceneManager.")]
40 | [SerializeField]
41 | private bool _addToDefaultScene = true;
42 | ///
43 | /// Areas in which players may spawn.
44 | ///
45 | [Tooltip("Areas in which players may spawn.")]
46 | public Transform[] Spawns = new Transform[0];
47 | #endregion
48 |
49 | #region Private.
50 | ///
51 | /// NetworkManager on this object or within this objects parents.
52 | ///
53 | private NetworkManager _networkManager;
54 | ///
55 | /// Next spawns to use.
56 | ///
57 | private int _nextSpawn;
58 | #endregion
59 |
60 | private readonly Dictionary _playerObjects = new Dictionary();
61 |
62 | private ServerSessionManager _serverSessionManager;
63 |
64 | private void Start()
65 | {
66 | InitializeOnce();
67 | }
68 |
69 | private void OnDestroy()
70 | {
71 | if (_networkManager != null)
72 | {
73 | _networkManager.SceneManager.OnClientLoadedStartScenes -= SceneManager_OnClientLoadedStartScenes;
74 | _serverSessionManager.OnRemotePlayerConnectionState -= ServerSessionManager_OnRemotePlayerConnectionState;
75 | }
76 | }
77 |
78 | ///
79 | /// Initializes this script for use.
80 | ///
81 | private void InitializeOnce()
82 | {
83 | _networkManager = InstanceFinder.NetworkManager;
84 | if (_networkManager == null)
85 | {
86 | NetworkManagerExtensions.LogWarning($"PlayerSpawner on {gameObject.name} cannot work as NetworkManager wasn't found on this object or within parent objects.");
87 | return;
88 | }
89 |
90 | _networkManager.SceneManager.OnClientLoadedStartScenes += SceneManager_OnClientLoadedStartScenes;
91 | _serverSessionManager = _networkManager.GetServerSessionManager();
92 | _serverSessionManager.OnRemotePlayerConnectionState += ServerSessionManager_OnRemotePlayerConnectionState;
93 | }
94 |
95 | ///
96 | /// Called when a client loads initial scenes after connecting.
97 | ///
98 | private void SceneManager_OnClientLoadedStartScenes(NetworkConnection conn, bool asServer)
99 | {
100 | if (!asServer)
101 | return;
102 | SessionPlayer player = conn.GetSessionPlayer();
103 |
104 | // A player object can be created for a player if the player has previously connected and loaded the start scenes.
105 | if (_playerObjects.TryGetValue(player, out NetworkSessionObject spawnedPlayer))
106 | {
107 | if (!spawnedPlayer)
108 | {
109 | NetworkManagerExtensions.LogWarning($@"Session Player {player} is reconnected, but Player object that was created with SessionPlayerSpawner was destroyed.");
110 | return;
111 | }
112 | if (spawnedPlayer.IsOffline)
113 | {
114 | NetworkManagerExtensions.LogWarning($@"Session Player {player} is reconnected, but Player object that was created with SessionPlayerSpawner was despawned.");
115 | return;
116 | }
117 | if (_addToDefaultScene)
118 | {
119 | _networkManager.SceneManager.AddOwnerToDefaultScene(spawnedPlayer.NetworkObject);
120 | }
121 | return;
122 | }
123 |
124 | if (_playerPrefab == null)
125 | {
126 | NetworkManagerExtensions.LogWarning($"Player prefab is empty and cannot be spawned for connection {conn.ClientId}.");
127 | return;
128 | }
129 |
130 | Vector3 position;
131 | Quaternion rotation;
132 | SetSpawn(_playerPrefab.transform, out position, out rotation);
133 |
134 | NetworkObject nob = _networkManager.GetPooledInstantiated(_playerPrefab.NetworkObject, position, rotation, true);
135 | var networkPlayerObject = nob.GetComponent();
136 | _playerObjects.Add(player, networkPlayerObject);
137 | _networkManager.ServerManager.Spawn(networkPlayerObject, player);
138 |
139 | //If there are no global scenes
140 | if (_addToDefaultScene)
141 | _networkManager.SceneManager.AddOwnerToDefaultScene(nob);
142 |
143 | OnSpawned?.Invoke(nob);
144 | }
145 |
146 | ///
147 | /// Called when a player connection state changed.
148 | ///
149 | private void ServerSessionManager_OnRemotePlayerConnectionState(SessionPlayer player, RemotePlayerConnectionStateArgs args)
150 | {
151 | if (args.State == PlayerConnectionState.PermanentlyDisconnected)
152 | {
153 | _playerObjects.Remove(player);
154 | }
155 | }
156 |
157 | ///
158 | /// Sets a spawn position and rotation.
159 | ///
160 | ///
161 | ///
162 | ///
163 | private void SetSpawn(Transform prefab, out Vector3 pos, out Quaternion rot)
164 | {
165 | //No spawns specified.
166 | if (Spawns.Length == 0)
167 | {
168 | SetSpawnUsingPrefab(prefab, out pos, out rot);
169 | return;
170 | }
171 |
172 | Transform result = Spawns[_nextSpawn];
173 | if (result == null)
174 | {
175 | SetSpawnUsingPrefab(prefab, out pos, out rot);
176 | }
177 | else
178 | {
179 | pos = result.position;
180 | rot = result.rotation;
181 | }
182 |
183 | //Increase next spawn and reset if needed.
184 | _nextSpawn++;
185 | if (_nextSpawn >= Spawns.Length)
186 | _nextSpawn = 0;
187 | }
188 |
189 | ///
190 | /// Sets spawn using values from prefab.
191 | ///
192 | ///
193 | ///
194 | ///
195 | private void SetSpawnUsingPrefab(Transform prefab, out Vector3 pos, out Quaternion rot)
196 | {
197 | pos = prefab.position;
198 | rot = prefab.rotation;
199 | }
200 | }
201 | }
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/ClientSessionManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using FishNet.Connection;
5 | using FishNet.Managing;
6 | using FishNet.Managing.Client;
7 | using FishNet.Transporting;
8 | using UnityEngine;
9 |
10 | namespace FishNet.Alven.SessionManagement
11 | {
12 | [DisallowMultipleComponent]
13 | [AddComponentMenu("FishNet/Manager/ClientSessionManager")]
14 | [DefaultExecutionOrder(-20000)]
15 | [RequireComponent(typeof(ClientManager))]
16 | public sealed class ClientSessionManager : MonoBehaviour
17 | {
18 | ///
19 | /// The local Session Player.
20 | ///
21 | public SessionPlayer Player { get; private set; } = SessionPlayer.Empty;
22 | ///
23 | /// Connected Session Players by ClientPlayerIds.
24 | ///
25 | public IReadOnlyDictionary Players => _players;
26 | ///
27 | /// NetworkManager for client.
28 | ///
29 | public NetworkManager NetworkManager => _clientManager.NetworkManager;
30 | ///
31 | /// Called after the remote Session Player connection state changes.
32 | ///
33 | public event Action OnRemotePlayerConnectionState;
34 | ///
35 | /// Called after the local Session Player connection state changes.
36 | ///
37 | public event Action OnPlayerConnectionState;
38 |
39 | private readonly Dictionary _players = new Dictionary();
40 | private readonly Dictionary _playersByConnectionIds = new Dictionary();
41 | private ClientManager _clientManager;
42 |
43 | private void Awake()
44 | {
45 | _clientManager = GetComponent();
46 | if (!NetworkManager) return;
47 | NetworkManager.RegisterInstance(this);
48 | _clientManager.OnClientConnectionState += OnClientConnectionState;
49 | _clientManager.OnRemoteConnectionState += OnRemoteConnectionState;
50 | _clientManager.OnAuthenticated += OnAuthenticated;
51 |
52 | _clientManager.RegisterBroadcast(OnPlayerConnectedBroadcast);
53 | _clientManager.RegisterBroadcast(OnConnectedPlayersBroadcast);
54 | _clientManager.RegisterBroadcast(OnPlayerConnectionBroadcast);
55 | }
56 |
57 | private void OnRemoteConnectionState(RemoteConnectionStateArgs args)
58 | {
59 | if (args.ConnectionState == RemoteConnectionState.Started)
60 | {
61 | SessionPlayer player = _playersByConnectionIds[args.ConnectionId];
62 |
63 | InvokeOnPlayerConnectionState(
64 | player.IsConnectedFirstTime ? PlayerConnectionState.Connected : PlayerConnectionState.Reconnected,
65 | player.ClientPlayerId);
66 | }
67 | }
68 |
69 | private void OnDestroy()
70 | {
71 | if (!NetworkManager) return;
72 | NetworkManager.UnregisterInstance();
73 |
74 | _clientManager.OnClientConnectionState -= OnClientConnectionState;
75 | _clientManager.OnRemoteConnectionState -= OnRemoteConnectionState;
76 | _clientManager.OnAuthenticated -= OnAuthenticated;
77 |
78 | _clientManager.UnregisterBroadcast(OnPlayerConnectedBroadcast);
79 | _clientManager.UnregisterBroadcast(OnConnectedPlayersBroadcast);
80 | _clientManager.UnregisterBroadcast(OnPlayerConnectionBroadcast);
81 |
82 | Reset();
83 | }
84 |
85 | internal SessionPlayer GetPlayer(NetworkConnection connection) => _playersByConnectionIds[connection.ClientId];
86 |
87 | ///
88 | /// Called before ClientManager.OnAuthenticated.
89 | ///
90 | private void OnPlayerConnectedBroadcast(PlayerConnectedBroadcast broadcast, Channel channel)
91 | {
92 | Player = broadcast.Player;
93 | Player.IsConnectedFirstTime = !broadcast.IsReconnected;
94 | }
95 |
96 | private void OnAuthenticated()
97 | {
98 | if (Player.IsConnectedFirstTime)
99 | {
100 | InvokeOnPlayerConnectionState(LocalPlayerConnectionState.Connected);
101 | }
102 | else
103 | {
104 | InvokeOnPlayerConnectionState(LocalPlayerConnectionState.Reconnected);
105 | }
106 | }
107 |
108 | private void OnClientConnectionState(ClientConnectionStateArgs args)
109 | {
110 | if (args.ConnectionState == LocalConnectionState.Stopped)
111 | {
112 | InvokeOnPlayerConnectionState(LocalPlayerConnectionState.Disconnected);
113 | Reset();
114 | }
115 | }
116 |
117 | private void InvokeOnPlayerConnectionState(LocalPlayerConnectionState state)
118 | {
119 | OnPlayerConnectionState?.Invoke(new PlayerConnectionStateArgs(state));
120 | }
121 |
122 | private void Reset()
123 | {
124 | Player = SessionPlayer.Empty;
125 | ClearPlayers();
126 | }
127 |
128 | private void OnConnectedPlayersBroadcast(ConnectedPlayersBroadcast broadcast, Channel channel)
129 | {
130 | ClearPlayers();
131 |
132 | for (var i = 0; i < broadcast.ClientPlayerIds.Length; i++)
133 | {
134 | int clientPlayerId = broadcast.ClientPlayerIds[i];
135 | int connectionId = broadcast.ConnectionIds[i];
136 |
137 | AddPlayer(new SessionPlayer(NetworkManager, clientPlayerId, connectionId));
138 | }
139 | }
140 |
141 | private void ClearPlayers()
142 | {
143 | foreach (SessionPlayer player in _players.Values.ToArray())
144 | {
145 | RemovePlayer(player);
146 | player.Dispose();
147 | }
148 | }
149 |
150 | private void OnPlayerConnectionBroadcast(PlayerConnectionChangeBroadcast args, Channel channel)
151 | {
152 | int clientPlayerId = args.ClientPlayerId;
153 | PlayerConnectionState state = args.State;
154 | switch (state)
155 | {
156 | case PlayerConnectionState.Connected:
157 | AddPlayer(new SessionPlayer(NetworkManager, clientPlayerId, args.ConnectionId));
158 | // InvokeOnPlayerConnectionState will be called from OnRemoteConnectionState.
159 | break;
160 | case PlayerConnectionState.Reconnected:
161 | ReconnectPlayer(_players[clientPlayerId], args.ConnectionId);
162 | // InvokeOnPlayerConnectionState will be called from OnRemoteConnectionState.
163 | break;
164 | case PlayerConnectionState.TemporarilyDisconnected:
165 | InvokeOnPlayerConnectionState(state, clientPlayerId);
166 | break;
167 | case PlayerConnectionState.PermanentlyDisconnected:
168 | if (Players.TryGetValue(clientPlayerId, out SessionPlayer p))
169 | {
170 | RemovePlayer(p);
171 | p.Dispose();
172 | }
173 | InvokeOnPlayerConnectionState(state, clientPlayerId);
174 | break;
175 | default:
176 | throw new ArgumentOutOfRangeException();
177 | }
178 | }
179 |
180 | private void InvokeOnPlayerConnectionState(PlayerConnectionState state, int clientPlayerId)
181 | {
182 | var rcs = new RemotePlayerConnectionStateArgs(state, clientPlayerId);
183 | OnRemotePlayerConnectionState?.Invoke(rcs);
184 | }
185 |
186 | private void AddPlayer(SessionPlayer player)
187 | {
188 | _players[player.ClientPlayerId] = player;
189 | _playersByConnectionIds[player.ConnectionId] = player;
190 | }
191 |
192 | private void RemovePlayer(SessionPlayer player)
193 | {
194 | _players.Remove(player.ClientPlayerId);
195 | _playersByConnectionIds.Remove(player.ConnectionId);
196 | }
197 |
198 | private void ReconnectPlayer(SessionPlayer player, int connectionId)
199 | {
200 | player.SetupReconnection(connectionId);
201 | _playersByConnectionIds.Remove(player.ConnectionId);
202 | _playersByConnectionIds[connectionId] = player;
203 | }
204 | }
205 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Session Management for FishNet.
2 |
3 | There is often a need to save the player's state when disconnecting from the server and accurately
4 | restore them to the player when reconnecting. For example, you may want to save a player's health, inventory.
5 | and other data so that the player can reconnect to that session and continue from the same state.
6 |
7 | Currently, FishNet does not have a built-in feature that allows you to do this, so this asset may look
8 | unfamiliar. But I've tried to keep it as simple as possible and make it look like it's a native feature.
9 |
10 | But you can participate in testing and report bugs. You can also suggest ideas to improve the documentation and add new features to the asset.
11 |
12 | If you have further questions, come find me as `ooonush` in the [FirstGearGames Discord](https://discord.gg/Ta9HgDh4Hj)!
13 |
14 | ## Support the Developer
15 |
16 | Asset is completely free and created entirely by myself.
17 | **If you want to support me, you can do so with [Donation Alerts](https://www.donationalerts.com/r/ooonush).**
18 |
19 | ## Requirements
20 |
21 | - FishNet 4.1.3 or newer.
22 |
23 | ## Installation
24 |
25 | Add this Git URL to Package Manager:
26 | ```
27 | https://github.com/ooonush/com.alven.fishnet.session-management.git?path=Assets/FishNet/Plugins/Alven/SessionManagement
28 | ```
29 |
30 | ## Player Identification
31 |
32 | In FishNet, when a player reconnects, a new NetworkConnection with a different ClientId is created for that player.
33 | This means that the server has no way of knowing whether a new player is connecting or reconnecting a previously connected player.
34 |
35 | Thus, the Session Manager uses the PlayerId string to identify players.
36 | When connecting, a player must authenticate by passing his PlayerId to the server.
37 | The server checks this PlayerId for correctness and matches it with the PlayerId of previously connected players.
38 | If such a PlayerId is found, the player reconnects and the server transfers the previously owned NetworkObjects to the player.
39 | Otherwise, the player is connected as a new player.
40 |
41 | This asset already has an authenticator that generates a PlayerId for the player.
42 | In order to use it, you must add a `BasicSessionAuthenticator` component to the scene and assign it to the
43 | [Authenticator](https://fish-networking.gitbook.io/docs/manual/components/authenticator) field in the
44 | [ServerManager](https://fish-networking.gitbook.io/docs/manual/components/managers/server-manager) component.
45 |
46 | The `BasicSessionAuthenticator` may not suit you and you can implement your own custom authentication.
47 | To do this you need to inherit the `SessionAuthenticator` abstract class.
48 | The authentication happens in the same way as in the
49 | [Authenticator](https://fish-networking.gitbook.io/docs/manual/components/authenticator) that is in FishNet.
50 | The main difference is that instead of the `OnAuthenticationResult` event, you have to call the `PassAuthentication` or `FailAuthentication` methods depending on whether the authentication has passed.
51 | You can see an example use case in the `BasicSessionAuthenticator.cs` script.
52 |
53 | ### Unity Authentication Support
54 |
55 | There is also support for `Unity Authentication` in this package.
56 | To use it, you need to install [Unity Authentication](https://unity.com/products/authentication) package and use `UnitySessionAuthenticator` component.
57 |
58 | ## Components Setup
59 |
60 | For the session manager to work, you must add several components:
61 | 1. `SessionAuthenticator` you want to use. Remember to assign it to the `Authenticator` field in the `ServerManager` component.
62 | 2. `ServerSessionManager` in the `ServerManager` GameObject.
63 | 3. `ClientSessionManager` in the `ClientManager` GameObject.
64 |
65 | ## SessionPlayer
66 | Instead of `NetworkConnection`, the `SessionPlayer` class is used in the Session Manager.
67 | Unlike `NetworkConnection`, `SessionPlayer` does not change when you reconnect.
68 |
69 | In `SessionPlayer` you can access properties such as:
70 | - string PlayerId // Available to server.
71 | - int ClientPlayerId // Available to server and clients.
72 | - bool IsLocalPlayer
73 | - bool IsConnected
74 | - NetworkConnection NetworkConnection
75 | - ...
76 |
77 | As you can see, the `PlayerId` string is only available on the server, which ensures security.
78 | Clients cannot recognize the PlayerId of other players, and so that they can still distinguish between players, there is a ClientPlayerId.
79 | This is a unique player identifier that is available to both server and clients.
80 |
81 | ## ServerSessionManager
82 |
83 | `ServerSessionManager`, oddly enough, is responsible for the server side of Session Management. This is similar to `ServerManager`.
84 | You can get the `ServerSessionManager` by using `NetworkManager.GetServerSessionManager()`.
85 |
86 | An important detail is that by default, `ServerSessionManager` does not store information about previously connected players.
87 | That is, when reconnecting, players will connect as new players.
88 |
89 | In order to change this, you must call the `StartSession()` method.
90 | And when you no longer need to store previously connected players, you can call the `EndSession()` method.
91 | You can also change the `IsSessionStarted` value in the inspector.
92 |
93 | The OnRemotePlayerConnectionState event is available in this class. It is called when the player states are changed:
94 | - **Connected** - The player has been connected first time. Is called after the player has been authenticated.
95 | - **Reconnected** - The player has been reconnected to this session. Is called after the player has been authenticated.
96 | - **PermanentlyDisconnected** - The player has been permanently disconnected and cannot reconnect to this session. Next time it will connect as a new player with the same PlayerId.
97 | - **TemporarilyDisconnected** - The player has been temporarily disconnected and can reconnect to this session using their own PlayerId.
98 |
99 | ## ClientSessionManager
100 |
101 | The `ClientSessionManager` is responsible for the client side of Session Management. This is similar to `ClientManager`.
102 | You can get the `ClientSessionManager` by using `NetworkManager.GetClientSessionManager()`.
103 |
104 | In this class, besides `OnRemotePlayerConnectionState`, the `OnPlayerConnectionState` event is also available, which is called for the local player:
105 | - **Connected** // The player has been connected first time. Is called after the player has been authenticated.
106 | - **Reconnected** // The player has been reconnected to this session. Is called after the player has been authenticated.
107 | - **Disconnected** // The player was disconnected. If a session was started on the server, he was disconnected temporarily and can reconnect. Otherwise he is disconnected permanently and next time he will connect as a new player with the same PlayerId.
108 |
109 | ## Getting SessionPlayer from NetworkConnection.
110 |
111 | In some cases, you can access the `NetworkConnection` using the `NetworkConnection.GetSessionPlayer()` method.
112 |
113 | This can be done inside `NetworkBehaviour` inside methods like `OnStartNetwork()`, `OnStartServer()` and so on.
114 | Basically, whenever the `NetworkBehaviour` exists on the network.
115 |
116 | You can also access `SessionPlayer` during calls to `ServerManager.OnAuthenticationResult`,
117 | `ClientManager.OnAuthenticated`, and `ClientManager.OnClientConnectionState`, `ClientManager.OnRemoteConnectionState`
118 | when ConnectionState is **Started**.
119 |
120 | Example:
121 | ```csharp
122 | private void Awake()
123 | {
124 | InstanceFinder.ClientManager.OnRemoteConnectionState += OnRemoteConnectionState;
125 | }
126 |
127 | private void OnRemoteConnectionState(RemoteConnectionStateArgs args)
128 | {
129 | NetworkConnection connection = InstanceFinder.ClientManager.Clients[args.ConnectionId];
130 | if (args.ConnectionState == RemoteConnectionState.Started)
131 | {
132 | // Getting SessionPlayer from NetworkConnection.
133 | SessionPlayer player = connection.GetSessionPlayer();
134 | Debug.Log("SessionPlayer Started : " + player.ClientPlayerId);
135 | }
136 | else
137 | {
138 | // SessionPlayer is not available when ConnectionState is not Started.
139 | // SessionPlayer player = connection.GetSessionPlayer();
140 | }
141 | }
142 | ```
143 |
144 | This may seem complicated and confusing, so I recommend not using callbacks from the
145 | `ServerManager` and `ClientManager` to get `SessionPlayer`. It is better to use `ClientSessionManager` and `ServerSessionManager`.
146 |
147 | ## SessionPlayer's ownership of NetworkObjects
148 |
149 | Just like `NetworkConnection`, `SessionPlayer` can own NetworkObjects.
150 | Objects owned by the player become the property of the server when temporarily disconnected.
151 | When reconnected, they are transferred back to the player.
152 |
153 | To give ownership of an object to a player, you must add a `NetworkSessionObject` component
154 | and call the `GiveOwnershipPlayer(SessionPlayer newOwner)` method. And `RemoveOwnership()` to remove ownership:
155 |
156 | ```csharp
157 | public class Foo : NetworkBehaviour
158 | {
159 | public void CustomGiveOwnership(SessionPlayer sessionPlayer)
160 | {
161 | GetComponent().GiveOwnershipPlayer(sessionPlayer);
162 | }
163 |
164 | public void CustomRemoveOwnership(SessionPlayer sessionPlayer)
165 | {
166 | RemoveOwnership();
167 | }
168 | }
169 | ```
170 |
171 | You can also call the `Spawn()` method to create an object in the player's ownership:
172 | ```csharp
173 | [SerializeField] private NetworkSessionObject _playerPrefab;
174 |
175 | private void Awake()
176 | {
177 | // Getting the ServerSessionManager
178 | var serverSessionManager = InstanceFinder.GetInstance();
179 | serverSessionManager.OnRemotePlayerConnectionState += OnRemotePlayerConnectionState;
180 | }
181 |
182 | private void OnRemotePlayerConnectionState(SessionPlayer sessionPlayer, RemotePlayerConnectionStateArgs args)
183 | {
184 | if (args.State == PlayerConnectionState.Connected)
185 | {
186 | NetworkSessionObject player = Instantiate(_playerPrefab);
187 |
188 | // Spawn player with session player ownership.
189 | InstanceFinder.ServerManager.Spawn(player, sessionPlayer);
190 | }
191 | }
192 | ```
193 |
194 | There is a `SessionPlayerSpawner` component in the asset that follows the `PlayerSpawner` logic from FishNet,
195 | but creates an object in the possession of SessionPlayer instead of `NetworkConnection`.
196 |
--------------------------------------------------------------------------------
/Assets/FishNet/Plugins/Alven/SessionManagement/Runtime/Managers/ServerSessionManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using FishNet.Connection;
5 | using FishNet.Managing;
6 | using FishNet.Managing.Server;
7 | using FishNet.Transporting;
8 | using UnityEngine;
9 |
10 | namespace FishNet.Alven.SessionManagement
11 | {
12 | [DisallowMultipleComponent]
13 | [AddComponentMenu("FishNet/Manager/ServerSessionManager")]
14 | [DefaultExecutionOrder(-20000)]
15 | [RequireComponent(typeof(ServerManager))]
16 | public sealed class ServerSessionManager : MonoBehaviour
17 | {
18 | ///
19 | /// NetworkManager for server.
20 | ///
21 | public NetworkManager NetworkManager => _serverManager.NetworkManager;
22 | ///
23 | /// Connected Session Players by ClientPlayerIds.
24 | ///
25 | public IReadOnlyDictionary Players => _playersByClientIds;
26 |
27 | ///
28 | /// Start a Session on server starting.
29 | ///
30 | [SerializeField] private bool _initiallySessionStarted;
31 | public bool IsSessionStarted { get; private set; }
32 | public event Action OnSessionStarted;
33 | public event Action OnSessionEnded;
34 |
35 | ///
36 | /// Called after the remote Session Player connection state changes.
37 | ///
38 | public event Action OnRemotePlayerConnectionState;
39 |
40 | private readonly Dictionary _players = new Dictionary();
41 | private readonly Dictionary _playersByClientIds = new Dictionary();
42 | private readonly Dictionary _playersByConnectionIds = new Dictionary();
43 |
44 | private ServerManager _serverManager;
45 | private int _nextClientPlayerId;
46 | private bool ShareIds => _serverManager.ShareIds;
47 |
48 | private void Awake()
49 | {
50 | _serverManager = GetComponent();
51 |
52 | if (!NetworkManager) return;
53 |
54 | NetworkManager.RegisterInstance(this);
55 | _serverManager.OnRemoteConnectionState += OnRemoteConnectionState;
56 | _serverManager.OnServerConnectionState += OnServerConnectionState;
57 | }
58 |
59 | private void OnDestroy()
60 | {
61 | if (!NetworkManager) return;
62 | NetworkManager.UnregisterInstance();
63 |
64 | _serverManager.OnRemoteConnectionState -= OnRemoteConnectionState;
65 | _serverManager.OnServerConnectionState -= OnServerConnectionState;
66 |
67 | Reset();
68 | }
69 |
70 | ///
71 | /// Start Session. Players cannot permanently disconnect after this call.
72 | ///
73 | public void StartSession()
74 | {
75 | if (IsSessionStarted)
76 | {
77 | return;
78 | }
79 | IsSessionStarted = true;
80 | OnSessionStarted?.Invoke();
81 | }
82 |
83 | ///
84 | /// Start Session. Players can permanently disconnect after this call.
85 | /// Temporarily disconnected players will be disconnected permanently.
86 | ///
87 | public void EndSession()
88 | {
89 | if (!IsSessionStarted)
90 | {
91 | return;
92 | }
93 | IsSessionStarted = false;
94 |
95 | foreach (SessionPlayer player in Players.Values.ToArray())
96 | {
97 | if (!player.IsConnected)
98 | {
99 | DisconnectPlayer(player, true);
100 | }
101 | }
102 |
103 | OnSessionEnded?.Invoke();
104 | }
105 |
106 | internal SessionPlayer GetPlayer(NetworkConnection connection)
107 | {
108 | return _playersByConnectionIds[connection.ClientId];
109 | }
110 |
111 | internal bool SetupPlayerConnection(SessionAuthenticator authenticator, string playerId, NetworkConnection connection)
112 | {
113 | if (string.IsNullOrEmpty(playerId))
114 | {
115 | Debug.LogWarning($"PlayerId for connection {connection.ClientId} is null. Authentication failed.");
116 | authenticator.InvokeAuthenticationResult(connection, false);
117 | return false;
118 | }
119 | if (!_players.TryGetValue(playerId, out SessionPlayer player))
120 | {
121 | player = new SessionPlayer(NetworkManager, _nextClientPlayerId, connection, playerId);
122 | _nextClientPlayerId++;
123 |
124 | AddPlayer(player);
125 | BroadcastPlayerConnectionChange(player, PlayerConnectionState.Connected, false);
126 | BroadcastPlayerConnected(player, false);
127 | authenticator.InvokeAuthenticationResult(connection, true);
128 | InvokeOnRemotePlayerConnectionState(player, PlayerConnectionState.Connected);
129 | return true;
130 | }
131 |
132 | if (!player.IsConnected)
133 | {
134 | ReconnectPlayer(player, connection);
135 | BroadcastPlayerConnectionChange(player, PlayerConnectionState.Reconnected, false);
136 | BroadcastPlayerConnected(player, true);
137 | authenticator.InvokeAuthenticationResult(connection, true);
138 | connection.OnLoadedStartScenes += OnConnectionLoadedStartScenes;
139 | InvokeOnRemotePlayerConnectionState(player, PlayerConnectionState.Reconnected);
140 | return true;
141 | }
142 |
143 | NetworkManager.LogWarning("Player with id " + playerId + " is already connected. Authentication failed.");
144 | authenticator.InvokeAuthenticationResult(connection, false);
145 | return false;
146 | }
147 |
148 | private void OnConnectionLoadedStartScenes(NetworkConnection connection, bool asServer)
149 | {
150 | if (!asServer) return;
151 |
152 | foreach (NetworkSessionObject sessionObject in connection.GetSessionPlayer().Objects)
153 | {
154 | sessionObject.GivingOwnership = true;
155 | sessionObject.GiveOwnership(connection);
156 | sessionObject.GivingOwnership = false;
157 | }
158 | }
159 |
160 | private void BroadcastPlayerConnected(SessionPlayer player, bool isReconnected)
161 | {
162 | var message = new PlayerConnectedBroadcast(player, isReconnected);
163 | _serverManager.Broadcast(player.NetworkConnection, message, false);
164 | }
165 |
166 | private void ReconnectPlayer(SessionPlayer player, NetworkConnection connection)
167 | {
168 | _playersByConnectionIds.Remove(player.ConnectionId);
169 | _playersByConnectionIds.Add(connection.ClientId, player);
170 | player.SetupReconnection(connection);
171 | }
172 |
173 | private void DisconnectPlayer(SessionPlayer player, bool permanently)
174 | {
175 | if (permanently)
176 | {
177 | InvokeOnRemotePlayerConnectionState(player, PlayerConnectionState.PermanentlyDisconnected);
178 | BroadcastPlayerConnectionChange(player, PlayerConnectionState.PermanentlyDisconnected);
179 |
180 | foreach (NetworkSessionObject networkPlayerObject in player.Objects.ToArray())
181 | {
182 | if (networkPlayerObject.IsSpawned)
183 | {
184 | networkPlayerObject.Despawn();
185 | }
186 | }
187 |
188 | RemovePlayer(player);
189 | player.Dispose();
190 | }
191 | else
192 | {
193 | InvokeOnRemotePlayerConnectionState(player, PlayerConnectionState.TemporarilyDisconnected);
194 | BroadcastPlayerConnectionChange(player, PlayerConnectionState.TemporarilyDisconnected);
195 | }
196 | }
197 |
198 | private void InvokeOnRemotePlayerConnectionState(SessionPlayer player, PlayerConnectionState state)
199 | {
200 | var args = new RemotePlayerConnectionStateArgs(state, player.ClientPlayerId);
201 | OnRemotePlayerConnectionState?.Invoke(player, args);
202 | }
203 |
204 | private void OnServerConnectionState(ServerConnectionStateArgs args)
205 | {
206 | switch (args.ConnectionState)
207 | {
208 | case LocalConnectionState.Stopped:
209 | Reset();
210 | break;
211 | case LocalConnectionState.Starting:
212 | break;
213 | case LocalConnectionState.Started:
214 | if (_serverManager.GetAuthenticator() is not SessionAuthenticator)
215 | {
216 | NetworkManager.LogError("ServerManager Authenticator is not a SessionAuthenticator.");
217 | }
218 | if (_initiallySessionStarted)
219 | {
220 | StartSession();
221 | }
222 | break;
223 | case LocalConnectionState.Stopping:
224 | break;
225 | default:
226 | throw new ArgumentOutOfRangeException();
227 | }
228 |
229 | if (args.ConnectionState == LocalConnectionState.Stopped)
230 | {
231 | }
232 | }
233 |
234 | private void OnRemoteConnectionState(NetworkConnection connection, RemoteConnectionStateArgs args)
235 | {
236 | if (connection.Authenticated && args.ConnectionState == RemoteConnectionState.Stopped)
237 | {
238 | connection.OnLoadedStartScenes -= OnConnectionLoadedStartScenes;
239 | DisconnectPlayer(GetPlayer(connection), !IsSessionStarted);
240 | }
241 | }
242 |
243 | private void AddPlayer(SessionPlayer player)
244 | {
245 | _players.Add(player.PlayerId, player);
246 | _playersByClientIds.Add(player.ClientPlayerId, player);
247 | _playersByConnectionIds[player.ConnectionId] = player;
248 | }
249 |
250 | private void RemovePlayer(SessionPlayer player)
251 | {
252 | _players.Remove(player.PlayerId);
253 | _playersByClientIds.Remove(player.ClientPlayerId);
254 | _playersByConnectionIds.Remove(player.ConnectionId);
255 | }
256 |
257 | private void Reset()
258 | {
259 | foreach (SessionPlayer player in _players.Values.ToArray())
260 | {
261 | RemovePlayer(player);
262 | player.Dispose();
263 | }
264 |
265 | _nextClientPlayerId = 1;
266 | }
267 |
268 | ///
269 | /// Sends a player connection state change to owner and other clients if applicable.
270 | ///
271 | private void BroadcastPlayerConnectionChange(SessionPlayer player, PlayerConnectionState state, bool requireAuthenticated = true)
272 | {
273 | NetworkConnection conn = player.NetworkConnection;
274 | bool connected = state == PlayerConnectionState.Connected || state == PlayerConnectionState.Reconnected;
275 | if (ShareIds)
276 | {
277 | var changeMsg = new PlayerConnectionChangeBroadcast(player.ClientPlayerId, conn.ClientId, state);
278 | foreach (NetworkConnection c in _serverManager.Clients.Values)
279 | {
280 | if (c.Authenticated)
281 | {
282 | _serverManager.Broadcast(c, changeMsg, requireAuthenticated);
283 | }
284 | }
285 |
286 | if (connected)
287 | {
288 | int[] clientPlayerIds = _playersByClientIds.Keys.ToArray();
289 | int[] connectionIds = _players.Values.Select(p => p.NetworkConnection.ClientId).ToArray();
290 | var allMsg = new ConnectedPlayersBroadcast(clientPlayerIds, connectionIds);
291 | conn.Broadcast(allMsg, requireAuthenticated);
292 | }
293 | }
294 | else if (connected)
295 | {
296 | /* Send broadcast only to the client which just disconnected.
297 | * Only send if connecting. If the client is disconnected there's no reason
298 | * to send them a disconnect msg. */
299 | var changeMsg = new PlayerConnectionChangeBroadcast(player.ClientPlayerId, conn.ClientId, state);
300 | _serverManager.Broadcast(conn, changeMsg, requireAuthenticated);
301 | }
302 | }
303 | }
304 | }
--------------------------------------------------------------------------------