/// <summary>
 ///     Default constructor to be used by protobuf.
 /// </summary>
 public AddCharacterMessage()
 {
     AddingCharacter = new Character();
     UserId = Guid.Empty;
     Location = new Vector3();
     TeleportExitId = 0;
     UseExitOrLocation = ExitOrLocation.Location;
 }
 /// <summary>
 ///     Constructor specifying entering charachter, teleport exit id, and user id. This assumes that the character is entering at a teleport exit id.
 /// </summary>
 /// <param name="character">The entering character.</param>
 /// <param name="teleportexitid">The id of the teleport exit that is being entered upon.</param>
 /// <param name="userid">The id of the user that owns the character that is entering.</param>
 public AddCharacterMessage(Character character, ushort teleportexitid, Guid userid)
 {
     AddingCharacter = character;
     UserId = userid;
     Location = new Vector3();
     TeleportExitId = teleportexitid;
     UseExitOrLocation = ExitOrLocation.Exit;
 }
 /// <summary>
 ///     Constructor specifying entering character, location, and user id. This assumes that the character is entering at a location.
 /// </summary>
 /// <param name="character">The character that is entering.</param>
 /// <param name="location">The location that the character will be at upon entering.</param>
 /// <param name="userid">The id of the user that owns the character that is entering.</param>
 public AddCharacterMessage(Character character, Vector3 location, Guid userid)
 {
     AddingCharacter = character;
     UserId = userid;
     Location = location;
     TeleportExitId = 0;
     UseExitOrLocation = ExitOrLocation.Location;
 }
        /// <summary>
        ///     Sends an enter region event to the supplied net connection.
        /// </summary>
        /// <param name="netConnection">The net connection that the event is going to.</param>
        /// <param name="character">The character that is entering.</param>
        /// <param name="userid">The user id that corresponds to the user that owns the character that is entering.</param>
        /// <remarks>
        ///     Events have the form:
        ///     Byte 1: Data Type - Event
        ///     Byte 2: ServerEventCode
        ///     Byte 3-n: Data
        /// </remarks>
        private void SendEnterRegionEvent(NetConnection netConnection, Character character, Guid userid)
        {
            EnterRegionEvent enterregionevent;
            enterregionevent = new EnterRegionEvent(character, userid);

            NetOutgoingMessage msg = LidgrenServer.CreateMessage();

            msg.Write((byte)DataTypes.Event);
            msg.Write((byte)ServerEventCodes.EnterRegion);
            msg.Write(Serialize(enterregionevent));

            LidgrenServer.SendMessage(msg, netConnection, NetDeliveryMethod.ReliableUnordered);
        }
        //MESSAGES MESSAGES MESSAGES MESSAGES
        /// <summary>
        ///     Sends an add character message to the region server corresponding to the region id found in character.
        ///     If teleportexitid is null, then it uses the position to enter at. If the position is null, use the teleportexitid
        ///     to determine where the character should spawn on the region.
        /// </summary>
        /// <param name="character">The character that is being added to the region server.</param>
        /// <param name="position">The character's possible spawn position.</param>
        /// <param name="teleportexitid">The teleport exit the character could come out of.</param>
        /// <param name="userid">The user id of the user that owns the character that is entering.</param>
        /// <remarks>
        ///     Messages have the form:
        ///     Byte 1: Region Message code
        ///     Byte 2-n: Data
        /// </remarks>
        /// <exception cref="Exception">
        ///     Exception is thrown if neither a position nor a teleport exit id is provided.
        /// </exception>
        private void AddCharacterMessage(Character character, Vector3 position, ushort? teleportexitid, Guid userid)
        {
            AddCharacterMessage addcharactermessage;
            if (teleportexitid != null)
            {
                addcharactermessage = new AddCharacterMessage(character, (ushort)teleportexitid, userid);
                addcharactermessage.UseExitOrLocation = ExitOrLocation.Exit;
                Console.WriteLine("Adding Character with teleportexit");
            }
            else if (position != null)
            {
                addcharactermessage = new AddCharacterMessage(character, position, userid);
                addcharactermessage.UseExitOrLocation = ExitOrLocation.Location;
                Console.WriteLine("Adding Character with position");
            }
            else
            {
                throw new Exception("Character must be added to a region using either position or a teleport exit id!");
            }

            NetOutgoingMessage msg = RegionServers[character.RegionId].CreateMessage();

            msg.Write((byte)RegionMessages.AddCharacter);
            msg.Write(Serialize(addcharactermessage));
            RegionServers[character.RegionId].SendMessage(msg, NetDeliveryMethod.ReliableOrdered);
        }
        /// <summary>
        ///     Process all information coming from clients, and other servers.
        /// </summary>
        /// <remarks>
        ///     Connection Approval is enabled, so when a client connects to this server it must
        ///     provide the correct data. That is a username and id.
        ///     The login server gets a special case, in that its username is "lgn", and its id is Guid.Empty.
        ///     If the master server hasn't been notified by the login server that a user is logging in, it won't
        ///     know about this information and will reject the client.
        /// </remarks>
        public void Process()
        {
            NetIncomingMessage msg;
            //Read what incoming messages we have from clients
            while ((msg = LidgrenServer.ReadMessage()) != null)
            {
                switch (msg.MessageType)
                {
                    //If it is a debug message, warning message, or status changed print the relevant information to the console
                    case NetIncomingMessageType.DebugMessage:
                        Console.WriteLine("Debug message: " + msg.ReadString());
                        break;

                    case NetIncomingMessageType.WarningMessage:
                        Console.WriteLine("Warning message: " + msg.ReadString());
                        break;

                    case NetIncomingMessageType.StatusChanged:
                        Console.WriteLine("Status changed for " + msg.SenderEndpoint + " to " + msg.SenderConnection.Status);
                        break;

                    //Check the connection approval for an accepted username and userid
                    case NetIncomingMessageType.ConnectionApproval:
                        //Read the username an userid from the message
                        string username = msg.ReadString();
                        Guid userid = new Guid(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes));

                        //If the username is "lgn" and the userid is Guid.Empty, this is the login server's client. Let it connect regardless.
                        //We should probably validate this harder in the future
                        if (username == "lgn" && userid == Guid.Empty)
                        {
                            Console.WriteLine("Login accept");
                            msg.SenderConnection.Approve();
                        }
                        //If the user with the userid and username has already been accepted by the login server
                        //and their information has been sent here, let them be accepted.
                        else if (Users.ContainsKey(userid) && Users[userid].Username == username)
                        {
                            Console.WriteLine("User: "******" accept.");
                            Users[userid].Connection = msg.SenderConnection;
                            msg.SenderConnection.Approve();
                        }
                        //Otherwise, don't let them in here.
                        else
                        {
                            Console.WriteLine("User: "******" with userid " + userid + " reject.");
                            msg.SenderConnection.Deny();
                        }
                        break;

                    case NetIncomingMessageType.Data:
                        //We are decrypting data. All data sent to a master server is encrypted with key "DERPHERP".
                        msg.Decrypt(new NetXtea("DERPHERP"));

                        //Figure out where this data came from.
                        CallSources source = (CallSources)msg.ReadByte();
                        Console.WriteLine("Got data from " + source);

                        //If we got the data from a login server
                        if (source == CallSources.LoginServer)
                        {
                            //Figure out what kind of login server request it is.
                            LoginServerRequests request = (LoginServerRequests)msg.ReadByte();

                            //If a user logged in, deal with it.
                            if (request == LoginServerRequests.Login)
                            {
                                //Deserialize our login message from the login server
                                LoginMessage loginmessage = new LoginMessage();
                                loginmessage = Serializer.Deserialize<LoginMessage>(new MemoryStream(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes)));
                                //Create a new user with the relevant information.
                                User u = new User();
                                u.Connection = msg.SenderConnection;
                                u.Id = loginmessage.LoginUser.Id;
                                u.Username = loginmessage.LoginUser.Username;
                                //Add the user to our master server
                                Users.Add(loginmessage.LoginUser.Id, u);
                                Console.WriteLine(u.Username + " logged in.");
                            }
                            //If a user logged out, double deal with it.
                            else if (request == LoginServerRequests.Logout)
                            {
                                //Deserialize the logout message
                                LogoutMessage logoutmessage = new LogoutMessage();
                                logoutmessage = Serializer.Deserialize<LogoutMessage>(new MemoryStream(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes)));
                                Console.WriteLine(Users[logoutmessage.UserId].Username + " logged out.");
                                //If this user had an active character (that would be on a region server) send a message to the
                                //region server to remove the character.
                                if(Users[logoutmessage.UserId].ActiveCharacter != null)
                                    SendRemoveCharacterMessage(Users[logoutmessage.UserId].ActiveCharacter.RegionId, logoutmessage.UserId);
                                //Remove the user from the master server.
                                Users.Remove(logoutmessage.UserId);
                            }
                        }
                        //If we got a message from the client
                        else if (source == CallSources.Client)
                        {
                            //It is a remote procedure call, so figure out what kind it is
                            RemoteProcedureCallCodes callcode = (RemoteProcedureCallCodes)msg.ReadByte();
                            //The user created a character.
                            if (callcode == RemoteProcedureCallCodes.CreateCharacter)
                            {
                                //Deserialize that message
                                CreateCharacterRequest createcharactersrequest = new CreateCharacterRequest();
                                createcharactersrequest = Serializer.Deserialize<CreateCharacterRequest>(new MemoryStream(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes)));

                                //See if a character with the name requested already exists.
                                if (!CharacterExists(createcharactersrequest.Name))
                                {
                                    //Create a new character with a new guid.
                                    Guid newguid = Guid.NewGuid();
                                    //With arbitrary information about the character for now. Default regionid 0, default pos and rot, default race, height and weight.
                                    Character character = new Character(createcharactersrequest.Name, RaceCode.human, 5, 170, newguid, new Vector3(0.0f, 10.0f, 0.0f), new Vector3(0.0f, 0.0f, 0.0f), 0, new Dictionary<ulong,int>());
                                    //Add the character to be associated with this user id.
                                    UserIdToCharacters[createcharactersrequest.UserId].Add(character);
                                    //Send a resposne to the client saying the creation was successful
                                    SendCreateCharacterResponse(msg.SenderConnection, true);
                                    Console.WriteLine("User " + Users[createcharactersrequest.UserId].Username + " created a character named " + createcharactersrequest.Name);
                                }
                                else
                                {
                                    //Otherwise send a response to the client saying the creation was unsuccessful
                                    SendCreateCharacterResponse(msg.SenderConnection, false);
                                    Console.WriteLine("User " + Users[createcharactersrequest.UserId].Username + " couldn't create a character named " + createcharactersrequest.Name);
                                }
                            }
                            //The user needs to get characters, so they can see which one to select
                            else if (callcode == RemoteProcedureCallCodes.GetCharacters)
                            {
                                //Deserialize the get characters request
                                GetCharactersRequest getcharactersrequest = new GetCharactersRequest();
                                getcharactersrequest = Serializer.Deserialize<GetCharactersRequest>(new MemoryStream(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes)));

                                //Make sure this user has a character list started
                                if (UserIdToCharacters.ContainsKey(getcharactersrequest.UserId))
                                {
                                    //If so, send the get characters response
                                    SendGetCharactersResponse(msg.SenderConnection, getcharactersrequest.UserId);
                                }
                                else
                                {
                                    //Else make a new list, and send the get characters response
                                    UserIdToCharacters.Add(getcharactersrequest.UserId, new List<Character>());
                                    SendGetCharactersResponse(msg.SenderConnection, getcharactersrequest.UserId);
                                }
                                Console.WriteLine("Getting Characters for " + Users[getcharactersrequest.UserId].Username);
                            }
                            //The user selected a character and is entering the world.
                            else if (callcode == RemoteProcedureCallCodes.EnterWorld)
                            {
                                //Deserialize the enterworld request
                                EnterWorldRequest enterworldrequest = new EnterWorldRequest();
                                enterworldrequest = Serializer.Deserialize<EnterWorldRequest>(new MemoryStream(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes)));

                                //Set this users active character to the one selected.
                                Users[enterworldrequest.UserId].ActiveCharacter = UserIdToCharacters[enterworldrequest.UserId].Find(delegate(Character c) { return c.Id == enterworldrequest.CharacterId; });

                                //Send an Add Character Message to the appropriate region server
                                AddCharacterMessage(UserIdToCharacters[enterworldrequest.UserId].Find(delegate(Character c) { return c.Id == enterworldrequest.CharacterId; }), Users[enterworldrequest.UserId].ActiveCharacter.Position, null, enterworldrequest.UserId);
                                Console.WriteLine("User " + Users[enterworldrequest.UserId].Username + " made their character " + Users[enterworldrequest.UserId].ActiveCharacter.Name + " enter the world into region " + Users[enterworldrequest.UserId].ActiveCharacter.RegionId);

                            }
                            else if (callcode == RemoteProcedureCallCodes.Teleport)
                            {
                                TeleportRequest request = Serializer.Deserialize<TeleportRequest>(new MemoryStream(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes)));

                                TeleportMessage(Users[request.UserId].ActiveCharacter.RegionId, request.UserId, request.TeleportId);
                                Console.WriteLine("Teleport request.");
                            }
                            //The client sent a character snapshot (position, rotation, velocity)
                            else if (callcode == RemoteProcedureCallCodes.CharacterSnapshot)
                            {
                                CharacterSnapshot snapshot = new CharacterSnapshot();
                                snapshot = Serializer.Deserialize<CharacterSnapshot>(new MemoryStream(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes)));
                                SendCharacterSnapshotMessage(Users[snapshot.UserId].ActiveCharacter.RegionId, snapshot);
                                //Console.WriteLine("Snapshot for " + Users[snapshot.UserId].ActiveCharacter.Name);
                            }

                            else if (callcode == RemoteProcedureCallCodes.DropItem)
                            {
                                DropItemRequest request = Serializer.Deserialize<DropItemRequest>(new MemoryStream(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes)));

                                DropItemMessage(Users[request.UserId].ActiveCharacter.RegionId, request.ItemId, request.UserId);
                                Console.WriteLine("Drop Item request for " + Users[request.UserId].ActiveCharacter.Name);
                            }
                            else if (callcode == RemoteProcedureCallCodes.PickupItem)
                            {
                                PickupItemRequest request = Serializer.Deserialize<PickupItemRequest>(new MemoryStream(msg.ReadBytes(msg.LengthBytes - msg.PositionInBytes)));
                                //TODO
                                PickupItemMessage(Users[request.UserId].ActiveCharacter.RegionId, request.UserId, request.ItemSpawnId);
                            }
                        }
                        break;
                }
                LidgrenServer.Recycle(msg);
            }
            //Or we recieved a message from one of the many region servers
            foreach (NetClient regionclient in RegionServers.Values)
            {
                NetIncomingMessage regionmessage;
                //Read a message from the queue
                while ((regionmessage = regionclient.ReadMessage()) != null)
                {
                    switch (regionmessage.MessageType)
                    {
                        //If it is debug, warning, or status changed output to screen
                        case NetIncomingMessageType.DebugMessage:
                            Console.WriteLine("Region Debug: " + regionmessage.ReadString());
                            break;
                        case NetIncomingMessageType.WarningMessage:
                            Console.WriteLine("Region Warning: " + regionmessage.ReadString());
                            break;

                        case NetIncomingMessageType.StatusChanged:
                            Console.WriteLine("Region status changed: " + regionmessage.SenderConnection.Status);
                            break;

                        //Or we got some actual data
                        case NetIncomingMessageType.Data:
                            //Figure out what kind of message it is
                            RegionMessages messagetype = (RegionMessages)regionmessage.ReadByte();

                            //A character was added to this region
                            if (messagetype == RegionMessages.AddCharacterBroadcast)
                            {
                                Console.WriteLine("Got an Add Character broadcast from a region.");
                                //Deserialize the broadcast
                                AddCharacterBroadcast addbroadcast = Serializer.Deserialize<AddCharacterBroadcast>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));

                                //Send an event to all the relevant users using their userid
                                foreach (Guid userid in addbroadcast.RecivingUserIds)
                                {
                                    SendEnterRegionEvent(Users[userid].Connection, addbroadcast.EnteringCharacter, addbroadcast.UserId);
                                }
                            }
                            //A user asked what characters were in this region, so we relay it back via enter region response
                            else if (messagetype == RegionMessages.AddCharacterResponse)
                            {
                                Console.WriteLine("Relaying characters in region as well as position.");
                                AddCharacterResponseMessage message = Serializer.Deserialize<AddCharacterResponseMessage>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));
                                Console.WriteLine(message.Location.X + " " + message.Location.Y + " " + message.Location.Z);
                                SendEnterRegionResponse(Users[message.MeantForUserId].Connection, message.Characters, message.ItemSpawns, message.Location, message.RegionId);
                            }
                            //A character was removed from this region
                            else if (messagetype == RegionMessages.RemoveCharacterBroadcast)
                            {
                                Console.WriteLine("Got a Remove Character broadcast from a region.");
                                //Deserialize the broadcast
                                RemoveCharacterBroadcast exitregionbroadcast = Serializer.Deserialize<RemoveCharacterBroadcast>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));

                                //Send it to all relevant user ids
                                foreach (Guid userid in exitregionbroadcast.RecivingUserIds)
                                {
                                    SendExitRegionEvent(Users[userid].Connection, exitregionbroadcast.UserId);
                                }
                            }
                            //A character sent a snapshot of their information to the region
                            else if (messagetype == RegionMessages.CharacterSnapshotBroadcast)
                            {
                                Console.WriteLine("Got a character snapshot broadcast from a region.");
                                CharacterSnapshotBroadcast charsnapbroadcast = Serializer.Deserialize<CharacterSnapshotBroadcast>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));

                                //Send the event to all relevant users
                                foreach (Guid userid in charsnapbroadcast.ReceivingUserIds)
                                    SendCharacterSnapshotEvent(Users[userid].Connection, charsnapbroadcast.Snapshot);
                            }

                            else if (messagetype == RegionMessages.TeleportCharacterResponse)
                            {
                                TeleportResponseMessage message = Serializer.Deserialize<TeleportResponseMessage>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));
                                Users[message.UserId].ActiveCharacter.RegionId = message.ExitRegionId;
                                Console.WriteLine("Got teleport character response, and adding character.");
                                AddCharacterMessage(Users[message.UserId].ActiveCharacter, null, message.TeleportExitId, message.UserId);

                            }

                            else if (messagetype == RegionMessages.DropItemResponse)
                            {
                                DropItemResponseMessage message = Serializer.Deserialize<DropItemResponseMessage>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));

                                SendDropItemResponse(Users[message.UserId].Connection, message.Success, message.ItemId);
                            }

                            else if(messagetype == RegionMessages.DropItemBroadcast)
                            {
                                DropItemBroadcast broadcast = Serializer.Deserialize<DropItemBroadcast>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));

                                foreach (Guid userid in broadcast.ReceivingUserIds)
                                {
                                    SendCreateItemSpawnEvent(Users[userid].Connection, broadcast.NewItemSpawn);
                                }
                            }
                            else if(messagetype == RegionMessages.PickupItemResponse)
                            {
                                PickupItemResponseMessage message = Serializer.Deserialize<PickupItemResponseMessage>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));

                                SendTakeItemResponse(Users[message.UserId].Connection, message.Success, message.ItemId);
                            }
                            else if(messagetype == RegionMessages.PickupItemBroadcast)
                            {
                                PickupItemBroadcast broadcast = Serializer.Deserialize<PickupItemBroadcast>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));

                                foreach(Guid userid in broadcast.Recipients)
                                {
                                    SendTakeItemEvent(Users[userid].Connection, broadcast.ItemSpawnId);
                                }
                            }
                            else if (messagetype == RegionMessages.SpawnItemBroadcast)
                            {
                                SpawnItemBroadcast broadcast = Serializer.Deserialize<SpawnItemBroadcast>(new MemoryStream(regionmessage.ReadBytes(regionmessage.LengthBytes - regionmessage.PositionInBytes)));

                                foreach(Guid userid in broadcast.Recipients)
                                {
                                    SendSpawnItemEvent(Users[userid].Connection, broadcast.SpawnId, broadcast.ItemId);
                                }
                            }
                            break;
                    }
                    LidgrenServer.Recycle(regionmessage);
                }
            }
        }
        /// <summary>
        ///     Broadcast that get's sent to multiple master servers when a character is added to this region.
        /// </summary>
        /// <param name="connection">The connection to send the broadcast to.</param>
        /// <param name="character">The character that was added.</param>
        /// <param name="userid">The userid of the character that was added.</param>
        /// <param name="recipients">The recipients that will recieve this on the master server it is going to.</param>
        /// <remarks>
        ///     Region Message Schema:
        ///     Byte 1: RegionMessage code
        ///     Byte 2-N: Data
        /// </remarks>
        private void SendAddCharacterBroadcast(NetConnection connection, Character character, Guid userid, List<Guid> recipients)
        {
            AddCharacterBroadcast enterregionbroadcast = new AddCharacterBroadcast()
            {
                EnteringCharacter = character,
                UserId = userid,
                RecivingUserIds = recipients
            };

            NetOutgoingMessage broadcastmessage = LidgrenServer.CreateMessage();
            broadcastmessage.Write((byte)RegionMessages.AddCharacterBroadcast);
            broadcastmessage.Write(Serialize(enterregionbroadcast));
            LidgrenServer.SendMessage(broadcastmessage, connection, NetDeliveryMethod.ReliableUnordered);
        }
 /// <summary>
 ///     Constructor specifying the entering character, the recipients and the user id.
 /// </summary>
 /// <param name="character">Character entering the region.</param>
 /// <param name="recipients">Recipients of this broadcast.</param>
 /// <param name="userid">The id of the user that owns the character that is entering.</param>
 public AddCharacterBroadcast(Character character, List<Guid> recipients, Guid userid)
 {
     EnteringCharacter = character;
     RecivingUserIds = recipients;
     UserId = userid;
 }
 /// <summary>
 ///     Default constructor required by protobuf.
 /// </summary>
 public AddCharacterBroadcast()
 {
     EnteringCharacter = new Character();
     RecivingUserIds = new List<Guid>();
     UserId = Guid.Empty;
 }
 /// <summary>
 ///     Constructor specifying the characters and the count of characters.
 /// </summary>
 /// <param name="chars">The characters owned by a user.</param>
 /// <param name="count">The count of characters owned by a user.</param>
 public GetCharactersResponse(Character[] chars, uint count)
 {
     Characters = chars;
     Count = count;
 }
 /// <summary>
 ///     Constructor letting entering character and user id be defined.
 /// </summary>
 /// <param name="c">The character that's entering the region.</param>
 /// <param name="userid">The userid to the user that owns this character.</param>
 public EnterRegionEvent(Character c, Guid userid)
 {
     EnteringCharacter = c;
     UserId = userid;
 }