first commit
This commit is contained in:
198
Assets/Photon/PhotonRealtime/Code/AppSettings.cs
Normal file
198
Assets/Photon/PhotonRealtime/Code/AppSettings.cs
Normal file
@ -0,0 +1,198 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="AppSettings.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>Settings for Photon application(s) and the server to connect to.</summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_2017_4_OR_NEWER
|
||||
#define SUPPORTED_UNITY
|
||||
#endif
|
||||
|
||||
namespace Photon.Realtime
|
||||
{
|
||||
using System;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
#if SUPPORTED_UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Settings for Photon application(s) and the server to connect to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is Serializable for Unity, so it can be included in ScriptableObject instances.
|
||||
/// </remarks>
|
||||
#if !NETFX_CORE || SUPPORTED_UNITY
|
||||
[Serializable]
|
||||
#endif
|
||||
public class AppSettings
|
||||
{
|
||||
/// <summary>AppId for Realtime or PUN.</summary>
|
||||
public string AppIdRealtime;
|
||||
|
||||
/// <summary>AppId for Photon Fusion.</summary>
|
||||
public string AppIdFusion;
|
||||
|
||||
/// <summary>AppId for Photon Chat.</summary>
|
||||
public string AppIdChat;
|
||||
|
||||
/// <summary>AppId for Photon Voice.</summary>
|
||||
public string AppIdVoice;
|
||||
|
||||
/// <summary>The AppVersion can be used to identify builds and will split the AppId distinct "Virtual AppIds" (important for matchmaking).</summary>
|
||||
public string AppVersion;
|
||||
|
||||
|
||||
/// <summary>If false, the app will attempt to connect to a Master Server (which is obsolete but sometimes still necessary).</summary>
|
||||
/// <remarks>if true, Server points to a NameServer (or is null, using the default), else it points to a MasterServer.</remarks>
|
||||
public bool UseNameServer = true;
|
||||
|
||||
/// <summary>Can be set to any of the Photon Cloud's region names to directly connect to that region.</summary>
|
||||
/// <remarks>if this IsNullOrEmpty() AND UseNameServer == true, use BestRegion. else, use a server</remarks>
|
||||
public string FixedRegion;
|
||||
|
||||
/// <summary>Set to a previous BestRegionSummary value before connecting.</summary>
|
||||
/// <remarks>
|
||||
/// This is a value used when the client connects to the "Best Region".</br>
|
||||
/// If this is null or empty, all regions gets pinged. Providing a previous summary on connect,
|
||||
/// speeds up best region selection and makes the previously selected region "sticky".</br>
|
||||
///
|
||||
/// Unity clients should store the BestRegionSummary in the PlayerPrefs.
|
||||
/// You can store the new result by implementing <see cref="IConnectionCallbacks.OnConnectedToMaster"/>.
|
||||
/// If <see cref="LoadBalancingClient.SummaryToCache"/> is not null, store this string.
|
||||
/// To avoid storing the value multiple times, you could set SummaryToCache to null.
|
||||
/// </remarks>
|
||||
#if SUPPORTED_UNITY
|
||||
[NonSerialized]
|
||||
#endif
|
||||
public string BestRegionSummaryFromStorage;
|
||||
|
||||
/// <summary>The address (hostname or IP) of the server to connect to.</summary>
|
||||
public string Server;
|
||||
|
||||
/// <summary>If not null, this sets the port of the first Photon server to connect to (that will "forward" the client as needed).</summary>
|
||||
public int Port;
|
||||
|
||||
/// <summary>The address (hostname or IP and port) of the proxy server.</summary>
|
||||
public string ProxyServer;
|
||||
|
||||
/// <summary>The network level protocol to use.</summary>
|
||||
public ConnectionProtocol Protocol = ConnectionProtocol.Udp;
|
||||
|
||||
/// <summary>Enables a fallback to another protocol in case a connect to the Name Server fails.</summary>
|
||||
/// <remarks>See: LoadBalancingClient.EnableProtocolFallback.</remarks>
|
||||
public bool EnableProtocolFallback = true;
|
||||
|
||||
/// <summary>Defines how authentication is done. On each system, once or once via a WSS connection (safe).</summary>
|
||||
public AuthModeOption AuthMode = AuthModeOption.Auth;
|
||||
|
||||
/// <summary>If true, the client will request the list of currently available lobbies.</summary>
|
||||
public bool EnableLobbyStatistics;
|
||||
|
||||
/// <summary>Log level for the network lib.</summary>
|
||||
public DebugLevel NetworkLogging = DebugLevel.ERROR;
|
||||
|
||||
/// <summary>If true, the Server field contains a Master Server address (if any address at all).</summary>
|
||||
public bool IsMasterServerAddress
|
||||
{
|
||||
get { return !this.UseNameServer; }
|
||||
}
|
||||
|
||||
/// <summary>If true, the client should fetch the region list from the Name Server and find the one with best ping.</summary>
|
||||
/// <remarks>See "Best Region" in the online docs.</remarks>
|
||||
public bool IsBestRegion
|
||||
{
|
||||
get { return this.UseNameServer && string.IsNullOrEmpty(this.FixedRegion); }
|
||||
}
|
||||
|
||||
/// <summary>If true, the default nameserver address for the Photon Cloud should be used.</summary>
|
||||
public bool IsDefaultNameServer
|
||||
{
|
||||
get { return this.UseNameServer && string.IsNullOrEmpty(this.Server); }
|
||||
}
|
||||
|
||||
/// <summary>If true, the default ports for a protocol will be used.</summary>
|
||||
public bool IsDefaultPort
|
||||
{
|
||||
get { return this.Port <= 0; }
|
||||
}
|
||||
|
||||
/// <summary>ToString but with more details.</summary>
|
||||
public string ToStringFull()
|
||||
{
|
||||
return string.Format(
|
||||
"appId {0}{1}{2}{3}" +
|
||||
"use ns: {4}, reg: {5}, {9}, " +
|
||||
"{6}{7}{8}" +
|
||||
"auth: {10}",
|
||||
String.IsNullOrEmpty(this.AppIdRealtime) ? string.Empty : "Realtime/PUN: " + this.HideAppId(this.AppIdRealtime) + ", ",
|
||||
String.IsNullOrEmpty(this.AppIdFusion) ? string.Empty : "Fusion: " + this.HideAppId(this.AppIdFusion) + ", ",
|
||||
String.IsNullOrEmpty(this.AppIdChat) ? string.Empty : "Chat: " + this.HideAppId(this.AppIdChat) + ", ",
|
||||
String.IsNullOrEmpty(this.AppIdVoice) ? string.Empty : "Voice: " + this.HideAppId(this.AppIdVoice) + ", ",
|
||||
String.IsNullOrEmpty(this.AppVersion) ? string.Empty : "AppVersion: " + this.AppVersion + ", ",
|
||||
"UseNameServer: " + this.UseNameServer + ", ",
|
||||
"Fixed Region: " + this.FixedRegion + ", ",
|
||||
//this.BestRegionSummaryFromStorage,
|
||||
String.IsNullOrEmpty(this.Server) ? string.Empty : "Server: " + this.Server + ", ",
|
||||
this.IsDefaultPort ? string.Empty : "Port: " + this.Port + ", ",
|
||||
String.IsNullOrEmpty(ProxyServer) ? string.Empty : "Proxy: " + this.ProxyServer + ", ",
|
||||
this.Protocol,
|
||||
this.AuthMode
|
||||
//this.EnableLobbyStatistics,
|
||||
//this.NetworkLogging,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Checks if a string is a Guid by attempting to create one.</summary>
|
||||
/// <param name="val">The potential guid to check.</param>
|
||||
/// <returns>True if new Guid(val) did not fail.</returns>
|
||||
public static bool IsAppId(string val)
|
||||
{
|
||||
try
|
||||
{
|
||||
new Guid(val);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private string HideAppId(string appId)
|
||||
{
|
||||
return string.IsNullOrEmpty(appId) || appId.Length < 8
|
||||
? appId
|
||||
: string.Concat(appId.Substring(0, 8), "***");
|
||||
}
|
||||
|
||||
public AppSettings CopyTo(AppSettings d)
|
||||
{
|
||||
d.AppIdRealtime = this.AppIdRealtime;
|
||||
d.AppIdFusion = this.AppIdFusion;
|
||||
d.AppIdChat = this.AppIdChat;
|
||||
d.AppIdVoice = this.AppIdVoice;
|
||||
d.AppVersion = this.AppVersion;
|
||||
d.UseNameServer = this.UseNameServer;
|
||||
d.FixedRegion = this.FixedRegion;
|
||||
d.BestRegionSummaryFromStorage = this.BestRegionSummaryFromStorage;
|
||||
d.Server = this.Server;
|
||||
d.Port = this.Port;
|
||||
d.ProxyServer = this.ProxyServer;
|
||||
d.Protocol = this.Protocol;
|
||||
d.AuthMode = this.AuthMode;
|
||||
d.EnableLobbyStatistics = this.EnableLobbyStatistics;
|
||||
d.NetworkLogging = this.NetworkLogging;
|
||||
d.EnableProtocolFallback = this.EnableProtocolFallback;
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
12
Assets/Photon/PhotonRealtime/Code/AppSettings.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/AppSettings.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 272beb7315eb9fc4daecafe2ff373baf
|
||||
timeCreated: 1521804700
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
199
Assets/Photon/PhotonRealtime/Code/ConnectionHandler.cs
Normal file
199
Assets/Photon/PhotonRealtime/Code/ConnectionHandler.cs
Normal file
@ -0,0 +1,199 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="ConnectionHandler.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// If the game logic does not call Service() for whatever reason, this keeps the connection.
|
||||
// </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.Diagnostics;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
|
||||
#if SUPPORTED_UNITY
|
||||
using UnityEngine;
|
||||
#endif
|
||||
|
||||
|
||||
#if SUPPORTED_UNITY
|
||||
public class ConnectionHandler : MonoBehaviour
|
||||
#else
|
||||
public class ConnectionHandler
|
||||
#endif
|
||||
{
|
||||
/// <summary>
|
||||
/// Photon client to log information and statistics from.
|
||||
/// </summary>
|
||||
public LoadBalancingClient Client { get; set; }
|
||||
|
||||
/// <summary>Option to let the fallback thread call Disconnect after the KeepAliveInBackground time. Default: false.</summary>
|
||||
/// <remarks>
|
||||
/// If set to true, the thread will disconnect the client regularly, should the client not call SendOutgoingCommands / Service.
|
||||
/// This may happen due to an app being in background (and not getting a lot of CPU time) or when loading assets.
|
||||
///
|
||||
/// If false, a regular timeout time will have to pass (on top) to time out the client.
|
||||
/// </remarks>
|
||||
public bool DisconnectAfterKeepAlive = false;
|
||||
|
||||
/// <summary>Defines for how long the Fallback Thread should keep the connection, before it may time out as usual.</summary>
|
||||
/// <remarks>We want to the Client to keep it's connection when an app is in the background (and doesn't call Update / Service Clients should not keep their connection indefinitely in the background, so after some milliseconds, the Fallback Thread should stop keeping it up.</remarks>
|
||||
public int KeepAliveInBackground = 60000;
|
||||
|
||||
/// <summary>Counts how often the Fallback Thread called SendAcksOnly, which is purely of interest to monitor if the game logic called SendOutgoingCommands as intended.</summary>
|
||||
public int CountSendAcksOnly { get; private set; }
|
||||
|
||||
/// <summary>True if a fallback thread is running. Will call the client's SendAcksOnly() method to keep the connection up.</summary>
|
||||
public bool FallbackThreadRunning
|
||||
{
|
||||
get { return this.fallbackThreadId < 255; }
|
||||
}
|
||||
|
||||
/// <summary>Keeps the ConnectionHandler, even if a new scene gets loaded.</summary>
|
||||
public bool ApplyDontDestroyOnLoad = true;
|
||||
|
||||
/// <summary>Indicates that the app is closing. Set in OnApplicationQuit().</summary>
|
||||
[NonSerialized]
|
||||
public static bool AppQuits;
|
||||
|
||||
|
||||
private byte fallbackThreadId = 255;
|
||||
private bool didSendAcks;
|
||||
private readonly Stopwatch backgroundStopwatch = new Stopwatch();
|
||||
|
||||
|
||||
#if SUPPORTED_UNITY
|
||||
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
|
||||
/// <summary>
|
||||
/// Resets statics for Domain Reload
|
||||
/// </summary>
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void StaticReset()
|
||||
{
|
||||
AppQuits = false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>Called by Unity when the application gets closed. The UnityEngine will also call OnDisable, which disconnects.</summary>
|
||||
protected void OnApplicationQuit()
|
||||
{
|
||||
AppQuits = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary></summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
if (this.ApplyDontDestroyOnLoad)
|
||||
{
|
||||
DontDestroyOnLoad(this.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called by Unity when the application gets closed. Disconnects if OnApplicationQuit() was called before.</summary>
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
this.StopFallbackSendAckThread();
|
||||
|
||||
if (AppQuits)
|
||||
{
|
||||
if (this.Client != null && this.Client.IsConnected)
|
||||
{
|
||||
this.Client.Disconnect();
|
||||
this.Client.LoadBalancingPeer.StopThread();
|
||||
}
|
||||
|
||||
SupportClass.StopAllBackgroundCalls();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
public void StartFallbackSendAckThread()
|
||||
{
|
||||
#if !UNITY_WEBGL
|
||||
if (this.FallbackThreadRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_SWITCH
|
||||
this.fallbackThreadId = SupportClass.StartBackgroundCalls(this.RealtimeFallbackThread, 50); // as workaround, we don't name the Thread.
|
||||
#else
|
||||
this.fallbackThreadId = SupportClass.StartBackgroundCalls(this.RealtimeFallbackThread, 50, "RealtimeFallbackThread");
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
public void StopFallbackSendAckThread()
|
||||
{
|
||||
#if !UNITY_WEBGL
|
||||
if (!this.FallbackThreadRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SupportClass.StopBackgroundCalls(this.fallbackThreadId);
|
||||
this.fallbackThreadId = 255;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// <summary>A thread which runs independent from the Update() calls. Keeps connections online while loading or in background. See <see cref="KeepAliveInBackground"/>.</summary>
|
||||
public bool RealtimeFallbackThread()
|
||||
{
|
||||
if (this.Client != null)
|
||||
{
|
||||
if (!this.Client.IsConnected)
|
||||
{
|
||||
this.didSendAcks = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.Client.LoadBalancingPeer.ConnectionTime - this.Client.LoadBalancingPeer.LastSendOutgoingTime > 100)
|
||||
{
|
||||
if (!this.didSendAcks)
|
||||
{
|
||||
backgroundStopwatch.Reset();
|
||||
backgroundStopwatch.Start();
|
||||
}
|
||||
|
||||
// check if the client should disconnect after some seconds in background
|
||||
if (backgroundStopwatch.ElapsedMilliseconds > this.KeepAliveInBackground)
|
||||
{
|
||||
if (this.DisconnectAfterKeepAlive)
|
||||
{
|
||||
this.Client.Disconnect();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
this.didSendAcks = true;
|
||||
this.CountSendAcksOnly++;
|
||||
this.Client.LoadBalancingPeer.SendAcksOnly();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.didSendAcks = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
12
Assets/Photon/PhotonRealtime/Code/ConnectionHandler.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/ConnectionHandler.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0b3ce6748186d3468e0dbaecb38b04c
|
||||
timeCreated: 1527243846
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
167
Assets/Photon/PhotonRealtime/Code/CustomTypesUnity.cs
Normal file
167
Assets/Photon/PhotonRealtime/Code/CustomTypesUnity.cs
Normal file
@ -0,0 +1,167 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="CustomTypes.cs" company="Exit Games GmbH">
|
||||
// PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Sets up support for Unity-specific types. Can be a blueprint how to register your own Custom Types for sending.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
|
||||
#define SUPPORTED_UNITY
|
||||
#endif
|
||||
|
||||
#if SUPPORTED_UNITY
|
||||
namespace Photon.Realtime
|
||||
{
|
||||
using Photon.Realtime;
|
||||
using ExitGames.Client.Photon;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Internally used class, containing de/serialization methods for various Unity-specific classes.
|
||||
/// Adding those to the Photon serialization protocol allows you to send them in events, etc.
|
||||
/// </summary>
|
||||
internal static class CustomTypesUnity
|
||||
{
|
||||
private const int SizeV2 = 2 * 4;
|
||||
private const int SizeV3 = 3 * 4;
|
||||
private const int SizeQuat = 4 * 4;
|
||||
|
||||
|
||||
/// <summary>Register de/serializer methods for Unity specific types. Makes the types usable in RaiseEvent and PUN.</summary>
|
||||
internal static void Register()
|
||||
{
|
||||
PhotonPeer.RegisterType(typeof(Vector2), (byte) 'W', SerializeVector2, DeserializeVector2);
|
||||
PhotonPeer.RegisterType(typeof(Vector3), (byte) 'V', SerializeVector3, DeserializeVector3);
|
||||
PhotonPeer.RegisterType(typeof(Quaternion), (byte) 'Q', SerializeQuaternion, DeserializeQuaternion);
|
||||
}
|
||||
|
||||
|
||||
#region Custom De/Serializer Methods
|
||||
|
||||
public static readonly byte[] memVector3 = new byte[SizeV3];
|
||||
|
||||
private static short SerializeVector3(StreamBuffer outStream, object customobject)
|
||||
{
|
||||
Vector3 vo = (Vector3) customobject;
|
||||
|
||||
int index = 0;
|
||||
lock (memVector3)
|
||||
{
|
||||
byte[] bytes = memVector3;
|
||||
Protocol.Serialize(vo.x, bytes, ref index);
|
||||
Protocol.Serialize(vo.y, bytes, ref index);
|
||||
Protocol.Serialize(vo.z, bytes, ref index);
|
||||
outStream.Write(bytes, 0, SizeV3);
|
||||
}
|
||||
|
||||
return SizeV3;
|
||||
}
|
||||
|
||||
private static object DeserializeVector3(StreamBuffer inStream, short length)
|
||||
{
|
||||
Vector3 vo = new Vector3();
|
||||
if (length != SizeV3)
|
||||
{
|
||||
return vo;
|
||||
}
|
||||
|
||||
lock (memVector3)
|
||||
{
|
||||
inStream.Read(memVector3, 0, SizeV3);
|
||||
int index = 0;
|
||||
Protocol.Deserialize(out vo.x, memVector3, ref index);
|
||||
Protocol.Deserialize(out vo.y, memVector3, ref index);
|
||||
Protocol.Deserialize(out vo.z, memVector3, ref index);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
|
||||
public static readonly byte[] memVector2 = new byte[SizeV2];
|
||||
|
||||
private static short SerializeVector2(StreamBuffer outStream, object customobject)
|
||||
{
|
||||
Vector2 vo = (Vector2) customobject;
|
||||
lock (memVector2)
|
||||
{
|
||||
byte[] bytes = memVector2;
|
||||
int index = 0;
|
||||
Protocol.Serialize(vo.x, bytes, ref index);
|
||||
Protocol.Serialize(vo.y, bytes, ref index);
|
||||
outStream.Write(bytes, 0, SizeV2);
|
||||
}
|
||||
|
||||
return SizeV2;
|
||||
}
|
||||
|
||||
private static object DeserializeVector2(StreamBuffer inStream, short length)
|
||||
{
|
||||
Vector2 vo = new Vector2();
|
||||
if (length != SizeV2)
|
||||
{
|
||||
return vo;
|
||||
}
|
||||
|
||||
lock (memVector2)
|
||||
{
|
||||
inStream.Read(memVector2, 0, SizeV2);
|
||||
int index = 0;
|
||||
Protocol.Deserialize(out vo.x, memVector2, ref index);
|
||||
Protocol.Deserialize(out vo.y, memVector2, ref index);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
|
||||
public static readonly byte[] memQuarternion = new byte[SizeQuat];
|
||||
|
||||
private static short SerializeQuaternion(StreamBuffer outStream, object customobject)
|
||||
{
|
||||
Quaternion o = (Quaternion) customobject;
|
||||
|
||||
lock (memQuarternion)
|
||||
{
|
||||
byte[] bytes = memQuarternion;
|
||||
int index = 0;
|
||||
Protocol.Serialize(o.w, bytes, ref index);
|
||||
Protocol.Serialize(o.x, bytes, ref index);
|
||||
Protocol.Serialize(o.y, bytes, ref index);
|
||||
Protocol.Serialize(o.z, bytes, ref index);
|
||||
outStream.Write(bytes, 0, SizeQuat);
|
||||
}
|
||||
|
||||
return 4 * 4;
|
||||
}
|
||||
|
||||
private static object DeserializeQuaternion(StreamBuffer inStream, short length)
|
||||
{
|
||||
Quaternion o = Quaternion.identity;
|
||||
if (length != SizeQuat)
|
||||
{
|
||||
return o;
|
||||
}
|
||||
|
||||
lock (memQuarternion)
|
||||
{
|
||||
inStream.Read(memQuarternion, 0, SizeQuat);
|
||||
int index = 0;
|
||||
Protocol.Deserialize(out o.w, memQuarternion, ref index);
|
||||
Protocol.Deserialize(out o.x, memQuarternion, ref index);
|
||||
Protocol.Deserialize(out o.y, memQuarternion, ref index);
|
||||
Protocol.Deserialize(out o.z, memQuarternion, ref index);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
11
Assets/Photon/PhotonRealtime/Code/CustomTypesUnity.cs.meta
Normal file
11
Assets/Photon/PhotonRealtime/Code/CustomTypesUnity.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c41ee868d8540674982e160cf16e0775
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
269
Assets/Photon/PhotonRealtime/Code/Extensions.cs
Normal file
269
Assets/Photon/PhotonRealtime/Code/Extensions.cs
Normal file
@ -0,0 +1,269 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="Extensions.cs" company="Exit Games GmbH">
|
||||
// Photon Extensions - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Provides some helpful methods and extensions for Hashtables, etc.
|
||||
// </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.Collections;
|
||||
using System.Collections.Generic;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
#if SUPPORTED_UNITY
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
#endif
|
||||
#if SUPPORTED_UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This static class defines some useful extension methods for several existing classes (e.g. Vector3, float and others).
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Merges all keys from addHash into the target. Adds new keys and updates the values of existing keys in target.
|
||||
/// </summary>
|
||||
/// <param name="target">The IDictionary to update.</param>
|
||||
/// <param name="addHash">The IDictionary containing data to merge into target.</param>
|
||||
public static void Merge(this IDictionary target, IDictionary addHash)
|
||||
{
|
||||
if (addHash == null || target.Equals(addHash))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (object key in addHash.Keys)
|
||||
{
|
||||
target[key] = addHash[key];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges keys of type string to target Hashtable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not remove keys from target (so non-string keys CAN be in target if they were before).
|
||||
/// </remarks>
|
||||
/// <param name="target">The target IDictionary passed in plus all string-typed keys from the addHash.</param>
|
||||
/// <param name="addHash">A IDictionary that should be merged partly into target to update it.</param>
|
||||
public static void MergeStringKeys(this IDictionary target, IDictionary addHash)
|
||||
{
|
||||
if (addHash == null || target.Equals(addHash))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (object key in addHash.Keys)
|
||||
{
|
||||
// only merge keys of type string
|
||||
if (key is string)
|
||||
{
|
||||
target[key] = addHash[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Helper method for debugging of IDictionary content, including type-information. Using this is not performant.</summary>
|
||||
/// <remarks>Should only be used for debugging as necessary.</remarks>
|
||||
/// <param name="origin">Some Dictionary or Hashtable.</param>
|
||||
/// <returns>String of the content of the IDictionary.</returns>
|
||||
public static string ToStringFull(this IDictionary origin)
|
||||
{
|
||||
return SupportClass.DictionaryToString(origin, false);
|
||||
}
|
||||
|
||||
/// <summary>Helper method for debugging of List<T> content. Using this is not performant.</summary>
|
||||
/// <remarks>Should only be used for debugging as necessary.</remarks>
|
||||
/// <param name="data">Any List<T> where T implements .ToString().</param>
|
||||
/// <returns>A comma-separated string containing each value's ToString().</returns>
|
||||
public static string ToStringFull<T>(this List<T> data)
|
||||
{
|
||||
if (data == null) return "null";
|
||||
|
||||
string[] sb = new string[data.Count];
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
{
|
||||
object o = data[i];
|
||||
sb[i] = (o != null) ? o.ToString() : "null";
|
||||
}
|
||||
|
||||
return string.Join(", ", sb);
|
||||
}
|
||||
|
||||
/// <summary>Helper method for debugging of object[] content. Using this is not performant.</summary>
|
||||
/// <remarks>Should only be used for debugging as necessary.</remarks>
|
||||
/// <param name="data">Any object[].</param>
|
||||
/// <returns>A comma-separated string containing each value's ToString().</returns>
|
||||
public static string ToStringFull(this object[] data)
|
||||
{
|
||||
if (data == null) return "null";
|
||||
|
||||
string[] sb = new string[data.Length];
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
object o = data[i];
|
||||
sb[i] = (o != null) ? o.ToString() : "null";
|
||||
}
|
||||
|
||||
return string.Join(", ", sb);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This method copies all string-typed keys of the original into a new Hashtable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not recurse (!) into hashes that might be values in the root-hash.
|
||||
/// This does not modify the original.
|
||||
/// </remarks>
|
||||
/// <param name="original">The original IDictonary to get string-typed keys from.</param>
|
||||
/// <returns>New Hashtable containing only string-typed keys of the original.</returns>
|
||||
public static Hashtable StripToStringKeys(this IDictionary original)
|
||||
{
|
||||
Hashtable target = new Hashtable();
|
||||
if (original != null)
|
||||
{
|
||||
foreach (object key in original.Keys)
|
||||
{
|
||||
if (key is string)
|
||||
{
|
||||
target[key] = original[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method copies all string-typed keys of the original into a new Hashtable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not recurse (!) into hashes that might be values in the root-hash.
|
||||
/// This does not modify the original.
|
||||
/// </remarks>
|
||||
/// <param name="original">The original IDictonary to get string-typed keys from.</param>
|
||||
/// <returns>New Hashtable containing only string-typed keys of the original.</returns>
|
||||
public static Hashtable StripToStringKeys(this Hashtable original)
|
||||
{
|
||||
Hashtable target = new Hashtable();
|
||||
if (original != null)
|
||||
{
|
||||
foreach (DictionaryEntry entry in original)
|
||||
{
|
||||
if (entry.Key is string)
|
||||
{
|
||||
target[entry.Key] = original[entry.Key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Used by StripKeysWithNullValues.</summary>
|
||||
/// <remarks>
|
||||
/// By making keysWithNullValue a static variable to clear before using, allocations only happen during the warm-up phase
|
||||
/// as the list needs to grow. Once it hit the high water mark for keys you need to remove.
|
||||
/// </remarks>
|
||||
private static readonly List<object> keysWithNullValue = new List<object>();
|
||||
|
||||
/// <summary>Removes all keys with null values.</summary>
|
||||
/// <remarks>
|
||||
/// Photon properties are removed by setting their value to null. Changes the original IDictionary!
|
||||
/// Uses lock(keysWithNullValue), which should be no problem in expected use cases.
|
||||
/// </remarks>
|
||||
/// <param name="original">The IDictionary to strip of keys with null value.</param>
|
||||
public static void StripKeysWithNullValues(this IDictionary original)
|
||||
{
|
||||
lock (keysWithNullValue)
|
||||
{
|
||||
keysWithNullValue.Clear();
|
||||
|
||||
foreach (DictionaryEntry entry in original)
|
||||
{
|
||||
if (entry.Value == null)
|
||||
{
|
||||
keysWithNullValue.Add(entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < keysWithNullValue.Count; i++)
|
||||
{
|
||||
var key = keysWithNullValue[i];
|
||||
original.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all keys with null values.</summary>
|
||||
/// <remarks>
|
||||
/// Photon properties are removed by setting their value to null. Changes the original IDictionary!
|
||||
/// Uses lock(keysWithNullValue), which should be no problem in expected use cases.
|
||||
/// </remarks>
|
||||
/// <param name="original">The IDictionary to strip of keys with null value.</param>
|
||||
public static void StripKeysWithNullValues(this Hashtable original)
|
||||
{
|
||||
lock (keysWithNullValue)
|
||||
{
|
||||
keysWithNullValue.Clear();
|
||||
|
||||
foreach (DictionaryEntry entry in original)
|
||||
{
|
||||
if (entry.Value == null)
|
||||
{
|
||||
keysWithNullValue.Add(entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < keysWithNullValue.Count; i++)
|
||||
{
|
||||
var key = keysWithNullValue[i];
|
||||
original.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a particular integer value is in an int-array.
|
||||
/// </summary>
|
||||
/// <remarks>This might be useful to look up if a particular actorNumber is in the list of players of a room.</remarks>
|
||||
/// <param name="target">The array of ints to check.</param>
|
||||
/// <param name="nr">The number to lookup in target.</param>
|
||||
/// <returns>True if nr was found in target.</returns>
|
||||
public static bool Contains(this int[] target, int nr)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < target.Length; index++)
|
||||
{
|
||||
if (target[index] == nr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
Assets/Photon/PhotonRealtime/Code/Extensions.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/Extensions.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c0464991e33a70498abdd85c150cc59
|
||||
labels:
|
||||
- ExitGames
|
||||
- PUN
|
||||
- Photon
|
||||
- Networking
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
48
Assets/Photon/PhotonRealtime/Code/FriendInfo.cs
Normal file
48
Assets/Photon/PhotonRealtime/Code/FriendInfo.cs
Normal file
@ -0,0 +1,48 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="FriendInfo.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Collection of values related to a user / friend.
|
||||
// </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 ExitGames.Client.Photon;
|
||||
|
||||
#if SUPPORTED_UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to store info about a friend's online state and in which room he/she is.
|
||||
/// </summary>
|
||||
public class FriendInfo
|
||||
{
|
||||
[System.Obsolete("Use UserId.")]
|
||||
public string Name { get { return this.UserId; } }
|
||||
public string UserId { get; internal protected set; }
|
||||
|
||||
public bool IsOnline { get; internal protected set; }
|
||||
public string Room { get; internal protected set; }
|
||||
|
||||
public bool IsInRoom
|
||||
{
|
||||
get { return this.IsOnline && !string.IsNullOrEmpty(this.Room); }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}\t is: {1}", this.UserId, (!this.IsOnline) ? "offline" : this.IsInRoom ? "playing" : "on master");
|
||||
}
|
||||
}
|
||||
}
|
7
Assets/Photon/PhotonRealtime/Code/FriendInfo.cs.meta
Normal file
7
Assets/Photon/PhotonRealtime/Code/FriendInfo.cs.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94ba1138c322ea04c8c37cfbcf87f468
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
4431
Assets/Photon/PhotonRealtime/Code/LoadBalancingClient.cs
Normal file
4431
Assets/Photon/PhotonRealtime/Code/LoadBalancingClient.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4e65968c7102bf42a77b6a5cade8743
|
||||
timeCreated: 1497614756
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
2236
Assets/Photon/PhotonRealtime/Code/LoadbalancingPeer.cs
Normal file
2236
Assets/Photon/PhotonRealtime/Code/LoadbalancingPeer.cs
Normal file
File diff suppressed because it is too large
Load Diff
12
Assets/Photon/PhotonRealtime/Code/LoadbalancingPeer.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/LoadbalancingPeer.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35c989013c977244186e524a4c90dcee
|
||||
labels:
|
||||
- ExitGames
|
||||
- PUN
|
||||
- Photon
|
||||
- Networking
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
480
Assets/Photon/PhotonRealtime/Code/PhotonPing.cs
Normal file
480
Assets/Photon/PhotonRealtime/Code/PhotonPing.cs
Normal file
@ -0,0 +1,480 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="PhotonPing.cs" company="Exit Games GmbH">
|
||||
// PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This file includes various PhotonPing implementations for different APIs,
|
||||
// platforms and protocols.
|
||||
// The RegionPinger class is the instance which selects the Ping implementation
|
||||
// to use.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace Photon.Realtime
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
|
||||
#if NETFX_CORE
|
||||
using System.Diagnostics;
|
||||
using Windows.Foundation;
|
||||
using Windows.Networking;
|
||||
using Windows.Networking.Sockets;
|
||||
using Windows.Storage.Streams;
|
||||
#endif
|
||||
|
||||
#if !NO_SOCKET && !NETFX_CORE
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Sockets;
|
||||
#endif
|
||||
|
||||
#if UNITY_WEBGL
|
||||
// import WWW class
|
||||
using UnityEngine;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Abstract implementation of PhotonPing, ase for pinging servers to find the "Best Region".
|
||||
/// </summary>
|
||||
public abstract class PhotonPing : IDisposable
|
||||
{
|
||||
public string DebugString = "";
|
||||
|
||||
public bool Successful;
|
||||
|
||||
protected internal bool GotResult;
|
||||
|
||||
protected internal int PingLength = 13;
|
||||
|
||||
protected internal byte[] PingBytes = new byte[] { 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x7d, 0x00 };
|
||||
|
||||
protected internal byte PingId;
|
||||
|
||||
private static readonly System.Random RandomIdProvider = new System.Random();
|
||||
|
||||
public virtual bool StartPing(string ip)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual bool Done()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected internal void Init()
|
||||
{
|
||||
this.GotResult = false;
|
||||
this.Successful = false;
|
||||
this.PingId = (byte)(RandomIdProvider.Next(255));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if !NETFX_CORE && !NO_SOCKET
|
||||
/// <summary>Uses C# Socket class from System.Net.Sockets (as Unity usually does).</summary>
|
||||
/// <remarks>Incompatible with Windows 8 Store/Phone API.</remarks>
|
||||
public class PingMono : PhotonPing
|
||||
{
|
||||
private Socket sock;
|
||||
|
||||
/// <summary>
|
||||
/// Sends a "Photon Ping" to a server.
|
||||
/// </summary>
|
||||
/// <param name="ip">Address in IPv4 or IPv6 format. An address containing a '.' will be interpreted as IPv4.</param>
|
||||
/// <returns>True if the Photon Ping could be sent.</returns>
|
||||
public override bool StartPing(string ip)
|
||||
{
|
||||
this.Init();
|
||||
|
||||
try
|
||||
{
|
||||
if (this.sock == null)
|
||||
{
|
||||
if (ip.Contains("."))
|
||||
{
|
||||
this.sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.sock = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
|
||||
}
|
||||
|
||||
this.sock.ReceiveTimeout = 5000;
|
||||
int port = (RegionHandler.PortToPingOverride != 0) ? RegionHandler.PortToPingOverride : 5055;
|
||||
this.sock.Connect(ip, port);
|
||||
}
|
||||
|
||||
|
||||
this.PingBytes[this.PingBytes.Length - 1] = this.PingId;
|
||||
this.sock.Send(this.PingBytes);
|
||||
this.PingBytes[this.PingBytes.Length - 1] = (byte)(this.PingId+1); // this buffer is re-used for the result/receive. invalidate the result now.
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.sock = null;
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Done()
|
||||
{
|
||||
if (this.GotResult || this.sock == null)
|
||||
{
|
||||
return true; // this just indicates the ping is no longer waiting. this.Successful value defines if the roundtrip completed
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
try
|
||||
{
|
||||
if (!this.sock.Poll(0, SelectMode.SelectRead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
read = this.sock.Receive(this.PingBytes, SocketFlags.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (this.sock != null)
|
||||
{
|
||||
this.sock.Close();
|
||||
this.sock = null;
|
||||
}
|
||||
this.DebugString += " Exception of socket! " + ex.GetType() + " ";
|
||||
return true; // this just indicates the ping is no longer waiting. this.Successful value defines if the roundtrip completed
|
||||
}
|
||||
|
||||
bool replyMatch = this.PingBytes[this.PingBytes.Length - 1] == this.PingId && read == this.PingLength;
|
||||
if (!replyMatch)
|
||||
{
|
||||
this.DebugString += " ReplyMatch is false! ";
|
||||
}
|
||||
|
||||
|
||||
this.Successful = replyMatch;
|
||||
this.GotResult = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.sock.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
this.sock = null;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if NETFX_CORE
|
||||
/// <summary>Windows store API implementation of PhotonPing, based on DatagramSocket for UDP.</summary>
|
||||
public class PingWindowsStore : PhotonPing
|
||||
{
|
||||
private DatagramSocket sock;
|
||||
private readonly object syncer = new object();
|
||||
|
||||
public override bool StartPing(string host)
|
||||
{
|
||||
lock (this.syncer)
|
||||
{
|
||||
this.Init();
|
||||
|
||||
int port = (RegionHandler.PortToPingOverride != 0) ? RegionHandler.PortToPingOverride : 5055;
|
||||
EndpointPair endPoint = new EndpointPair(null, string.Empty, new HostName(host), port.ToString());
|
||||
this.sock = new DatagramSocket();
|
||||
this.sock.MessageReceived += this.OnMessageReceived;
|
||||
|
||||
IAsyncAction result = this.sock.ConnectAsync(endPoint);
|
||||
result.Completed = this.OnConnected;
|
||||
this.DebugString += " End StartPing";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Done()
|
||||
{
|
||||
lock (this.syncer)
|
||||
{
|
||||
return this.GotResult || this.sock == null; // this just indicates the ping is no longer waiting. this.Successful value defines if the roundtrip completed
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
lock (this.syncer)
|
||||
{
|
||||
this.sock = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnected(IAsyncAction asyncinfo, AsyncStatus asyncstatus)
|
||||
{
|
||||
lock (this.syncer)
|
||||
{
|
||||
if (asyncinfo.AsTask().IsCompleted && !asyncinfo.AsTask().IsFaulted && this.sock != null && this.sock.Information.RemoteAddress != null)
|
||||
{
|
||||
this.PingBytes[this.PingBytes.Length - 1] = this.PingId;
|
||||
|
||||
DataWriter writer;
|
||||
writer = new DataWriter(this.sock.OutputStream);
|
||||
writer.WriteBytes(this.PingBytes);
|
||||
DataWriterStoreOperation res = writer.StoreAsync();
|
||||
res.AsTask().Wait(100);
|
||||
|
||||
this.PingBytes[this.PingBytes.Length - 1] = (byte)(this.PingId + 1); // this buffer is re-used for the result/receive. invalidate the result now.
|
||||
|
||||
writer.DetachStream();
|
||||
writer.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.sock = null; // will cause Done() to return true but this.Successful defines if the roundtrip completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
|
||||
{
|
||||
lock (this.syncer)
|
||||
{
|
||||
DataReader reader = null;
|
||||
try
|
||||
{
|
||||
reader = args.GetDataReader();
|
||||
uint receivedByteCount = reader.UnconsumedBufferLength;
|
||||
if (receivedByteCount > 0)
|
||||
{
|
||||
byte[] resultBytes = new byte[receivedByteCount];
|
||||
reader.ReadBytes(resultBytes);
|
||||
|
||||
//TODO: check result bytes!
|
||||
|
||||
|
||||
this.Successful = receivedByteCount == this.PingLength && resultBytes[resultBytes.Length - 1] == this.PingId;
|
||||
this.GotResult = true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: handle error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if NATIVE_SOCKETS
|
||||
/// <summary>Abstract base class to provide proper resource management for the below native ping implementations</summary>
|
||||
public abstract class PingNative : PhotonPing
|
||||
{
|
||||
// Native socket states - according to EnetConnect.h state definitions
|
||||
protected enum NativeSocketState : byte
|
||||
{
|
||||
Disconnected = 0,
|
||||
Connecting = 1,
|
||||
Connected = 2,
|
||||
ConnectionError = 3,
|
||||
SendError = 4,
|
||||
ReceiveError = 5,
|
||||
Disconnecting = 6
|
||||
}
|
||||
|
||||
protected IntPtr pConnectionHandler = IntPtr.Zero;
|
||||
|
||||
~PingNative()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Uses dynamic linked native Photon socket library via DllImport("PhotonSocketPlugin") attribute (as done by Unity Android and Unity PS3).</summary>
|
||||
public class PingNativeDynamic : PingNative
|
||||
{
|
||||
public override bool StartPing(string ip)
|
||||
{
|
||||
lock (SocketUdpNativeDynamic.syncer)
|
||||
{
|
||||
base.Init();
|
||||
|
||||
if(pConnectionHandler == IntPtr.Zero)
|
||||
{
|
||||
pConnectionHandler = SocketUdpNativeDynamic.egconnect(ip);
|
||||
SocketUdpNativeDynamic.egservice(pConnectionHandler);
|
||||
byte state = SocketUdpNativeDynamic.eggetState(pConnectionHandler);
|
||||
while (state == (byte) NativeSocketState.Connecting)
|
||||
{
|
||||
SocketUdpNativeDynamic.egservice(pConnectionHandler);
|
||||
state = SocketUdpNativeDynamic.eggetState(pConnectionHandler);
|
||||
}
|
||||
}
|
||||
|
||||
PingBytes[PingBytes.Length - 1] = PingId;
|
||||
SocketUdpNativeDynamic.egsend(pConnectionHandler, PingBytes, PingBytes.Length);
|
||||
SocketUdpNativeDynamic.egservice(pConnectionHandler);
|
||||
|
||||
PingBytes[PingBytes.Length - 1] = (byte) (PingId - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Done()
|
||||
{
|
||||
lock (SocketUdpNativeDynamic.syncer)
|
||||
{
|
||||
if (this.GotResult || pConnectionHandler == IntPtr.Zero)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int available = SocketUdpNativeDynamic.egservice(pConnectionHandler);
|
||||
if (available < PingLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int pingBytesLength = PingBytes.Length;
|
||||
int bytesInRemainginDatagrams = SocketUdpNativeDynamic.egread(pConnectionHandler, PingBytes, ref pingBytesLength);
|
||||
this.Successful = (PingBytes != null && PingBytes[PingBytes.Length - 1] == PingId);
|
||||
//Debug.Log("Successful: " + this.Successful + " bytesInRemainginDatagrams: " + bytesInRemainginDatagrams + " PingId: " + PingId);
|
||||
|
||||
this.GotResult = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
lock (SocketUdpNativeDynamic.syncer)
|
||||
{
|
||||
if (this.pConnectionHandler != IntPtr.Zero)
|
||||
SocketUdpNativeDynamic.egdisconnect(this.pConnectionHandler);
|
||||
this.pConnectionHandler = IntPtr.Zero;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
#if NATIVE_SOCKETS && NATIVE_SOCKETS_STATIC
|
||||
/// <summary>Uses static linked native Photon socket library via DllImport("__Internal") attribute (as done by Unity iOS and Unity Switch).</summary>
|
||||
public class PingNativeStatic : PingNative
|
||||
{
|
||||
public override bool StartPing(string ip)
|
||||
{
|
||||
base.Init();
|
||||
|
||||
lock (SocketUdpNativeStatic.syncer)
|
||||
{
|
||||
if(pConnectionHandler == IntPtr.Zero)
|
||||
{
|
||||
pConnectionHandler = SocketUdpNativeStatic.egconnect(ip);
|
||||
SocketUdpNativeStatic.egservice(pConnectionHandler);
|
||||
byte state = SocketUdpNativeStatic.eggetState(pConnectionHandler);
|
||||
while (state == (byte) NativeSocketState.Connecting)
|
||||
{
|
||||
SocketUdpNativeStatic.egservice(pConnectionHandler);
|
||||
state = SocketUdpNativeStatic.eggetState(pConnectionHandler);
|
||||
Thread.Sleep(0); // suspending execution for a moment is critical on Switch for the OS to update the socket
|
||||
}
|
||||
}
|
||||
|
||||
PingBytes[PingBytes.Length - 1] = PingId;
|
||||
SocketUdpNativeStatic.egsend(pConnectionHandler, PingBytes, PingBytes.Length);
|
||||
SocketUdpNativeStatic.egservice(pConnectionHandler);
|
||||
|
||||
PingBytes[PingBytes.Length - 1] = (byte) (PingId - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Done()
|
||||
{
|
||||
lock (SocketUdpNativeStatic.syncer)
|
||||
{
|
||||
if (this.GotResult || pConnectionHandler == IntPtr.Zero)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int available = SocketUdpNativeStatic.egservice(pConnectionHandler);
|
||||
if (available < PingLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int pingBytesLength = PingBytes.Length;
|
||||
int bytesInRemainginDatagrams = SocketUdpNativeStatic.egread(pConnectionHandler, PingBytes, ref pingBytesLength);
|
||||
this.Successful = (PingBytes != null && PingBytes[PingBytes.Length - 1] == PingId);
|
||||
//Debug.Log("Successful: " + this.Successful + " bytesInRemainginDatagrams: " + bytesInRemainginDatagrams + " PingId: " + PingId);
|
||||
|
||||
this.GotResult = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
lock (SocketUdpNativeStatic.syncer)
|
||||
{
|
||||
if (pConnectionHandler != IntPtr.Zero)
|
||||
SocketUdpNativeStatic.egdisconnect(pConnectionHandler);
|
||||
pConnectionHandler = IntPtr.Zero;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#if UNITY_WEBGL
|
||||
public class PingHttp : PhotonPing
|
||||
{
|
||||
private WWW webRequest;
|
||||
|
||||
public override bool StartPing(string address)
|
||||
{
|
||||
base.Init();
|
||||
|
||||
address = "https://" + address + "/photon/m/?ping&r=" + UnityEngine.Random.Range(0, 10000);
|
||||
this.webRequest = new WWW(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Done()
|
||||
{
|
||||
if (this.webRequest.isDone)
|
||||
{
|
||||
Successful = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
this.webRequest.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
12
Assets/Photon/PhotonRealtime/Code/PhotonPing.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/PhotonPing.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a2f6055139b44142954461627d344bc
|
||||
timeCreated: 1524653911
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1
Assets/Photon/PhotonRealtime/Code/PhotonPingClasses.cs
Normal file
1
Assets/Photon/PhotonRealtime/Code/PhotonPingClasses.cs
Normal file
@ -0,0 +1 @@
|
||||
// this file is no longer used. it can be deleted safely.
|
12
Assets/Photon/PhotonRealtime/Code/PhotonPingClasses.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/PhotonPingClasses.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d93148764d7961d4a8b8112bd166cf35
|
||||
timeCreated: 1494420905
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
6
Assets/Photon/PhotonRealtime/Code/PhotonRealtime.asmdef
Normal file
6
Assets/Photon/PhotonRealtime/Code/PhotonRealtime.asmdef
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "PhotonRealtime",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": []
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 831409e8f9d13b5479a3baef9822ad34
|
||||
timeCreated: 1537459565
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
452
Assets/Photon/PhotonRealtime/Code/Player.cs
Normal file
452
Assets/Photon/PhotonRealtime/Code/Player.cs
Normal file
@ -0,0 +1,452 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <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;
|
||||
}
|
||||
}
|
||||
}
|
12
Assets/Photon/PhotonRealtime/Code/Player.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/Player.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3e4b5bebc687044b9c6c2803c36be3d
|
||||
labels:
|
||||
- ExitGames
|
||||
- PUN
|
||||
- Photon
|
||||
- Networking
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
90
Assets/Photon/PhotonRealtime/Code/Region.cs
Normal file
90
Assets/Photon/PhotonRealtime/Code/Region.cs
Normal file
@ -0,0 +1,90 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="Region.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Represents regions in the Photon Cloud.
|
||||
// </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 ExitGames.Client.Photon;
|
||||
|
||||
#if SUPPORTED_UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
public class Region
|
||||
{
|
||||
public string Code { get; private set; }
|
||||
|
||||
/// <summary>Unlike the CloudRegionCode, this may contain cluster information.</summary>
|
||||
public string Cluster { get; private set; }
|
||||
|
||||
public string HostAndPort { get; protected internal set; }
|
||||
|
||||
public int Ping { get; set; }
|
||||
|
||||
public bool WasPinged { get { return this.Ping != int.MaxValue; } }
|
||||
|
||||
public Region(string code, string address)
|
||||
{
|
||||
this.SetCodeAndCluster(code);
|
||||
this.HostAndPort = address;
|
||||
this.Ping = int.MaxValue;
|
||||
}
|
||||
|
||||
public Region(string code, int ping)
|
||||
{
|
||||
this.SetCodeAndCluster(code);
|
||||
this.Ping = ping;
|
||||
}
|
||||
|
||||
private void SetCodeAndCluster(string codeAsString)
|
||||
{
|
||||
if (codeAsString == null)
|
||||
{
|
||||
this.Code = "";
|
||||
this.Cluster = "";
|
||||
return;
|
||||
}
|
||||
|
||||
codeAsString = codeAsString.ToLower();
|
||||
int slash = codeAsString.IndexOf('/');
|
||||
this.Code = slash <= 0 ? codeAsString : codeAsString.Substring(0, slash);
|
||||
this.Cluster = slash <= 0 ? "" : codeAsString.Substring(slash+1, codeAsString.Length-slash-1);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.ToString(false);
|
||||
}
|
||||
|
||||
public string ToString(bool compact = false)
|
||||
{
|
||||
string regionCluster = this.Code;
|
||||
if (!string.IsNullOrEmpty(this.Cluster))
|
||||
{
|
||||
regionCluster += "/" + this.Cluster;
|
||||
}
|
||||
|
||||
if (compact)
|
||||
{
|
||||
return string.Format("{0}:{1}", regionCluster, this.Ping);
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Format("{0}[{2}]: {1}ms", regionCluster, this.Ping, this.HostAndPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
Assets/Photon/PhotonRealtime/Code/Region.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/Region.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bddf4f4bde2f8dd46927411229ce1302
|
||||
timeCreated: 1494420905
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
699
Assets/Photon/PhotonRealtime/Code/RegionHandler.cs
Normal file
699
Assets/Photon/PhotonRealtime/Code/RegionHandler.cs
Normal file
@ -0,0 +1,699 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="RegionHandler.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// The RegionHandler class provides methods to ping a list of regions,
|
||||
// to find the one with best ping.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
|
||||
#define SUPPORTED_UNITY
|
||||
#endif
|
||||
|
||||
#if UNITY_WEBGL
|
||||
#define PING_VIA_COROUTINE
|
||||
#endif
|
||||
|
||||
namespace Photon.Realtime
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Net;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
#if SUPPORTED_UNITY
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
#endif
|
||||
#if SUPPORTED_UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods to work with Photon's regions (Photon Cloud) and can be use to find the one with best ping.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When a client uses a Name Server to fetch the list of available regions, the LoadBalancingClient will create a RegionHandler
|
||||
/// and provide it via the OnRegionListReceived callback.
|
||||
///
|
||||
/// Your logic can decide to either connect to one of those regional servers, or it may use PingMinimumOfRegions to test
|
||||
/// which region provides the best ping.
|
||||
///
|
||||
/// It makes sense to make clients "sticky" to a region when one gets selected.
|
||||
/// This can be achieved by storing the SummaryToCache value, once pinging was done.
|
||||
/// When the client connects again, the previous SummaryToCache helps limiting the number of regions to ping.
|
||||
/// In best case, only the previously selected region gets re-pinged and if the current ping is not much worse, this one region is used again.
|
||||
/// </remarks>
|
||||
public class RegionHandler
|
||||
{
|
||||
/// <summary>The implementation of PhotonPing to use for region pinging (Best Region detection).</summary>
|
||||
/// <remarks>Defaults to null, which means the Type is set automatically.</remarks>
|
||||
public static Type PingImplementation;
|
||||
|
||||
/// <summary>A list of region names for the Photon Cloud. Set by the result of OpGetRegions().</summary>
|
||||
/// <remarks>
|
||||
/// Implement ILoadBalancingCallbacks and register for the callbacks to get OnRegionListReceived(RegionHandler regionHandler).
|
||||
/// You can also put a "case OperationCode.GetRegions:" into your OnOperationResponse method to notice when the result is available.
|
||||
/// </remarks>
|
||||
public List<Region> EnabledRegions { get; protected internal set; }
|
||||
|
||||
private string availableRegionCodes;
|
||||
|
||||
private Region bestRegionCache;
|
||||
|
||||
/// <summary>
|
||||
/// When PingMinimumOfRegions was called and completed, the BestRegion is identified by best ping.
|
||||
/// </summary>
|
||||
public Region BestRegion
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.EnabledRegions == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (this.bestRegionCache != null)
|
||||
{
|
||||
return this.bestRegionCache;
|
||||
}
|
||||
|
||||
this.EnabledRegions.Sort((a, b) => a.Ping.CompareTo(b.Ping) );
|
||||
|
||||
this.bestRegionCache = this.EnabledRegions[0];
|
||||
return this.bestRegionCache;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This value summarizes the results of pinging currently available regions (after PingMinimumOfRegions finished).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value should be stored in the client by the game logic.
|
||||
/// When connecting again, use it as previous summary to speed up pinging regions and to make the best region sticky for the client.
|
||||
/// </remarks>
|
||||
public string SummaryToCache
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.BestRegion != null) {
|
||||
return this.BestRegion.Code + ";" + this.BestRegion.Ping + ";" + this.availableRegionCodes;
|
||||
}
|
||||
|
||||
return this.availableRegionCodes;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetResults()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendFormat("Region Pinging Result: {0}\n", this.BestRegion.ToString());
|
||||
foreach (RegionPinger region in this.pingerList)
|
||||
{
|
||||
sb.AppendFormat(region.GetResults() + "\n");
|
||||
}
|
||||
sb.AppendFormat("Previous summary: {0}", this.previousSummaryProvided);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void SetRegions(OperationResponse opGetRegions)
|
||||
{
|
||||
if (opGetRegions.OperationCode != OperationCode.GetRegions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (opGetRegions.ReturnCode != ErrorCode.Ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] regions = opGetRegions[ParameterCode.Region] as string[];
|
||||
string[] servers = opGetRegions[ParameterCode.Address] as string[];
|
||||
if (regions == null || servers == null || regions.Length != servers.Length)
|
||||
{
|
||||
//TODO: log error
|
||||
//Debug.LogError("The region arrays from Name Server are not ok. Must be non-null and same length. " + (regions == null) + " " + (servers == null) + "\n" + opGetRegions.ToStringFull());
|
||||
return;
|
||||
}
|
||||
|
||||
this.bestRegionCache = null;
|
||||
this.EnabledRegions = new List<Region>(regions.Length);
|
||||
|
||||
for (int i = 0; i < regions.Length; i++)
|
||||
{
|
||||
string server = servers[i];
|
||||
if (PortToPingOverride != 0)
|
||||
{
|
||||
server = LoadBalancingClient.ReplacePortWithAlternative(servers[i], PortToPingOverride);
|
||||
}
|
||||
|
||||
Region tmp = new Region(regions[i], server);
|
||||
if (string.IsNullOrEmpty(tmp.Code))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.EnabledRegions.Add(tmp);
|
||||
}
|
||||
|
||||
Array.Sort(regions);
|
||||
this.availableRegionCodes = string.Join(",", regions);
|
||||
}
|
||||
|
||||
private List<RegionPinger> pingerList = new List<RegionPinger>();
|
||||
private Action<RegionHandler> onCompleteCall;
|
||||
private int previousPing;
|
||||
public bool IsPinging { get; private set; }
|
||||
private string previousSummaryProvided;
|
||||
|
||||
protected internal static ushort PortToPingOverride;
|
||||
|
||||
|
||||
public RegionHandler(ushort masterServerPortOverride = 0)
|
||||
{
|
||||
PortToPingOverride = masterServerPortOverride;
|
||||
}
|
||||
|
||||
|
||||
public bool PingMinimumOfRegions(Action<RegionHandler> onCompleteCallback, string previousSummary)
|
||||
{
|
||||
if (this.EnabledRegions == null || this.EnabledRegions.Count == 0)
|
||||
{
|
||||
//TODO: log error
|
||||
//Debug.LogError("No regions available. Maybe all got filtered out or the AppId is not correctly configured.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.IsPinging)
|
||||
{
|
||||
//TODO: log warning
|
||||
//Debug.LogWarning("PingMinimumOfRegions() skipped, because this RegionHandler is already pinging some regions.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.IsPinging = true;
|
||||
this.onCompleteCall = onCompleteCallback;
|
||||
this.previousSummaryProvided = previousSummary;
|
||||
|
||||
if (string.IsNullOrEmpty(previousSummary))
|
||||
{
|
||||
return this.PingEnabledRegions();
|
||||
}
|
||||
|
||||
string[] values = previousSummary.Split(';');
|
||||
if (values.Length < 3)
|
||||
{
|
||||
return this.PingEnabledRegions();
|
||||
}
|
||||
|
||||
int prevBestRegionPing;
|
||||
bool secondValueIsInt = Int32.TryParse(values[1], out prevBestRegionPing);
|
||||
if (!secondValueIsInt)
|
||||
{
|
||||
return this.PingEnabledRegions();
|
||||
}
|
||||
|
||||
string prevBestRegionCode = values[0];
|
||||
string prevAvailableRegionCodes = values[2];
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(prevBestRegionCode))
|
||||
{
|
||||
return this.PingEnabledRegions();
|
||||
}
|
||||
if (string.IsNullOrEmpty(prevAvailableRegionCodes))
|
||||
{
|
||||
return this.PingEnabledRegions();
|
||||
}
|
||||
if (!this.availableRegionCodes.Equals(prevAvailableRegionCodes) || !this.availableRegionCodes.Contains(prevBestRegionCode))
|
||||
{
|
||||
return this.PingEnabledRegions();
|
||||
}
|
||||
if (prevBestRegionPing >= RegionPinger.PingWhenFailed)
|
||||
{
|
||||
return this.PingEnabledRegions();
|
||||
}
|
||||
|
||||
// let's check only the preferred region to detect if it's still "good enough"
|
||||
this.previousPing = prevBestRegionPing;
|
||||
|
||||
|
||||
Region preferred = this.EnabledRegions.Find(r => r.Code.Equals(prevBestRegionCode));
|
||||
RegionPinger singlePinger = new RegionPinger(preferred, this.OnPreferredRegionPinged);
|
||||
|
||||
lock (this.pingerList)
|
||||
{
|
||||
this.pingerList.Add(singlePinger);
|
||||
}
|
||||
|
||||
singlePinger.Start();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnPreferredRegionPinged(Region preferredRegion)
|
||||
{
|
||||
if (preferredRegion.Ping > this.previousPing * 1.50f)
|
||||
{
|
||||
this.PingEnabledRegions();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.IsPinging = false;
|
||||
this.onCompleteCall(this);
|
||||
#if PING_VIA_COROUTINE
|
||||
MonoBehaviourEmpty.SelfDestroy();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool PingEnabledRegions()
|
||||
{
|
||||
if (this.EnabledRegions == null || this.EnabledRegions.Count == 0)
|
||||
{
|
||||
//TODO: log
|
||||
//Debug.LogError("No regions available. Maybe all got filtered out or the AppId is not correctly configured.");
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (this.pingerList)
|
||||
{
|
||||
this.pingerList.Clear();
|
||||
|
||||
foreach (Region region in this.EnabledRegions)
|
||||
{
|
||||
RegionPinger rp = new RegionPinger(region, this.OnRegionDone);
|
||||
this.pingerList.Add(rp);
|
||||
rp.Start(); // TODO: check return value
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnRegionDone(Region region)
|
||||
{
|
||||
lock (this.pingerList)
|
||||
{
|
||||
if (this.IsPinging == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.bestRegionCache = null;
|
||||
foreach (RegionPinger pinger in this.pingerList)
|
||||
{
|
||||
if (!pinger.Done)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.IsPinging = false;
|
||||
}
|
||||
|
||||
this.onCompleteCall(this);
|
||||
#if PING_VIA_COROUTINE
|
||||
MonoBehaviourEmpty.SelfDestroy();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public class RegionPinger
|
||||
{
|
||||
public static int Attempts = 5;
|
||||
public static bool IgnoreInitialAttempt = true;
|
||||
public static int MaxMilliseconsPerPing = 800; // enter a value you're sure some server can beat (have a lower rtt)
|
||||
public static int PingWhenFailed = Attempts * MaxMilliseconsPerPing;
|
||||
|
||||
private Region region;
|
||||
private string regionAddress;
|
||||
public int CurrentAttempt = 0;
|
||||
|
||||
public bool Done { get; private set; }
|
||||
private Action<Region> onDoneCall;
|
||||
|
||||
private PhotonPing ping;
|
||||
|
||||
private List<int> rttResults;
|
||||
|
||||
public RegionPinger(Region region, Action<Region> onDoneCallback)
|
||||
{
|
||||
this.region = region;
|
||||
this.region.Ping = PingWhenFailed;
|
||||
this.Done = false;
|
||||
this.onDoneCall = onDoneCallback;
|
||||
}
|
||||
|
||||
/// <summary>Selects the best fitting ping implementation or uses the one set in RegionHandler.PingImplementation.</summary>
|
||||
/// <returns>PhotonPing instance to use.</returns>
|
||||
private PhotonPing GetPingImplementation()
|
||||
{
|
||||
PhotonPing ping = null;
|
||||
|
||||
// using each type explicitly in the conditional code, makes sure Unity doesn't strip the class / constructor.
|
||||
|
||||
#if !UNITY_EDITOR && NETFX_CORE
|
||||
if (RegionHandler.PingImplementation == null || RegionHandler.PingImplementation == typeof(PingWindowsStore))
|
||||
{
|
||||
ping = new PingWindowsStore();
|
||||
}
|
||||
#elif NATIVE_SOCKETS || NO_SOCKET
|
||||
if (RegionHandler.PingImplementation == null || RegionHandler.PingImplementation == typeof(PingNativeDynamic))
|
||||
{
|
||||
ping = new PingNativeDynamic();
|
||||
}
|
||||
#elif UNITY_WEBGL
|
||||
if (RegionHandler.PingImplementation == null || RegionHandler.PingImplementation == typeof(PingHttp))
|
||||
{
|
||||
ping = new PingHttp();
|
||||
}
|
||||
#else
|
||||
if (RegionHandler.PingImplementation == null || RegionHandler.PingImplementation == typeof(PingMono))
|
||||
{
|
||||
ping = new PingMono();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ping == null)
|
||||
{
|
||||
if (RegionHandler.PingImplementation != null)
|
||||
{
|
||||
ping = (PhotonPing)Activator.CreateInstance(RegionHandler.PingImplementation);
|
||||
}
|
||||
}
|
||||
|
||||
return ping;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Starts the ping routine for the assigned region.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Pinging runs in a ThreadPool worker item or (if needed) in a Thread.
|
||||
/// WebGL runs pinging on the Main Thread as coroutine.
|
||||
/// </remarks>
|
||||
/// <returns>Always true.</returns>
|
||||
public bool Start()
|
||||
{
|
||||
// all addresses for Photon region servers will contain a :port ending. this needs to be removed first.
|
||||
// PhotonPing.StartPing() requires a plain (IP) address without port or protocol-prefix (on all but Windows 8.1 and WebGL platforms).
|
||||
string address = this.region.HostAndPort;
|
||||
int indexOfColon = address.LastIndexOf(':');
|
||||
if (indexOfColon > 1)
|
||||
{
|
||||
address = address.Substring(0, indexOfColon);
|
||||
}
|
||||
this.regionAddress = ResolveHost(address);
|
||||
|
||||
|
||||
this.ping = this.GetPingImplementation();
|
||||
|
||||
|
||||
this.Done = false;
|
||||
this.CurrentAttempt = 0;
|
||||
this.rttResults = new List<int>(Attempts);
|
||||
|
||||
|
||||
#if PING_VIA_COROUTINE
|
||||
MonoBehaviourEmpty.Instance.StartCoroutine(this.RegionPingCoroutine());
|
||||
#else
|
||||
bool queued = false;
|
||||
#if !NETFX_CORE
|
||||
try
|
||||
{
|
||||
queued = ThreadPool.QueueUserWorkItem(this.RegionPingPooled);
|
||||
}
|
||||
catch
|
||||
{
|
||||
queued = false;
|
||||
}
|
||||
#endif
|
||||
if (!queued)
|
||||
{
|
||||
SupportClass.StartBackgroundCalls(this.RegionPingThreaded, 0, "RegionPing_" + this.region.Code + "_" + this.region.Cluster);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// wraps RegionPingThreaded() to get the signature compatible with ThreadPool.QueueUserWorkItem
|
||||
protected internal void RegionPingPooled(object context)
|
||||
{
|
||||
this.RegionPingThreaded();
|
||||
}
|
||||
|
||||
protected internal bool RegionPingThreaded()
|
||||
{
|
||||
this.region.Ping = PingWhenFailed;
|
||||
|
||||
float rttSum = 0.0f;
|
||||
int replyCount = 0;
|
||||
|
||||
|
||||
Stopwatch sw = new Stopwatch();
|
||||
for (this.CurrentAttempt = 0; this.CurrentAttempt < Attempts; this.CurrentAttempt++)
|
||||
{
|
||||
bool overtime = false;
|
||||
sw.Reset();
|
||||
sw.Start();
|
||||
|
||||
try
|
||||
{
|
||||
this.ping.StartPing(this.regionAddress);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("RegionPinger.RegionPingThreaded() catched an exception for ping.StartPing(). Exception: " + e + " Source: " + e.Source + " Message: " + e.Message);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
while (!this.ping.Done())
|
||||
{
|
||||
if (sw.ElapsedMilliseconds >= MaxMilliseconsPerPing)
|
||||
{
|
||||
overtime = true;
|
||||
break;
|
||||
}
|
||||
#if !NETFX_CORE
|
||||
System.Threading.Thread.Sleep(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
sw.Stop();
|
||||
int rtt = (int)sw.ElapsedMilliseconds;
|
||||
this.rttResults.Add(rtt);
|
||||
|
||||
if (IgnoreInitialAttempt && this.CurrentAttempt == 0)
|
||||
{
|
||||
// do nothing.
|
||||
}
|
||||
else if (this.ping.Successful && !overtime)
|
||||
{
|
||||
rttSum += rtt;
|
||||
replyCount++;
|
||||
this.region.Ping = (int)((rttSum) / replyCount);
|
||||
}
|
||||
|
||||
#if !NETFX_CORE
|
||||
System.Threading.Thread.Sleep(10);
|
||||
#endif
|
||||
}
|
||||
|
||||
//Debug.Log("Done: "+ this.region.Code);
|
||||
this.Done = true;
|
||||
this.ping.Dispose();
|
||||
|
||||
this.onDoneCall(this.region);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#if SUPPORTED_UNITY
|
||||
/// <remarks>
|
||||
/// Affected by frame-rate of app, as this Coroutine checks the socket for a result once per frame.
|
||||
/// </remarks>
|
||||
protected internal IEnumerator RegionPingCoroutine()
|
||||
{
|
||||
this.region.Ping = PingWhenFailed;
|
||||
|
||||
float rttSum = 0.0f;
|
||||
int replyCount = 0;
|
||||
|
||||
|
||||
Stopwatch sw = new Stopwatch();
|
||||
for (this.CurrentAttempt = 0; this.CurrentAttempt < Attempts; this.CurrentAttempt++)
|
||||
{
|
||||
bool overtime = false;
|
||||
sw.Reset();
|
||||
sw.Start();
|
||||
|
||||
try
|
||||
{
|
||||
this.ping.StartPing(this.regionAddress);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log("catched: " + e);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
while (!this.ping.Done())
|
||||
{
|
||||
if (sw.ElapsedMilliseconds >= MaxMilliseconsPerPing)
|
||||
{
|
||||
overtime = true;
|
||||
break;
|
||||
}
|
||||
yield return 0; // keep this loop tight, to avoid adding local lag to rtt.
|
||||
}
|
||||
|
||||
|
||||
sw.Stop();
|
||||
int rtt = (int)sw.ElapsedMilliseconds;
|
||||
this.rttResults.Add(rtt);
|
||||
|
||||
|
||||
if (IgnoreInitialAttempt && this.CurrentAttempt == 0)
|
||||
{
|
||||
// do nothing.
|
||||
}
|
||||
else if (this.ping.Successful && !overtime)
|
||||
{
|
||||
rttSum += rtt;
|
||||
replyCount++;
|
||||
this.region.Ping = (int)((rttSum) / replyCount);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
}
|
||||
|
||||
|
||||
//Debug.Log("Done: "+ this.region.Code);
|
||||
this.Done = true;
|
||||
this.ping.Dispose();
|
||||
this.onDoneCall(this.region);
|
||||
yield return null;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
public string GetResults()
|
||||
{
|
||||
return string.Format("{0}: {1} ({2})", this.region.Code, this.region.Ping, this.rttResults.ToStringFull());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to resolve a hostname into an IP string or returns empty string if that fails.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To be compatible with most platforms, the address family is checked like this:<br/>
|
||||
/// if (ipAddress.AddressFamily.ToString().Contains("6")) // ipv6...
|
||||
/// </remarks>
|
||||
/// <param name="hostName">Hostname to resolve.</param>
|
||||
/// <returns>IP string or empty string if resolution fails</returns>
|
||||
public static string ResolveHost(string hostName)
|
||||
{
|
||||
|
||||
if (hostName.StartsWith("wss://"))
|
||||
{
|
||||
hostName = hostName.Substring(6);
|
||||
}
|
||||
if (hostName.StartsWith("ws://"))
|
||||
{
|
||||
hostName = hostName.Substring(5);
|
||||
}
|
||||
|
||||
string ipv4Address = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
#if UNITY_WSA || NETFX_CORE || UNITY_WEBGL
|
||||
return hostName;
|
||||
#else
|
||||
|
||||
IPAddress[] address = Dns.GetHostAddresses(hostName);
|
||||
if (address.Length == 1)
|
||||
{
|
||||
return address[0].ToString();
|
||||
}
|
||||
|
||||
// if we got more addresses, try to pick a IPv6 one
|
||||
// checking ipAddress.ToString() means we don't have to import System.Net.Sockets, which is not available on some platforms (Metro)
|
||||
for (int index = 0; index < address.Length; index++)
|
||||
{
|
||||
IPAddress ipAddress = address[index];
|
||||
if (ipAddress != null)
|
||||
{
|
||||
if (ipAddress.ToString().Contains(":"))
|
||||
{
|
||||
return ipAddress.ToString();
|
||||
}
|
||||
if (string.IsNullOrEmpty(ipv4Address))
|
||||
{
|
||||
ipv4Address = address.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("RegionPinger.ResolveHost() catched an exception for Dns.GetHostAddresses(). Exception: " + e + " Source: " + e.Source + " Message: " + e.Message);
|
||||
}
|
||||
|
||||
return ipv4Address;
|
||||
}
|
||||
}
|
||||
|
||||
#if PING_VIA_COROUTINE
|
||||
internal class MonoBehaviourEmpty : MonoBehaviour
|
||||
{
|
||||
private static bool instanceSet; // to avoid instance null check which may be incorrect
|
||||
private static MonoBehaviourEmpty instance;
|
||||
|
||||
public static MonoBehaviourEmpty Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instanceSet)
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
GameObject go = new GameObject();
|
||||
DontDestroyOnLoad(go);
|
||||
go.name = "RegionPinger";
|
||||
instance = go.AddComponent<MonoBehaviourEmpty>();
|
||||
instanceSet = true;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SelfDestroy()
|
||||
{
|
||||
if (instanceSet)
|
||||
{
|
||||
instanceSet = false;
|
||||
Destroy(instance.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
7
Assets/Photon/PhotonRealtime/Code/RegionHandler.cs.meta
Normal file
7
Assets/Photon/PhotonRealtime/Code/RegionHandler.cs.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 207807222df026f40ac3688a3a051e38
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
630
Assets/Photon/PhotonRealtime/Code/Room.cs
Normal file
630
Assets/Photon/PhotonRealtime/Code/Room.cs
Normal file
@ -0,0 +1,630 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="Room.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// The Room class resembles the properties known about the room in which
|
||||
// a game/match happens.
|
||||
// </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 || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a room a client joins/joined.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Contains a list of current players, their properties and those of this room, too.
|
||||
/// A room instance has a number of "well known" properties like IsOpen, MaxPlayers which can be changed.
|
||||
/// Your own, custom properties can be set via SetCustomProperties() while being in the room.
|
||||
///
|
||||
/// Typically, this class should be extended by a game-specific implementation with logic and extra features.
|
||||
/// </remarks>
|
||||
public class Room : RoomInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// A reference to the LoadBalancingClient which is currently keeping the connection and state.
|
||||
/// </summary>
|
||||
public LoadBalancingClient LoadBalancingClient { get; set; }
|
||||
|
||||
/// <summary>The name of a room. Unique identifier (per region and virtual appid) for a room/match.</summary>
|
||||
/// <remarks>The name can't be changed once it's set by the server.</remarks>
|
||||
public new string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
internal set
|
||||
{
|
||||
this.name = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool isOffline;
|
||||
|
||||
public bool IsOffline
|
||||
{
|
||||
get
|
||||
{
|
||||
return isOffline;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
isOffline = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines if the room can be joined.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not affect listing in a lobby but joining the room will fail if not open.
|
||||
/// If not open, the room is excluded from random matchmaking.
|
||||
/// Due to racing conditions, found matches might become closed while users are trying to join.
|
||||
/// Simply re-connect to master and find another.
|
||||
/// Use property "IsVisible" to not list the room.
|
||||
///
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public new bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isOpen;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != this.isOpen)
|
||||
{
|
||||
if (!this.isOffline)
|
||||
{
|
||||
this.LoadBalancingClient.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.IsOpen, value } });
|
||||
}
|
||||
}
|
||||
|
||||
this.isOpen = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines if the room is listed in its lobby.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Rooms can be created invisible, or changed to invisible.
|
||||
/// To change if a room can be joined, use property: open.
|
||||
///
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public new bool IsVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isVisible;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != this.isVisible)
|
||||
{
|
||||
if (!this.isOffline)
|
||||
{
|
||||
this.LoadBalancingClient.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.IsVisible, value } });
|
||||
}
|
||||
}
|
||||
|
||||
this.isVisible = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a limit of players to this room. This property is synced and shown in lobby, too.
|
||||
/// If the room is full (players count == maxplayers), joining this room will fail.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public new byte MaxPlayers
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.maxPlayers;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != this.maxPlayers)
|
||||
{
|
||||
if (!this.isOffline)
|
||||
{
|
||||
this.LoadBalancingClient.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.MaxPlayers, value } });
|
||||
}
|
||||
}
|
||||
|
||||
this.maxPlayers = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The count of players in this Room (using this.Players.Count).</summary>
|
||||
public new byte PlayerCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Players == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (byte)this.Players.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>While inside a Room, this is the list of players who are also in that room.</summary>
|
||||
private Dictionary<int, Player> players = new Dictionary<int, Player>();
|
||||
|
||||
/// <summary>While inside a Room, this is the list of players who are also in that room.</summary>
|
||||
public Dictionary<int, Player> Players
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.players;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
this.players = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of users who are expected to join this room. In matchmaking, Photon blocks a slot for each of these UserIDs out of the MaxPlayers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages.
|
||||
/// Define expected players in the methods: <see cref="LoadBalancingClient.OpCreateRoom"/>, <see cref="LoadBalancingClient.OpJoinRoom"/> and <see cref="LoadBalancingClient.OpJoinRandomRoom"/>.
|
||||
/// </remarks>
|
||||
public string[] ExpectedUsers
|
||||
{
|
||||
get { return this.expectedUsers; }
|
||||
}
|
||||
|
||||
/// <summary>Player Time To Live. How long any player can be inactive (due to disconnect or leave) before the user gets removed from the playerlist (freeing a slot).</summary>
|
||||
public int PlayerTtl
|
||||
{
|
||||
get { return this.playerTtl; }
|
||||
|
||||
set
|
||||
{
|
||||
if (value != this.playerTtl)
|
||||
{
|
||||
if (!this.isOffline)
|
||||
{
|
||||
this.LoadBalancingClient.OpSetPropertyOfRoom(GamePropertyKey.PlayerTtl, value); // TODO: implement Offline Mode
|
||||
}
|
||||
}
|
||||
|
||||
this.playerTtl = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Room Time To Live. How long a room stays available (and in server-memory), after the last player becomes inactive. After this time, the room gets persisted or destroyed.</summary>
|
||||
public int EmptyRoomTtl
|
||||
{
|
||||
get { return this.emptyRoomTtl; }
|
||||
|
||||
set
|
||||
{
|
||||
if (value != this.emptyRoomTtl)
|
||||
{
|
||||
if (!this.isOffline)
|
||||
{
|
||||
this.LoadBalancingClient.OpSetPropertyOfRoom(GamePropertyKey.EmptyRoomTtl, value); // TODO: implement Offline Mode
|
||||
}
|
||||
}
|
||||
|
||||
this.emptyRoomTtl = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ID (actorNumber, actorNumber) of the player who's the master of this Room.
|
||||
/// Note: This changes when the current master leaves the room.
|
||||
/// </summary>
|
||||
public int MasterClientId { get { return this.masterClientId; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of custom properties that are in the RoomInfo of the Lobby.
|
||||
/// This list is defined when creating the room and can't be changed afterwards. Compare: LoadBalancingClient.OpCreateRoom()
|
||||
/// </summary>
|
||||
/// <remarks>You could name properties that are not set from the beginning. Those will be synced with the lobby when added later on.</remarks>
|
||||
public string[] PropertiesListedInLobby
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.propertiesListedInLobby;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
this.propertiesListedInLobby = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this room cleans up the event cache when a player (actor) leaves.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This affects which events joining players get.
|
||||
///
|
||||
/// Set in room creation via RoomOptions.CleanupCacheOnLeave.
|
||||
///
|
||||
/// Within PUN, auto cleanup of events means that cached RPCs and instantiated networked objects are deleted from the room.
|
||||
/// </remarks>
|
||||
public bool AutoCleanUp
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.autoCleanUp;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Define if the client who calls SetProperties should receive the properties update event or not. </summary>
|
||||
public bool BroadcastPropertiesChangeToAll { get; private set; }
|
||||
/// <summary>Define if Join and Leave events should not be sent to clients in the room. </summary>
|
||||
public bool SuppressRoomEvents { get; private set; }
|
||||
/// <summary>Extends SuppressRoomEvents: Define if Join and Leave events but also the actors' list and their respective properties should not be sent to clients. </summary>
|
||||
public bool SuppressPlayerInfo { get; private set; }
|
||||
/// <summary>Define if UserIds of the players are broadcast in the room. Useful for FindFriends and reserving slots for expected users.</summary>
|
||||
public bool PublishUserId { get; private set; }
|
||||
/// <summary>Define if actor or room properties with null values are removed on the server or kept.</summary>
|
||||
public bool DeleteNullProperties { get; private set; }
|
||||
|
||||
#if SERVERSDK
|
||||
/// <summary>Define if rooms should have unique UserId per actor and that UserIds are used instead of actor number in rejoin.</summary>
|
||||
public bool CheckUserOnJoin { get; private set; }
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>Creates a Room (representation) with given name and properties and the "listing options" as provided by parameters.</summary>
|
||||
/// <param name="roomName">Name of the room (can be null until it's actually created on server).</param>
|
||||
/// <param name="options">Room options.</param>
|
||||
public Room(string roomName, RoomOptions options, bool isOffline = false) : base(roomName, options != null ? options.CustomRoomProperties : null)
|
||||
{
|
||||
// base() sets name and (custom)properties. here we set "well known" properties
|
||||
if (options != null)
|
||||
{
|
||||
this.isVisible = options.IsVisible;
|
||||
this.isOpen = options.IsOpen;
|
||||
this.maxPlayers = options.MaxPlayers;
|
||||
this.propertiesListedInLobby = options.CustomRoomPropertiesForLobby;
|
||||
//this.playerTtl = options.PlayerTtl; // set via well known properties
|
||||
//this.emptyRoomTtl = options.EmptyRoomTtl; // set via well known properties
|
||||
}
|
||||
|
||||
this.isOffline = isOffline;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Read (received) room option flags into related bool parameters.</summary>
|
||||
/// <remarks>This is for internal use. The operation response for join and create room operations is read this way.</remarks>
|
||||
/// <param name="roomFlags"></param>
|
||||
internal void InternalCacheRoomFlags(int roomFlags)
|
||||
{
|
||||
this.BroadcastPropertiesChangeToAll = (roomFlags & (int)RoomOptionBit.BroadcastPropsChangeToAll) != 0;
|
||||
this.SuppressRoomEvents = (roomFlags & (int)RoomOptionBit.SuppressRoomEvents) != 0;
|
||||
this.SuppressPlayerInfo = (roomFlags & (int)RoomOptionBit.SuppressPlayerInfo) != 0;
|
||||
this.PublishUserId = (roomFlags & (int)RoomOptionBit.PublishUserId) != 0;
|
||||
this.DeleteNullProperties = (roomFlags & (int)RoomOptionBit.DeleteNullProps) != 0;
|
||||
#if SERVERSDK
|
||||
this.CheckUserOnJoin = (roomFlags & (int)RoomOptionBit.CheckUserOnJoin) != 0;
|
||||
#endif
|
||||
this.autoCleanUp = (roomFlags & (int)RoomOptionBit.DeleteCacheOnLeave) != 0;
|
||||
}
|
||||
|
||||
protected internal override void InternalCacheProperties(Hashtable propertiesToCache)
|
||||
{
|
||||
int oldMasterId = this.masterClientId;
|
||||
|
||||
base.InternalCacheProperties(propertiesToCache); // important: updating the properties fields has no way to do callbacks on change
|
||||
|
||||
if (oldMasterId != 0 && this.masterClientId != oldMasterId)
|
||||
{
|
||||
this.LoadBalancingClient.InRoomCallbackTargets.OnMasterClientSwitched(this.GetPlayer(this.masterClientId));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates and synchronizes this Room'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 that changes.</param>
|
||||
/// <param name="expectedProperties">Provide some keys/values to use as condition for setting the new values. Client must be in room.</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.
|
||||
/// Otherwise, returns if this operation could be sent to the server.
|
||||
/// </returns>
|
||||
public virtual bool SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedProperties = null, WebFlags webFlags = null)
|
||||
{
|
||||
if (propertiesToSet == null || propertiesToSet.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable;
|
||||
|
||||
if (this.isOffline)
|
||||
{
|
||||
if (customProps.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Merge and delete values.
|
||||
this.CustomProperties.Merge(customProps);
|
||||
this.CustomProperties.StripKeysWithNullValues();
|
||||
|
||||
// invoking callbacks
|
||||
this.LoadBalancingClient.InRoomCallbackTargets.OnRoomPropertiesUpdate(propertiesToSet);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// send (sync) these new values if in online room
|
||||
return this.LoadBalancingClient.OpSetPropertiesOfRoom(customProps, expectedProperties, webFlags);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables you to define the properties available in the lobby if not all properties are needed to pick a room.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Limit the amount of properties sent to users in the lobby to improve speed and stability.
|
||||
/// </remarks>
|
||||
/// <param name="lobbyProps">An array of custom room property names to forward to the lobby.</param>
|
||||
/// <returns>If the operation could be sent to the server.</returns>
|
||||
public bool SetPropertiesListedInLobby(string[] lobbyProps)
|
||||
{
|
||||
if (this.isOffline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Hashtable customProps = new Hashtable();
|
||||
customProps[GamePropertyKey.PropsListedInLobby] = lobbyProps;
|
||||
return this.LoadBalancingClient.OpSetPropertiesOfRoom(customProps);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes a player from this room's Players Dictionary.
|
||||
/// This is internally used by the LoadBalancing API. There is usually no need to remove players yourself.
|
||||
/// This is not a way to "kick" players.
|
||||
/// </summary>
|
||||
protected internal virtual void RemovePlayer(Player player)
|
||||
{
|
||||
this.Players.Remove(player.ActorNumber);
|
||||
player.RoomReference = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a player from this room's Players Dictionary.
|
||||
/// </summary>
|
||||
protected internal virtual void RemovePlayer(int id)
|
||||
{
|
||||
this.RemovePlayer(this.GetPlayer(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks the server to assign another player as Master Client of your current room.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// RaiseEvent has the option to send messages only to the Master Client of a room.
|
||||
/// SetMasterClient affects which client gets those messages.
|
||||
///
|
||||
/// This method calls an operation on the server to set a new Master Client, which takes a roundtrip.
|
||||
/// In case of success, this client and the others get the new Master Client from the server.
|
||||
///
|
||||
/// SetMasterClient tells the server which current Master Client should be replaced with the new one.
|
||||
/// It will fail, if anything switches the Master Client moments earlier. There is no callback for this
|
||||
/// error. All clients should get the new Master Client assigned by the server anyways.
|
||||
///
|
||||
/// See also: MasterClientId
|
||||
/// </remarks>
|
||||
/// <param name="masterClientPlayer">The player to become the next Master Client.</param>
|
||||
/// <returns>False when this operation couldn't be done currently. Requires a v4 Photon Server.</returns>
|
||||
public bool SetMasterClient(Player masterClientPlayer)
|
||||
{
|
||||
if (this.isOffline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Hashtable newProps = new Hashtable() { { GamePropertyKey.MasterClientId, masterClientPlayer.ActorNumber } };
|
||||
Hashtable prevProps = new Hashtable() { { GamePropertyKey.MasterClientId, this.MasterClientId } };
|
||||
return this.LoadBalancingClient.OpSetPropertiesOfRoom(newProps, prevProps);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player is in the room's list already and calls StorePlayer() if not.
|
||||
/// </summary>
|
||||
/// <param name="player">The new player - identified by ID.</param>
|
||||
/// <returns>False if the player could not be added (cause it was in the list already).</returns>
|
||||
public virtual bool AddPlayer(Player player)
|
||||
{
|
||||
if (!this.Players.ContainsKey(player.ActorNumber))
|
||||
{
|
||||
this.StorePlayer(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a player reference in the Players dictionary (no matter if it existed before or not).
|
||||
/// </summary>
|
||||
/// <param name="player">The Player instance to insert into the room.</param>
|
||||
public virtual Player StorePlayer(Player player)
|
||||
{
|
||||
this.Players[player.ActorNumber] = player;
|
||||
player.RoomReference = this;
|
||||
|
||||
//// while initializing the room, the players are not guaranteed to be added in-order
|
||||
//if (this.MasterClientId == 0 || player.ActorNumber < this.MasterClientId)
|
||||
//{
|
||||
// this.masterClientId = player.ActorNumber;
|
||||
//}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the player with given actorNumber (a.k.a. ID).
|
||||
/// Only useful when in a Room, as IDs are only valid per Room.
|
||||
/// </summary>
|
||||
/// <param name="id">ID to look for.</param>
|
||||
/// <param name="findMaster">If true, the Master Client is returned for ID == 0.</param>
|
||||
/// <returns>The player with the ID or null.</returns>
|
||||
public virtual Player GetPlayer(int id, bool findMaster = false)
|
||||
{
|
||||
int idToFind = (findMaster && id == 0) ? this.MasterClientId : id;
|
||||
|
||||
Player result = null;
|
||||
this.Players.TryGetValue(idToFind, out result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to remove all current expected users from the server's Slot Reservation list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this operation can conflict with new/other users joining. They might be
|
||||
/// adding users to the list of expected users before or after this client called ClearExpectedUsers.
|
||||
///
|
||||
/// This room's expectedUsers value will update, when the server sends a successful update.
|
||||
///
|
||||
/// Internals: This methods wraps up setting the ExpectedUsers property of a room.
|
||||
/// </remarks>
|
||||
/// <returns>If the operation could be sent to the server.</returns>
|
||||
public bool ClearExpectedUsers()
|
||||
{
|
||||
if (this.ExpectedUsers == null || this.ExpectedUsers.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return this.SetExpectedUsers(new string[0], this.ExpectedUsers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to update the expected users from the server's Slot Reservation list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this operation can conflict with new/other users joining. They might be
|
||||
/// adding users to the list of expected users before or after this client called SetExpectedUsers.
|
||||
///
|
||||
/// This room's expectedUsers value will update, when the server sends a successful update.
|
||||
///
|
||||
/// Internals: This methods wraps up setting the ExpectedUsers property of a room.
|
||||
/// </remarks>
|
||||
/// <param name="newExpectedUsers">The new array of UserIDs to be reserved in the room.</param>
|
||||
/// <returns>If the operation could be sent to the server.</returns>
|
||||
public bool SetExpectedUsers(string[] newExpectedUsers)
|
||||
{
|
||||
if (newExpectedUsers == null || newExpectedUsers.Length == 0)
|
||||
{
|
||||
this.LoadBalancingClient.DebugReturn(DebugLevel.ERROR, "newExpectedUsers array is null or empty, call Room.ClearExpectedUsers() instead if this is what you want.");
|
||||
return false;
|
||||
}
|
||||
return this.SetExpectedUsers(newExpectedUsers, this.ExpectedUsers);
|
||||
}
|
||||
|
||||
private bool SetExpectedUsers(string[] newExpectedUsers, string[] oldExpectedUsers)
|
||||
{
|
||||
if (this.isOffline)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Hashtable gameProperties = new Hashtable(1);
|
||||
gameProperties.Add(GamePropertyKey.ExpectedUsers, newExpectedUsers);
|
||||
Hashtable expectedProperties = null;
|
||||
if (oldExpectedUsers != null)
|
||||
{
|
||||
expectedProperties = new Hashtable(1);
|
||||
expectedProperties.Add(GamePropertyKey.ExpectedUsers, oldExpectedUsers);
|
||||
}
|
||||
return this.LoadBalancingClient.OpSetPropertiesOfRoom(gameProperties, expectedProperties);
|
||||
}
|
||||
|
||||
/// <summary>Returns a summary of this Room instance as string.</summary>
|
||||
/// <returns>Summary of this Room instance.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Room: '{0}' {1},{2} {4}/{3} players.", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount);
|
||||
}
|
||||
|
||||
/// <summary>Returns a summary of this Room instance as longer string, including Custom Properties.</summary>
|
||||
/// <returns>Summary of this Room instance.</returns>
|
||||
public new string ToStringFull()
|
||||
{
|
||||
return string.Format("Room: '{0}' {1},{2} {4}/{3} players.\ncustomProps: {5}", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount, this.CustomProperties.ToStringFull());
|
||||
}
|
||||
}
|
||||
}
|
12
Assets/Photon/PhotonRealtime/Code/Room.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/Room.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17568a7a5552c09428dd48e73548b8b8
|
||||
labels:
|
||||
- ExitGames
|
||||
- PUN
|
||||
- Photon
|
||||
- Networking
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
274
Assets/Photon/PhotonRealtime/Code/RoomInfo.cs
Normal file
274
Assets/Photon/PhotonRealtime/Code/RoomInfo.cs
Normal file
@ -0,0 +1,274 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="RoomInfo.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This class resembles info about available rooms, as sent by the Master
|
||||
// server's lobby. Consider all values as readonly.
|
||||
// </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.Collections;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
#if SUPPORTED_UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A simplified room with just the info required to list and join, used for the room listing in the lobby.
|
||||
/// The properties are not settable (IsOpen, MaxPlayers, etc).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class resembles info about available rooms, as sent by the Master server's lobby.
|
||||
/// Consider all values as readonly. None are synced (only updated by events by server).
|
||||
/// </remarks>
|
||||
public class RoomInfo
|
||||
{
|
||||
/// <summary>Used in lobby, to mark rooms that are no longer listed (for being full, closed or hidden).</summary>
|
||||
public bool RemovedFromList;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
private Hashtable customProperties = new Hashtable();
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected byte maxPlayers = 0;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected int emptyRoomTtl = 0;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected int playerTtl = 0;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected string[] expectedUsers;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected bool isOpen = true;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected bool isVisible = true;
|
||||
|
||||
/// <summary>Backing field for property. False unless the GameProperty is set to true (else it's not sent).</summary>
|
||||
protected bool autoCleanUp = true;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected string name;
|
||||
|
||||
/// <summary>Backing field for master client id (actorNumber). defined by server in room props and ev leave.</summary>
|
||||
public int masterClientId;
|
||||
|
||||
/// <summary>Backing field for property.</summary>
|
||||
protected string[] propertiesListedInLobby;
|
||||
|
||||
/// <summary>Read-only "cache" of custom properties of a room. Set via Room.SetCustomProperties (not available for RoomInfo class!).</summary>
|
||||
/// <remarks>All keys are string-typed and the values depend on the game/application.</remarks>
|
||||
/// <see cref="Room.SetCustomProperties"/>
|
||||
public Hashtable CustomProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.customProperties;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The name of a room. Unique identifier for a room/match (per AppId + game-Version).</summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Count of players currently in room. This property is overwritten by the Room class (used when you're in a Room).
|
||||
/// </summary>
|
||||
public int PlayerCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The limit of players for this room. This property is shown in lobby, too.
|
||||
/// If the room is full (players count == maxplayers), joining this room will fail.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public byte MaxPlayers
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.maxPlayers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines if the room can be joined.
|
||||
/// This does not affect listing in a lobby but joining the room will fail if not open.
|
||||
/// If not open, the room is excluded from random matchmaking.
|
||||
/// Due to racing conditions, found matches might become closed even while you join them.
|
||||
/// Simply re-connect to master and find another.
|
||||
/// Use property "IsVisible" to not list the room.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isOpen;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines if the room is listed in its lobby.
|
||||
/// Rooms can be created invisible, or changed to invisible.
|
||||
/// To change if a room can be joined, use property: open.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As part of RoomInfo this can't be set.
|
||||
/// As part of a Room (which the player joined), the setter will update the server and all clients.
|
||||
/// </remarks>
|
||||
public bool IsVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isVisible;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a RoomInfo to be used in room listings in lobby.
|
||||
/// </summary>
|
||||
/// <param name="roomName">Name of the room and unique ID at the same time.</param>
|
||||
/// <param name="roomProperties">Properties for this room.</param>
|
||||
protected internal RoomInfo(string roomName, Hashtable roomProperties)
|
||||
{
|
||||
this.InternalCacheProperties(roomProperties);
|
||||
|
||||
this.name = roomName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes RoomInfo comparable (by name).
|
||||
/// </summary>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
RoomInfo otherRoomInfo = other as RoomInfo;
|
||||
return (otherRoomInfo != null && this.Name.Equals(otherRoomInfo.name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accompanies Equals, using the name's HashCode as return.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.name.GetHashCode();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Returns most interesting room values as string.</summary>
|
||||
/// <returns>Summary of this RoomInfo instance.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Room: '{0}' {1},{2} {4}/{3} players.", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount);
|
||||
}
|
||||
|
||||
/// <summary>Returns most interesting room values as string, including custom properties.</summary>
|
||||
/// <returns>Summary of this RoomInfo instance.</returns>
|
||||
public string ToStringFull()
|
||||
{
|
||||
return string.Format("Room: '{0}' {1},{2} {4}/{3} players.\ncustomProps: {5}", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount, this.customProperties.ToStringFull());
|
||||
}
|
||||
|
||||
/// <summary>Copies "well known" properties to fields (IsVisible, etc) and caches the custom properties (string-keys only) in a local hashtable.</summary>
|
||||
/// <param name="propertiesToCache">New or updated properties to store in this RoomInfo.</param>
|
||||
protected internal virtual void InternalCacheProperties(Hashtable propertiesToCache)
|
||||
{
|
||||
if (propertiesToCache == null || propertiesToCache.Count == 0 || this.customProperties.Equals(propertiesToCache))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check of this game was removed from the list. in that case, we don't
|
||||
// need to read any further properties
|
||||
// list updates will remove this game from the game listing
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.Removed))
|
||||
{
|
||||
this.RemovedFromList = (bool)propertiesToCache[GamePropertyKey.Removed];
|
||||
if (this.RemovedFromList)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the "well known" properties of the room, if available
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.MaxPlayers))
|
||||
{
|
||||
this.maxPlayers = (byte)propertiesToCache[GamePropertyKey.MaxPlayers];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.IsOpen))
|
||||
{
|
||||
this.isOpen = (bool)propertiesToCache[GamePropertyKey.IsOpen];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.IsVisible))
|
||||
{
|
||||
this.isVisible = (bool)propertiesToCache[GamePropertyKey.IsVisible];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.PlayerCount))
|
||||
{
|
||||
this.PlayerCount = (int)((byte)propertiesToCache[GamePropertyKey.PlayerCount]);
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.CleanupCacheOnLeave))
|
||||
{
|
||||
this.autoCleanUp = (bool)propertiesToCache[GamePropertyKey.CleanupCacheOnLeave];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.MasterClientId))
|
||||
{
|
||||
this.masterClientId = (int)propertiesToCache[GamePropertyKey.MasterClientId];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey(GamePropertyKey.PropsListedInLobby))
|
||||
{
|
||||
this.propertiesListedInLobby = propertiesToCache[GamePropertyKey.PropsListedInLobby] as string[];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey((byte)GamePropertyKey.ExpectedUsers))
|
||||
{
|
||||
this.expectedUsers = (string[])propertiesToCache[GamePropertyKey.ExpectedUsers];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey((byte)GamePropertyKey.EmptyRoomTtl))
|
||||
{
|
||||
this.emptyRoomTtl = (int)propertiesToCache[GamePropertyKey.EmptyRoomTtl];
|
||||
}
|
||||
|
||||
if (propertiesToCache.ContainsKey((byte)GamePropertyKey.PlayerTtl))
|
||||
{
|
||||
this.playerTtl = (int)propertiesToCache[GamePropertyKey.PlayerTtl];
|
||||
}
|
||||
|
||||
// merge the custom properties (from your application) to the cache (only string-typed keys will be kept)
|
||||
this.customProperties.MergeStringKeys(propertiesToCache);
|
||||
this.customProperties.StripKeysWithNullValues();
|
||||
}
|
||||
}
|
||||
}
|
12
Assets/Photon/PhotonRealtime/Code/RoomInfo.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/RoomInfo.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71760b65ad7d5b842942c797a0366fa7
|
||||
labels:
|
||||
- ExitGames
|
||||
- PUN
|
||||
- Photon
|
||||
- Networking
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
434
Assets/Photon/PhotonRealtime/Code/SupportLogger.cs
Normal file
434
Assets/Photon/PhotonRealtime/Code/SupportLogger.cs
Normal file
@ -0,0 +1,434 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="SupportLogger.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Implements callbacks of the Realtime API to logs selected information
|
||||
// for support cases.
|
||||
// </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.Text;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Stopwatch = System.Diagnostics.Stopwatch;
|
||||
|
||||
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>
|
||||
/// Helper class to debug log basic information about Photon client and vital traffic statistics.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Set SupportLogger.Client for this to work.
|
||||
/// </remarks>
|
||||
#if SUPPORTED_UNITY
|
||||
[DisallowMultipleComponent]
|
||||
#if PUN_2_OR_NEWER || FUSION_UNITY
|
||||
[AddComponentMenu("")] // hide from Unity Menus and searches
|
||||
#endif
|
||||
public class SupportLogger : MonoBehaviour, IConnectionCallbacks , IMatchmakingCallbacks , IInRoomCallbacks, ILobbyCallbacks, IErrorInfoCallback
|
||||
#else
|
||||
public class SupportLogger : IConnectionCallbacks, IInRoomCallbacks, IMatchmakingCallbacks , ILobbyCallbacks
|
||||
#endif
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggle to enable or disable traffic statistics logging.
|
||||
/// </summary>
|
||||
public bool LogTrafficStats = true;
|
||||
private bool loggedStillOfflineMessage;
|
||||
|
||||
private LoadBalancingClient client;
|
||||
|
||||
private Stopwatch startStopwatch;
|
||||
|
||||
/// helps skip the initial OnApplicationPause call, which is not really of interest on start
|
||||
private bool initialOnApplicationPauseSkipped = false;
|
||||
|
||||
private int pingMax;
|
||||
private int pingMin;
|
||||
|
||||
/// <summary>
|
||||
/// Photon client to log information and statistics from.
|
||||
/// </summary>
|
||||
public LoadBalancingClient Client
|
||||
{
|
||||
get { return this.client; }
|
||||
set
|
||||
{
|
||||
if (this.client != value)
|
||||
{
|
||||
if (this.client != null)
|
||||
{
|
||||
this.client.RemoveCallbackTarget(this);
|
||||
}
|
||||
this.client = value;
|
||||
if (this.client != null)
|
||||
{
|
||||
this.client.AddCallbackTarget(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if SUPPORTED_UNITY
|
||||
protected void Start()
|
||||
{
|
||||
this.LogBasics();
|
||||
|
||||
if (this.startStopwatch == null)
|
||||
{
|
||||
this.startStopwatch = new Stopwatch();
|
||||
this.startStopwatch.Start();
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnDestroy()
|
||||
{
|
||||
this.Client = null; // will remove this SupportLogger as callback target
|
||||
}
|
||||
|
||||
protected void OnApplicationPause(bool pause)
|
||||
{
|
||||
if (!this.initialOnApplicationPauseSkipped)
|
||||
{
|
||||
this.initialOnApplicationPauseSkipped = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log(string.Format("{0} SupportLogger OnApplicationPause({1}). Client: {2}.", this.GetFormattedTimestamp(), pause, this.client == null ? "null" : this.client.State.ToString()));
|
||||
}
|
||||
|
||||
protected void OnApplicationQuit()
|
||||
{
|
||||
this.CancelInvoke();
|
||||
}
|
||||
#endif
|
||||
|
||||
public void StartLogStats()
|
||||
{
|
||||
#if SUPPORTED_UNITY
|
||||
this.InvokeRepeating("LogStats", 10, 10);
|
||||
#else
|
||||
Debug.Log("Not implemented for non-Unity projects.");
|
||||
#endif
|
||||
}
|
||||
|
||||
public void StopLogStats()
|
||||
{
|
||||
#if SUPPORTED_UNITY
|
||||
this.CancelInvoke("LogStats");
|
||||
#else
|
||||
Debug.Log("Not implemented for non-Unity projects.");
|
||||
#endif
|
||||
}
|
||||
|
||||
private void StartTrackValues()
|
||||
{
|
||||
#if SUPPORTED_UNITY
|
||||
this.InvokeRepeating("TrackValues", 0.5f, 0.5f);
|
||||
#else
|
||||
Debug.Log("Not implemented for non-Unity projects.");
|
||||
#endif
|
||||
}
|
||||
|
||||
private void StopTrackValues()
|
||||
{
|
||||
#if SUPPORTED_UNITY
|
||||
this.CancelInvoke("TrackValues");
|
||||
#else
|
||||
Debug.Log("Not implemented for non-Unity projects.");
|
||||
#endif
|
||||
}
|
||||
|
||||
private string GetFormattedTimestamp()
|
||||
{
|
||||
if (this.startStopwatch == null)
|
||||
{
|
||||
this.startStopwatch = new Stopwatch();
|
||||
this.startStopwatch.Start();
|
||||
}
|
||||
|
||||
TimeSpan span = this.startStopwatch.Elapsed;
|
||||
if (span.Minutes > 0)
|
||||
{
|
||||
return string.Format("[{0}:{1}.{1}]", span.Minutes, span.Seconds, span.Milliseconds);
|
||||
}
|
||||
|
||||
return string.Format("[{0}.{1}]", span.Seconds, span.Milliseconds);
|
||||
}
|
||||
|
||||
|
||||
// called via InvokeRepeatedly
|
||||
private void TrackValues()
|
||||
{
|
||||
if (this.client != null)
|
||||
{
|
||||
int currentRtt = this.client.LoadBalancingPeer.RoundTripTime;
|
||||
if (currentRtt > this.pingMax)
|
||||
{
|
||||
this.pingMax = currentRtt;
|
||||
}
|
||||
if (currentRtt < this.pingMin)
|
||||
{
|
||||
this.pingMin = currentRtt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Debug logs vital traffic statistics about the attached Photon Client.
|
||||
/// </summary>
|
||||
public void LogStats()
|
||||
{
|
||||
if (this.client == null || this.client.State == ClientState.PeerCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.LogTrafficStats)
|
||||
{
|
||||
Debug.Log(string.Format("{0} SupportLogger {1} Ping min/max: {2}/{3}", this.GetFormattedTimestamp() , this.client.LoadBalancingPeer.VitalStatsToString(false) , this.pingMin , this.pingMax));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug logs basic information (AppId, AppVersion, PeerID, Server address, Region) about the attached Photon Client.
|
||||
/// </summary>
|
||||
private void LogBasics()
|
||||
{
|
||||
if (this.client != null)
|
||||
{
|
||||
List<string> buildProperties = new List<string>(10);
|
||||
#if SUPPORTED_UNITY
|
||||
buildProperties.Add(Application.unityVersion);
|
||||
buildProperties.Add(Application.platform.ToString());
|
||||
#endif
|
||||
#if ENABLE_IL2CPP
|
||||
buildProperties.Add("ENABLE_IL2CPP");
|
||||
#endif
|
||||
#if ENABLE_MONO
|
||||
buildProperties.Add("ENABLE_MONO");
|
||||
#endif
|
||||
#if DEBUG
|
||||
buildProperties.Add("DEBUG");
|
||||
#endif
|
||||
#if MASTER
|
||||
buildProperties.Add("MASTER");
|
||||
#endif
|
||||
#if NET_4_6
|
||||
buildProperties.Add("NET_4_6");
|
||||
#endif
|
||||
#if NET_STANDARD_2_0
|
||||
buildProperties.Add("NET_STANDARD_2_0");
|
||||
#endif
|
||||
#if NETFX_CORE
|
||||
buildProperties.Add("NETFX_CORE");
|
||||
#endif
|
||||
#if NET_LEGACY
|
||||
buildProperties.Add("NET_LEGACY");
|
||||
#endif
|
||||
#if UNITY_64
|
||||
buildProperties.Add("UNITY_64");
|
||||
#endif
|
||||
#if UNITY_FUSION
|
||||
buildProperties.Add("UNITY_FUSION");
|
||||
#endif
|
||||
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
string appIdShort = string.IsNullOrEmpty(this.client.AppId) || this.client.AppId.Length < 8 ? this.client.AppId : string.Concat(this.client.AppId.Substring(0, 8), "***");
|
||||
|
||||
sb.AppendFormat("{0} SupportLogger Info: ", this.GetFormattedTimestamp());
|
||||
sb.AppendFormat("AppID: \"{0}\" AppVersion: \"{1}\" Client: v{2} ({4}) Build: {3} ", appIdShort, this.client.AppVersion, PhotonPeer.Version, string.Join(", ", buildProperties.ToArray()), this.client.LoadBalancingPeer.TargetFramework);
|
||||
if (this.client != null && this.client.LoadBalancingPeer != null && this.client.LoadBalancingPeer.SocketImplementation != null)
|
||||
{
|
||||
sb.AppendFormat("Socket: {0} ", this.client.LoadBalancingPeer.SocketImplementation.Name);
|
||||
}
|
||||
|
||||
sb.AppendFormat("UserId: \"{0}\" AuthType: {1} AuthMode: {2} {3} ", this.client.UserId, (this.client.AuthValues != null) ? this.client.AuthValues.AuthType.ToString() : "N/A", this.client.AuthMode, this.client.EncryptionMode);
|
||||
|
||||
sb.AppendFormat("State: {0} ", this.client.State);
|
||||
sb.AppendFormat("PeerID: {0} ", this.client.LoadBalancingPeer.PeerID);
|
||||
sb.AppendFormat("NameServer: {0} Current Server: {1} IP: {2} Region: {3} ", this.client.NameServerHost, this.client.CurrentServerAddress, this.client.LoadBalancingPeer.ServerIpAddress, this.client.CloudRegion);
|
||||
|
||||
Debug.LogWarning(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void OnConnected()
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnConnected().");
|
||||
this.pingMax = 0;
|
||||
this.pingMin = this.client.LoadBalancingPeer.RoundTripTime;
|
||||
this.LogBasics();
|
||||
|
||||
if (this.LogTrafficStats)
|
||||
{
|
||||
this.client.LoadBalancingPeer.TrafficStatsEnabled = false;
|
||||
this.client.LoadBalancingPeer.TrafficStatsEnabled = true;
|
||||
this.StartLogStats();
|
||||
}
|
||||
|
||||
this.StartTrackValues();
|
||||
}
|
||||
|
||||
public void OnConnectedToMaster()
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnConnectedToMaster().");
|
||||
}
|
||||
|
||||
public void OnFriendListUpdate(List<FriendInfo> friendList)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnFriendListUpdate(friendList).");
|
||||
}
|
||||
|
||||
public void OnJoinedLobby()
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnJoinedLobby(" + this.client.CurrentLobby + ").");
|
||||
}
|
||||
|
||||
public void OnLeftLobby()
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnLeftLobby().");
|
||||
}
|
||||
|
||||
public void OnCreateRoomFailed(short returnCode, string message)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnCreateRoomFailed(" + returnCode+","+message+").");
|
||||
}
|
||||
|
||||
public void OnJoinedRoom()
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnJoinedRoom(" + this.client.CurrentRoom + "). " + this.client.CurrentLobby + " GameServer:" + this.client.GameServerAddress);
|
||||
}
|
||||
|
||||
public void OnJoinRoomFailed(short returnCode, string message)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnJoinRoomFailed(" + returnCode+","+message+").");
|
||||
}
|
||||
|
||||
public void OnJoinRandomFailed(short returnCode, string message)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnJoinRandomFailed(" + returnCode+","+message+").");
|
||||
}
|
||||
|
||||
public void OnCreatedRoom()
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnCreatedRoom(" + this.client.CurrentRoom + "). " + this.client.CurrentLobby + " GameServer:" + this.client.GameServerAddress);
|
||||
}
|
||||
|
||||
public void OnLeftRoom()
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnLeftRoom().");
|
||||
}
|
||||
|
||||
public void OnDisconnected(DisconnectCause cause)
|
||||
{
|
||||
this.StopLogStats();
|
||||
this.StopTrackValues();
|
||||
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnDisconnected(" + cause + ").");
|
||||
this.LogBasics();
|
||||
this.LogStats();
|
||||
}
|
||||
|
||||
public void OnRegionListReceived(RegionHandler regionHandler)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnRegionListReceived(regionHandler).");
|
||||
}
|
||||
|
||||
public void OnRoomListUpdate(List<RoomInfo> roomList)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnRoomListUpdate(roomList). roomList.Count: " + roomList.Count);
|
||||
}
|
||||
|
||||
public void OnPlayerEnteredRoom(Player newPlayer)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnPlayerEnteredRoom(" + newPlayer+").");
|
||||
}
|
||||
|
||||
public void OnPlayerLeftRoom(Player otherPlayer)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnPlayerLeftRoom(" + otherPlayer+").");
|
||||
}
|
||||
|
||||
public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnRoomPropertiesUpdate(propertiesThatChanged).");
|
||||
}
|
||||
|
||||
public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnPlayerPropertiesUpdate(targetPlayer,changedProps).");
|
||||
}
|
||||
|
||||
public void OnMasterClientSwitched(Player newMasterClient)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnMasterClientSwitched(" + newMasterClient+").");
|
||||
}
|
||||
|
||||
public void OnCustomAuthenticationResponse(Dictionary<string, object> data)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnCustomAuthenticationResponse(" + data.ToStringFull()+").");
|
||||
}
|
||||
|
||||
public void OnCustomAuthenticationFailed (string debugMessage)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnCustomAuthenticationFailed(" + debugMessage+").");
|
||||
}
|
||||
|
||||
public void OnLobbyStatisticsUpdate(List<TypedLobbyInfo> lobbyStatistics)
|
||||
{
|
||||
Debug.Log(this.GetFormattedTimestamp() + " SupportLogger OnLobbyStatisticsUpdate(lobbyStatistics).");
|
||||
}
|
||||
|
||||
|
||||
#if !SUPPORTED_UNITY
|
||||
private static class Debug
|
||||
{
|
||||
public static void Log(string msg)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(msg);
|
||||
}
|
||||
public static void LogWarning(string msg)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(msg);
|
||||
}
|
||||
public static void LogError(string msg)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(msg);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public void OnErrorInfo(ErrorInfo errorInfo)
|
||||
{
|
||||
Debug.LogError(errorInfo.ToString());
|
||||
}
|
||||
}
|
||||
}
|
7
Assets/Photon/PhotonRealtime/Code/SupportLogger.cs.meta
Normal file
7
Assets/Photon/PhotonRealtime/Code/SupportLogger.cs.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b61c60d38639484ebbd7f2100dd3d08
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
8
Assets/Photon/PhotonRealtime/Code/Unity.meta
Normal file
8
Assets/Photon/PhotonRealtime/Code/Unity.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa14aa576f7e18f4bb6c4c93368a235d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Photon/PhotonRealtime/Code/Unity/Editor.meta
Normal file
8
Assets/Photon/PhotonRealtime/Code/Unity/Editor.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a02814c0303a60f488813e6111993aaa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
260
Assets/Photon/PhotonRealtime/Code/Unity/Editor/AccountService.cs
Normal file
260
Assets/Photon/PhotonRealtime/Code/Unity/Editor/AccountService.cs
Normal file
@ -0,0 +1,260 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="AccountService.cs" company="Exit Games GmbH">
|
||||
// Photon Cloud Account Service - Copyright (C) 2012 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Provides methods to register a new user-account for the Photon Cloud and
|
||||
// get the resulting appId.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_2017_4_OR_NEWER
|
||||
#define SUPPORTED_UNITY
|
||||
#endif
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
namespace Photon.Realtime
|
||||
{
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a instance of the Account Service to register Photon Cloud accounts.
|
||||
/// </summary>
|
||||
public class AccountService
|
||||
{
|
||||
private const string ServiceUrl = "https://partner.photonengine.com/api/{0}/User/RegisterEx";
|
||||
|
||||
private readonly Dictionary<string, string> RequestHeaders = new Dictionary<string, string>
|
||||
{
|
||||
{ "Content-Type", "application/json" },
|
||||
{ "x-functions-key", "" }
|
||||
};
|
||||
|
||||
private const string DefaultContext = "Unity";
|
||||
|
||||
private const string DefaultToken = "VQ920wVUieLHT9c3v1ZCbytaLXpXbktUztKb3iYLCdiRKjUagcl6eg==";
|
||||
|
||||
/// <summary>
|
||||
/// third parties custom context, if null, defaults to DefaultContext property value
|
||||
/// </summary>
|
||||
public string CustomContext = null; // "PartnerCode" on the server
|
||||
|
||||
/// <summary>
|
||||
/// third parties custom token. If null, defaults to DefaultToken property value
|
||||
/// </summary>
|
||||
public string CustomToken = null;
|
||||
|
||||
/// <summary>
|
||||
/// If this AccountService instance is currently waiting for a response. While pending, RegisterByEmail is blocked.
|
||||
/// </summary>
|
||||
public bool RequestPendingResult = false;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to create a Photon Cloud Account asynchronously. Blocked while RequestPendingResult is true.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Once your callback is called, check ReturnCode, Message and AppId to get the result of this attempt.
|
||||
/// </remarks>
|
||||
/// <param name="email">Email of the account.</param>
|
||||
/// <param name="serviceTypes">Defines which type of Photon-service is being requested.</param>
|
||||
/// <param name="callback">Called when the result is available.</param>
|
||||
/// <param name="errorCallback">Called when the request failed.</param>
|
||||
/// <param name="origin">Can be used to identify the origin of the registration (which package is being used).</param>
|
||||
public bool RegisterByEmail(string email, List<ServiceTypes> serviceTypes, Action<AccountServiceResponse> callback = null, Action<string> errorCallback = null, string origin = null)
|
||||
{
|
||||
if (this.RequestPendingResult)
|
||||
{
|
||||
Debug.LogError("Registration request pending result. Not sending another.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidEmail(email))
|
||||
{
|
||||
Debug.LogErrorFormat("Email \"{0}\" is not valid", email);
|
||||
return false;
|
||||
}
|
||||
|
||||
string serviceTypeString = GetServiceTypesFromList(serviceTypes);
|
||||
if (string.IsNullOrEmpty(serviceTypeString))
|
||||
{
|
||||
Debug.LogError("serviceTypes string is null or empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
string fullUrl = GetUrlWithQueryStringEscaped(email, serviceTypeString, origin);
|
||||
|
||||
RequestHeaders["x-functions-key"] = string.IsNullOrEmpty(CustomToken) ? DefaultToken : CustomToken;
|
||||
|
||||
|
||||
this.RequestPendingResult = true;
|
||||
|
||||
PhotonEditorUtils.StartCoroutine(
|
||||
PhotonEditorUtils.HttpPost(fullUrl,
|
||||
RequestHeaders,
|
||||
null,
|
||||
s =>
|
||||
{
|
||||
this.RequestPendingResult = false;
|
||||
//Debug.LogWarningFormat("received response {0}", s);
|
||||
if (string.IsNullOrEmpty(s))
|
||||
{
|
||||
if (errorCallback != null)
|
||||
{
|
||||
errorCallback("Server's response was empty. Please register through account website during this service interruption.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AccountServiceResponse ase = this.ParseResult(s);
|
||||
if (ase == null)
|
||||
{
|
||||
if (errorCallback != null)
|
||||
{
|
||||
errorCallback("Error parsing registration response. Please try registering from account website");
|
||||
}
|
||||
}
|
||||
else if (callback != null)
|
||||
{
|
||||
callback(ase);
|
||||
}
|
||||
}
|
||||
},
|
||||
e =>
|
||||
{
|
||||
this.RequestPendingResult = false;
|
||||
if (errorCallback != null)
|
||||
{
|
||||
errorCallback(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private string GetUrlWithQueryStringEscaped(string email, string serviceTypes, string originAv)
|
||||
{
|
||||
string emailEscaped = UnityEngine.Networking.UnityWebRequest.EscapeURL(email);
|
||||
string st = UnityEngine.Networking.UnityWebRequest.EscapeURL(serviceTypes);
|
||||
string uv = UnityEngine.Networking.UnityWebRequest.EscapeURL(Application.unityVersion);
|
||||
string serviceUrl = string.Format(ServiceUrl, string.IsNullOrEmpty(CustomContext) ? DefaultContext : CustomContext );
|
||||
|
||||
return string.Format("{0}?email={1}&st={2}&uv={3}&av={4}", serviceUrl, emailEscaped, st, uv, originAv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the Json response and applies it to local properties.
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
private AccountServiceResponse ParseResult(string result)
|
||||
{
|
||||
try
|
||||
{
|
||||
AccountServiceResponse res = JsonUtility.FromJson<AccountServiceResponse>(result);
|
||||
// Unity's JsonUtility does not support deserializing Dictionary, we manually parse it, dirty & ugly af, better then using a 3rd party lib
|
||||
if (res.ReturnCode == AccountServiceReturnCodes.Success)
|
||||
{
|
||||
string[] parts = result.Split(new[] { "\"ApplicationIds\":{" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
parts = parts[1].Split('}');
|
||||
string applicationIds = parts[0];
|
||||
if (!string.IsNullOrEmpty(applicationIds))
|
||||
{
|
||||
parts = applicationIds.Split(new[] { ',', '"', ':' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
res.ApplicationIds = new Dictionary<string, string>(parts.Length / 2);
|
||||
for (int i = 0; i < parts.Length; i = i + 2)
|
||||
{
|
||||
res.ApplicationIds.Add(parts[i], parts[i + 1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("The server did not return any AppId, ApplicationIds was empty in the response.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
catch (Exception ex) // probably JSON parsing exception, check if returned string is valid JSON
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns the list items to a comma separated string. Returns null if list is null or empty.
|
||||
/// </summary>
|
||||
/// <param name="appTypes">List of service types.</param>
|
||||
/// <returns>Returns null if list is null or empty.</returns>
|
||||
private static string GetServiceTypesFromList(List<ServiceTypes> appTypes)
|
||||
{
|
||||
if (appTypes == null || appTypes.Count <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string serviceTypes = ((int)appTypes[0]).ToString();
|
||||
for (int i = 1; i < appTypes.Count; i++)
|
||||
{
|
||||
int appType = (int)appTypes[i];
|
||||
serviceTypes = string.Format("{0},{1}", serviceTypes, appType);
|
||||
}
|
||||
|
||||
return serviceTypes;
|
||||
}
|
||||
|
||||
// RFC2822 compliant matching 99.9% of all email addresses in actual use today
|
||||
// according to http://www.regular-expressions.info/email.html [22.02.2012]
|
||||
private static Regex reg = new Regex("^((?>[a-zA-Z\\d!#$%&'*+\\-/=?^_{|}~]+\\x20*|\"((?=[\\x01-\\x7f])[^\"\\]|\\[\\x01-\\x7f])*\"\\x20*)*(?<angle><))?((?!\\.)(?>\\.?[a-zA-Z\\d!#$%&'*+\\-/=?^_{|}~]+)+|\"((?=[\\x01-\\x7f])[^\"\\]|\\[\\x01-\\x7f])*\")@(((?!-)[a-zA-Z\\d\\-]+(?<!-)\\.)+[a-zA-Z]{2,}|\\[(((?(?<!\\[)\\.)(25[0-5]|2[0-4]\\d|[01]?\\d?\\d)){4}|[a-zA-Z\\d\\-]*[a-zA-Z\\d]:((?=[\\x01-\\x7f])[^\\\\[\\]]|\\[\\x01-\\x7f])+)\\])(?(angle)>)$",
|
||||
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
public static bool IsValidEmail(string mailAddress)
|
||||
{
|
||||
if (string.IsNullOrEmpty(mailAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var result = reg.Match(mailAddress);
|
||||
return result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class AccountServiceResponse
|
||||
{
|
||||
public int ReturnCode;
|
||||
public string Message;
|
||||
public Dictionary<string, string> ApplicationIds; // Unity's JsonUtility does not support deserializing Dictionary
|
||||
}
|
||||
|
||||
|
||||
public class AccountServiceReturnCodes
|
||||
{
|
||||
public static int Success = 0;
|
||||
public static int EmailAlreadyRegistered = 8;
|
||||
public static int InvalidParameters = 12;
|
||||
}
|
||||
|
||||
public enum ServiceTypes
|
||||
{
|
||||
Realtime = 0,
|
||||
Turnbased = 1,
|
||||
Chat = 2,
|
||||
Voice = 3,
|
||||
TrueSync = 4,
|
||||
Pun = 5,
|
||||
Thunder = 6,
|
||||
Quantum = 7,
|
||||
Fusion = 8,
|
||||
Bolt = 20
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 166dfe22956ef0341b28e18d0499e363
|
||||
labels:
|
||||
- ExitGames
|
||||
- PUN
|
||||
- Photon
|
||||
- Networking
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
@ -0,0 +1,333 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="PhotonEditorUtils.cs" company="Exit Games GmbH">
|
||||
// PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Unity Editor Utils
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#pragma warning disable 618 // Deprecation warnings
|
||||
|
||||
|
||||
#if UNITY_2017_4_OR_NEWER
|
||||
#define SUPPORTED_UNITY
|
||||
#endif
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
namespace Photon.Realtime
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
|
||||
[InitializeOnLoad]
|
||||
public static class PhotonEditorUtils
|
||||
{
|
||||
/// <summary>Stores a flag which tells Editor scripts if the PhotonEditor.OnProjectChanged got called since initialization.</summary>
|
||||
/// <remarks>If not, the AssetDatabase is likely not usable yet and instances of ScriptableObject can't be loaded.</remarks>
|
||||
public static bool ProjectChangedWasCalled;
|
||||
|
||||
|
||||
/// <summary>True if the ChatClient of the Photon Chat API is available. If so, the editor may (e.g.) show additional options in settings.</summary>
|
||||
public static bool HasChat;
|
||||
|
||||
/// <summary>True if the VoiceClient of the Photon Voice API is available. If so, the editor may (e.g.) show additional options in settings.</summary>
|
||||
public static bool HasVoice;
|
||||
|
||||
/// <summary>True if PUN is in the project.</summary>
|
||||
public static bool HasPun;
|
||||
|
||||
/// <summary>True if Photon Fusion is available in the project (and enabled).</summary>
|
||||
public static bool HasFusion;
|
||||
|
||||
/// <summary>True if the PhotonEditorUtils checked the available products / APIs. If so, the editor may (e.g.) show additional options in settings.</summary>
|
||||
public static bool HasCheckedProducts;
|
||||
|
||||
static PhotonEditorUtils()
|
||||
{
|
||||
HasVoice = Type.GetType("Photon.Voice.VoiceClient, Assembly-CSharp") != null || Type.GetType("Photon.Voice.VoiceClient, Assembly-CSharp-firstpass") != null || Type.GetType("Photon.Voice.VoiceClient, PhotonVoice.API") != null;
|
||||
HasChat = Type.GetType("Photon.Chat.ChatClient, Assembly-CSharp") != null || Type.GetType("Photon.Chat.ChatClient, Assembly-CSharp-firstpass") != null || Type.GetType("Photon.Chat.ChatClient, PhotonChat") != null;
|
||||
HasPun = Type.GetType("Photon.Pun.PhotonNetwork, Assembly-CSharp") != null || Type.GetType("Photon.Pun.PhotonNetwork, Assembly-CSharp-firstpass") != null || Type.GetType("Photon.Pun.PhotonNetwork, PhotonUnityNetworking") != null;
|
||||
#if FUSION_WEAVER
|
||||
HasFusion = true;
|
||||
#endif
|
||||
PhotonEditorUtils.HasCheckedProducts = true;
|
||||
|
||||
if (EditorPrefs.HasKey("DisablePun") && EditorPrefs.GetBool("DisablePun"))
|
||||
{
|
||||
HasPun = false;
|
||||
}
|
||||
|
||||
if (HasPun)
|
||||
{
|
||||
// MOUNTING SYMBOLS
|
||||
#if !PHOTON_UNITY_NETWORKING
|
||||
AddScriptingDefineSymbolToAllBuildTargetGroups("PHOTON_UNITY_NETWORKING");
|
||||
#endif
|
||||
|
||||
#if !PUN_2_0_OR_NEWER
|
||||
AddScriptingDefineSymbolToAllBuildTargetGroups("PUN_2_0_OR_NEWER");
|
||||
#endif
|
||||
|
||||
#if !PUN_2_OR_NEWER
|
||||
AddScriptingDefineSymbolToAllBuildTargetGroups("PUN_2_OR_NEWER");
|
||||
#endif
|
||||
|
||||
#if !PUN_2_19_OR_NEWER
|
||||
AddScriptingDefineSymbolToAllBuildTargetGroups("PUN_2_19_OR_NEWER");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a given scripting define symbol to all build target groups
|
||||
/// You can see all scripting define symbols ( not the internal ones, only the one for this project), in the PlayerSettings inspector
|
||||
/// </summary>
|
||||
/// <param name="defineSymbol">Define symbol.</param>
|
||||
public static void AddScriptingDefineSymbolToAllBuildTargetGroups(string defineSymbol)
|
||||
{
|
||||
foreach (BuildTarget target in Enum.GetValues(typeof(BuildTarget)))
|
||||
{
|
||||
BuildTargetGroup group = BuildPipeline.GetBuildTargetGroup(target);
|
||||
|
||||
if (group == BuildTargetGroup.Unknown)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var defineSymbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(group).Split(';').Select(d => d.Trim()).ToList();
|
||||
|
||||
if (!defineSymbols.Contains(defineSymbol))
|
||||
{
|
||||
defineSymbols.Add(defineSymbol);
|
||||
|
||||
try
|
||||
{
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, string.Join(";", defineSymbols.ToArray()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log("Could not set Photon " + defineSymbol + " defines for build target: " + target + " group: " + group + " " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes PUN2's Script Define Symbols from project
|
||||
/// </summary>
|
||||
public static void CleanUpPunDefineSymbols()
|
||||
{
|
||||
foreach (BuildTarget target in Enum.GetValues(typeof(BuildTarget)))
|
||||
{
|
||||
BuildTargetGroup group = BuildPipeline.GetBuildTargetGroup(target);
|
||||
|
||||
if (group == BuildTargetGroup.Unknown)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var defineSymbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(group)
|
||||
.Split(';')
|
||||
.Select(d => d.Trim())
|
||||
.ToList();
|
||||
|
||||
List<string> newDefineSymbols = new List<string>();
|
||||
foreach (var symbol in defineSymbols)
|
||||
{
|
||||
if ("PHOTON_UNITY_NETWORKING".Equals(symbol) || symbol.StartsWith("PUN_2_"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
newDefineSymbols.Add(symbol);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, string.Join(";", newDefineSymbols.ToArray()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogErrorFormat("Could not set clean up PUN2's define symbols for build target: {0} group: {1}, {2}", target, group, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent directory of a path. Recursive Function, will return null if parentName not found
|
||||
/// </summary>
|
||||
/// <returns>The parent directory</returns>
|
||||
/// <param name="path">Path.</param>
|
||||
/// <param name="parentName">Parent name.</param>
|
||||
public static string GetParent(string path, string parentName)
|
||||
{
|
||||
var dir = new DirectoryInfo(path);
|
||||
|
||||
if (dir.Parent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(parentName))
|
||||
{
|
||||
return dir.Parent.FullName;
|
||||
}
|
||||
|
||||
if (dir.Parent.Name == parentName)
|
||||
{
|
||||
return dir.Parent.FullName;
|
||||
}
|
||||
|
||||
return GetParent(dir.Parent.FullName, parentName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a GameObject is a prefab asset or part of a prefab asset, as opposed to an instance in the scene hierarchy
|
||||
/// </summary>
|
||||
/// <returns><c>true</c>, if a prefab asset or part of it, <c>false</c> otherwise.</returns>
|
||||
/// <param name="go">The GameObject to check</param>
|
||||
public static bool IsPrefab(GameObject go)
|
||||
{
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
return UnityEditor.SceneManagement.PrefabStageUtility.GetPrefabStage(go) != null || EditorUtility.IsPersistent(go);
|
||||
#elif UNITY_2018_3_OR_NEWER
|
||||
return UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetPrefabStage(go) != null || EditorUtility.IsPersistent(go);
|
||||
#else
|
||||
return EditorUtility.IsPersistent(go);
|
||||
#endif
|
||||
}
|
||||
|
||||
//https://forum.unity.com/threads/using-unitywebrequest-in-editor-tools.397466/#post-4485181
|
||||
public static void StartCoroutine(System.Collections.IEnumerator update)
|
||||
{
|
||||
EditorApplication.CallbackFunction closureCallback = null;
|
||||
|
||||
closureCallback = () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (update.MoveNext() == false)
|
||||
{
|
||||
EditorApplication.update -= closureCallback;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
EditorApplication.update -= closureCallback;
|
||||
}
|
||||
};
|
||||
|
||||
EditorApplication.update += closureCallback;
|
||||
}
|
||||
|
||||
public static System.Collections.IEnumerator HttpPost(string url, Dictionary<string, string> headers, byte[] payload, Action<string> successCallback, Action<string> errorCallback)
|
||||
{
|
||||
using (UnityWebRequest w = new UnityWebRequest(url, "POST"))
|
||||
{
|
||||
if (payload != null)
|
||||
{
|
||||
w.uploadHandler = new UploadHandlerRaw(payload);
|
||||
}
|
||||
w.downloadHandler = new DownloadHandlerBuffer();
|
||||
if (headers != null)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
{
|
||||
w.SetRequestHeader(header.Key, header.Value);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
yield return w.SendWebRequest();
|
||||
#else
|
||||
yield return w.Send();
|
||||
#endif
|
||||
|
||||
while (w.isDone == false)
|
||||
yield return null;
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
if (w.result == UnityWebRequest.Result.ProtocolError || w.result == UnityWebRequest.Result.ConnectionError || w.result == UnityWebRequest.Result.DataProcessingError)
|
||||
#elif UNITY_2017_1_OR_NEWER
|
||||
if (w.isNetworkError || w.isHttpError)
|
||||
#endif
|
||||
{
|
||||
if (errorCallback != null)
|
||||
{
|
||||
errorCallback(w.error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (successCallback != null)
|
||||
{
|
||||
successCallback(w.downloadHandler.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a Foldout using a toggle with (GUIStyle)"Foldout") and a separate label. This is a workaround for 2019.3 foldout arrows not working.
|
||||
/// </summary>
|
||||
/// <param name="isExpanded"></param>
|
||||
/// <param name="label"></param>
|
||||
/// <returns>Returns the new isExpanded value.</returns>
|
||||
public static bool Foldout(this SerializedProperty isExpanded, GUIContent label)
|
||||
{
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
bool newvalue = EditorGUI.Toggle(new Rect(rect) { xMin = rect.xMin + 2 }, GUIContent.none, isExpanded.boolValue, (GUIStyle)"Foldout");
|
||||
EditorGUI.LabelField(new Rect(rect) { xMin = rect.xMin + 15 }, label);
|
||||
if (newvalue != isExpanded.boolValue)
|
||||
{
|
||||
isExpanded.boolValue = newvalue;
|
||||
isExpanded.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
return newvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Foldout using a toggle with (GUIStyle)"Foldout") and a separate label. This is a workaround for 2019.3 foldout arrows not working.
|
||||
/// </summary>
|
||||
/// <param name="isExpanded"></param>
|
||||
/// <param name="label"></param>
|
||||
/// <returns>Returns the new isExpanded value.</returns>
|
||||
public static bool Foldout(this bool isExpanded, GUIContent label)
|
||||
{
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
bool newvalue = EditorGUI.Toggle(new Rect(rect) { xMin = rect.xMin + 2 }, GUIContent.none, isExpanded, (GUIStyle)"Foldout");
|
||||
EditorGUI.LabelField(new Rect(rect) { xMin = rect.xMin + 15 }, label);
|
||||
return newvalue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class CleanUpDefinesOnPunDelete : UnityEditor.AssetModificationProcessor
|
||||
{
|
||||
public static AssetDeleteResult OnWillDeleteAsset(string assetPath, RemoveAssetOptions rao)
|
||||
{
|
||||
if ("Assets/Photon/PhotonUnityNetworking".Equals(assetPath))
|
||||
{
|
||||
PhotonEditorUtils.CleanUpPunDefineSymbols();
|
||||
}
|
||||
|
||||
return AssetDeleteResult.DidNotDelete;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 607340ca505d53d4f8e785423fac7964
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
116
Assets/Photon/PhotonRealtime/Code/Unity/PhotonAppSettings.cs
Normal file
116
Assets/Photon/PhotonRealtime/Code/Unity/PhotonAppSettings.cs
Normal file
@ -0,0 +1,116 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="PhotonAppSettings.cs" company="Exit Games GmbH">
|
||||
// </copyright>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_2017_4_OR_NEWER
|
||||
#define SUPPORTED_UNITY
|
||||
#endif
|
||||
|
||||
|
||||
#if !PHOTON_UNITY_NETWORKING
|
||||
|
||||
namespace Photon.Realtime
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
/// <summary>
|
||||
/// Collection of connection-relevant settings, used internally by PhotonNetwork.ConnectUsingSettings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Includes the AppSettings class from the Realtime APIs plus some other, PUN-relevant, settings.</remarks>
|
||||
[Serializable]
|
||||
[HelpURL("https://doc.photonengine.com/en-us/pun/v2/getting-started/initial-setup")]
|
||||
public class PhotonAppSettings : ScriptableObject
|
||||
{
|
||||
[Tooltip("Core Photon Server/Cloud settings.")]
|
||||
public AppSettings AppSettings;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[HideInInspector]
|
||||
public bool DisableAutoOpenWizard;
|
||||
//public bool ShowSettings;
|
||||
//public bool DevRegionSetOnce;
|
||||
#endif
|
||||
|
||||
private static PhotonAppSettings instance;
|
||||
|
||||
/// <summary>Serialized server settings, written by the Setup Wizard for use in ConnectUsingSettings.</summary>
|
||||
public static PhotonAppSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
LoadOrCreateSettings();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private set { instance = value; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void LoadOrCreateSettings()
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
Debug.LogWarning("Instance is not null. Will not LoadOrCreateSettings().");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// let's check if the AssetDatabase finds the file; aimed to avoid multiple files being created, potentially a futile step
|
||||
AssetDatabase.Refresh();
|
||||
#endif
|
||||
|
||||
// try to load the resource / asset (ServerSettings a.k.a. PhotonServerSettings)
|
||||
instance = (PhotonAppSettings)Resources.Load(typeof(PhotonAppSettings).Name, typeof(PhotonAppSettings));
|
||||
if (instance != null)
|
||||
{
|
||||
//Debug.LogWarning("Settings from Resources."); // DEBUG
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// create it if not loaded
|
||||
if (instance == null)
|
||||
{
|
||||
instance = (PhotonAppSettings)CreateInstance(typeof(PhotonAppSettings));
|
||||
if (instance == null)
|
||||
{
|
||||
Debug.LogError("Failed to create ServerSettings. PUN is unable to run this way. If you deleted it from the project, reload the Editor.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Debug.LogWarning("Settings created!"); // DEBUG
|
||||
}
|
||||
|
||||
// in the editor, store the settings file as it's not loaded
|
||||
#if UNITY_EDITOR
|
||||
string punResourcesDirectory = "Assets/Photon/Resources/";
|
||||
string serverSettingsAssetPath = punResourcesDirectory + typeof(PhotonAppSettings).Name + ".asset";
|
||||
string serverSettingsDirectory = Path.GetDirectoryName(serverSettingsAssetPath);
|
||||
|
||||
if (!Directory.Exists(serverSettingsDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(serverSettingsDirectory);
|
||||
AssetDatabase.ImportAsset(serverSettingsDirectory);
|
||||
}
|
||||
|
||||
AssetDatabase.CreateAsset(instance, serverSettingsAssetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
|
||||
//Debug.Log("Settings stored to DB."); // DEBUG
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a389b614f52fbf347a1533dbbf245033
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
187
Assets/Photon/PhotonRealtime/Code/WebRpc.cs
Normal file
187
Assets/Photon/PhotonRealtime/Code/WebRpc.cs
Normal file
@ -0,0 +1,187 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="WebRpc.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// This class wraps responses of a Photon WebRPC call, coming from a
|
||||
// third party web service.
|
||||
// </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.Collections.Generic;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
#if SUPPORTED_UNITY || NETFX_CORE
|
||||
using Hashtable = ExitGames.Client.Photon.Hashtable;
|
||||
using SupportClass = ExitGames.Client.Photon.SupportClass;
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>Reads an operation response of a WebRpc and provides convenient access to most common values.</summary>
|
||||
/// <remarks>
|
||||
/// See LoadBalancingClient.OpWebRpc.<br/>
|
||||
/// Create a WebRpcResponse to access common result values.<br/>
|
||||
/// The operationResponse.OperationCode should be: OperationCode.WebRpc.<br/>
|
||||
/// </remarks>
|
||||
public class WebRpcResponse
|
||||
{
|
||||
/// <summary>Name of the WebRpc that was called.</summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>ResultCode of the WebService that answered the WebRpc.</summary>
|
||||
/// <remarks>
|
||||
/// 0 is: "OK" for WebRPCs.<br/>
|
||||
/// -1 is: No ResultCode by WebRpc service (check <see cref="OperationResponse.ReturnCode"/>).<br/>
|
||||
/// Other ResultCode are defined by the individual WebRpc and service.
|
||||
/// </remarks>
|
||||
public int ResultCode { get; private set; }
|
||||
[System.Obsolete("Use ResultCode instead")]
|
||||
public int ReturnCode
|
||||
{
|
||||
get { return ResultCode; }
|
||||
}
|
||||
|
||||
/// <summary>Might be empty or null.</summary>
|
||||
public string Message { get; private set; }
|
||||
[System.Obsolete("Use Message instead")]
|
||||
public string DebugMessage
|
||||
{
|
||||
get { return Message; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Other key/values returned by the webservice that answered the WebRpc.</summary>
|
||||
public Dictionary<string, object> Parameters { get; private set; }
|
||||
|
||||
/// <summary>An OperationResponse for a WebRpc is needed to read it's values.</summary>
|
||||
public WebRpcResponse(OperationResponse response)
|
||||
{
|
||||
object value;
|
||||
if (response.Parameters.TryGetValue(ParameterCode.UriPath, out value))
|
||||
{
|
||||
this.Name = value as string;
|
||||
}
|
||||
|
||||
this.ResultCode = -1;
|
||||
if (response.Parameters.TryGetValue(ParameterCode.WebRpcReturnCode, out value))
|
||||
{
|
||||
this.ResultCode = (byte)value;
|
||||
}
|
||||
|
||||
if (response.Parameters.TryGetValue(ParameterCode.WebRpcParameters, out value))
|
||||
{
|
||||
this.Parameters = value as Dictionary<string, object>;
|
||||
}
|
||||
|
||||
if (response.Parameters.TryGetValue(ParameterCode.WebRpcReturnMessage, out value))
|
||||
{
|
||||
this.Message = value as string;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Turns the response into an easier to read string.</summary>
|
||||
/// <returns>String resembling the result.</returns>
|
||||
public string ToStringFull()
|
||||
{
|
||||
return string.Format("{0}={2}: {1} \"{3}\"", this.Name, SupportClass.DictionaryToString(this.Parameters), this.ResultCode, this.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Optional flags to be used in Photon client SDKs with Op RaiseEvent and Op SetProperties.
|
||||
/// Introduced mainly for webhooks 1.2 to control behavior of forwarded HTTP requests.
|
||||
/// </summary>
|
||||
public class WebFlags
|
||||
{
|
||||
|
||||
public readonly static WebFlags Default = new WebFlags(0);
|
||||
public byte WebhookFlags;
|
||||
/// <summary>
|
||||
/// Indicates whether to forward HTTP request to web service or not.
|
||||
/// </summary>
|
||||
public bool HttpForward
|
||||
{
|
||||
get { return (WebhookFlags & HttpForwardConst) != 0; }
|
||||
set {
|
||||
if (value)
|
||||
{
|
||||
WebhookFlags |= HttpForwardConst;
|
||||
}
|
||||
else
|
||||
{
|
||||
WebhookFlags = (byte) (WebhookFlags & ~(1 << 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
public const byte HttpForwardConst = 0x01;
|
||||
/// <summary>
|
||||
/// Indicates whether to send AuthCookie of actor in the HTTP request to web service or not.
|
||||
/// </summary>
|
||||
public bool SendAuthCookie
|
||||
{
|
||||
get { return (WebhookFlags & SendAuthCookieConst) != 0; }
|
||||
set {
|
||||
if (value)
|
||||
{
|
||||
WebhookFlags |= SendAuthCookieConst;
|
||||
}
|
||||
else
|
||||
{
|
||||
WebhookFlags = (byte)(WebhookFlags & ~(1 << 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
public const byte SendAuthCookieConst = 0x02;
|
||||
/// <summary>
|
||||
/// Indicates whether to send HTTP request synchronously or asynchronously to web service.
|
||||
/// </summary>
|
||||
public bool SendSync
|
||||
{
|
||||
get { return (WebhookFlags & SendSyncConst) != 0; }
|
||||
set {
|
||||
if (value)
|
||||
{
|
||||
WebhookFlags |= SendSyncConst;
|
||||
}
|
||||
else
|
||||
{
|
||||
WebhookFlags = (byte)(WebhookFlags & ~(1 << 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
public const byte SendSyncConst = 0x04;
|
||||
/// <summary>
|
||||
/// Indicates whether to send serialized game state in HTTP request to web service or not.
|
||||
/// </summary>
|
||||
public bool SendState
|
||||
{
|
||||
get { return (WebhookFlags & SendStateConst) != 0; }
|
||||
set {
|
||||
if (value)
|
||||
{
|
||||
WebhookFlags |= SendStateConst;
|
||||
}
|
||||
else
|
||||
{
|
||||
WebhookFlags = (byte)(WebhookFlags & ~(1 << 3));
|
||||
}
|
||||
}
|
||||
}
|
||||
public const byte SendStateConst = 0x08;
|
||||
|
||||
public WebFlags(byte webhookFlags)
|
||||
{
|
||||
WebhookFlags = webhookFlags;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
12
Assets/Photon/PhotonRealtime/Code/WebRpc.cs.meta
Normal file
12
Assets/Photon/PhotonRealtime/Code/WebRpc.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 294b0a24f6c841f48acf4bf696a4d764
|
||||
timeCreated: 1493901324
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
386
Assets/Photon/PhotonRealtime/Code/changes-realtime.txt
Normal file
386
Assets/Photon/PhotonRealtime/Code/changes-realtime.txt
Normal file
@ -0,0 +1,386 @@
|
||||
|
||||
Photon Realtime C# Client - Changelog
|
||||
Exit Games GmbH - www.photonengine.com - forum.photonengine.com
|
||||
|
||||
|
||||
Version 4.1.6.11 (22. November 2021)
|
||||
Changed: For Unity, PhotonEditorUtils.IsPrefab to use the correct prefab stage utility for Unity 2021.2 and up.
|
||||
Added: CustomAuthenticationType.Epic to authenticate via Epic Online Services (EOS).
|
||||
Added: CustomAuthenticationType.FacebookGaming to authenticate via Facebook Gaming services.
|
||||
|
||||
Version 4.1.6.8 (12. October 2021)
|
||||
Changed: CustomTypesUnity. The default Quaternion is now Quaternion.identity (which is a legit Quaterion, as opposed to the previously used new Quaternion). Added const values for each type's byte-size.
|
||||
Added: Checks to avoid creating a player for actorNumber 0.
|
||||
Added: CheckIfOpCanBeSent to OpJoinOrCreateRoom and OpRejoinRoom. Avoids changing state and repeating operation.
|
||||
Removed: Surplus checks if the LoadBalancingPeer is non-null. This is done in CheckIfOpCanBeSent(), too.
|
||||
|
||||
Version 4.1.6.7 (27. September 2021)
|
||||
Replaced: Pointer_stringify with UTF8ToString in WebSocket.jslib. It was removed from newer Emscripten versions and is no longer working when exporting from Unity 2021.2. betas. Replaced with UTF8ToString. Tested in 2018.4.x and a current beta.
|
||||
Fixed: Usage of websocketType for console.
|
||||
|
||||
Version 4.1.6.6 (17. September 2021)
|
||||
Removed: Usage of class EncryptorNative. This needs to be replaced by EncryptorNativeSource where the Native Socket Plugin should be used.
|
||||
Changed: The Native Socket Plugin can also be used for UDP on Xbox (not in Editor).
|
||||
Removed: CheckConnectSetupXboxOne() as platform requirements were turned into recommendations.
|
||||
Changed: For Xbox, order of checked assemblies when getting Type SocketNativeSource. It's more likely to be in Assembly-CSharp.
|
||||
|
||||
Version 4.1.6.5 (24. August 2021)
|
||||
Updated: The default name server host to "ns.photonengine.io". When using WSS on this, it expects TLS1.2 (Win 7 and old Unity versions may not support this).
|
||||
Fixed: Internally used OpJoinRoom() will now send custom room properties (all RoomOptions set) to the Master Server, too. This allows the Master Server to immediately find the room, even if property filtering is used. This was affecting matchmaking with OpJoinOrCreateRoom.
|
||||
|
||||
Version 4.1.6.4 (26. July 2021)
|
||||
Internal: Added PhotonEditorTools.HasFusion value and added AppSettings.AppIdFusion as well as ClientAppType.Fusion.
|
||||
Added: ConnectionHandler.DisconnectAfterKeepAlive to let the fallback thread call Disconnect after the KeepAliveInBackground time. Default: false.
|
||||
Changed: ConnectionHandler timer implementation to use a Stopwatch, which is a little more clear about what happens. PUN does not expose this value, so you need to set it in code (change the default).
|
||||
Added: Hashtable specific extension methods StripKeysWithNullValues and StripToStringKeys which allocates less.
|
||||
|
||||
Version 4.1.6.3 (06. July 2021)
|
||||
Added: Handling of ErrorCode Invalid Operation (-2) for Authenticate requests. This becomes a DisconnectCause = OperationNotAllowedInCurrentState.
|
||||
|
||||
Version 4.1.6.2 (17. June 2021)
|
||||
Changed: The enumeration CustomAuthenticationType was changed to fix naming inconsistencies. Use PlayStation4 and PlayStation5 respectively. Old variants are available as obsolete.
|
||||
|
||||
Version 4.1.6.0 (19. May 2021)
|
||||
Changed: As tokenless auth is no longer allowed when using a Name Server, there is now an error log and authentication will not be done from the client. This is not happening in the normal workflow but a precaution to detect issues, should something unpredicted happen.
|
||||
Changed: SupportLogger formatting of the timestamp to include the minutes (if available). Hours will not be logged (so in the logs, there is a wrap around of log-timestamps after 60 min).
|
||||
|
||||
Version 4.1.5.4 (13. April 2021)
|
||||
Changed: Connect-methods in the LoadBalancingClient now fail "early" when the peer is already connected. This prevents unintentional modifications of the state and auth token.
|
||||
Added: The LoadBalancingClient will now check if the token is available when the AuthMode 'AuthOnce' or 'AuthOnceWss' require it. If it's not available, the client will log an error and prevent the connection attempt. If this happens, the client is already in a bad state and won't connect anyways.
|
||||
|
||||
Version 4.1.5.3 (07. April 2021)
|
||||
Fixed: Protocol Fallback will now connect correctly when a fixed region is set.
|
||||
Changed: ConnectUsingSettings internally sets connectToBestRegion value, based on CloudRegion (equals appSettings.FixedRegion). This avoids pinging when a fixed region is set. Leads to callback OnDisconnected(InvalidRegion).
|
||||
|
||||
Version 4.1.5.2 (12. March 2021)
|
||||
Changed: Region pinging will now apply port overrides for the Master Server port for UDP and address-based pinging (in the address and the UDP pings).
|
||||
Changed: The RegionHandler gets the override port from lbc.ServerPortOverrides.MasterServerPort. If it's 0, the default port 5055 is being used.
|
||||
Changed: The LoadBalancingClient.ReplacePortWithAlternative is now static and internal and can be used by the RegionHandler.
|
||||
Added: TargetFramework (of the used dll) to SupportLogger output.
|
||||
Added: Room.GetPlayer() option to find the Master Client, if the id is 0 (which is commonly used as "owned/controlled by the room, so it requires the Master Client).
|
||||
|
||||
Version 4.1.5.1 (09. March 2021)
|
||||
Removed: Setting of protocol 1.6 when connecting directly to a Master Server. This was a workaround for older Photon Server SDKs.
|
||||
Note: If you host Photon yourself, you can set the LoadBalancingClient.SerializationProtocol to SerializationProtocol.GpBinaryV16, before you connect.
|
||||
Changed: OpLeaveRoom will now clear the GameServerAddress and private room-entering cache. This cleans the state and prevents accidental success for ReconnectAndRejoinRoom calls.
|
||||
Changed: ReconnectToMaster will check if MasterServerAddress and authToken are known. These are minimum requirements to reconnect to the master (after a connection loss).
|
||||
Added: CustomAuthenticationType.Playstation5 (value 12).
|
||||
|
||||
Version 4.1.5.0 (23. February 2021)
|
||||
Added: LoadBalancingClient.ClientType and related code. Defines which sort of AppId should be expected. The LoadBalancingClient supports Realtime and Voice app types. Default: Realtime.
|
||||
Note: This allows using AppSettings for Voice and Realtime clients more easily. ConnectUsingSettings will pick the relevant appid with this info.
|
||||
Moved: EncryptionDataParameters class to become an internal class of the LoadBalancingClient (it was not used outside of LBC).
|
||||
Removed: Room.StorePlayer was trying to calculate the Master Client (and was wrong about it). This triggered a OnMasterClientSwitched() callback in some cases of rejoining a room.
|
||||
|
||||
Version 4.1.4.9 (12. January 2021)
|
||||
Internal: EncryptionMode.DatagramEncryptionGCMRandomSequence (12) with .DatagramEncryptionGCM (13).
|
||||
Internal: TokenForInit is now an object instead of a string. This also affects the AuthenticationValues class. This enables the server to send a byte[], which is more effective than a string.
|
||||
Changed: Exposed all RoomOptions in Room class. Room.PublishUserId, Room.DeleteNullProperties.
|
||||
Changed: Room.AutoCleanUp can also be set from room options flag.
|
||||
Internal: EnterRoomParams and OpJoinRoom now use a JoinMode (replacing EnterRoomParams.CreateIfNotExists and EnterRoomParams.RejoinOnly).
|
||||
Fixed: Unity-specific CustomTypes.cs to actually only compile for Unity.
|
||||
Updated: EncryptionMode.DatagramEncryptionGCMRandomSequence (12) with .DatagramEncryptionGCM in the LoadBalancingClient.
|
||||
Added: Room.SetExpectedUsers() to let the server know who's also coming (or no longer coming).
|
||||
|
||||
Version 4.1.4.8 (30. November 2020)
|
||||
Added: Conditional compilation in methods which are only for UNITY_WEBGL and UNITY_XBOXONE. The code needed to compile for any platform, even though it should not run anywhere else.
|
||||
Added: Support for UNITY_GAMECORE.
|
||||
Added: Parameter checks for GetGameList (and PUN GetCustomRoomList). The operation is not sent unless the parameters are OK.
|
||||
Added: SupportLogger.OnDestroy to be removed from callback targets.
|
||||
Added: RoomOptions.SuppressPlayerInfo. It can be used to skip join and leave events as well as properties broadcasts in rooms.
|
||||
|
||||
Version 4.1.4.7 (25. November 2020)
|
||||
Fixed: It was possible to trigger multiple calls of Authenticate by calling ConnectToRegionMaster(region) multiple times while being connected to the Name Server.
|
||||
Changed: AuthenticationValues.ToString() to expose less data while showing which values are set.
|
||||
|
||||
Version 4.1.4.6 (17. November 2020)
|
||||
Added: Unity-specific types will now be registered for de/serialization automatically (in Unity projects) using CustomTypesUnity (in LoadBalancingClient constructor).
|
||||
Note: The Player class needs to reference the current room to deserialize, so it is not a serializable type in the Realtime API (but in PUN). Send the Player.ActorNumber instead and the receiver looks up the ActorNumber in the room.
|
||||
Changed: The client will now call disconnect if any operation response has a ReturnCode == ErrorCode.OperationLimitReached. The resulting OnDisconnect callback gets the new DisconnectCause.DisconnectByOperationLimit value.
|
||||
Added: DisconnectCause.DisconnectByDisconnectMessage. If the server has a "low level" reason to disconnect a client, it can now send a Disconnect Message with some debug info. The LoadBalancingClient will log this message as error and disconnect. When this gets used will be added to the docs.
|
||||
Changed: The BroadcastProperties parameter is now always sent to the Game Server, no matter if there are properties. This fixes a recent issue where UserIds were not broadcast in rooms, if no other player properties were used (see v4.1.4.5 changes).
|
||||
Changed: UseAlternativeUdpPorts is replaced by ServerPortOverrides, which allow configuration of ports per server (including using no override).
|
||||
Changed: UseAlternativeUdpPorts is obsolete and no longer used.
|
||||
Changed: LoadBalancingClient.NameServerPortOverride is now named NameServerPortInAppSettings, which is closer to what it resembles. It is overwritten by the ServerPortOverrides if the nameserver port is > 0 there.
|
||||
Added: Struct PhotonPortDefinition.
|
||||
Added: ReplacePortWithAlternative method to wrap up the replacement as per our address rules.
|
||||
Changed: When the client does not connect initially and EnableProtocolFallback is enabled, the ServerPortOverrides are reset and the fallback protocol will use the default ports for the Name Server (defined per protocol).
|
||||
Changed: ReconnectAndRejoin() no longer affects the value IsUsingNameServer. This is only about reconnecting.
|
||||
Changed: ReconnectAndRejoin() was logging error-level messages for expected situations. Now, there are only warnings in the logs. You can simply check the return value, which is false on error (then you should connect again, as usual).
|
||||
Internal: The pool type for paramDictionaryPool, which is used in OpRaiseEvent. It is now a ParameterDictionary, which will wrap some value-types into pooled objects. This helps avoid some memory allocation.
|
||||
Internal: ParameterCode.Secret is now .Token to match the server naming.
|
||||
Changed: Logging level when OpRaiseEvent fails due to leaving a room. It reports as INFO level, when leaving or disconnecting.
|
||||
Added: Error log for Op Authenticate, if the client does not have a token to authenticate on a Master or Game Server. That token comes from the Name Server and should be automatically present and used. Note: The client will send the auth but may fail due to missing token.
|
||||
Changed: LoadBalancingClient.Disconnect() now checks if the client was just created or is disconnecting already. Those cases won't trigger a callback, so there is no surplus attempt to disconnect the peer. Also, the State does not change. An INFO level debug message helps detect those cases.
|
||||
|
||||
Version 4.1.4.5 (02. September 2020)
|
||||
Added: Unity-only ConnectionHandler.StaticReset() to allow entering playmode without domain reload.
|
||||
Added: ErrorCode definition for OperationLimitReached (32743). The client will become unable to call any further operations (to safeguard the room/server) and get disconnected soon after. Currently used for SetProperties only.
|
||||
Fixed: Region pinging for the case that none of the regions answered anything. Then, all region results caused a callback.
|
||||
Added: Support for rooms that suppress room-events.
|
||||
Added: Field bool Room.SuppressRoomEvents to allow checking if room events are suppressed.
|
||||
Changed: If room events are suppressed, the callbacks OnCreatedRoom() and OnJoinedRoom() are now called by the operation response (not waiting for the suppressed events).
|
||||
Renamed: Room.SetRoomFlags() to InternalCacheRoomFlags().
|
||||
Changed: When entering a room, the local players NickName property (255) is not sent if NullOrEmpty.
|
||||
Changed: LoadBalancingApi should not use Unity.Debug unconditionally.
|
||||
Updated: AuthenticationValues reference slightly. Better wording.
|
||||
Updated: Token setter to be protected internal. Only needs to be set by Photon LB API.
|
||||
Fixed: Csproject files for Chat API and LoadBalancing API. The two projects now import the dll based on the current target framework (dynamic).
|
||||
|
||||
Version 4.1.4.4 (6. July 2020)
|
||||
Fixed: AppSettings.CopyTo method. It was not copying the new value EnableProtocolFallback yet.
|
||||
Fixed: The date of version 4.1.4.3 in this changelog.
|
||||
Updated: Error handling in PingWindowsStore.
|
||||
|
||||
Version 4.1.4.3 (24. June 2020)
|
||||
Added: Check that the Dev Region override is in the list of available regions (from Name Server). If not, the first available region is being used.
|
||||
Changed: RegionPinger.Start() now attempts to use a ThreadPool WorkerItem to ping individual regions. Only if that fails, a separate managed Thread is used. This should help avoid running into Thread limits (if any). Builds for NETFX_CORE don't use this.
|
||||
Changed: Error handling in PingMono. SocketExceptions were not handled entirely.
|
||||
Changed: PhotonPingClasses is no longer used. It's empty and can be removed.
|
||||
Changed: Handling of event LobbyStats in case of LoadBalancingPeer.UseByteArraySlicePoolForEvents. This relates to a new option in the PhotonPeer to use pooled memory (wrapped byte[] basically) when incoming events only contain a byte[]. See changes-library.txt and API reference.
|
||||
Added: Initial puzzle pieces for WSS Proxy support. This is not yet funtional. To actually use the proxy settings, a special WebSocket implementation is needed.
|
||||
Changed: The AppSettings.Port value is now also usable when connecting to a Name Server initially. This allows custom ports on a (custom) Name Server.
|
||||
Added: Various details to the SupportLogger output.
|
||||
Changed: Callback OnCreatedRoom() is now also triggered by the event Join. This is the same trigger as for JoinOrCreateRoom (if that created a new room).
|
||||
Added: Player.HasRejoined value. This is set for the local and remote players and can help define the workflow on ReJoin.
|
||||
Added: LoadBalancingClient.EnableProtocolFallback. If enabled, the client will try another protocol if the initial connect to the Name Server fails.
|
||||
Added: AppSettings.EnableProtocolFallback with a default of true. Used in ConnectUsingSettings to set the client's value accordingly.
|
||||
Internal: Added setting the LoadBalancingClient.Server in Connect and other places, so it should be accurate.
|
||||
Added: Checks for connections on WebGL and Xbox One. Settings that can be corrected (to match the platform's requirements) are corrected with a log note. An Exception is thrown, if the settings can't be corrected automatically. This means Connect methods may throw an Exception in some cases, which are to be fixed during development time.
|
||||
|
||||
Version 4.1.4.2 (8. May 2020 - rev5519)
|
||||
Fixed: EventLeave handling. The IsInactive property is to false before doing the OnPlayerLeftRoom callback.
|
||||
Note: Use Player.IsInactive to know the 'reason' for the callback.
|
||||
|
||||
Version 4.1.4.0 (27. April 2020 rev5469)
|
||||
Fixed: When connecting directly to a Master Server (self hosted), the serialization protocol 1.6 is used (not the detault 1.8). This was changed accidentally in v4.1.3.0 (and thus PUN 2.17).
|
||||
Added: Connection state check in CheckIfOpCanBeSent() to fail early if an op can't be sent.
|
||||
Updated: WebRPC handling and added an example to the doc of OnWebRpcResponse.
|
||||
Added: Logging of OnJoinRoomFailed to SupportLogger.
|
||||
Changed: StripKeysWithNullValues to reuse a list of key with null-values. This causes less allocations and garbage to clean. As a downside, the method now runs only for one thread (even for distinct IDictionary instances). This should not be a problem for the expected use case.
|
||||
Changed: The LoadBalancingClient.OnStatusChanged will handle StatusCode.SendError as disconnect due to Exception. Log level INFO should log the inner exception when sending.
|
||||
Fixed: Cleanup of the RoomReference for the local player object on disconnect. This prevents issues when setting properties, after joining and leaving a room (back on Master Server).
|
||||
Internal: LoadBalancingClient.ExpectedProtocol is now a nullable property with private setter. It's used internally (only) to switch to the target transport protocol when Authmode AuthOnceWss is "done".
|
||||
Fixed: EventLeave handling. When a player gets removed from the room, the IsInactive property must be set to false before doing the OnPlayerLeftRoom callback. Use Player.IsInactive to know the 'reason' for the callback.
|
||||
|
||||
Version 4.1.3.0 (23.03.2020 rev5394)
|
||||
Changed: Trying to set empty custom properties Hashtable will fail (return false) and log error.
|
||||
Changed: In online mode, trying to set properties while not joined to a room will fail (return false) and log error. Caching local player properties could be allowed though.
|
||||
Changed: Player.InternalCacheProperties is now "protected internal" and no longer "public".
|
||||
Changed: LoadBalancingClient.OpSetPropertiesOfRoom is now "protected internal" and no longer "public". Use LoadBalancingClient.OpSetCustomPropertiesOfRoom or Room.SetCustomProperties.
|
||||
Changed: StatusCode.EncryptionFailedToEstablish will cause disconnection with DisconnectCause.ExceptionOnConnect.
|
||||
Changed: Fix for Unity cloud build context to prevent sanitization of server settings, it's not needed and for some reason doesn't detect the current server settings.
|
||||
Changed: AppSettings. The NonSerialized attribute is now only used in Unity builds.
|
||||
Changed: With the updated Photon library (v4.1.3.0), the encryption can be set to true on any connection, including WSS. It should not be used conditionally for Auth.
|
||||
Changed: GetPingImplementation() new sets a default PhotonPingImplementation for UNITY_WEBGL, NETFX_CORE and regular Mono/.Net.
|
||||
Internal: New datagram encryption mode DatagramEncryptionGCMRandomSequence (value 12). To be used later on.
|
||||
Internal: The SocketWebTcp.SerializationProtocol is now selected by the PhotonSocket with the Peer's current value. It no longer needs to be set.
|
||||
|
||||
Version 4.1.2.20 (12. December 2019 - rev5296)
|
||||
Added: New callback IErrorInfoCallback.OnErrorInfo when the client receives an ErrorInfo event from the server.
|
||||
Changed: RegionPinger use a singleton MonoBehaviourEmpty for coroutine ping in WebGL in Unity.
|
||||
Added: LoadBalancingClient.SerializationProtocol property to get or set the binary protocol version. Use this always instead of setting it via LoadBalancingPeer.SerializationProtocolType.
|
||||
Added: LoadBalancingClient.ConnectUsingSettings(AppSettings appSettings). This allows Best Region connect, setting a custom name server and much more.
|
||||
Changed: Connect() is now ConnectToMasterServer(). Unlike ConnectUsingSettings(), this requires some settings to be done on the client.
|
||||
Added: Return parameter for all methods that set properties on the server.
|
||||
Note: Room.SetCustomProperties returns bool. If you did override this, your project will have errors, make sure to fix the return type.
|
||||
Changed: Failed Authentication will no longer call the OnDisconnected callback twice. This should better align with expectations.
|
||||
Internal: Pinging regions via coroutine (when Threads are unavailable) now uses only one GameObject for all pinging.
|
||||
|
||||
Version 4.1.2.19 (12. November 2019 - rev5266)
|
||||
Fixed: The ExpectedProtocol is now also set for Authmode "AuthOnce" for ConnectToNameServer and ConnectToRegionMaster.
|
||||
Changed: Player.ToString() to show ActorNumber and NickName. The PlayerProperties are included in ToStringFull().
|
||||
Changed: Logging message for OpAuthenticateOnce (only logged at info level and up).
|
||||
Changed: Avoiding a potential nullreference exception on leave of another player.
|
||||
Updated: Cluster support. The LoadBalancingClient.CurrentCluster gives access to the current cluster. Region.SetCodeAndCluster() now separates the cluster string properly. See ConnectToRegionMaster, too.
|
||||
Fixed: Changing the SupportLogger.Client now skips registering for callbacks, if the new value is null.
|
||||
Changed: Logging of SupportLogger.
|
||||
Added: LoadBalancingClient.OpJoinRandomOrCreateRoom. This can be used to create a room, should random matchmaking fail. This helps avoid race conditons where players can't find one another.
|
||||
Added: Extra null-check in handling of event leave.
|
||||
|
||||
Version 4.1.2.17 (9. August 2019 - rev5188)
|
||||
Changed: SupportLogger. Traffic stats are enabled by default. The PhotonHandler no longer has to enable this.
|
||||
Added: Min/max ping to logged statistics. SupportLogger.TrackValues is invoked to keep track of min/max ping for each connection.
|
||||
Fixed: Statistics logging is started on Connect and stopped OnDisconnected().
|
||||
Changed: Callback registration.
|
||||
Changed: All callback target changes are now queued in one queue and executed in order. This avoids cases where our implementation defined if a callback got added or removed, when a target got added and removed.
|
||||
Changed: Duplicate addition and removal is now prevented.
|
||||
Changed: The callback containers now know and use the LoadBalancingClient to update the targets. This means they don't need lists for additions and removals anymore.
|
||||
Changed: Events will now also update the callback targets before executing the event callback.
|
||||
Changed: WebRpcCallbacksContainer is now also a List<i> like the other containers. This is simpler to maintain. The only "custom" callback implementation is that for events. This was an event delegate before and remains one. No change for this.
|
||||
Changed: The callbacks to OnDisconnected(reason). On timeout or exception, there is now only one callback which happens after the client/peer is actually disconnected. The peer's status changes (timeout, etc.) are used to identify the reason but don't cause a callback on their own. Previously, you could run into more than one OnDisconnected call and fail to reconnect in the first one.
|
||||
Changed: There are now fewer calls of OnDisconnected(). Example: A timeout disconnect caused two calls because signalled the timeout and the second signalled when the client locally finished disconnecting. Now, the DisconnectCause stores the reason and there is only one callback.
|
||||
Changed: Internal use of DisconnectCause. It's now set in LBC.Disconnect() and by errors from the Peer. It's reset to "None" before calls to Peer.Connect().
|
||||
Removed: DisconnectCause enum values that were obsolete already. DisconnectByServerUserLimit is now MaxCcuReached. TimeoutDisconnect is now ClientTimeout. DisconnectByServer is now ServerTimeout.
|
||||
Changed: OnStatusChanged() case Disconnect always sets the CurrentRoom to null. It is no longer used to store the roomname from the Master Server to use on the Game Server.
|
||||
Removed: LoadBalancingClient.didAuthenticate, which is no longer needed for the cleaned up logic.
|
||||
Changed: WebRpcResponse.ReturnCode -> ResultCode and WebRpcResponse.DebugMessage -> Message.
|
||||
Changed: AuthenticationValues.ToString() to include more useful info.
|
||||
Changed: Execution order for disconnect on quit. As OnApplicationQuit is followed by OnDisable, PUN now disconnects in OnDisable, if OnApplicationQuit was called previously. This means that you can now send a final message in OnApplicationQuit. It's not guaranteed to arrive anywhere, as the message won't be repeated on loss.
|
||||
Removed: PhotonHandler.OnDestroy which was not needed anymore. OnDisable is also called when the app quits, so it's adequate to tear down the thread there.
|
||||
Removed: Outdated and unused PingMonoEditor.
|
||||
Changed: Random ID for each ping is now truly randomized.
|
||||
Changed: Region pinging result logging. Per region, all rtts are available, as well as a previous summary (if any). Pun gets it via RegionHandler.GetResults().
|
||||
|
||||
Version 4.1.2.15 (7. June 2019 - rev5137)
|
||||
Added: Options for OpFindFriends to filter which rooms should be returned by the server. The default is the same as before but now the friend list may exclude rooms which are not yet on the game server, invisible, closed (or any combination).
|
||||
Added: SimulateConnectionLoss(bool) to simplify testing of connection loss. This uses the built-in network simulation to get a client timeout disconnect.
|
||||
Removed: EventExt class, which only contained obsolete methods.
|
||||
|
||||
Version 4.1.2.14 (6. May 2019 - rev5097)
|
||||
Changed: Realtime API changes are now listed in a separate changes file.
|
||||
Updated: Demos to make use of Sender and CustomData.
|
||||
|
||||
Version 4.1.2.13 (3. May 2019 - rev5086)
|
||||
Changed: Renamed ClientState items which ended on "Gameserver" and "Masterserver" to using PascalCase "GameServer" and "MasterServer". The previous names are obsolete to ease upgrading this, if needed.
|
||||
Updated: Checks if any given operation can be sent (to the currently connected server) or not. As not all operations are available on all server types, this may help avoid some mismatches.
|
||||
Fixed: BroadcastPropsChangeToAll is applied correctly (it wasn't used properly, when false).
|
||||
Fixed: When in offline mode, OpSetPropertiesOfActor does not get called (it failed due to not being connected).
|
||||
|
||||
Version 4.1.2.11 (15. April 2019 - rev5043)
|
||||
Changed: InLobby property is now checking the State == JoinedLobby, which is analog to InRoom.
|
||||
Changed: IsConnectedAndReady is now also false for ClientState.DisconnectingFromGameserver, DisconnectingFromMasterserver and DisconnectingFromNameServer.
|
||||
Changed: GetGameList check if the filter is null or empty. It's not sent for empty filters.
|
||||
Added: Nintendo Switch as CustomAuthenticationType.
|
||||
|
||||
Version 4.1.2.10 (11. March 2019 - rev5023)
|
||||
Changed: The cached "best region" is cleared whenever a region's pinging finishes. This fixes a potential issue when the BestRegion value is used before the pinging is done. Then, you end up with a wrong selection.
|
||||
Changed: PhotonPing now reuses the Socket per region.
|
||||
Changed: The RegionHandler now checks #if PING_VIA_COROUTINE to use a coroutine instead of a thread (per region). This is for WebGL exports from Unity.
|
||||
Changed: The SupportLogger now uses a Stopwatch to log the time (not depending on Unity's APIs).
|
||||
|
||||
Version 4.1.2.1 (31. July 2018 - rev4787)
|
||||
Changed: OnStateChangeAction is now named StateChanged and provides a "previous state" value. State changes only trigger the event-call when the value actually changes.
|
||||
Renamed: OnEventAction to EventReceived and OnOpResponseAction to OpResponseReceived.
|
||||
Added: LoadBalancingClient now has AddCallbackTarget and RemoveCallbackTarget to simplify registering for various callbacks.
|
||||
|
||||
Version 4.1.2.0 (3. May 2018 - rev4660)
|
||||
Changed: The namespace to the simpler "Photon.Realtime".
|
||||
Added: Various callbacks to signal specific situations / events. To get those, a class must implement the interface and be added to a list of "Targets". See: ILoadBalancingCallbacks.
|
||||
Added: RegionHandler, which provides methods to ping a list of regions and to find the one with best ping. This moves PUN's "Best Region" feature to the LoadBalancing API.
|
||||
Moved: The PhotonPing was part of the dll but is now part of LoadBalancing.
|
||||
Added: LoadBalancingClient.UseAlternativeUdpPorts. This way, UDP may use ports of the Steam Multiplayer port-range by simply replacing existing port strings in addresses.
|
||||
Changed: RaiseEvent now has an overload, that uses RaiseEventOptions and SendOptions. The old variant is obsolete but will still work.
|
||||
Changed: CheckUserOnJoin is now set by default. The RoomOptions.CheckUserOnJoin got removed.
|
||||
Added: Client-side checks and limits for OpFindFriends.
|
||||
Added: Optional parameter sendAuthCookie to OpLeaveRoom. The app can control what's passed from Photon to a server via WebHook.
|
||||
Changes: The room list for lobbies is no longer part of the LoadBalancingClient. Instead, implement the callback for the changed room list.
|
||||
Added: AppSettings, a base class to host AppId and some settings for a title. This will help make it available across products. Right now, the LoadBalancingClient is not using it yet.
|
||||
Changed: Player.ID is now .ActorNumber, which mirrors the server's naming.
|
||||
Fixed: Unity compile defines to support Unity 2018,
|
||||
|
||||
Version 4.1.1.18 (19. December 2017 - rev4540)
|
||||
Changed: FriendInfo.Name is now "UserId", which is up to date with it's usage.
|
||||
Changed: CheckUserOnJoin is now set by default. The RoomOptions.CheckUserOnJoin got removed.
|
||||
|
||||
Version 4.1.1.17 (11. October 2017 - rev4465)
|
||||
Changed: OperationCode const byte Join = 255 is now marked obsolete. We use "JoinGame" instead.
|
||||
Added: DisconnectCause.AuthenticationTicketExpired.
|
||||
Fixed: DebugReturn call in Unity WebGL.
|
||||
|
||||
Version 4.1.1.15 (17. July 2017 - rev4232)
|
||||
Added: LoadBalancingClient.TransportProtocol as shortcut to the use PhotonPeer's TransportProtocol value. This enables setting the protocol easily while not connected.
|
||||
Added: LoadBalancingClient.SocketImplementationConfig as shortcut to modify PhotonPeer's SocketImplementationConfig. This enables you to setup which IPhotonSocket implementation to use for which network protocol.
|
||||
Changed: LoadBalancingPeer.ConfigUnitySockets() to try to find our websocket implementations in the assembly, making the SocketWebTcpCoroutine and SocketWebTcpThread classes optional.
|
||||
Removed: Class "SocketWebTcp" is no longer found by ConfigUnitySockets().
|
||||
|
||||
Version 4.1.1.14 (5. July 2017 - rev4191)
|
||||
Changed: The ClientState "Uninitialized" is now "PeerCreated". This is the initial state. ConnectedToMaster is now ConnectedToMasterserver (both use the same value).
|
||||
Updated: ClientState values descriptions.
|
||||
Internal: GameEnteredOnGameServer() first sets the local player's actorNumber, then updates the player lists.
|
||||
|
||||
Version 4.1.1.8 (24. February 2017 - rev3873)
|
||||
Added: Player.UserId field and code to read published UserIds from the player properties in CacheProperties(). When publishing the UserId in a room (RoomOptions.PublishUserId = true), the UserId becomes available for all players in a room. Good to find/make friends or follow a team player into another room.
|
||||
Added: New matchmaking operation: OpGetGameList(typedLobby, sqlFilter). This fetches a list of rooms that match the filter. You can show lists of rooms with specific properties, if needed (or still use OpJoinRandom).
|
||||
Fixed: WebFlags properties setters.
|
||||
|
||||
Version 4.1.1.7 (16. December 2016)
|
||||
Fixed: Demos with persistent (Turnbased) games. The Memory Demo was not setting up rooms correctly (which led to errors joining them) and used a "join" rather than a "rejoin" to get into saved games (getting added to the room once more).
|
||||
|
||||
Version 4.1.1.6 (9. December 2016 - rev3801)
|
||||
Added: OpJoinRandom will now "remember" to send ExpectedUsers to the Game Server (by caching the value).
|
||||
Added: AuthEvent and it's handling. This (internally sent) event can now update the client's token anytime (before that expires).
|
||||
Added: LoadBalancingClient.OpChangeGroups().
|
||||
Changed: LoadBalancingClient.Disconnect() no longer sets it's own State to Disconnected. It waits till the state-change callback gets called by the lib.
|
||||
|
||||
Version 4.1.1.2 (13. September 2016 - rev3652)
|
||||
Removed: LoadBalancingClient.PlayerName and Player.Name. Were obsolete for more than a year. There is a NickName and the UserId can be set in the AuthValues.
|
||||
Removed: OpJoinRoom() overload with actorNumber. This was obsolete. To enable clients to return to a room, set AuthValues and a userID.
|
||||
Changed: LoadBalancingClient no longer overrides the protocol for Unity WebGL. This is done in the LoadBalancingPeer.ConfigUnitySockets().
|
||||
Changed: GetNameServerAddress() is the same in Chat and LoadBalancing APIs now.
|
||||
Added: DisconnectCause.DisconnectByServerLogic and handling for this case. You can check this DisconnectedCause when the LoadBalancingClient.State is ClientState.Disconnected.
|
||||
Added: Hashtable definition to use Photon's own implementation for Windows Store builds (NETFX_CORE). This must be used but it means you to use the same Hashtable definition in all builds (no matter if 8.1 or 10).
|
||||
Added: Support for WebGL export in Unity.
|
||||
Changed: OnStateChangeAction, OnEventAction and OnOpResponseAction are now events. To register a method in one of those, use += and to deregister you need to use -=. This prevents assigning a new method and de-registering any previously registered ones.
|
||||
|
||||
Version 4.1.1.0 (15. August 2016 - rev3536)
|
||||
Fixed: Room.ClearExpectedUsers() is now sending it's current, local "expected users" to update the server with "CAS" (Check and Swap). This gives the client an update when the values become valid (which updates the local cache after the roundtrip).
|
||||
Added: Support for the 'Server Side Master Client' feature. The Room will read master client updates from the server accordingly. Room.SetMasterClient() enables you to override the server's selection (provided it did not change before your operation gets executed).
|
||||
Changed: Option for bool WebForward into the new "WebFlags". This allows fine control of which data is being sent to WebHooks. This affects all SetProperties, OpWebRPC and the RaiseEventOptions.
|
||||
Added: WebRPC.cs to the LoadBalancing API folder (was available separately before). It contains WebFlags and WebRpcResponse.
|
||||
|
||||
Version 4.1.0.6 (21. June 2016 - rev3376)
|
||||
Fixed: LoadBalancingPeer.OpRaiseEvent(...) to send operations (and events) unencrypted again.
|
||||
|
||||
Version 4.1.0.2 (21. April 2016 - rev3283)
|
||||
Added: Expected Users. This affects the Room, LoadBalancingClient, JoinRoom, JoinOrCreateRoom and CreateRoom.
|
||||
Added: null check in Extensions.StripToStringKeys().
|
||||
Fixed: FriendInfo.IsInRoom, which returned the opposite of it's naming! Also changed FriendInfo ToString() according to PUN's.
|
||||
Added: RoomInfo expectedUsersField, which is updated with room properties (well known ones).
|
||||
Added: Room.ExpectedUsers and ClearExpectedUsers() to expose the list of expected players.
|
||||
Added: RoomInfo.serverSideMasterClient and masterClientIdField (also updated with well known properties).
|
||||
Changed: OpRaiseEvent now re-uses a Dictionary in the LoadBalancingPeer. It uses Clear(), rather than creating a new Dict each time.
|
||||
Changed: AuthenticationValues to also use C# properties and and backup-fields. This is guaranteed to work in Unity.
|
||||
Updated: EventCode ErrorInfo reference with a link to "WebHooks" doc online.
|
||||
Changed: Disconnect handling in the LoadBalancingClient. The client should reset correctly and log info, if it's in a State where a disconnect is a proper error. Note: In some cases like "switching server", a disconnect is expected, so it's not an error then.
|
||||
Fixed: PlayerProperties sent to game server will now include well-known properties again. This fixes the "NickName missing" bug.
|
||||
Fixed: LoadBalancingClient.State value when the client fails to join or create a game on the Master Server. The state is correctly re-set to ClientState.JoinedLobby or ClientState.ConnectedToMaster.
|
||||
Internal: Added private inLobby value, to store if the client was/is in a lobby on the Master Server.
|
||||
Fixed: DemoClient (in demo-loadbalancing) now makes use of the Name Server by using: ConnectToRegionMaster("eu").
|
||||
Added: DemoClient now has debug output when the connection times out or can't be established.
|
||||
|
||||
Version 4.0.5.1 (18. January 2016 - rev3187)
|
||||
Added: OpSetCustomPropertiesOfActor() and OpSetCustomPropertiesOfRoom() now check locally, if the client is currently in a room. It must be, to be able to set these properties. An exception exists for setting properties for the local player's actorNumber, but those are better set via LoadBalancingClient.LocalPlayer.
|
||||
|
||||
Version 4.0.0.11 (28. October 2015 - rev3093)
|
||||
Added: LeaveLobby handling in OnOperationResponse(), which sets the client's state correctly.
|
||||
Changed: Order of execution for Ev Join. If user is known (inactive user rejoins), the player's props are read. The actor list is used, if available.
|
||||
Changed: RoomOptions to use properties with backup-fields to avoid issues in Unity which has issues with Object Initializer (curly brackets).
|
||||
Changed: JoinMode 2 is now "JoinOrRejoin". Was: "Rejoin".
|
||||
Added: ErrorCode constant AuthenticationTicketExpired.
|
||||
Internal: OpJoinRoom, OpCreateRoom and OpJoinRandomRoom no longer use a (growing) list of properties. Instead, classes were created to "sum up" their parameters. The api for games didn't change.
|
||||
Internal: Related to the refactoring of Join/Create, the LoadBalancingClient now creates a Room instance when the client arrived on the GameServer (before, it got created in the initial "create" call).
|
||||
|
||||
Version 4.0.0.10 (14. July 2015 - rev2988)
|
||||
Updated: Description for IsConnectedAndReady.
|
||||
Changed: NameServerAddress to return a fitting address depending on protocol (including WebSocket but not yet RHTTP).
|
||||
Updated: The only name server host is now "ns.exitgames.com", which gets turned into a proper address by protocol.
|
||||
Changed: LoadBalancingClient.CustomAuthenticationValues is now .AuthValues. You can use those values to identify a user, even if you don't setup an external, custom authentication service.
|
||||
Changed: LoadBalancingClient.UserId no longer directly stores the id but puts it into AuthValues. This means, the UserId could also be set via setting AuthValues.
|
||||
Changed: The API of AuthenticationValues. There is now the UserId and AddAuthParameter() replaces the less general SetAuthParameters() (which only set specific key/values).
|
||||
Changed: PlayerName gets renamed to NickName, so PhotonPlayer.Name becomes .NickName and LoadBalancingClient.Name becomes .NickName, too. The old naming is marked as obsolete.
|
||||
Changed: Particle Demo now connects to the Cloud by default (because it's easier to setup and try). You can define your own Master Server (Photon OnPremise) of course.
|
||||
Added: GamePropertyKey.MasterClientId (248) and ParameterCode.MasterClientId (203)
|
||||
Added: ParameterCode.ExpectedValues (231)
|
||||
Added: ParameterCode.SuppressRoomEvents (237)
|
||||
|
||||
Version 4.0.0.6 (05. December 2014 - rev2758)
|
||||
Added: LoadBalancingClient.OpJoinOrCreateRoom overload which has lobby as parameter. If a room gets created, this defines in which lobby it belongs.
|
||||
Changed: LoadBalancingPeer: Added new error code PluginMismatch, documentation for Plugins parameter code.
|
||||
|
||||
Version 4.0.0.1 (17. June 2014 - rev2663)
|
||||
Added: LoadBalancingClient.OpRaiseEvent(). Now that LoadBalancingClient USES a loadBalancingPeer (and doesn't extend it), things are much easier by offering this method, too!
|
||||
Added: LoadBalancingClient.IsConnected and .IsConnectedAndReady to LB API. Going to be part of the API from now on.
|
||||
Removed: Unused fields clientId and clientCount.
|
||||
Changed: Field for internal use "lastJoinActorNumber" is now private as intended.
|
||||
Changed: LoadBalancingClient.Disconnect is now setting it's own state to Disconnected if the connection got closed (as expected).
|
||||
|
||||
Version 4.0.0.0 (23. May 2014 - rev2614)
|
||||
Changed: LoadBalancingClient.FriendList creation/update is delayed until the server's response is available. This avoids cases where the friends are offline for the moment between requesting the update and getting it. Initially, it is null as before.
|
||||
Added: some methods to Player to find next player, etc. Useful for turnbased games to find an opponent.
|
||||
Added: LoadBalancingClient.UserId, which is the ID of a user(account). This is used in FindFriends and when you fetch account-related data (like save-games for Turnbased games). Set it before Connect*(). As fallback when empty during connect, the PlayerName is used instead.
|
||||
Removed: LoadBalancingPeer.OpSetCustomPropertiesOfActor and OpSetPropertyOfRoom which were too special to be so low level. Could be implemented to LBClient.
|
||||
Fixed: OpJoinRandomRoom and OpCreateRoom which didn't reset the ActorNr to claim when entering the room. Depending on previous actions, some calls of those methods did fail when the actorNumber wasn't available.
|
||||
Changed: OperationCode.Rpc is now called OperationCode.WebRpc. It's simply much cleaner (considering PUN has RPCs as well but in a different context).
|
||||
Changed: WebRpcResponse reading to be able to handle additional data.
|
||||
Added: Parameter webForward to: OpSetCustomPropertiesOfRoom and OpSetPropertiesOfRoom. The "old" overloads of these methods are still there, too. If webForward is true, the properties are sent to the WebHooks.
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 530dcba8d8fbbc24a8894ca925cadd8a
|
||||
timeCreated: 1558358700
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user