From 6beebf3e7294995239e103e90e03e74a824b57de Mon Sep 17 00:00:00 2001 From: Arufonsu <17498701+Arufonsu@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:14:18 -0300 Subject: [PATCH 1/3] feature: simplified escape menu + In game interface setting to toggle this feature. + Fix: "InvalidOperationException: Collection was modified (during iteration);" client crash was happening upon selecting 'yes' on Combat Warning prompt when logging out/exiting. --- .../Database/GameDatabase.cs | 4 + .../Gwen/Control/Base.cs | 5 +- Intersect.Client/Core/Input.cs | 11 +- .../Interface/Game/GameInterface.cs | 3 + Intersect.Client/Interface/Game/Menu.cs | 13 +- .../Interface/Game/SimplifiedEscapeMenu.cs | 167 ++++++++++++++++++ .../Interface/Shared/SettingsWindow.cs | 9 + Intersect.Client/Localization/Strings.cs | 3 + 8 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 Intersect.Client/Interface/Game/SimplifiedEscapeMenu.cs diff --git a/Intersect.Client.Framework/Database/GameDatabase.cs b/Intersect.Client.Framework/Database/GameDatabase.cs index e772fcb64..ef1de0c3f 100644 --- a/Intersect.Client.Framework/Database/GameDatabase.cs +++ b/Intersect.Client.Framework/Database/GameDatabase.cs @@ -58,6 +58,8 @@ public abstract partial class GameDatabase public bool ShowHealthAsPercentage { get; set; } public bool ShowManaAsPercentage { get; set; } + + public bool SimplifiedEscapeMenu { get; set; } public TypewriterBehavior TypewriterBehavior { get; set; } @@ -130,6 +132,7 @@ public virtual void LoadPreferences() ShowExperienceAsPercentage = LoadPreference(nameof(ShowExperienceAsPercentage), true); ShowHealthAsPercentage = LoadPreference(nameof(ShowHealthAsPercentage), false); ShowManaAsPercentage = LoadPreference(nameof(ShowManaAsPercentage), false); + SimplifiedEscapeMenu = LoadPreference(nameof(SimplifiedEscapeMenu), false); TypewriterBehavior = LoadPreference(nameof(TypewriterBehavior), TypewriterBehavior.Word); UIScale = LoadPreference(nameof(UIScale), 1.0f); WorldZoom = LoadPreference(nameof(WorldZoom), 1.0f); @@ -166,6 +169,7 @@ public virtual void SavePreferences() SavePreference(nameof(ShowExperienceAsPercentage), ShowExperienceAsPercentage); SavePreference(nameof(ShowHealthAsPercentage), ShowHealthAsPercentage); SavePreference(nameof(ShowManaAsPercentage), ShowManaAsPercentage); + SavePreference(nameof(SimplifiedEscapeMenu), SimplifiedEscapeMenu); SavePreference(nameof(TypewriterBehavior), TypewriterBehavior); SavePreference(nameof(UIScale), UIScale); SavePreference(nameof(WorldZoom), WorldZoom); diff --git a/Intersect.Client.Framework/Gwen/Control/Base.cs b/Intersect.Client.Framework/Gwen/Control/Base.cs index 628a82e50..39fbc1a9a 100644 --- a/Intersect.Client.Framework/Gwen/Control/Base.cs +++ b/Intersect.Client.Framework/Gwen/Control/Base.cs @@ -765,7 +765,10 @@ public virtual void Dispose() Gwen.ToolTip.ControlDeleted(this); Animation.Cancel(this); - mChildren?.ForEach(child => child?.Dispose()); + // [Fix]: "InvalidOperationException: Collection was modified (during iteration); enumeration operation may not execute". + // (Creates a copy of the children list to avoid modifying the collection during iteration). + var childrenCopy = new List(mChildren); + childrenCopy.ForEach(child => child.Dispose()); mChildren?.Clear(); mInnerPanel?.Dispose(); diff --git a/Intersect.Client/Core/Input.cs b/Intersect.Client/Core/Input.cs index 4d6948b66..02964631c 100644 --- a/Intersect.Client/Core/Input.cs +++ b/Intersect.Client/Core/Input.cs @@ -133,7 +133,16 @@ public static void OnKeyPressed(Keys modifier, Keys key) } else { - Interface.Interface.GameUi?.EscapeMenu?.ToggleHidden(); + var simplifiedEscapeMenuSetting = Globals.Database.SimplifiedEscapeMenu; + + if (!simplifiedEscapeMenuSetting) + { + Interface.Interface.GameUi?.EscapeMenu?.ToggleHidden(); + } + else + { + Interface.Interface.GameUi?.SimplifiedEscapeMenu?.ToggleHidden(); + } } } diff --git a/Intersect.Client/Interface/Game/GameInterface.cs b/Intersect.Client/Interface/Game/GameInterface.cs index e55e36ec3..a59008ce9 100644 --- a/Intersect.Client/Interface/Game/GameInterface.cs +++ b/Intersect.Client/Interface/Game/GameInterface.cs @@ -93,6 +93,7 @@ public GameInterface(Canvas canvas) : base(canvas) { GameCanvas = canvas; EscapeMenu = new EscapeMenu(GameCanvas) {IsHidden = true}; + SimplifiedEscapeMenu = new SimplifiedEscapeMenu(GameCanvas) {IsHidden = true}; AnnouncementWindow = new AnnouncementWindow(GameCanvas) { IsHidden = true }; InitGameGui(); @@ -101,6 +102,8 @@ public GameInterface(Canvas canvas) : base(canvas) public Canvas GameCanvas { get; } public EscapeMenu EscapeMenu { get; } + + public SimplifiedEscapeMenu SimplifiedEscapeMenu { get; } public AnnouncementWindow AnnouncementWindow { get; } diff --git a/Intersect.Client/Interface/Game/Menu.cs b/Intersect.Client/Interface/Game/Menu.cs index 6e1698e89..aae01d550 100644 --- a/Intersect.Client/Interface/Game/Menu.cs +++ b/Intersect.Client/Interface/Game/Menu.cs @@ -37,7 +37,7 @@ public partial class Menu private readonly ImagePanel mMenuBackground; - private readonly Button mMenuButton; + public readonly Button mMenuButton; //Menu Container private readonly ImagePanel mMenuContainer; @@ -357,7 +357,16 @@ public bool HasWindowsOpen() //Input Handlers private static void MenuButtonClicked(Base sender, ClickedEventArgs arguments) { - Interface.GameUi?.EscapeMenu?.ToggleHidden(); + var simplifiedEscapeMenuSetting = Globals.Database.SimplifiedEscapeMenu; + + if (!simplifiedEscapeMenuSetting) + { + Interface.GameUi?.EscapeMenu?.ToggleHidden(); + } + else + { + Interface.GameUi?.SimplifiedEscapeMenu?.ToggleHidden(); + } } private void PartyBtn_Clicked(Base sender, ClickedEventArgs arguments) diff --git a/Intersect.Client/Interface/Game/SimplifiedEscapeMenu.cs b/Intersect.Client/Interface/Game/SimplifiedEscapeMenu.cs new file mode 100644 index 000000000..31ff568fb --- /dev/null +++ b/Intersect.Client/Interface/Game/SimplifiedEscapeMenu.cs @@ -0,0 +1,167 @@ +using Intersect.Client.Core; +using Intersect.Client.Framework.File_Management; +using Intersect.Client.Framework.Gwen; +using Intersect.Client.Framework.Gwen.Control; +using Intersect.Client.Framework.Gwen.Control.EventArguments; +using Intersect.Client.General; +using Intersect.Client.Interface.Shared; +using Intersect.Client.Localization; +using Intersect.Utilities; + +namespace Intersect.Client.Interface.Game; + +public sealed partial class SimplifiedEscapeMenu : Framework.Gwen.Control.Menu +{ + private readonly SettingsWindow _settingsWindow; + private readonly MenuItem _settings; + private readonly MenuItem _character; + private readonly MenuItem _logout; + private readonly MenuItem _exit; + + public SimplifiedEscapeMenu(Canvas gameCanvas) : base(gameCanvas, nameof(SimplifiedEscapeMenu)) + { + IsHidden = true; + IconMarginDisabled = true; + _settingsWindow = new SettingsWindow(gameCanvas, null, null); + + Children.Clear(); + + _settings = AddItem(Strings.EscapeMenu.Settings); + _character = AddItem(Strings.EscapeMenu.CharacterSelect); + _logout = AddItem(Strings.EscapeMenu.Logout); + _exit = AddItem(Strings.EscapeMenu.ExitToDesktop); + + _settings.Clicked += OpenSettingsWindow; + _character.Clicked += LogoutToCharacterSelectSelectClicked; + _logout.Clicked += LogoutToMainToMainMenuClicked; + _exit.Clicked += ExitToDesktopToDesktopClicked; + + LoadJsonUi(GameContentManager.UI.InGame, Graphics.Renderer?.GetResolutionString()); + } + + public override void ToggleHidden() + { + if (!_settingsWindow.IsHidden) + { + return; + } + + if (this.IsHidden) + { + // Position the context menu within the game canvas if near borders. + var menuPosX = Interface.GameUi.GameMenu.mMenuButton.LocalPosToCanvas(new Point(0, 0)).X; + var menuPosY = Interface.GameUi.GameMenu.mMenuButton.LocalPosToCanvas(new Point(0, 0)).Y; + var newX = menuPosX; + var newY = menuPosY + Interface.GameUi.GameMenu.mMenuButton.Height + 6; + + if (newX + Width >= Canvas?.Width) + { + newX = menuPosX - Width + Interface.GameUi.GameMenu.mMenuButton.Width; + } + + if (newY + Height >= Canvas?.Height) + { + newY = menuPosY - Height - 6; + } + + SizeToChildren(); + Open(Pos.None); + SetPosition(newX, newY); + } + else + { + Close(); + } + } + + private void LogoutToCharacterSelectSelectClicked(Base sender, ClickedEventArgs arguments) + { + if (Globals.Me?.CombatTimer > Timing.Global.Milliseconds) + { + _ = new InputBox( + title: Strings.Combat.WarningTitle, + prompt: Strings.Combat.WarningCharacterSelect, + inputType: InputBox.InputType.YesNo, + onSuccess: LogoutToCharacterSelect + ); + } + else + { + LogoutToCharacterSelect(null, null); + } + } + + private void LogoutToMainToMainMenuClicked(Base sender, ClickedEventArgs arguments) + { + if (Globals.Me?.CombatTimer > Timing.Global.Milliseconds) + { + _ = new InputBox( + title: Strings.Combat.WarningTitle, + prompt: Strings.Combat.WarningLogout, + inputType: InputBox.InputType.YesNo, + onSuccess: LogoutToMainMenu + ); + } + else + { + LogoutToMainMenu(null, null); + } + } + + private void ExitToDesktopToDesktopClicked(Base sender, ClickedEventArgs arguments) + { + if (Globals.Me?.CombatTimer > Timing.Global.Milliseconds) + { + _ = new InputBox( + title: Strings.Combat.WarningTitle, + prompt: Strings.Combat.WarningExitDesktop, + inputType: InputBox.InputType.YesNo, + onSuccess: ExitToDesktop + ); + } + else + { + ExitToDesktop(null, null); + } + } + + private void OpenSettingsWindow(object? sender, EventArgs? e) + { + if (!_settingsWindow.IsHidden) + { + return; + } + + _settingsWindow.Show(); + } + + private static void LogoutToCharacterSelect(object? sender, EventArgs? e) + { + if (Globals.Me != null) + { + Globals.Me.CombatTimer = 0; + } + + Main.Logout(true); + } + + private static void LogoutToMainMenu(object? sender, EventArgs? e) + { + if (Globals.Me != null) + { + Globals.Me.CombatTimer = 0; + } + + Main.Logout(false); + } + + private static void ExitToDesktop(object? sender, EventArgs? e) + { + if (Globals.Me != null) + { + Globals.Me.CombatTimer = 0; + } + + Globals.IsRunning = false; + } +} \ No newline at end of file diff --git a/Intersect.Client/Interface/Shared/SettingsWindow.cs b/Intersect.Client/Interface/Shared/SettingsWindow.cs index b7cf28fb8..ff9baa3cf 100644 --- a/Intersect.Client/Interface/Shared/SettingsWindow.cs +++ b/Intersect.Client/Interface/Shared/SettingsWindow.cs @@ -45,6 +45,7 @@ public partial class SettingsWindow : ImagePanel private readonly LabeledCheckBox _showExperienceAsPercentageCheckbox; private readonly LabeledCheckBox _showHealthAsPercentageCheckbox; private readonly LabeledCheckBox _showManaAsPercentageCheckbox; + private readonly LabeledCheckBox _simplifiedEscapeMenu; // Game Settings - Information. private readonly ScrollControl _informationSettings; @@ -180,6 +181,12 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : { Text = Strings.Settings.ShowManaAsPercentage }; + + // Game Settings - Interface: simplified escape menu. + _simplifiedEscapeMenu = new LabeledCheckBox(_interfaceSettings, "SimplifiedEscapeMenu") + { + Text = Strings.Settings.SimplifiedEscapeMenu + }; // Game Settings - Information. _informationSettings = new ScrollControl(_gameSettingsContainer, "InformationSettings"); @@ -730,6 +737,7 @@ public void Show(bool returnToMenu = false) _showHealthAsPercentageCheckbox.IsChecked = Globals.Database.ShowHealthAsPercentage; _showManaAsPercentageCheckbox.IsChecked = Globals.Database.ShowManaAsPercentage; _showExperienceAsPercentageCheckbox.IsChecked = Globals.Database.ShowExperienceAsPercentage; + _simplifiedEscapeMenu.IsChecked = Globals.Database.SimplifiedEscapeMenu; _friendOverheadInfoCheckbox.IsChecked = Globals.Database.FriendOverheadInfo; _guildMemberOverheadInfoCheckbox.IsChecked = Globals.Database.GuildMemberOverheadInfo; _myOverheadInfoCheckbox.IsChecked = Globals.Database.MyOverheadInfo; @@ -910,6 +918,7 @@ private void SettingsApplyBtn_Clicked(Base sender, ClickedEventArgs arguments) Globals.Database.ShowExperienceAsPercentage = _showExperienceAsPercentageCheckbox.IsChecked; Globals.Database.ShowHealthAsPercentage = _showHealthAsPercentageCheckbox.IsChecked; Globals.Database.ShowManaAsPercentage = _showManaAsPercentageCheckbox.IsChecked; + Globals.Database.SimplifiedEscapeMenu = _simplifiedEscapeMenu.IsChecked; Globals.Database.FriendOverheadInfo = _friendOverheadInfoCheckbox.IsChecked; Globals.Database.GuildMemberOverheadInfo = _guildMemberOverheadInfoCheckbox.IsChecked; Globals.Database.MyOverheadInfo = _myOverheadInfoCheckbox.IsChecked; diff --git a/Intersect.Client/Localization/Strings.cs b/Intersect.Client/Localization/Strings.cs index 3b44e1ada..99c146558 100644 --- a/Intersect.Client/Localization/Strings.cs +++ b/Intersect.Client/Localization/Strings.cs @@ -1932,6 +1932,9 @@ public partial struct Settings [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString ShowPlayerOverheadInformation = @"Show players overhead information"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString SimplifiedEscapeMenu = @"Simplified escape menu"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString StickyTarget = @"Sticky Target"; From c3f32e8632438fd643b41cae42bec1858b048476 Mon Sep 17 00:00:00 2001 From: Arufonsu <17498701+Arufonsu@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:54:31 -0300 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E2=99=BB=EF=B8=8F=20Apply=20review?= =?UTF-8?q?=20changes=20(I)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Intersect.Client.Framework/Gwen/Control/Base.cs | 10 +++++++--- Intersect.Client/Core/Input.cs | 6 +++--- Intersect.Client/Interface/Game/Menu.cs | 6 +++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Intersect.Client.Framework/Gwen/Control/Base.cs b/Intersect.Client.Framework/Gwen/Control/Base.cs index 39fbc1a9a..c730eb207 100644 --- a/Intersect.Client.Framework/Gwen/Control/Base.cs +++ b/Intersect.Client.Framework/Gwen/Control/Base.cs @@ -766,9 +766,13 @@ public virtual void Dispose() Animation.Cancel(this); // [Fix]: "InvalidOperationException: Collection was modified (during iteration); enumeration operation may not execute". - // (Creates a copy of the children list to avoid modifying the collection during iteration). - var childrenCopy = new List(mChildren); - childrenCopy.ForEach(child => child.Dispose()); + // (Creates an array copy of the children to avoid modifying the collection during iteration). + var children = mChildren.ToArray(); + foreach (var child in children) + { + child.Dispose(); + } + mChildren?.Clear(); mInnerPanel?.Dispose(); diff --git a/Intersect.Client/Core/Input.cs b/Intersect.Client/Core/Input.cs index 02964631c..2367f4f8d 100644 --- a/Intersect.Client/Core/Input.cs +++ b/Intersect.Client/Core/Input.cs @@ -135,13 +135,13 @@ public static void OnKeyPressed(Keys modifier, Keys key) { var simplifiedEscapeMenuSetting = Globals.Database.SimplifiedEscapeMenu; - if (!simplifiedEscapeMenuSetting) + if (simplifiedEscapeMenuSetting) { - Interface.Interface.GameUi?.EscapeMenu?.ToggleHidden(); + Interface.Interface.GameUi?.SimplifiedEscapeMenu?.ToggleHidden(); } else { - Interface.Interface.GameUi?.SimplifiedEscapeMenu?.ToggleHidden(); + Interface.Interface.GameUi?.EscapeMenu?.ToggleHidden(); } } } diff --git a/Intersect.Client/Interface/Game/Menu.cs b/Intersect.Client/Interface/Game/Menu.cs index aae01d550..48ebc8556 100644 --- a/Intersect.Client/Interface/Game/Menu.cs +++ b/Intersect.Client/Interface/Game/Menu.cs @@ -359,13 +359,13 @@ private static void MenuButtonClicked(Base sender, ClickedEventArgs arguments) { var simplifiedEscapeMenuSetting = Globals.Database.SimplifiedEscapeMenu; - if (!simplifiedEscapeMenuSetting) + if (simplifiedEscapeMenuSetting) { - Interface.GameUi?.EscapeMenu?.ToggleHidden(); + Interface.GameUi?.SimplifiedEscapeMenu?.ToggleHidden(); } else { - Interface.GameUi?.SimplifiedEscapeMenu?.ToggleHidden(); + Interface.GameUi?.EscapeMenu?.ToggleHidden(); } } From b33cc08c375289bece8327ca0a0fb1cc725dcdc7 Mon Sep 17 00:00:00 2001 From: Arufonsu <17498701+Arufonsu@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:15:02 -0300 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E2=99=BB=EF=B8=8F=20Apply=20review?= =?UTF-8?q?=20changes=20(II)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Intersect.Client/Interface/Game/Menu.cs | 6 +++--- .../Interface/Game/SimplifiedEscapeMenu.cs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Intersect.Client/Interface/Game/Menu.cs b/Intersect.Client/Interface/Game/Menu.cs index 48ebc8556..7b41abcc3 100644 --- a/Intersect.Client/Interface/Game/Menu.cs +++ b/Intersect.Client/Interface/Game/Menu.cs @@ -37,7 +37,7 @@ public partial class Menu private readonly ImagePanel mMenuBackground; - public readonly Button mMenuButton; + private readonly Button mMenuButton; //Menu Container private readonly ImagePanel mMenuContainer; @@ -355,13 +355,13 @@ public bool HasWindowsOpen() } //Input Handlers - private static void MenuButtonClicked(Base sender, ClickedEventArgs arguments) + private void MenuButtonClicked(Base sender, ClickedEventArgs arguments) { var simplifiedEscapeMenuSetting = Globals.Database.SimplifiedEscapeMenu; if (simplifiedEscapeMenuSetting) { - Interface.GameUi?.SimplifiedEscapeMenu?.ToggleHidden(); + Interface.GameUi?.SimplifiedEscapeMenu?.ToggleHidden(mMenuButton); } else { diff --git a/Intersect.Client/Interface/Game/SimplifiedEscapeMenu.cs b/Intersect.Client/Interface/Game/SimplifiedEscapeMenu.cs index 31ff568fb..3e315befc 100644 --- a/Intersect.Client/Interface/Game/SimplifiedEscapeMenu.cs +++ b/Intersect.Client/Interface/Game/SimplifiedEscapeMenu.cs @@ -39,9 +39,9 @@ public SimplifiedEscapeMenu(Canvas gameCanvas) : base(gameCanvas, nameof(Simplif LoadJsonUi(GameContentManager.UI.InGame, Graphics.Renderer?.GetResolutionString()); } - public override void ToggleHidden() + public void ToggleHidden(Button? target) { - if (!_settingsWindow.IsHidden) + if (!_settingsWindow.IsHidden || target == null) { return; } @@ -49,14 +49,14 @@ public override void ToggleHidden() if (this.IsHidden) { // Position the context menu within the game canvas if near borders. - var menuPosX = Interface.GameUi.GameMenu.mMenuButton.LocalPosToCanvas(new Point(0, 0)).X; - var menuPosY = Interface.GameUi.GameMenu.mMenuButton.LocalPosToCanvas(new Point(0, 0)).Y; + var menuPosX = target.LocalPosToCanvas(new Point(0, 0)).X; + var menuPosY = target.LocalPosToCanvas(new Point(0, 0)).Y; var newX = menuPosX; - var newY = menuPosY + Interface.GameUi.GameMenu.mMenuButton.Height + 6; + var newY = menuPosY + target.Height + 6; if (newX + Width >= Canvas?.Width) { - newX = menuPosX - Width + Interface.GameUi.GameMenu.mMenuButton.Width; + newX = menuPosX - Width + target.Width; } if (newY + Height >= Canvas?.Height)