/// <summary> /// Returns 0.0f-1.0f based on how well the Character can use the itemcomponent /// </summary> /// <returns>0.5f if all the skills meet the skill requirements exactly, 1.0f if they're way above and 0.0f if way less</returns> public float DegreeOfSuccess(Character character, List <Skill> requiredSkills) { if (requiredSkills.Count == 0) { return(1.0f); } if (character == null) { string errorMsg = "ItemComponent.DegreeOfSuccess failed (character was null).\n" + Environment.StackTrace; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); return(0.0f); } float skillSuccessSum = 0.0f; for (int i = 0; i < requiredSkills.Count; i++) { float characterLevel = character.GetSkillLevel(requiredSkills[i].Identifier); skillSuccessSum += (characterLevel - requiredSkills[i].Level); } float average = skillSuccessSum / requiredSkills.Count; return(((average + 100.0f) / 2.0f) / 100.0f); }
private void CreateDoorBody() { Vector2 position = ConvertUnits.ToSimUnits(item.Position + (dockingTarget.door.Item.WorldPosition - item.WorldPosition)); if (!MathUtils.IsValid(position)) { string errorMsg = "Attempted to create a door body at an invalid position (item pos: " + item.Position + ", item world pos: " + item.WorldPosition + ", docking target world pos: " + DockingTarget.door.Item.WorldPosition + ")\n" + Environment.StackTrace; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "DockingPort.CreateDoorBody:InvalidPosition", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); position = Vector2.Zero; } doorBody = BodyFactory.CreateRectangle(GameMain.World, dockingTarget.door.Body.width, dockingTarget.door.Body.height, 1.0f, position, dockingTarget.door); doorBody.CollisionCategories = Physics.CollisionWall; doorBody.BodyType = BodyType.Static; }
public override void Update(float deltaTime) { if (!started) { return; } if (OnOwnerDetermined != null && OwnerConnection != null) { OnOwnerDetermined?.Invoke(OwnerConnection); OnOwnerDetermined = null; } //backwards for loop so we can remove elements while iterating for (int i = connectedClients.Count - 1; i >= 0; i--) { SteamP2PConnection conn = connectedClients[i] as SteamP2PConnection; conn.Decay(deltaTime); if (conn.Timeout < 0.0) { Disconnect(conn, "Timed out"); } } try { while (ChildServerRelay.Read(out byte[] incBuf)) { IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, OwnerConnection); HandleDataMessage(inc); } } catch (Exception e) { string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace; GameAnalyticsManager.AddErrorEventOnce("SteamP2PServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #else if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } #endif } for (int i = 0; i < pendingClients.Count; i++) { PendingClient pendingClient = pendingClients[i]; UpdatePendingClient(pendingClient); if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; } } }
/// <summary> /// Write the events to the outgoing message. The recipient parameter is only needed for ServerEntityEventManager /// </summary> protected void Write(IWriteMessage msg, List <NetEntityEvent> eventsToSync, out List <NetEntityEvent> sentEvents, Client recipient = null) { //write into a temporary buffer so we can write the number of events before the actual data IWriteMessage tempBuffer = new WriteOnlyMessage(); sentEvents = new List <NetEntityEvent>(); int eventCount = 0; foreach (NetEntityEvent e in eventsToSync) { //write into a temporary buffer so we can write the length before the actual data IWriteMessage tempEventBuffer = new WriteOnlyMessage(); try { WriteEvent(tempEventBuffer, e, recipient); } catch (Exception exception) { DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception); GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:WriteFailed" + e.Entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to write an event for the entity \"" + e.Entity + "\"\n" + exception.StackTrace.CleanupStackTrace()); //write an empty event to avoid messing up IDs //(otherwise the clients might read the next event in the message and think its ID //is consecutive to the previous one, even though we skipped over this broken event) tempBuffer.Write(Entity.NullEntityID); tempBuffer.WritePadBits(); eventCount++; continue; } if (eventCount > 0 && msg.LengthBytes + tempBuffer.LengthBytes + tempEventBuffer.LengthBytes > MaxEventBufferLength) { //no more room in this packet break; } tempBuffer.Write(e.EntityID); tempBuffer.WriteVariableUInt32((uint)tempEventBuffer.LengthBytes); tempBuffer.Write(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes); tempBuffer.WritePadBits(); sentEvents.Add(e); eventCount++; } if (eventCount > 0) { msg.Write(eventsToSync[0].ID); msg.Write((byte)eventCount); msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes); } }
public void ServerAdminWrite(IWriteMessage outMsg, Client c) { try { if (outMsg == null) { throw new ArgumentException("OutMsg was null"); } if (GameMain.Server == null) { throw new Exception("GameMain.Server was null"); } if (!c.HasPermission(ClientPermissions.Ban)) { outMsg.Write(false); outMsg.WritePadBits(); return; } outMsg.Write(true); outMsg.Write(c.Connection == GameMain.Server.OwnerConnection); outMsg.WritePadBits(); outMsg.WriteVariableUInt32((UInt32)bannedPlayers.Count); for (int i = 0; i < bannedPlayers.Count; i++) { BannedPlayer bannedPlayer = bannedPlayers[i]; outMsg.Write(bannedPlayer.Name); outMsg.Write(bannedPlayer.UniqueIdentifier); outMsg.Write(bannedPlayer.IsRangeBan); outMsg.Write(bannedPlayer.ExpirationTime != null); outMsg.WritePadBits(); if (bannedPlayer.ExpirationTime != null) { double hoursFromNow = (bannedPlayer.ExpirationTime.Value - DateTime.Now).TotalHours; outMsg.Write(hoursFromNow); } outMsg.Write(bannedPlayer.Reason ?? ""); if (c.Connection == GameMain.Server.OwnerConnection) { outMsg.Write(bannedPlayer.EndPoint); outMsg.Write(bannedPlayer.SteamID); } } } catch (Exception e) { string errorMsg = "Error while writing banlist. {" + e + "}\n" + e.StackTrace.CleanupStackTrace(); GameAnalyticsManager.AddErrorEventOnce("Banlist.ServerAdminWrite", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); throw; } }
private void PushCharactersAway() { if (!MathUtils.IsValid(item.SimPosition)) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")"); GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:DoorPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")."); return; } Vector2 simPos = ConvertUnits.ToSimUnits(new Vector2(item.Rect.X, item.Rect.Y)); Vector2 currSize = IsHorizontal ? new Vector2(item.Rect.Width * (1.0f - openState), doorSprite.size.Y * item.Scale) : new Vector2(doorSprite.size.X * item.Scale, item.Rect.Height * (1.0f - openState)); Vector2 simSize = ConvertUnits.ToSimUnits(currSize); foreach (Character c in Character.CharacterList) { if (!c.Enabled) { continue; } if (!MathUtils.IsValid(c.SimPosition)) { if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")"); } GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:CharacterPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")." + " Removed: " + c.Removed + " Remoteplayer: " + c.IsRemotePlayer); continue; } int dir = IsHorizontal ? Math.Sign(c.SimPosition.Y - item.SimPosition.Y) : Math.Sign(c.SimPosition.X - item.SimPosition.X); foreach (Limb limb in c.AnimController.Limbs) { if (limb.IsSevered) { continue; } if (PushBodyOutOfDoorway(c, limb.body, dir, simPos, simSize) && damageSoundCooldown <= 0.0f) { #if CLIENT SoundPlayer.PlayDamageSound("LimbBlunt", 1.0f, limb.body); #endif damageSoundCooldown = 0.5f; } } PushBodyOutOfDoorway(c, c.AnimController.Collider, dir, simPos, simSize); } }
public void Awake() { //Check if there is an existing instance of this object if (instance && instance != this) { DestroyImmediate(gameObject); //Delete duplicate } else { instance = this; //Make this object the only instance DontDestroyOnLoad(gameObject); //Set as do not destroy } }
public void SetContainedItemPositions() { Vector2 simPos = item.SimPosition; Vector2 displayPos = item.Position; float currentRotation = itemRotation; if (item.body != null) { currentRotation += item.body.Rotation; } foreach (Item contained in Inventory.Items) { if (contained == null) { continue; } if (contained.body != null) { try { contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, currentRotation); contained.body.SetPrevTransform(contained.body.SimPosition, contained.body.Rotation); contained.body.UpdateDrawPosition(); } catch (Exception e) { DebugConsole.Log("SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace); GameAnalyticsManager.AddErrorEventOnce("ItemContainer.SetContainedItemPositions.InvalidPosition:" + contained.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace); } contained.body.Submarine = item.Submarine; } contained.Rect = new Rectangle( (int)(displayPos.X - contained.Rect.Width / 2.0f), (int)(displayPos.Y + contained.Rect.Height / 2.0f), contained.Rect.Width, contained.Rect.Height); contained.Submarine = item.Submarine; contained.CurrentHull = item.CurrentHull; contained.SetContainedItemPositions(); } }
private float GetSoundVolume(ItemSound sound) { if (sound == null) { return(0.0f); } if (sound.VolumeProperty == "") { return(sound.VolumeMultiplier); } if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property)) { float newVolume = 0.0f; try { newVolume = (float)property.GetValue(this); } catch { return(0.0f); } newVolume *= sound.VolumeMultiplier; if (!MathUtils.IsValid(newVolume)) { DebugConsole.Log("Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume); GameAnalyticsManager.AddErrorEventOnce( "ItemComponent.PlaySound:" + item.Name + GetType().ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume); return(0.0f); } return(MathHelper.Clamp(newVolume, 0.0f, 1.0f)); } return(0.0f); }
public static void Check(string extraErrorMsg = "") { ALError error; if ((error = AL.GetError()) != ALError.NoError) { string errorMsg = "OpenAL error: " + AL.GetErrorString(error); if (!string.IsNullOrEmpty(extraErrorMsg)) { errorMsg += " {" + extraErrorMsg + "} "; } errorMsg += "\n" + Environment.StackTrace; #if DEBUG DebugConsole.ThrowError(errorMsg); #else DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red); #endif GameAnalyticsManager.AddErrorEventOnce( "OggStream.Check:" + AL.GetErrorString(error) + extraErrorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); } }
private void CreateDoorBody() { if (doorBody != null) { GameMain.World.Remove(doorBody); doorBody = null; } Vector2 position = ConvertUnits.ToSimUnits(item.Position + (DockingTarget.Door.Item.WorldPosition - item.WorldPosition)); if (!MathUtils.IsValid(position)) { string errorMsg = "Attempted to create a door body at an invalid position (item pos: " + item.Position + ", item world pos: " + item.WorldPosition + ", docking target world pos: " + DockingTarget.Door.Item.WorldPosition + ")\n" + Environment.StackTrace.CleanupStackTrace(); DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce( "DockingPort.CreateDoorBody:InvalidPosition", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); position = Vector2.Zero; } System.Diagnostics.Debug.Assert(doorBody == null); doorBody = GameMain.World.CreateRectangle( DockingTarget.Door.Body.width, DockingTarget.Door.Body.height, 1.0f, position); doorBody.UserData = DockingTarget.Door; doorBody.CollisionCategories = Physics.CollisionWall; doorBody.BodyType = BodyType.Static; }
/// <summary> /// Read the events from the message, ignoring ones we've already received /// </summary> public void Read(ServerNetObject type, NetIncomingMessage msg, float sendingTime, List <IServerSerializable> entities) { UInt16 unreceivedEntityEventCount = 0; if (type == ServerNetObject.ENTITY_EVENT_INITIAL) { unreceivedEntityEventCount = msg.ReadUInt16(); firstNewID = msg.ReadUInt16(); if (GameSettings.VerboseLogging) { DebugConsole.NewMessage( "received midround syncing msg, unreceived: " + unreceivedEntityEventCount + ", first new ID: " + firstNewID, Microsoft.Xna.Framework.Color.Yellow); } } else if (firstNewID != null) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("midround syncing complete, switching to ID " + (UInt16)(firstNewID - 1), Microsoft.Xna.Framework.Color.Yellow); } lastReceivedID = (UInt16)(firstNewID - 1); firstNewID = null; } entities.Clear(); UInt16 firstEventID = msg.ReadUInt16(); int eventCount = msg.ReadByte(); for (int i = 0; i < eventCount; i++) { UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i); UInt16 entityID = msg.ReadUInt16(); if (entityID == 0) { msg.ReadPadBits(); if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; } continue; } byte msgLength = msg.ReadByte(); IServerSerializable entity = Entity.FindEntityByID(entityID) as IServerSerializable; entities.Add(entity); //skip the event if we've already received it or if the entity isn't found if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null) { if (GameSettings.VerboseLogging) { if (thisEventID != (UInt16)(lastReceivedID + 1)) { DebugConsole.NewMessage( "received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")", thisEventID < lastReceivedID + 1 ? Microsoft.Xna.Framework.Color.Yellow : Microsoft.Xna.Framework.Color.Red); } else if (entity == null) { DebugConsole.NewMessage( "received msg " + thisEventID + ", entity " + entityID + " not found", Microsoft.Xna.Framework.Color.Red); } } msg.Position += msgLength * 8; } else { long msgPosition = msg.Position; if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")", Microsoft.Xna.Framework.Color.Green); } lastReceivedID++; try { ReadEvent(msg, entity, sendingTime); } catch (Exception e) { if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); } GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to read event for entity \"" + entity.ToString() + "\"!\n" + e.StackTrace); msg.Position = msgPosition + msgLength * 8; } } msg.ReadPadBits(); } }
private VoipCapture(string deviceName) : base(GameMain.Client?.ID ?? 0, true, false) { Disconnected = false; VoipConfig.SetupEncoding(); //set up capture device captureDevice = Alc.CaptureOpenDevice(deviceName, VoipConfig.FREQUENCY, Al.FormatMono16, VoipConfig.BUFFER_SIZE * 5); if (captureDevice == IntPtr.Zero) { DebugConsole.NewMessage("Alc.CaptureOpenDevice attempt 1 failed: error code " + Alc.GetError(IntPtr.Zero).ToString(), Color.Orange); //attempt using a smaller buffer size captureDevice = Alc.CaptureOpenDevice(deviceName, VoipConfig.FREQUENCY, Al.FormatMono16, VoipConfig.BUFFER_SIZE * 2); } if (captureDevice == IntPtr.Zero) { DebugConsole.NewMessage("Alc.CaptureOpenDevice attempt 2 failed: error code " + Alc.GetError(IntPtr.Zero).ToString(), Color.Orange); //attempt using the default device captureDevice = Alc.CaptureOpenDevice("", VoipConfig.FREQUENCY, Al.FormatMono16, VoipConfig.BUFFER_SIZE * 2); } if (captureDevice == IntPtr.Zero) { string errorCode = Alc.GetError(IntPtr.Zero).ToString(); if (!GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "capturedevicenotfound")) { GUI.SettingsMenuOpen = false; new GUIMessageBox(TextManager.Get("Error"), (TextManager.Get("VoipCaptureDeviceNotFound", returnNull: true) ?? "Could not start voice capture, suitable capture device not found.") + " (" + errorCode + ")") { UserData = "capturedevicenotfound" }; } GameAnalyticsManager.AddErrorEventOnce("Alc.CaptureDeviceOpenFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Alc.CaptureDeviceOpen(" + deviceName + ") failed. Error code: " + errorCode); GameMain.Config.VoiceSetting = GameSettings.VoiceMode.Disabled; Instance?.Dispose(); Instance = null; return; } int alError = Al.GetError(); int alcError = Alc.GetError(captureDevice); if (alcError != Alc.NoError) { throw new Exception("Failed to open capture device: " + alcError.ToString() + " (ALC)"); } if (alError != Al.NoError) { throw new Exception("Failed to open capture device: " + alError.ToString() + " (AL)"); } CanDetectDisconnect = Alc.IsExtensionPresent(captureDevice, "ALC_EXT_disconnect"); alcError = Alc.GetError(captureDevice); if (alcError != Alc.NoError) { throw new Exception("Error determining if disconnect can be detected: " + alcError.ToString()); } Alc.CaptureStart(captureDevice); alcError = Alc.GetError(captureDevice); if (alcError != Alc.NoError) { throw new Exception("Failed to start capturing: " + alcError.ToString()); } capturing = true; captureThread = new Thread(UpdateCapture) { IsBackground = true, Name = "VoipCapture" }; captureThread.Start(); }
private bool PushBodyOutOfDoorway(Character c, PhysicsBody body, int dir, Vector2 doorRectSimPos, Vector2 doorRectSimSize) { if (!MathUtils.IsValid(body.SimPosition)) { DebugConsole.ThrowError("Failed to push a limb out of a doorway - position of the body (character \"" + c.Name + "\") is not valid (" + body.SimPosition + ")"); GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:LimbPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + body.SimPosition + ")." + " Removed: " + c.Removed + " Remoteplayer: " + c.IsRemotePlayer); return(false); } float diff; if (IsHorizontal) { if (body.SimPosition.X < doorRectSimPos.X || body.SimPosition.X > doorRectSimPos.X + doorRectSimSize.X) { return(false); } diff = body.SimPosition.Y - item.SimPosition.Y; } else { if (body.SimPosition.Y > doorRectSimPos.Y || body.SimPosition.Y < doorRectSimPos.Y - doorRectSimSize.Y) { return(false); } diff = body.SimPosition.X - item.SimPosition.X; } //if the limb is at a different side of the door than the character (collider), //immediately teleport it to the correct side if (Math.Sign(diff) != dir) { if (IsHorizontal) { body.SetTransform(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * doorRectSimSize.Y * 2.0f), body.Rotation); } else { body.SetTransform(new Vector2(item.SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y), body.Rotation); } } //apply an impulse to push the limb further from the door if (IsHorizontal) { if (Math.Abs(body.SimPosition.Y - item.SimPosition.Y) > doorRectSimSize.Y * 0.5f) { return(false); } body.ApplyLinearImpulse(new Vector2(isOpen ? 0.0f : 1.0f, dir * 2.0f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity); } else { if (Math.Abs(body.SimPosition.X - item.SimPosition.X) > doorRectSimSize.X * 0.5f) { return(false); } body.ApplyLinearImpulse(new Vector2(dir * 2.0f, isOpen ? 0.0f : -1.0f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity); } //don't stun if the door was broken a moment ago //otherwise enabling the door's collider and pushing the character away will interrupt repairing if (lastBrokenTime < Timing.TotalTime - 1.0f) { c.SetStun(0.2f); } return(true); }
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) { bool isDocked = msg.ReadBoolean(); for (int i = 0; i < 2; i++) { if (hulls[i] == null) { continue; } item.linkedTo.Remove(hulls[i]); hulls[i].Remove(); hulls[i] = null; } if (gap != null) { item.linkedTo.Remove(gap); gap.Remove(); gap = null; } if (isDocked) { ushort dockingTargetID = msg.ReadUInt16(); bool isLocked = msg.ReadBoolean(); Entity targetEntity = Entity.FindEntityByID(dockingTargetID); if (targetEntity == null || !(targetEntity is Item)) { DebugConsole.ThrowError("Invalid docking port network event (can't dock to " + targetEntity?.ToString() ?? "null" + ")"); return; } DockingTarget = (targetEntity as Item).GetComponent <DockingPort>(); if (DockingTarget == null) { DebugConsole.ThrowError("Invalid docking port network event (" + targetEntity + " doesn't have a docking port component)"); return; } Dock(DockingTarget); if (joint == null) { string errorMsg = "Error while reading a docking port network event (Dock method did not create a joint between the ports)." + " Submarine: " + (item.Submarine?.Name ?? "null") + ", target submarine: " + (DockingTarget.item.Submarine?.Name ?? "null"); if (item.Submarine?.DockedTo.Contains(DockingTarget.item.Submarine) ?? false) { errorMsg += "\nAlready docked."; } if (item.Submarine == DockingTarget.item.Submarine) { errorMsg += "\nTrying to dock the submarine to itself."; } GameAnalyticsManager.AddErrorEventOnce("DockingPort.ClientRead:JointNotCreated", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); } if (isLocked) { Lock(isNetworkMessage: true, forcePosition: true); } } else { Undock(); } }
/// <summary> /// Write the events to the outgoing message. The recipient parameter is only needed for ServerEntityEventManager /// </summary> protected void Write(NetOutgoingMessage msg, List <NetEntityEvent> eventsToSync, out List <NetEntityEvent> sentEvents, Client recipient = null) { //write into a temporary buffer so we can write the number of events before the actual data NetBuffer tempBuffer = new NetBuffer(); sentEvents = new List <NetEntityEvent>(); int eventCount = 0; foreach (NetEntityEvent e in eventsToSync) { //write into a temporary buffer so we can write the length before the actual data NetBuffer tempEventBuffer = new NetBuffer(); try { WriteEvent(tempEventBuffer, e, recipient); } catch (Exception exception) { DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception); GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:WriteFailed" + e.Entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to write an event for the entity \"" + e.Entity + "\"\n" + exception.StackTrace); //write an empty event to avoid messing up IDs //(otherwise the clients might read the next event in the message and think its ID //is consecutive to the previous one, even though we skipped over this broken event) tempBuffer.Write(Entity.NullEntityID); tempBuffer.WritePadBits(); eventCount++; continue; } //the length of the data is written as a byte, so the data needs to be less than 255 bytes long if (tempEventBuffer.LengthBytes > 255) { DebugConsole.ThrowError("Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes"); GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:TooLong" + e.Entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes"); //write an empty event to prevent breaking the event syncing tempBuffer.Write(Entity.NullEntityID); tempBuffer.WritePadBits(); eventCount++; continue; } if (msg.LengthBytes + tempBuffer.LengthBytes + tempEventBuffer.LengthBytes > MaxEventBufferLength) { //no more room in this packet break; } tempBuffer.Write(e.EntityID); tempBuffer.Write((byte)tempEventBuffer.LengthBytes); tempBuffer.Write(tempEventBuffer); tempBuffer.WritePadBits(); sentEvents.Add(e); eventCount++; } if (eventCount > 0) { msg.Write(eventsToSync[0].ID); msg.Write((byte)eventCount); msg.Write(tempBuffer); } }
/// <summary> /// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails. /// </summary> public bool Read(ServerNetObject type, NetIncomingMessage msg, float sendingTime, List <IServerSerializable> entities) { UInt16 unreceivedEntityEventCount = 0; if (type == ServerNetObject.ENTITY_EVENT_INITIAL) { unreceivedEntityEventCount = msg.ReadUInt16(); firstNewID = msg.ReadUInt16(); if (GameSettings.VerboseLogging) { DebugConsole.NewMessage( "received midround syncing msg, unreceived: " + unreceivedEntityEventCount + ", first new ID: " + firstNewID, Microsoft.Xna.Framework.Color.Yellow); } } else if (firstNewID != null) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("midround syncing complete, switching to ID " + (UInt16)(firstNewID - 1), Microsoft.Xna.Framework.Color.Yellow); } lastReceivedID = (UInt16)(firstNewID - 1); firstNewID = null; } entities.Clear(); UInt16 firstEventID = msg.ReadUInt16(); int eventCount = msg.ReadByte(); for (int i = 0; i < eventCount; i++) { UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i); UInt16 entityID = msg.ReadUInt16(); if (entityID == Entity.NullEntityID) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)", Microsoft.Xna.Framework.Color.Orange); } msg.ReadPadBits(); entities.Add(null); if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; } continue; } byte msgLength = msg.ReadByte(); IServerSerializable entity = Entity.FindEntityByID(entityID) as IServerSerializable; entities.Add(entity); //skip the event if we've already received it or if the entity isn't found if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null) { if (thisEventID != (UInt16)(lastReceivedID + 1)) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage( "Received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")", NetIdUtils.IdMoreRecent(thisEventID, (UInt16)(lastReceivedID + 1)) ? Microsoft.Xna.Framework.Color.Red : Microsoft.Xna.Framework.Color.Yellow); } } else if (entity == null) { DebugConsole.NewMessage( "Received msg " + thisEventID + ", entity " + entityID + " not found", Microsoft.Xna.Framework.Color.Red); GameMain.Client.ReportError(ClientNetError.MISSING_ENTITY, eventID: thisEventID, entityID: entityID); return(false); } msg.Position += msgLength * 8; } else { long msgPosition = msg.Position; if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")", Microsoft.Xna.Framework.Color.Green); } lastReceivedID++; try { ReadEvent(msg, entity, sendingTime); } catch (Exception e) { string errorMsg = "Failed to read event for entity \"" + entity.ToString() + "\" (" + e.Message + ")! (MidRoundSyncing: " + thisClient.MidRoundSyncing + ")\n" + e.StackTrace; errorMsg += "\nPrevious entities:"; for (int j = entities.Count - 2; j >= 0; j--) { errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString()); } if (GameSettings.VerboseLogging) { DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); } GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); msg.Position = msgPosition + msgLength * 8; } } msg.ReadPadBits(); } return(true); }
public override void Update(float deltaTime) { if (netServer == null) { return; } if (OnOwnerDetermined != null && OwnerConnection != null) { OnOwnerDetermined?.Invoke(OwnerConnection); OnOwnerDetermined = null; } netServer.ReadMessages(incomingLidgrenMessages); //process incoming connections first foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval)) { HandleConnection(inc); } try { //after processing connections, go ahead with the rest of the messages foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType != NetIncomingMessageType.ConnectionApproval)) { switch (inc.MessageType) { case NetIncomingMessageType.Data: HandleDataMessage(inc); break; case NetIncomingMessageType.StatusChanged: HandleStatusChanged(inc); break; } } } catch (Exception e) { string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace.CleanupStackTrace(); GameAnalyticsManager.AddErrorEventOnce("LidgrenServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #else if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } #endif } for (int i = 0; i < pendingClients.Count; i++) { PendingClient pendingClient = pendingClients[i]; var connection = pendingClient.Connection as LidgrenConnection; if (connection.NetConnection.Status == NetConnectionStatus.InitiatedConnect || connection.NetConnection.Status == NetConnectionStatus.ReceivedInitiation || connection.NetConnection.Status == NetConnectionStatus.RespondedAwaitingApproval || connection.NetConnection.Status == NetConnectionStatus.RespondedConnect) { continue; } UpdatePendingClient(pendingClient); if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; } } incomingLidgrenMessages.Clear(); }
// TODO: Consider using generics, interfaces, or inheritance instead of reflection -> would be easier to debug when something changes/goes wrong. // For example, currently we can edit the constructors but they will fail in runtime because the parameters are not changed here. // It's also painful to find where the constructors are used, because the references exist only at runtime. public static ItemComponent Load(XElement element, Item item, string file, bool errorMessages = true) { Type t; string type = element.Name.ToString().ToLowerInvariant(); try { // Get the type of a specified class. t = Type.GetType("Barotrauma.Items.Components." + type + "", false, true); if (t == null) { if (errorMessages) { DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + file + ")"); } return(null); } } catch (Exception e) { if (errorMessages) { DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + file + ")", e); } return(null); } ConstructorInfo constructor; try { if (t != typeof(ItemComponent) && !t.IsSubclassOf(typeof(ItemComponent))) { return(null); } constructor = t.GetConstructor(new Type[] { typeof(Item), typeof(XElement) }); if (constructor == null) { DebugConsole.ThrowError("Could not find the constructor of the component \"" + type + "\" (" + file + ")"); return(null); } } catch (Exception e) { DebugConsole.ThrowError("Could not find the constructor of the component \"" + type + "\" (" + file + ")", e); return(null); } ItemComponent ic = null; try { object[] lobject = new object[] { item, element }; object component = constructor.Invoke(lobject); ic = (ItemComponent)component; ic.name = element.Name.ToString(); } catch (TargetInvocationException e) { DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e.InnerException); GameAnalyticsManager.AddErrorEventOnce("ItemComponent.Load:TargetInvocationException" + item.Name + element.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Error while loading entity of the type " + t + " (" + e.InnerException + ")\n" + Environment.StackTrace.CleanupStackTrace()); } return(ic); }
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); }
private void PushCharactersAway() { if (!MathUtils.IsValid(item.SimPosition)) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")"); GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:DoorPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to push a character out of a doorway - position of the door is not valid (" + item.SimPosition + ")."); return; } //push characters out of the doorway when the door is closing/opening Vector2 simPos = ConvertUnits.ToSimUnits(new Vector2(item.Rect.X, item.Rect.Y)); Vector2 currSize = isHorizontal ? new Vector2(item.Rect.Width * (1.0f - openState), doorSprite.size.Y) : new Vector2(doorSprite.size.X, item.Rect.Height * (1.0f - openState)); Vector2 simSize = ConvertUnits.ToSimUnits(currSize); foreach (Character c in Character.CharacterList) { if (!c.Enabled) { continue; } if (!MathUtils.IsValid(c.SimPosition)) { DebugConsole.ThrowError("Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")"); GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:CharacterPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + c.SimPosition + ")." + " Removed: " + c.Removed + " Remoteplayer: " + c.IsRemotePlayer); continue; } int dir = isHorizontal ? Math.Sign(c.SimPosition.Y - item.SimPosition.Y) : Math.Sign(c.SimPosition.X - item.SimPosition.X); List <PhysicsBody> bodies = c.AnimController.Limbs.Select(l => l.body).ToList(); bodies.Add(c.AnimController.Collider); foreach (PhysicsBody body in bodies) { float diff = 0.0f; if (!MathUtils.IsValid(body.SimPosition)) { DebugConsole.ThrowError("Failed to push a limb out of a doorway - position of the body (character \"" + c.Name + "\") is not valid (" + body.SimPosition + ")"); GameAnalyticsManager.AddErrorEventOnce("PushCharactersAway:LimbPosInvalid", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to push a character out of a doorway - position of the character \"" + c.Name + "\" is not valid (" + body.SimPosition + ")." + " Removed: " + c.Removed + " Remoteplayer: " + c.IsRemotePlayer); continue; } if (isHorizontal) { if (body.SimPosition.X < simPos.X || body.SimPosition.X > simPos.X + simSize.X) { continue; } diff = body.SimPosition.Y - item.SimPosition.Y; } else { if (body.SimPosition.Y > simPos.Y || body.SimPosition.Y < simPos.Y - simSize.Y) { continue; } diff = body.SimPosition.X - item.SimPosition.X; } if (Math.Sign(diff) != dir) { #if CLIENT SoundPlayer.PlayDamageSound("LimbBlunt", 1.0f, body); #endif if (isHorizontal) { body.SetTransform(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * simSize.Y * 2.0f), body.Rotation); body.ApplyLinearImpulse(new Vector2(isOpen ? 0.0f : 1.0f, dir * 2.0f)); } else { body.SetTransform(new Vector2(item.SimPosition.X + dir * simSize.X * 1.2f, body.SimPosition.Y), body.Rotation); body.ApplyLinearImpulse(new Vector2(dir * 0.5f, isOpen ? 0.0f : -1.0f)); } } if (isHorizontal) { if (Math.Abs(body.SimPosition.Y - item.SimPosition.Y) > simSize.Y * 0.5f) { continue; } body.ApplyLinearImpulse(new Vector2(isOpen ? 0.0f : 1.0f, dir * 0.5f)); } else { if (Math.Abs(body.SimPosition.X - item.SimPosition.X) > simSize.X * 0.5f) { continue; } body.ApplyLinearImpulse(new Vector2(dir * 0.5f, isOpen ? 0.0f : -1.0f)); } c.SetStun(0.2f); } } }
/// <summary> /// Creates a copy of the specified workshop item in the staging folder and an editor that can be used to edit and update the item /// </summary> public static void CreateWorkshopItemStaging(Workshop.Item existingItem, out Workshop.Editor itemEditor, out ContentPackage contentPackage) { if (!existingItem.Installed) { itemEditor = null; contentPackage = null; DebugConsole.ThrowError("Cannot edit the workshop item \"" + existingItem.Title + "\" because it has not been installed."); return; } var stagingFolder = new DirectoryInfo(WorkshopItemStagingFolder); if (stagingFolder.Exists) { SaveUtil.ClearFolder(stagingFolder.FullName); } else { stagingFolder.Create(); } itemEditor = instance.client.Workshop.EditItem(existingItem.Id); itemEditor.Visibility = Workshop.Editor.VisibilityType.Public; itemEditor.Title = existingItem.Title; itemEditor.Tags = existingItem.Tags.ToList(); itemEditor.Description = existingItem.Description; itemEditor.WorkshopUploadAppId = AppID; itemEditor.Folder = stagingFolder.FullName; string previewImagePath = Path.GetFullPath(Path.Combine(itemEditor.Folder, PreviewImageName)); itemEditor.PreviewImage = previewImagePath; try { if (File.Exists(previewImagePath)) { File.Delete(previewImagePath); } Uri baseAddress = new Uri(existingItem.PreviewImageUrl); Uri directory = new Uri(baseAddress, "."); // "." == current dir, like MS-DOS string fileName = Path.GetFileName(baseAddress.LocalPath); IRestClient client = new RestClient(directory); var request = new RestRequest(fileName, Method.GET); var response = client.Execute(request); if (response.ResponseStatus == ResponseStatus.Completed) { File.WriteAllBytes(previewImagePath, response.RawBytes); } } catch (Exception e) { string errorMsg = "Failed to save workshop item preview image to \"" + previewImagePath + "\" when creating workshop item staging folder."; GameAnalyticsManager.AddErrorEventOnce("SteamManager.CreateWorkshopItemStaging:WriteAllBytesFailed" + previewImagePath, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + e.Message); } ContentPackage tempContentPackage = new ContentPackage(Path.Combine(existingItem.Directory.FullName, MetadataFileName)); //item already installed, copy it from the game folder if (existingItem != null && CheckWorkshopItemEnabled(existingItem, checkContentFiles: false)) { string installedItemPath = GetWorkshopItemContentPackagePath(tempContentPackage); if (File.Exists(installedItemPath)) { tempContentPackage = new ContentPackage(installedItemPath); } } if (File.Exists(tempContentPackage.Path)) { string newContentPackagePath = Path.Combine(WorkshopItemStagingFolder, MetadataFileName); File.Copy(tempContentPackage.Path, newContentPackagePath, overwrite: true); contentPackage = new ContentPackage(newContentPackagePath); foreach (ContentFile contentFile in tempContentPackage.Files) { string sourceFile; if (contentFile.Type == ContentType.Submarine && File.Exists(contentFile.Path)) { sourceFile = contentFile.Path; } else { sourceFile = Path.Combine(existingItem.Directory.FullName, contentFile.Path); } if (!File.Exists(sourceFile)) { continue; } //make sure the destination directory exists string destinationPath = Path.Combine(WorkshopItemStagingFolder, contentFile.Path); Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); File.Copy(sourceFile, destinationPath, overwrite: true); contentPackage.AddFile(contentFile.Path, contentFile.Type); } } else { contentPackage = ContentPackage.CreatePackage(existingItem.Title, Path.Combine(WorkshopItemStagingFolder, MetadataFileName), false); contentPackage.Save(contentPackage.Path); } }
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(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 => { 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"); } ); } 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"); } ); } } 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 SetContainedItemPositions() { Vector2 transformedItemPos = ItemPos * item.Scale; Vector2 transformedItemInterval = ItemInterval * item.Scale; Vector2 transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f); Vector2 transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y); if (item.body == null) { if (item.FlippedX) { transformedItemPos.X = -transformedItemPos.X; transformedItemPos.X += item.Rect.Width; transformedItemInterval.X = -transformedItemInterval.X; transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X; } if (item.FlippedY) { transformedItemPos.Y = -transformedItemPos.Y; transformedItemPos.Y -= item.Rect.Height; transformedItemInterval.Y = -transformedItemInterval.Y; transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y; } transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y); if (Math.Abs(item.Rotation) > 0.01f) { Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation)); transformedItemPos = Vector2.Transform(transformedItemPos, transform); transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform); } } else { Matrix transform = Matrix.CreateRotationZ(item.body.Rotation); if (item.body.Dir == -1.0f) { transformedItemPos.X = -transformedItemPos.X; transformedItemInterval.X = -transformedItemInterval.X; transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X; } transformedItemPos = Vector2.Transform(transformedItemPos, transform); transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform); transformedItemPos += item.Position; } float currentRotation = itemRotation; if (item.body != null) { currentRotation += item.body.Rotation; } int i = 0; Vector2 currentItemPos = transformedItemPos; foreach (Item contained in Inventory.AllItems) { if (contained.body != null) { try { Vector2 simPos = ConvertUnits.ToSimUnits(currentItemPos); contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, currentRotation); contained.body.SetPrevTransform(contained.body.SimPosition, contained.body.Rotation); contained.body.UpdateDrawPosition(); } catch (Exception e) { DebugConsole.Log("SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("ItemContainer.SetContainedItemPositions.InvalidPosition:" + contained.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace.CleanupStackTrace()); } contained.body.Submarine = item.Submarine; } contained.Rect = new Rectangle( (int)(currentItemPos.X - contained.Rect.Width / 2.0f), (int)(currentItemPos.Y + contained.Rect.Height / 2.0f), contained.Rect.Width, contained.Rect.Height); contained.Submarine = item.Submarine; contained.CurrentHull = item.CurrentHull; contained.SetContainedItemPositions(); i++; if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f) { //interval set on both axes -> use a grid layout currentItemPos += transformedItemIntervalHorizontal; if (i % ItemsPerRow == 0) { currentItemPos = transformedItemPos; currentItemPos += transformedItemIntervalVertical * (i / ItemsPerRow); } } else { currentItemPos += transformedItemInterval; } } }
public override void Update(float deltaTime) { if (netServer == null) { return; } if (OnOwnerDetermined != null && OwnerConnection != null) { OnOwnerDetermined?.Invoke(OwnerConnection); OnOwnerDetermined = null; } netServer.ReadMessages(incomingLidgrenMessages); //backwards for loop so we can remove elements while iterating for (int i = connectedClients.Count - 1; i >= 0; i--) { connectedClients[i].Decay(deltaTime); if (connectedClients[i].Timeout < 0.0) { Disconnect(connectedClients[i], "Timed out"); } } //process incoming connections first foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval)) { HandleConnection(inc); } try { //after processing connections, go ahead with the rest of the messages foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType != NetIncomingMessageType.ConnectionApproval)) { switch (inc.MessageType) { case NetIncomingMessageType.Data: HandleDataMessage(inc); break; case NetIncomingMessageType.StatusChanged: HandleStatusChanged(inc); break; } } } catch (Exception e) { string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace; GameAnalyticsManager.AddErrorEventOnce("SteamP2PServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); #if DEBUG DebugConsole.ThrowError(errorMsg); #else if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } #endif } for (int i = 0; i < pendingClients.Count; i++) { PendingClient pendingClient = pendingClients[i]; UpdatePendingClient(pendingClient); if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; } } incomingLidgrenMessages.Clear(); }
public void Update(float deltaTime) { activeTransfers.RemoveAll(t => t.Connection.Status != NetworkConnectionStatus.Connected); var endedTransfers = activeTransfers.FindAll(t => t.Connection.Status != NetworkConnectionStatus.Connected || t.Status == FileTransferStatus.Finished || t.Status == FileTransferStatus.Canceled || t.Status == FileTransferStatus.Error); foreach (FileTransferOut transfer in endedTransfers) { activeTransfers.Remove(transfer); OnEnded(transfer); } foreach (FileTransferOut transfer in activeTransfers) { transfer.WaitTimer -= deltaTime; if (transfer.WaitTimer > 0.0f) { continue; } transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime; // send another part of the file long remaining = transfer.Data.Length - transfer.SentOffset; int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining); IWriteMessage message; try { //first message; send length, file name etc //wait for acknowledgement before sending data if (!transfer.Acknowledged) { message = new WriteOnlyMessage(); message.Write((byte)ServerPacketHeader.FILE_TRANSFER); //if the recipient is the owner of the server (= a client running the server from the main exe) //we don't need to send anything, the client can just read the file directly if (transfer.Connection == GameMain.Server.OwnerConnection) { message.Write((byte)FileTransferMessageType.TransferOnSameMachine); message.Write((byte)transfer.ID); message.Write((byte)transfer.FileType); message.Write(transfer.FilePath); peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); transfer.Status = FileTransferStatus.Finished; } else { message.Write((byte)FileTransferMessageType.Initiate); message.Write((byte)transfer.ID); message.Write((byte)transfer.FileType); //message.Write((ushort)chunkLen); message.Write(transfer.Data.Length); message.Write(transfer.FileName); peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); transfer.Status = FileTransferStatus.Sending; if (GameSettings.VerboseLogging) { DebugConsole.Log("Sending file transfer initiation message: "); DebugConsole.Log(" File: " + transfer.FileName); DebugConsole.Log(" Size: " + transfer.Data.Length); DebugConsole.Log(" ID: " + transfer.ID); } } return; } message = new WriteOnlyMessage(); message.Write((byte)ServerPacketHeader.FILE_TRANSFER); message.Write((byte)FileTransferMessageType.Data); message.Write((byte)transfer.ID); message.Write(transfer.SentOffset); byte[] sendBytes = new byte[sendByteCount]; Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount); message.Write((ushort)sendByteCount); message.Write(sendBytes, 0, sendByteCount); transfer.SentOffset += sendByteCount; if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 5 || transfer.SentOffset >= transfer.Data.Length) { transfer.SentOffset = transfer.KnownReceivedOffset; } peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); } catch (Exception e) { DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e); GameAnalyticsManager.AddErrorEventOnce( "FileSender.Update:Exception", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace); transfer.Status = FileTransferStatus.Error; break; } if (GameSettings.VerboseLogging) { DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)"); } } }
private void Send(FileTransferOut transfer) { // send another part of the file long remaining = transfer.Data.Length - transfer.SentOffset; int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining); IWriteMessage message; try { //first message; send length, file name etc //wait for acknowledgement before sending data if (!transfer.Acknowledged) { message = new WriteOnlyMessage(); message.Write((byte)ServerPacketHeader.FILE_TRANSFER); //if the recipient is the owner of the server (= a client running the server from the main exe) //we don't need to send anything, the client can just read the file directly if (transfer.Connection == GameMain.Server.OwnerConnection) { message.Write((byte)FileTransferMessageType.TransferOnSameMachine); message.Write((byte)transfer.ID); message.Write((byte)transfer.FileType); message.Write(transfer.FilePath); peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); transfer.Status = FileTransferStatus.Finished; } else { message.Write((byte)FileTransferMessageType.Initiate); message.Write((byte)transfer.ID); message.Write((byte)transfer.FileType); //message.Write((ushort)chunkLen); message.Write(transfer.Data.Length); message.Write(transfer.FileName); peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); transfer.Status = FileTransferStatus.Sending; if (GameSettings.VerboseLogging) { DebugConsole.Log("Sending file transfer initiation message: "); DebugConsole.Log(" File: " + transfer.FileName); DebugConsole.Log(" Size: " + transfer.Data.Length); DebugConsole.Log(" ID: " + transfer.ID); } } transfer.WaitTimer = 0.1f; return; } message = new WriteOnlyMessage(); message.Write((byte)ServerPacketHeader.FILE_TRANSFER); message.Write((byte)FileTransferMessageType.Data); message.Write((byte)transfer.ID); message.Write(transfer.SentOffset); byte[] sendBytes = new byte[sendByteCount]; Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount); message.Write((ushort)sendByteCount); message.Write(sendBytes, 0, sendByteCount); transfer.SentOffset += sendByteCount; if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 10 || transfer.SentOffset >= transfer.Data.Length) { transfer.SentOffset = transfer.KnownReceivedOffset; transfer.WaitTimer = 0.5f; } peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable); } catch (Exception e) { DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e); GameAnalyticsManager.AddErrorEventOnce( "FileSender.Update:Exception", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); transfer.Status = FileTransferStatus.Error; return; } if (GameSettings.VerboseLogging) { DebugConsole.Log($"Sending {sendByteCount} bytes of the file {transfer.FileName} ({transfer.SentOffset / 1000}/{transfer.Data.Length / 1000} kB sent)"); } }
/// <summary> /// Write the events to the outgoing message. The recipient parameter is only needed for ServerEntityEventManager /// </summary> protected void Write(NetOutgoingMessage msg, List <NetEntityEvent> eventsToSync, Client recipient = null) { //write into a temporary buffer so we can write the number of events before the actual data NetBuffer tempBuffer = new NetBuffer(); int eventCount = 0; foreach (NetEntityEvent e in eventsToSync) { //write into a temporary buffer so we can write the length before the actual data NetBuffer tempEventBuffer = new NetBuffer(); try { WriteEvent(tempEventBuffer, e, recipient); } catch (Exception exception) { DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception); GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:WriteFailed" + e.Entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to write an event for the entity \"" + e.Entity + "\"\n" + exception.StackTrace); //write an empty event to avoid messing up IDs //(otherwise the clients might read the next event in the message and think its ID //is consecutive to the previous one, even though we skipped over this broken event) tempBuffer.Write((UInt16)0); tempBuffer.WritePadBits(); eventCount++; continue; } if (msg.LengthBytes + tempBuffer.LengthBytes + tempEventBuffer.LengthBytes > NetPeerConfiguration.kDefaultMTU - 20) { //no more room in this packet break; } if (tempEventBuffer.LengthBytes > 255) { DebugConsole.ThrowError("Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes"); GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:TooLong" + e.Entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes"); //write an empty event breaking the event syncing tempBuffer.Write((UInt16)0); tempBuffer.WritePadBits(); eventCount++; continue; } //the ID has been taken by another entity (the original entity has been removed) -> write an empty event else if (Entity.FindEntityByID(e.Entity.ID) != e.Entity || e.Entity.IdFreed) { //technically the clients don't have any use for these, but removing events and shifting the IDs of all //consecutive ones is so error-prone that I think this is a safer option tempBuffer.Write((UInt16)0); tempBuffer.WritePadBits(); } else { tempBuffer.Write((UInt16)e.Entity.ID); tempBuffer.Write((byte)tempEventBuffer.LengthBytes); tempBuffer.Write(tempEventBuffer); tempBuffer.WritePadBits(); } eventCount++; } if (eventCount > 0) { msg.Write(eventsToSync[0].ID); msg.Write((byte)eventCount); msg.Write(tempBuffer); } }
/// <summary> /// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails. /// </summary> public bool Read(ServerNetObject type, IReadMessage msg, float sendingTime, List <IServerSerializable> entities) { UInt16 unreceivedEntityEventCount = 0; if (type == ServerNetObject.ENTITY_EVENT_INITIAL) { unreceivedEntityEventCount = msg.ReadUInt16(); firstNewID = msg.ReadUInt16(); if (GameSettings.VerboseLogging) { DebugConsole.NewMessage( "received midround syncing msg, unreceived: " + unreceivedEntityEventCount + ", first new ID: " + firstNewID, Microsoft.Xna.Framework.Color.Yellow); } } else { MidRoundSyncingDone = true; if (firstNewID != null) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("midround syncing complete, switching to ID " + (UInt16)(firstNewID - 1), Microsoft.Xna.Framework.Color.Yellow); } lastReceivedID = (UInt16)(firstNewID - 1); firstNewID = null; } } entities.Clear(); UInt16 firstEventID = msg.ReadUInt16(); int eventCount = msg.ReadByte(); for (int i = 0; i < eventCount; i++) { //16 = entity ID, 8 = msg length if (msg.BitPosition + 16 + 8 > msg.LengthBits) { string errorMsg = $"Error while reading a message from the server. Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits})."; errorMsg += "\nPrevious entities:"; for (int j = entities.Count - 1; j >= 0; j--) { errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString()); } DebugConsole.ThrowError(errorMsg); return(false); } UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i); UInt16 entityID = msg.ReadUInt16(); if (entityID == Entity.NullEntityID) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)", Microsoft.Xna.Framework.Color.Orange); } msg.ReadPadBits(); entities.Add(null); if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; } continue; } int msgLength = (int)msg.ReadVariableUInt32(); IServerSerializable entity = Entity.FindEntityByID(entityID) as IServerSerializable; entities.Add(entity); //skip the event if we've already received it or if the entity isn't found if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null) { if (thisEventID != (UInt16)(lastReceivedID + 1)) { if (GameSettings.VerboseLogging) { DebugConsole.NewMessage( "Received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")", NetIdUtils.IdMoreRecent(thisEventID, (UInt16)(lastReceivedID + 1)) ? GUI.Style.Red : Microsoft.Xna.Framework.Color.Yellow); } } else if (entity == null) { DebugConsole.NewMessage( "Received msg " + thisEventID + ", entity " + entityID + " not found", GUI.Style.Red); GameMain.Client.ReportError(ClientNetError.MISSING_ENTITY, eventID: thisEventID, entityID: entityID); return(false); } msg.BitPosition += msgLength * 8; msg.ReadPadBits(); } else { long msgPosition = msg.BitPosition; if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")", Microsoft.Xna.Framework.Color.Green); } lastReceivedID++; try { ReadEvent(msg, entity, sendingTime); msg.ReadPadBits(); if (msg.BitPosition != msgPosition + msgLength * 8) { string errorMsg = "Message byte position incorrect after reading an event for the entity \"" + entity.ToString() + "\". Read " + (msg.BitPosition - msgPosition) + " bits, expected message length was " + (msgLength * 8) + " bits."; #if DEBUG DebugConsole.ThrowError(errorMsg); #endif GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); //TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick //msg.BitPosition = (int)(msgPosition + msgLength * 8); } } catch (Exception e) { string errorMsg = "Failed to read event for entity \"" + entity.ToString() + "\" (" + e.Message + ")! (MidRoundSyncing: " + thisClient.MidRoundSyncing + ")\n" + e.StackTrace.CleanupStackTrace(); errorMsg += "\nPrevious entities:"; for (int j = entities.Count - 2; j >= 0; j--) { errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString()); } DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); msg.BitPosition = (int)(msgPosition + msgLength * 8); msg.ReadPadBits(); } } } return(true); }
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); }