// --------------------------------------------------------------------------------------------------------------------
//
// 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);
}
}
}