From 3a3935ed4831aace052d844a8de97aa71e1f3334 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Fri, 19 May 2023 14:46:04 -0700 Subject: [PATCH 1/3] Improve in-game exception handling. Any thrown exceptions kick the game back to the main menu, reset the display size, and show a "lost connection" dialog. --- EndlessClient/Controllers/MainButtonController.cs | 7 +++++-- EndlessClient/GameExecution/EndlessGame.cs | 14 ++++++++++---- .../Network/PacketHandlerGameComponent.cs | 7 +------ .../Rendering/Character/PeriodicEmoteHandler.cs | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/EndlessClient/Controllers/MainButtonController.cs b/EndlessClient/Controllers/MainButtonController.cs index f83adf0e8..fc95380af 100644 --- a/EndlessClient/Controllers/MainButtonController.cs +++ b/EndlessClient/Controllers/MainButtonController.cs @@ -49,12 +49,15 @@ public void GoToInitialState() _gameStateActions.ChangeToState(GameStates.Initial); } - public void GoToInitialStateAndDisconnect() + public void GoToInitialStateAndDisconnect(bool showLostConnection = false) { GoToInitialState(); StopReceivingAndDisconnect(); _resetStateAction.ResetState(); + + if (showLostConnection) + _errorDialogDisplayAction.ShowConnectionLost(false); } public async Task ClickCreateAccount() @@ -150,7 +153,7 @@ public interface IMainButtonController { void GoToInitialState(); - void GoToInitialStateAndDisconnect(); + void GoToInitialStateAndDisconnect(bool showLostConnection = false); Task ClickCreateAccount(); diff --git a/EndlessClient/GameExecution/EndlessGame.cs b/EndlessClient/GameExecution/EndlessGame.cs index b5657a6d3..77f3f5fec 100644 --- a/EndlessClient/GameExecution/EndlessGame.cs +++ b/EndlessClient/GameExecution/EndlessGame.cs @@ -1,6 +1,7 @@ using AutomaticTypeMapper; using EndlessClient.Audio; using EndlessClient.Content; +using EndlessClient.Controllers; using EndlessClient.ControlSets; using EndlessClient.Rendering; using EndlessClient.Rendering.Chat; @@ -43,6 +44,8 @@ public class EndlessGame : Game, IEndlessGame private readonly IMfxPlayer _mfxPlayer; private readonly IXnaControlSoundMapper _soundMapper; private readonly IFixedTimeStepRepository _fixedTimeStepRepository; + private readonly IMainButtonController _mainButtonController; + private GraphicsDeviceManager _graphicsDeviceManager; private KeyboardState _previousKeyState; @@ -69,7 +72,8 @@ public EndlessGame(IClientWindowSizeRepository windowSizeRepository, IConfigurationProvider configurationProvider, IMfxPlayer mfxPlayer, IXnaControlSoundMapper soundMapper, - IFixedTimeStepRepository fixedTimeStepRepository) + IFixedTimeStepRepository fixedTimeStepRepository, + IMainButtonController mainButtonController) { _windowSizeRepository = windowSizeRepository; _contentProvider = contentProvider; @@ -86,6 +90,8 @@ public EndlessGame(IClientWindowSizeRepository windowSizeRepository, _mfxPlayer = mfxPlayer; _soundMapper = soundMapper; _fixedTimeStepRepository = fixedTimeStepRepository; + _mainButtonController = mainButtonController; + _graphicsDeviceManager = new GraphicsDeviceManager(this) { PreferredBackBufferWidth = ClientWindowSizeRepository.DEFAULT_BACKBUFFER_WIDTH, @@ -203,9 +209,9 @@ protected override void Update(GameTime gameTime) { base.Update(gameTime); } - catch (InvalidOperationException ioe) when (ioe.InnerException is NullReferenceException) + catch { - // hide "failed to compare two elements in the array" error from Monogame + _mainButtonController.GoToInitialStateAndDisconnect(showLostConnection: true); } _lastFrameUpdate = gameTime.TotalGameTime; @@ -287,7 +293,7 @@ private void SetUpInitialControlSet() _controlSetRepository.CurrentControlSet); _controlSetRepository.CurrentControlSet = controls; - //since the controls are being created in Initialize(), adding them to the default game + //since the controls are being created in LoadContent(), adding them to the default game // doesn't call the Initialize() method on any controls, so it must be done here foreach (var xnaControl in _controlSetRepository.CurrentControlSet.AllComponents) xnaControl.Initialize(); diff --git a/EndlessClient/Network/PacketHandlerGameComponent.cs b/EndlessClient/Network/PacketHandlerGameComponent.cs index 23c8ec696..ed4c587ae 100644 --- a/EndlessClient/Network/PacketHandlerGameComponent.cs +++ b/EndlessClient/Network/PacketHandlerGameComponent.cs @@ -1,6 +1,5 @@ using AutomaticTypeMapper; using EndlessClient.Controllers; -using EndlessClient.Dialogs.Actions; using EndlessClient.GameExecution; using EOLib.Net.Communication; using EOLib.Net.Handlers; @@ -14,21 +13,18 @@ public class PacketHandlerGameComponent : GameComponent private readonly IOutOfBandPacketHandler _packetHandler; private readonly INetworkClientProvider _networkClientProvider; private readonly IGameStateProvider _gameStateProvider; - private readonly IErrorDialogDisplayAction _errorDialogDisplayAction; private readonly IMainButtonController _mainButtonController; public PacketHandlerGameComponent(IEndlessGame game, IOutOfBandPacketHandler packetHandler, INetworkClientProvider networkClientProvider, IGameStateProvider gameStateProvider, - IErrorDialogDisplayAction errorDialogDisplayAction, IMainButtonController mainButtonController) : base((Game) game) { _packetHandler = packetHandler; _networkClientProvider = networkClientProvider; _gameStateProvider = gameStateProvider; - _errorDialogDisplayAction = errorDialogDisplayAction; _mainButtonController = mainButtonController; UpdateOrder = int.MinValue; @@ -41,8 +37,7 @@ public override void Update(GameTime gameTime) !_networkClientProvider.NetworkClient.Connected) { var isInGame = _gameStateProvider.CurrentState == GameStates.PlayingTheGame; - _mainButtonController.GoToInitialStateAndDisconnect(); - _errorDialogDisplayAction.ShowConnectionLost(isInGame); + _mainButtonController.GoToInitialStateAndDisconnect(showLostConnection: true); } _packetHandler.PollForPacketsAndHandle(); diff --git a/EndlessClient/Rendering/Character/PeriodicEmoteHandler.cs b/EndlessClient/Rendering/Character/PeriodicEmoteHandler.cs index 286dcf8d8..01602aaf0 100644 --- a/EndlessClient/Rendering/Character/PeriodicEmoteHandler.cs +++ b/EndlessClient/Rendering/Character/PeriodicEmoteHandler.cs @@ -109,7 +109,7 @@ public override void Update(GameTime gameTime) { if ((now - _userInputTimeProvider.LastInputTime).TotalMilliseconds >= AFK_TIME_MS + AFK_TIME_ALERT_MS) { - _mainButtonController.GoToInitialStateAndDisconnect(); + _mainButtonController.GoToInitialStateAndDisconnect(showLostConnection: true); } else { From 08ce610eb2a7d926407b1b8859f72d316e8861f4 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Sat, 20 May 2023 01:07:44 -0700 Subject: [PATCH 2/3] Invoke game state change operations that generate lots of new components on the main update thread. Should fix crashes with GameComponentsCollection in MonoGame due to async threads modifying the components collection. Also improves performance of switching to in-game. --- EndlessClient/Controllers/LoginController.cs | 19 ++++++++++++------- .../Controllers/MainButtonController.cs | 12 +++++++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/EndlessClient/Controllers/LoginController.cs b/EndlessClient/Controllers/LoginController.cs index 4431105de..de1974a48 100644 --- a/EndlessClient/Controllers/LoginController.cs +++ b/EndlessClient/Controllers/LoginController.cs @@ -101,7 +101,9 @@ public async Task LoginToAccount(ILoginParameters loginParameters) var reply = loginToServerOperation.Result; if (reply == LoginReply.Ok) - _gameStateActions.ChangeToState(GameStates.LoggedIn); + { + await DispatcherGameComponent.Invoke(() => _gameStateActions.ChangeToState(GameStates.LoggedIn)); + } else { _errorDisplayAction.ShowLoginError(reply); @@ -227,12 +229,15 @@ await DispatcherGameComponent.Invoke(() => _clientWindowSizeRepository.Resizable = true; } - _gameStateActions.ChangeToState(GameStates.PlayingTheGame); - _chatTextBoxActions.FocusChatTextBox(); - _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, - EOResourceID.LOADING_GAME_HINT_FIRST); - _firstTimePlayerActions.WarnFirstTimePlayers(); - _mapChangedActions.ActiveCharacterEnterMapForLogin(); + await DispatcherGameComponent.Invoke(() => + { + _gameStateActions.ChangeToState(GameStates.PlayingTheGame); + _chatTextBoxActions.FocusChatTextBox(); + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, + EOResourceID.LOADING_GAME_HINT_FIRST); + _firstTimePlayerActions.WarnFirstTimePlayers(); + _mapChangedActions.ActiveCharacterEnterMapForLogin(); + }); } private void SetInitialStateAndShowError(NoDataSentException ex) diff --git a/EndlessClient/Controllers/MainButtonController.cs b/EndlessClient/Controllers/MainButtonController.cs index fc95380af..5f50375bb 100644 --- a/EndlessClient/Controllers/MainButtonController.cs +++ b/EndlessClient/Controllers/MainButtonController.cs @@ -1,6 +1,7 @@ using AutomaticTypeMapper; using EndlessClient.Dialogs.Actions; using EndlessClient.GameExecution; +using EndlessClient.Rendering; using EOLib.Domain; using EOLib.Domain.Protocol; using EOLib.Net.Communication; @@ -66,8 +67,11 @@ public async Task ClickCreateAccount() if (result) { - _gameStateActions.ChangeToState(GameStates.CreateAccount); - _accountDialogDisplayActions.ShowInitialCreateWarningDialog(); + await DispatcherGameComponent.Invoke(() => + { + _gameStateActions.ChangeToState(GameStates.CreateAccount); + _accountDialogDisplayActions.ShowInitialCreateWarningDialog(); + }); } } @@ -76,7 +80,9 @@ public async Task ClickLogin() var result = await StartNetworkConnection().ConfigureAwait(false); if (result) - _gameStateActions.ChangeToState(GameStates.Login); + { + await DispatcherGameComponent.Invoke(() => _gameStateActions.ChangeToState(GameStates.Login)); + } } public void ClickViewCredits() From fb00c47c9c96a775bd938204ee5a72a16f4611c4 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Sat, 20 May 2023 01:13:02 -0700 Subject: [PATCH 3/3] Take NPC walking into account when getting map cell state Fixes bug where walking into an NPC wouldn't move the character until the NPC had fully animated to the next tile, making it difficult to chase passive NPCs. --- EOLib/Domain/Map/MapCellStateProvider.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/EOLib/Domain/Map/MapCellStateProvider.cs b/EOLib/Domain/Map/MapCellStateProvider.cs index 4d11e22dd..d551685ef 100644 --- a/EOLib/Domain/Map/MapCellStateProvider.cs +++ b/EOLib/Domain/Map/MapCellStateProvider.cs @@ -45,7 +45,11 @@ public IMapCellState GetCellStateAt(int x, int y) Option npc = Option.None(); if (_mapStateProvider.NPCs.TryGetValues(new MapCoordinate(x, y), out var npcs)) + { npc = npcs.FirstOrNone(); + if (npc.Map(x => x.IsActing(NPCActionState.Walking)).ValueOr(false)) + npc = Option.None(); + } var items = _mapStateProvider.MapItems.TryGetValues(new MapCoordinate(x, y), out var mapItems) ? mapItems.OrderByDescending(i => i.UniqueID)