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:
Texel 2023-01-24 04:03:23 -05:00 committed by Touhexel
parent 8ec6828fa0
commit 9b69003715
119 changed files with 15444 additions and 0 deletions

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8a4408e5f601f774eb5f3fb98b07f009
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee1c48ab6ff903f4c823e7cf48b423e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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");
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 723f710efc79ee0469dbe94bf0cc9ddb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f9a8ceffad89d164e8b5ba037f5d34f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3aba55d840ae78459c990a41ed84f82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0b0942472c9351047afc23b868b562f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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());
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f05b351233593247979ece22db7a9f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 34e9ca7b04eb3424c915e47461a9ca43
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e82402aea03f000428f5ca11fec7ecfc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 64d9415f03804bb40b359b53619181be
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View 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:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 16bfdffb4c4f75240b7dd4720c995413
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2139bd8d6da096942a30073374f7f0ab
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 88e56dcdd2ef1f942b8f19c74c4cf805
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 81339ade5de3e2d419b360cfde8630c1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02e6576dec44f344eb5d58b9dfe5bdc0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f621e51e5c8aead49bd766c4b959a859
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ff7e276bdb10f1a4f9bdbfeaafadf2f2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cfe55c94da5ac634db8d4ca3d1891173
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);

View file

@ -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:

View file

@ -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

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 004b47cc08839884586e6b91d620b418
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 83aeb43ebfe53ea4285ca591ed80efe4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View 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: