/// <summary> /// Send a shutdown notification message to a destination server. /// </summary> /// <param name="Server">Supplies the destination server.</param> public void SendMessageShutdownNotify(GameServer Server) { NetworkMessage Message = CreateDatagramMessage(DATAGRAM_MESSAGE_CMD.CMD_SHUTDOWN_NOTIFY); BufferBuilder Builder = Message.GetBuilder(); Builder.WriteInt32(LocalServerId); SendMessageToServer(Message, Server); }
/// <summary> /// Send an IPC wakeup message to a destination server. /// </summary> /// <param name="Server">Supplies the destination server.</param> public void SendMessageIPCWakeup(GameServer Server) { NetworkMessage Message = CreateDatagramMessage(DATAGRAM_MESSAGE_CMD.CMD_IPC_WAKE); BufferBuilder Builder = Message.GetBuilder(); Builder.WriteInt32(LocalServerId); Builder.WriteInt32(Server.ServerId); SendMessageToServer(Message, Server); }
/// <summary> /// Send a database online/offline notification to a server. /// </summary> /// <param name="Server">Supplies the destination server.</param> /// <param name="Online">Supplies 1 if the database is viewed as /// online, else 0 if the database is viewed as offline.</param> public void SendNotifyMessageDatabaseStatus(GameServer Server, bool Online) { NetworkMessage Message = CreateDatagramMessage(DATAGRAM_MESSAGE_CMD.CMD_NOTIFY_DATABASE_STATUS); BufferBuilder Builder = Message.GetBuilder(); Builder.WriteInt32(LocalServerId); Builder.WriteByte(Online ? (byte)1 : (byte)0); SendMessageToServer(Message, Server); }
/// <summary> /// Send a direct message to a server. Failures are ignored. /// </summary> /// <param name="Data">Supplies the message payload.</param> /// <param name="Server">Supplies the destination server.</param> private void SendMessageToServer(byte[] Data, GameServer Server) { try { IPAddress Address; if (Server.ServerId == LocalServerId) Address = IPAddress.Loopback; else Address = Server.GetIPAddress(); SocketIo.SendMessage(Data, Address, Server.ServerPort); } catch { } }
/// <summary> /// This function inserts a server into the various server lists and /// issues the server load event. /// </summary> /// <param name="Server">Supplies the server object to insert. /// </param> /// <param name="Database">Supplies the database connection to use for /// queries, if required. The active rowset may be consumed.</param> private void InsertNewServer(GameServer Server, IALFADatabase Database) { // // Mark the server as visited so that if we come in on the main // thread during the middle of a server synchronization cycle, we // won't immediate offline the server. // Server.Visited = true; Server.RefreshOnlineStatus(Database); ServerList.Add(Server); OnServerLoaded(Server); }
/// <summary> /// Handle a database status notification. /// </summary> /// <param name="Message">Supplies the message.</param> /// <param name="Source">Supplies the message sender.</param> private void OnRecvMessageNotifyDatabaseStatus(NetworkMessage Message, GameServer Source) { BufferParser Parser = Message.GetParser(); int SourceServerId = Parser.ReadInt32(); bool DatabaseOnline = (Parser.ReadByte() == 0 ? false : true); Parser.FinishParsing(); if (SourceServerId != Source.ServerId) return; lock (WorldManager) { if (Source.Online) Source.DatabaseOnline = DatabaseOnline; } }
/// <summary> /// Handle a shutdown notification message. /// </summary> /// <param name="Message">Supplies the message.</param> /// <param name="Source">Supplies the message sender.</param> private void OnRecvMessageShutdownNotify(NetworkMessage Message, GameServer Source) { BufferParser Parser = Message.GetParser(); int SourceServerId = Parser.ReadInt32(); Parser.FinishParsing(); if (SourceServerId != Source.ServerId) return; // // Tear down any state between the two servers. // }
/// <summary> /// Handle an IPC wakeup message received. /// </summary> /// <param name="Message">Supplies the message.</param> /// <param name="Source">Supplies the message sender.</param> private void OnRecvMessageIPCWake(NetworkMessage Message, GameServer Source) { BufferParser Parser = Message.GetParser(); int SourceServerId = Parser.ReadInt32(); int DestinationServerId = Parser.ReadInt32(); Parser.FinishParsing(); if (SourceServerId != Source.ServerId || LocalServerId != DestinationServerId) return; // // Signal the world manager that it should break out of the polling // wait and immediately check for new IPC messages that are // available to process. // WorldManager.SignalIPCEventWakeup(); }
/// <summary> /// Send a direct message to a server. Failures are ignored. /// </summary> /// <param name="Message">Supplies the message payload.</param> /// <param name="Server">Supplies the destination server.</param> private void SendMessageToServer(NetworkMessage Message, GameServer Server) { if (!Server.Online) return; try { SendMessageToServer(Message.FinalizeMessage(), Server); } catch { } }
/// <summary> /// Create a new CharacterJoinEvent. /// </summary> /// <param name="Character">Supplies the character.</param> /// <param name="IsDM">Supplies true if the character was DM /// privileged at join time.</param> /// <param name="Server">Supplies the server that the character has /// joined.</param> public CharacterJoinEvent(GameCharacter Character, bool IsDM, GameServer Server) { this.Character = Character; this.IsDM = IsDM; this.Server = Server; }
/// <summary> /// Create a new CharacterPartEvent. /// </summary> /// <param name="Character">Supplies the character.</param> /// <param name="IsDM">Supplies true if the character was DM /// privileged at part time.</param> /// <param name="Server">Supplies the server that the character has /// parted.</param> public CharacterPartEvent(GameCharacter Character, bool IsDM, GameServer Server) { this.Character = Character; this.IsDM = IsDM; this.Server = Server; }
/// <summary> /// This method is called when a server has had all of its data loaded /// from the database. The server is inserted already. /// </summary> /// <param name="Server">Supplies the server object hwich has been /// loaded.</param> private void OnServerLoaded(GameServer Server) { if (Server.Online) EnqueueEvent(new ServerJoinEvent(Server)); }
/// <summary> /// This method performs the initial synchronization step at first run /// that downloads the initial character list. A bulk query is issued /// here to reduce the number of database round-trips at startup time. /// /// Note that no attempt is made to mark offline characters here. That /// step is done in the normal synchronization round, as this is the /// initial round anyway. /// </summary> private void PerformInitialSynchronization() { IALFADatabase Database = DatabaseLinkQueryThread; List<InitialSynchronizationRow> Rowset = new List<InitialSynchronizationRow>(); Database.ACR_SQLQuery( "SELECT " + "`characters`.`ID` AS character_id, " + // 0 "`characters`.`PlayerID` AS player_id, " + // 1 "`characters`.`Name` AS character_name, " + // 2 "`characters`.`ServerID` AS server_id, " + // 3 "`characters`.`Location` AS character_location, " + // 4 "`players`.`IsDM` AS player_is_dm, " + // 5 "`players`.`Name` AS player_name, " + // 6 "`servers`.`IPAddress` AS server_address_string, " + // 7 "`servers`.`Name` AS server_name " + // 8 "FROM " + "`characters` " + "INNER JOIN `servers` ON `characters`.`ServerID` = `servers`.`ID` " + "INNER JOIN `players` ON `characters`.`PlayerID` = `players`.`ID` " + "WHERE " + "`characters`.`IsOnline` = 1 " ); while (Database.ACR_SQLFetch()) { InitialSynchronizationRow Row; Row.CharacterId = Convert.ToInt32(Database.ACR_SQLGetData(0)); Row.PlayerId = Convert.ToInt32(Database.ACR_SQLGetData(1)); Row.CharacterName = Database.ACR_SQLGetData(2); Row.ServerId = Convert.ToInt32(Database.ACR_SQLGetData(3)); Row.CharacterLocation = Database.ACR_SQLGetData(4); Row.PlayerIsDM = ConvertToBoolean(Database.ACR_SQLGetData(5)); Row.PlayerName = Database.ACR_SQLGetData(6); Row.ServerAddressString = Database.ACR_SQLGetData(7); Row.ServerName = Database.ACR_SQLGetData(8); Rowset.Add(Row); } lock (this) { // // Update entries. // foreach (InitialSynchronizationRow Row in Rowset) { GameServer Server = (from S in Servers where S.ServerId == Row.ServerId select S).FirstOrDefault(); if (Server == null) { Server = new GameServer(this); Server.ServerName = Row.ServerName; Server.ServerId = Row.ServerId; Server.SetHostnameAndPort(Row.ServerAddressString); InsertNewServer(Server, Database); } GamePlayer Player = (from P in Players where P.PlayerId == Row.PlayerId select P).FirstOrDefault(); if (Player == null) { Player = new GamePlayer(this); Player.PlayerName = Row.PlayerName; Player.PlayerId = Row.PlayerId; Player.IsDM = Row.PlayerIsDM; InsertNewPlayer(Player, Database); } GameCharacter Character = (from C in Characters where C.CharacterId == Row.CharacterId select C).FirstOrDefault(); if (Character == null) { Character = new GameCharacter(this); Character.CharacterId = Row.CharacterId; Character.PlayerId = Row.PlayerId; Character.Online = true; Character.CharacterName = Row.CharacterName; Character.LocationString = Row.CharacterLocation; InsertNewCharacter(Character, Row.ServerId, Database, null); } } #if DEBUG_MODE WriteDiagnosticLog(String.Format("GameWorldManager.PerformInitialSynchronization: Synchronized {0} servers, {1} players, {2} characters.", ServerList.Count, PlayerList.Count, CharacterList.Count)); #endif } }
/// <summary> /// This method is called when a run script IPC event is received. /// </summary> /// <param name="SourceServer">Supplies the sender server, which may be /// may be null if the request was not sent by a server but external /// automation.</param> /// <param name="ScriptName">Supplies the name of the script.</param> /// <param name="ScriptArgument">Supplies an optional argument for the /// script main function.</param> private void OnRunScript(GameServer SourceServer, string ScriptName, string ScriptArgument) { EnqueueEvent(new RunScriptEvent(SourceServer, ScriptName, ScriptArgument)); }
/// <summary> /// This method is called when a server is discovered to have gone /// offline. /// </summary> /// <param name="Server">Suplies the server that is now considered to /// be offline.</param> private void OnServerPart(GameServer Server) { EnqueueEvent(new ServerPartEvent(Server)); }
/// <summary> /// This method is called when a server is discovered to have come /// online. The server is inserted already. /// </summary> /// <param name="Server">Supplies the server that is now considered to /// be online.</param> private void OnServerJoin(GameServer Server) { EnqueueEvent(new ServerJoinEvent(Server)); }
/// <summary> /// Reference the data for a server by the server id. If the data /// was not yet available, it is retrieved from the database. /// </summary> /// <param name="ServerId">Supplies the object id.</param> /// <param name="Database">Supplies the database connection to use for /// queries, if required. The active rowset may be consumed.</param> /// <returns>The object data is returned, else null if the object did /// not exist.</returns> public GameServer ReferenceServerById(int ServerId, IALFADatabase Database) { // // Check if the object is already known first. // GameServer Server = (from S in Servers where S.ServerId == ServerId select S).FirstOrDefault(); if (Server != null) return Server; // // Need to pull the data from the database. // if (Database == null) return null; Database.ACR_SQLQuery(String.Format( "SELECT `Name`, `IPAddress` FROM `servers` WHERE `ID` = {0}", ServerId)); if (!Database.ACR_SQLFetch()) return null; Server = new GameServer(this); Server.ServerName = Database.ACR_SQLGetData(0); Server.ServerId = ServerId; Server.SetHostnameAndPort(Database.ACR_SQLGetData(1)); InsertNewServer(Server, Database); return Server; }
/// <summary> /// Reference the data for a server by the server name. If the data /// was not yet available, it is retrieved from the database. /// </summary> /// <param name="ServerName">Supplies the object name.</param> /// <param name="Database">Supplies the database connection to use for /// queries, if required. The active rowset may be consumed.</param> /// <returns>The object data is returned, else null if the object did /// not exist.</returns> public GameServer ReferenceServerByName(string ServerName, IALFADatabase Database) { // // Check if the object is already known first. // GameServer Server = (from S in Servers where S.ServerName.Equals(ServerName, StringComparison.InvariantCultureIgnoreCase) select S).FirstOrDefault(); if (Server != null) return Server; // // Need to pull the data from the database. // if (Database == null) return null; Database.ACR_SQLQuery(String.Format( "SELECT `ID`, `IPAddress`, `Name` FROM `servers` WHERE `Name` = '{0}'", Database.ACR_SQLEncodeSpecialChars(ServerName))); if (!Database.ACR_SQLFetch()) return null; Server = new GameServer(this); Server.ServerId = Convert.ToInt32(Database.ACR_SQLGetData(0)); Server.SetHostnameAndPort(Database.ACR_SQLGetData(1)); Server.ServerName = Database.ACR_SQLGetData(2); InsertNewServer(Server, Database); return Server; }
/// <summary> /// Create a new RunScriptEvent. /// </summary> /// <param name="SourceServer">Supplies the sender server, which may be /// may be null if the request was not sent by a server but external /// automation.</param> /// <param name="ScriptName">Supplies the name of the script.</param> /// <param name="ScriptArgument">Supplies an optional argument for the /// script main function.</param> public RunScriptEvent(GameServer SourceServer, string ScriptName, string ScriptArgument) { this.SourceServer = SourceServer; this.ScriptName = ScriptName; this.ScriptArgument = ScriptArgument; }
/// <summary> /// This method periodically notifies the player of the current portal /// status until the portal attempt completes (or is aborted). /// </summary> /// <param name="PlayerObjectId">Supplies the player to enlist in /// portal status update notifications.</param> /// <param name="Server">Supplies the destination server.</param> private void PortalStatusCheck(uint PlayerObjectId, GameServer Server) { DelayCommand(1.0f, delegate() { // // Bail out if the portal attempt is aborted, e.g. if we have // timed out. // if ((GetDatabase().ACR_GetPCLocalFlags(PlayerObjectId) & ALFA.Database.ACR_PC_LOCAL_FLAG_PORTAL_IN_PROGRESS) == 0) return; // // If the character is no longer spooled, then complete the // portal sequence. // if (!IsCharacterSpooled(PlayerObjectId)) { // // Force pending database writes relating to the player to // complete. // GetDatabase().ACR_FlushQueryQueue(PlayerObjectId); // // Disable the next internal character save for this player // object. This prevents the autosave on logout from // contending with the remote server's initial character // read. // // N.B. Normally, this is not a problem, as the server // vault subsystem uses file locking internally. But // ALFA uses SSHFS, which does not support any sort // of file locking at all (all requestors are let on // through). // // Thus, to avoid the remote server getting into a // state where it reads a character being transferred // from the final autosave on logout, we suppress the // final autosave. // // Script-initiated saves are already suppressed by // the ACR_PC_LOCAL_FLAG_PORTAL_IN_PROGRESS PC local // flag bit, which just leaves the server's internal // save. // if (!DisableCharacterSave(PlayerObjectId)) { SendMessageToPC(PlayerObjectId, "Unable to setup for server character transfer - internal error. Please notify the tech team."); SendMessageToPC(PlayerObjectId, "Aborting portal attempt due to error..."); GetDatabase().ACR_SetPCLocalFlags( PlayerObjectId, GetDatabase().ACR_GetPCLocalFlags(PlayerObjectId) & ~(ALFA.Database.ACR_PC_LOCAL_FLAG_PORTAL_IN_PROGRESS)); return; } // // Now retrieve the portal configuration information from // the data system and transfer the player. // // Note that there is no going back from this point. If // the client does not disconnect on its own, we will boot // the client later on (because we can never know if the // client would try and log on to the remote server or if // it has given up after having sent the request). // SendMessageToPC(PlayerObjectId, "Transferring to server " + Server.Name + "..."); GetDatabase().ACR_SetPCLocalFlags( PlayerObjectId, GetDatabase().ACR_GetPCLocalFlags(PlayerObjectId) | ALFA.Database.ACR_PC_LOCAL_FLAG_PORTAL_COMMITTED); lock (WorldManager) { ActivatePortal( PlayerObjectId, String.Format("{0}:{1}", Server.ServerHostname, Server.ServerPort), WorldManager.Configuration.PlayerPassword, "", TRUE); } return; } // // Otherwise, send a notification to the player and start the // next continuation. // SendMessageToPC(PlayerObjectId, "Character transfer in progress..."); PortalStatusCheck(PlayerObjectId, Server); }); }
/// <summary> /// Create a new ServerJoinEvent. /// </summary> /// <param name="Server">Supplies the server.</param> public ServerJoinEvent(GameServer Server) { this.Server = Server; }
/// <summary> /// This method is called when a portal transition has been committed /// to send a player to a remote server. /// </summary> /// <param name="State">Supplies the local state for the player that is /// committed to a portal transition. /// </param> /// <param name="Server">Supplies the destination server for the portal /// event.</param> /// <param name="Script">Supplies the script object.</param> public static void SendGUIStateToServer(PlayerState State, GameServer Server, ACR_ServerCommunicator Script) { // // Build the resynchronization command. The resynchronization // command is parsed by HandleGUIResync(). Note that the remote // and local servers may not be of the same version, i.e. the // remote server may not understand fields that are created by the // local server if it is of a newer version. // ResyncState ResyncInfo = new ResyncState(State.PlayerId, 0); if (State.ChatSelectGUIExpanded) ResyncInfo.ResyncFlags |= RESYNC_FLAG_CHAT_SELECT_EXPANDED; // // Dispatch the resync script execution request to the remote // server. The remote server will process it when the request has // been received. // Script.RunScriptOnServer(Server.ServerId, "acr_resync_gui", ResyncInfo.ToString()); }
/// <summary> /// Create a new ServerPartEvent. /// </summary> /// <param name="Server">Supplies the server.</param> public ServerPartEvent(GameServer Server) { this.Server = Server; }