From f1c4bd3c676fa56d521565749387cb1659f74c5f Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 19 Jul 2018 21:13:18 +0300 Subject: [PATCH] - Some of the non-game-crashing error messages are sent to GameAnalytics. - Changed crash severity from Error to Critical. - Exception handling when loading submarine preview images. - Checking if position is valid in Ragdoll.SetPosition. --- .../Source/Characters/Animation/Ragdoll.cs | 3 +++ .../Source/Networking/GameClient.cs | 25 +++++++++++-------- .../ClientEntityEventManager.cs | 3 +++ Barotrauma/BarotraumaClient/Source/Program.cs | 2 +- .../Source/Screens/NetLobbyScreen.cs | 16 ++++++++++-- .../Source/Sounds/OggStream.cs | 4 +++ .../Source/Characters/AI/AITarget.cs | 6 +++++ .../Source/Characters/Animation/Ragdoll.cs | 15 +++++++++++ .../BarotraumaShared/Source/DebugConsole.cs | 11 +++++++- .../Source/GameAnalyticsManager.cs | 15 +++++++++++ .../Source/Items/Components/Door.cs | 6 +++++ .../BarotraumaShared/Source/Map/Submarine.cs | 14 +++++++++-- .../NetEntityEvent/NetEntityEventManager.cs | 6 +++++ 13 files changed, 109 insertions(+), 17 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index 309920928..6b9d32c9e 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -81,6 +81,9 @@ namespace Barotrauma if (Limbs == null) { DebugConsole.ThrowError("Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace); + GameAnalyticsManager.AddErrorEventOnce("Ragdoll.Draw:LimbsRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace); return; } diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index e58636d35..adbc146eb 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -1020,29 +1020,32 @@ namespace Barotrauma.Networking ChatMessage.ClientRead(inc); break; default: - DebugConsole.ThrowError("Error while reading update from server (unknown object header \""+objHeader+"\"!)"); - if (prevObjHeader != null) + List errorLines = new List { - DebugConsole.ThrowError("Previous object type: " + prevObjHeader.ToString()); - } - else - { - DebugConsole.ThrowError("Error occurred on the very first object!"); - } - DebugConsole.ThrowError("Previous object was " + (inc.Position - prevBitPos) + " bits long (" + (inc.PositionInBytes - prevBytePos) + " bytes)"); + "Error while reading update from server (unknown object header \"" + objHeader + "\"!)", + prevObjHeader != null ? "Previous object type: " + prevObjHeader.ToString() : "Error occurred on the very first object!", + "Previous object was " + (inc.Position - prevBitPos) + " bits long (" + (inc.PositionInBytes - prevBytePos) + " bytes)" + }; if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL) { foreach (IServerSerializable ent in entities) { if (ent == null) { - DebugConsole.ThrowError(" - NULL"); + errorLines.Add(" - NULL"); continue; } Entity e = ent as Entity; - DebugConsole.ThrowError(" - "+e.ToString()); + errorLines.Add(" - " + e.ToString()); } } + + foreach (string line in errorLines) + { + DebugConsole.ThrowError(line); + } + GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, string.Join("\n", errorLines)); + DebugConsole.ThrowError("Writing object data to \"crashreport_object.bin\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues"); FileStream fl = File.Open("crashreport_object.bin", FileMode.Create); diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs index 2ea912a2f..4beda97e2 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs @@ -191,6 +191,9 @@ namespace Barotrauma.Networking 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; } diff --git a/Barotrauma/BarotraumaClient/Source/Program.cs b/Barotrauma/BarotraumaClient/Source/Program.cs index 4cb2af6c2..2e3f81929 100644 --- a/Barotrauma/BarotraumaClient/Source/Program.cs +++ b/Barotrauma/BarotraumaClient/Source/Program.cs @@ -203,7 +203,7 @@ namespace Barotrauma if (GameSettings.SendUserStatistics) { CrashMessageBox( "A crash report (\"crashreport.log\") was saved in the root folder of the game and sent to the developers."); - GameAnalytics.AddErrorEvent(EGAErrorSeverity.Error, crashReport); + GameAnalytics.AddErrorEvent(EGAErrorSeverity.Critical, crashReport); GameAnalytics.OnStop(); } else diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index 287edb042..0f9b9ad1b 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -737,8 +737,20 @@ namespace Barotrauma //hash will be null if opening the sub file failed -> don't select the sub if (string.IsNullOrWhiteSpace(hash)) { - (component as GUITextBlock).TextColor = Color.DarkRed * 0.8f; - component.CanBeFocused = false; + if (component is GUITextBlock textBlock) + { + textBlock.TextColor = Color.DarkRed * 0.8f; + textBlock.CanBeFocused = false; + } + else + { + DebugConsole.ThrowError("Failed to select submarine. Selected GUIComponent was of the type \"" + (component == null ? "null" : component.GetType().ToString()) + "\"."); + GameAnalyticsManager.AddErrorEventOnce( + "NetLobbyScreen.SelectSub:InvalidComponent", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to select submarine. Selected GUIComponent was of the type \"" + (component == null ? "null" : component.GetType().ToString()) + "\"."); + } + StartButton.Enabled = false; diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs b/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs index 8f88dfbb8..6f761edd3 100644 --- a/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs +++ b/Barotrauma/BarotraumaClient/Source/Sounds/OggStream.cs @@ -55,6 +55,10 @@ namespace Barotrauma.Sounds #else DebugConsole.NewMessage("OpenAL error: " + AL.GetErrorString(error) + "\n" + Environment.StackTrace, Microsoft.Xna.Framework.Color.Red); #endif + GameAnalyticsManager.AddErrorEventOnce( + "OggStream.Check:"+ AL.GetErrorString(error), + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "OpenAL error: " + AL.GetErrorString(error) + "\n" + Environment.StackTrace); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs index 87f40912c..d857cf3b6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs @@ -38,6 +38,9 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace); #endif + GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to access a removed AITarget\n" + Environment.StackTrace); return Vector2.Zero; } @@ -54,6 +57,9 @@ namespace Barotrauma #if DEBUG DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace); #endif + GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to access a removed AITarget\n" + Environment.StackTrace); return Vector2.Zero; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs index c9a371e76..3a5f28887 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs @@ -25,6 +25,11 @@ namespace Barotrauma if (limbs == null) { DebugConsole.ThrowError("Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this)); + GameAnalyticsManager.AddErrorEventOnce( + "Ragdoll.Limbs:AccessRemoved", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this)); + return new Limb[0]; } return limbs; @@ -1165,6 +1170,16 @@ namespace Barotrauma public void SetPosition(Vector2 simPosition, bool lerp = false) { + if (!MathUtils.IsValid(simPosition)) + { + DebugConsole.ThrowError("Attempted to move a ragdoll (" + character.Name + ") to an invalid position (" + simPosition + "). " + Environment.StackTrace); + GameAnalyticsManager.AddErrorEventOnce( + "Ragdoll.SetPosition:InvalidPosition", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Attempted to move a ragdoll (" + character.Name + ") to an invalid position (" + simPosition + "). " + Environment.StackTrace); + return; + } + Vector2 limbMoveAmount = simPosition - MainLimb.SimPosition; Collider.SetTransform(simPosition, Collider.Rotation); diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs index 2b65dccf9..078ef2755 100644 --- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs @@ -1898,7 +1898,16 @@ namespace Barotrauma if (string.IsNullOrWhiteSpace(command)) return; string[] splitCommand = SplitCommand(command); - + if (splitCommand.Length == 0) + { + DebugConsole.ThrowError("Failed to execute command \"" + command + "\"!"); + GameAnalyticsManager.AddErrorEventOnce( + "DebugConsole.ExecuteCommand:LengthZero", + GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Failed to execute command \"" + command + "\"!"); + return; + } + if (!splitCommand[0].ToLowerInvariant().Equals("admin")) { NewMessage(command, Color.White, true); diff --git a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs index 81374dab6..e96489c16 100644 --- a/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs +++ b/Barotrauma/BarotraumaShared/Source/GameAnalyticsManager.cs @@ -1,5 +1,6 @@ using GameAnalyticsSDK.Net; using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -9,6 +10,8 @@ namespace Barotrauma { public static class GameAnalyticsManager { + private static HashSet sentEventIdentifiers = new HashSet(); + public static void Init() { #if DEBUG @@ -47,5 +50,17 @@ namespace Barotrauma contentPackageName.Replace(":", "").Substring(0, Math.Min(32, contentPackageName.Length))); } } + + /// + /// Adds an error event to GameAnalytics if an event with the same identifier has not been added yet. + /// + public static void AddErrorEventOnce(string identifier, EGAErrorSeverity errorSeverity, string message) + { + if (!GameSettings.SendUserStatistics) return; + if (sentEventIdentifiers.Contains(identifier)) return; + + GameAnalytics.AddErrorEvent(errorSeverity, message); + sentEventIdentifiers.Add(identifier); + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 3cf5947a8..b3c8a5b03 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -352,6 +352,8 @@ namespace Barotrauma.Items.Components 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; } @@ -370,6 +372,10 @@ namespace Barotrauma.Items.Components 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); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs index e5ade2ae9..d291304b5 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs @@ -300,9 +300,19 @@ namespace Barotrauma string previewImageData = doc.Root.GetAttributeString("previewimage", ""); if (!string.IsNullOrEmpty(previewImageData)) { - using (MemoryStream mem = new MemoryStream(Convert.FromBase64String(previewImageData))) + try { - PreviewImage = new Sprite(TextureLoader.FromStream(mem), null, null); + using (MemoryStream mem = new MemoryStream(Convert.FromBase64String(previewImageData))) + { + PreviewImage = new Sprite(TextureLoader.FromStream(mem), null, null); + } + } + catch (Exception e) + { + DebugConsole.ThrowError("Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.", e); + GameAnalyticsManager.AddErrorEventOnce("Submarine..ctor:PreviewImageLoadingFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, + "Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted."); + PreviewImage = null; } } #endif diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs index fad368b6e..eafb6fb1b 100644 --- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs @@ -30,6 +30,9 @@ namespace Barotrauma.Networking 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 @@ -49,6 +52,9 @@ namespace Barotrauma.Networking 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"); } //the ID has been taken by another entity (the original entity has been removed) -> write an empty event