// -------------------------------------------------------------------------------------------------------------------- // // Part of: Photon Unity Utilities, // // // Implements teams in a room/game with help of player properties. // // // Teams are defined by name and code. Change this to get more / different teams. // There are no rules when / if you can join a team. You could add this in JoinTeam or something. // // developer@exitgames.com // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections.Generic; using UnityEngine; using Photon.Realtime; using Hashtable = ExitGames.Client.Photon.Hashtable; namespace Photon.Pun.UtilityScripts { [Serializable] public class PhotonTeam { public string Name; public byte Code; public override string ToString() { return string.Format("{0} [{1}]", this.Name, this.Code); } } /// /// Implements teams in a room/game with help of player properties. Access them by Player.GetTeam extension. /// /// /// Teams are defined by enum Team. Change this to get more / different teams. /// There are no rules when / if you can join a team. You could add this in JoinTeam or something. /// [DisallowMultipleComponent] public class PhotonTeamsManager : MonoBehaviour, IMatchmakingCallbacks, IInRoomCallbacks { #if UNITY_EDITOR #pragma warning disable 0414 [SerializeField] private bool listFoldIsOpen = true; #pragma warning restore 0414 #endif [SerializeField] private List teamsList = new List { new PhotonTeam { Name = "Blue", Code = 1 }, new PhotonTeam { Name = "Red", Code = 2 } }; private Dictionary teamsByCode; private Dictionary teamsByName; /// The main list of teams with their player-lists. Automatically kept up to date. private Dictionary> playersPerTeam; /// Defines the player custom property name to use for team affinity of "this" player. public const string TeamPlayerProp = "_pt"; public static event Action PlayerJoinedTeam; public static event Action PlayerLeftTeam; private static PhotonTeamsManager instance; public static PhotonTeamsManager Instance { get { if (instance == null) { instance = FindObjectOfType(); if (instance == null) { GameObject obj = new GameObject(); obj.name = "PhotonTeamsManager"; instance = obj.AddComponent(); } instance.Init(); } return instance; } } #region MonoBehaviour private void Awake() { if (instance == null || ReferenceEquals(this, instance)) { this.Init(); instance = this; } else { Destroy(this); } } private void OnEnable() { PhotonNetwork.AddCallbackTarget(this); } private void OnDisable() { PhotonNetwork.RemoveCallbackTarget(this); this.ClearTeams(); } private void Init() { teamsByCode = new Dictionary(teamsList.Count); teamsByName = new Dictionary(teamsList.Count); playersPerTeam = new Dictionary>(teamsList.Count); for (int i = 0; i < teamsList.Count; i++) { teamsByCode[teamsList[i].Code] = teamsList[i]; teamsByName[teamsList[i].Name] = teamsList[i]; playersPerTeam[teamsList[i].Code] = new HashSet(); } } #endregion #region IMatchmakingCallbacks void IMatchmakingCallbacks.OnJoinedRoom() { this.UpdateTeams(); } void IMatchmakingCallbacks.OnLeftRoom() { this.ClearTeams(); } void IInRoomCallbacks.OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { object temp; if (changedProps.TryGetValue(TeamPlayerProp, out temp)) { if (temp == null) { foreach (byte code in playersPerTeam.Keys) { if (playersPerTeam[code].Remove(targetPlayer)) { if (PlayerLeftTeam != null) { PlayerLeftTeam(targetPlayer, teamsByCode[code]); } break; } } } else if (temp is byte) { byte teamCode = (byte) temp; // check if player switched teams, remove from previous team foreach (byte code in playersPerTeam.Keys) { if (code == teamCode) { continue; } if (playersPerTeam[code].Remove(targetPlayer)) { if (PlayerLeftTeam != null) { PlayerLeftTeam(targetPlayer, teamsByCode[code]); } break; } } PhotonTeam team = teamsByCode[teamCode]; if (!playersPerTeam[teamCode].Add(targetPlayer)) { Debug.LogWarningFormat("Unexpected situation while setting team {0} for player {1}, updating teams for all", team, targetPlayer); this.UpdateTeams(); } if (PlayerJoinedTeam != null) { PlayerJoinedTeam(targetPlayer, team); } } else { Debug.LogErrorFormat("Unexpected: custom property key {0} should have of type byte, instead we got {1} of type {2}. Player: {3}", TeamPlayerProp, temp, temp.GetType(), targetPlayer); } } } void IInRoomCallbacks.OnPlayerLeftRoom(Player otherPlayer) { if (otherPlayer.IsInactive) { return; } PhotonTeam team = otherPlayer.GetPhotonTeam(); if (team != null && !playersPerTeam[team.Code].Remove(otherPlayer)) { Debug.LogWarningFormat("Unexpected situation while removing player {0} who left from team {1}, updating teams for all", otherPlayer, team); // revert to 'brute force' in case of unexpected situation this.UpdateTeams(); } } void IInRoomCallbacks.OnPlayerEnteredRoom(Player newPlayer) { PhotonTeam team = newPlayer.GetPhotonTeam(); if (team == null) { return; } if (playersPerTeam[team.Code].Contains(newPlayer)) { // player rejoined w/ same team return; } // check if player rejoined w/ different team, remove from previous team foreach (var key in teamsByCode.Keys) { if (playersPerTeam[key].Remove(newPlayer)) { break; } } if (!playersPerTeam[team.Code].Add(newPlayer)) { Debug.LogWarningFormat("Unexpected situation while adding player {0} who joined to team {1}, updating teams for all", newPlayer, team); // revert to 'brute force' in case of unexpected situation this.UpdateTeams(); } } #endregion #region Private methods private void UpdateTeams() { this.ClearTeams(); for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++) { Player player = PhotonNetwork.PlayerList[i]; PhotonTeam playerTeam = player.GetPhotonTeam(); if (playerTeam != null) { playersPerTeam[playerTeam.Code].Add(player); } } } private void ClearTeams() { foreach (var key in playersPerTeam.Keys) { playersPerTeam[key].Clear(); } } #endregion #region Public API /// /// Find a PhotonTeam using a team code. /// /// The team code. /// The team to be assigned if found. /// If successful or not. public bool TryGetTeamByCode(byte code, out PhotonTeam team) { return teamsByCode.TryGetValue(code, out team); } /// /// Find a PhotonTeam using a team name. /// /// The team name. /// The team to be assigned if found. /// If successful or not. public bool TryGetTeamByName(string teamName, out PhotonTeam team) { return teamsByName.TryGetValue(teamName, out team); } /// /// Gets all teams available. /// /// Returns all teams available. public PhotonTeam[] GetAvailableTeams() { if (teamsList != null) { return teamsList.ToArray(); } return null; } /// /// Gets all players joined to a team using a team code. /// /// The code of the team. /// The array of players to be filled. /// If successful or not. public bool TryGetTeamMembers(byte code, out Player[] members) { members = null; HashSet players; if (this.playersPerTeam.TryGetValue(code, out players)) { members = new Player[players.Count]; int i = 0; foreach (var player in players) { members[i] = player; i++; } return true; } return false; } /// /// Gets all players joined to a team using a team name. /// /// The name of the team. /// The array of players to be filled. /// If successful or not. public bool TryGetTeamMembers(string teamName, out Player[] members) { members = null; PhotonTeam team; if (this.TryGetTeamByName(teamName, out team)) { return this.TryGetTeamMembers(team.Code, out members); } return false; } /// /// Gets all players joined to a team. /// /// The team which will be used to find players. /// The array of players to be filled. /// If successful or not. public bool TryGetTeamMembers(PhotonTeam team, out Player[] members) { members = null; if (team != null) { return this.TryGetTeamMembers(team.Code, out members); } return false; } /// /// Gets all team mates of a player. /// /// The player whose team mates will be searched. /// The array of players to be filled. /// If successful or not. public bool TryGetTeamMatesOfPlayer(Player player, out Player[] teamMates) { teamMates = null; if (player == null) { return false; } PhotonTeam team = player.GetPhotonTeam(); if (team == null) { return false; } HashSet players; if (this.playersPerTeam.TryGetValue(team.Code, out players)) { if (!players.Contains(player)) { Debug.LogWarningFormat("Unexpected situation while getting team mates of player {0} who is joined to team {1}, updating teams for all", player, team); // revert to 'brute force' in case of unexpected situation this.UpdateTeams(); } teamMates = new Player[players.Count - 1]; int i = 0; foreach (var p in players) { if (p.Equals(player)) { continue; } teamMates[i] = p; i++; } return true; } return false; } /// /// Gets the number of players in a team by team code. /// /// Unique code of the team /// Number of players joined to the team. public int GetTeamMembersCount(byte code) { PhotonTeam team; if (this.TryGetTeamByCode(code, out team)) { return this.GetTeamMembersCount(team); } return 0; } /// /// Gets the number of players in a team by team name. /// /// Unique name of the team /// Number of players joined to the team. public int GetTeamMembersCount(string name) { PhotonTeam team; if (this.TryGetTeamByName(name, out team)) { return this.GetTeamMembersCount(team); } return 0; } /// /// Gets the number of players in a team. /// /// The team you want to know the size of /// Number of players joined to the team. public int GetTeamMembersCount(PhotonTeam team) { HashSet players; if (team != null && this.playersPerTeam.TryGetValue(team.Code, out players) && players != null) { return players.Count; } return 0; } #endregion #region Unused methods void IMatchmakingCallbacks.OnFriendListUpdate(List friendList) { } void IMatchmakingCallbacks.OnCreatedRoom() { } void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message) { } void IMatchmakingCallbacks.OnJoinRoomFailed(short returnCode, string message) { } void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message) { } void IInRoomCallbacks.OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { } void IInRoomCallbacks.OnMasterClientSwitched(Player newMasterClient) { } #endregion } /// Extension methods for the Player class that make use of PhotonTeamsManager. public static class PhotonTeamExtensions { /// Gets the team the player is currently joined to. Null if none. /// The team the player is currently joined to. Null if none. public static PhotonTeam GetPhotonTeam(this Player player) { object teamId; PhotonTeam team; if (player.CustomProperties.TryGetValue(PhotonTeamsManager.TeamPlayerProp, out teamId) && PhotonTeamsManager.Instance.TryGetTeamByCode((byte)teamId, out team)) { return team; } return null; } /// /// Join a team. /// /// The player who will join a team. /// The team to be joined. /// public static bool JoinTeam(this Player player, PhotonTeam team) { if (team == null) { Debug.LogWarning("JoinTeam failed: PhotonTeam provided is null"); return false; } PhotonTeam currentTeam = player.GetPhotonTeam(); if (currentTeam != null) { Debug.LogWarningFormat("JoinTeam failed: player ({0}) is already joined to a team ({1}), call SwitchTeam instead", player, team); return false; } return player.SetCustomProperties(new Hashtable { { PhotonTeamsManager.TeamPlayerProp, team.Code } }); } /// /// Join a team using team code. /// /// The player who will join the team. /// The code fo the team to be joined. /// public static bool JoinTeam(this Player player, byte teamCode) { PhotonTeam team; return PhotonTeamsManager.Instance.TryGetTeamByCode(teamCode, out team) && player.JoinTeam(team); } /// /// Join a team using team name. /// /// The player who will join the team. /// The name of the team to be joined. /// public static bool JoinTeam(this Player player, string teamName) { PhotonTeam team; return PhotonTeamsManager.Instance.TryGetTeamByName(teamName, out team) && player.JoinTeam(team); } /// Switch that player's team to the one you assign. /// Internally checks if this player is in that team already or not. Only team switches are actually sent. /// /// public static bool SwitchTeam(this Player player, PhotonTeam team) { if (team == null) { Debug.LogWarning("SwitchTeam failed: PhotonTeam provided is null"); return false; } PhotonTeam currentTeam = player.GetPhotonTeam(); if (currentTeam == null) { Debug.LogWarningFormat("SwitchTeam failed: player ({0}) was not joined to any team, call JoinTeam instead", player); return false; } if (currentTeam.Code == team.Code) { Debug.LogWarningFormat("SwitchTeam failed: player ({0}) is already joined to the same team {1}", player, team); return false; } return player.SetCustomProperties(new Hashtable { { PhotonTeamsManager.TeamPlayerProp, team.Code } }, new Hashtable { { PhotonTeamsManager.TeamPlayerProp, currentTeam.Code }}); } /// Switch the player's team using a team code. /// Internally checks if this player is in that team already or not. /// The player that will switch teams. /// The code of the team to switch to. /// If the team switch request is queued to be sent to the server or done in case offline or not joined to a room yet. public static bool SwitchTeam(this Player player, byte teamCode) { PhotonTeam team; return PhotonTeamsManager.Instance.TryGetTeamByCode(teamCode, out team) && player.SwitchTeam(team); } /// Switch the player's team using a team name. /// Internally checks if this player is in that team already or not. /// The player that will switch teams. /// The name of the team to switch to. /// If the team switch request is queued to be sent to the server or done in case offline or not joined to a room yet. public static bool SwitchTeam(this Player player, string teamName) { PhotonTeam team; return PhotonTeamsManager.Instance.TryGetTeamByName(teamName, out team) && player.SwitchTeam(team); } /// /// Leave the current team if any. /// /// /// If the leaving team request is queued to be sent to the server or done in case offline or not joined to a room yet. public static bool LeaveCurrentTeam(this Player player) { PhotonTeam currentTeam = player.GetPhotonTeam(); if (currentTeam == null) { Debug.LogWarningFormat("LeaveCurrentTeam failed: player ({0}) was not joined to any team", player); return false; } return player.SetCustomProperties(new Hashtable {{PhotonTeamsManager.TeamPlayerProp, null}}, new Hashtable {{PhotonTeamsManager.TeamPlayerProp, currentTeam.Code}}); } /// /// Try to get the team mates. /// /// The player to get the team mates of. /// The team mates array to fill. /// If successful or not. public static bool TryGetTeamMates(this Player player, out Player[] teamMates) { return PhotonTeamsManager.Instance.TryGetTeamMatesOfPlayer(player, out teamMates); } } }