PhotonPlayer.ipAddress and PhotonPlayer.port

jashan
jashan ✭✭
One thing I found a bit tricky with Photon Unity Networking was that it didn't support PhotonPlayer.ipAddress and PhotonPlayer.port. I'm using this for a couple of things on the server (Unity game server), so not having it was a bit of trouble for me when moving over from Unity networking to Photon Unity Networking.

However, it's not so hard to fix (it was kind of hard for me because I ran into a few dead ends here and there - but that's just because I'm still learning my way around in Photon / Photon Unity Networking / Loadbalancer).

You need a few changes both in Photon Unity Networking and Loadbalancer for this to work. For me, I only need the ipAddresses / ports for the MasterClient and IIRC, Unity networking also only has all ipAddresses in the "server", so that should be the same as Unity now (i.e. MasterClient has all ipAddresses, clients only have their own). In my set up, only servers can be MasterClients and a client can never "become" a MasterClient - so that's an assumption I'm using (not sure if a promoted MasterClient would get the data on promotion ... probably not, so if you need it, you'll have to implement it ;-) ).

So, the changes I applied in Photon Unity Networking (Unity side of things):

PhotonPlayer.cs:
public string ipAddress {
        get { 
            // instead of using "PlayerIP", it's better to use some constant
            // (last code snippet in this posting for one possible option)
            if (mCustomProperties.ContainsKey("PlayerIP"))
                return (string) mCustomProperties["PlayerIP"];

            return "NETW_ERROR";  
        }
    }

    public int port {
        get { 
            if (mCustomProperties.ContainsKey("PlayerPort"))
                return (int) mCustomProperties["PlayerPort"];

            return -1; 
        }
    }

And in NetworkingPeer.cs in the method "OnOperationResponse" (this is a longer method, so I'm just posting my change which is changing the order of ChangeLocalID and readOutStandardProperties; be careful - this also include another change of mine where actorNr are short instead of int - with the original Photon Unity Networking, this is int!):
this.State = global::PeerState.Joined;
this.mRoomToGetInto.IsLocalClientInside = true;

// the local player's actor-properties are not returned in join-result. add this player to the list
// JC (2011-09-21): actorNr is now short (only locally, for me ;-) )
short localActorNr = (short) (int) operationResponse[ParameterCode.ActorNr]; 
ChangeLocalID(localActorNr);

// JC (2011-10-07): Changed order because we need to make sure our own player gets updated correctly!
Hashtable actorProperties = (Hashtable)operationResponse[ParameterCode.ActorProperties];
Hashtable gameProperties = (Hashtable)operationResponse[ParameterCode.GameProperties];
this.readoutStandardProperties(gameProperties, actorProperties, 0);

if (this.mPlayernameHasToBeUpdated)
{
    this.SendPlayerName();
}

And, in OnEvent (still NetworkingPeer.cs), you need to add below case EventCode.Join a call to "CacheProperties" before sending the mono messages:
if (orgPlayer == null) {
    bool isLocal = (mLocalActor.ID == actorNr);
    Debug.LogWarning("New player=" + isLocal + " player: " + actorName + " nr=" + actorNr);
    AddNewPlayer(actorNr, new PhotonPlayer(isLocal, actorNr, actorName));
}
this.mRoomToGetInto.playerCount = this.mActors.Count;
mActors[actorNr].CacheProperties(actorProperties); // JC (2011-10-07): need to cache the properties
if (mActors[actorNr] == mLocalActor) {
    SendMonoMessage("OnJoinedRoom");
} else {
    SendMonoMessage("OnPhotonPlayerConnected", mActors[actorNr]);
}

Those changes will only work with a modified version of Loadbalancer (part of the Photon 3 SDK):

Changes in LiteGame.cs, method HandleJoinOperation, as soon as we have the actor, we can set the properties (this also assures that players have ipAddress on their own PhotonPlayer):
// create an new actor
Actor actor;
if (this.TryAddPeerToGame(peer, out actor) == false) {
    peer.SendOperationResponse(
        new OperationResponse {
            OperationCode = joinRequest.OperationRequest.OperationCode, 
            ReturnCode = -1, 
            DebugMessage = "Peer already joined the specified game."
        }, 
        sendParameters);
        return null;
}

// START JC (2011-10-07): Set address and port, add to response
// this is for the player himself
actor.Properties.Set("PlayerIP", peer.RemoteIP);
actor.Properties.Set("PlayerPort", peer.RemotePort);

// this is distributed only to the MasterClient (unless someone sets
// joinRequest.BroadcastActorProperties = true)
joinRequest.ActorProperties["PlayerIP"] = peer.RemoteIP;
joinRequest.ActorProperties["PlayerPort"] = peer.RemotePort;
// END JC (2011-10-07): Set address and port, add to response

// set game properties for join from the first actor
if (this.Actors.Count == 1 && joinRequest.GameProperties != null) {
    this.Properties.SetProperties(joinRequest.GameProperties);
}

And finally, PublishJoinEvents(...) now always sends the properties (which contain the ipAddress) to the MasterClient - but only to the others when BroadcastActorProperties is active ... this is not really necessary but I'd rather not have all clients know all ip-addresses of all other clients. This is a little tricky, though: If you use the properties for other purposes, you might run into a conflict situation here, so watch out. Here's the full modified code of that method.

protected virtual void PublishJoinEvent(LitePeer peer, JoinRequest joinRequest) {
    Actor actor = this.GetActorByPeer(peer);
    if (actor == null) {
        return;
    }

    // JC (2011-10-07): Create 2 events: 1 for the MasterClient, 1 for the others
    // generate a join event and publish to all actors in the room
    var joinEventMaster = new JoinEvent(actor.ActorNr, this.Actors.GetActorNumbers().ToArray());
    var joinEventOthers = new JoinEvent(actor.ActorNr, this.Actors.GetActorNumbers().ToArray());


    IEnumerable<Actor> recipientsMaster = new[] { this.Actors[0] };
    joinEventMaster.ActorProperties = joinRequest.ActorProperties;
    this.PublishEvent(joinEventMaster, recipientsMaster, new SendParameters());
            
    IEnumerable<Actor> recipientsAllButMaster = this.Actors.GetExcludedList(this.Actors[0]);
    if (joinRequest.BroadcastActorProperties) {
        joinEventOthers.ActorProperties = joinRequest.ActorProperties;
    }
    this.PublishEvent(joinEventOthers, recipientsAllButMaster, new SendParameters());
}

Instead of using hardcoded strings in different places ("PlayerIP", "PlayerPort"), I'd strongly recommend using either an enum or a class like the one below (preferably compiled into a shared DLL to keep things simple):
namespace Photon.LoadBalancing.Custom {
    public class ActorProperties {
        public static string PlayerIP = "PlayerIP"; // stores the player's IP
        public static string PlayerPort = "PlayerPort"; // stores the player's port
    }
}