452 lines
20 KiB
C#
452 lines
20 KiB
C#
|
// ----------------------------------------------------------------------------
|
||
|
// <copyright file="Player.cs" company="Exit Games GmbH">
|
||
|
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||
|
// </copyright>
|
||
|
// <summary>
|
||
|
// Per client in a room, a Player is created. This client's Player is also
|
||
|
// known as PhotonClient.LocalPlayer and the only one you might change
|
||
|
// properties for.
|
||
|
// </summary>
|
||
|
// <author>developer@photonengine.com</author>
|
||
|
// ----------------------------------------------------------------------------
|
||
|
|
||
|
#if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
|
||
|
#define SUPPORTED_UNITY
|
||
|
#endif
|
||
|
|
||
|
|
||
|
namespace Photon.Realtime
|
||
|
{
|
||
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using ExitGames.Client.Photon;
|
||
|
|
||
|
#if SUPPORTED_UNITY
|
||
|
using UnityEngine;
|
||
|
#endif
|
||
|
#if SUPPORTED_UNITY || NETFX_CORE
|
||
|
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||
|
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Summarizes a "player" within a room, identified (in that room) by ID (or "actorNumber").
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Each player has a actorNumber, valid for that room. It's -1 until assigned by server (and client logic).
|
||
|
/// </remarks>
|
||
|
public class Player
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Used internally to identify the masterclient of a room.
|
||
|
/// </summary>
|
||
|
protected internal Room RoomReference { get; set; }
|
||
|
|
||
|
|
||
|
/// <summary>Backing field for property.</summary>
|
||
|
private int actorNumber = -1;
|
||
|
|
||
|
/// <summary>Identifier of this player in current room. Also known as: actorNumber or actorNumber. It's -1 outside of rooms.</summary>
|
||
|
/// <remarks>The ID is assigned per room and only valid in that context. It will change even on leave and re-join. IDs are never re-used per room.</remarks>
|
||
|
public int ActorNumber
|
||
|
{
|
||
|
get { return this.actorNumber; }
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>Only one player is controlled by each client. Others are not local.</summary>
|
||
|
public readonly bool IsLocal;
|
||
|
|
||
|
|
||
|
public bool HasRejoined
|
||
|
{
|
||
|
get; internal set;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>Background field for nickName.</summary>
|
||
|
private string nickName = string.Empty;
|
||
|
|
||
|
/// <summary>Non-unique nickname of this player. Synced automatically in a room.</summary>
|
||
|
/// <remarks>
|
||
|
/// A player might change his own playername in a room (it's only a property).
|
||
|
/// Setting this value updates the server and other players (using an operation).
|
||
|
/// </remarks>
|
||
|
public string NickName
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.nickName;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
if (!string.IsNullOrEmpty(this.nickName) && this.nickName.Equals(value))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.nickName = value;
|
||
|
|
||
|
// update a room, if we changed our nickName locally
|
||
|
if (this.IsLocal)
|
||
|
{
|
||
|
this.SetPlayerNameProperty();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>UserId of the player, available when the room got created with RoomOptions.PublishUserId = true.</summary>
|
||
|
/// <remarks>Useful for <see cref="LoadBalancingClient.OpFindFriends"/> and blocking slots in a room for expected players (e.g. in <see cref="LoadBalancingClient.OpCreateRoom"/>).</remarks>
|
||
|
public string UserId { get; internal set; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// True if this player is the Master Client of the current room.
|
||
|
/// </summary>
|
||
|
public bool IsMasterClient
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.RoomReference == null)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return this.ActorNumber == this.RoomReference.MasterClientId;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>If this player is active in the room (and getting events which are currently being sent).</summary>
|
||
|
/// <remarks>
|
||
|
/// Inactive players keep their spot in a room but otherwise behave as if offline (no matter what their actual connection status is).
|
||
|
/// The room needs a PlayerTTL != 0. If a player is inactive for longer than PlayerTTL, the server will remove this player from the room.
|
||
|
/// For a client "rejoining" a room, is the same as joining it: It gets properties, cached events and then the live events.
|
||
|
/// </remarks>
|
||
|
public bool IsInactive { get; protected internal set; }
|
||
|
|
||
|
/// <summary>Read-only cache for custom properties of player. Set via Player.SetCustomProperties.</summary>
|
||
|
/// <remarks>
|
||
|
/// Don't modify the content of this Hashtable. Use SetCustomProperties and the
|
||
|
/// properties of this class to modify values. When you use those, the client will
|
||
|
/// sync values with the server.
|
||
|
/// </remarks>
|
||
|
/// <see cref="SetCustomProperties"/>
|
||
|
public Hashtable CustomProperties { get; set; }
|
||
|
|
||
|
/// <summary>Can be used to store a reference that's useful to know "by player".</summary>
|
||
|
/// <remarks>Example: Set a player's character as Tag by assigning the GameObject on Instantiate.</remarks>
|
||
|
public object TagObject;
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a player instance.
|
||
|
/// To extend and replace this Player, override LoadBalancingPeer.CreatePlayer().
|
||
|
/// </summary>
|
||
|
/// <param name="nickName">NickName of the player (a "well known property").</param>
|
||
|
/// <param name="actorNumber">ID or ActorNumber of this player in the current room (a shortcut to identify each player in room)</param>
|
||
|
/// <param name="isLocal">If this is the local peer's player (or a remote one).</param>
|
||
|
protected internal Player(string nickName, int actorNumber, bool isLocal) : this(nickName, actorNumber, isLocal, null)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a player instance.
|
||
|
/// To extend and replace this Player, override LoadBalancingPeer.CreatePlayer().
|
||
|
/// </summary>
|
||
|
/// <param name="nickName">NickName of the player (a "well known property").</param>
|
||
|
/// <param name="actorNumber">ID or ActorNumber of this player in the current room (a shortcut to identify each player in room)</param>
|
||
|
/// <param name="isLocal">If this is the local peer's player (or a remote one).</param>
|
||
|
/// <param name="playerProperties">A Hashtable of custom properties to be synced. Must use String-typed keys and serializable datatypes as values.</param>
|
||
|
protected internal Player(string nickName, int actorNumber, bool isLocal, Hashtable playerProperties)
|
||
|
{
|
||
|
this.IsLocal = isLocal;
|
||
|
this.actorNumber = actorNumber;
|
||
|
this.NickName = nickName;
|
||
|
|
||
|
this.CustomProperties = new Hashtable();
|
||
|
this.InternalCacheProperties(playerProperties);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get a Player by ActorNumber (Player.ID).
|
||
|
/// </summary>
|
||
|
/// <param name="id">ActorNumber of the a player in this room.</param>
|
||
|
/// <returns>Player or null.</returns>
|
||
|
public Player Get(int id)
|
||
|
{
|
||
|
if (this.RoomReference == null)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return this.RoomReference.GetPlayer(id);
|
||
|
}
|
||
|
|
||
|
/// <summary>Gets this Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around.</summary>
|
||
|
/// <returns>Player or null.</returns>
|
||
|
public Player GetNext()
|
||
|
{
|
||
|
return GetNextFor(this.ActorNumber);
|
||
|
}
|
||
|
|
||
|
/// <summary>Gets a Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around.</summary>
|
||
|
/// <remarks>Useful when you pass something to the next player. For example: passing the turn to the next player.</remarks>
|
||
|
/// <param name="currentPlayer">The Player for which the next is being needed.</param>
|
||
|
/// <returns>Player or null.</returns>
|
||
|
public Player GetNextFor(Player currentPlayer)
|
||
|
{
|
||
|
if (currentPlayer == null)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
return GetNextFor(currentPlayer.ActorNumber);
|
||
|
}
|
||
|
|
||
|
/// <summary>Gets a Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around.</summary>
|
||
|
/// <remarks>Useful when you pass something to the next player. For example: passing the turn to the next player.</remarks>
|
||
|
/// <param name="currentPlayerId">The ActorNumber (Player.ID) for which the next is being needed.</param>
|
||
|
/// <returns>Player or null.</returns>
|
||
|
public Player GetNextFor(int currentPlayerId)
|
||
|
{
|
||
|
if (this.RoomReference == null || this.RoomReference.Players == null || this.RoomReference.Players.Count < 2)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
Dictionary<int, Player> players = this.RoomReference.Players;
|
||
|
int nextHigherId = int.MaxValue; // we look for the next higher ID
|
||
|
int lowestId = currentPlayerId; // if we are the player with the highest ID, there is no higher and we return to the lowest player's id
|
||
|
|
||
|
foreach (int playerid in players.Keys)
|
||
|
{
|
||
|
if (playerid < lowestId)
|
||
|
{
|
||
|
lowestId = playerid; // less than any other ID (which must be at least less than this player's id).
|
||
|
}
|
||
|
else if (playerid > currentPlayerId && playerid < nextHigherId)
|
||
|
{
|
||
|
nextHigherId = playerid; // more than our ID and less than those found so far.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//UnityEngine.Debug.LogWarning("Debug. " + currentPlayerId + " lower: " + lowestId + " higher: " + nextHigherId + " ");
|
||
|
//UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(currentPlayerId));
|
||
|
//UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(lowestId));
|
||
|
//if (nextHigherId != int.MaxValue) UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(nextHigherId));
|
||
|
return (nextHigherId != int.MaxValue) ? players[nextHigherId] : players[lowestId];
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>Caches properties for new Players or when updates of remote players are received. Use SetCustomProperties() for a synced update.</summary>
|
||
|
/// <remarks>
|
||
|
/// This only updates the CustomProperties and doesn't send them to the server.
|
||
|
/// Mostly used when creating new remote players, where the server sends their properties.
|
||
|
/// </remarks>
|
||
|
protected internal virtual void InternalCacheProperties(Hashtable properties)
|
||
|
{
|
||
|
if (properties == null || properties.Count == 0 || this.CustomProperties.Equals(properties))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (properties.ContainsKey(ActorProperties.PlayerName))
|
||
|
{
|
||
|
string nameInServersProperties = (string)properties[ActorProperties.PlayerName];
|
||
|
if (nameInServersProperties != null)
|
||
|
{
|
||
|
if (this.IsLocal)
|
||
|
{
|
||
|
// the local playername is different than in the properties coming from the server
|
||
|
// so the local nickName was changed and the server is outdated -> update server
|
||
|
// update property instead of using the outdated nickName coming from server
|
||
|
if (!nameInServersProperties.Equals(this.nickName))
|
||
|
{
|
||
|
this.SetPlayerNameProperty();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.NickName = nameInServersProperties;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (properties.ContainsKey(ActorProperties.UserId))
|
||
|
{
|
||
|
this.UserId = (string)properties[ActorProperties.UserId];
|
||
|
}
|
||
|
if (properties.ContainsKey(ActorProperties.IsInactive))
|
||
|
{
|
||
|
this.IsInactive = (bool)properties[ActorProperties.IsInactive]; //TURNBASED new well-known propery for players
|
||
|
}
|
||
|
|
||
|
this.CustomProperties.MergeStringKeys(properties);
|
||
|
this.CustomProperties.StripKeysWithNullValues();
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Brief summary string of the Player: ActorNumber and NickName
|
||
|
/// </summary>
|
||
|
public override string ToString()
|
||
|
{
|
||
|
return string.Format("#{0:00} '{1}'",this.ActorNumber, this.NickName);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// String summary of the Player: player.ID, name and all custom properties of this user.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Use with care and not every frame!
|
||
|
/// Converts the customProperties to a String on every single call.
|
||
|
/// </remarks>
|
||
|
public string ToStringFull()
|
||
|
{
|
||
|
return string.Format("#{0:00} '{1}'{2} {3}", this.ActorNumber, this.NickName, this.IsInactive ? " (inactive)" : "", this.CustomProperties.ToStringFull());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// If players are equal (by GetHasCode, which returns this.ID).
|
||
|
/// </summary>
|
||
|
public override bool Equals(object p)
|
||
|
{
|
||
|
Player pp = p as Player;
|
||
|
return (pp != null && this.GetHashCode() == pp.GetHashCode());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Accompanies Equals, using the ID (actorNumber) as HashCode to return.
|
||
|
/// </summary>
|
||
|
public override int GetHashCode()
|
||
|
{
|
||
|
return this.ActorNumber;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Used internally, to update this client's playerID when assigned (doesn't change after assignment).
|
||
|
/// </summary>
|
||
|
protected internal void ChangeLocalID(int newID)
|
||
|
{
|
||
|
if (!this.IsLocal)
|
||
|
{
|
||
|
//Debug.LogError("ERROR You should never change Player IDs!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.actorNumber = newID;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Updates and synchronizes this Player's Custom Properties. Optionally, expectedProperties can be provided as condition.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Custom Properties are a set of string keys and arbitrary values which is synchronized
|
||
|
/// for the players in a Room. They are available when the client enters the room, as
|
||
|
/// they are in the response of OpJoin and OpCreate.
|
||
|
///
|
||
|
/// Custom Properties either relate to the (current) Room or a Player (in that Room).
|
||
|
///
|
||
|
/// Both classes locally cache the current key/values and make them available as
|
||
|
/// property: CustomProperties. This is provided only to read them.
|
||
|
/// You must use the method SetCustomProperties to set/modify them.
|
||
|
///
|
||
|
/// Any client can set any Custom Properties anytime (when in a room).
|
||
|
/// It's up to the game logic to organize how they are best used.
|
||
|
///
|
||
|
/// You should call SetCustomProperties only with key/values that are new or changed. This reduces
|
||
|
/// traffic and performance.
|
||
|
///
|
||
|
/// Unless you define some expectedProperties, setting key/values is always permitted.
|
||
|
/// In this case, the property-setting client will not receive the new values from the server but
|
||
|
/// instead update its local cache in SetCustomProperties.
|
||
|
///
|
||
|
/// If you define expectedProperties, the server will skip updates if the server property-cache
|
||
|
/// does not contain all expectedProperties with the same values.
|
||
|
/// In this case, the property-setting client will get an update from the server and update it's
|
||
|
/// cached key/values at about the same time as everyone else.
|
||
|
///
|
||
|
/// The benefit of using expectedProperties can be only one client successfully sets a key from
|
||
|
/// one known value to another.
|
||
|
/// As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally.
|
||
|
/// When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their
|
||
|
/// actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to
|
||
|
/// take the item will have it (and the others fail to set the ownership).
|
||
|
///
|
||
|
/// Properties get saved with the game state for Turnbased games (which use IsPersistent = true).
|
||
|
/// </remarks>
|
||
|
/// <param name="propertiesToSet">Hashtable of Custom Properties to be set. </param>
|
||
|
/// <param name="expectedValues">If non-null, these are the property-values the server will check as condition for this update.</param>
|
||
|
/// <param name="webFlags">Defines if this SetCustomProperties-operation gets forwarded to your WebHooks. Client must be in room.</param>
|
||
|
/// <returns>
|
||
|
/// False if propertiesToSet is null or empty or have zero string keys.
|
||
|
/// True in offline mode even if expectedProperties or webFlags are used.
|
||
|
/// If not in a room, returns true if local player and expectedValues and webFlags are null.
|
||
|
/// (Use this to cache properties to be sent when joining a room).
|
||
|
/// Otherwise, returns if this operation could be sent to the server.
|
||
|
/// </returns>
|
||
|
public bool SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedValues = null, WebFlags webFlags = null)
|
||
|
{
|
||
|
if (propertiesToSet == null || propertiesToSet.Count == 0)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable;
|
||
|
|
||
|
if (this.RoomReference != null)
|
||
|
{
|
||
|
if (this.RoomReference.IsOffline)
|
||
|
{
|
||
|
if (customProps.Count == 0)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
this.CustomProperties.Merge(customProps);
|
||
|
this.CustomProperties.StripKeysWithNullValues();
|
||
|
// invoking callbacks
|
||
|
this.RoomReference.LoadBalancingClient.InRoomCallbackTargets.OnPlayerPropertiesUpdate(this, customProps);
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Hashtable customPropsToCheck = expectedValues.StripToStringKeys() as Hashtable;
|
||
|
|
||
|
// send (sync) these new values if in online room
|
||
|
return this.RoomReference.LoadBalancingClient.OpSetPropertiesOfActor(this.actorNumber, customProps, customPropsToCheck, webFlags);
|
||
|
}
|
||
|
}
|
||
|
if (this.IsLocal)
|
||
|
{
|
||
|
if (customProps.Count == 0)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (expectedValues == null && webFlags == null)
|
||
|
{
|
||
|
this.CustomProperties.Merge(customProps);
|
||
|
this.CustomProperties.StripKeysWithNullValues();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>Uses OpSetPropertiesOfActor to sync this player's NickName (server is being updated with this.NickName).</summary>
|
||
|
private bool SetPlayerNameProperty()
|
||
|
{
|
||
|
if (this.RoomReference != null && !this.RoomReference.IsOffline)
|
||
|
{
|
||
|
Hashtable properties = new Hashtable();
|
||
|
properties[ActorProperties.PlayerName] = this.nickName;
|
||
|
return this.RoomReference.LoadBalancingClient.OpSetPropertiesOfActor(this.ActorNumber, properties);
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|