Results 1 to 3 of 3
  1. #1
    Join Date
    Feb 2014
    Posts
    236

    Beyond the MMO Videos

    This post goes beyond Nelson's videos to fix the bugs left in the project.

    Log4Net upgrades
    The first thing I noticed after the last video was that Log4Net was complaining about it still being setup for Photon 3.
    Fortunately the fixes are rather simple. They are based on the Photon LoadBalancing solution.


    1. Edit BuzzMMO.Server.Master/Application.cs
    We are replacing the private Log field as well as moving the Logging setup out to its own method.
    I am quoting the entire method even though it will be changed later for other Photon4 fixes.

    Code:
    using System.IO;
    using BuzzMMO.Base;
    using Photon.SocketServer;
    
    using ExitGames.Logging;
    using ExitGames.Logging.Log4Net;
    using log4net;
    using log4net.Config;
    
    namespace BuzzMMO.Server.Master
    {
        public class Application : ApplicationBase
        {
            //private static readonly ILog Log = LogManager.GetLogger(typeof(Application));         //old photon 3 code
            private static readonly ILogger Log = ExitGames.Logging.LogManager.GetCurrentClassLogger();
    
            private readonly MasterServerContext _application;
    
            public Application()
            {
                _application = new MasterServerContext(new SimpleSerializer());
            }
    
            protected override Photon.SocketServer.PeerBase CreatePeer(InitRequest initRequest)
            {
                return new Peer(_application, initRequest);
            }
    
            protected override void Setup()
            {
                InitLogging();              //call the new logging init method
    
                //GlobalContext.Properties["ServerName"] = "Master";                                                         //old photon 3 code
                //XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(BinaryPath, "log4net.config")));               //old photon 3 code
                //ExitGames.Logging.LogManager.SetLoggerFactory(ExitGames.Logging.Log4Net.Log4NetLoggerFactory.Instance);    //old photon 3 code
    
                //Log.Info("HIGHLIGHT - BLEGH");                                                                             //old photon 3 code
    
                Log.Info("HIGHLIGHT - Master Server Started");
            }
    
            //Log4Net setup on Master server
            protected virtual void InitLogging()
            {
                ExitGames.Logging.LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);
                GlobalContext.Properties["ServerName"] = "Master";
                XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(this.BinaryPath, "log4net.config")));
            }
    
            protected override void TearDown()
            {
                _application.Dispose();
            }
        }
    }
    2. Edit BuzzMMO.Server.Region/Application.cs
    Only the relevant bits are quoted below.

    Code:
    using System;
    using System.IO;
    using System.Net;
    using BuzzMMO.Base;
    using BuzzMMO.Data;
    using BuzzMMO.Data.Entities;
    using ExitGames.Concurrency.Fibers;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using Photon.SocketServer;
    using Photon.SocketServer.ServerToServer;
    
    using ExitGames.Logging;
    using ExitGames.Logging.Log4Net;
    using log4net;
    using log4net.Config;
    
    
    namespace BuzzMMO.Server.Region
    {
        public class Application : ApplicationBase
        {
            private const int ServerConnectTimeout = 5000;          //in mS
    
            //private static readonly ILog Log = LogManager.GetLogger(typeof(Application));     //photon 3
            private static readonly ILogger Log = ExitGames.Logging.LogManager.GetCurrentClassLogger();
    
            private readonly GameServerContext _application;
            private readonly IFiber _fiber;
    
     ................stuff left out.....................
    
            protected override void Setup()
            {
                var gameServerConfig = _application.Config.GameServer;
                using (var database = new MMODatabaseContext())
                {
                    var server = database.GameServers.Find(gameServerConfig.UniqueId);
                    if (server != null)
                    {
                        server.Name = gameServerConfig.Name;
                    }
                    else
                    {
                        server = new GameServerEntity
                        {
                            Id = gameServerConfig.UniqueId,
                            Name = gameServerConfig.Name,
                            CreatedAt = DateTime.UtcNow
                        };
    
                        database.GameServers.Add(server);
                    }
    
                    database.SaveChanges();
                }
    
                InitLogging(gameServerConfig.Name);
    
                ConnectToMasterServer();
            }
    
    ................stuff left out.....................
    
            //Log4Net setup on Region server
            protected virtual void InitLogging(string name)
            {
                ExitGames.Logging.LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);
                GlobalContext.Properties["ServerName"] = name;
                XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(this.BinaryPath, "log4net.config")));
            }
    
            protected override void TearDown()
            {
                _application.Dispose();
            }
        }
    }

  2. #2
    Join Date
    Feb 2014
    Posts
    236
    Partial Upgrade to Photon 4 including Region server connecting via TCP

    I will be re-visiting the entire Log4Net thing again because at the moment we are using a bad hybrid of old and new log4net. It works without errors, but isn't satisfactory. I am debating whether to use the standalone dll that Nelson uses, or the one in Photon4 itself. Nelson had argued for a single dll for compatibility, but we shall see.


    Following along with am385's Photon4 upgrade (https://www.3dbuzz.com/forum//threads/204607)
    I noticed that a region server needs it own connection method on the master server, but that our master server was always listening on port 5055, so it was impossible to switch via the request's incoming port number.

    The LoadBalancing demo application provided by Photon solved that problem by having the region server connect via TCP rather than UDP. I also decided to modify and use the existing Config.json to get the tcp port rather than hard code it in.

    So we have a lot of changes to make.

    1. PhotonServer.config
    First one is to actually add in the TCP connections inside the PhotonServer.config file inside our photon-4-0-29\deploy\bin_win64 folder.
    For easy comparison here is my entire file - yours may be different:

    Code:
    <?xml version="1.0" encoding="Windows-1252"?>
    
    <Configuration>
      <!-- Multiple instances are supported. Each instance has its own node in the config file. -->
        
     <GameInstance1
        MaxMessageSize="512000"
        MaxQueuedDataPerPeer="512000"
        PerPeerMaxReliableDataInTransit="51200"
        PerPeerTransmitRateLimitKBSec="256"
        PerPeerTransmitRatePeriodMilliseconds="200"
        MinimumTimeout="5000"
        MaximumTimeout="30000">
        
        <UDPListeners>
          <UDPListener
            IPAddress="0.0.0.0"
            Port="5056">
          </UDPListener>
        </UDPListeners>
        
        <Runtime
          Assembly="PhotonHostRuntime, Culture=neutral"
          Type="PhotonHostRuntime.PhotonDomainManager"
          UnhandledExceptionPolicy="Ignore">
        </Runtime>
        
        <Applications Default="BuzzMMOGame1">
          <Application
            Name="BuzzMMOGame1"
            BaseDirectory="BuzzMMO/Game1"
            Assembly="BuzzMMO.Server.Region"
            Type="BuzzMMO.Server.Region.Application"
            ForceAutoRestart="true"
            WatchFiles="dll;config"
            ExcludeFiles="log4net.config" 
          />
        </Applications>
      </GameInstance1>
      
      <GameInstance2
        MaxMessageSize="512000"
        MaxQueuedDataPerPeer="512000"
        PerPeerMaxReliableDataInTransit="51200"
        PerPeerTransmitRateLimitKBSec="256"
        PerPeerTransmitRatePeriodMilliseconds="200"
        MinimumTimeout="5000"
        MaximumTimeout="30000">
        
        <UDPListeners>
          <UDPListener
            IPAddress="0.0.0.0"
            Port="5057">
          </UDPListener>
        </UDPListeners>
        
        <Runtime
          Assembly="PhotonHostRuntime, Culture=neutral"
          Type="PhotonHostRuntime.PhotonDomainManager"
          UnhandledExceptionPolicy="Ignore">
        </Runtime>
        
        <Applications Default="BuzzMMOGame2">
          <Application
            Name="BuzzMMOGame2"
            BaseDirectory="BuzzMMO/Game2"
            Assembly="BuzzMMO.Server.Region"
            Type="BuzzMMO.Server.Region.Application"
            ForceAutoRestart="true"
            WatchFiles="dll;config"
            ExcludeFiles="log4net.config" 
          />
        </Applications>
      </GameInstance2>
       
      <MasterServer
        MaxMessageSize="512000"
        MaxQueuedDataPerPeer="512000"
        PerPeerMaxReliableDataInTransit="51200"
        PerPeerTransmitRateLimitKBSec="256"
        PerPeerTransmitRatePeriodMilliseconds="200"
        MinimumTimeout="5000"
        MaximumTimeout="30000">
        
        <UDPListeners>
          <UDPListener
            IPAddress="0.0.0.0"
            Port="5055">
          </UDPListener>
        </UDPListeners>
        
          <!-- DON'T EDIT THIS. TCP listener for GameServers on Master application -->
        <TCPListeners>
          <TCPListener
            IPAddress="0.0.0.0"
            Port="4520">
          </TCPListener>
        </TCPListeners>
    
        <Runtime
          Assembly="PhotonHostRuntime, Culture=neutral"
          Type="PhotonHostRuntime.PhotonDomainManager"
          UnhandledExceptionPolicy="Ignore">
        </Runtime>
        
        <Applications Default="BuzzMMOMaster">
          <Application
            Name="BuzzMMOMaster"
            BaseDirectory="BuzzMMO/Master"
            Assembly="BuzzMMO.Server.Master"
            Type="BuzzMMO.Server.Master.Application"
            ForceAutoRestart="true"
            WatchFiles="dll;config"
            ExcludeFiles="log4net.config" 
          />
        </Applications>
      </MasterServer>   
    
      <Default
        MaxMessageSize="512000"
        MaxQueuedDataPerPeer="512000"
        PerPeerMaxReliableDataInTransit="51200"
        PerPeerTransmitRateLimitKBSec="256"
        PerPeerTransmitRatePeriodMilliseconds="200"
        MinimumTimeout="5000"
        MaximumTimeout="30000">
        
         <!--0.0.0.0 opens listeners on all available IPs. Machines with multiple IPs should define the correct one here.
         Port 5055 is Photon's default for UDP connections.-->
        <UDPListeners>
          <UDPListener
            IPAddress="0.0.0.0"
            Port="5055">
            OverrideApplication="BuzzMMOMaster">
          </UDPListener>
          <UDPListener
            IPAddress="0.0.0.0"
            Port="5056">
            OverrideApplication="BuzzMMOGame1">
          </UDPListener>
          <UDPListener
            IPAddress="0.0.0.0"
            Port="5057">
            OverrideApplication="BuzzMMOGame2">
          </UDPListener>
        </UDPListeners>
    
        <TCPListeners>
          <TCPListener
            IPAddress="0.0.0.0"
            Port="4520">
          </TCPListener>
        </TCPListeners>
      
         Defines the Photon Runtime Assembly to use.
        <Runtime
          Assembly="PhotonHostRuntime, Culture=neutral"
          Type="PhotonHostRuntime.PhotonDomainManager"
          UnhandledExceptionPolicy="Ignore">
        </Runtime>
        
        <Applications Default="BuzzMMOMaster">
          <Application
            Name="BuzzMMOMaster"
            BaseDirectory="BuzzMMO/Master"
            Assembly="BuzzMMO.Server.Master"
            Type="BuzzMMO.Server.Master.Application"
            ForceAutoRestart="true"
            WatchFiles="dll;config"
            ExcludeFiles="log4net.config" />
          
          <Application
            Name="BuzzMMOGame1"
            BaseDirectory="BuzzMMO/Game1"
            Assembly="BuzzMMO.Server.Region"
            Type="BuzzMMO.Server.Region.Application"
            ForceAutoRestart="true"
            WatchFiles="dll;config"
            ExcludeFiles="log4net.config" />
    
          <Application
            Name="BuzzMMOGame2"
            BaseDirectory="BuzzMMO/Game2"
            Assembly="BuzzMMO.Server.Region"
            Type="BuzzMMO.Server.Region.Application"
            ForceAutoRestart="true"
            WatchFiles="dll;config"
            ExcludeFiles="log4net.config" />
        </Applications>
      </Default>  
    </Configuration>
    2. Edit our BuzzMMO.Server.Region/Config.json to include a TcpConnection for MasterServer:

    Code:
    {
      "MasterServer": 
      {
        "UdpConnection": 
        {
          "Address": "127.0.0.1:5055",
          "ApplicationName": "BuzzMMOMaster"
        },
        "TcpConnection": 
        {
          "Address": "127.0.0.1:4520",
          "ApplicationName": "BuzzMMOMaster"
        }
      }, 
      "GameServer": {
        "Name": "<placeholder>",
        "UdpConnection": {
          "Address": "<placeholder>",
          "ApplicationName": "<placeholder>"
        },
        "UniqueId": "<placeholder>"  
      }
    }
    3. Edit our BuzzMMO.Server/MasterServerConfig.cs to include a TcpConnection property

    Code:
    using BuzzMMO.Base;
    
    namespace BuzzMMO.Server
    {
        public class MasterServerConfig
        {
            public PhotonServerConnection UdpConnection { get; set; }
            public PhotonServerConnection TcpConnection { get; set; }
        }
    }
    4. Create a class BuzzMMO.Server.Master.Config.cs to mirror the one in the Region project.

    Code:
    namespace BuzzMMO.Server.Master
    {
        public class Config
        {
            public MasterServerConfig MasterServer;
            public GameServerConfig GameServer;
        }
    }

    5. Create a dummy class for the RegionServer connection. We will modify this at a later point to use the incoming p2p stuff. But for now create BuzzMMO.Server.Master/RegionPeer.cs

    Code:
    using BuzzMMO.Base;
    using BuzzMMO.Server.Master.Systems;
    using BuzzMMO.Server.Master.Systems.Region;
    using Photon.SocketServer;
    
    namespace BuzzMMO.Server.Master
    {
        //server connection. clients use peer
        //TODO: This is not correct. Re-write to base it on MasterServerPeer in the region project
        public class RegionPeer : MMOPeerBase<ClientContext>
        {
            private readonly MasterServerContext _application;
    
            public RegionPeer(MasterServerContext application, InitRequest initRequest) : base(initRequest)
            {
                _application = application;
            }
    
            protected override ClientContext CreateContext(ContextType type, OperationRequest request)
            {
                //todo: we should do some authentication - eg get token from region server
                if (type == ContextType.Region)
                {
                    var context = new RegionServerContext(_application, this);
                    context.Systems.Create<GameInstanceSystem>();
                    return context;
                }
    
                return null;
            }
        }
    }
    6. Edit our BuzzMMO.Server.Master/MasterServerContext.cs because we are now passing in a Config (part of the stuff we need to do to deserialize the config.json file)
    You will need to add in NewtonSoft.Json references using NuGet.

    Code:
    using Autofac;
    using BuzzMMO.Base;
    using BuzzMMO.Base.Components.Systems.Master;
    using BuzzMMO.Data;
    using BuzzMMO.Server.Components;
    
    namespace BuzzMMO.Server.Master
    {
        public class MasterServerContext : ServerContext
        {
            public Config Config { get; }
    
            public MasterServerContext(ISerializer serializer, Config config) : base(serializer)
            {
                Config = config;
            }
    
            protected override void Configure(ContainerBuilder builder)
            {
                SystemTypeRegistry.ScanAssembly(typeof(MasterServerContext).Assembly);
                PlayerSystemsComponentMap.AutoMapAssembly(typeof(ILoginSystemServer).Assembly, typeof(ComponentInterfaceAttribute));
                RegionSystemsComponentMap.AutoMapAssembly(typeof(IGameInstancesMaster).Assembly, typeof(ComponentInterfaceAttribute));
    
                foreach (var system in SystemTypeRegistry.RegisteredSystems)
                    builder.RegisterType(system.ConcreteType).AsSelf().InstancePerLifetimeScope();
    
                builder.RegisterType<MMODatabaseContext>().AsSelf().InstancePerDependency();
    
                builder.Register(_ => this).AsSelf().SingleInstance();
                builder.RegisterType<MainLobbyService>().AsSelf().SingleInstance();
                builder.RegisterType<GameLobby>().AsSelf().InstancePerDependency();
                builder.RegisterType<RegionServerQueue>().AsSelf().SingleInstance();
            }
        }
    }

    7. Edit our BuzzMMO.Server.Master/Peer.cs slightly.
    Well maybe you don't .... I had originally removed the ClientContext.Region tests and it worked, but put it back cos we might use udp for some things later from the region server

    Code:
    using BuzzMMO.Base;
    using BuzzMMO.Server.Master.Systems;
    using BuzzMMO.Server.Master.Systems.Region;
    using Photon.SocketServer;
    
    namespace BuzzMMO.Server.Master
    {
        //client incoming connection. servers connect via serverpeer
        public class Peer : MMOPeerBase<ClientContext>
        {
            private readonly MasterServerContext _application;
    
            public Peer(MasterServerContext application, InitRequest initRequest) : base(initRequest)
            {
                _application = application;
            }
    
            protected override ClientContext CreateContext(ContextType type, OperationRequest request)
            {
                if (type == ContextType.Player)
                {
                    var context = new PlayerContextMaster(_application, this);
                    context.Systems.Create<LoginSystem>();
                    return context;
                }
    
                if (type == ContextType.Region)
                {
                    var context = new RegionServerContext(_application, this);
                    context.Systems.Create<GameInstanceSystem>();
                    return context;
                }
    
                return null;
            }
        }
    }

    8. Edit our BuzzMMO.Server.Master/Application.cs to do all the TCP checks and route it to the correct stuff as needed.

    Code:
    using System.IO;
    using BuzzMMO.Base;
    using Photon.SocketServer;
    
    using ExitGames.Logging;
    using ExitGames.Logging.Log4Net;
    using log4net;
    using log4net.Config;
    using Newtonsoft.Json;
    
    namespace BuzzMMO.Server.Master
    {
        public class Application : ApplicationBase
        {
            private static readonly ILogger Log = ExitGames.Logging.LogManager.GetCurrentClassLogger();
    
            private readonly MasterServerContext _application;
    
            public Application()
            {
                // look inside the game 1 folder for the masterserver listen tcp port
                var config = JsonConvert.DeserializeObject<Config>(File.ReadAllText(Path.Combine(BinaryPath, "../../Game1/bin/", "Config.json")));
    
                _application = new MasterServerContext(new SimpleSerializer(), config);
            }
    
            protected override Photon.SocketServer.PeerBase CreatePeer(InitRequest initRequest)
            {
                var addressParts = _application.Config.MasterServer.TcpConnection.Address.Split(':');
                var masterServerPort = int.Parse(addressParts[1]);
                Log.Debug($"Address port received in the initRequest: {masterServerPort}");
    
                if (initRequest.LocalPort == masterServerPort)
                {
                    Log.Debug($"Connection via port {initRequest.LocalPort} going to RegionPeer class");
                    return new RegionPeer(_application, initRequest);
                }
    
                if (initRequest.LocalPort == 5055)
                {
                    Log.Debug($"Connection via port {initRequest.LocalPort} going to Peer class");
                    return new Peer(_application, initRequest);
                }
    
                Log.Warn($"Connection attempt via unmapped port {initRequest.LocalPort}");
                return null;
            }
    
            protected override void Setup()
            {
                InitLogging();
    
                Log.Info("HIGHLIGHT - Master Server Started");
            }
    
            //Log4Net setup on Master server
            protected virtual void InitLogging()
            {
                ExitGames.Logging.LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);
                GlobalContext.Properties["ServerName"] = "Master";
                XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(this.BinaryPath, "log4net.config")));
            }
    
            protected override void TearDown()
            {
                _application.Dispose();
            }
        }
    }

    9. Edit our BuzzMMO.Server.Region/Application.cs to put in am385's changes modding it slightly to use TCP

    Code:
    using System;
    using System.IO;
    using System.Net;
    using BuzzMMO.Base;
    using BuzzMMO.Data;
    using BuzzMMO.Data.Entities;
    using ExitGames.Concurrency.Fibers;
    using Newtonsoft.Json;
    using Photon.SocketServer;
    using Photon.SocketServer.ServerToServer;
    
    using ExitGames.Logging;
    using ExitGames.Logging.Log4Net;
    using log4net;
    using log4net.Config;
    
    namespace BuzzMMO.Server.Region
    {
        public class Application : ApplicationBase
        {
            private const int ServerConnectTimeout = 5000;          //in mS
    
            private static readonly ILogger Log = ExitGames.Logging.LogManager.GetCurrentClassLogger();
    
            private readonly GameServerContext _application;
            private readonly IFiber _fiber;
    
            public Application()
            {
                _fiber = new ThreadFiber();
                _fiber.Start();
    
                var config = JsonConvert.DeserializeObject<Config>(File.ReadAllText(Path.Combine(BinaryPath, "Config.json")));
                _application = new GameServerContext(new SimpleSerializer(), config);
            }
    
            // only for incoming requests.
            protected override PeerBase CreatePeer(InitRequest initRequest)
            {
                return new Peer(_application, initRequest);
            }
    
            //called automatically when application is first run
            protected override void Setup()
            {
                var gameServerConfig = _application.Config.GameServer;
                using (var database = new MMODatabaseContext())
                {
                    var server = database.GameServers.Find(gameServerConfig.UniqueId);
                    if (server != null)
                    {
                        server.Name = gameServerConfig.Name;
                    }
                    else
                    {
                        server = new GameServerEntity
                        {
                            Id = gameServerConfig.UniqueId,
                            Name = gameServerConfig.Name,
                            CreatedAt = DateTime.UtcNow
                        };
    
                        database.GameServers.Add(server);
                    }
    
                    database.SaveChanges();
                }
    
                InitLogging(gameServerConfig.Name);
    
                ConnectToMasterServer();
            }
    
            private void ConnectToMasterServer()
            {
                IPEndPoint endpoint;
                try
                {
                    var addressParts = _application.Config.MasterServer.TcpConnection.Address.Split(':');       //get the tcp connection details
                    endpoint = new IPEndPoint(IPAddress.Parse(addressParts[0]), int.Parse(addressParts[1]));    //assumes ip address in config.json, not dns- check the loadbalancing demo
                }
                catch (Exception e)
                {
                    throw new InvalidOperationException("Cannot parse Config.MasterServer.TcpConnection.Address (please specify it as <ip>:<port>", e);
                }
    
                Log.Info($"Connecting to master server on: {_application.Config.MasterServer.TcpConnection}");
    
                var peer = new MasterServerPeer(this, _application);
                if (peer.ConnectTcp(endpoint, _application.Config.MasterServer.TcpConnection.ApplicationName))
                {
                    Log.Info($"Connected to master server on: {_application.Config.MasterServer.TcpConnection}");
    
                    peer.OnDisconnected += () => _fiber.Schedule(ConnectToMasterServer, ServerConnectTimeout);
                }
            }
    
            protected override void OnServerConnectionFailed(int errorCode, string errorMessage, object state)
            {
                Log.Error($"Could not connect to master server {_application.Config.MasterServer.TcpConnection}: {errorCode} / {errorMessage}");
                _fiber.Schedule(ConnectToMasterServer, ServerConnectTimeout);
            }
    
            //Log4Net setup on Region server
            protected virtual void InitLogging(string name)
            {
                ExitGames.Logging.LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);
                GlobalContext.Properties["ServerName"] = name;
                XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(this.BinaryPath, "log4net.config")));
            }
    
            protected override void TearDown()
            {
                _application.Dispose();
            }
        }
    }
    10. Edit our BuzzMMO.Server.Region/MasterServerPeer.cs file to include am385's changes. Here is the entire file for comparison:

    Code:
    using System;
    using System.Collections.Generic;
    using Autofac;
    using BuzzMMO.Base;
    using BuzzMMO.Base.Async;
    using BuzzMMO.Client.Infrastructure;
    using BuzzMMO.Client.Systems;
    using log4net;
    using Photon.SocketServer;
    using Photon.SocketServer.ServerToServer;
    using PhotonHostRuntimeInterfaces;
    
    namespace BuzzMMO.Server.Region
    {
        public class MasterServerPeer : OutboundS2SPeer, ISystemFactory, IClientTransport
        {
            private static readonly ILog Log = LogManager.GetLogger(typeof(MasterServerPeer));
    
            public event Action OnDisconnected;
    
            private readonly GameServerContext _application;
            private readonly BuzzMMO.Client.Systems.ClientSystems _clientSystems;
            private readonly ComponentMap _componentMap;
            private readonly ILifetimeScope _scope;
            private readonly CallbackByteMap<Action<OperationCode, Dictionary<byte, object>>> _callbacks;
            private readonly Action<EventCode, Dictionary<byte, object>>[] _eventHandlers;
    
            protected readonly HashSet<IClientTransportListener> Listeners;
    
            public MasterServerPeer(Application app, GameServerContext application) : base(app)
            {
                _callbacks = new CallbackByteMap<Action<OperationCode, Dictionary<byte, object>>>();
                _eventHandlers = new Action<EventCode, Dictionary<byte, object>>[byte.MaxValue + 1];
                Listeners = new HashSet<IClientTransportListener>();
    
                _application = application;
                _scope = application.Container.BeginLifetimeScope();
                _componentMap = new ComponentMap();
    
                var operationWriter = new SystemsOperationWriter(_application.Serializer, this);
                _clientSystems = new Client.Systems.ClientSystems(_componentMap, this, operationWriter);
                AddEventReader(new SystemsEventReader(_application.Serializer, _clientSystems, this));
    
                application.OnDisposed += OnApplicationDisposed;
            }
    
            protected override void OnConnectionEstablished(object responseObject)
            {
                SendOperation(OperationCode.InitContext, new Dictionary<byte, object> { [(byte)OperationParameter.ContextType] = ContextType.Region });
            }
    
            protected override void OnConnectionFailed(int errorCode, string errorMessage)
            {
                Log.Error($"Error connecting to master server: {errorCode} / {errorMessage}");
            }
    
            private void OnApplicationDisposed()
            {
                Disconnect();
            }
    
            protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
            {
            }
    
            protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
            {
                Log.Info($"MasterServerPeer Disconnect: {reasonCode} / {reasonDetail}");
                OnDisconnected?.Invoke();
                _scope.Dispose();
                _application.OnDisposed -= OnApplicationDisposed;
            }
    
            protected override void OnEvent(IEventData eventData, SendParameters sendParameters)
            {
                HandleEvent((EventCode)eventData.Code, eventData.Parameters);
            }
    
            protected override void OnOperationResponse(OperationResponse operationResponse, SendParameters sendParameters)
            {
                HandleSystemCallback((OperationCode)operationResponse.OperationCode, operationResponse.Parameters);
            }
    
            ISystemBase ISystemFactory.CreateSystem(Type interfaceType, Func<Type, object> proxyFactory, out Type concreteType)
            {
                var registeredSystem = _application.SystemTypeRegistry.GetSystemFromClientInterfaceType(interfaceType);
                var instance = _scope.Resolve(registeredSystem.ConcreteType);
                var proxy = proxyFactory(registeredSystem.ServerInterfaceType);
                ((IMasterSystemBase)instance).SetContext(_application, proxy);
    
                concreteType = registeredSystem.ConcreteType;
                return (ISystemBase)instance;
            }
    
            public void AddEventReader(IEventReaderModule eventReader)
            {
                foreach (var registration in eventReader.GetRegistration())
                {
                    if (_eventHandlers[(int)registration.Code] != null)
                    {
                        var oldHandler = _eventHandlers[(int)registration.Code];
                        var action = registration.Action;
    
                        _eventHandlers[(int)registration.Code] = (code, parameters) =>
                        {
                            oldHandler(code, parameters);
                            action(code, parameters);
                        };
                    }
                    else
                    {
                        _eventHandlers[(int)registration.Code] = registration.Action;
                    }
                }
            }
    
            public void SendOperation(OperationCode code, Dictionary<byte, object> parameters)
            {
                SendOperationInternal(code, parameters);
            }
    
            public void SendOperation(OperationCode code, Dictionary<byte, object> parameters, Action<OperationCode, Dictionary<byte, object>> onResponse)
            {
                parameters[(byte)OperationParameter.SystemInvokeId] = _callbacks.RegisterCallback(onResponse);
                SendOperationInternal(code, parameters);
            }
    
            public void AddListener(IClientTransportListener listener)
            {
                Listeners.Add(listener);
            }
    
            Deferred IClientTransport.Connect()
            {
                return Deferred.Success();
            }
    
            void IClientTransport.Service()
            {
            }
    
            Deferred IClientTransport.Disconnect()
            {
                return Deferred.Success();
            }
    
            protected void HandleSystemCallback(OperationCode code, Dictionary<byte, object> parameters)
            {
                if (code != OperationCode.SendSystemResponse)
                    throw new ArgumentException($"Code {code} is not valid for handling responses", nameof(code));
    
                var methodInvokeId = (byte)parameters[(byte)OperationParameter.SystemInvokeId];
                var callback = _callbacks.GetCallback(methodInvokeId);
                callback(code, parameters);
            }
    
            protected void HandleEvent(EventCode code, Dictionary<byte, object> parameters)
            {
                var handler = _eventHandlers[(byte)code];
                if (handler == null)
                    throw new InvalidOperationException($"Handler for event code {code} was not registered");
    
                handler(code, parameters);
            }
    
            protected void SendOperationInternal(OperationCode code, Dictionary<byte, object> parameters)
            {
                SendOperationRequest(new OperationRequest((byte)code, parameters), new SendParameters
                {
                    Unreliable = false
                });
            }
        }
    }


    ---------------
    Having done all that, you will see in the logs that region servers are connecting on 4520 TCP port and being sent off to the RegionPeer class while clients are sent off to the Peer class.
    Last edited by oldngrey; Yesterday at 12:14 AM.

  3. #3
    Join Date
    Feb 2014
    Posts
    236
    Recording the User's IP address and Last Login date/time when logging in

    I decided that having the user's IP address recorded in the useringames table was both too hard and kinda useless.
    Instead the place I wanted the user's IP address recorded was in the users table. While I was at it, I decided to also include a new column recording when they last logged in to either the website or the game lobby.
    Of course the Website admin Index page now shows their last login and last recorded ip address.
    Because the useringames table has an index to the id of the user, the game could easily access the user's ip address anyway, so I also dropped the ipaddress from the useringames table.

    1. First up make the changes to the user table and data/entities
    Edit BuzzMMO.Data.Entities/User.cs

    Here is the entire file, the only change is in the lines adding in LastLogin and LastIp

    Code:
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Security.Cryptography;
    
    namespace BuzzMMO.Data.Entities
    {
        public class User
        {
            public int Id { get; set; }
    
            [Required, MaxLength(128)]
            public string Username { get; set; }
    
            [Required, MaxLength(128)]
            public string Password { get; set; }
    
            [Required, MaxLength(128)]
            public string Email { get; set; }
            
            [MaxLength(64)]
            public string VerifyEmailToken { get; set; }
    
            [MaxLength(64)]
            public string ResetPasswordToken { get; set; }
    
            public DateTime? ResetPasswordTokenExpiresAt { get; set; }
    
            public DateTime? LastLogin { get; set; }
    
            [MaxLength(64)]
            public string LastIp { get; set; }
    
            public virtual ICollection<Role> Roles { get; set; }
    
            private const int WorkFactor = 13;
    
            public void SetPassword(string password)
            {
                Password = BCrypt.Net.BCrypt.HashPassword(password, WorkFactor);
            }
    
            public static void FakeHash()
            {
                BCrypt.Net.BCrypt.HashPassword("", WorkFactor);
            }
    
            public bool CheckPassword(string password)
            {
                return BCrypt.Net.BCrypt.Verify(password, Password);
            }
    
            public void GenerateEmailVerificationToken()
            {
                VerifyEmailToken = GenerateRandomToken();
            }
    
            public void GenerateResetPasswordToken()
            {
                ResetPasswordToken = GenerateRandomToken();
                ResetPasswordTokenExpiresAt = DateTime.UtcNow.AddMinutes(30);
            }
    
            public void ClearResetPasswordToken()
            {
                ResetPasswordToken = null;
                ResetPasswordTokenExpiresAt = null;
            }
    
            private string GenerateRandomToken()
            {
                using (var random = new RNGCryptoServiceProvider())
                {
                    var buffer = new byte[32];
                    random.GetNonZeroBytes(buffer);
                    return Convert.ToBase64String(buffer);
                }
            }
        }
    }
    You can then check that these columns are nullable after going to the PackageManagerConsole and typing in something like:
    Code:
    add-migration AddIpAndLastLoinToUser
    Then check the migration file resembles this:
    Code:
    namespace BuzzMMO.Data.Migrations
    {
        using System;
        using System.Data.Entity.Migrations;
        
        public partial class AddIpAndLastLoinToUser : DbMigration
        {
            public override void Up()
            {
                AddColumn("dbo.Users", "LastLogin", c => c.DateTime(nullable: true, precision: 0));
                AddColumn("dbo.Users", "LastIp", c => c.String(nullable: true, maxLength: 64, storeType: "nvarchar"));
            }
            
            public override void Down()
            {
                DropColumn("Users", "LastIp");
                DropColumn("Users", "LastLogin");
            }
        }
    }
    If all went well, type
    Code:
    update-database
    When looking at the table in the database the users table should have the LastLogin and LastIp columns both nullable.


    2. Next make the changes to the useringames table and data/entities
    Edit BuzzMMO.Data.Entities/UserInGame.cs

    Here is the entire file with the changes as the commented out lines.
    Code:
    using System.ComponentModel.DataAnnotations;
    using BuzzMMO.Base.Components;
    
    namespace BuzzMMO.Data.Entities
    {
        public class UserInGame
        {
            public virtual int Id { get; set; }
    
            [Required]
            public virtual User Player { get; set; }
    
            [Required]
            public virtual GameEntity Game { get; set; }
    
            [Required]
            public virtual bool HasAbandoned { get; set; }
    
            [Required]
            public virtual GameHero Hero { get; set; }
    
            [Required]
            public virtual GameTeam Team { get; set; }
    
            [Required, MaxLength(64)]
            public string Token { get; set; }
    
            //[Required, MaxLength(64)]
            //public string RequestIp { get; set; }
        }
    }
    Go to the PackageManagerConsole and type in something like:
    Code:
    add-migration RemoveIPFromUserInGame
    Then check the migration file resembles this:
    Code:
    namespace BuzzMMO.Data.Migrations
    {
        using System;
        using System.Data.Entity.Migrations;
        
        public partial class RemoveIPFromUserInGame : DbMigration
        {
            public override void Up()
            {
                DropColumn("dbo.UserInGames", "RequestIp");
            }
            
            public override void Down()
            {
                AddColumn("dbo.UserInGames", "RequestIp", c => c.String(nullable: false, maxLength: 64, storeType: "nvarchar"));
            }
        }
    }
    If all went well, type
    Code:
    update-database
    When looking at the table in the database the useringames table should have the ipaddress column removed.

    3. Edit BuzzMMO.Server.Master/Application.cs to remove a Log.Debug line that was misleading. Here is the entire file:
    Code:
    using System.IO;
    using BuzzMMO.Base;
    using Photon.SocketServer;
    
    using ExitGames.Logging;
    using ExitGames.Logging.Log4Net;
    using log4net;
    using log4net.Config;
    using Newtonsoft.Json;
    
    namespace BuzzMMO.Server.Master
    {
        public class Application : ApplicationBase
        {
            private static readonly ILogger Log = ExitGames.Logging.LogManager.GetCurrentClassLogger();
    
            private readonly MasterServerContext _application;
    
            public Application()
            {
                // look inside the game 1 folder for the masterserver listen tcp port
                var config = JsonConvert.DeserializeObject<Config>(File.ReadAllText(Path.Combine(BinaryPath, "../../Game1/bin/", "Config.json")));
    
                _application = new MasterServerContext(new SimpleSerializer(), config);
            }
    
            protected override Photon.SocketServer.PeerBase CreatePeer(InitRequest initRequest)
            {
                var addressParts = _application.Config.MasterServer.TcpConnection.Address.Split(':');
                var masterServerPort = int.Parse(addressParts[1]);
    
                if (initRequest.LocalPort == masterServerPort)
                {
                    Log.Debug($"Connection via port {initRequest.LocalPort} going to RegionPeer class");
                    return new RegionPeer(_application, initRequest);
                }
    
                if (initRequest.LocalPort == 5055)
                {
                    Log.Debug($"Connection via port {initRequest.LocalPort} going to Peer class");
                    return new Peer(_application, initRequest);
                }
    
                Log.Warn($"Connection attempt via unmapped port {initRequest.LocalPort}");
                return null;
            }
    
            protected override void Setup()
            {
                InitLogging();
    
                Log.Info("HIGHLIGHT - Master Server Started");
            }
    
            //Log4Net setup on Master server
            protected virtual void InitLogging()
            {
                ExitGames.Logging.LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);
                GlobalContext.Properties["ServerName"] = "Master";
                XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(this.BinaryPath, "log4net.config")));
            }
    
            protected override void TearDown()
            {
                _application.Dispose();
            }
        }
    }
    4. Edit BuzzMMO.Web.Areas.Admin.Views.User/Index.cshtml to add in the new lines to displayed users
    Code:
    @model BuzzMMO.Web.Areas.Admin.ViewModels.UsersIndex
    
    <h1>Users</h1>
    
    <p>
        @Html.ActionLink("Create User", "Create", new { }, new { @class = "btn btn-default btn-sm" })
    </p>
    
    
    <table class="table table-striped">
        <thead>
            <tr>
                <th>Id</th>
                <th>Username</th>
                <th>Email</th>
                <th>Last Login</th>
                <th>Last Known IP</th>
                <th>Roles</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var user in Model.Users)
            {
                <tr>
                    <td>@user.Id</td>
                    <td>@user.Username</td>
                    <td>@user.Email</td>
                    <td>@user.LastLogin</td>
                    <td>@user.LastIp</td>
                    <td>@string.Join(", ", user.Roles.Select(t => t.Name))</td>
                    <td>
                        <div class="btn-group">
                            <a href="@Url.Action("Edit", new {user.Id})" class="btn btn-xs btn-primary">
                                <i class="glyphicon glyphicon-edit"></i>
                                edit
                            </a>
    
                            <a href="@Url.Action("resetpassword", new {user.Id})" class="btn btn-xs btn-default">
                                <i class="glyphicon glyphicon-lock"></i>
                                reset password
                            </a>
    
                            @if (user.Id != Auth.User.Id)
                            {
                                <a href="@Url.Action("delete", new {user.Id})" class="btn btn-xs btn-danger post" data-post="Are you sure you want to delete @user.Username?">
                                    <i class="glyphicon glyphicon-remove"></i>
                                    delete
                                </a>
                            }
                        </div>
                    </td>
                </tr>
            }
        </tbody>
    </table>
    5. Edit BuzzMMO.Server/ClientContext.cs to save the LastLogin and IP to the database
    Code:
    using System;
    using System.Collections.Generic;
    using System.Data.Entity.Migrations;
    using System.IO;
    using System.Linq;
    using Autofac;
    using BuzzMMO.Base;
    using BuzzMMO.Base.Async;
    using BuzzMMO.Base.Components.Systems.Master;
    using BuzzMMO.Base.Extensions;
    using BuzzMMO.Data;
    using BuzzMMO.Data.Entities;
    
    namespace BuzzMMO.Server
    {
        public abstract class ClientContext : IDisposable
        {
            private volatile bool _isConnected;
    
            public bool IsConnected => _isConnected;
            public event Action<ClientContext> Disconnected;
    
            public ServerContext Application { get; private set; }
            public ClientSystems Systems { get; private set; }
            public ILifetimeScope ClientScope { get; private set; }
            public IServerTransport Transport { get; private set; }
            public ComponentMap SystemsComponentMap { get; private set; }
    
            public UserDetails UserDetails { get; private set; }
            public bool IsAuthenticated => UserDetails != null;
    
            protected ClientContext(ServerContext application, ComponentMap systemsComponentMap, IServerTransport transport)
            {
                _isConnected = true;
    
                Application = application;
                Systems = new ClientSystems(this);
                ClientScope = application.Container.BeginLifetimeScope(this);
                SystemsComponentMap = systemsComponentMap;
                Transport = transport;
    
                transport.SendData(application.Events.SyncComponentMap(EventCode.SyncSystemsComponentMap, systemsComponentMap));
            }
    
            public void Login(User user, string requestIp)
            {
                if (IsAuthenticated)
                    throw new RpcException("Client already authenticated");
    
                // save the user's LastLogin and ip associated with the token or just an ip called debug if none
                using (var database = new MMODatabaseContext())
                {
                    var foundUser = database.Users.Find(user.Id);
                    if (foundUser == null)
                    {
                        throw new RpcException("User not found when logging in to game");
                    }
    
                    foundUser.LastLogin = DateTime.UtcNow;
                    foundUser.LastIp = requestIp;
                    database.Users.AddOrUpdate();
                    database.SaveChanges();
                }
    
                UserDetails = new UserDetails(
                    user.Id, 
                    user.Username, 
                    new HashSet<string>(user.Roles.Select(r => r.Name))
                    );
            }
    
            public virtual void OnOperationRequest(OperationCode code, Dictionary<byte, object> parameters)
            {
                InvokeMethodOnSystem((byte)code, parameters);
            }
    
            protected void InvokeMethodOnSystem(byte serverInterfaceComponentId, Dictionary<byte, object> requestParameters)
            {
                var serverInterfaceComponent = SystemsComponentMap.Components[serverInterfaceComponentId];
                var methodId = requestParameters.Get<byte>(OperationParameter.MethodId);
                var method = serverInterfaceComponent.Methods[methodId];
                var argumentBytes = requestParameters.Get<byte[]>(OperationParameter.ArgumentBytes);
    
                object[] arguments;
                using (var ms = new MemoryStream(argumentBytes))
                using (var br = new BinaryReader(ms))
                {
                    arguments = Application.Serializer.ReadArguments(br, method.ParameterTypes);
                }
    
                var systemObject = Systems.GetByServerInterfaceId(serverInterfaceComponentId);
    
                if (method.ReturnType == MappedMethodReturnType.Void)
                {
                    method.Invoke(systemObject, arguments);
                    return;
                }
    
                Deferred deferred = null;
    
                try
                {
                    deferred = (Deferred)method.Invoke(systemObject, arguments);
    
                    if (deferred == null)
                        throw new InvalidOperationException($"Method {method.MethodInfo.Name} on component {method.Component.Type.Name} returned a null deferred");
                }
                catch (RpcException e)
                {
                    deferred = Deferred.Fail(e.Message);
                    throw;
                }
                catch
                {
                    deferred = Deferred.Fail("There was a fatal error");
                    throw;
                }
                finally
                {
                    var responseParameters = new Dictionary<byte, object>
                    {
                        [(byte)OperationParameter.SystemInvokeId] = requestParameters.Get<byte>(OperationParameter.SystemInvokeId)
                    };
    
                    deferred
                        .Save(responseParameters, Application.Serializer)
                        .OnSuccess(() => Transport.SendOperationResponse(OperationCode.SendSystemResponse, responseParameters));
                }
            }
    
            internal void OnDisconnect()
            {
                Systems.Dispose();
                ClientScope.Dispose();
                _isConnected = false;
    
                Disconnected?.Invoke(this);
            }
    
            public void Dispose()
            {
                Transport.Disconnect();
            }
        }
    }
    5. Edit BuzzMMO.Data.Services/GameEntityService.cs to remove Nelson's dummy ip address
    Code:
    using System;
    using System.Collections.Generic;
    using System.Data.Entity.Validation;
    using System.Linq;
    using System.Security.Cryptography;
    using BuzzMMO.Base.Components;
    using BuzzMMO.Data.Entities;
    using log4net;
    
    namespace BuzzMMO.Data.Services
    {
        public class GameEntityService
        {
            private static readonly ILog Log = LogManager.GetLogger(typeof(GameEntityService));
    
            private readonly MMODatabaseContext _database;
    
            public GameEntityService(MMODatabaseContext database)
            {
                _database = database;
            }
    
            public GameEntity CreateGame(Guid serverId, GameParameters parameters)
            {
                var gameEntity = new GameEntity
                {
                    LevelName = "Level1",
                    Server = _database.GameServers.Find(serverId),
                    Players = new HashSet<UserInGame>(),
                    CreatedAt = DateTime.UtcNow
                };
    
                using (var random = new RNGCryptoServiceProvider())
                {
                    foreach (var player in parameters.PlayerDetails)
                    {
                        var buffer = new byte[32];
                        random.GetNonZeroBytes(buffer);
                        
                        gameEntity.Players.Add(new UserInGame
                        {
                            Game = gameEntity,
                            Player = _database.Users.Find(player.Id),
                            Hero = player.Hero,
                            Team = player.Team,
                            Token = Convert.ToBase64String(buffer),
                        });
                    }
                }
    
                _database.Games.Add(gameEntity);
    
                try
                {
                    _database.SaveChanges();
                }
                catch (DbEntityValidationException e)
                {
                    Log.Error(string.Join(", ", e.EntityValidationErrors.Select(t => string.Join(", ", t.ValidationErrors.Select(t2 => $"{t2.PropertyName}: {t2.ErrorMessage}")))));
                    throw;
                }
    
                return gameEntity;
            }
    
            public UserInGame GetPlayerInGame(Guid serverId, int gameId, string token)
            {
                var userInGame = _database.UsersInGame.FirstOrDefault(
                        t => t.Game.Server.Id == serverId && t.Game.Id == gameId && t.Token == token);
    
                if (userInGame == null)
                {
                    Log.Warn($"Player with token {token} for game {gameId} not found");
                    return null;
                }
    
                if (userInGame.Token != token)
                {
                    Log.Warn($"Player with token {token} for game {gameId} has an invalid token");
                    return null;
                }
    
                return userInGame;
            }
    
            public void DestroyGame(int gameId)
            {
                var game = _database.Games.Find(gameId);
                if (game == null)
                    throw new ArgumentException($"Game with id {gameId} not found!", nameof(gameId));
    
                game.DestroyedAt = DateTime.UtcNow;
                _database.SaveChanges();
            }
        }
    }
    5. Edit BuzzMMO.Web.Controllers/AuthController.cs to record the login date/time when logging in to the website:
    Code:
    using System;
    using System.Data.Entity.Migrations;
    using System.Linq;
    using System.Web.Mvc;
    using BuzzMMO.Base;
    using BuzzMMO.Data;
    using BuzzMMO.Web.ViewModels;
    
    namespace BuzzMMO.Web.Controllers
    {
        public class AuthController : Controller
        {
            public ActionResult Login()
            {
                return View(new AuthLogin());
            }
    
            [HttpPost]
            public ActionResult Login(AuthLogin form, string returnUrl)
            {
                if (!ModelState.IsValid)
                    return View(form);
    
                using (var database = new MMODatabaseContext())
                {
                    var user = database.Users.SingleOrDefault(t => t.Username == form.Username);
    
    
                    if (user == null)
                        Data.Entities.User.FakeHash();
    
                    if (user == null || !user.CheckPassword(form.Password))
                    {
                        ModelState.AddModelError("Password", "Username or Password is incorrect");
                        return View(form);
                    }
    
                    user.LastLogin = DateTime.UtcNow;
                    database.Users.AddOrUpdate();
                    database.SaveChanges();
    
                    // set the cookie
                    Auth.User = user;
    
                    if (!string.IsNullOrWhiteSpace(returnUrl))
                        return Redirect(returnUrl);
    
                    return RedirectToAction("Index", "Home", new { area = "" });
                }
            }
    
            [HttpPost]
            [Authorize(Roles = Roles.Registered)]
            public ActionResult Logout()
            {
                Auth.Logout();
                return RedirectToAction("Login");
            }
        }
    }
    5. Edit BuzzMMO.Server.Master.Systems/LoginSystem.cs to include the ipaddress when reading the user table. We now add an extra argument when calling Context.Login to pass in the ip address
    Code:
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using BuzzMMO.Base;
    using BuzzMMO.Base.Async;
    using BuzzMMO.Base.Components.Systems.Master;
    using BuzzMMO.Data;
    using log4net;
    
    namespace BuzzMMO.Server.Master.Systems
    {
        public class LoginSystem : PlayerSystemBase<ILoginSystemServer, ILoginSystemClient>, ILoginSystemServer
        {
            private static readonly ILog Log = LogManager.GetLogger(typeof(LoginSystem));
    
            private readonly MMODatabaseContext _database;
            private readonly MainLobbyService _mainLobby;
    
            public LoginSystem(MMODatabaseContext database, MainLobbyService mainLobby)
            {
                _database = database;
                _mainLobby = mainLobby;
            }
    
            protected override void Awake()
            {
                Proxy.DebugSetAvailablePlayers(_database.Users.Include(u => u.Roles).AsEnumerable().Select(
                    user => new UserDetails(
                        user.Id, 
                        user.Username, 
                        new HashSet<string>(user.Roles.Select(r => r.Name))))
                        .ToArray());
    
                // included only in bi-directional server client communications
                //var def = Proxy.DebugSetAvailablePlayers(_database.Users.Include(u => u.Roles).AsEnumerable().Select(
                //    user => new UserDetails(user.Id, user.Username, new HashSet<string>(user.Roles.Select(r => r.Name)))).ToArray());
    
                //def.OnSuccess(res =>
                //{
                //    Log.Info($"Got {def.Result} from client");
                //});
            }
    
            // client logging in to master server with a token
            public Deferred<UserDetails> Login(string token)
            {
                Log.Info($"Logging in with token {token}");
    
                if (string.IsNullOrWhiteSpace(token))
                    return Deferred.Fail<UserDetails>("Invalid token");
    
                var tokenRecord = _database.ClientAuthenticationTokens
                    .Include(t => t.RequestIp)
                    .Include(t => t.User)
                    .Include(t => t.User.Roles)
                    .FirstOrDefault(t => t.Token == token);
    
                if (tokenRecord == null)
                    return Deferred.Fail<UserDetails>("Invalid token");
    
                Context.Login(tokenRecord.User, tokenRecord.RequestIp);
                return Deferred.Success(Context.UserDetails);
            }
    
            public Deferred<UserDetails> DebugLogin(int userId)
            {
                Log.Info($"Logging in with debug login rather than a token {userId}");
    
                var user = _database.Users.Include(u => u.Roles).FirstOrDefault(u => u.Id == userId);
                if (user == null)
                    return Deferred.Fail<UserDetails>("Invalid user id");
    
                Context.Login(user, "Debug");
                return Deferred.Success(Context.UserDetails);
            }
    
            public Deferred JoinMainLobby()
            {
                Log.Info("Joining main lobby");
    
                return _mainLobby.AddPlayer(Context)
                    .OnSuccess(() => Context.Systems.Destroy<LoginSystem>());
            }
        }
    }
    6. Edit BuzzMMO.Server.Region/Peer.cs to also include the new ipaddress argument when calling Context.Login. This actually gets the ipaddress too from the MMOPeerBase when logging in with a token to the lobby
    Code:
    using Autofac;
    using BuzzMMO.Base;
    using BuzzMMO.Data.Services;
    using log4net;
    using Photon.SocketServer;
    
    namespace BuzzMMO.Server.Region
    {
        public class Peer : MMOPeerBase<ClientContext>
        {
            private static readonly ILog Log = LogManager.GetLogger(typeof(Peer));
    
            private readonly GameServerContext _application;
    
            public Peer(GameServerContext application, InitRequest initRequest) : base(initRequest)
            {
                _application = application;
            }
    
            protected override ClientContext CreateContext(ContextType type, OperationRequest request)
            {
                if (type == ContextType.Player)
                {
                    var gameId = (int)request[(byte)OperationParameter.GameId];
                    var token = (string)request[(byte)OperationParameter.Token];
    
                    Log.Info($"Player with token {token} is connecting to game {gameId}");
    
                    var gameEntityService = _application.Container.Resolve<GameEntityService>();
                    var playerInGame = gameEntityService.GetPlayerInGame(
                        _application.Config.GameServer.UniqueId,
                        gameId,
                        token);
    
                    if (playerInGame == null || playerInGame.HasAbandoned)
                    {
                        Disconnect();
                        return null;
                    }
    
                    var context = new PlayerContextGame(_application, this);
                    context.Login(playerInGame.Player, RemoteIP);           //found in MMOPeerBase
                    Log.Info($"Player from {RemoteIP} connected as {context.UserDetails.Username}");
    
                    _application.AddPlayerToGame(gameId, context)
                        .OnFail(Disconnect);
    
                    return context;
                }
    
                return null;
            }
        }
    }

    Now whenever we login we record what information we can get. (Logging in to the website doesn't save the ip address though)
    Last edited by oldngrey; Today at 03:19 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •