public void CreateEvent(IServerSerializable entity, object[] extraData = null) { if (entity == null || !(entity is Entity)) { DebugConsole.ThrowError("Can't create an entity event for " + entity + "!"); return; } if (((Entity)entity).Removed && !(entity is Level)) { DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the entity has been removed.\n" + Environment.StackTrace); return; } if (((Entity)entity).IdFreed) { DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the ID of the entity has been freed.\n" + Environment.StackTrace); return; } var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1)); if (extraData != null) { newEvent.SetData(extraData); } //remove events that have been sent to all clients, they are redundant now //keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID events.RemoveAll(e => NetIdUtils.IdMoreRecent(lastSentToAll, e.ID)); if (server.ConnectedClients.Count(c => c.InGame) == 0 && events.Count > 1) { events.RemoveRange(0, events.Count - 1); } for (int i = events.Count - 1; i >= 0; i--) { //we already have an identical event that's waiting to be sent // -> no need to add a new one if (events[i].IsDuplicate(newEvent) && !events[i].Sent) { return; } } ID++; events.Add(newEvent); if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent))) { //create a copy of the event and give it a new ID var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1)); uniqueEvent.SetData(extraData); uniqueEvents.Add(uniqueEvent); } }
public void CreateEvent(IServerSerializable entity, object[] extraData = null) { if (entity == null || !(entity is Entity)) { DebugConsole.ThrowError("Can't create an entity event for " + entity + "!"); return; } if (((Entity)entity).Removed && !(entity is Level)) { DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the entity has been removed.\n"+Environment.StackTrace); return; } if (((Entity)entity).IdFreed) { DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the ID of the entity has been freed.\n"+Environment.StackTrace); return; } var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1)); if (extraData != null) newEvent.SetData(extraData); bool inGameClientsPresent = server.ConnectedClients.Count(c => c.InGame) > 0; //remove old events that have been sent to all clients, they are redundant now // keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID // and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced if (Timing.TotalTime > GameMain.GameSession.RoundStartTime + NetConfig.RoundStartSyncDuration) { events.RemoveAll(e => (NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) && e.CreateTime < Timing.TotalTime - NetConfig.EventRemovalTime); } for (int i = events.Count - 1; i >= 0; i--) { //we already have an identical event that's waiting to be sent // -> no need to add a new one if (events[i].IsDuplicate(newEvent) && !events[i].Sent) return; } ID++; events.Add(newEvent); if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent))) { //create a copy of the event and give it a new ID var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1)); uniqueEvent.SetData(extraData); uniqueEvents.Add(uniqueEvent); } }
public void Update(List <Client> clients) { foreach (BufferedEvent bufferedEvent in bufferedEvents) { if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead) { bufferedEvent.IsProcessed = true; continue; } //delay reading the events until the inputs for the corresponding frame have been processed //UNLESS the character is unconscious, in which case we'll read the messages immediately (because further inputs will be ignored) //atm the "give in" command is the only thing unconscious characters can do, other types of events are ignored if (!bufferedEvent.Character.IsIncapacitated && NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID)) { continue; } try { ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender); } catch (Exception e) { string entityName = bufferedEvent.TargetEntity == null ? "null" : bufferedEvent.TargetEntity.ToString(); if (GameSettings.VerboseLogging) { string errorMsg = "Failed to read server event for entity \"" + entityName + "\"!"; GameServer.Log(errorMsg + "\n" + e.StackTrace.CleanupStackTrace(), ServerLog.MessageType.Error); DebugConsole.ThrowError(errorMsg, e); } GameAnalyticsManager.AddErrorEventOnce("ServerEntityEventManager.Read:ReadFailed" + entityName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to read server event for entity \"" + entityName + "\"!\n" + e.StackTrace.CleanupStackTrace()); } bufferedEvent.IsProcessed = true; } var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync); if (inGameClients.Count > 0) { lastSentToAnyone = inGameClients[0].LastRecvEntityEventID; lastSentToAll = inGameClients[0].LastRecvEntityEventID; if (server.OwnerConnection != null) { var owner = clients.Find(c => c.Connection == server.OwnerConnection); if (owner != null) { lastSentToAll = owner.LastRecvEntityEventID; } } inGameClients.ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) { lastSentToAll = c.LastRecvEntityEventID; } if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone)) { lastSentToAnyone = c.LastRecvEntityEventID; } }); lastSentToAnyoneTime = events.Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime; if (Timing.TotalTime - lastWarningTime > 5.0 && Timing.TotalTime - lastSentToAnyoneTime > 10.0 && Timing.TotalTime > GameMain.GameSession.RoundStartTime + NetConfig.RoundStartSyncDuration) { lastWarningTime = Timing.TotalTime; GameServer.Log("WARNING: ServerEntityEventManager is lagging behind! Last sent id: " + lastSentToAnyone.ToString() + ", latest create id: " + ID.ToString(), ServerLog.MessageType.ServerMessage); events.ForEach(e => e.ResetCreateTime()); //TODO: reset clients if this happens, maybe do it if a majority are behind rather than all of them? } clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) { lastSentToAll = (ushort)(c.FirstNewEventID - 1); } }); ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1)); if (firstEventToResend != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + NetConfig.RoundStartSyncDuration && ((lastSentToAnyoneTime - firstEventToResend.CreateTime) > NetConfig.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > NetConfig.OldEventKickTime)) { // This event is 10 seconds older than the last one we've successfully sent, // kick everyone that hasn't received it yet, this is way too old // UNLESS the event was created when the client was still midround syncing, // in which case we'll wait until the timeout runs out before kicking the client List <Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) && (firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0)); toKick.ForEach(c => { DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red); GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + " (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " + (lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" + " Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error); server.DisconnectClient(c, "", DisconnectReason.ExcessiveDesyncOldEvent + "/ServerMessage.ExcessiveDesyncOldEvent"); } ); } if (events.Count > 0) { //the client is waiting for an event that we don't have anymore //(the ID they're expecting is smaller than the ID of the first event in our list) List <Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent(events[0].ID, (UInt16)(c.LastRecvEntityEventID + 1))); toKick.ForEach(c => { DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red); GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.Error); server.DisconnectClient(c, "", DisconnectReason.ExcessiveDesyncRemovedEvent + "/ServerMessage.ExcessiveDesyncRemovedEvent"); }); } } var timedOutClients = clients.FindAll(c => c.Connection != GameMain.Server.OwnerConnection && c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut); foreach (Client timedOutClient in timedOutClients) { GameServer.Log("Disconnecting client " + GameServer.ClientLogName(timedOutClient) + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error); GameMain.Server.DisconnectClient(timedOutClient, "", DisconnectReason.SyncTimeout + "/ServerMessage.SyncTimeout"); } bufferedEvents.RemoveAll(b => b.IsProcessed); }
public void Update(List <Client> clients) { foreach (BufferedEvent bufferedEvent in bufferedEvents) { if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead) { bufferedEvent.IsProcessed = true; continue; } //delay reading the events until the inputs for the corresponding frame have been processed //UNLESS the character is unconscious, in which case we'll read the messages immediately (because further inputs will be ignored) //atm the "give in" command is the only thing unconscious characters can do, other types of events are ignored if (!bufferedEvent.Character.IsUnconscious && NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID)) { continue; } try { ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender); } catch (Exception e) { string entityName = bufferedEvent.TargetEntity == null ? "null" : bufferedEvent.TargetEntity.ToString(); if (GameSettings.VerboseLogging) { string errorMsg = "Failed to read server event for entity \"" + entityName + "\"!"; GameServer.Log(errorMsg + "\n" + e.StackTrace, ServerLog.MessageType.Error); DebugConsole.ThrowError(errorMsg, e); } GameAnalyticsManager.AddErrorEventOnce("ServerEntityEventManager.Read:ReadFailed" + entityName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to read server event for entity \"" + entityName + "\"!\n" + e.StackTrace); } bufferedEvent.IsProcessed = true; } var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync); if (inGameClients.Count > 0) { lastSentToAll = inGameClients[0].LastRecvEntityEventID; if (server.OwnerConnection != null) { var owner = clients.Find(c => c.Connection == server.OwnerConnection); if (owner != null) { lastSentToAll = owner.LastRecvEntityEventID; } } inGameClients.ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) { lastSentToAll = c.LastRecvEntityEventID; } }); clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) { lastSentToAll = (ushort)(c.FirstNewEventID - 1); } }); ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1)); if (firstEventToResend != null && (Timing.TotalTime - firstEventToResend.CreateTime) > 10.0f) { //it's been 10 seconds since this event was created, kick everyone that hasn't received it yet, this is way too old // UNLESS the event was created when the client was still midround syncing, in which case we'll wait until the timeout // runs out before kicking the client List <Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) && (firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut)); toKick.ForEach(c => { DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected old event " + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red); GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected old event " + (c.LastRecvEntityEventID + 1).ToString() + " (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago)" + " Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error); server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncOldEvent"); } ); } if (events.Count > 0) { //the client is waiting for an event that we don't have anymore //(the ID they're expecting is smaller than the ID of the first event in our list) List <Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent(events[0].ID, (UInt16)(c.LastRecvEntityEventID + 1))); toKick.ForEach(c => { DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red); GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.Error); server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncRemovedEvent"); }); } } var timedOutClients = clients.FindAll(c => c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut); foreach (Client timedOutClient in timedOutClients) { GameServer.Log("Disconnecting client " + timedOutClient.Name + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error); GameMain.Server.DisconnectClient(timedOutClient, "", "ServerMessage.SyncTimeout"); } bufferedEvents.RemoveAll(b => b.IsProcessed); }
public void Update(List <Client> clients) { foreach (BufferedEvent bufferedEvent in bufferedEvents) { if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead) { bufferedEvent.IsProcessed = true; continue; } //delay reading the events until the inputs for the corresponding frame have been processed //UNLESS the character is unconscious, in which case we'll read the messages immediately (because further inputs will be ignored) //atm the "give in" command is the only thing unconscious characters can do, other types of events are ignored if (!bufferedEvent.Character.IsUnconscious && NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID)) { continue; } try { ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender); } catch (Exception e) { if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to read event for entity \"" + bufferedEvent.TargetEntity.ToString() + "!", e); } } bufferedEvent.IsProcessed = true; } var inGameClients = clients.FindAll(c => c.inGame && !c.NeedsMidRoundSync); if (inGameClients.Count > 0) { lastSentToAll = inGameClients[0].lastRecvEntityEventID; inGameClients.ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.lastRecvEntityEventID)) { lastSentToAll = c.lastRecvEntityEventID; } }); ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1)); if (firstEventToResend != null && (Timing.TotalTime - firstEventToResend.CreateTime) > 10.0f) { //it's been 10 seconds since this event was created //kick everyone that hasn't received it yet, this is way too old List <Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.lastRecvEntityEventID)); toKick.ForEach(c => server.DisconnectClient(c, "", "You have been disconnected because of excessive desync")); } if (events.Count > 0) { //the client is waiting for an event that we don't have anymore //(the ID they're expecting is smaller than the ID of the first event in our list) List <Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent(events[0].ID, (UInt16)(c.lastRecvEntityEventID + 1))); toKick.ForEach(c => server.DisconnectClient(c, "", "You have been disconnected because of excessive desync")); } } var timedOutClients = clients.FindAll(c => c.inGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut); timedOutClients.ForEach(c => GameMain.Server.DisconnectClient(c, "", "You have been disconnected because syncing your client with the server took too long.")); bufferedEvents.RemoveAll(b => b.IsProcessed); }
public void Update(List <Client> clients) { foreach (BufferedEvent bufferedEvent in bufferedEvents) { if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead) { bufferedEvent.IsProcessed = true; continue; } //delay reading the events until the inputs for the corresponding frame have been processed //UNLESS the character is unconscious, in which case we'll read the messages immediately (because further inputs will be ignored) //atm the "give in" command is the only thing unconscious characters can do, other types of events are ignored if (!bufferedEvent.Character.IsUnconscious && NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID)) { continue; } try { ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender); } catch (Exception e) { string entityName = bufferedEvent.TargetEntity == null ? "null" : bufferedEvent.TargetEntity.ToString(); if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to read server event for entity \"" + entityName + "\"!", e); } GameAnalyticsManager.AddErrorEventOnce("ServerEntityEventManager.Read:ReadFailed" + entityName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to read server event for entity \"" + entityName + "\"!\n" + e.StackTrace); } bufferedEvent.IsProcessed = true; } var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync); if (inGameClients.Count > 0) { lastSentToAll = inGameClients[0].LastRecvEntityEventID; inGameClients.ForEach(c => { if (NetIdUtils.IdMoreRecent((ushort)(lastSentToAll - 1), c.LastRecvEntityEventID)) { lastSentToAll = (ushort)(c.LastRecvEntityEventID - 1); } }); ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1)); if (firstEventToResend != null && (Timing.TotalTime - firstEventToResend.CreateTime) > (10.0f * GameMain.NilMod.DesyncTimerMultiplier)) { //it's been 10 seconds since this event was created //kick everyone that hasn't received it yet, this is way too old List <Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID)); toKick.ForEach(c => { DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected old event " + c.LastRecvEntityEventID.ToString() + ")", Microsoft.Xna.Framework.Color.Red); server.DisconnectClient(c, "", "You have been disconnected because of excessive desync (Expected old event, inform the server host increasing 'DesyncTimerMultiplier' could help."); } ); } if (events.Count > 0) { //the client is waiting for an event that we don't have anymore //(the ID they're expecting is smaller than the ID of the first event in our list) List <Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent(events[0].ID, (UInt16)(c.LastRecvEntityEventID + 1))); toKick.ForEach(c => { DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected " + c.LastRecvEntityEventID.ToString() + ", last available is " + events[0].ID.ToString() + ")", Microsoft.Xna.Framework.Color.Red); server.DisconnectClient(c, "", "You have been disconnected because of excessive desync (Event no longer exists)"); } ); } } var timedOutClients = clients.FindAll(c => c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut); timedOutClients.ForEach(c => GameMain.Server.DisconnectClient(c, "", "You have been disconnected because syncing your client with the server took too long.")); bufferedEvents.RemoveAll(b => b.IsProcessed); }