Initial commit of Unity files, Network Library, Project settings (Compiler flags for networking to work on Unity), Unity Version 2019.4 LTS
This commit is contained in:
parent
8ec6828fa0
commit
9b69003715
119 changed files with 15444 additions and 0 deletions
8
Assets/Runtime/Photon/PhotonLoadbalancingApi.meta
Normal file
8
Assets/Runtime/Photon/PhotonLoadbalancingApi.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8a4408e5f601f774eb5f3fb98b07f009
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
180
Assets/Runtime/Photon/PhotonLoadbalancingApi/Extensions.cs
Normal file
180
Assets/Runtime/Photon/PhotonLoadbalancingApi/Extensions.cs
Normal file
|
@ -0,0 +1,180 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="Extensions.cs" company="Exit Games GmbH">
|
||||
// Photon Extensions - Copyright (C) 2017 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Provides some helpful methods and extensions for Hashtables, etc.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if UNITY_4_7_OR_NEWER
|
||||
#define UNITY
|
||||
#endif
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using System.Collections;
|
||||
|
||||
#if UNITY
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
#endif
|
||||
#if 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 IDicitionary 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, inlcuding 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 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 removes all key-value pairs that have a null-reference as value.
|
||||
/// Photon properties are removed by setting their value to null.
|
||||
/// Changes the original passed IDictionary!
|
||||
/// </summary>
|
||||
/// <param name="original">The IDictionary to strip of keys with null-values.</param>
|
||||
public static void StripKeysWithNullValues(this IDictionary original)
|
||||
{
|
||||
object[] keys = new object[original.Count];
|
||||
original.Keys.CopyTo(keys, 0);
|
||||
|
||||
for (int index = 0; index < keys.Length; index++)
|
||||
{
|
||||
var key = keys[index];
|
||||
if (original[key] == null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ee1c48ab6ff903f4c823e7cf48b423e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
45
Assets/Runtime/Photon/PhotonLoadbalancingApi/FriendInfo.cs
Normal file
45
Assets/Runtime/Photon/PhotonLoadbalancingApi/FriendInfo.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="FriendInfo.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2013 Exit Games GmbH
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Collection of values related to a user / friend.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
#if UNITY_4_7_OR_NEWER
|
||||
#define UNITY
|
||||
#endif
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
#if 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
|
||||
{
|
||||
public string Name { 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.Name, (!this.IsOnline) ? "offline" : this.IsInRoom ? "playing" : "on master");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 723f710efc79ee0469dbe94bf0cc9ddb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
2582
Assets/Runtime/Photon/PhotonLoadbalancingApi/LoadBalancingClient.cs
Normal file
2582
Assets/Runtime/Photon/PhotonLoadbalancingApi/LoadBalancingClient.cs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f9a8ceffad89d164e8b5ba037f5d34f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1893
Assets/Runtime/Photon/PhotonLoadbalancingApi/LoadBalancingPeer.cs
Normal file
1893
Assets/Runtime/Photon/PhotonLoadbalancingApi/LoadBalancingPeer.cs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a3aba55d840ae78459c990a41ed84f82
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
430
Assets/Runtime/Photon/PhotonLoadbalancingApi/Player.cs
Normal file
430
Assets/Runtime/Photon/PhotonLoadbalancingApi/Player.cs
Normal file
|
@ -0,0 +1,430 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="Player.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2011 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>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#define UNITY
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
#if UNITY
|
||||
using UnityEngine;
|
||||
#endif
|
||||
#if 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 "actorID").
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each player has a actorID, 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 actorID = -1;
|
||||
|
||||
/// <summary>Identifier of this player in current room. Also known as: actorNumber or actorID. 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 ID
|
||||
{
|
||||
get { return this.actorID; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Only one player is controlled by each client. Others are not local.</summary>
|
||||
public readonly bool IsLocal;
|
||||
|
||||
|
||||
/// <summary>Background field for nickName.</summary>
|
||||
private string nickName;
|
||||
|
||||
/// <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, while being in a room)
|
||||
if (this.IsLocal && this.RoomReference != null && this.RoomReference.IsLocalClientInside)
|
||||
{
|
||||
this.SetPlayerNameProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>UserId of the player, available when the room got created with RoomOptions.PublishUserId = true.</summary>
|
||||
/// <remarks>Useful for PhotonNetwork.FindFriends and blocking slots in a room for expected players (e.g. in PhotonNetwork.CreateRoom).</remarks>
|
||||
public string UserId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if this player is the Master Client of the current room.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See also: PhotonNetwork.masterClient.
|
||||
/// </remarks>
|
||||
public bool IsMasterClient
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.RoomReference == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.ID == 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; 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; private set; }
|
||||
|
||||
/// <summary>Creates a Hashtable with all properties (custom and "well known" ones).</summary>
|
||||
/// <remarks>Creates new Hashtables each time used, so if used more often, cache this.</remarks>
|
||||
public Hashtable AllProperties
|
||||
{
|
||||
get
|
||||
{
|
||||
Hashtable allProps = new Hashtable();
|
||||
allProps.Merge(this.CustomProperties);
|
||||
allProps[ActorProperties.PlayerName] = this.nickName;
|
||||
return allProps;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="actorID">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 actorID, bool isLocal) : this(nickName, actorID, 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="actorID">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 actorID, bool isLocal, Hashtable playerProperties)
|
||||
{
|
||||
this.IsLocal = isLocal;
|
||||
this.actorID = actorID;
|
||||
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.ID);
|
||||
}
|
||||
|
||||
/// <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.ID);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public 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. Includes name or player.ID and if it's the Master Client.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return this.NickName + " " + SupportClass.DictionaryToString(this.CustomProperties);
|
||||
}
|
||||
|
||||
/// <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.ID, 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.ID;
|
||||
}
|
||||
|
||||
/// <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.actorID = 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>
|
||||
public void SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedValues = null, WebFlags webFlags = null)
|
||||
{
|
||||
if (propertiesToSet == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable;
|
||||
Hashtable customPropsToCheck = expectedValues.StripToStringKeys() as Hashtable;
|
||||
|
||||
|
||||
// no expected values -> set and callback
|
||||
bool noCas = customPropsToCheck == null || customPropsToCheck.Count == 0;
|
||||
|
||||
|
||||
if (noCas)
|
||||
{
|
||||
this.CustomProperties.Merge(customProps);
|
||||
this.CustomProperties.StripKeysWithNullValues();
|
||||
}
|
||||
|
||||
// send (sync) these new values if in room
|
||||
if (this.RoomReference != null && this.RoomReference.IsLocalClientInside)
|
||||
{
|
||||
this.RoomReference.LoadBalancingClient.loadBalancingPeer.OpSetPropertiesOfActor(this.actorID, customProps, customPropsToCheck, webFlags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Uses OpSetPropertiesOfActor to sync this player's NickName (server is being updated with this.NickName).</summary>
|
||||
private void SetPlayerNameProperty()
|
||||
{
|
||||
if (this.RoomReference != null && this.RoomReference.IsLocalClientInside)
|
||||
{
|
||||
Hashtable properties = new Hashtable();
|
||||
properties[ActorProperties.PlayerName] = this.nickName;
|
||||
this.RoomReference.LoadBalancingClient.loadBalancingPeer.OpSetPropertiesOfActor(this.ID, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Runtime/Photon/PhotonLoadbalancingApi/Player.cs.meta
Normal file
11
Assets/Runtime/Photon/PhotonLoadbalancingApi/Player.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0b0942472c9351047afc23b868b562f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
474
Assets/Runtime/Photon/PhotonLoadbalancingApi/Room.cs
Normal file
474
Assets/Runtime/Photon/PhotonLoadbalancingApi/Room.cs
Normal file
|
@ -0,0 +1,474 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="Room.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2011 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>
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#define UNITY
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using ExitGames.Client.Photon;
|
||||
|
||||
#if 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
|
||||
{
|
||||
protected internal int PlayerTTL;
|
||||
protected internal int RoomTTL;
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the LoadbalancingClient which is currently keeping the connection and state.
|
||||
/// </summary>
|
||||
protected internal LoadBalancingClient LoadBalancingClient { get; set; }
|
||||
|
||||
/// <summary>The name of a room. Unique identifier (per Loadbalancing group) 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (!this.IsLocalClientInside)
|
||||
{
|
||||
LoadBalancingClient.DebugReturn(DebugLevel.WARNING, "Can't set room properties when not in that room.");
|
||||
}
|
||||
|
||||
if (value != this.isOpen)
|
||||
{
|
||||
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 (!this.IsLocalClientInside)
|
||||
{
|
||||
LoadBalancingClient.DebugReturn(DebugLevel.WARNING, "Can't set room properties when not in that room.");
|
||||
}
|
||||
|
||||
if (value != this.isVisible)
|
||||
{
|
||||
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 (!this.IsLocalClientInside)
|
||||
{
|
||||
LoadBalancingClient.DebugReturn(DebugLevel.WARNING, "Can't set room properties when not in that room.");
|
||||
}
|
||||
|
||||
if (value != this.maxPlayers)
|
||||
{
|
||||
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 players;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
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 PhotonNetwork methods: CreateRoom, JoinRoom and JoinOrCreateRoom.
|
||||
/// </remarks>
|
||||
public string[] ExpectedUsers
|
||||
{
|
||||
get { return this.expectedUsers; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ID (actorID, 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 uses autoCleanUp to remove all (buffered) RPCs and instantiated GameObjects when a player leaves.
|
||||
/// </summary>
|
||||
public bool AutoCleanUp
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.autoCleanUp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
protected internal Room(string roomName, RoomOptions options) : 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;
|
||||
this.RoomTTL = options.EmptyRoomTtl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
public virtual void SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedProperties = null, WebFlags webFlags = null)
|
||||
{
|
||||
Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable;
|
||||
|
||||
// merge (and delete null-values), unless we use CAS (expected props)
|
||||
if (expectedProperties == null || expectedProperties.Count == 0)
|
||||
{
|
||||
this.CustomProperties.Merge(customProps);
|
||||
this.CustomProperties.StripKeysWithNullValues();
|
||||
}
|
||||
|
||||
// send (sync) these new values if in room
|
||||
if (this.IsLocalClientInside)
|
||||
{
|
||||
this.LoadBalancingClient.loadBalancingPeer.OpSetPropertiesOfRoom(customProps, expectedProperties, webFlags);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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="propertiesListedInLobby">An array of custom room property names to forward to the lobby.</param>
|
||||
public void SetPropertiesListedInLobby(string[] propertiesListedInLobby)
|
||||
{
|
||||
Hashtable customProps = new Hashtable();
|
||||
customProps[GamePropertyKey.PropsListedInLobby] = propertiesListedInLobby;
|
||||
|
||||
bool sent = this.LoadBalancingClient.OpSetPropertiesOfRoom(customProps);
|
||||
|
||||
if (sent)
|
||||
{
|
||||
this.propertiesListedInLobby = propertiesListedInLobby;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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.ID);
|
||||
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.IsLocalClientInside)
|
||||
{
|
||||
this.LoadBalancingClient.DebugReturn(DebugLevel.WARNING, "SetMasterClient can only be called for the current room (being in one).");
|
||||
return false;
|
||||
}
|
||||
|
||||
Hashtable newProps = new Hashtable() { { GamePropertyKey.MasterClientId, masterClientPlayer.ID } };
|
||||
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.ID))
|
||||
{
|
||||
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.ID] = player;
|
||||
player.RoomReference = this;
|
||||
|
||||
// while initializing the room, the players are not guaranteed to be added in-order
|
||||
if (this.MasterClientId == 0 || player.ID < this.MasterClientId)
|
||||
{
|
||||
this.masterClientId = player.ID;
|
||||
}
|
||||
|
||||
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>
|
||||
/// <returns>The player with the ID or null.</returns>
|
||||
public virtual Player GetPlayer(int id)
|
||||
{
|
||||
Player result = null;
|
||||
this.Players.TryGetValue(id, 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>
|
||||
public void ClearExpectedUsers()
|
||||
{
|
||||
Hashtable props = new Hashtable();
|
||||
props[GamePropertyKey.ExpectedUsers] = new string[0];
|
||||
Hashtable expected = new Hashtable();
|
||||
expected[GamePropertyKey.ExpectedUsers] = this.ExpectedUsers;
|
||||
this.LoadBalancingClient.OpSetPropertiesOfRoom(props, expected);
|
||||
}
|
||||
|
||||
|
||||
/// <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());
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Runtime/Photon/PhotonLoadbalancingApi/Room.cs.meta
Normal file
11
Assets/Runtime/Photon/PhotonLoadbalancingApi/Room.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7f05b351233593247979ece22db7a9f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
261
Assets/Runtime/Photon/PhotonLoadbalancingApi/RoomInfo.cs
Normal file
261
Assets/Runtime/Photon/PhotonLoadbalancingApi/RoomInfo.cs
Normal file
|
@ -0,0 +1,261 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="RoomInfo.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2011 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_OR_NEWER
|
||||
#define UNITY
|
||||
#endif
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using System.Collections;
|
||||
|
||||
#if 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 internally in lobby, to mark rooms that are no longer listed (for being full, closed or hidden).</summary>
|
||||
protected internal 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 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>
|
||||
protected internal 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>
|
||||
/// State if the local client is already in the game or still going to join it on gameserver (in lobby: false).
|
||||
/// </summary>
|
||||
public bool IsLocalClientInside { get; 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];
|
||||
}
|
||||
|
||||
// merge the custom properties (from your application) to the cache (only string-typed keys will be kept)
|
||||
this.customProperties.MergeStringKeys(propertiesToCache);
|
||||
this.customProperties.StripKeysWithNullValues();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 34e9ca7b04eb3424c915e47461a9ca43
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
167
Assets/Runtime/Photon/PhotonLoadbalancingApi/WebRpc.cs
Normal file
167
Assets/Runtime/Photon/PhotonLoadbalancingApi/WebRpc.cs
Normal file
|
@ -0,0 +1,167 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// <copyright file="WebRpc.cs" company="Exit Games GmbH">
|
||||
// Loadbalancing Framework for Photon - Copyright (C) 2016 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_OR_NEWER
|
||||
#define UNITY
|
||||
#endif
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon.LoadBalancing
|
||||
{
|
||||
using ExitGames.Client.Photon;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if 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 method PhotonNetwork.WebRpc.<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>ReturnCode of the WebService that answered the WebRpc.</summary>
|
||||
/// <remarks>
|
||||
/// 1 is: "OK" for WebRPCs.<br/>
|
||||
/// -1 is: No ReturnCode by WebRpc service (check OperationResponse.ReturnCode).<br/>
|
||||
/// Other ReturnCodes are defined by the individual WebRpc and service.
|
||||
/// </remarks>
|
||||
public int ReturnCode { get; private set; }
|
||||
|
||||
/// <summary>Might be empty or null.</summary>
|
||||
public string DebugMessage { get; private set; }
|
||||
|
||||
/// <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;
|
||||
response.Parameters.TryGetValue(ParameterCode.UriPath, out value);
|
||||
this.Name = value as string;
|
||||
|
||||
response.Parameters.TryGetValue(ParameterCode.WebRpcReturnCode, out value);
|
||||
this.ReturnCode = (value != null) ? (byte)value : -1;
|
||||
|
||||
response.Parameters.TryGetValue(ParameterCode.WebRpcParameters, out value);
|
||||
this.Parameters = value as Dictionary<string, object>;
|
||||
|
||||
response.Parameters.TryGetValue(ParameterCode.WebRpcReturnMessage, out value);
|
||||
this.DebugMessage = 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.ReturnCode, this.DebugMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
Assets/Runtime/Photon/PhotonLoadbalancingApi/WebRpc.cs.meta
Normal file
11
Assets/Runtime/Photon/PhotonLoadbalancingApi/WebRpc.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e82402aea03f000428f5ca11fec7ecfc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Runtime/Photon/Plugins.meta
Normal file
8
Assets/Runtime/Photon/Plugins.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 64d9415f03804bb40b359b53619181be
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
Assets/Runtime/Photon/Plugins/Photon3Unity3D.dll
Normal file
BIN
Assets/Runtime/Photon/Plugins/Photon3Unity3D.dll
Normal file
Binary file not shown.
113
Assets/Runtime/Photon/Plugins/Photon3Unity3D.dll.meta
Normal file
113
Assets/Runtime/Photon/Plugins/Photon3Unity3D.dll.meta
Normal file
|
@ -0,0 +1,113 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3124388131362e24dbb79c27c2fc0a89
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
- first:
|
||||
'': Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 0
|
||||
Exclude Editor: 0
|
||||
Exclude Linux: 0
|
||||
Exclude Linux64: 0
|
||||
Exclude LinuxUniversal: 0
|
||||
Exclude OSXUniversal: 0
|
||||
Exclude WebGL: 0
|
||||
Exclude Win: 0
|
||||
Exclude Win64: 0
|
||||
Exclude iOS: 0
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARMv7
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
- first:
|
||||
Facebook: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Facebook: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Linux
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: x86
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: x86_64
|
||||
- first:
|
||||
Standalone: LinuxUniversal
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
WebGL: WebGL
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
2109
Assets/Runtime/Photon/Plugins/Photon3Unity3D.xml
Normal file
2109
Assets/Runtime/Photon/Plugins/Photon3Unity3D.xml
Normal file
File diff suppressed because it is too large
Load diff
7
Assets/Runtime/Photon/Plugins/Photon3Unity3D.xml.meta
Normal file
7
Assets/Runtime/Photon/Plugins/Photon3Unity3D.xml.meta
Normal file
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 16bfdffb4c4f75240b7dd4720c995413
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Runtime/Photon/Plugins/PhotonWebSocket.meta
Normal file
8
Assets/Runtime/Photon/Plugins/PhotonWebSocket.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2139bd8d6da096942a30073374f7f0ab
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
37
Assets/Runtime/Photon/Plugins/PhotonWebSocket/PingHttp.cs
Normal file
37
Assets/Runtime/Photon/Plugins/PhotonWebSocket/PingHttp.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
#if UNITY_WEBGL
|
||||
|
||||
namespace ExitGames.Client.Photon
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
public class PingHttp : PhotonPing
|
||||
{
|
||||
private WWW webRequest;
|
||||
|
||||
public override bool StartPing(string address)
|
||||
{
|
||||
address = "https://" + address + "/photon/m/?ping&r=" + UnityEngine.Random.Range(0, 10000);
|
||||
Debug.Log("StartPing: " + address);
|
||||
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
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 88e56dcdd2ef1f942b8f19c74c4cf805
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,22 @@
|
|||
To use WebSockets with the Photon C# library, you need to import this folder into your project.
|
||||
|
||||
SocketWebTcpThread can be used in all cases where the Thread class is available
|
||||
SocketWebTcpCoroutine must be used for WebGL exports and when the Thread class is unavailable
|
||||
|
||||
WebSocket.cs is used in all exports
|
||||
websocket-sharp.dll is used when not exporting to a browser (and in Unity Editor)
|
||||
WebSocket.jslib is used for WebGL exports by Unity (and must be setup accordingly)
|
||||
|
||||
|
||||
A WebGL export from Unity will find and use these files internally.
|
||||
Any other project will have to setup a few things in code:
|
||||
|
||||
Define "WEBSOCKET" for your project to make the SocketWebTcp classes available.
|
||||
To make a connection by WebSocket, setup the PhotonPeer (LoadBalancingPeer, ChatPeer, etc) similar to this:
|
||||
|
||||
Debug.Log("WSS Setup");
|
||||
PhotonPeer.TransportProtocol = ConnectionProtocol.WebSocket; // or WebSocketSecure for a release
|
||||
PhotonPeer.SocketImplementationConfig[ConnectionProtocol.WebSocket] = typeof(SocketWebTcpThread);
|
||||
PhotonPeer.SocketImplementationConfig[ConnectionProtocol.WebSocketSecure] = typeof(SocketWebTcpThread);
|
||||
|
||||
//PhotonPeer.DebugOut = DebugLevel.INFO; // this would show some logs from the SocketWebTcp implementation
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 81339ade5de3e2d419b360cfde8630c1
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,312 @@
|
|||
#if UNITY_WEBGL || UNITY_XBOXONE || WEBSOCKET
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="SocketWebTcpCoroutine.cs" company="Exit Games GmbH">
|
||||
// Copyright (c) Exit Games GmbH. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Internal class to encapsulate the network i/o functionality for the realtime libary.
|
||||
// </summary>
|
||||
// <author>developer@exitgames.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using SupportClassPun = ExitGames.Client.Photon.SupportClass;
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon
|
||||
{
|
||||
#if UNITY_5_3 || UNITY_5_3_OR_NEWER
|
||||
/// <summary>
|
||||
/// Yield Instruction to Wait for real seconds. Very important to keep connection working if Time.TimeScale is altered, we still want accurate network events
|
||||
/// </summary>
|
||||
public sealed class WaitForRealSeconds : CustomYieldInstruction
|
||||
{
|
||||
private readonly float _endTime;
|
||||
|
||||
public override bool keepWaiting
|
||||
{
|
||||
get { return _endTime > Time.realtimeSinceStartup; }
|
||||
}
|
||||
|
||||
public WaitForRealSeconds(float seconds)
|
||||
{
|
||||
_endTime = Time.realtimeSinceStartup + seconds;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Internal class to encapsulate the network i/o functionality for the realtime libary.
|
||||
/// </summary>
|
||||
public class SocketWebTcpCoroutine : IPhotonSocket, IDisposable
|
||||
{
|
||||
private WebSocket sock;
|
||||
|
||||
private GameObject websocketConnectionObject;
|
||||
|
||||
/// <summary>Constructor. Checks if "expected" protocol matches.</summary>
|
||||
public SocketWebTcpCoroutine(PeerBase npeer) : base(npeer)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.INFO, "new SocketWebTcpCoroutine(). Server: " + this.ConnectAddress + " protocol: " + this.Protocol);
|
||||
}
|
||||
|
||||
switch (this.Protocol)
|
||||
{
|
||||
case ConnectionProtocol.WebSocket:
|
||||
break;
|
||||
case ConnectionProtocol.WebSocketSecure:
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Protocol '" + this.Protocol + "' not supported by WebSocket");
|
||||
}
|
||||
|
||||
this.PollReceive = false;
|
||||
}
|
||||
|
||||
/// <summary>Connect the websocket (base checks if this was already connected).</summary>
|
||||
public override bool Connect()
|
||||
{
|
||||
bool baseOk = base.Connect();
|
||||
if (!baseOk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Connecting;
|
||||
|
||||
|
||||
if (this.websocketConnectionObject != null)
|
||||
{
|
||||
UnityEngine.Object.Destroy(this.websocketConnectionObject);
|
||||
}
|
||||
|
||||
this.websocketConnectionObject = new GameObject("websocketConnectionObject");
|
||||
MonoBehaviour mb = this.websocketConnectionObject.AddComponent<MonoBehaviourExt>();
|
||||
this.websocketConnectionObject.hideFlags = HideFlags.HideInHierarchy;
|
||||
UnityEngine.Object.DontDestroyOnLoad(this.websocketConnectionObject);
|
||||
|
||||
|
||||
this.sock = new WebSocket(new Uri(this.ConnectAddress));
|
||||
// connecting the socket is off-loaded into the coroutine which we start now
|
||||
|
||||
mb.StartCoroutine(this.ReceiveLoop());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Disconnect the websocket (no matter what it does right now).</summary>
|
||||
public override bool Disconnect()
|
||||
{
|
||||
if (this.State == PhotonSocketState.Disconnecting || this.State == PhotonSocketState.Disconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.INFO, "SocketWebTcpCoroutine.Disconnect()");
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Disconnecting;
|
||||
if (this.sock != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.sock.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
this.sock = null;
|
||||
}
|
||||
|
||||
if (this.websocketConnectionObject != null)
|
||||
{
|
||||
UnityEngine.Object.Destroy(this.websocketConnectionObject);
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Disconnected;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Calls Disconnect.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disconnect();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Used by TPeer to send.</summary>
|
||||
public override PhotonSocketError Send(byte[] data, int length)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Connected)
|
||||
{
|
||||
return PhotonSocketError.Skipped;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ALL, "Sending: " + SupportClassPun.ByteArrayToString(data));
|
||||
}
|
||||
|
||||
this.sock.Send(data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Cannot send to: " + this.ConnectAddress + ". " + e.Message);
|
||||
|
||||
if (this.State == PhotonSocketState.Connected)
|
||||
{
|
||||
this.HandleException(StatusCode.Exception);
|
||||
}
|
||||
return PhotonSocketError.Exception;
|
||||
}
|
||||
|
||||
return PhotonSocketError.Success;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Not used currently.</summary>
|
||||
public override PhotonSocketError Receive(out byte[] data)
|
||||
{
|
||||
data = null;
|
||||
return PhotonSocketError.NoData;
|
||||
}
|
||||
|
||||
/// <summary>Used by TPeer to receive.</summary>
|
||||
public IEnumerator ReceiveLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.sock.Connect();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ConnectAddress + "' Exception: " + e);
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
}
|
||||
|
||||
while (this.State == PhotonSocketState.Connecting && this.sock != null && !this.sock.Connected && this.sock.Error == null)
|
||||
{
|
||||
#if UNITY_5_3 || UNITY_5_3_OR_NEWER
|
||||
yield return new WaitForRealSeconds(0.02f);
|
||||
#else
|
||||
float waittime = Time.realtimeSinceStartup + 0.2f;
|
||||
while (Time.realtimeSinceStartup < waittime) yield return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (this.sock == null || this.sock.Error != null)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Exiting receive thread. Server: " + this.ConnectAddress + " Error: " + ((this.sock!=null)?this.sock.Error:"socket==null"));
|
||||
this.HandleException(StatusCode.ExceptionOnConnect);
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
// connected
|
||||
this.State = PhotonSocketState.Connected;
|
||||
this.peerBase.OnConnect();
|
||||
|
||||
|
||||
byte[] inBuff = null;
|
||||
|
||||
// receiving
|
||||
while (this.State == PhotonSocketState.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.sock.Error != null)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Exiting receive thread (inside loop). Server: " + this.ConnectAddress + " Error: " + this.sock.Error);
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
inBuff = this.sock.Recv();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ConnectAddress + "' Exception: " + e);
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (inBuff == null || inBuff.Length == 0)
|
||||
{
|
||||
// nothing received. wait a bit, try again
|
||||
#if UNITY_5_3 || UNITY_5_3_OR_NEWER
|
||||
yield return new WaitForRealSeconds(0.02f);
|
||||
#else
|
||||
float waittime = Time.realtimeSinceStartup + 0.02f;
|
||||
while (Time.realtimeSinceStartup < waittime) yield return 0;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
if (inBuff.Length < 0)
|
||||
{
|
||||
// got disconnected (from remote or net)
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
this.HandleException(StatusCode.DisconnectByServer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ALL, "TCP << " + inBuff.Length + " = " + SupportClassPun.ByteArrayToString(inBuff));
|
||||
}
|
||||
|
||||
this.HandleReceivedDatagram(inBuff, inBuff.Length, false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ConnectAddress + "' Exception: " + e);
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
internal class MonoBehaviourExt : MonoBehaviour { }
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 02e6576dec44f344eb5d58b9dfe5bdc0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,268 @@
|
|||
#if WEBSOCKET
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="SocketWebTcpThread.cs" company="Exit Games GmbH">
|
||||
// Copyright (c) Exit Games GmbH. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Internal class to encapsulate the network i/o functionality for the realtime libary.
|
||||
// </summary>
|
||||
// <author>developer@photonengine.com</author>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security;
|
||||
using System.Threading;
|
||||
|
||||
|
||||
namespace ExitGames.Client.Photon
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal class to encapsulate the network i/o functionality for the realtime libary.
|
||||
/// </summary>
|
||||
public class SocketWebTcpThread : IPhotonSocket, IDisposable
|
||||
{
|
||||
private WebSocket sock;
|
||||
|
||||
|
||||
/// <summary>Constructor. Checks if "expected" protocol matches.</summary>
|
||||
public SocketWebTcpThread(PeerBase npeer) : base(npeer)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.INFO, "new SocketWebTcpThread(). Server: " + this.ConnectAddress + " protocol: " + this.Protocol+ " State: " + this.State);
|
||||
}
|
||||
|
||||
switch (this.Protocol)
|
||||
{
|
||||
case ConnectionProtocol.WebSocket:
|
||||
break;
|
||||
case ConnectionProtocol.WebSocketSecure:
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Protocol '" + this.Protocol + "' not supported by WebSocket");
|
||||
}
|
||||
|
||||
this.PollReceive = false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Connect the websocket (base checks if this was already connected).</summary>
|
||||
public override bool Connect()
|
||||
{
|
||||
bool baseOk = base.Connect();
|
||||
if (!baseOk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Connecting;
|
||||
|
||||
Thread dns = new Thread(this.DnsAndConnect);
|
||||
dns.Name = "photon dns thread";
|
||||
dns.IsBackground = true;
|
||||
dns.Start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Internally used by this class to resolve the hostname to IP.</summary>
|
||||
internal void DnsAndConnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
IPAddress ipAddress = IPhotonSocket.GetIpAddress(this.ServerAddress);
|
||||
if (ipAddress == null)
|
||||
{
|
||||
throw new ArgumentException("DNS failed to resolve for address: " + this.ServerAddress);
|
||||
}
|
||||
|
||||
this.AddressResolvedAsIpv6 = ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6;
|
||||
|
||||
|
||||
if (this.State != PhotonSocketState.Connecting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.sock = new WebSocket(new Uri(this.ConnectAddress));
|
||||
this.sock.Connect();
|
||||
|
||||
while (this.sock != null && !this.sock.Connected && this.sock.Error == null)
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
}
|
||||
|
||||
if (this.sock.Error != null)
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Exiting receive thread. Server: " + this.ConnectAddress + " Error: " + this.sock.Error);
|
||||
this.HandleException(StatusCode.ExceptionOnConnect);
|
||||
return;
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Connected;
|
||||
this.peerBase.OnConnect();
|
||||
}
|
||||
catch (SecurityException se)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Connect() to '" + this.ConnectAddress + "' failed: " + se.ToString());
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.SecurityExceptionOnConnect);
|
||||
return;
|
||||
}
|
||||
catch (Exception se)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Connect() to '" + this.ConnectAddress + "' failed: " + se.ToString());
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnConnect);
|
||||
return;
|
||||
}
|
||||
|
||||
Thread run = new Thread(new ThreadStart(this.ReceiveLoop));
|
||||
run.Name = "photon receive thread";
|
||||
run.IsBackground = true;
|
||||
run.Start();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Disconnect the websocket (no matter what it does right now).</summary>
|
||||
public override bool Disconnect()
|
||||
{
|
||||
if (this.State == PhotonSocketState.Disconnecting || this.State == PhotonSocketState.Disconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.ReportDebugOfLevel(DebugLevel.INFO))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.INFO, "SocketWebTcpThread.Disconnect()");
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Disconnecting;
|
||||
if (this.sock != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.sock.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
this.sock = null;
|
||||
}
|
||||
|
||||
this.State = PhotonSocketState.Disconnected;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Calls Disconnect.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Disconnect();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Used by TPeer to send.</summary>
|
||||
public override PhotonSocketError Send(byte[] data, int length)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Connected)
|
||||
{
|
||||
return PhotonSocketError.Skipped;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ALL, "Sending: " + SupportClass.ByteArrayToString(data));
|
||||
}
|
||||
|
||||
this.sock.Send(data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Cannot send to: " + this.ConnectAddress + ". " + e.Message);
|
||||
|
||||
if (this.State == PhotonSocketState.Connected)
|
||||
{
|
||||
this.HandleException(StatusCode.Exception);
|
||||
}
|
||||
return PhotonSocketError.Exception;
|
||||
}
|
||||
|
||||
return PhotonSocketError.Success;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Not used currently.</summary>
|
||||
public override PhotonSocketError Receive(out byte[] data)
|
||||
{
|
||||
data = null;
|
||||
return PhotonSocketError.NoData;
|
||||
}
|
||||
|
||||
/// <summary>Used by TPeer to receive.</summary>
|
||||
public void ReceiveLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (this.State == PhotonSocketState.Connected)
|
||||
{
|
||||
if (this.sock.Error != null)
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ERROR, "Exiting receive thread (inside loop). Server: " + this.ConnectAddress + " Error: " + this.sock.Error);
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
byte[] inBuff = this.sock.Recv();
|
||||
if (inBuff == null || inBuff.Length == 0)
|
||||
{
|
||||
Thread.Sleep(0);
|
||||
continue;
|
||||
}
|
||||
if (inBuff.Length < 0)
|
||||
{
|
||||
// got disconnected (from remote or net)
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
this.HandleException(StatusCode.DisconnectByServer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ALL))
|
||||
{
|
||||
this.Listener.DebugReturn(DebugLevel.ALL, "TCP << " + inBuff.Length + " = " + SupportClass.ByteArrayToString(inBuff));
|
||||
}
|
||||
|
||||
this.HandleReceivedDatagram(inBuff, inBuff.Length, false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected)
|
||||
{
|
||||
if (this.ReportDebugOfLevel(DebugLevel.ERROR))
|
||||
{
|
||||
this.EnqueueDebugReturn(DebugLevel.ERROR, "Receive issue. State: " + this.State + ". Server: '" + this.ConnectAddress + "' Exception: " + e);
|
||||
}
|
||||
|
||||
this.HandleException(StatusCode.ExceptionOnReceive);
|
||||
}
|
||||
}
|
||||
|
||||
this.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f621e51e5c8aead49bd766c4b959a859
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ff7e276bdb10f1a4f9bdbfeaafadf2f2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,155 @@
|
|||
#if UNITY_WEBGL || UNITY_XBOXONE || WEBSOCKET
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
using System.Runtime.InteropServices;
|
||||
#else
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Authentication;
|
||||
#endif
|
||||
|
||||
|
||||
public class WebSocket
|
||||
{
|
||||
private Uri mUrl;
|
||||
|
||||
public WebSocket(Uri url)
|
||||
{
|
||||
mUrl = url;
|
||||
|
||||
string protocol = mUrl.Scheme;
|
||||
if (!protocol.Equals("ws") && !protocol.Equals("wss"))
|
||||
throw new ArgumentException("Unsupported protocol: " + protocol);
|
||||
}
|
||||
|
||||
public void SendString(string str)
|
||||
{
|
||||
Send(Encoding.UTF8.GetBytes (str));
|
||||
}
|
||||
|
||||
public string RecvString()
|
||||
{
|
||||
byte[] retval = Recv();
|
||||
if (retval == null)
|
||||
return null;
|
||||
return Encoding.UTF8.GetString (retval);
|
||||
}
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
[DllImport("__Internal")]
|
||||
private static extern int SocketCreate (string url);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int SocketState (int socketInstance);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void SocketSend (int socketInstance, byte[] ptr, int length);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void SocketRecv (int socketInstance, byte[] ptr, int length);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int SocketRecvLength (int socketInstance);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern void SocketClose (int socketInstance);
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern int SocketError (int socketInstance, byte[] ptr, int length);
|
||||
|
||||
int m_NativeRef = 0;
|
||||
|
||||
public void Send(byte[] buffer)
|
||||
{
|
||||
SocketSend (m_NativeRef, buffer, buffer.Length);
|
||||
}
|
||||
|
||||
public byte[] Recv()
|
||||
{
|
||||
int length = SocketRecvLength (m_NativeRef);
|
||||
if (length == 0)
|
||||
return null;
|
||||
byte[] buffer = new byte[length];
|
||||
SocketRecv (m_NativeRef, buffer, length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
m_NativeRef = SocketCreate (mUrl.ToString());
|
||||
|
||||
//while (SocketState(m_NativeRef) == 0)
|
||||
// yield return 0;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
SocketClose(m_NativeRef);
|
||||
}
|
||||
|
||||
public bool Connected
|
||||
{
|
||||
get { return SocketState(m_NativeRef) != 0; }
|
||||
}
|
||||
|
||||
public string Error
|
||||
{
|
||||
get {
|
||||
const int bufsize = 1024;
|
||||
byte[] buffer = new byte[bufsize];
|
||||
int result = SocketError (m_NativeRef, buffer, bufsize);
|
||||
|
||||
if (result == 0)
|
||||
return null;
|
||||
|
||||
return Encoding.UTF8.GetString (buffer);
|
||||
}
|
||||
}
|
||||
#else
|
||||
WebSocketSharp.WebSocket m_Socket;
|
||||
Queue<byte[]> m_Messages = new Queue<byte[]>();
|
||||
bool m_IsConnected = false;
|
||||
string m_Error = null;
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
m_Socket = new WebSocketSharp.WebSocket(mUrl.ToString(), new string[] { "GpBinaryV16" });// modified by TS
|
||||
m_Socket.SslConfiguration.EnabledSslProtocols = m_Socket.SslConfiguration.EnabledSslProtocols | (SslProtocols)(3072| 768);
|
||||
m_Socket.OnMessage += (sender, e) => m_Messages.Enqueue(e.RawData);
|
||||
m_Socket.OnOpen += (sender, e) => m_IsConnected = true;
|
||||
m_Socket.OnError += (sender, e) => m_Error = e.Message + (e.Exception == null ? "" : " / " + e.Exception);
|
||||
m_Socket.ConnectAsync();
|
||||
}
|
||||
|
||||
public bool Connected { get { return m_IsConnected; } }// added by TS
|
||||
|
||||
|
||||
public void Send(byte[] buffer)
|
||||
{
|
||||
m_Socket.Send(buffer);
|
||||
}
|
||||
|
||||
public byte[] Recv()
|
||||
{
|
||||
if (m_Messages.Count == 0)
|
||||
return null;
|
||||
return m_Messages.Dequeue();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
m_Socket.Close();
|
||||
}
|
||||
|
||||
public string Error
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Error;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cfe55c94da5ac634db8d4ca3d1891173
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,116 @@
|
|||
var LibraryWebSockets = {
|
||||
$webSocketInstances: [],
|
||||
|
||||
SocketCreate: function(url)
|
||||
{
|
||||
var str = Pointer_stringify(url);
|
||||
var socket = {
|
||||
socket: new WebSocket(str, ['GpBinaryV16']),
|
||||
buffer: new Uint8Array(0),
|
||||
error: null,
|
||||
messages: []
|
||||
}
|
||||
socket.socket.binaryType = 'arraybuffer';
|
||||
socket.socket.onmessage = function (e) {
|
||||
// if (e.data instanceof Blob)
|
||||
// {
|
||||
// var reader = new FileReader();
|
||||
// reader.addEventListener("loadend", function() {
|
||||
// var array = new Uint8Array(reader.result);
|
||||
// socket.messages.push(array);
|
||||
// });
|
||||
// reader.readAsArrayBuffer(e.data);
|
||||
// }
|
||||
if (e.data instanceof ArrayBuffer)
|
||||
{
|
||||
var array = new Uint8Array(e.data);
|
||||
socket.messages.push(array);
|
||||
}
|
||||
};
|
||||
socket.socket.onclose = function (e) {
|
||||
if (e.code != 1000)
|
||||
{
|
||||
if (e.reason != null && e.reason.length > 0)
|
||||
socket.error = e.reason;
|
||||
else
|
||||
{
|
||||
switch (e.code)
|
||||
{
|
||||
case 1001:
|
||||
socket.error = "Endpoint going away.";
|
||||
break;
|
||||
case 1002:
|
||||
socket.error = "Protocol error.";
|
||||
break;
|
||||
case 1003:
|
||||
socket.error = "Unsupported message.";
|
||||
break;
|
||||
case 1005:
|
||||
socket.error = "No status.";
|
||||
break;
|
||||
case 1006:
|
||||
socket.error = "Abnormal disconnection.";
|
||||
break;
|
||||
case 1009:
|
||||
socket.error = "Data frame too large.";
|
||||
break;
|
||||
default:
|
||||
socket.error = "Error "+e.code;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var instance = webSocketInstances.push(socket) - 1;
|
||||
return instance;
|
||||
},
|
||||
|
||||
SocketState: function (socketInstance)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
return socket.socket.readyState;
|
||||
},
|
||||
|
||||
SocketError: function (socketInstance, ptr, bufsize)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
if (socket.error == null)
|
||||
return 0;
|
||||
var str = socket.error.slice(0, Math.max(0, bufsize - 1));
|
||||
writeStringToMemory(str, ptr, false);
|
||||
return 1;
|
||||
},
|
||||
|
||||
SocketSend: function (socketInstance, ptr, length)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
socket.socket.send (HEAPU8.buffer.slice(ptr, ptr+length));
|
||||
},
|
||||
|
||||
SocketRecvLength: function(socketInstance)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
if (socket.messages.length == 0)
|
||||
return 0;
|
||||
return socket.messages[0].length;
|
||||
},
|
||||
|
||||
SocketRecv: function (socketInstance, ptr, length)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
if (socket.messages.length == 0)
|
||||
return 0;
|
||||
if (socket.messages[0].length > length)
|
||||
return 0;
|
||||
HEAPU8.set(socket.messages[0], ptr);
|
||||
socket.messages = socket.messages.slice(1);
|
||||
},
|
||||
|
||||
SocketClose: function (socketInstance)
|
||||
{
|
||||
var socket = webSocketInstances[socketInstance];
|
||||
socket.socket.close();
|
||||
}
|
||||
};
|
||||
|
||||
autoAddDeps(LibraryWebSockets, '$webSocketInstances');
|
||||
mergeInto(LibraryManager.library, LibraryWebSockets);
|
|
@ -0,0 +1,34 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 21d8d0d3e9f68ae43975534c13f23964
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Facebook: WebGL
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
WebGL: WebGL
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,3 @@
|
|||
websocket-sharp.dll built from https://github.com/sta/websocket-sharp.git, commit 869dfb09778de51081b0ae64bd2c3217cffe0699 on Aug 24, 2016.
|
||||
|
||||
websocket-sharp is provided under The MIT License as mentioned here: https://github.com/sta/websocket-sharp#license
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 004b47cc08839884586e6b91d620b418
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Binary file not shown.
|
@ -0,0 +1,30 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0fb606431450aa343839e85c42dd9660
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Runtime/Photon/Plugins/Wsa.meta
Normal file
8
Assets/Runtime/Photon/Plugins/Wsa.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 83aeb43ebfe53ea4285ca591ed80efe4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
Assets/Runtime/Photon/Plugins/Wsa/Photon3Unity3D.dll
Normal file
BIN
Assets/Runtime/Photon/Plugins/Wsa/Photon3Unity3D.dll
Normal file
Binary file not shown.
114
Assets/Runtime/Photon/Plugins/Wsa/Photon3Unity3D.dll.meta
Normal file
114
Assets/Runtime/Photon/Plugins/Wsa/Photon3Unity3D.dll.meta
Normal file
|
@ -0,0 +1,114 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8b3cc63923895354ca37992f6c87ab63
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
- first:
|
||||
'': Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 1
|
||||
Exclude Editor: 1
|
||||
Exclude Linux: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude LinuxUniversal: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude WebGL: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
Exclude iOS: 1
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: ARMv7
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
- first:
|
||||
Facebook: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Facebook: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Linux
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
- first:
|
||||
Standalone: LinuxUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
WebGL: WebGL
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue