2019-02-20 15:58:50 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using UnityEngine;
|
|
|
|
using UnityEngine.SceneManagement;
|
|
|
|
using UnityEngine.Serialization;
|
|
|
|
|
|
|
|
namespace Mirror
|
|
|
|
{
|
|
|
|
[AddComponentMenu("Network/NetworkLobbyManager")]
|
|
|
|
[HelpURL("https://vis2k.github.io/Mirror/Components/NetworkLobbyManager")]
|
|
|
|
public class NetworkLobbyManager : NetworkManager
|
|
|
|
{
|
2019-03-16 14:47:33 +00:00
|
|
|
public struct PendingPlayer
|
2019-02-20 15:58:50 +00:00
|
|
|
{
|
|
|
|
public NetworkConnection conn;
|
|
|
|
public GameObject lobbyPlayer;
|
|
|
|
}
|
|
|
|
|
|
|
|
// configuration
|
|
|
|
[Header("Lobby Settings")]
|
|
|
|
[FormerlySerializedAs("m_ShowLobbyGUI")] [SerializeField] internal bool showLobbyGUI = true;
|
|
|
|
[FormerlySerializedAs("m_MinPlayers")] [SerializeField] int minPlayers = 1;
|
|
|
|
[FormerlySerializedAs("m_LobbyPlayerPrefab")] [SerializeField] NetworkLobbyPlayer lobbyPlayerPrefab;
|
|
|
|
|
|
|
|
[Scene]
|
|
|
|
public string LobbyScene;
|
|
|
|
|
|
|
|
[Scene]
|
|
|
|
public string GameplayScene;
|
|
|
|
|
|
|
|
// runtime data
|
2019-03-16 14:47:33 +00:00
|
|
|
[FormerlySerializedAs("m_PendingPlayers")] public List<PendingPlayer> pendingPlayers = new List<PendingPlayer>();
|
|
|
|
public List<NetworkLobbyPlayer> lobbySlots = new List<NetworkLobbyPlayer>();
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-02-23 00:39:36 +00:00
|
|
|
public bool allPlayersReady;
|
2019-02-20 15:58:50 +00:00
|
|
|
|
|
|
|
public override void OnValidate()
|
|
|
|
{
|
|
|
|
// always >= 0
|
|
|
|
maxConnections = Mathf.Max(maxConnections, 0);
|
|
|
|
|
2019-03-06 08:08:34 +00:00
|
|
|
// always <= maxConnections
|
2019-02-20 15:58:50 +00:00
|
|
|
minPlayers = Mathf.Min(minPlayers, maxConnections);
|
|
|
|
|
|
|
|
// always >= 0
|
|
|
|
minPlayers = Mathf.Max(minPlayers, 0);
|
|
|
|
|
|
|
|
if (lobbyPlayerPrefab != null)
|
|
|
|
{
|
|
|
|
NetworkIdentity identity = lobbyPlayerPrefab.GetComponent<NetworkIdentity>();
|
|
|
|
if (identity == null)
|
|
|
|
{
|
|
|
|
lobbyPlayerPrefab = null;
|
|
|
|
Debug.LogError("LobbyPlayer prefab must have a NetworkIdentity component.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
base.OnValidate();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void PlayerLoadedScene(NetworkConnection conn)
|
|
|
|
{
|
|
|
|
if (LogFilter.Debug) Debug.Log("NetworkLobbyManager OnSceneLoadedMessage");
|
|
|
|
SceneLoadedForPlayer(conn, conn.playerController.gameObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
internal void ReadyStatusChanged()
|
|
|
|
{
|
|
|
|
int CurrentPlayers = 0;
|
|
|
|
int ReadyPlayers = 0;
|
2019-02-24 19:31:13 +00:00
|
|
|
|
2019-02-20 15:58:50 +00:00
|
|
|
foreach (NetworkLobbyPlayer item in lobbySlots)
|
|
|
|
{
|
|
|
|
if (item != null)
|
|
|
|
{
|
|
|
|
CurrentPlayers++;
|
|
|
|
if (item.ReadyToBegin)
|
|
|
|
ReadyPlayers++;
|
|
|
|
}
|
|
|
|
}
|
2019-02-24 19:31:13 +00:00
|
|
|
|
2019-02-20 15:58:50 +00:00
|
|
|
if (CurrentPlayers == ReadyPlayers)
|
|
|
|
CheckReadyToBegin();
|
|
|
|
else
|
|
|
|
allPlayersReady = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SceneLoadedForPlayer(NetworkConnection conn, GameObject lobbyPlayerGameObject)
|
|
|
|
{
|
|
|
|
// if not a lobby player.. dont replace it
|
|
|
|
if (lobbyPlayerGameObject.GetComponent<NetworkLobbyPlayer>() == null) return;
|
|
|
|
|
|
|
|
if (LogFilter.Debug) Debug.LogFormat("NetworkLobby SceneLoadedForPlayer scene: {0} {1}", SceneManager.GetActiveScene().name, conn);
|
|
|
|
|
|
|
|
if (SceneManager.GetActiveScene().name == LobbyScene)
|
|
|
|
{
|
|
|
|
// cant be ready in lobby, add to ready list
|
|
|
|
PendingPlayer pending;
|
|
|
|
pending.conn = conn;
|
|
|
|
pending.lobbyPlayer = lobbyPlayerGameObject;
|
|
|
|
pendingPlayers.Add(pending);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
GameObject gamePlayer = OnLobbyServerCreateGamePlayer(conn);
|
|
|
|
if (gamePlayer == null)
|
|
|
|
{
|
|
|
|
// get start position from base class
|
|
|
|
Transform startPos = GetStartPosition();
|
|
|
|
gamePlayer = startPos != null
|
|
|
|
? Instantiate(playerPrefab, startPos.position, startPos.rotation)
|
|
|
|
: Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
|
|
|
|
gamePlayer.name = playerPrefab.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!OnLobbyServerSceneLoadedForPlayer(lobbyPlayerGameObject, gamePlayer))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// replace lobby player with game player
|
|
|
|
NetworkServer.ReplacePlayerForConnection(conn, gamePlayer);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void CheckReadyToBegin()
|
|
|
|
{
|
|
|
|
if (SceneManager.GetActiveScene().name != LobbyScene) return;
|
|
|
|
|
|
|
|
if (minPlayers > 0 && NetworkServer.connections.Count(conn => conn.Value != null && conn.Value.playerController.gameObject.GetComponent<NetworkLobbyPlayer>().ReadyToBegin) < minPlayers)
|
|
|
|
{
|
|
|
|
allPlayersReady = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pendingPlayers.Clear();
|
|
|
|
allPlayersReady = true;
|
|
|
|
OnLobbyServerPlayersReady();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CallOnClientEnterLobby()
|
|
|
|
{
|
|
|
|
OnLobbyClientEnter();
|
|
|
|
foreach (NetworkLobbyPlayer player in lobbySlots)
|
|
|
|
player?.OnClientEnterLobby();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CallOnClientExitLobby()
|
|
|
|
{
|
|
|
|
OnLobbyClientExit();
|
|
|
|
foreach (NetworkLobbyPlayer player in lobbySlots)
|
|
|
|
player?.OnClientExitLobby();
|
|
|
|
}
|
|
|
|
|
|
|
|
#region server handlers
|
|
|
|
|
|
|
|
public override void OnServerConnect(NetworkConnection conn)
|
|
|
|
{
|
|
|
|
if (numPlayers >= maxConnections)
|
|
|
|
{
|
|
|
|
conn.Disconnect();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// cannot join game in progress
|
|
|
|
if (SceneManager.GetActiveScene().name != LobbyScene)
|
|
|
|
{
|
|
|
|
conn.Disconnect();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
base.OnServerConnect(conn);
|
|
|
|
OnLobbyServerConnect(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnServerDisconnect(NetworkConnection conn)
|
|
|
|
{
|
|
|
|
if (conn.playerController != null)
|
|
|
|
{
|
|
|
|
NetworkLobbyPlayer player = conn.playerController.GetComponent<NetworkLobbyPlayer>();
|
|
|
|
|
|
|
|
if (player != null)
|
|
|
|
lobbySlots.Remove(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
allPlayersReady = false;
|
|
|
|
|
|
|
|
foreach (NetworkLobbyPlayer player in lobbySlots)
|
|
|
|
{
|
|
|
|
if (player != null)
|
|
|
|
player.GetComponent<NetworkLobbyPlayer>().ReadyToBegin = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SceneManager.GetActiveScene().name == LobbyScene)
|
|
|
|
RecalculateLobbyPlayerIndices();
|
|
|
|
|
|
|
|
base.OnServerDisconnect(conn);
|
|
|
|
OnLobbyServerDisconnect(conn);
|
|
|
|
}
|
|
|
|
|
2019-03-13 14:49:11 +00:00
|
|
|
[System.Obsolete("Use OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) instead")]
|
|
|
|
public override void OnServerAddPlayer(NetworkConnection conn)
|
|
|
|
{
|
|
|
|
OnServerAddPlayer(conn, null);
|
|
|
|
}
|
|
|
|
|
2019-03-03 18:53:24 +00:00
|
|
|
public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage)
|
2019-02-20 15:58:50 +00:00
|
|
|
{
|
|
|
|
if (SceneManager.GetActiveScene().name != LobbyScene) return;
|
|
|
|
|
|
|
|
if (lobbySlots.Count == maxConnections) return;
|
|
|
|
|
|
|
|
allPlayersReady = false;
|
|
|
|
|
2019-03-01 13:29:38 +00:00
|
|
|
if (LogFilter.Debug) Debug.LogFormat("NetworkLobbyManager.OnServerAddPlayer playerPrefab:{0}", lobbyPlayerPrefab.name);
|
2019-02-20 15:58:50 +00:00
|
|
|
|
|
|
|
GameObject newLobbyGameObject = OnLobbyServerCreateLobbyPlayer(conn);
|
|
|
|
if (newLobbyGameObject == null)
|
|
|
|
newLobbyGameObject = (GameObject)Instantiate(lobbyPlayerPrefab.gameObject, Vector3.zero, Quaternion.identity);
|
|
|
|
|
|
|
|
NetworkLobbyPlayer newLobbyPlayer = newLobbyGameObject.GetComponent<NetworkLobbyPlayer>();
|
|
|
|
|
|
|
|
lobbySlots.Add(newLobbyPlayer);
|
|
|
|
|
|
|
|
RecalculateLobbyPlayerIndices();
|
|
|
|
|
|
|
|
NetworkServer.AddPlayerForConnection(conn, newLobbyGameObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RecalculateLobbyPlayerIndices()
|
|
|
|
{
|
|
|
|
if (lobbySlots.Count > 0)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < lobbySlots.Count; i++)
|
|
|
|
{
|
|
|
|
lobbySlots[i].Index = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void ServerChangeScene(string sceneName)
|
|
|
|
{
|
|
|
|
if (sceneName == LobbyScene)
|
|
|
|
{
|
|
|
|
foreach (NetworkLobbyPlayer lobbyPlayer in lobbySlots)
|
|
|
|
{
|
|
|
|
if (lobbyPlayer == null) continue;
|
|
|
|
|
|
|
|
// find the game-player object for this connection, and destroy it
|
|
|
|
NetworkIdentity identity = lobbyPlayer.GetComponent<NetworkIdentity>();
|
|
|
|
|
|
|
|
NetworkIdentity playerController = identity.connectionToClient.playerController;
|
|
|
|
NetworkServer.Destroy(playerController.gameObject);
|
|
|
|
|
|
|
|
if (NetworkServer.active)
|
|
|
|
{
|
|
|
|
// re-add the lobby object
|
|
|
|
lobbyPlayer.GetComponent<NetworkLobbyPlayer>().ReadyToBegin = false;
|
|
|
|
NetworkServer.ReplacePlayerForConnection(identity.connectionToClient, lobbyPlayer.gameObject);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (dontDestroyOnLoad)
|
|
|
|
{
|
|
|
|
foreach (NetworkLobbyPlayer lobbyPlayer in lobbySlots)
|
|
|
|
{
|
|
|
|
if (lobbyPlayer != null)
|
|
|
|
{
|
|
|
|
lobbyPlayer.transform.SetParent(null);
|
|
|
|
DontDestroyOnLoad(lobbyPlayer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
base.ServerChangeScene(sceneName);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnServerSceneChanged(string sceneName)
|
|
|
|
{
|
|
|
|
if (sceneName != LobbyScene)
|
|
|
|
{
|
|
|
|
// call SceneLoadedForPlayer on any players that become ready while we were loading the scene.
|
|
|
|
foreach (PendingPlayer pending in pendingPlayers)
|
|
|
|
{
|
|
|
|
SceneLoadedForPlayer(pending.conn, pending.lobbyPlayer);
|
|
|
|
}
|
|
|
|
|
|
|
|
pendingPlayers.Clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
OnLobbyServerSceneChanged(sceneName);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnStartServer()
|
|
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(LobbyScene))
|
|
|
|
{
|
|
|
|
Debug.LogError("NetworkLobbyManager LobbyScene is empty. Set the LobbyScene in the inspector for the NetworkLobbyMangaer");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(GameplayScene))
|
|
|
|
{
|
|
|
|
Debug.LogError("NetworkLobbyManager PlayScene is empty. Set the PlayScene in the inspector for the NetworkLobbyMangaer");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
OnLobbyStartServer();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnStartHost()
|
|
|
|
{
|
|
|
|
OnLobbyStartHost();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnStopServer()
|
|
|
|
{
|
|
|
|
lobbySlots.Clear();
|
|
|
|
base.OnStopServer();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnStopHost()
|
|
|
|
{
|
|
|
|
OnLobbyStopHost();
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region client handlers
|
|
|
|
|
|
|
|
public override void OnStartClient(NetworkClient lobbyClient)
|
|
|
|
{
|
|
|
|
if (lobbyPlayerPrefab == null || lobbyPlayerPrefab.gameObject == null)
|
|
|
|
Debug.LogError("NetworkLobbyManager no LobbyPlayer prefab is registered. Please add a LobbyPlayer prefab.");
|
|
|
|
else
|
|
|
|
ClientScene.RegisterPrefab(lobbyPlayerPrefab.gameObject);
|
|
|
|
|
|
|
|
if (playerPrefab == null)
|
|
|
|
Debug.LogError("NetworkLobbyManager no GamePlayer prefab is registered. Please add a GamePlayer prefab.");
|
|
|
|
else
|
|
|
|
ClientScene.RegisterPrefab(playerPrefab);
|
|
|
|
|
|
|
|
OnLobbyStartClient(lobbyClient);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnClientConnect(NetworkConnection conn)
|
|
|
|
{
|
|
|
|
OnLobbyClientConnect(conn);
|
|
|
|
CallOnClientEnterLobby();
|
|
|
|
base.OnClientConnect(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnClientDisconnect(NetworkConnection conn)
|
|
|
|
{
|
|
|
|
OnLobbyClientDisconnect(conn);
|
|
|
|
base.OnClientDisconnect(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnStopClient()
|
|
|
|
{
|
|
|
|
OnLobbyStopClient();
|
|
|
|
CallOnClientExitLobby();
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(offlineScene))
|
|
|
|
{
|
|
|
|
// Move the LobbyManager from the virtual DontDestroyOnLoad scene to the Game scene.
|
|
|
|
// This let's it be destroyed when client changes to the Offline scene.
|
|
|
|
SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnClientChangeScene(string newSceneName)
|
|
|
|
{
|
|
|
|
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene from {0} to {1}", SceneManager.GetActiveScene().name, newSceneName);
|
|
|
|
|
|
|
|
if (SceneManager.GetActiveScene().name == LobbyScene && newSceneName == GameplayScene && dontDestroyOnLoad && IsClientConnected() && client != null)
|
|
|
|
{
|
|
|
|
GameObject lobbyPlayer = client?.connection?.playerController?.gameObject;
|
|
|
|
if (lobbyPlayer != null)
|
|
|
|
{
|
|
|
|
lobbyPlayer.transform.SetParent(null);
|
|
|
|
DontDestroyOnLoad(lobbyPlayer);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
Debug.LogWarningFormat("OnClientChangeScene: lobbyPlayer is null");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene {0} {1} {2}", dontDestroyOnLoad, IsClientConnected(), client != null);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void OnClientSceneChanged(NetworkConnection conn)
|
|
|
|
{
|
|
|
|
if (SceneManager.GetActiveScene().name == LobbyScene)
|
|
|
|
{
|
|
|
|
if (client.isConnected)
|
|
|
|
CallOnClientEnterLobby();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
CallOnClientExitLobby();
|
|
|
|
|
|
|
|
base.OnClientSceneChanged(conn);
|
|
|
|
OnLobbyClientSceneChanged(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region lobby server virtuals
|
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyStartHost() {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyStopHost() {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyStartServer() {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyServerConnect(NetworkConnection conn) {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyServerDisconnect(NetworkConnection conn) {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyServerSceneChanged(string sceneName) {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
|
|
|
public virtual GameObject OnLobbyServerCreateLobbyPlayer(NetworkConnection conn)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public virtual GameObject OnLobbyServerCreateGamePlayer(NetworkConnection conn)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// for users to apply settings from their lobby player object to their in-game player object
|
|
|
|
public virtual bool OnLobbyServerSceneLoadedForPlayer(GameObject lobbyPlayer, GameObject gamePlayer)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public virtual void OnLobbyServerPlayersReady()
|
|
|
|
{
|
|
|
|
// all players are readyToBegin, start the game
|
|
|
|
ServerChangeScene(GameplayScene);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region lobby client virtuals
|
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyClientEnter() {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyClientExit() {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyClientConnect(NetworkConnection conn) {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyClientDisconnect(NetworkConnection conn) {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyStartClient(NetworkClient lobbyClient) {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyStopClient() {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyClientSceneChanged(NetworkConnection conn) {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
|
|
|
// for users to handle adding a player failed on the server
|
2019-03-04 07:48:28 +00:00
|
|
|
public virtual void OnLobbyClientAddPlayerFailed() {}
|
2019-02-20 15:58:50 +00:00
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region optional UI
|
|
|
|
|
|
|
|
public virtual void OnGUI()
|
|
|
|
{
|
|
|
|
if (!showLobbyGUI)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (SceneManager.GetActiveScene().name != LobbyScene)
|
|
|
|
return;
|
|
|
|
|
|
|
|
GUI.Box(new Rect(10f, 180f, 520f, 150f), "PLAYERS");
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
}
|
2019-02-25 12:27:27 +00:00
|
|
|
}
|