private void SynchronizeOnlineCharacters()
{
IALFADatabase Database = DatabaseLinkQueryThread;
//
// Query the current player list and synchronize with our internal
// state.
//
// There are several steps here:
//
// 1) Mark all online characters as "not visited".
// 2) Retrieve the character list from the database. For each
// character that wasn't already marked as online, or which has
// changed servers, update the state and fire the character join
// event. Also, flag any character that is in the list from the
// database as "visited".
// 3) Sweep the online character list for characters that are still
// flagged as "not visited". These characters are those that
// have gone offline in the interim time and which need to have
// the character part event fired.
//
//
// First - query the database. This query returns a list of all
// online characters from all servers that have checked in in the
// past ten minutes. We treat any server that has not checked in
// within at least that long as having no online players.
//
List<SynchronizeOnlineCharactersRow> Rowset = new List<SynchronizeOnlineCharactersRow>();
Database.ACR_SQLQuery(
"SELECT " +
"`characters`.`ID` AS character_id, " +
"`characters`.`Location` as character_location, " +
"`players`.`IsDM` AS character_is_dm, " +
"`servers`.`ID` AS character_server_id " +
"FROM `characters` " +
"INNER JOIN `players` ON `players`.`ID` = `characters`.`PlayerID` " +
"INNER JOIN `servers` ON `servers`.`ID` = `characters`.`ServerID` " +
"INNER JOIN `pwdata` ON `pwdata`.`Name` = `servers`.`Name` " +
"WHERE `characters`.`IsOnline` = 1 " +
"AND pwdata.`Key` = 'ACR_TIME_SERVERTIME' " +
"AND pwdata.`Last` >= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 10 MINUTE) "
);
//
// Clear visited.
//
// N.B. The Visited flag is only managed on the query thread or
// prior to list insertion during initial character creation.
//
// Thus, while we must lock to protect the integrity of the
// list, there's no need to worry about another thread
// altering the Visited state after we drop the lock.
//
lock (this)
{
foreach (GameCharacter Character in OnlineCharacters)
{
Character.Visited = false;
}
}
//
// Read each database row.
//
while (Database.ACR_SQLFetch())
{
SynchronizeOnlineCharactersRow Row;
Row.CharacterId = Convert.ToInt32(Database.ACR_SQLGetData(0));
Row.LocationString = Database.ACR_SQLGetData(1);
Row.IsDM = ConvertToBoolean(Database.ACR_SQLGetData(2));
Row.ServerId = Convert.ToInt32(Database.ACR_SQLGetData(3));
Rowset.Add(Row);
}
lock (this)
{
//
// Update entries.
//
foreach (SynchronizeOnlineCharactersRow Row in Rowset)
{
int CharacterId = Row.CharacterId;
string LocationString = Row.LocationString;
bool IsDM = Row.IsDM;
int ServerId = Row.ServerId;
GameCharacter Character = ReferenceCharacterById(CharacterId, Database, IsDM);
GameServer Server = ReferenceServerById(ServerId, Database);
//
// Update the DM state of the character.
//
Character.Visited = true;
Character.Player.IsDM = IsDM;
Character.LocationString = LocationString;
if (Character.Server == Server)
continue;
//
// The character changed servers or came online from not
// being online, send the appropriate part and join events.
//
if (Character.Server != null && Character.Online)
{
OnCharacterPart(Character);
Character.Server.Characters.Remove(Character);
Character.Server = null;
OnlineCharacterList.Remove(Character);
Character.Online = false;
}
//
// If the user's server is still marked as offline, mark it
// as online now. It has to have checked in for it to have
// been returned in the query as it is.
//
if (!Server.Online)
{
Server.Online = true;
Server.DatabaseOnline = true;
OnServerJoin(Server);
}
Character.Server = Server;
try
{
Character.Server.Characters.Add(Character);
try
{
OnlineCharacterList.Add(Character);
}
catch
{
Character.Server.Characters.Remove(Character);
throw;
}
}
catch
{
Character.Server = null;
Character.Online = false;
throw;
}
Character.Online = true;
OnCharacterJoin(Character);
}
//
// Sweep offline characters.
//
var NowOfflineCharacters = (from C in OnlineCharacters
where C.Visited == false
select C);
List<GameCharacter> ObjectsToRemove = new List<GameCharacter>(NowOfflineCharacters);
foreach (GameCharacter Character in ObjectsToRemove)
{
OnCharacterPart(Character);
if (Character.Server != null)
Character.Server.Characters.Remove(Character);
Character.Online = false;
OnlineCharacterList.Remove(Character);
}
}
}