From cb89daabe3a692833ec65cdac2ae31122519059a Mon Sep 17 00:00:00 2001 From: devo1929 Date: Wed, 11 May 2022 22:31:38 -0400 Subject: [PATCH 01/19] Quick match --- ClientCore/ClientConfiguration.cs | 2 + ClientCore/Enums/ProgressBarModeEnum.cs | 9 + ClientCore/Exceptions/ClientException.cs | 11 + .../Exceptions/ClientRequestException.cs | 14 + ClientGUI/Parser.cs | 8 +- ClientGUI/XNAClientDropDown.cs | 14 +- ClientGUI/XNAClientProgressBar.cs | 85 ++++ ClientGUI/XNAMessageBox.cs | 7 +- ClientGUI/XNAScrollablePanel.cs | 12 + DXClient.sln | 194 ++++++++++ DXMainClient.Tests/DXMainClient.Tests.csproj | 72 ++++ DXMainClient.Tests/Properties/AssemblyInfo.cs | 35 ++ .../QmRequestResponseConverterTests.cs | 50 +++ .../QmResponses/qm_please_wait_response.json | 5 + .../QmResponses/qm_spawn_response.json | 50 +++ DXMainClient.Tests/packages.config | 4 + DXMainClient/DXGUI/Generic/TopBar.cs | 5 + .../QuickMatch/QuickMatchLobbyFooterPanel.cs | 37 ++ .../QuickMatch/QuickMatchLobbyPanel.cs | 364 ++++++++++++++++++ .../QuickMatch/QuickMatchLoginPanel.cs | 100 +++++ .../QuickMatch/QuickMatchMapList.cs | 151 ++++++++ .../QuickMatch/QuickMatchMapListItem.cs | 164 ++++++++ .../QuickMatch/QuickMatchStatusOverlay.cs | 214 ++++++++++ .../QuickMatch/QuickMatchWindow.cs | 92 +++++ DXMainClient/DXMainClient.csproj | 2 + .../EventArgs/QmStatusMessageEventArgs.cs | 17 + .../Models/Events/IQmOverlayStatusEvent.cs | 5 + .../Events/QmCancelingRequestMatchEvent.cs | 6 + .../Models/Events/QmErrorMessageEvent.cs | 14 + .../QuickMatch/Models/Events/QmEvent.cs | 6 + .../Models/Events/QmLadderMapsEvent.cs | 13 + .../Models/Events/QmLadderStatsEvent.cs | 11 + .../Events/QmLaddersAndUserAccountsEvent.cs | 16 + .../Models/Events/QmLoadingLadderMapsEvent.cs | 5 + .../Events/QmLoadingLadderStatsEvent.cs | 5 + .../QmLoadingLaddersAndUserAccountsEvent.cs | 5 + .../Models/Events/QmLoggingInEvent.cs | 5 + .../QuickMatch/Models/Events/QmLoginEvent.cs | 6 + .../QuickMatch/Models/Events/QmLogoutEvent.cs | 5 + .../Models/Events/QmMasterSideSelected.cs | 11 + .../Models/Events/QmRequestResponseEvent.cs | 11 + .../Models/Events/QmRequestingMatchEvent.cs | 13 + .../Events/QmUserAccountSelectedEvent.cs | 11 + .../CnCNet/QuickMatch/Models/QmAuthData.cs | 9 + .../CnCNet/QuickMatch/Models/QmData.cs | 11 + .../CnCNet/QuickMatch/Models/QmLadder.cs | 53 +++ .../CnCNet/QuickMatch/Models/QmLadderMap.cs | 65 ++++ .../CnCNet/QuickMatch/Models/QmLadderRules.cs | 75 ++++ .../CnCNet/QuickMatch/Models/QmLadderStats.cs | 26 ++ .../QuickMatch/Models/QmLoginRequest.cs | 13 + .../CnCNet/QuickMatch/Models/QmMap.cs | 19 + .../QuickMatch/Models/QmMatchRequest.cs | 57 +++ .../CnCNet/QuickMatch/Models/QmQuitRequest.cs | 12 + .../CnCNet/QuickMatch/Models/QmRequest.cs | 16 + .../Models/QmRequestErrorResponse.cs | 8 + .../Models/QmRequestFatalResponse.cs | 8 + .../Models/QmRequestQuitResponse.cs | 8 + .../QuickMatch/Models/QmRequestResponse.cs | 34 ++ .../Models/QmRequestResponseSpawnSettings.cs | 78 ++++ .../Models/QmRequestSpawnResponse.cs | 10 + .../Models/QmRequestSpawnResponseSpawn.cs | 25 ++ .../QmRequestSpawnResponseSpawnOther.cs | 33 ++ .../Models/QmRequestUpdateResponse.cs | 8 + .../Models/QmRequestWaitResponse.cs | 13 + .../CnCNet/QuickMatch/Models/QmSettings.cs | 46 +++ .../CnCNet/QuickMatch/Models/QmSide.cs | 24 ++ .../CnCNet/QuickMatch/Models/QmUserAccount.cs | 22 ++ .../QuickMatch/Models/QmUserSettings.cs | 105 +++++ .../CnCNet/QuickMatch/QmApiService.cs | 164 ++++++++ .../QuickMatch/QmRequestResponseConverter.cs | 38 ++ .../QmRequestSpawnResponseSpawnConverter.cs | 50 +++ .../CnCNet/QuickMatch/QmRequestTypes.cs | 9 + .../CnCNet/QuickMatch/QmResponseTypes.cs | 12 + .../CnCNet/QuickMatch/QmService.cs | 273 +++++++++++++ .../CnCNet/QuickMatch/QmSettingsService.cs | 111 ++++++ .../CnCNet/QuickMatch/QmStrings.cs | 77 ++++ .../QuickMatch/QmUserSettingsService.cs | 24 ++ DXMainClient/Domain/Multiplayer/MapLoader.cs | 11 + Docs/INISystem.md | 238 ++++++------ build/AfterPublish.targets | 5 +- 80 files changed, 3531 insertions(+), 125 deletions(-) create mode 100644 ClientCore/Enums/ProgressBarModeEnum.cs create mode 100644 ClientCore/Exceptions/ClientException.cs create mode 100644 ClientCore/Exceptions/ClientRequestException.cs create mode 100644 ClientGUI/XNAClientProgressBar.cs create mode 100644 ClientGUI/XNAScrollablePanel.cs create mode 100644 DXMainClient.Tests/DXMainClient.Tests.csproj create mode 100644 DXMainClient.Tests/Properties/AssemblyInfo.cs create mode 100644 DXMainClient.Tests/QmRequestResponseConverterTests.cs create mode 100644 DXMainClient.Tests/TestData/QmResponses/qm_please_wait_response.json create mode 100644 DXMainClient.Tests/TestData/QmResponses/qm_spawn_response.json create mode 100644 DXMainClient.Tests/packages.config create mode 100644 DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyFooterPanel.cs create mode 100644 DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs create mode 100644 DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs create mode 100644 DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs create mode 100644 DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapListItem.cs create mode 100644 DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs create mode 100644 DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/EventArgs/QmStatusMessageEventArgs.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/IQmOverlayStatusEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmCancelingRequestMatchEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmErrorMessageEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderMapsEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderStatsEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLaddersAndUserAccountsEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderMapsEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderStatsEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLaddersAndUserAccountsEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoggingInEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoginEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLogoutEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmMasterSideSelected.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestResponseEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestingMatchEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmUserAccountSelectedEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmAuthData.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmData.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLoginRequest.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMap.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestErrorResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestFatalResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestQuitResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawnOther.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestUpdateResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestWaitResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSide.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserAccount.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestResponseConverter.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestSpawnResponseSpawnConverter.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmStrings.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs diff --git a/ClientCore/ClientConfiguration.cs b/ClientCore/ClientConfiguration.cs index 9f1f58e1b..61831397e 100644 --- a/ClientCore/ClientConfiguration.cs +++ b/ClientCore/ClientConfiguration.cs @@ -254,6 +254,8 @@ public string GetThemePath(string themeName) public string MPMapsIniPath => SafePath.CombineFilePath(clientDefinitionsIni.GetStringValue(SETTINGS, "MPMapsPath", SafePath.CombineFilePath("INI", "MPMaps.ini"))); + public string QuickMatchPath => clientDefinitionsIni.GetStringValue(SETTINGS, "QuickMatchPath", "INI/QuickMatch.ini"); + public string KeyboardINI => clientDefinitionsIni.GetStringValue(SETTINGS, "KeyboardINI", "Keyboard.ini"); public int MinimumIngameWidth => clientDefinitionsIni.GetIntValue(SETTINGS, "MinimumIngameWidth", 640); diff --git a/ClientCore/Enums/ProgressBarModeEnum.cs b/ClientCore/Enums/ProgressBarModeEnum.cs new file mode 100644 index 000000000..b36b60a2e --- /dev/null +++ b/ClientCore/Enums/ProgressBarModeEnum.cs @@ -0,0 +1,9 @@ +using System.ComponentModel; + +namespace ClientCore.Enums; + +public enum ProgressBarModeEnum +{ + Determinate = 0, + Indeterminate = 1 +} \ No newline at end of file diff --git a/ClientCore/Exceptions/ClientException.cs b/ClientCore/Exceptions/ClientException.cs new file mode 100644 index 000000000..dbcaf3b5a --- /dev/null +++ b/ClientCore/Exceptions/ClientException.cs @@ -0,0 +1,11 @@ +using System; + +namespace ClientCore.Exceptions +{ + public class ClientException : Exception + { + public ClientException(string message, Exception innerException = null) : base(message, innerException) + { + } + } +} diff --git a/ClientCore/Exceptions/ClientRequestException.cs b/ClientCore/Exceptions/ClientRequestException.cs new file mode 100644 index 000000000..552e34d66 --- /dev/null +++ b/ClientCore/Exceptions/ClientRequestException.cs @@ -0,0 +1,14 @@ +using System.Net; + +namespace ClientCore.Exceptions +{ + public class ClientRequestException : ClientException + { + public HttpStatusCode? StatusCode { get; } + + public ClientRequestException(string message, HttpStatusCode? statusCode = null) : base(message) + { + StatusCode = statusCode; + } + } +} diff --git a/ClientGUI/Parser.cs b/ClientGUI/Parser.cs index bd23ca5b3..a91a46f0b 100644 --- a/ClientGUI/Parser.cs +++ b/ClientGUI/Parser.cs @@ -24,6 +24,7 @@ using Rampastring.XNAUI.XNAControls; using System; using System.Collections.Generic; +using System.Text.RegularExpressions; namespace ClientGUI { @@ -69,6 +70,9 @@ private XNAControl GetControl(string controlName) if (controlName == primaryControl.Name) return primaryControl; + if (controlName == primaryControl.Parent.Name) + return primaryControl.Parent; + var control = Find(primaryControl.Children, controlName); if (control == null) throw new KeyNotFoundException($"Control '{controlName}' not found while parsing input '{Input}'"); @@ -104,7 +108,7 @@ public void SetPrimaryControl(XNAControl primaryControl) public int GetExprValue(string input, XNAControl parsingControl) { this.parsingControl = parsingControl; - Input = input; + Input = Regex.Replace(input, @"\s", ""); tokenPlace = 0; return GetExprValue(); } @@ -115,8 +119,6 @@ private int GetExprValue() while (true) { - SkipWhitespace(); - if (IsEndOfInput()) return value; diff --git a/ClientGUI/XNAClientDropDown.cs b/ClientGUI/XNAClientDropDown.cs index 18e94686d..270294bbe 100644 --- a/ClientGUI/XNAClientDropDown.cs +++ b/ClientGUI/XNAClientDropDown.cs @@ -9,6 +9,8 @@ public class XNAClientDropDown : XNADropDown { public ToolTip ToolTip { get; set; } + public bool DisabledMouseScroll { get; set; } + public XNAClientDropDown(WindowManager windowManager) : base(windowManager) { } @@ -46,6 +48,16 @@ public override void OnMouseLeftDown() UpdateToolTipBlock(); } + public override void OnMouseScrolled() + { + if (DisabledMouseScroll) + return; + + base.OnMouseScrolled(); + } + + public void Close() => CloseDropDown(); + protected override void CloseDropDown() { base.CloseDropDown(); @@ -60,4 +72,4 @@ protected void UpdateToolTipBlock() ToolTip.Blocked = true; } } -} +} \ No newline at end of file diff --git a/ClientGUI/XNAClientProgressBar.cs b/ClientGUI/XNAClientProgressBar.cs new file mode 100644 index 000000000..587cc2156 --- /dev/null +++ b/ClientGUI/XNAClientProgressBar.cs @@ -0,0 +1,85 @@ +using System; +using ClientCore.Enums; +using Microsoft.Xna.Framework; +using Rampastring.Tools; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace ClientGUI; + +public class XNAClientProgressBar : XNAProgressBar +{ + public int Speed { get; set; } = 4; + + public double WidthRatio { get; set; } = 0.25; + + public ProgressBarModeEnum ProgressBarMode { get; set; } + + private int _left { get; set; } + + public XNAClientProgressBar(WindowManager windowManager) : base(windowManager) + { + } + + public override void Update(GameTime gameTime) + { + _left = (_left + Speed) % Width; + + base.Update(gameTime); + } + + public override void Draw(GameTime gameTime) + { + switch (ProgressBarMode) + { + case ProgressBarModeEnum.Indeterminate: + DrawIndeterminateMode(gameTime); + return; + case ProgressBarModeEnum.Determinate: + default: + base.Draw(gameTime); + return; + } + } + + public void DrawIndeterminateMode(GameTime gameTime) + { + Rectangle wrect = RenderRectangle(); + int filledWidth = (int)(wrect.Width * WidthRatio); + + for (int i = 0; i < BorderWidth; i++) + { + var rect = new Rectangle(wrect.X + i, wrect.Y + i, wrect.Width - i, wrect.Height - i); + + Renderer.DrawRectangle(rect, BorderColor); + } + + Renderer.FillRectangle(new Rectangle(wrect.X + BorderWidth, wrect.Y + BorderWidth, wrect.Width - BorderWidth * 2, wrect.Height - BorderWidth * 2), UnfilledColor); + + if (_left + filledWidth > wrect.Width - BorderWidth * 2) + { + Renderer.FillRectangle(new Rectangle(wrect.X + BorderWidth, wrect.Y + BorderWidth, (_left + filledWidth) - (wrect.Width - (BorderWidth * 2)), wrect.Height - BorderWidth * 2), FilledColor); + } + + Renderer.FillRectangle(new Rectangle(wrect.X + BorderWidth + _left, wrect.Y + BorderWidth, Math.Min(filledWidth, wrect.Width - (BorderWidth * 2) - _left), wrect.Height - BorderWidth * 2), FilledColor); + } + + public override void ParseAttributeFromINI(IniFile iniFile, string key, string value) + { + switch (key) + { + case "WidthRatio": + WidthRatio = double.Parse(value); + return; + case "ProgressBarMode": + ProgressBarMode = (ProgressBarModeEnum)Enum.Parse(typeof(ProgressBarModeEnum), value); + return; + case "Speed": + Speed = int.Parse(value); + return; + default: + base.ParseAttributeFromINI(iniFile, key, value); + return; + } + } +} \ No newline at end of file diff --git a/ClientGUI/XNAMessageBox.cs b/ClientGUI/XNAMessageBox.cs index 06351c588..c4afa1b0d 100644 --- a/ClientGUI/XNAMessageBox.cs +++ b/ClientGUI/XNAMessageBox.cs @@ -262,7 +262,10 @@ private static void MsgBox_OKClicked(XNAMessageBox messageBox) /// The caption of the message box. /// The description in the message box. /// The XNAMessageBox instance that is created. - public static XNAMessageBox ShowYesNoDialog(WindowManager windowManager, string caption, string description) + public static XNAMessageBox ShowYesNoDialog(WindowManager windowManager, string caption, string description) + => ShowYesNoDialog(windowManager, caption, description, null); + + public static XNAMessageBox ShowYesNoDialog(WindowManager windowManager, string caption, string description, Action yesAction) { var panel = new DarkeningPanel(windowManager); windowManager.AddAndInitializeControl(panel); @@ -274,6 +277,8 @@ public static XNAMessageBox ShowYesNoDialog(WindowManager windowManager, string panel.AddChild(msgBox); msgBox.YesClickedAction = MsgBox_YesClicked; + if (yesAction != null) + msgBox.YesClickedAction += yesAction; msgBox.NoClickedAction = MsgBox_NoClicked; return msgBox; diff --git a/ClientGUI/XNAScrollablePanel.cs b/ClientGUI/XNAScrollablePanel.cs new file mode 100644 index 000000000..d9dc190ff --- /dev/null +++ b/ClientGUI/XNAScrollablePanel.cs @@ -0,0 +1,12 @@ +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace ClientGUI; + +public class XNAScrollablePanel : XNAPanel +{ + public XNAScrollablePanel(WindowManager windowManager) : base(windowManager) + { + DrawMode = ControlDrawMode.UNIQUE_RENDER_TARGET; + } +} \ No newline at end of file diff --git a/DXClient.sln b/DXClient.sln index 303801c36..3d8b1da5e 100644 --- a/DXClient.sln +++ b/DXClient.sln @@ -24,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution build\WinForms.props = build\WinForms.props EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DXMainClient.Tests", "DXMainClient.Tests\DXMainClient.Tests.csproj", "{A29B49BC-82DA-4CC7-948E-8497717EC1D8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AresUniversalGLDebug|Any CPU = AresUniversalGLDebug|Any CPU @@ -1024,6 +1026,198 @@ Global {2CD114E3-C30F-4A95-9B4A-455C2C8F82E5}.YRWindowsXNARelease|x64.ActiveCfg = YRWindowsXNARelease|x64 {2CD114E3-C30F-4A95-9B4A-455C2C8F82E5}.YRWindowsXNARelease|x86.ActiveCfg = YRWindowsXNARelease|x86 {2CD114E3-C30F-4A95-9B4A-455C2C8F82E5}.YRWindowsXNARelease|x86.Build.0 = YRWindowsXNARelease|x86 + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLDebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLDebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLDebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLDebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLDebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLDebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLDebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLRelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLRelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLRelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLRelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLRelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLRelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLRelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresUniversalGLRelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXDebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXDebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXDebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXDebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXDebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXDebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXDebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXRelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXRelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXRelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXRelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXRelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXRelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXRelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsDXRelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLDebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLDebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLDebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLDebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLDebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLDebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLDebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLRelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLRelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLRelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLRelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLRelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLRelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLRelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsGLRelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNADebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNADebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNADebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNADebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNADebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNADebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNADebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNADebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNARelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNARelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNARelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNARelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNARelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNARelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNARelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.AresWindowsXNARelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLDebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLDebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLDebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLDebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLDebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLDebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLDebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLRelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLRelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLRelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLRelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLRelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLRelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLRelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSUniversalGLRelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXDebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXDebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXDebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXDebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXDebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXDebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXDebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXRelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXRelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXRelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXRelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXRelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXRelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXRelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsDXRelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLDebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLDebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLDebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLDebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLDebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLDebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLDebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLRelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLRelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLRelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLRelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLRelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLRelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLRelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsGLRelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNADebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNADebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNADebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNADebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNADebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNADebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNADebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNADebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNARelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNARelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNARelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNARelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNARelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNARelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNARelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.TSWindowsXNARelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLDebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLDebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLDebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLDebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLDebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLDebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLDebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLRelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLRelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLRelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLRelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLRelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLRelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLRelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRUniversalGLRelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXDebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXDebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXDebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXDebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXDebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXDebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXDebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXRelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXRelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXRelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXRelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXRelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXRelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXRelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsDXRelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLDebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLDebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLDebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLDebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLDebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLDebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLDebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLDebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLRelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLRelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLRelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLRelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLRelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLRelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLRelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsGLRelease|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNADebug|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNADebug|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNADebug|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNADebug|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNADebug|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNADebug|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNADebug|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNADebug|x86.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNARelease|Any CPU.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNARelease|Any CPU.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNARelease|ARM64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNARelease|ARM64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNARelease|x64.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNARelease|x64.Build.0 = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNARelease|x86.ActiveCfg = Debug|Any CPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8}.YRWindowsXNARelease|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DXMainClient.Tests/DXMainClient.Tests.csproj b/DXMainClient.Tests/DXMainClient.Tests.csproj new file mode 100644 index 000000000..7c4c0366c --- /dev/null +++ b/DXMainClient.Tests/DXMainClient.Tests.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {A29B49BC-82DA-4CC7-948E-8497717EC1D8} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + DXMainClient.Tests + DXMainClient.Tests + v4.8 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + {97458c1e-2e6c-4c5c-93c7-16a6712802e9} + DXMainClient + + + + + + diff --git a/DXMainClient.Tests/Properties/AssemblyInfo.cs b/DXMainClient.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..aecc474a5 --- /dev/null +++ b/DXMainClient.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DXMainClient.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DXMainClient.Tests")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("A29B49BC-82DA-4CC7-948E-8497717EC1D8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/DXMainClient.Tests/QmRequestResponseConverterTests.cs b/DXMainClient.Tests/QmRequestResponseConverterTests.cs new file mode 100644 index 000000000..3e023873a --- /dev/null +++ b/DXMainClient.Tests/QmRequestResponseConverterTests.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using Newtonsoft.Json; +using NUnit.Framework; + +namespace DXMainClient.Tests +{ + [TestFixture] + public class QmRequestResponseConverterTests + { + [Test] + public void Test_SpawnRead() + { + string json = GetSpawnJson(); + + QmRequestResponse response = JsonConvert.DeserializeObject(json); + + Assert.IsInstanceOf(response); + } + + [Test] + public void Test_SpawnWrite() + { + string jsonIn = GetSpawnJson(); + + QmRequestResponse response = JsonConvert.DeserializeObject(jsonIn); + + string jsonOut = JsonConvert.SerializeObject(response); + + Console.WriteLine(jsonOut); + } + + [Test] + public void Test_PleaseWaitRead() + { + string json = GetPleaseWaitJson(); + + QmRequestResponse response = JsonConvert.DeserializeObject(json); + + Assert.IsInstanceOf(response); + } + + private static string GetSpawnJson() => GetJson("qm_spawn_response"); + + private static string GetPleaseWaitJson() => GetJson("qm_please_wait_response"); + + private static string GetJson(string filename) => File.ReadAllText($"TestData/QmResponses/{filename}.json"); + } +} \ No newline at end of file diff --git a/DXMainClient.Tests/TestData/QmResponses/qm_please_wait_response.json b/DXMainClient.Tests/TestData/QmResponses/qm_please_wait_response.json new file mode 100644 index 000000000..36f913e5f --- /dev/null +++ b/DXMainClient.Tests/TestData/QmResponses/qm_please_wait_response.json @@ -0,0 +1,5 @@ +{ + "type": "please wait", + "checkback": 10, + "no_sooner_than": 5 +} diff --git a/DXMainClient.Tests/TestData/QmResponses/qm_spawn_response.json b/DXMainClient.Tests/TestData/QmResponses/qm_spawn_response.json new file mode 100644 index 000000000..8bf7b37fa --- /dev/null +++ b/DXMainClient.Tests/TestData/QmResponses/qm_spawn_response.json @@ -0,0 +1,50 @@ +{ + "type": "spawn", + "gameID": 1001, + "spawn": { + "SpawnLocations": { + "Multi2": -1, + "Multi1": -1 + }, + "Settings": { + "UIMapName": "Dawn of Peril", + "MapHash": "3e1a6efe2370f1b26419ece66672b52bf98dc131", + "Seed": 101, + "GameID": 101, + "WOLGameID": 101, + "Host": "No", + "IsSpectator": "No", + "Name": "TestMe", + "Port": 1000, + "Side": 0, + "Color": 1, + "GameSpeed": "0", + "Credits": "10000", + "UnitCount": "0", + "Superweapons": "Yes", + "Tournament": "1", + "ShortGame": "Yes", + "Bases": "Yes", + "MCVRedeploy": "Yes", + "MultipleFactory": "Yes", + "Crates": "No", + "GameMode": "Battle", + "FrameSendRate": "4", + "DisableSWvsYuri": "Yes" + }, + "Other1": { + "Name": "TestOther1", + "Side": 0, + "Color": 0, + "Ip": "12.34.56.78", + "Port": 1000, + "IPv6": "", + "PortV6": 0, + "LanIP": "", + "LanPort": 1000 + } + }, + "client": { + "show_map_preview": 1 + } +} diff --git a/DXMainClient.Tests/packages.config b/DXMainClient.Tests/packages.config new file mode 100644 index 000000000..c108d442f --- /dev/null +++ b/DXMainClient.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/DXMainClient/DXGUI/Generic/TopBar.cs b/DXMainClient/DXGUI/Generic/TopBar.cs index 2a309493e..8e6ad4e8d 100644 --- a/DXMainClient/DXGUI/Generic/TopBar.cs +++ b/DXMainClient/DXGUI/Generic/TopBar.cs @@ -10,6 +10,7 @@ using ClientCore; using System.Threading; using DTAClient.Domain.Multiplayer.CnCNet; +using DTAClient.DXGUI.Multiplayer.QuickMatch; using DTAClient.Online.EventArguments; using DTAConfig; using Localization; @@ -53,6 +54,7 @@ PrivateMessageHandler privateMessageHandler private ISwitchable privateMessageSwitch; private OptionsWindow optionsWindow; + private QuickMatchWindow quickMatchWindow; private XNAClientButton btnMainButton; private XNAClientButton btnCnCNetLobby; @@ -107,6 +109,8 @@ public void SetOptionsWindow(OptionsWindow optionsWindow) optionsWindow.EnabledChanged += OptionsWindow_EnabledChanged; } + public void SetQuickMatchWindow(QuickMatchWindow quickMatchWindow) => this.quickMatchWindow = quickMatchWindow; + private void OptionsWindow_EnabledChanged(object sender, EventArgs e) { if (!lanMode) @@ -324,6 +328,7 @@ private void BtnMainButton_LeftClick(object sender, EventArgs e) LastSwitchType = SwitchType.PRIMARY; cncnetLobbySwitch.SwitchOff(); privateMessageSwitch.SwitchOff(); + quickMatchWindow.Disable(); primarySwitches[primarySwitches.Count - 1].SwitchOn(); // HACK warning diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyFooterPanel.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyFooterPanel.cs new file mode 100644 index 000000000..05c30a199 --- /dev/null +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyFooterPanel.cs @@ -0,0 +1,37 @@ +using System; +using ClientGUI; +using Rampastring.XNAUI; + +namespace DTAClient.DXGUI.Multiplayer.QuickMatch; + +public class QuickMatchLobbyFooterPanel : INItializableWindow +{ + private XNAClientButton btnQuickMatch; + private XNAClientButton btnLogout; + private XNAClientButton btnExit; + + public EventHandler LogoutEvent; + + public EventHandler ExitEvent; + + public EventHandler QuickMatchEvent; + + public QuickMatchLobbyFooterPanel(WindowManager windowManager) : base(windowManager) + { + } + + public override void Initialize() + { + IniNameOverride = nameof(QuickMatchLobbyFooterPanel); + base.Initialize(); + + btnLogout = FindChild(nameof(btnLogout)); + btnLogout.LeftClick += (_, _) => LogoutEvent?.Invoke(this, null); + + btnExit = FindChild(nameof(btnExit)); + btnExit.LeftClick += (_, _) => ExitEvent?.Invoke(this, null); + + btnQuickMatch = FindChild(nameof(btnQuickMatch)); + btnQuickMatch.LeftClick += (_, _) => QuickMatchEvent?.Invoke(this, null); + } +} \ No newline at end of file diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs new file mode 100644 index 000000000..3ccd0df0a --- /dev/null +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ClientCore.Exceptions; +using ClientGUI; +using DTAClient.Domain.Multiplayer; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using Localization; +using Rampastring.Tools; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; +using Timer = System.Timers.Timer; + +namespace DTAClient.DXGUI.Multiplayer.QuickMatch +{ + public class QuickMatchLobbyPanel : INItializableWindow + { + private readonly QmService qmService; + private readonly MapLoader mapLoader; + + public event EventHandler Exit; + + public event EventHandler LogoutEvent; + + private QuickMatchMapList mapList; + private QuickMatchLobbyFooterPanel footerPanel; + private XNAClientDropDown ddUserAccounts; + private XNAClientDropDown ddNicknames; + private XNAClientDropDown ddSides; + private XNAPanel mapPreviewBox; + private XNAPanel settingsPanel; + private XNAClientButton btnMap; + private XNAClientButton btnSettings; + private XNALabel lblStats; + + private readonly EnhancedSoundEffect matchFoundSoundEffect; + private readonly QmSettings qmSettings; + + public QuickMatchLobbyPanel(WindowManager windowManager) : base(windowManager) + { + qmService = QmService.GetInstance(); + qmService.QmEvent += HandleQmEvent; + + mapLoader = MapLoader.GetInstance(); + + qmSettings = QmSettingsService.GetInstance().GetSettings(); + matchFoundSoundEffect = new EnhancedSoundEffect(qmSettings.MatchFoundSoundFile); + + IniNameOverride = nameof(QuickMatchLobbyPanel); + } + + public override void Initialize() + { + base.Initialize(); + + mapList = FindChild(nameof(mapList)); + mapList.MapSelectedEvent += HandleMapSelectedEventEvent; + + footerPanel = FindChild(nameof(footerPanel)); + footerPanel.ExitEvent += (sender, args) => Exit?.Invoke(sender, args); + footerPanel.LogoutEvent += BtnLogout_LeftClick; + footerPanel.QuickMatchEvent += BtnQuickMatch_LeftClick; + + ddUserAccounts = FindChild(nameof(ddUserAccounts)); + ddUserAccounts.SelectedIndexChanged += HandleUserAccountSelected; + + ddNicknames = FindChild(nameof(ddNicknames)); + + ddSides = FindChild(nameof(ddSides)); + ddSides.SelectedIndexChanged += HandleSideSelected; + ddSides.DisabledMouseScroll = true; + + mapPreviewBox = FindChild(nameof(mapPreviewBox)); + mapPreviewBox.PanelBackgroundDrawMode = PanelBackgroundImageDrawMode.CENTERED; + + settingsPanel = FindChild(nameof(settingsPanel)); + + btnMap = FindChild(nameof(btnMap)); + btnMap.LeftClick += (_, _) => EnableRightPanel(mapPreviewBox); + + btnSettings = FindChild(nameof(btnSettings)); + btnSettings.LeftClick += (_, _) => EnableRightPanel(settingsPanel); + + lblStats = FindChild(nameof(lblStats)); + + EnabledChanged += EnabledChangedEvent; + } + + private void HandleQmEvent(object sender, QmEvent qmEvent) + { + switch (qmEvent) + { + case QmLadderMapsEvent e: + HandleLadderMapsEvent(e.LadderMaps); + return; + case QmLadderStatsEvent e: + HandleLadderStatsEvent(e.LadderStats); + return; + case QmLoadingLadderStatsEvent: + HandleLoadingLadderStatsEvent(); + return; + case QmLaddersAndUserAccountsEvent e: + HandleLoadLadderAndUserAccountsEvent(e); + return; + case QmUserAccountSelectedEvent e: + HandleUserAccountSelected(e.UserAccount); + return; + case QmLoginEvent: + Enable(); + return; + case QmLogoutEvent: + HandleLogoutEvent(); + return; + } + } + + private void EnableRightPanel(XNAControl control) + { + foreach (XNAControl parentChild in control.Parent.Children) + parentChild.Disable(); + + control.Enable(); + } + + private void EnabledChangedEvent(object sender, EventArgs e) + { + if (!Enabled) + return; + + LoadLaddersAndUserAccountsAsync(); + } + + private void LoadLaddersAndUserAccountsAsync() + { + qmService.LoadLaddersAndUserAccountsAsync(); + } + + private void BtnQuickMatch_LeftClick(object sender, EventArgs eventArgs) + => RequestQuickMatch(); + + private void RequestQuickMatch() + { + QmRequest matchRequest = CreateMatchRequest(); + if (matchRequest == null) + { + XNAMessageBox.Show(WindowManager, QmStrings.GenericErrorTitle, QmStrings.UnableToCreateMatchRequestDataError); + return; + } + + qmService.RequestMatchAsync(); + } + + private void HandleQuickMatchSpawnResponse(QmRequestResponse qmRequestResponse) + { + XNAMessageBox.Show(WindowManager, QmStrings.GenericErrorTitle, "qm spawn"); + } + + private QmMatchRequest CreateMatchRequest() + { + QmUserAccount userAccount = GetSelectedUserAccount(); + if (userAccount == null) + { + XNAMessageBox.Show(WindowManager, QmStrings.GenericErrorTitle, QmStrings.NoLadderSelectedError); + return null; + } + + QmSide side = GetSelectedSide(); + if (side == null) + { + XNAMessageBox.Show(WindowManager, QmStrings.GenericErrorTitle, QmStrings.NoSideSelectedError); + return null; + } + + if (side.IsRandom) + side = GetRandomSide(); + + return new QmMatchRequest { Ladder = userAccount.Ladder.Abbreviation, PlayerName = userAccount.Username, Side = side.LocalId }; + } + + private QmSide GetRandomSide() + { + int randomIndex = new Random().Next(0, ddSides.Items.Count - 2); // account for "Random" + return ddSides.Items.Select(i => i.Tag as QmSide).ElementAt(randomIndex); + } + + private QmSide GetSelectedSide() + => ddSides.SelectedItem?.Tag as QmSide; + + private QmUserAccount GetSelectedUserAccount() + => ddUserAccounts.SelectedItem?.Tag as QmUserAccount; + + private void BtnLogout_LeftClick(object sender, EventArgs eventArgs) + { + XNAMessageBox.ShowYesNoDialog(WindowManager, QmStrings.ConfirmationCaption, QmStrings.LogoutConfirmation, box => + { + qmService.Logout(); + LogoutEvent?.Invoke(this, null); + }); + } + + private void UserAccountsUpdated(IEnumerable userAccounts) + { + ddUserAccounts.Items.Clear(); + foreach (QmUserAccount userAccount in userAccounts) + { + ddUserAccounts.AddItem(new XNADropDownItem() { Text = userAccount.Ladder.Name, Tag = userAccount }); + } + + if (ddUserAccounts.Items.Count == 0) + return; + + string cachedLadder = qmService.GetCachedLadder(); + if (!string.IsNullOrEmpty(cachedLadder)) + ddUserAccounts.SelectedIndex = ddUserAccounts.Items.FindIndex(i => (i.Tag as QmUserAccount)?.Ladder.Abbreviation == cachedLadder); + + if (ddUserAccounts.SelectedIndex < 0) + ddUserAccounts.SelectedIndex = 0; + } + + /// + /// Called when the QM service has finished the login process + /// + /// + /// + private void HandleLoadLadderAndUserAccountsEvent(QmLaddersAndUserAccountsEvent e) + => UserAccountsUpdated(e.UserAccounts); + + private void HandleSideSelected(object sender, EventArgs eventArgs) + => qmService.SetMasterSide(GetSelectedSide()); + + /// + /// Called when the user has selected a UserAccount from the drop down + /// + /// + /// + private void HandleUserAccountSelected(object sender, EventArgs eventArgs) + { + if (ddUserAccounts.SelectedItem?.Tag is not QmUserAccount selectedUserAccount) + return; + + UpdateNickames(selectedUserAccount); + UpdateSides(selectedUserAccount); + mapList.Clear(); + qmService.SetUserAccount(selectedUserAccount); + } + + private void LoadLadderMapsAsync(QmLadder ladder) => qmService.LoadLadderMapsForAbbrAsync(ladder.Abbreviation); + + private void LoadLadderStatsAsync(QmLadder ladder) => qmService.LoadLadderStatsForAbbrAsync(ladder.Abbreviation); + + /// + /// Update the nicknames drop down + /// + /// + private void UpdateNickames(QmUserAccount selectedUserAccount) + { + ddNicknames.Items.Clear(); + + ddNicknames.AddItem(new XNADropDownItem() { Text = selectedUserAccount.Username, Tag = selectedUserAccount }); + + ddNicknames.SelectedIndex = 0; + } + + /// + /// Update the top Sides dropdown + /// + /// + private void UpdateSides(QmUserAccount selectedUserAccount) + { + ddSides.Items.Clear(); + + QmLadder ladder = qmService.GetLadderForId(selectedUserAccount.LadderId); + IEnumerable sides = ladder.Sides.Append(QmSide.CreateRandomSide()); + + foreach (QmSide side in sides) + { + ddSides.AddItem(new XNADropDownItem { Text = side.Name, Tag = side }); + } + + if (ddSides.Items.Count > 0) + ddSides.SelectedIndex = 0; + } + + /// + /// Called when the QM service has loaded new ladder maps for the ladder selected + /// + /// + /// + private void HandleLadderMapsEvent(IEnumerable maps) + { + mapList.Clear(); + var ladderMaps = maps?.ToList() ?? new List(); + if (!ladderMaps.Any()) + return; + + if (ddUserAccounts.SelectedItem?.Tag is not QmUserAccount selectedUserAccount) + return; + + var ladder = qmService.GetLadderForId(selectedUserAccount.LadderId); + + mapList.AddItems(ladderMaps.Select(ladderMap => new QuickMatchMapListItem(WindowManager, ladderMap, ladder))); + } + + /// + /// Called when the QM service has loaded new ladder maps for the ladder selected + /// + private void HandleLadderStatsEvent(QmLadderStats stats) + => lblStats.Text = stats == null ? "No stats found..." : $"Players waiting: {stats.QueuedPlayerCount}, Recent matches: {stats.RecentMatchCount}"; + + /// + /// Called when the QM service has loaded new ladder maps for the ladder selected + /// + private void HandleLoadingLadderStatsEvent() => lblStats.Text = QmStrings.LoadingStats; + + private void HandleUserAccountSelected(QmUserAccount userAccount) + { + mapPreviewBox.BackgroundTexture = null; + LoadLadderMapsAsync(userAccount.Ladder); + LoadLadderStatsAsync(userAccount.Ladder); + } + + private void HandleMatchedEvent() + { + } + + /// + /// Called when the user selects a map in the list + /// + /// + /// + private void HandleMapSelectedEventEvent(object sender, QmLadderMap qmLadderMap) + { + if (qmLadderMap == null) + return; + + Map map = mapLoader.GetMapForSHA(qmLadderMap.Hash); + + mapPreviewBox.BackgroundTexture = map?.LoadPreviewTexture(); + EnableRightPanel(mapPreviewBox); + } + + private void HandleLogoutEvent() + { + Disable(); + ClearUISelections(); + } + + public void ClearUISelections() + { + ddNicknames.Items.Clear(); + ddNicknames.SelectedIndex = -1; + ddSides.Items.Clear(); + ddSides.SelectedIndex = -1; + ddUserAccounts.Items.Clear(); + ddUserAccounts.SelectedIndex = -1; + mapList.Clear(); + mapPreviewBox.BackgroundTexture = null; + } + } +} \ No newline at end of file diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs new file mode 100644 index 000000000..99ca5f594 --- /dev/null +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs @@ -0,0 +1,100 @@ +using System; +using System.Threading.Tasks; +using ClientCore.Exceptions; +using ClientGUI; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace DTAClient.DXGUI.Multiplayer.QuickMatch +{ + public class QuickMatchLoginPanel : INItializableWindow + { + public event EventHandler Exit; + private const string LoginErrorTitle = "Login Error"; + private readonly QmService qmService; + + private XNATextBox tbEmail; + private XNAPasswordBox tbPassword; + private bool loginInitialized; + + public event EventHandler LoginEvent; + + public QuickMatchLoginPanel(WindowManager windowManager) : base(windowManager) + { + qmService = QmService.GetInstance(); + qmService.QmEvent += HandleQmEvent; + IniNameOverride = nameof(QuickMatchLoginPanel); + } + + public override void Initialize() + { + base.Initialize(); + + XNAClientButton btnLogin; + btnLogin = FindChild(nameof(btnLogin)); + btnLogin.LeftClick += BtnLogin_LeftClick; + + XNAClientButton btnCancel; + btnCancel = FindChild(nameof(btnCancel)); + btnCancel.LeftClick += (_, _) => Exit?.Invoke(this, null); + + tbEmail = FindChild(nameof(tbEmail)); + tbEmail.Text = qmService.GetCachedEmail() ?? string.Empty; + + tbPassword = FindChild(nameof(tbPassword)); + + EnabledChanged += InitLogin; + } + + private void HandleQmEvent(object sender, QmEvent qmEvent) + { + switch (qmEvent) + { + case QmLoginEvent: + Disable(); + return; + case QmLogoutEvent: + Enable(); + return; + } + } + + public void InitLogin(object sender, EventArgs eventArgs) + { + if (!Enabled || loginInitialized) + return; + + if (qmService.IsLoggedIn()) + qmService.RefreshAsync(); + + loginInitialized = true; + } + + private void BtnLogin_LeftClick(object sender, EventArgs eventArgs) + { + if (!ValidateForm()) + return; + + qmService.LoginAsync(tbEmail.Text, tbPassword.Password); + } + + private bool ValidateForm() + { + if (string.IsNullOrEmpty(tbEmail.Text)) + { + XNAMessageBox.Show(WindowManager, "No Email specified", LoginErrorTitle); + return false; + } + + if (string.IsNullOrEmpty(tbPassword.Text)) + { + XNAMessageBox.Show(WindowManager, "No Password specified", LoginErrorTitle); + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs new file mode 100644 index 000000000..a263aec6b --- /dev/null +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ClientGUI; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using Microsoft.Xna.Framework; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace DTAClient.DXGUI.Multiplayer.QuickMatch +{ + public class QuickMatchMapList : INItializableWindow + { + private const int MouseScrollRate = 6; + public const int ItemHeight = 22; + public event EventHandler MapSelectedEvent; + + private XNALabel lblVeto; + private XNALabel lblSides; + private XNALabel lblMaps; + private XNAScrollablePanel mapListPanel; + private readonly QmService qmService; + public XNAScrollBar scrollBar { get; private set; } + + private QmSide masterQmSide { get; set; } + + public int VetoX => lblVeto?.X ?? 0; + public int VetoWidth => lblVeto?.Width ?? 0; + public int SidesX => lblSides?.X ?? 0; + public int SidesWidth => lblSides?.Width ?? 0; + public int MapsX => lblMaps?.X ?? 0; + public int MapsWidth => lblMaps?.Width ?? 0; + + public QuickMatchMapList(WindowManager windowManager) : base(windowManager) + { + qmService = QmService.GetInstance(); + } + + public override void Initialize() + { + base.Initialize(); + + lblVeto = FindChild(nameof(lblVeto)); + lblSides = FindChild(nameof(lblSides)); + lblMaps = FindChild(nameof(lblMaps)); + scrollBar = FindChild(nameof(scrollBar)); + mapListPanel = FindChild(nameof(mapListPanel)); + + MouseScrolled += OnMouseScrolled; + + qmService.QmEvent += HandleQmEvent; + } + + public void AddItems(IEnumerable listItems) + { + foreach (QuickMatchMapListItem quickMatchMapListItem in listItems) + AddItem(quickMatchMapListItem); + } + + public override void Draw(GameTime gameTime) + { + var children = MapItemChildren.ToList(); + scrollBar.Length = children.Count * ItemHeight; + scrollBar.DisplayedPixelCount = mapListPanel.Height - 4; + scrollBar.Refresh(); + for (int i = 0; i < children.Count; i++) + children[i].ClientRectangle = new Rectangle(0, (i * ItemHeight) - scrollBar.ViewTop, Width - scrollBar.ScrollWidth, ItemHeight); + + base.Draw(gameTime); + } + + public void SetMasterSide(QmSide qmSide) + { + masterQmSide = qmSide; + foreach (QuickMatchMapListItem quickMatchMapListItem in MapItemChildren) + quickMatchMapListItem.SetMasterSide(masterQmSide); + } + + private void HandleQmEvent(object sender, QmEvent qmEvent) + { + switch (qmEvent) + { + case QmMasterSideSelected e: + SetMasterSide(e.Side); + return; + } + } + + private void OnMouseScrolled(object sender, EventArgs e) + { + int viewTop = GetNewScrollBarViewTop(); + if (viewTop == scrollBar.ViewTop) + return; + + scrollBar.ViewTop = viewTop; + + foreach (QuickMatchMapListItem quickMatchMapListItem in MapItemChildren.ToList()) + { + quickMatchMapListItem.CloseDropDowns(); + } + } + + private void AddItem(QuickMatchMapListItem listItem) + { + listItem.LeftClickMap += MapItem_LeftClick; + listItem.SideSelected += (_, _) => MapSideSelected(listItem); + listItem.SetParentList(this); + listItem.SetMasterSide(masterQmSide); + mapListPanel.AddChild(listItem); + } + + private void MapSideSelected(QuickMatchMapListItem listItem) + { + + } + + private int GetNewScrollBarViewTop() + { + int scrollWheelValue = Cursor.ScrollWheelValue; + int viewTop = scrollBar.ViewTop - (scrollWheelValue * MouseScrollRate); + int maxViewTop = scrollBar.Length - scrollBar.DisplayedPixelCount; + + if (viewTop < 0) + viewTop = 0; + else if (viewTop > maxViewTop) + viewTop = maxViewTop; + + return viewTop; + } + + private void MapItem_LeftClick(object sender, EventArgs eventArgs) + { + var selectedItem = sender as QuickMatchMapListItem; + foreach (QuickMatchMapListItem quickMatchMapItem in MapItemChildren) + quickMatchMapItem.Selected = quickMatchMapItem == selectedItem; + + MapSelectedEvent?.Invoke(this, selectedItem?.LadderMap); + } + + public void Clear() + { + foreach (QuickMatchMapListItem child in MapItemChildren.ToList()) + mapListPanel.RemoveChild(child); + } + + private IEnumerable MapItemChildren + => mapListPanel.Children.Select(c => c as QuickMatchMapListItem).Where(i => i != null); + } +} \ No newline at end of file diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapListItem.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapListItem.cs new file mode 100644 index 000000000..0d789e4b6 --- /dev/null +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapListItem.cs @@ -0,0 +1,164 @@ +using System; +using System.Linq; +using ClientGUI; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using Microsoft.Xna.Framework; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace DTAClient.DXGUI.Multiplayer.QuickMatch +{ + public class QuickMatchMapListItem : XNAPanel + { + public event EventHandler LeftClickMap; + + public event EventHandler SideSelected; + + public readonly QmLadderMap LadderMap; + private readonly QmLadder ladder; + private XNAClientCheckBox cbVeto; + private XNAClientDropDown ddSide; + private XNAPanel panelMap; + private XNALabel lblMap; + private Color defaultTextColor; + private QmSide masterQmSide; + + private XNAPanel topBorder; + private XNAPanel bottomBorder; + private XNAPanel rightBorder; + + private QuickMatchMapList ParentList; + + private bool selected; + + public int OpenedDownWindowBottom => GetWindowRectangle().Bottom + (ddSide.ItemHeight * ddSide.Items.Count); + + public bool Selected + { + get => selected; + set + { + selected = value; + panelMap.BackgroundTexture = selected ? AssetLoader.CreateTexture(new Color(255, 0, 0), 1, 1) : null; + } + } + + public QuickMatchMapListItem(WindowManager windowManager, QmLadderMap ladderMap, QmLadder ladder) : base(windowManager) + { + LadderMap = ladderMap; + this.ladder = ladder; + } + + public override void Initialize() + { + base.Initialize(); + DrawBorders = false; + + topBorder = new XNAPanel(WindowManager); + topBorder.DrawBorders = true; + AddChild(topBorder); + + bottomBorder = new XNAPanel(WindowManager); + bottomBorder.DrawBorders = true; + AddChild(bottomBorder); + + rightBorder = new XNAPanel(WindowManager); + rightBorder.DrawBorders = true; + AddChild(rightBorder); + + cbVeto = new XNAClientCheckBox(WindowManager); + cbVeto.CheckedChanged += CbVeto_CheckChanged; + + ddSide = new XNAClientDropDown(WindowManager); + ddSide.DisabledMouseScroll = true; + defaultTextColor = ddSide.TextColor; + ddSide.SelectedIndexChanged += Side_Selected; + AddChild(ddSide); + + panelMap = new XNAPanel(WindowManager); + panelMap.LeftClick += Map_LeftClicked; + panelMap.DrawBorders = false; + AddChild(panelMap); + + lblMap = new XNALabel(WindowManager); + lblMap.LeftClick += Map_LeftClicked; + lblMap.ClientRectangle = new Rectangle(4, 2, panelMap.Width, panelMap.Height); + panelMap.AddChild(lblMap); + AddChild(cbVeto); + + InitUI(); + } + + public void SetMasterSide(QmSide qmSide) + { + masterQmSide = qmSide; + + if (!(ddSide?.Items.Any() ?? false)) + return; + + ddSide.SelectedIndex = masterQmSide == null ? 0 : ddSide.SelectedIndex = ddSide.Items.FindIndex(i => ((QmSide)i.Tag).Name == qmSide.Name); + } + + public void SetParentList(QuickMatchMapList parentList) => ParentList = parentList; + + public override void Draw(GameTime gameTime) + { + ddSide.OpenUp = OpenedDownWindowBottom > ParentList.scrollBar.GetWindowRectangle().Bottom; + + base.Draw(gameTime); + } + + private void CbVeto_CheckChanged(object sender, EventArgs e) + { + ddSide.TextColor = cbVeto.Checked ? UISettings.ActiveSettings.DisabledItemColor : defaultTextColor; + lblMap.TextColor = cbVeto.Checked ? UISettings.ActiveSettings.DisabledItemColor : defaultTextColor; + ddSide.AllowDropDown = !cbVeto.Checked; + } + + private void Side_Selected(object sender, EventArgs e) => SideSelected?.Invoke(this, ddSide.SelectedItem?.Tag as QmSide); + + private void Map_LeftClicked(object sender, EventArgs eventArgs) => LeftClickMap?.Invoke(this, EventArgs.Empty); + + private void InitUI() + { + ddSide.Items.Clear(); + foreach (int ladderMapAllowedSideId in LadderMap.AllowedSideIds) + { + QmSide side = ladder.Sides.FirstOrDefault(s => s.LocalId == ladderMapAllowedSideId); + if (side == null) + continue; + + ddSide.AddItem(new XNADropDownItem() { Text = side.Name, Tag = side }); + } + + var randomSide = QmSide.CreateRandomSide(); + ddSide.AddItem(new XNADropDownItem() { Text = randomSide.Name, Tag = randomSide }); + + if (ddSide.Items.Count > 0) + ddSide.SelectedIndex = 0; + + if (masterQmSide != null) + SetMasterSide(masterQmSide); + + lblMap.Text = LadderMap.Description; + + cbVeto.ClientRectangle = new Rectangle(ParentList.VetoX, 0, ParentList.VetoWidth, QuickMatchMapList.ItemHeight); + ddSide.ClientRectangle = new Rectangle(ParentList.SidesX, 0, ParentList.SidesWidth, QuickMatchMapList.ItemHeight); + panelMap.ClientRectangle = new Rectangle(ParentList.MapsX, 0, ParentList.MapsWidth, QuickMatchMapList.ItemHeight); + + topBorder.ClientRectangle = new Rectangle(panelMap.X, panelMap.Y, panelMap.Width, 1); + bottomBorder.ClientRectangle = new Rectangle(panelMap.X, panelMap.Bottom, panelMap.Width, 1); + rightBorder.ClientRectangle = new Rectangle(panelMap.Right, panelMap.Y, 1, panelMap.Height); + } + + public bool IsVetoed() => cbVeto.Checked; + + public bool ContainsPointVertical(Point point) => Y < point.Y && Y + Height < point.Y; + + public void CloseDropDowns() + { + ddSide.Close(); + } + } +} \ No newline at end of file diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs new file mode 100644 index 000000000..7fad3325e --- /dev/null +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Timers; +using ClientCore.Enums; +using ClientGUI; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using Microsoft.Xna.Framework; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace DTAClient.DXGUI.Multiplayer.QuickMatch +{ + public class QuickMatchStatusOverlay : INItializableWindow + { + private int DefaultInternalWidth; + + private const int BUTTON_WIDTH = 92; + private const int BUTTON_GAP = 10; + private const int BUTTON_HEIGHT = 21; + + private XNAPanel statusOverlayBox { get; set; } + + private XNALabel statusMessage { get; set; } + + private XNAPanel pnlButtons { get; set; } + + private Type lastQmLoadingEventType { get; set; } + + private string currentMessage { get; set; } + + private int matchupFoundConfirmTimeLeft { get; set; } + + private Timer matchupFoundConfirmTimer { get; set; } + private const int matchupFoundTimerInterval = 100; + + private XNAClientProgressBar progressBar; + + private readonly QmService qmService; + private readonly QmSettings qmSettings; + + public QuickMatchStatusOverlay(WindowManager windowManager) : base(windowManager) + { + qmService = QmService.GetInstance(); + qmService.QmEvent += HandleQmEvent; + qmSettings = QmSettingsService.GetInstance().GetSettings(); + + matchupFoundConfirmTimer = new Timer(matchupFoundTimerInterval); + matchupFoundConfirmTimer.AutoReset = true; + matchupFoundConfirmTimer.Elapsed += (_, _) => ReduceMatchupFoundConfirmTimeLeft(); + } + + public override void Initialize() + { + base.Initialize(); + + statusOverlayBox = FindChild(nameof(statusOverlayBox)); + DefaultInternalWidth = statusOverlayBox.ClientRectangle.Width; + + statusMessage = FindChild(nameof(statusMessage)); + + pnlButtons = FindChild(nameof(pnlButtons)); + + progressBar = FindChild(nameof(progressBar)); + } + + private void HandleQmEvent(object sender, QmEvent qmEvent) + { + switch (qmEvent) + { + case QmLoggingInEvent: + HandleLoggingInEvent(); + break; + case QmLoginEvent: + HandleLoginEvent(); + break; + case QmLoadingLaddersAndUserAccountsEvent: + HandleLoadingLaddersAndUserAccountsEvent(); + break; + case QmLaddersAndUserAccountsEvent: + HandleLaddersAndUserAccountsEvent(); + break; + case QmLoadingLadderMapsEvent: + HandleLoadingLadderMapsEvent(); + break; + case QmLadderMapsEvent: + HandleLadderMapsEvent(); + break; + case QmRequestingMatchEvent e: + HandleRequestingMatchEvent(e); + break; + case QmCancelingRequestMatchEvent: + HandleCancelingMatchRequest(); + break; + case QmErrorMessageEvent: + Disable(); + return; + case QmRequestResponseEvent e: + HandleRequestResponseEvent(e); + return; + } + + if (qmEvent is IQmOverlayStatusEvent) + lastQmLoadingEventType = qmEvent.GetType(); + } + + private void HandleLoggingInEvent() => SetStatus(QmStrings.LoggingInStatus); + + private void HandleLoginEvent() => CloseIfLastEventType(typeof(QmLoggingInEvent)); + + private void HandleLoadingLaddersAndUserAccountsEvent() => SetStatus(QmStrings.LoadingLaddersAndAccountsStatus); + + private void HandleLaddersAndUserAccountsEvent() => CloseIfLastEventType(typeof(QmLoadingLaddersAndUserAccountsEvent)); + + private void HandleLoadingLadderMapsEvent() => SetStatus(QmStrings.LoadingLadderMapsStatus); + + private void HandleLadderMapsEvent() => CloseIfLastEventType(typeof(QmLoadingLadderMapsEvent)); + + private void HandleRequestingMatchEvent(QmRequestingMatchEvent e) => SetStatus(QmStrings.RequestingMatchStatus, e.CancelAction); + + private void HandleRequestResponseEvent(QmRequestResponseEvent e) + { + QmRequestResponse response = e.Response; + switch (true) + { + case true when response is QmRequestWaitResponse: + return; // need to keep the overlay open while waiting + case true when response is QmRequestSpawnResponse spawnResponse: + HandleSpawnResponseEvent(spawnResponse); + return; + default: + CloseIfLastEventType(typeof(QmRequestingMatchEvent), typeof(QmCancelingRequestMatchEvent)); + return; + } + } + + private void HandleSpawnResponseEvent(QmRequestSpawnResponse spawnResponse) + { + const int ratio = 1000 / matchupFoundTimerInterval; + int max = qmSettings.MatchFoundWaitSeconds * matchupFoundTimerInterval / ratio; + progressBar.Maximum = max; + progressBar.Value = max; + var actions = new List> { new(QmStrings.MatchupFoundConfirmYes, () => qmService.AcceptMatchAsync()), new(QmStrings.MatchupFoundConfirmNo, Disable) }; + SetStatus(QmStrings.MatchupFoundConfirmMsg, actions, ProgressBarModeEnum.Determinate); + matchupFoundConfirmTimer.Start(); + } + + private void HandleCancelingMatchRequest() => SetStatus(QmStrings.CancelingMatchRequestStatus); + + private void CloseIfLastEventType(params Type[] lastEventType) + { + if (lastEventType.Any(t => t == lastQmLoadingEventType)) + Disable(); + } + + private void ReduceMatchupFoundConfirmTimeLeft() + { + progressBar.Value -= 1; + + if (progressBar.Value != 0) + return; + + matchupFoundConfirmTimer.Stop(); + Disable(); + } + + private void SetStatus(string message, Tuple button) + => SetStatus(message, new List> { button }); + + private void SetStatus(string message, IEnumerable> buttons = null, ProgressBarModeEnum progressBarMode = ProgressBarModeEnum.Indeterminate) + { + currentMessage = message; + statusMessage.Text = message; + progressBar.ProgressBarMode = progressBarMode; + + ResizeForText(); + AddButtons(buttons); + Enable(); + } + + private void ResizeForText() + { + Vector2 textDimensions = Renderer.GetTextDimensions(statusMessage.Text, statusMessage.FontIndex); + + statusOverlayBox.Width = (int)Math.Max(DefaultInternalWidth, textDimensions.X + 60); + statusOverlayBox.X = (Width / 2) - (statusOverlayBox.Width / 2); + } + + private void AddButtons(IEnumerable> buttons = null) + { + foreach (XNAControl xnaControl in pnlButtons.Children.ToList()) + pnlButtons.RemoveChild(xnaControl); + + if (buttons == null) + return; + + var buttonDefinitions = buttons.ToList(); + int fullWidth = (BUTTON_WIDTH * buttonDefinitions.Count) + (BUTTON_GAP * (buttonDefinitions.Count - 1)); + int startX = (statusOverlayBox.Width / 2) - (fullWidth / 2); + + for (int i = 0; i < buttonDefinitions.Count; i++) + { + Tuple buttonDefinition = buttonDefinitions[i]; + var button = new XNAClientButton(WindowManager); + button.Text = buttonDefinition.Item1; + button.LeftClick += (_, _) => buttonDefinition.Item2(); + button.ClientRectangle = new Rectangle(startX + (i * BUTTON_WIDTH) + (i * (buttonDefinitions.Count - 1) * BUTTON_GAP), 0, BUTTON_WIDTH, BUTTON_HEIGHT); + pnlButtons.AddChild(button); + } + } + } +} \ No newline at end of file diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs new file mode 100644 index 000000000..22b90efe0 --- /dev/null +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs @@ -0,0 +1,92 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Timers; +using ClientCore.Exceptions; +using ClientGUI; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using Microsoft.Xna.Framework; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; +using Timer = System.Timers.Timer; + +namespace DTAClient.DXGUI.Multiplayer.QuickMatch +{ + public class QuickMatchWindow : INItializableWindow + { + private readonly QmService qmService; + private readonly QmSettingsService qmSettingsService; + + private QuickMatchLoginPanel loginPanel; + + private QuickMatchLobbyPanel lobbyPanel; + + private XNAPanel headerGameLogo; + + public QuickMatchWindow(WindowManager windowManager) : base(windowManager) + { + qmService = QmService.GetInstance(); + qmService.QmEvent += HandleQmEvent; + qmSettingsService = QmSettingsService.GetInstance(); + } + + public override void Initialize() + { + Name = nameof(QuickMatchWindow); + BackgroundTexture = AssetLoader.CreateTexture(new Color(0, 0, 0, 255), 1, 1); + PanelBackgroundDrawMode = PanelBackgroundImageDrawMode.STRETCHED; + + base.Initialize(); + + loginPanel = FindChild(nameof(loginPanel)); + loginPanel.Exit += (sender, args) => Disable(); + + lobbyPanel = FindChild(nameof(lobbyPanel)); + lobbyPanel.Exit += (sender, args) => Disable(); + + headerGameLogo = FindChild(nameof(headerGameLogo)); + + WindowManager.CenterControlOnScreen(this); + + EnabledChanged += EnabledChangedEvent; + } + + private void HandleQmEvent(object sender, QmEvent qmEvent) + { + switch (qmEvent) + { + case QmUserAccountSelectedEvent e: + HandleUserAccountSelected(e.UserAccount); + return; + case QmErrorMessageEvent e: + HandleErrorMessageEvent(e); + return; + } + } + + private void HandleErrorMessageEvent(QmErrorMessageEvent e) + => XNAMessageBox.Show(WindowManager, e.ErrorTitle, e.ErrorMessage); + + private void HandleUserAccountSelected(QmUserAccount userAccount) + { + headerGameLogo.BackgroundTexture = qmSettingsService.GetSettings().GetLadderHeaderLogo(userAccount.Ladder.Abbreviation); + if (headerGameLogo.BackgroundTexture == null) + return; + + // Resize image to ensure proper ratio and spacing from right edge + float imageRatio = (float)headerGameLogo.BackgroundTexture.Width / headerGameLogo.BackgroundTexture.Height; + int newImageWidth = (int)imageRatio * headerGameLogo.Height; + headerGameLogo.ClientRectangle = new Rectangle(headerGameLogo.Parent.Right - newImageWidth - headerGameLogo.Parent.X, headerGameLogo.Y, newImageWidth, headerGameLogo.Height); + } + + private void EnabledChangedEvent(object sender, EventArgs e) + { + if (!Enabled) + return; + + loginPanel.Enable(); + } + } +} \ No newline at end of file diff --git a/DXMainClient/DXMainClient.csproj b/DXMainClient/DXMainClient.csproj index e6a35201d..48792467c 100644 --- a/DXMainClient/DXMainClient.csproj +++ b/DXMainClient/DXMainClient.csproj @@ -49,10 +49,12 @@ + + diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/EventArgs/QmStatusMessageEventArgs.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/EventArgs/QmStatusMessageEventArgs.cs new file mode 100644 index 000000000..3e441eb20 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/EventArgs/QmStatusMessageEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +{ + public class QmStatusMessageEventArgs : EventArgs + { + public string Message { get; } + + public Action CancelAction { get; } + + public QmStatusMessageEventArgs(string message, Action cancelAction = null) + { + Message = message; + CancelAction = cancelAction; + } + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/IQmOverlayStatusEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/IQmOverlayStatusEvent.cs new file mode 100644 index 000000000..0941850ee --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/IQmOverlayStatusEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public interface IQmOverlayStatusEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmCancelingRequestMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmCancelingRequestMatchEvent.cs new file mode 100644 index 000000000..90fb8cb40 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmCancelingRequestMatchEvent.cs @@ -0,0 +1,6 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmCancelingRequestMatchEvent : QmEvent +{ + +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmErrorMessageEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmErrorMessageEvent.cs new file mode 100644 index 000000000..58e47bb1a --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmErrorMessageEvent.cs @@ -0,0 +1,14 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmErrorMessageEvent : QmEvent +{ + public string ErrorTitle { get; } + + public string ErrorMessage { get; } + + public QmErrorMessageEvent(string errorMessage, string errorTitle = null) + { + ErrorMessage = errorMessage; + ErrorTitle = errorTitle ?? QmStrings.GenericErrorTitle; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmEvent.cs new file mode 100644 index 000000000..2e44336a1 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmEvent.cs @@ -0,0 +1,6 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public abstract class QmEvent +{ + +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderMapsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderMapsEvent.cs new file mode 100644 index 000000000..679ed4c1f --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderMapsEvent.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmLadderMapsEvent : QmEvent +{ + public IEnumerable LadderMaps { get; } + + public QmLadderMapsEvent(IEnumerable qmLadderMaps) + { + LadderMaps = qmLadderMaps; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderStatsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderStatsEvent.cs new file mode 100644 index 000000000..e645ae13d --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderStatsEvent.cs @@ -0,0 +1,11 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmLadderStatsEvent : QmEvent +{ + public QmLadderStats LadderStats { get; } + + public QmLadderStatsEvent(QmLadderStats ladderStats) + { + LadderStats = ladderStats; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLaddersAndUserAccountsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLaddersAndUserAccountsEvent.cs new file mode 100644 index 000000000..22faddafd --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLaddersAndUserAccountsEvent.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmLaddersAndUserAccountsEvent : QmEvent +{ + public IEnumerable Ladders { get; } + + public IEnumerable UserAccounts { get; } + + public QmLaddersAndUserAccountsEvent(IEnumerable ladders, IEnumerable userAccounts) + { + Ladders = ladders; + UserAccounts = userAccounts; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderMapsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderMapsEvent.cs new file mode 100644 index 000000000..4163206a7 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderMapsEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmLoadingLadderMapsEvent : QmEvent, IQmOverlayStatusEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderStatsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderStatsEvent.cs new file mode 100644 index 000000000..508dc9d6b --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderStatsEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmLoadingLadderStatsEvent : QmEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLaddersAndUserAccountsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLaddersAndUserAccountsEvent.cs new file mode 100644 index 000000000..1cbb80635 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLaddersAndUserAccountsEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmLoadingLaddersAndUserAccountsEvent : QmEvent, IQmOverlayStatusEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoggingInEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoggingInEvent.cs new file mode 100644 index 000000000..e49f9f1a5 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoggingInEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmLoggingInEvent : QmEvent, IQmOverlayStatusEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoginEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoginEvent.cs new file mode 100644 index 000000000..42ad67a7c --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoginEvent.cs @@ -0,0 +1,6 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmLoginEvent : QmEvent +{ + public string ErrorMessage { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLogoutEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLogoutEvent.cs new file mode 100644 index 000000000..a4ffa138f --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLogoutEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmLogoutEvent : QmEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmMasterSideSelected.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmMasterSideSelected.cs new file mode 100644 index 000000000..b3b42b7a5 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmMasterSideSelected.cs @@ -0,0 +1,11 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmMasterSideSelected : QmEvent +{ + public readonly QmSide Side; + + public QmMasterSideSelected(QmSide side) + { + Side = side; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestResponseEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestResponseEvent.cs new file mode 100644 index 000000000..38dcb70ae --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestResponseEvent.cs @@ -0,0 +1,11 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmRequestResponseEvent : QmEvent +{ + public QmRequestResponse Response { get; } + + public QmRequestResponseEvent(QmRequestResponse response) + { + Response = response; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestingMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestingMatchEvent.cs new file mode 100644 index 000000000..c10907dbc --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestingMatchEvent.cs @@ -0,0 +1,13 @@ +using System; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmRequestingMatchEvent : QmEvent, IQmOverlayStatusEvent +{ + public Tuple CancelAction { get; } + + public QmRequestingMatchEvent(Action cancelAction) + { + CancelAction = new Tuple("Cancel", cancelAction); + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmUserAccountSelectedEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmUserAccountSelectedEvent.cs new file mode 100644 index 000000000..a1c341550 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmUserAccountSelectedEvent.cs @@ -0,0 +1,11 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmUserAccountSelectedEvent : QmEvent +{ + public QmUserAccount UserAccount { get; } + + public QmUserAccountSelectedEvent(QmUserAccount userAccount) + { + UserAccount = userAccount; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmAuthData.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmAuthData.cs new file mode 100644 index 000000000..d5313ce49 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmAuthData.cs @@ -0,0 +1,9 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmAuthData + { + public string Token { get; set; } + public string Email { get; set; } + public string Name { get; set; } + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmData.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmData.cs new file mode 100644 index 000000000..21afe7308 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmData.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmData + { + public List Ladders { get; set; } + + public List UserAccounts { get; set; } + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs new file mode 100644 index 000000000..b13d61719 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmLadder + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("abbreviation")] + public string Abbreviation { get; set; } + + [JsonProperty("game")] + public string Game { get; set; } + + [JsonProperty("clans_allowed")] + private int clansAllowed { get; set; } + + [JsonIgnore] + public bool ClansAllowed => clansAllowed == 1; + + [JsonProperty("game_object_schema_id")] + public int GameObjectSchemaId { get; set; } + + [JsonProperty("map_pool_id")] + public int MapPoolId { get; set; } + + [JsonProperty("private")] + private int _private { get; set; } + + [JsonIgnore] + public bool IsPrivate => _private == 1; + + [JsonProperty("sides")] + public IEnumerable Sides { get; set; } + + [JsonProperty("vetoes")] + public int VetoesRemaining { get; set; } + + [JsonProperty("allowed_sides")] + public IEnumerable AllowedSideLocalIds { get; set; } + + [JsonProperty("current")] + public string Current { get; set; } + + [JsonProperty("qm_ladder_rules")] + public QmLadderRules LadderRules { get; set; } + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs new file mode 100644 index 000000000..b07cdc83c --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmLadderMap + { + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("ladder_id")] + public int LadderId { get; set; } + + [JsonProperty("map_id")] + public int MapId { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("bit_idx")] + public int BitIndex { get; set; } + + [JsonProperty("valid")] + private int valid { get; set; } + + [JsonIgnore] + public bool IsValid => valid == 1; + + [JsonProperty("spawn_order")] + public string SpawnOrder { get; set; } + + [JsonProperty("team1_spawn_order")] + public string Team1SpawnOrder { get; set; } + + [JsonProperty("team2_spawn_order")] + public string Team2SpawnOrder { get; set; } + + [JsonProperty("allowed_sides")] + public IEnumerable AllowedSideIds { get; set; } + + [JsonProperty("admin_description")] + public string AdminDescription { get; set; } + + [JsonProperty("map_pool_id")] + public int MapPoolId { get; set; } + + [JsonProperty("rejectable")] + private int rejectable { get; set; } + + [JsonIgnore] + public bool IsRejectable => rejectable == 1; + + [JsonProperty("default_reject")] + private int defaultReject { get; set; } + + [JsonIgnore] + public bool IsDefaultReject => defaultReject == 1; + + [JsonProperty("hash")] + public string Hash { get; set; } + + [JsonProperty("map")] + public QmMap Map { get; set; } + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs new file mode 100644 index 000000000..d62704c67 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmLadderRules + { + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("ladder_id")] + public int LadderId { get; set; } + + [JsonProperty("player_count")] + public int PlayerCount { get; set; } + + [JsonProperty("map_vetoes")] + public int MapVetoes { get; set; } + + [JsonProperty("max_difference")] + public int MaxDifference { get; set; } + + [JsonProperty("all_sides")] + private string allSides { get; set; } + + [JsonIgnore] + public IEnumerable AllSides => allSides?.Split(',').Select(int.Parse) ?? new List(); + + [JsonProperty("allowed_sides")] + private string allowedSides { get; set; } + + [JsonIgnore] + public IEnumerable AllowedSides => allSides?.Split(',').Select(int.Parse) ?? new List(); + + [JsonProperty("bail_time")] + public int BailTime { get; set; } + + [JsonProperty("bail_fps")] + public int BailFps { get; set; } + + [JsonProperty("tier2_rating")] + public int Tier2Rating { get; set; } + + [JsonProperty("rating_per_second")] + public double RatingPerSecond { get; set; } + + [JsonProperty("max_points_difference")] + public int MaxPointsDifference { get; set; } + + [JsonProperty("points_per_second")] + public int PointsPerSecond { get; set; } + + [JsonProperty("use_elo_points")] + private int useEloPoints { get; set; } + + [JsonIgnore] + public bool UseEloPoints => useEloPoints == 1; + + [JsonProperty("wol_k")] + public int WolK { get; set; } + + [JsonProperty("show_map_preview")] + private int showMapPreview { get; set; } + + [JsonIgnore] + public bool ShowMapPreview => showMapPreview == 1; + + [JsonProperty("reduce_map_repeats")] + private int reduceMapRepeats { get; set; } + + [JsonIgnore] + public bool ReduceMapRepeats => reduceMapRepeats == 1; + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs new file mode 100644 index 000000000..82f351e02 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs @@ -0,0 +1,26 @@ +using System; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmLadderStats + { + [JsonProperty("recentMatchedPlayers")] + public int RecentMatchedPlayerCount { get; set; } + + [JsonProperty("queuedPlayers")] + public int QueuedPlayerCount { get; set; } + + [JsonProperty("past24hMatches")] + public int Past24HourMatchCount { get; set; } + + [JsonProperty("recentMatches")] + public int RecentMatchCount { get; set; } + + [JsonProperty("activeMatches")] + public int ActiveMatchCount { get; set; } + + [JsonProperty("time")] + public DateTime DateTime { get; set; } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLoginRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLoginRequest.cs new file mode 100644 index 000000000..987ecbde7 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLoginRequest.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QMLoginRequest + { + [JsonProperty("email")] + public string Email { get; set; } + + [JsonProperty("password")] + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMap.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMap.cs new file mode 100644 index 000000000..406e2e967 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMap.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmMap + { + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("hash")] + public string Hash { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("ladder_id")] + public int LadderId { get; set; } + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs new file mode 100644 index 000000000..99fbf8ba4 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs @@ -0,0 +1,57 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmMatchRequest : QmRequest + { + [JsonProperty("lan_ip")] + public string LanIP { get; set; } + + [JsonProperty("lan_port")] + public string LanPort { get; set; } + + [JsonProperty("ipv6_address")] + public string IPv6Address { get; set; } + + [JsonProperty("ipv6_port")] + public string IPv6Port { get; set; } + + [JsonProperty("ip_address")] + public string IPAddress { get; set; } + + [JsonProperty("ip_port")] + public string IPPort { get; set; } + + [JsonProperty("side")] + public int Side { get; set; } + + [JsonProperty("map_bitfield")] + public string MapBitfield { get; set; } + + [JsonProperty("version")] + public string Version { get; set; } = "2.0"; + + [JsonProperty("platform")] + public string Platform { get; set; } + + [JsonProperty("map_sides")] + public string[] MapSides { get; set; } + + [JsonProperty("ai_dat")] + public string CheatSeen { get; set; } + + [JsonProperty("exe_hash")] + public string ExeHash { get; set; } + + [JsonProperty("ddraw")] + public string DDrawHash { get; set; } + + [JsonProperty("session")] + public string Session { get; set; } + + public QmMatchRequest() + { + Type = QmRequestTypes.MatchMeUp; + } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs new file mode 100644 index 000000000..a31b84082 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmQuitRequest : QmRequest + { + public QmQuitRequest() + { + Type = QmRequestTypes.Quit; + } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs new file mode 100644 index 000000000..6f09aa705 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public abstract class QmRequest + { + [JsonProperty("type")] + public string Type { get; set; } + + [JsonIgnore] + public string Ladder { get; set; } + + [JsonIgnore] + public string PlayerName { get; set; } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestErrorResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestErrorResponse.cs new file mode 100644 index 000000000..ff71c5257 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestErrorResponse.cs @@ -0,0 +1,8 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmRequestErrorResponse : QmRequestResponse + { + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestFatalResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestFatalResponse.cs new file mode 100644 index 000000000..b533ef396 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestFatalResponse.cs @@ -0,0 +1,8 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmRequestFatalResponse : QmRequestErrorResponse + { + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestQuitResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestQuitResponse.cs new file mode 100644 index 000000000..1e3c2f60c --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestQuitResponse.cs @@ -0,0 +1,8 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmRequestQuitResponse : QmRequestResponse + { + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponse.cs new file mode 100644 index 000000000..872404741 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponse.cs @@ -0,0 +1,34 @@ +using System; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + [JsonConverter(typeof(QmRequestResponseConverter))] + public abstract class QmRequestResponse + { + public const string TypeKey = "type"; + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("message")] + public string Message { get; set; } + + [JsonIgnore] + public bool IsSuccessful => this is not QmRequestErrorResponse; + + public static Type GetSubType(string type) + { + return type switch + { + QmResponseTypes.Wait => typeof(QmRequestWaitResponse), + QmResponseTypes.Spawn => typeof(QmRequestSpawnResponse), + QmResponseTypes.Error => typeof(QmRequestErrorResponse), + QmResponseTypes.Fatal => typeof(QmRequestFatalResponse), + QmResponseTypes.Update => typeof(QmRequestUpdateResponse), + QmResponseTypes.Quit => typeof(QmRequestQuitResponse), + _ => null + }; + } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs new file mode 100644 index 000000000..b2598ef68 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs @@ -0,0 +1,78 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmRequestResponseSpawnSettings +{ + [JsonProperty("UIMapName")] + public string UIMapName { get; set; } + + [JsonProperty("MapHash")] + public string MapHash { get; set; } + + [JsonProperty("Seed")] + public int Seed { get; set; } + + [JsonProperty("GameID")] + public int GameId { get; set; } + + [JsonProperty("WOLGameID")] + public int WOLGameId { get; set; } + + [JsonProperty("Host")] + public string Host { get; set; } + + [JsonProperty("IsSpectator")] + public string IsSpectator { get; set; } + + [JsonProperty("Name")] + public string Name { get; set; } + + [JsonProperty("Port")] + public int Port { get; set; } + + [JsonProperty("Side")] + public int SideId { get; set; } + + [JsonProperty("Color")] + public int ColorId { get; set; } + + [JsonProperty("GameSpeed")] + public string GameSpeedId { get; set; } + + [JsonProperty("Credits")] + public string Credits { get; set; } + + [JsonProperty("UnitCount")] + public string UnitCount { get; set; } + + [JsonProperty("SuperWeapons")] + public string SuperWeapons { get; set; } + + [JsonProperty("Tournament")] + public string Tournament { get; set; } + + [JsonProperty("ShortGame")] + public string ShortGame { get; set; } + + [JsonProperty("Bases")] + public string Bases { get; set; } + + [JsonProperty("MCVRedeploy")] + public string MCVRedeploy { get; set; } + + [JsonProperty("MultipleFactory")] + public string MultipleFactory { get; set; } + + [JsonProperty("Crates")] + public string Crates { get; set; } + + [JsonProperty("GameMode")] + public string GameMode { get; set; } + + [JsonProperty("FrameSendRate")] + public string FrameSendRate { get; set; } + + [JsonProperty("DisableSWvsYuri")] + public string DisableSWvsYuri { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponse.cs new file mode 100644 index 000000000..f5dc33685 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponse.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmRequestSpawnResponse : QmRequestResponse + { + [JsonProperty("spawn")] + public QmRequestSpawnResponseSpawn Spawn { get; set; } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs new file mode 100644 index 000000000..c20940c1f --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +[JsonConverter(typeof(QmRequestSpawnResponseSpawnConverter))] +public class QmRequestSpawnResponseSpawn +{ + [JsonProperty("SpawnLocations")] + public IDictionary SpawnLocations { get; set; } + + [JsonProperty("Settings")] + public QmRequestResponseSpawnSettings Settings { get; set; } + + /// + /// This is NOT part of the typical JSON that is used to serialize/deserialize this class. + /// + /// The typical JSON contains explicit properties of "Other1", "Other2", up to "Other7". + /// Rather than having an explicit property in this class for each one, we use the + /// to read/write out each property + /// into the list you see below. + /// + [JsonIgnore] + public IEnumerable Others { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawnOther.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawnOther.cs new file mode 100644 index 000000000..72001f707 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawnOther.cs @@ -0,0 +1,33 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmRequestSpawnResponseSpawnOther +{ + [JsonProperty("Name")] + public string Name { get; set; } + + [JsonProperty("Side")] + public int Side { get; set; } + + [JsonProperty("Color")] + public int Color { get; set; } + + [JsonProperty("Ip")] + public string Ip { get; set; } + + [JsonProperty("Port")] + public int Port { get; set; } + + [JsonProperty("IPv6")] + public string IPv6 { get; set; } + + [JsonProperty("PortV6")] + public int PortV6 { get; set; } + + [JsonProperty("LanIP")] + public string LanIP { get; set; } + + [JsonProperty("LanPort")] + public int LanPort { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestUpdateResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestUpdateResponse.cs new file mode 100644 index 000000000..d40498f2f --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestUpdateResponse.cs @@ -0,0 +1,8 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmRequestUpdateResponse : QmRequestResponse + { + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestWaitResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestWaitResponse.cs new file mode 100644 index 000000000..008e7834a --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestWaitResponse.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmRequestWaitResponse : QmRequestResponse + { + [JsonProperty("checkback")] + public int CheckBack { get; set; } + + [JsonProperty("no_sooner_than")] + public int NoSoonerThan { get; set; } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs new file mode 100644 index 000000000..f1b7d33d7 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework.Graphics; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmSettings + { + public const string DefaultBaseUrl = "https://ladder.cncnet.org"; + public const string DefaultLoginUrl = "/api/v1/auth/login"; + public const string DefaultRefreshUrl = "/api/v1/auth/refresh"; + public const string DefaultServerStatusUrl = "/api/v1/ping"; + public const string DefaultGetUserAccountsUrl = "/api/v1/user/account"; + public const string DefaultGetLaddersUrl = "/api/v1/ladder"; + public const string DefaultGetLadderMapsUrl = "/api/v1/qm/ladder/{0}/maps"; + public const string DefaultGetLadderStatsUrl = "/api/v1/qm/ladder/{0}/stats"; + public const string DefaultQuickMatchUrl = "/api/v1/qm/{0}/{1}"; + public const int DefaultMatchFoundWaitSeconds = 20; + + public string BaseUrl { get; set; } = DefaultBaseUrl; + + public string LoginUrl { get; set; } = DefaultLoginUrl; + + public string RefreshUrl { get; set; } = DefaultRefreshUrl; + + public string ServerStatusUrl { get; set; } = DefaultServerStatusUrl; + + public string GetUserAccountsUrl { get; set; } = DefaultGetUserAccountsUrl; + + public string GetLaddersUrl { get; set; } = DefaultGetLaddersUrl; + + public string GetLadderMapsUrlFormat { get; set; } = DefaultGetLadderMapsUrl; + + public string GetLadderStatsUrlFormat { get; set; } = DefaultGetLadderStatsUrl; + + public string QuickMatchUrlFormat { get; set; } = DefaultQuickMatchUrl; + + public string MatchFoundSoundFile { get; set; } + + public int MatchFoundWaitSeconds { get; set; } = DefaultMatchFoundWaitSeconds; + + public IDictionary HeaderLogos = new Dictionary(); + + public Texture2D GetLadderHeaderLogo(string ladder) + => !HeaderLogos.ContainsKey(ladder) ? null : HeaderLogos[ladder]; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSide.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSide.cs new file mode 100644 index 000000000..89f784293 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSide.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmSide + { + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("ladder_id")] + public int LadderId { get; set; } + + [JsonProperty("local_id")] + public int LocalId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonIgnore] + public bool IsRandom => Name == QmStrings.RandomSideName; + + public static QmSide CreateRandomSide() => new() { Name = QmStrings.RandomSideName }; + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserAccount.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserAccount.cs new file mode 100644 index 000000000..2c189cb6e --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserAccount.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmUserAccount + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("username")] + public string Username { get; set; } + + [JsonProperty("ladder_id")] + public int LadderId { get; set; } + + [JsonProperty("card_id")] + public int CardId { get; set; } + + [JsonProperty("ladder")] + public QmLadder Ladder { get; set; } + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs new file mode 100644 index 000000000..3cde2e8ad --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs @@ -0,0 +1,105 @@ +using System; +using System.IO; +using ClientCore; +using Newtonsoft.Json; +using Rampastring.Tools; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public class QmUserSettings + { + private static readonly string SettingsFile = $"{ProgramConstants.ClientUserFilesPath}QuickMatchSettings.ini"; + + private const string BasicSectionKey = "Basic"; + private const string AuthSectionKey = "Auth"; + private const string AuthDataKey = "AuthData"; + private const string EmailKey = "Email"; + private const string LadderKey = "Ladder"; + private const string SideKey = "Side"; + + public string Email { get; set; } + + public string Ladder { get; set; } + + public int? SideId { get; set; } + + public QmAuthData AuthData { get; set; } + + private QmUserSettings() + { + } + + public static QmUserSettings Load() + { + var settings = new QmUserSettings(); + if (!File.Exists(SettingsFile)) + return settings; + + var iniFile = new IniFile(SettingsFile); + LoadAuthSettings(iniFile, settings); + LoadBasicSettings(iniFile, settings); + + return settings; + } + + public void ClearAuthData() => AuthData = null; + + public void Save() + { + var iniFile = new IniFile(); + var authSection = new IniSection(AuthSectionKey); + authSection.AddKey(EmailKey, Email ?? string.Empty); + authSection.AddKey(AuthDataKey, JsonConvert.SerializeObject(AuthData)); + + var basicSection = new IniSection(BasicSectionKey); + basicSection.AddKey(LadderKey, Ladder ?? string.Empty); + basicSection.AddKey(SideKey, SideId?.ToString() ?? string.Empty); + + iniFile.AddSection(authSection); + iniFile.AddSection(basicSection); + iniFile.WriteIniFile(SettingsFile); + } + + private static void LoadAuthSettings(IniFile iniFile, QmUserSettings settings) + { + IniSection authSection = iniFile.GetSection(AuthSectionKey); + if (authSection == null) + return; + + settings.AuthData = GetAuthData(authSection); + settings.Email = authSection.GetStringValue(EmailKey, null); + } + + private static void LoadBasicSettings(IniFile iniFile, QmUserSettings settings) + { + IniSection basicSection = iniFile.GetSection(BasicSectionKey); + if (basicSection == null) + return; + + settings.Ladder = basicSection.GetStringValue(LadderKey, null); + int sideId = basicSection.GetIntValue(SideKey, -1); + if (sideId != -1) + settings.SideId = sideId; + } + + private static QmAuthData GetAuthData(IniSection section) + { + if (!section.KeyExists(AuthDataKey)) + return null; + + string authDataValue = section.GetStringValue(AuthDataKey, null); + if (string.IsNullOrEmpty(authDataValue)) + return null; + + try + { + return JsonConvert.DeserializeObject(authDataValue); + } + catch (Exception e) + { + Logger.Log(e.StackTrace); + return null; + } + } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs new file mode 100644 index 000000000..4eee4b0ff --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using ClientCore.Exceptions; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using Newtonsoft.Json; +using Rampastring.Tools; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +{ + public class QmApiService : IDisposable + { + private HttpClient _httpClient; + private readonly QmSettings qmSettings; + private string _token; + + private static QmApiService instance; + + private QmApiService() + { + qmSettings = QmSettingsService.GetInstance().GetSettings(); + } + + public static QmApiService GetInstance() => instance ??= new QmApiService(); + + public void SetToken(string token) + { + _token = token; + HttpClient httpClient = GetHttpClient(); + httpClient.DefaultRequestHeaders.Clear(); + if (!string.IsNullOrEmpty(token)) + httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_token}"); + } + + public async Task> LoadLadderMapsForAbbrAsync(string ladderAbbreviation) + { + HttpClient httpClient = GetHttpClient(); + string url = string.Format(qmSettings.GetLadderMapsUrlFormat, ladderAbbreviation); + HttpResponseMessage response = await httpClient.GetAsync(url); + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.LoadingLadderMapsErrorFormat, response.ReasonPhrase)); + + return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + } + + public async Task LoadLadderStatsForAbbrAsync(string ladderAbbreviation) + { + HttpClient httpClient = GetHttpClient(); + string url = string.Format(qmSettings.GetLadderStatsUrlFormat, ladderAbbreviation); + HttpResponseMessage response = await httpClient.GetAsync(url); + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.LoadingLadderStatsErrorFormat, response.ReasonPhrase)); + + return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + } + + public async Task> LoadUserAccountsAsync() + { + HttpClient httpClient = GetHttpClient(); + HttpResponseMessage response = await httpClient.GetAsync(qmSettings.GetUserAccountsUrl); + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.LoadingUserAccountsErrorFormat, response.ReasonPhrase)); + + return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + } + + public async Task> LoadLaddersAsync() + { + HttpClient httpClient = GetHttpClient(); + HttpResponseMessage response = await httpClient.GetAsync(qmSettings.GetLaddersUrl); + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.LoadingLaddersErrorFormat, response.ReasonPhrase)); + + return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + } + + public async Task LoginAsync(string email, string password) + { + HttpClient httpClient = GetHttpClient(); + var postBodyContent = new StringContent(JsonConvert.SerializeObject(new QMLoginRequest() { Email = email, Password = password }), Encoding.Default, "application/json"); + var response = await httpClient.PostAsync(qmSettings.LoginUrl, postBodyContent); + + return await HandleLoginResponse(response, QmStrings.LoggingInUnknownErrorFormat); + } + + public async Task RefreshAsync() + { + HttpClient httpClient = GetHttpClient(); + HttpResponseMessage response = await httpClient.GetAsync(qmSettings.RefreshUrl); + + return await HandleLoginResponse(response, "Error refreshing token: {0}, {1}"); + } + + private async Task HandleLoginResponse(HttpResponseMessage response, string unknownErrorFormat) + { + if (!response.IsSuccessStatusCode) + return await HandleFailedLoginResponse(response, unknownErrorFormat); + + string responseBody = await response.Content.ReadAsStringAsync(); + QmAuthData authData = JsonConvert.DeserializeObject(responseBody); + if (authData == null) + throw new ClientException(responseBody); + + return authData; + } + + private async Task HandleFailedLoginResponse(HttpResponseMessage response, string unknownErrorFormat) + { + string responseBody = await response.Content.ReadAsStringAsync(); + string message; + switch (response.StatusCode) + { + case HttpStatusCode.BadGateway: + message = QmStrings.ServerUnreachableError; + break; + case HttpStatusCode.Unauthorized: + message = QmStrings.InvalidUsernamePasswordError; + break; + default: + message = string.Format(unknownErrorFormat, response.ReasonPhrase, responseBody); + break; + } + + throw new ClientRequestException(message, response.StatusCode); + } + + public bool IsServerAvailable() + { + HttpClient httpClient = GetHttpClient(); + HttpResponseMessage response = httpClient.GetAsync(qmSettings.ServerStatusUrl).Result; + return response.IsSuccessStatusCode; + } + + private HttpClient GetHttpClient() => + _httpClient ??= new HttpClient { BaseAddress = new Uri(qmSettings.BaseUrl), Timeout = TimeSpan.FromSeconds(10) }; + + public async Task QuickMatchRequestAsync(QmRequest qmRequest) + { + HttpClient httpClient = GetHttpClient(); + string url = string.Format(qmSettings.QuickMatchUrlFormat, qmRequest.Ladder, qmRequest.PlayerName); + HttpResponseMessage response = await httpClient.PostAsync(url, new StringContent(JsonConvert.SerializeObject(qmRequest), Encoding.Default, "application/json")); + + string responseBody = await response.Content.ReadAsStringAsync(); + Logger.Log(responseBody); + + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.RequestingMatchErrorFormat, response.ReasonPhrase)); + + QmRequestResponse matchRequestResponse = JsonConvert.DeserializeObject(responseBody); + if (!(matchRequestResponse?.IsSuccessful ?? false)) + throw new ClientException(string.Format(QmStrings.RequestingMatchErrorFormat, matchRequestResponse?.Message ?? matchRequestResponse?.Description ?? "unknown")); + + return matchRequestResponse; + } + + public void Dispose() + { + _httpClient?.Dispose(); + } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestResponseConverter.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestResponseConverter.cs new file mode 100644 index 000000000..e27113bbf --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestResponseConverter.cs @@ -0,0 +1,38 @@ +using System; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +/// +/// The response from the Ladder api for a match request can come back in a few different response types: +/// +/// +public class QmRequestResponseConverter : JsonConverter +{ + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, QmRequestResponse value, JsonSerializer serializer) => throw new NotImplementedException(); + + public override QmRequestResponse ReadJson(JsonReader reader, Type objectType, QmRequestResponse existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var token = JObject.Load(reader); + string responseType = token[QmRequestResponse.TypeKey]?.ToString(); + + if (responseType == null) + return null; + + Type subType = QmRequestResponse.GetSubType(responseType); + + existingValue ??= Activator.CreateInstance(subType) as QmRequestResponse; + + if (existingValue == null) + return null; + + using JsonReader subReader = token.CreateReader(); + serializer.Populate(subReader, existingValue); + + return existingValue; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestSpawnResponseSpawnConverter.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestSpawnResponseSpawnConverter.cs new file mode 100644 index 000000000..6bf1f2f76 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestSpawnResponseSpawnConverter.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public class QmRequestSpawnResponseSpawnConverter : JsonConverter +{ + private const int MaxOtherSpawns = 7; + + public override void WriteJson(JsonWriter writer, QmRequestSpawnResponseSpawn value, JsonSerializer serializer) + { + var obj = new JObject(); + + List others = value?.Others?.ToList() ?? new List(); + for (int i = 0; i < others.Count; i++) + obj.Add($"Other{i + 1}", JObject.FromObject(others[i])); + + obj.WriteTo(writer); + } + + public override QmRequestSpawnResponseSpawn ReadJson(JsonReader reader, Type objectType, QmRequestSpawnResponseSpawn existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var token = JObject.Load(reader); + + existingValue ??= new QmRequestSpawnResponseSpawn(); + + // populate base properties that require no specific conversions + using JsonReader subReader = token.CreateReader(); + serializer.Populate(subReader, existingValue); + + var others = new List(); + for (int i = 1; i <= MaxOtherSpawns; i++) + { + JToken otherN = token[$"Other{i}"]; + if (otherN == null) + break; + + others.Add(JsonConvert.DeserializeObject(otherN.ToString())); + } + + if (others.Any()) + existingValue.Others = others; + + return existingValue; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs new file mode 100644 index 000000000..dccee908a --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs @@ -0,0 +1,9 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +{ + public static class QmRequestTypes + { + public const string Quit = "quit"; + public const string Update = "update"; + public const string MatchMeUp = "match me up"; + } +} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs new file mode 100644 index 000000000..6fcdfe7dd --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs @@ -0,0 +1,12 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +{ + public static class QmResponseTypes + { + public const string Error = "error"; + public const string Fatal = "fatal"; + public const string Spawn = "spawn"; + public const string Update = "update"; + public const string Quit = "quit"; + public const string Wait = "please wait"; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs new file mode 100644 index 000000000..5e6e7c844 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Timers; +using ClientCore.Exceptions; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using JWT; +using JWT.Algorithms; +using JWT.Exceptions; +using JWT.Serializers; +using Rampastring.Tools; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public class QmService : IDisposable +{ + private readonly QmUserSettingsService userSettingsService; + private readonly QmApiService apiService; + private readonly QmUserSettings qmUserSettings; + private readonly QmData qmData; + + private static QmService _instance; + private readonly Timer retryRequestmatchTimer; + private QmMatchRequest qmMatchRequest; + + private QmService() + { + userSettingsService = QmUserSettingsService.GetInstance(); + apiService = QmApiService.GetInstance(); + qmUserSettings = userSettingsService.GetSettings(); + qmData = new QmData(); + + retryRequestmatchTimer = new Timer(); + retryRequestmatchTimer.AutoReset = false; + retryRequestmatchTimer.Elapsed += (_, _) => RetryRequestMatchAsync(); + + qmMatchRequest = new QmMatchRequest(); + } + + public event EventHandler QmEvent; + + public static QmService GetInstance() => _instance ??= new QmService(); + + public IEnumerable GetUserAccounts() => qmData?.UserAccounts; + + public QmLadder GetLadderForId(int ladderId) => qmData.Ladders.FirstOrDefault(l => l.Id == ladderId); + + public string GetCachedEmail() => qmUserSettings.Email; + + public string GetCachedLadder() => qmUserSettings.Ladder; + + public bool IsServerAvailable() => apiService.IsServerAvailable(); + + /// + /// Login process to cncnet. + /// + /// The email for login. + /// The password for login. + public void LoginAsync(string email, string password) => + ExecuteLoginRequest(async () => + { + QmAuthData authData = await apiService.LoginAsync(email, password); + FinishLogin(authData, email); + }); + + /// + /// Attempts to refresh an existing auth tokenl. + /// + public void RefreshAsync() => + ExecuteLoginRequest(async () => + { + QmAuthData authData = await apiService.RefreshAsync(); + FinishLogin(authData); + }); + + /// + /// Simply clear all auth data from our settings. + /// + public void Logout() + { + ClearAuthData(); + QmEvent?.Invoke(this, new QmLogoutEvent()); + } + + public bool IsLoggedIn() + { + if (qmUserSettings.AuthData == null) + return false; + + try + { + DecodeToken(qmUserSettings.AuthData.Token); + } + catch (TokenExpiredException) + { + Logger.Log(QmStrings.TokenExpiredError); + return false; + } + catch (Exception e) + { + Logger.Log(e.StackTrace); + return false; + } + + apiService.SetToken(qmUserSettings.AuthData.Token); + + return true; + } + + public void SetUserAccount(QmUserAccount userAccount) + { + string laddAbbr = userAccount?.Ladder?.Abbreviation; + qmUserSettings.Ladder = laddAbbr; + qmMatchRequest.Ladder = laddAbbr; + qmMatchRequest.PlayerName = userAccount?.Username; + userSettingsService.SaveSettings(); + QmEvent?.Invoke(this, new QmUserAccountSelectedEvent(userAccount)); + } + + public void SetMasterSide(QmSide side) + { + qmUserSettings.SideId = side?.LocalId; + qmMatchRequest.Side = side?.LocalId ?? -1; + userSettingsService.SaveSettings(); + QmEvent?.Invoke(this, new QmMasterSideSelected(side)); + } + + public void SetMapSides(string[] mapSides) + { + // TODO call this from the lobby panel + qmMatchRequest.MapSides = mapSides; + } + + public void LoadLaddersAndUserAccountsAsync() => + ExecuteRequest(new QmLoadingLaddersAndUserAccountsEvent(), async () => + { + Task> loadLaddersTask = apiService.LoadLaddersAsync(); + Task> loadUserAccountsTask = apiService.LoadUserAccountsAsync(); + + await Task.WhenAll(loadLaddersTask, loadUserAccountsTask); + qmData.Ladders = loadLaddersTask.Result.ToList(); + qmData.UserAccounts = loadUserAccountsTask.Result.ToList(); + + if (!qmData.Ladders.Any()) + { + QmEvent?.Invoke(this, new QmErrorMessageEvent(QmStrings.NoLaddersFoundError)); + return; + } + + if (!qmData.UserAccounts.Any()) + { + QmEvent?.Invoke(this, new QmErrorMessageEvent(QmStrings.NoUserAccountsFoundError)); + return; + } + + QmEvent?.Invoke(this, new QmLaddersAndUserAccountsEvent(qmData.Ladders, qmData.UserAccounts)); + }); + + public void LoadLadderMapsForAbbrAsync(string ladderAbbr) => + ExecuteRequest(new QmLoadingLadderMapsEvent(), async () => + { + IEnumerable ladderMaps = await apiService.LoadLadderMapsForAbbrAsync(ladderAbbr); + QmEvent?.Invoke(this, new QmLadderMapsEvent(ladderMaps)); + }); + + public void LoadLadderStatsForAbbrAsync(string ladderAbbr) => + ExecuteRequest(new QmLoadingLadderStatsEvent(), async () => + { + QmLadderStats ladderStats = await apiService.LoadLadderStatsForAbbrAsync(ladderAbbr); + QmEvent?.Invoke(this, new QmLadderStatsEvent(ladderStats)); + }); + + public void RequestMatchAsync() => + ExecuteRequest(new QmRequestingMatchEvent(CancelRequestMatchAsync), async () => + { + QmRequestResponse response = await apiService.QuickMatchRequestAsync(qmMatchRequest); + HandleQuickMatchResponse(response); + }); + + public void AcceptMatchAsync() + { + } + + private void RetryRequestMatchAsync() => + RequestMatchAsync(); + + public void Dispose() + { + apiService.Dispose(); + } + + private void HandleQuickMatchResponse(QmRequestResponse qmRequestResponse) + { + switch (true) + { + case true when qmRequestResponse is QmRequestWaitResponse waitResponse: + retryRequestmatchTimer.Interval = waitResponse.CheckBack * 1000; + retryRequestmatchTimer.Start(); + break; + } + + QmEvent?.Invoke(this, new QmRequestResponseEvent(qmRequestResponse)); + } + + /// + /// We only need to verify the expiration date of the token so that we can refresh or request a new one if it is expired. + /// We do not need to worry about the signature. The API will handle that validation when the token is used. + /// + /// The token to be decoded. + private static void DecodeToken(string token) + { + IJsonSerializer serializer = new JsonNetSerializer(); + IDateTimeProvider provider = new UtcDateTimeProvider(); + ValidationParameters validationParameters = ValidationParameters.Default; + validationParameters.ValidateSignature = false; + IJwtValidator validator = new JwtValidator(serializer, provider, validationParameters); + IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); + IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric + IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm); + + decoder.Decode(token, "nokey", verify: true); + } + + private void ClearAuthData() + { + userSettingsService.ClearAuthData(); + userSettingsService.SaveSettings(); + apiService.SetToken(null); + } + + private void CancelRequestMatchAsync() => + ExecuteRequest(new QmCancelingRequestMatchEvent(), async () => + { + retryRequestmatchTimer.Stop(); + qmMatchRequest.Type = QmRequestTypes.Quit; + QmRequestResponse response = await apiService.QuickMatchRequestAsync(new QmQuitRequest()); + QmEvent?.Invoke(this, new QmRequestResponseEvent(response)); + }); + + private void ExecuteLoginRequest(Func func) => + ExecuteRequest(new QmLoggingInEvent(), async () => + { + await func(); + QmEvent?.Invoke(this, new QmLoginEvent()); + }); + + private void ExecuteRequest(QmEvent qmEvent, Func requestAction) + { + QmEvent?.Invoke(this, qmEvent); + Task.Run(async () => + { + try + { + await requestAction(); + } + catch (Exception e) + { + QmEvent?.Invoke(this, new QmErrorMessageEvent((e as ClientException)?.Message ?? QmStrings.UnknownError)); + } + }); + } + + private void FinishLogin(QmAuthData authData, string email = null) + { + qmUserSettings.AuthData = authData; + qmUserSettings.Email = email ?? qmUserSettings.Email; + userSettingsService.SaveSettings(); + + apiService.SetToken(authData.Token); + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs new file mode 100644 index 000000000..2cdbdf675 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs @@ -0,0 +1,111 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ClientCore; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using Rampastring.Tools; +using Rampastring.XNAUI; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +{ + public class QmSettingsService + { + private static QmSettingsService Instance; + private static readonly string SettingsFile = ClientConfiguration.Instance.QuickMatchPath; + + private const string BasicSectionKey = "Basic"; + private const string SoundsSectionKey = "Sounds"; + private const string HeaderLogosSectionKey = "HeaderLogos"; + + private const string BaseUrlKey = "BaseUrl"; + private const string LoginUrlKey = "LoginUrl"; + private const string RefreshUrlKey = "RefreshUrl"; + private const string ServerStatusUrlKey = "ServerStatusUrl"; + private const string GetUserAccountsUrlKey = "GetUserAccountsUrl"; + private const string GetLaddersUrlKey = "GetLaddersUrl"; + private const string GetLadderMapsUrlKey = "GetLadderMapsUrl"; + + private const string MatchFoundSoundFileKey = "MatchFoundSoundFile"; + + private QmSettings qmSettings; + + private QmSettingsService() + { + } + + public static QmSettingsService GetInstance() => Instance ??= new QmSettingsService(); + + public QmSettings GetSettings() => qmSettings ??= LoadSettings(); + + private static QmSettings LoadSettings() + { + var settings = new QmSettings(); + if (!File.Exists(SettingsFile)) + SaveSettings(settings); // init the settings file + + var iniFile = new IniFile(SettingsFile); + LoadBasicSettings(iniFile, settings); + LoadSoundSettings(iniFile, settings); + LoadHeaderLogoSettings(iniFile, settings); + + return settings; + } + + private static void LoadBasicSettings(IniFile iniFile, QmSettings settings) + { + IniSection basicSection = iniFile.GetSection(BasicSectionKey); + if (basicSection == null) + return; + + settings.BaseUrl = basicSection.GetStringValue(BaseUrlKey, QmSettings.DefaultBaseUrl); + settings.LoginUrl = basicSection.GetStringValue(LoginUrlKey, QmSettings.DefaultLoginUrl); + settings.RefreshUrl = basicSection.GetStringValue(RefreshUrlKey, QmSettings.DefaultRefreshUrl); + settings.ServerStatusUrl = basicSection.GetStringValue(ServerStatusUrlKey, QmSettings.DefaultServerStatusUrl); + settings.GetUserAccountsUrl = basicSection.GetStringValue(GetUserAccountsUrlKey, QmSettings.DefaultGetUserAccountsUrl); + settings.GetLaddersUrl = basicSection.GetStringValue(GetLaddersUrlKey, QmSettings.DefaultGetLaddersUrl); + settings.GetLadderMapsUrlFormat = basicSection.GetStringValue(GetLadderMapsUrlKey, QmSettings.DefaultGetLadderMapsUrl); + settings.MatchFoundWaitSeconds = basicSection.GetIntValue(GetLadderMapsUrlKey, QmSettings.DefaultMatchFoundWaitSeconds); + } + + private static void LoadSoundSettings(IniFile iniFile, QmSettings settings) + { + IniSection soundsSection = iniFile.GetSection(SoundsSectionKey); + if (soundsSection == null) + return; + + string matchFoundSoundFile = soundsSection.GetStringValue(MatchFoundSoundFileKey, null); + if (matchFoundSoundFile == null) + return; + + matchFoundSoundFile = SafePath.CombineFilePath("Resources", matchFoundSoundFile); + if (File.Exists(matchFoundSoundFile)) + settings.MatchFoundSoundFile = matchFoundSoundFile; + } + + private static void LoadHeaderLogoSettings(IniFile iniFile, QmSettings settings) + { + IniSection headerLogosSection = iniFile.GetSection(HeaderLogosSectionKey); + if (headerLogosSection == null) + return; + + foreach (KeyValuePair keyValuePair in headerLogosSection.Keys.Where(keyValuePair => AssetLoader.AssetExists(keyValuePair.Value))) + settings.HeaderLogos.Add(keyValuePair.Key, AssetLoader.LoadTexture(keyValuePair.Value)); + } + + public static void SaveSettings(QmSettings settings) + { + var iniFile = new IniFile(); + var basicSection = new IniSection(BasicSectionKey); + basicSection.AddKey(BaseUrlKey, settings.BaseUrl); + basicSection.AddKey(LoginUrlKey, settings.LoginUrl); + basicSection.AddKey(RefreshUrlKey, settings.RefreshUrl); + basicSection.AddKey(ServerStatusUrlKey, settings.ServerStatusUrl); + basicSection.AddKey(GetUserAccountsUrlKey, settings.GetUserAccountsUrl); + basicSection.AddKey(GetLaddersUrlKey, settings.GetLaddersUrl); + basicSection.AddKey(GetLadderMapsUrlKey, settings.GetLadderMapsUrlFormat); + + iniFile.AddSection(basicSection); + iniFile.WriteIniFile(SettingsFile); + } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmStrings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmStrings.cs new file mode 100644 index 000000000..bb8244fc6 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmStrings.cs @@ -0,0 +1,77 @@ +using Localization; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public static class QmStrings +{ + // Error Messages + public static string TokenExpiredError => "QuickMatch token is expired".L10N("QM:Error:TokenExpired"); + + public static string NoLaddersFoundError => "No quick match ladders currently found.".L10N("QM:Error:NoLaddersFound"); + + public static string NoUserAccountsFoundError => "No user accounts found in quick match. Are you registered for this month?".L10N("QM:Error:NoUserAccountsFound"); + + public static string LoadingLadderStatsError => "Error loading ladder stats".L10N("QM:Error:LoadingLadderStats"); + + public static string LoadingLadderMapsError => "Error loading ladder maps".L10N("QM:Error:LoadingLadderMaps"); + + public static string ServerUnreachableError => "Server unreachable".L10N("QM:Error:ServerUnreachable"); + + public static string InvalidUsernamePasswordError => "Invalid username/password".L10N("QM:Error:InvalidUsernamePassword"); + + public static string NoSideSelectedError => "No side selected".L10N("QM:Error:NoSideSelected"); + + public static string NoLadderSelectedError => "No ladder selected".L10N("QM:Error:NoLadderSelected"); + + public static string UnknownError => "Unknown error occurred".L10N("QM:Error:Unknown"); + + public static string LoggingInUnknownError => "Error logging in".L10N("QM:Error:LoggingInUnknown"); + + public static string CancelingMatchRequestError => "Error canceling match request".L10N("QM:Error:CancelingMatchRequest"); + + public static string RequestingMatchUnknownError => "Error requesting match".L10N("QM:Error:RequestingMatchUnknown"); + + public static string LoadingLaddersAndAccountsUnknownError => "Error loading ladders and accounts...".L10N("QM:Error:LoadingLaddersAndAccountsUnknown"); + + // Error Messages Formatted + public static string LoadingLadderMapsErrorFormat => "Error loading ladder maps: {0}".L10N("QM:Error:LoadingLadderMapsFormat"); + + public static string LoadingLadderStatsErrorFormat => "Error loading ladder stats: {0}".L10N("QM:Error:LoadingLadderStatsFormat"); + + public static string LoadingUserAccountsErrorFormat => "Error loading user accounts: {0}".L10N("QM:Error:LoadingUserAccountsFormat"); + + public static string LoadingLaddersErrorFormat => "Error loading ladders: {0}".L10N("QM:Error:LoadingLaddersFormat"); + + public static string LoggingInUnknownErrorFormat => "Error logging in: {0}, {1}".L10N("QM:Error:LoggingInUnknownFormat"); + + public static string RequestingMatchErrorFormat => "Error requesting match: {0}".L10N("QM:Error:RequestingMatchFormat"); + + public static string UnableToCreateMatchRequestDataError => "Unable to create match request data".L10N("QM:Error:UnableToCreateMatchRequestDataError"); + + // UI Messages + public static string GenericErrorTitle => "Error".L10N("QM:UI:GenericErrorTitle"); + + public static string LogoutConfirmation => "Are you sure you want to log out?".L10N("QM:UI:LogoutConfirmation"); + + public static string ConfirmationCaption => "Confirmation".L10N("QM:UI:ConfirmationCaption"); + + public static string RequestingMatchStatus => "Requesting match".L10N("QM:UI:RequestingMatchStatus"); + + public static string CancelingMatchRequestStatus => "Canceling match request".L10N("QM:UI:CancelingMatchRequest"); + + public static string LoadingStats => "Loading stats...".L10N("QM:UI:LoadingStats"); + + public static string LoadingLaddersAndAccountsStatus => "Loading ladders and accounts".L10N("QM:UI:LoadingLaddersAndAccountsStatus"); + + public static string LoadingLadderMapsStatus => "Loading ladder maps".L10N("QM:UI:LoadingLadderMapsStatus"); + + public static string LoggingInStatus => "Logging in".L10N("QM:UI:LoggingInStatus"); + + public static string RandomSideName => "Random".L10N("QM:UI:RandomSideName"); + + public static string MatchupFoundConfirmMsg => "Matchup found! Are you ready?".L10N("QM:UI:MatchupFoundConfirm:Msg"); + + public static string MatchupFoundConfirmYes => "I'm Ready".L10N("QM:UI:MatchupFoundConfirm:Yes"); + + public static string MatchupFoundConfirmNo => "Cancel".L10N("QM:UI:MatchupFoundConfirm:No"); +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs new file mode 100644 index 000000000..78a20bac8 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs @@ -0,0 +1,24 @@ +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +{ + public class QmUserSettingsService + { + private QmUserSettings qmUserSettings; + + private static QmUserSettingsService instance; + + private QmUserSettingsService() + { + + } + + public static QmUserSettingsService GetInstance() => instance ??= new QmUserSettingsService(); + + public QmUserSettings GetSettings() => qmUserSettings ??= QmUserSettings.Load(); + + public void SaveSettings() => qmUserSettings.Save(); + + public void ClearAuthData() => qmUserSettings.ClearAuthData(); + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/MapLoader.cs b/DXMainClient/Domain/Multiplayer/MapLoader.cs index 7c91a1690..d3d54b760 100644 --- a/DXMainClient/Domain/Multiplayer/MapLoader.cs +++ b/DXMainClient/Domain/Multiplayer/MapLoader.cs @@ -21,6 +21,8 @@ public class MapLoader private const string GameModeAliasesSection = "GameModeAliases"; private const int CurrentCustomMapCacheVersion = 1; + private static MapLoader Instance; + /// /// List of game modes. /// @@ -46,6 +48,12 @@ public class MapLoader /// private string[] AllowedGameModes = ClientConfiguration.Instance.AllowedCustomGameModes.Split(','); + private MapLoader() + { + } + + public static MapLoader GetInstance() => Instance ?? (Instance = new MapLoader()); + /// /// Loads multiplayer map info asynchonously. /// @@ -330,5 +338,8 @@ private void AddMapToGameModes(Map map, bool enableLogging) } } } + + public Map GetMapForSHA(string sha1) + => GameModeMaps.Select(gmm => gmm.Map).FirstOrDefault(m => m.SHA1 == sha1); } } diff --git a/Docs/INISystem.md b/Docs/INISystem.md index 1269a1974..a40786983 100644 --- a/Docs/INISystem.md +++ b/Docs/INISystem.md @@ -7,8 +7,8 @@ The `[ParserConstants]` section of the `GlobalThemeSettings.ini` file contains c ### Predefined System Constants -`RESOLUTION_WIDTH`: the width of the window when it is initialized -`RESOLUTION_HEIGHT`: the height of the window when it is initialized +`RESOLUTION_WIDTH`: the width of the window when it is initialized +`RESOLUTION_HEIGHT`: the height of the window when it is initialized ### User Defined Constants @@ -22,7 +22,7 @@ $X=MY_EXAMPLE_CONSTANT ``` _NOTE: Constants can only be used in [dynamic control properties](#dynamic-control-properties)_ -## Control properties: +## Control properties: Below lists basic and dynamic control properties. Ordering of properties is important. If there is a property that relies on the size of a control, the properties must set the size of that control first. @@ -31,121 +31,121 @@ Basic control properties cannot use constants #### XNAControl -`X` = `{integer}` the X location of the control -`Y` = `{integer}` the Y location of the control -`Location` = `{comma separated integers}` the X and Y location of the control. -`Width` = `{integer}` the Width of the control -`Height` = `{integer}` the Height of the control -`Size` = `{comma separated integers}` the Width and Height of the control. -`Text` = `{string}` the text to display for the control (ex: buttons, labels, etc...) -`Visible` = `{true/false or yes/no}` whether or not the control should be visible by default -`Enabled` = `{true/false or yes/no}` whether or not the control should be enabled by default -`DistanceFromRightBorder` = `{integer}` the distance of the right edge of this control from the right edge of its parent. This control MUST have a parent. -`DistanceFromBottomBorder` = `{integer}` the distance of the bottom edge of this control from the bottom edge of its parent. This control MUST have a parent. -`FillWidth` = `{integer}` this will set the width of this control to fill the parent/window MINUS this value, starting from the its X position -`FillHeight` = `{integer}` this will set the height of this control to fill the parent/window MINUS this value, starting from the its Y position -`DrawOrder` -`UpdateOrder` -`RemapColor` +`X` = `{integer}` the X location of the control +`Y` = `{integer}` the Y location of the control +`Location` = `{comma separated integers}` the X and Y location of the control. +`Width` = `{integer}` the Width of the control +`Height` = `{integer}` the Height of the control +`Size` = `{comma separated integers}` the Width and Height of the control. +`Text` = `{string}` the text to display for the control (ex: buttons, labels, etc...) +`Visible` = `{true/false or yes/no}` whether or not the control should be visible by default +`Enabled` = `{true/false or yes/no}` whether or not the control should be enabled by default +`DistanceFromRightBorder` = `{integer}` the distance of the right edge of this control from the right edge of its parent. This control MUST have a parent. +`DistanceFromBottomBorder` = `{integer}` the distance of the bottom edge of this control from the bottom edge of its parent. This control MUST have a parent. +`FillWidth` = `{integer}` this will set the width of this control to fill the parent/window MINUS this value, starting from the its X position +`FillHeight` = `{integer}` this will set the height of this control to fill the parent/window MINUS this value, starting from the its Y position +`DrawOrder` +`UpdateOrder` +`RemapColor` #### XNAPanel -_(inherits XNAControl)_ +_(inherits [XNAControl](#xnacontrol))_ -`BorderColor` -`DrawMode` -`AlphaRate` -`BackgroundTexture` -`SolidColorBackgroundTexture` -`DrawBorders` -`Padding` +`BorderColor` +`DrawMode` +`AlphaRate` +`BackgroundTexture` +`SolidColorBackgroundTexture` +`DrawBorders` +`Padding` #### XNAExtraPanel -_(inherits XNAPanel)_ +_(inherits [XNAPanel](#xnapanel))_ -`BackgroundTexture` +`BackgroundTexture` #### XNALabel -_(inherits XNAControl)_ +_(inherits [XNAControl](#xnacontrol))_ -`RemapColor` -`TextColor` -`FontIndex` -`AnchorPoint` -`TextAnchor` -`TextShadowDistance` +`RemapColor` +`TextColor` +`FontIndex` +`AnchorPoint` +`TextAnchor` +`TextShadowDistance` #### XNAButton -_(inherits XNAControl)_ - -`TextColorIdle` -`TextColorHover` -`HoverSoundEffect` -`ClickSoundEffect` -`AdaptiveText` -`AlphaRate` -`FontIndex` -`IdleTexture` -`HoverTexture` -`TextShadowDistance` +_(inherits [XNAControl](#xnacontrol))_ + +`TextColorIdle` +`TextColorHover` +`HoverSoundEffect` +`ClickSoundEffect` +`AdaptiveText` +`AlphaRate` +`FontIndex` +`IdleTexture` +`HoverTexture` +`TextShadowDistance` #### XNAClientButton -_(inherits XNAButton)_ +_(inherits [XNAButton](#xnabutton))_ -`MatchTextureSize` +`MatchTextureSize` #### XNALinkButton -_(inherits XNAClientButton)_ +_(inherits [XNAClientButton](#xnaclientbutton))_ -`URL` -`ToolTip` = {string} tooltip for checkbox. '@' can be used for newlines +`URL` +`ToolTip` = {string} tooltip for checkbox. '@' can be used for newlines #### XNACheckbox -_(inherits XNAControl)_ +_(inherits [XNAControl](#xnacontrol))_ -`FontIndex` -`IdleColor` -`HighlightColor` -`AlphaRate` -`AllowChecking` -`Checked` +`FontIndex` +`IdleColor` +`HighlightColor` +`AlphaRate` +`AllowChecking` +`Checked` #### XNAClientCheckbox -_(inherits XNACheckBox)_ +_(inherits [XNACheckBox](#xnacheckbox))_ `ToolTip` = {string} tooltip for checkbox. '@' can be used for newlines #### XNADropDown -_(inherits XNAControl)_ - -`OpenUp` -`DropDownTexture` -`DropDownOpenTexture` -`ItemHeight` -`ClickSoundEffect` -`FontIndex` -`BorderColor` -`FocusColor` -`BackColor` -`~~DisabledItemColor~~` -`OptionN` +_(inherits [XNAControl](#xnacontrol))_ + +`OpenUp` +`DropDownTexture` +`DropDownOpenTexture` +`ItemHeight` +`ClickSoundEffect` +`FontIndex` +`BorderColor` +`FocusColor` +`BackColor` +`~~DisabledItemColor~~` +`OptionN` #### XNAClientDropDown -_(inherits XNADropDown)_ +_(inherits [XNADropDown](#xnadropdown))_ -`ToolTip` = {string} tooltip for checkbox. '@' can be used for newlines +`ToolTip` = {string} tooltip for checkbox. '@' can be used for newlines #### XNATabControl -_(inherits XNAControl)_ +_(inherits [XNAControl](#xnacontrol))_ -`RemapColor` -`TextColor` -`TextColorDisabled` -`RemoveTabIndexN` +`RemapColor` +`TextColor` +`TextColorDisabled` +`RemoveTabIndexN` #### XNATextBox -_(inherits XNAControl)_ +_(inherits [XNAControl](#xnacontrol))_ -`MaximumTextLength` +`MaximumTextLength` ### Basic Control Property Examples ``` @@ -176,49 +176,49 @@ Following controls are only available as children of `XNAOptionsPanel` and deriv ##### SettingCheckBox _(inherits XNAClientCheckBox)_ -`DefaultValue` = `{true/false or yes/no}` default state of the checkbox. Value of `Checked` will be used if it is set and this isn't. Otherwise defaults to `false`. -`SettingSection` = `{string}` name of the section in settings INI the setting is saved to. Defaults to `CustomSettings`. -`SettingKey` = `{string}` name of the key in settings INI the setting is saved to. Defaults to `CONTROLNAME_Value` if `WriteSettingValue` is set, otherwise `CONTROLNAME_Checked`. -`WriteSettingValue` = `{true/false or yes/no}` enable to write a specific string value to setting INI key instead of the checked state of the checkbox. Defaults to `false`. -`EnabledSettingValue` = `{string}` value to write to setting INI key if `WriteSettingValue` is set and checkbox is checked. -`DisabledSettingValue` = `{string}` value to write to setting INI key if `WriteSettingValue` is set and checkbox is not checked. -`RestartRequired` = `{true/false or yes/no}` whether or not this setting requires restarting the client to apply. Defaults to `false`. -`ParentCheckBoxName` = `{string}` name of a `XNAClientCheckBox` control to use as a parent checkbox that is required to either be checked or unchecked, depending on value of `ParentCheckBoxRequiredValue` for this checkbox to be enabled. Only works if name can be resolved to an existing control belonging to same parent as current checkbox. -`ParentCheckBoxRequiredValue` = `{true/false or yes/no}` state required from the parent checkbox for this one to be enabled. Defaults to `true`. +`DefaultValue` = `{true/false or yes/no}` default state of the checkbox. Value of `Checked` will be used if it is set and this isn't. Otherwise defaults to `false`. +`SettingSection` = `{string}` name of the section in settings INI the setting is saved to. Defaults to `CustomSettings`. +`SettingKey` = `{string}` name of the key in settings INI the setting is saved to. Defaults to `CONTROLNAME_Value` if `WriteSettingValue` is set, otherwise `CONTROLNAME_Checked`. +`WriteSettingValue` = `{true/false or yes/no}` enable to write a specific string value to setting INI key instead of the checked state of the checkbox. Defaults to `false`. +`EnabledSettingValue` = `{string}` value to write to setting INI key if `WriteSettingValue` is set and checkbox is checked. +`DisabledSettingValue` = `{string}` value to write to setting INI key if `WriteSettingValue` is set and checkbox is not checked. +`RestartRequired` = `{true/false or yes/no}` whether or not this setting requires restarting the client to apply. Defaults to `false`. +`ParentCheckBoxName` = `{string}` name of a `XNAClientCheckBox` control to use as a parent checkbox that is required to either be checked or unchecked, depending on value of `ParentCheckBoxRequiredValue` for this checkbox to be enabled. Only works if name can be resolved to an existing control belonging to same parent as current checkbox. +`ParentCheckBoxRequiredValue` = `{true/false or yes/no}` state required from the parent checkbox for this one to be enabled. Defaults to `true`. ##### FileSettingCheckBox _(inherits XNAClientCheckBox)_ -`DefaultValue` = `{true/false or yes/no}` default state of the checkbox. Value of `Checked` will be used if it is set and this isn't. Otherwise defaults to `false`. -`SettingSection` = `{string}` name of the section in settings INI the setting is saved to. Defaults to `CustomSettings`. -`SettingKey` = `{string}` name of the key in settings INI the setting is saved to. Defaults to `CONTROLNAME_Value` if `WriteSettingValue` is set, otherwise `CONTROLNAME_Checked`. -`RestartRequired` = `{true/false or yes/no}` whether or not this setting requires restarting the client to apply. Defaults to `false`. -`ParentCheckBoxName` = `{string}` name of a `XNAClientCheckBox` control to use as a parent checkbox that is required to either be checked or unchecked, depending on value of `ParentCheckBoxRequiredValue` for this checkbox to be enabled. Only works if name can be resolved to an existing control belonging to same parent as current checkbox. -`ParentCheckBoxRequiredValue` = `{true/false or yes/no}` state required from the parent checkbox for this one to be enabled. Defaults to `true`. -`CheckAvailability` = `{true/false or yes/no}` if set, whether or not the checkbox can be (un)checked depends on if the files to copy are actually present. Defaults to `false`. -`ResetUnavailableValue` = `{true/false or yes/no}` if set together with `CheckAvailability`, checkbox set to a value that is unavailable will be reset back to `DefaultValue`. Defaults to `false`. -`EnabledFileN` = `{comma-separated strings}` files to copy if checkbox is checked. N starts from 0 and is incremented by 1 until no value is found. Expects 2-3 comma-separated strings in following format: source path relative to game root folder, destination path relative to game root folder and a [file operation option](#appendix-file-operation-options). -`DisabledFileN` = `{comma-separated strings}` files to copy if checkbox is not checked. N starts from 0 and is incremented by 1 until no value is found. Expects 2-3 comma-separated strings in following format: source path relative to game root folder, destination path relative to game root folder and a [file operation option](#appendix-file-operation-options). +`DefaultValue` = `{true/false or yes/no}` default state of the checkbox. Value of `Checked` will be used if it is set and this isn't. Otherwise defaults to `false`. +`SettingSection` = `{string}` name of the section in settings INI the setting is saved to. Defaults to `CustomSettings`. +`SettingKey` = `{string}` name of the key in settings INI the setting is saved to. Defaults to `CONTROLNAME_Value` if `WriteSettingValue` is set, otherwise `CONTROLNAME_Checked`. +`RestartRequired` = `{true/false or yes/no}` whether or not this setting requires restarting the client to apply. Defaults to `false`. +`ParentCheckBoxName` = `{string}` name of a `XNAClientCheckBox` control to use as a parent checkbox that is required to either be checked or unchecked, depending on value of `ParentCheckBoxRequiredValue` for this checkbox to be enabled. Only works if name can be resolved to an existing control belonging to same parent as current checkbox. +`ParentCheckBoxRequiredValue` = `{true/false or yes/no}` state required from the parent checkbox for this one to be enabled. Defaults to `true`. +`CheckAvailability` = `{true/false or yes/no}` if set, whether or not the checkbox can be (un)checked depends on if the files to copy are actually present. Defaults to `false`. +`ResetUnavailableValue` = `{true/false or yes/no}` if set together with `CheckAvailability`, checkbox set to a value that is unavailable will be reset back to `DefaultValue`. Defaults to `false`. +`EnabledFileN` = `{comma-separated strings}` files to copy if checkbox is checked. N starts from 0 and is incremented by 1 until no value is found. Expects 2-3 comma-separated strings in following format: source path relative to game root folder, destination path relative to game root folder and a [file operation option](#appendix-file-operation-options). +`DisabledFileN` = `{comma-separated strings}` files to copy if checkbox is not checked. N starts from 0 and is incremented by 1 until no value is found. Expects 2-3 comma-separated strings in following format: source path relative to game root folder, destination path relative to game root folder and a [file operation option](#appendix-file-operation-options). ##### SettingDropDown _(inherits XNAClientDropDown)_ -`Items` = `{comma-separated strings}` comma-separated list of strings to include as items to display on the dropdown control. -`DefaultValue` = `{integer}` default item index of the dropdown. Defaults to 0 (first item). -`SettingSection` = `{string}` name of the section in settings INI the setting is saved to. Defaults to `CustomSettings`. -`SettingKey` = `{string}` name of the key in settings INI the setting is saved to. Defaults to `CONTROLNAME_Value` if `WriteSettingValue` is set, otherwise `CONTROLNAME_SelectedIndex`. -`WriteSettingValue` = `{true/false or yes/no}` enable to write selected item value to the setting INI key instead of the checked state of the checkbox. Defaults to `false`. -`RestartRequired` = `{true/false or yes/no}` whether or not this setting requires restarting the client to apply. Defaults to `false`. +`Items` = `{comma-separated strings}` comma-separated list of strings to include as items to display on the dropdown control. +`DefaultValue` = `{integer}` default item index of the dropdown. Defaults to 0 (first item). +`SettingSection` = `{string}` name of the section in settings INI the setting is saved to. Defaults to `CustomSettings`. +`SettingKey` = `{string}` name of the key in settings INI the setting is saved to. Defaults to `CONTROLNAME_Value` if `WriteSettingValue` is set, otherwise `CONTROLNAME_SelectedIndex`. +`WriteSettingValue` = `{true/false or yes/no}` enable to write selected item value to the setting INI key instead of the checked state of the checkbox. Defaults to `false`. +`RestartRequired` = `{true/false or yes/no}` whether or not this setting requires restarting the client to apply. Defaults to `false`. ##### FileSettingDropDown _(inherits XNAClientDropDown)_ -`Items` = `{comma-separated strings}` comma-separated list of strings to include as items to display on the dropdown control. -`DefaultValue` = `{integer}` default item index of the dropdown. Defaults to 0 (first item). -`SettingSection` = `{string}` name of the section in settings INI the setting is saved to. Defaults to `CustomSettings`. -`SettingKey` = `{string}` name of the key in settings INI the setting is saved to. Defaults to `CONTROLNAME_SelectedIndex`. -`RestartRequired` = `{true/false or yes/no}` whether or not this setting requires restarting the client to apply. Defaults to `false`. -`ItemXFileN` = `{comma-separated strings}` files to copy when dropdown item X is selected. N starts from 0 and is incremented by 1 until no value is found. Expects 2-3 comma-separated strings in following format: source path relative to game root folder, destination path relative to game root folder and a [file operation option](#appendix-file-operation-options). +`Items` = `{comma-separated strings}` comma-separated list of strings to include as items to display on the dropdown control. +`DefaultValue` = `{integer}` default item index of the dropdown. Defaults to 0 (first item). +`SettingSection` = `{string}` name of the section in settings INI the setting is saved to. Defaults to `CustomSettings`. +`SettingKey` = `{string}` name of the key in settings INI the setting is saved to. Defaults to `CONTROLNAME_SelectedIndex`. +`RestartRequired` = `{true/false or yes/no}` whether or not this setting requires restarting the client to apply. Defaults to `false`. +`ItemXFileN` = `{comma-separated strings}` files to copy when dropdown item X is selected. N starts from 0 and is incremented by 1 until no value is found. Expects 2-3 comma-separated strings in following format: source path relative to game root folder, destination path relative to game root folder and a [file operation option](#appendix-file-operation-options). ##### Appendix: File Operation Options @@ -234,11 +234,11 @@ Dynamic Control Properties CAN use constants These can ONLY be used in parent controls that inherit the `INItializableWindow` class -`$X` = ``{integer}`` the X location of the control -`$Y` = ``{integer}`` the Y location of the control -`$Width` = ``{integer}`` the Width of the control -`$Height` = ``{integer}`` the Height of the control -`$TextAnchor` +`$X` = ``{integer}`` the X location of the control +`$Y` = ``{integer}`` the Y location of the control +`$Width` = ``{integer}`` the Width of the control +`$Height` = ``{integer}`` the Height of the control +`$TextAnchor` ### Dynamic Control Property Examples ``` diff --git a/build/AfterPublish.targets b/build/AfterPublish.targets index d8853bba6..d31fecd18 100644 --- a/build/AfterPublish.targets +++ b/build/AfterPublish.targets @@ -36,6 +36,7 @@ + @@ -60,6 +61,7 @@ + @@ -78,6 +80,7 @@ + @@ -123,4 +126,4 @@ - \ No newline at end of file + From c7a594d89f2406d843269ec71966b48af698a776 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Mon, 31 Oct 2022 06:46:52 -0400 Subject: [PATCH 02/19] Start writing spawn ini --- .../QuickMatch/Models/QmReadyRequest.cs | 22 +++++++ .../Models/QmRequestResponseSpawnSettings.cs | 38 ++--------- .../Models/QmRequestSpawnResponseSpawn.cs | 2 +- .../CnCNet/QuickMatch/QmService.cs | 66 ++++++++++++++++++- 4 files changed, 93 insertions(+), 35 deletions(-) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs new file mode 100644 index 000000000..e2d2f2209 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +{ + public abstract class QmReadyRequest + { + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("status")] + public string Status { get; private set; } + + [JsonProperty("seed")] + public int Seed { get; set; } + + public QmReadyRequest() + { + Type = "update"; + Status = "ready"; + } + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs index b2598ef68..81afccaf9 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs @@ -1,78 +1,52 @@ -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; public class QmRequestResponseSpawnSettings { - [JsonProperty("UIMapName")] public string UIMapName { get; set; } - [JsonProperty("MapHash")] public string MapHash { get; set; } - [JsonProperty("Seed")] public int Seed { get; set; } - [JsonProperty("GameID")] - public int GameId { get; set; } + public int GameID { get; set; } - [JsonProperty("WOLGameID")] - public int WOLGameId { get; set; } + public int WOLGameID { get; set; } - [JsonProperty("Host")] public string Host { get; set; } - [JsonProperty("IsSpectator")] public string IsSpectator { get; set; } - [JsonProperty("Name")] public string Name { get; set; } - [JsonProperty("Port")] public int Port { get; set; } - [JsonProperty("Side")] - public int SideId { get; set; } + public int Side { get; set; } - [JsonProperty("Color")] - public int ColorId { get; set; } + public int Color { get; set; } - [JsonProperty("GameSpeed")] - public string GameSpeedId { get; set; } + public string GameSpeed { get; set; } - [JsonProperty("Credits")] public string Credits { get; set; } - [JsonProperty("UnitCount")] public string UnitCount { get; set; } - [JsonProperty("SuperWeapons")] public string SuperWeapons { get; set; } - [JsonProperty("Tournament")] public string Tournament { get; set; } - [JsonProperty("ShortGame")] public string ShortGame { get; set; } - [JsonProperty("Bases")] public string Bases { get; set; } - [JsonProperty("MCVRedeploy")] public string MCVRedeploy { get; set; } - [JsonProperty("MultipleFactory")] public string MultipleFactory { get; set; } - [JsonProperty("Crates")] public string Crates { get; set; } - [JsonProperty("GameMode")] public string GameMode { get; set; } - [JsonProperty("FrameSendRate")] public string FrameSendRate { get; set; } - [JsonProperty("DisableSWvsYuri")] public string DisableSWvsYuri { get; set; } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs index c20940c1f..294c4b3a9 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs @@ -21,5 +21,5 @@ public class QmRequestSpawnResponseSpawn /// into the list you see below. /// [JsonIgnore] - public IEnumerable Others { get; set; } + public List Others { get; set; } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs index 5e6e7c844..484213494 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using System.Timers; +using ClientCore; using ClientCore.Exceptions; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; @@ -172,6 +175,9 @@ public void LoadLadderStatsForAbbrAsync(string ladderAbbr) => QmEvent?.Invoke(this, new QmLadderStatsEvent(ladderStats)); }); + /// + /// This is called when the user clicks the button to begin searching for a match. + /// public void RequestMatchAsync() => ExecuteRequest(new QmRequestingMatchEvent(CancelRequestMatchAsync), async () => { @@ -179,18 +185,74 @@ public void RequestMatchAsync() => HandleQuickMatchResponse(response); }); + /// + /// This is called when the user clicks the "I'm Ready" button in the match found dialog. + /// public void AcceptMatchAsync() { } - private void RetryRequestMatchAsync() => - RequestMatchAsync(); + public void WriteSpawnIni(QmRequestSpawnResponse spawnResponse) + { + IniFile spawnIni = CreateSpawnIniFile(); + + // SETTINGS section + var settings = new IniSection("Settings"); + settings.SetStringValue("Scenario", "spawnmap.ini"); + settings.SetStringValue("QuickMatch", "Yes"); + + foreach (PropertyInfo prop in spawnResponse.Spawn.Settings.GetType().GetProperties()) + settings.SetStringValue(prop.Name, prop.GetValue(spawnResponse.Spawn.Settings).ToString()); + // End SETTINGS sections + + // OTHER# sections + for (int i = 0; i < spawnResponse.Spawn.Others.Count; i++) + { + // Headers for OTHER# sections are 1-based index + var otherSection = new IniSection($"Other{i + 1}"); + QmRequestSpawnResponseSpawnOther other = spawnResponse.Spawn.Others[i]; + + foreach (PropertyInfo otherProp in other.GetType().GetProperties()) + otherSection.SetStringValue(otherProp.Name, otherProp.GetValue(other).ToString()); + + spawnIni.AddSection(otherSection); + } + // End OTHER# sections + + // SPAWNLOCATIONS section + var spawnLocationsSection = new IniSection("SpawnLocation"); + foreach (KeyValuePair spawnLocation in spawnResponse.Spawn.SpawnLocations) + spawnLocationsSection.SetStringValue(spawnLocation.Key, spawnLocation.Value.ToString()); + + spawnIni.AddSection(spawnLocationsSection); + // End SPAWNLOCATIONS section + + // TUNNEL section + var tunnel = new IniSection("Tunnel"); + // TODO IP and port information + // tunnel.SetStringValue("Ip", spawnResponse.Spawn.Settings.); + // tunnel.SetIntValue("Port", tunnelHandler.CurrentTunnel.Port); + spawnIni.AddSection(tunnel); + // End TUNNEL section + } + + public IniFile CreateSpawnIniFile() + { + FileInfo spawnerSettingsFile = SafePath.GetFile(ProgramConstants.GamePath, ProgramConstants.SPAWNER_SETTINGS); + + spawnerSettingsFile.Delete(); + + return new IniFile(spawnerSettingsFile.FullName); + } public void Dispose() { apiService.Dispose(); } + private void RetryRequestMatchAsync() => + RequestMatchAsync(); + private void HandleQuickMatchResponse(QmRequestResponse qmRequestResponse) { switch (true) From 44feca23934e15d33307888d7afc62765a58a859 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Fri, 4 Nov 2022 17:26:35 -0400 Subject: [PATCH 03/19] Tab to next/prev controls, trigger login on enter --- .../Multiplayer/QuickMatch/QuickMatchLoginPanel.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs index 99ca5f594..75323bdf3 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs @@ -34,7 +34,7 @@ public override void Initialize() XNAClientButton btnLogin; btnLogin = FindChild(nameof(btnLogin)); - btnLogin.LeftClick += BtnLogin_LeftClick; + btnLogin.LeftClick += (_, _) => Login(); XNAClientButton btnCancel; btnCancel = FindChild(nameof(btnCancel)); @@ -42,8 +42,10 @@ public override void Initialize() tbEmail = FindChild(nameof(tbEmail)); tbEmail.Text = qmService.GetCachedEmail() ?? string.Empty; + tbEmail.EnterPressed += (_, _) => Login(); tbPassword = FindChild(nameof(tbPassword)); + tbPassword.EnterPressed += (_, _) => Login(); EnabledChanged += InitLogin; } @@ -72,7 +74,7 @@ public void InitLogin(object sender, EventArgs eventArgs) loginInitialized = true; } - private void BtnLogin_LeftClick(object sender, EventArgs eventArgs) + private void Login() { if (!ValidateForm()) return; @@ -84,13 +86,13 @@ private bool ValidateForm() { if (string.IsNullOrEmpty(tbEmail.Text)) { - XNAMessageBox.Show(WindowManager, "No Email specified", LoginErrorTitle); + XNAMessageBox.Show(WindowManager, LoginErrorTitle, "No Email specified"); return false; } if (string.IsNullOrEmpty(tbPassword.Text)) { - XNAMessageBox.Show(WindowManager, "No Password specified", LoginErrorTitle); + XNAMessageBox.Show(WindowManager, LoginErrorTitle, "No Password specified"); return false; } From 884a0e565d266303b466b139fbd95388c1122889 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Fri, 4 Nov 2022 19:17:05 -0400 Subject: [PATCH 04/19] Log exception --- DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs index 484213494..f7fb04b41 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs @@ -319,6 +319,7 @@ private void ExecuteRequest(QmEvent qmEvent, Func requestAction) } catch (Exception e) { + Logger.Log(e.StackTrace); QmEvent?.Invoke(this, new QmErrorMessageEvent((e as ClientException)?.Message ?? QmStrings.UnknownError)); } }); From 3540809c48fd94540aa587eff7a010ba16cb2420 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Sat, 5 Nov 2022 07:33:10 -0400 Subject: [PATCH 05/19] Fix slight issue with login panel appearing after going back to main menu --- .../Multiplayer/QuickMatch/QuickMatchLoginPanel.cs | 11 ++++------- .../DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs | 4 ++++ .../Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs index 75323bdf3..d4fca8e77 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs @@ -1,6 +1,4 @@ using System; -using System.Threading.Tasks; -using ClientCore.Exceptions; using ClientGUI; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; @@ -12,12 +10,13 @@ namespace DTAClient.DXGUI.Multiplayer.QuickMatch public class QuickMatchLoginPanel : INItializableWindow { public event EventHandler Exit; + private const string LoginErrorTitle = "Login Error"; + private readonly QmService qmService; private XNATextBox tbEmail; private XNAPasswordBox tbPassword; - private bool loginInitialized; public event EventHandler LoginEvent; @@ -65,13 +64,11 @@ private void HandleQmEvent(object sender, QmEvent qmEvent) public void InitLogin(object sender, EventArgs eventArgs) { - if (!Enabled || loginInitialized) + if (!Enabled) return; - if (qmService.IsLoggedIn()) + if (qmService.HasToken()) qmService.RefreshAsync(); - - loginInitialized = true; } private void Login() diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs index 22b90efe0..85864af4d 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs @@ -84,7 +84,11 @@ private void HandleUserAccountSelected(QmUserAccount userAccount) private void EnabledChangedEvent(object sender, EventArgs e) { if (!Enabled) + { + loginPanel.Disable(); + lobbyPanel.Disable(); return; + } loginPanel.Enable(); } diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs index f7fb04b41..d7b0201a2 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs @@ -87,7 +87,7 @@ public void Logout() QmEvent?.Invoke(this, new QmLogoutEvent()); } - public bool IsLoggedIn() + public bool HasToken() { if (qmUserSettings.AuthData == null) return false; From a4920073708abd0f686db0d6b57e9aa31a2e6612 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Sat, 5 Nov 2022 20:18:58 -0400 Subject: [PATCH 06/19] Update ladder stats for new structure --- .../CnCNet/QuickMatch/Models/QmLadderStats.cs | 2 +- .../QuickMatch/Models/QmLadderStatsTime.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStatsTime.cs diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs index 82f351e02..f480cea38 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs @@ -21,6 +21,6 @@ public class QmLadderStats public int ActiveMatchCount { get; set; } [JsonProperty("time")] - public DateTime DateTime { get; set; } + public QmLadderStatsTime Time { get; set; } } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStatsTime.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStatsTime.cs new file mode 100644 index 000000000..abd99bed1 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStatsTime.cs @@ -0,0 +1,16 @@ +using System; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmLadderStatsTime +{ + [JsonProperty("date")] + public DateTime Date { get; set; } + + [JsonProperty("timezone_type")] + public int TimezoneType { get; set; } + + [JsonProperty("timezone")] + public string Timezone { get; set; } +} \ No newline at end of file From c591ebafd0b7f30af0b7637bac214ca41581ca35 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Sun, 6 Nov 2022 05:47:03 -0500 Subject: [PATCH 07/19] More logging changes --- .../Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs | 2 +- .../Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs index 3cde2e8ad..983f30be7 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs @@ -97,7 +97,7 @@ private static QmAuthData GetAuthData(IniSection section) } catch (Exception e) { - Logger.Log(e.StackTrace); + Logger.Log(e.ToString()); return null; } } diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs index d7b0201a2..94e73ec07 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs @@ -103,7 +103,7 @@ public bool HasToken() } catch (Exception e) { - Logger.Log(e.StackTrace); + Logger.Log(e.ToString()); return false; } @@ -319,7 +319,7 @@ private void ExecuteRequest(QmEvent qmEvent, Func requestAction) } catch (Exception e) { - Logger.Log(e.StackTrace); + Logger.Log(e.ToString()); QmEvent?.Invoke(this, new QmErrorMessageEvent((e as ClientException)?.Message ?? QmStrings.UnknownError)); } }); From 5be0fe98047611423904c383e15c33e32e7b52b4 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Sun, 6 Nov 2022 11:14:25 -0500 Subject: [PATCH 08/19] Make map_pool_id nullable --- .../Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs | 2 +- .../Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs index b13d61719..679006f76 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs @@ -27,7 +27,7 @@ public class QmLadder public int GameObjectSchemaId { get; set; } [JsonProperty("map_pool_id")] - public int MapPoolId { get; set; } + public int? MapPoolId { get; set; } [JsonProperty("private")] private int _private { get; set; } diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs index b07cdc83c..f0c8d8b9d 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs @@ -42,7 +42,7 @@ public class QmLadderMap public string AdminDescription { get; set; } [JsonProperty("map_pool_id")] - public int MapPoolId { get; set; } + public int? MapPoolId { get; set; } [JsonProperty("rejectable")] private int rejectable { get; set; } From d5275cb830d90ca0e0c7c815cd47f3e0414cd8e6 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Sun, 6 Nov 2022 12:34:22 -0500 Subject: [PATCH 09/19] Decimal instead of int for points_per_second --- .../Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs index d62704c67..4df6b8d6f 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs @@ -43,13 +43,13 @@ public class QmLadderRules public int Tier2Rating { get; set; } [JsonProperty("rating_per_second")] - public double RatingPerSecond { get; set; } + public decimal RatingPerSecond { get; set; } [JsonProperty("max_points_difference")] public int MaxPointsDifference { get; set; } [JsonProperty("points_per_second")] - public int PointsPerSecond { get; set; } + public decimal PointsPerSecond { get; set; } [JsonProperty("use_elo_points")] private int useEloPoints { get; set; } From 31f33ceddc587be5d44723733270aa7a28021d4c Mon Sep 17 00:00:00 2001 From: devo1929 Date: Sun, 6 Nov 2022 12:51:40 -0500 Subject: [PATCH 10/19] Limit incoming ladders --- .../Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs | 2 ++ .../Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs | 10 +++++++++- .../Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs index f1b7d33d7..56af72783 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs @@ -36,6 +36,8 @@ public class QmSettings public string MatchFoundSoundFile { get; set; } + public List AllowedLadders { get; set; } = new(); + public int MatchFoundWaitSeconds { get; set; } = DefaultMatchFoundWaitSeconds; public IDictionary HeaderLogos = new Dictionary(); diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs index 94e73ec07..582835f05 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs @@ -21,7 +21,10 @@ public class QmService : IDisposable { private readonly QmUserSettingsService userSettingsService; private readonly QmApiService apiService; + private readonly QmSettingsService settingsService; + private readonly QmUserSettings qmUserSettings; + private readonly QmSettings qmSettings; private readonly QmData qmData; private static QmService _instance; @@ -32,7 +35,10 @@ private QmService() { userSettingsService = QmUserSettingsService.GetInstance(); apiService = QmApiService.GetInstance(); + settingsService = QmSettingsService.GetInstance(); + qmUserSettings = userSettingsService.GetSettings(); + qmSettings = settingsService.GetSettings(); qmData = new QmData(); retryRequestmatchTimer = new Timer(); @@ -144,7 +150,9 @@ public void LoadLaddersAndUserAccountsAsync() => await Task.WhenAll(loadLaddersTask, loadUserAccountsTask); qmData.Ladders = loadLaddersTask.Result.ToList(); - qmData.UserAccounts = loadUserAccountsTask.Result.ToList(); + qmData.UserAccounts = loadUserAccountsTask.Result + .Where(ua => qmSettings.AllowedLadders.Contains(ua.Ladder.Game)) + .ToList(); if (!qmData.Ladders.Any()) { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs index 2cdbdf675..de43e2d31 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs @@ -26,6 +26,7 @@ public class QmSettingsService private const string GetLadderMapsUrlKey = "GetLadderMapsUrl"; private const string MatchFoundSoundFileKey = "MatchFoundSoundFile"; + private const string AllowedLaddersKey = "AllowedLadders"; private QmSettings qmSettings; @@ -65,6 +66,7 @@ private static void LoadBasicSettings(IniFile iniFile, QmSettings settings) settings.GetLaddersUrl = basicSection.GetStringValue(GetLaddersUrlKey, QmSettings.DefaultGetLaddersUrl); settings.GetLadderMapsUrlFormat = basicSection.GetStringValue(GetLadderMapsUrlKey, QmSettings.DefaultGetLadderMapsUrl); settings.MatchFoundWaitSeconds = basicSection.GetIntValue(GetLadderMapsUrlKey, QmSettings.DefaultMatchFoundWaitSeconds); + settings.AllowedLadders = basicSection.GetStringValue(AllowedLaddersKey, string.Empty).Split(',').ToList(); } private static void LoadSoundSettings(IniFile iniFile, QmSettings settings) From c463cd9bbc23494d2c2fa4a434f10c13474f62ac Mon Sep 17 00:00:00 2001 From: devo1929 Date: Sun, 6 Nov 2022 12:55:26 -0500 Subject: [PATCH 11/19] Prevent dupe ladders --- DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs index 582835f05..208a9963f 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs @@ -152,6 +152,9 @@ public void LoadLaddersAndUserAccountsAsync() => qmData.Ladders = loadLaddersTask.Result.ToList(); qmData.UserAccounts = loadUserAccountsTask.Result .Where(ua => qmSettings.AllowedLadders.Contains(ua.Ladder.Game)) + .GroupBy(ua => ua.Id) // remove possible duplicates + .Select(g => g.First()) + .OrderBy(ua => ua.Ladder.Name) .ToList(); if (!qmData.Ladders.Any()) From 9dfcb8068637b60e32e99158d719ffd38810bcb8 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Tue, 8 Nov 2022 06:37:19 -0500 Subject: [PATCH 12/19] More handling of accept/reject workflow --- .../QuickMatch/QuickMatchLobbyPanel.cs | 8 +- .../QuickMatch/QuickMatchMapList.cs | 17 +- .../QuickMatch/QuickMatchMapListItem.cs | 2 + .../QuickMatch/QuickMatchStatusOverlay.cs | 26 +- .../Events/QmNotReadyRequestMatchEvent.cs | 5 + .../Models/Events/QmReadyRequestMatchEvent.cs | 5 + .../QuickMatch/Models/QmMatchRequest.cs | 20 +- .../QuickMatch/Models/QmNotReadyRequest.cs | 9 + .../CnCNet/QuickMatch/Models/QmQuitRequest.cs | 11 +- .../QuickMatch/Models/QmReadyRequest.cs | 22 +- .../CnCNet/QuickMatch/Models/QmRequest.cs | 18 +- .../QuickMatch/Models/QmUpdateRequest.cs | 18 ++ .../CnCNet/QuickMatch/QmApiService.cs | 239 +++++++++--------- .../CnCNet/QuickMatch/QmMatchFoundTimer.cs | 25 ++ .../CnCNet/QuickMatch/QmRequestTypes.cs | 15 +- .../CnCNet/QuickMatch/QmResponseTypes.cs | 19 +- .../CnCNet/QuickMatch/QmService.cs | 72 +++++- .../CnCNet/QuickMatch/QmSettingsService.cs | 201 ++++++++------- .../QuickMatch/QmUserSettingsService.cs | 25 +- 19 files changed, 433 insertions(+), 324 deletions(-) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmNotReadyRequestMatchEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmReadyRequestMatchEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmNotReadyRequest.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUpdateRequest.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMatchFoundTimer.cs diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs index 3ccd0df0a..f55526795 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs @@ -59,6 +59,7 @@ public override void Initialize() mapList = FindChild(nameof(mapList)); mapList.MapSelectedEvent += HandleMapSelectedEventEvent; + mapList.MapSideSelectedEvent += HandleMapSideSelectedEvent; footerPanel = FindChild(nameof(footerPanel)); footerPanel.ExitEvent += (sender, args) => Exit?.Invoke(sender, args); @@ -178,7 +179,7 @@ private QmMatchRequest CreateMatchRequest() if (side.IsRandom) side = GetRandomSide(); - return new QmMatchRequest { Ladder = userAccount.Ladder.Abbreviation, PlayerName = userAccount.Username, Side = side.LocalId }; + return new QmMatchRequest { Side = side.LocalId }; } private QmSide GetRandomSide() @@ -343,6 +344,11 @@ private void HandleMapSelectedEventEvent(object sender, QmLadderMap qmLadderMap) EnableRightPanel(mapPreviewBox); } + private void HandleMapSideSelectedEvent(object sender, IEnumerable mapSides) + { + qmService.SetMapSides(mapSides); + } + private void HandleLogoutEvent() { Disable(); diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs index a263aec6b..dbdb9ac01 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs @@ -15,22 +15,31 @@ public class QuickMatchMapList : INItializableWindow { private const int MouseScrollRate = 6; public const int ItemHeight = 22; + public event EventHandler MapSelectedEvent; + public event EventHandler> MapSideSelectedEvent; + private XNALabel lblVeto; private XNALabel lblSides; private XNALabel lblMaps; private XNAScrollablePanel mapListPanel; private readonly QmService qmService; + public XNAScrollBar scrollBar { get; private set; } private QmSide masterQmSide { get; set; } public int VetoX => lblVeto?.X ?? 0; + public int VetoWidth => lblVeto?.Width ?? 0; + public int SidesX => lblSides?.X ?? 0; + public int SidesWidth => lblSides?.Width ?? 0; + public int MapsX => lblMaps?.X ?? 0; + public int MapsWidth => lblMaps?.Width ?? 0; public QuickMatchMapList(WindowManager windowManager) : base(windowManager) @@ -105,16 +114,14 @@ private void OnMouseScrolled(object sender, EventArgs e) private void AddItem(QuickMatchMapListItem listItem) { listItem.LeftClickMap += MapItem_LeftClick; - listItem.SideSelected += (_, _) => MapSideSelected(listItem); + listItem.SideSelected += (_, _) => MapSideSelected(); listItem.SetParentList(this); listItem.SetMasterSide(masterQmSide); mapListPanel.AddChild(listItem); } - private void MapSideSelected(QuickMatchMapListItem listItem) - { - - } + private void MapSideSelected() + => MapSideSelectedEvent?.Invoke(this, MapItemChildren.Select(c => c.GetSelectedSide())); private int GetNewScrollBarViewTop() { diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapListItem.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapListItem.cs index 0d789e4b6..25aab492f 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapListItem.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapListItem.cs @@ -102,6 +102,8 @@ public void SetMasterSide(QmSide qmSide) public void SetParentList(QuickMatchMapList parentList) => ParentList = parentList; + public int GetSelectedSide() => ddSide.SelectedIndex; + public override void Draw(GameTime gameTime) { ddSide.OpenUp = OpenedDownWindowBottom > ParentList.scrollBar.GetWindowRectangle().Bottom; diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs index 7fad3325e..2bf949dcf 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs @@ -33,8 +33,8 @@ public class QuickMatchStatusOverlay : INItializableWindow private int matchupFoundConfirmTimeLeft { get; set; } - private Timer matchupFoundConfirmTimer { get; set; } - private const int matchupFoundTimerInterval = 100; + private QmMatchFoundTimer matchupFoundConfirmTimer { get; set; } + private XNAClientProgressBar progressBar; @@ -47,9 +47,8 @@ public QuickMatchStatusOverlay(WindowManager windowManager) : base(windowManager qmService.QmEvent += HandleQmEvent; qmSettings = QmSettingsService.GetInstance().GetSettings(); - matchupFoundConfirmTimer = new Timer(matchupFoundTimerInterval); - matchupFoundConfirmTimer.AutoReset = true; - matchupFoundConfirmTimer.Elapsed += (_, _) => ReduceMatchupFoundConfirmTimeLeft(); + matchupFoundConfirmTimer = new QmMatchFoundTimer(); + matchupFoundConfirmTimer.SetElapsedAction(ReduceMatchupFoundConfirmTimeLeft); } public override void Initialize() @@ -138,15 +137,25 @@ private void HandleRequestResponseEvent(QmRequestResponseEvent e) private void HandleSpawnResponseEvent(QmRequestSpawnResponse spawnResponse) { - const int ratio = 1000 / matchupFoundTimerInterval; - int max = qmSettings.MatchFoundWaitSeconds * matchupFoundTimerInterval / ratio; + int interval = matchupFoundConfirmTimer.GetInterval(); + int ratio = 1000 / interval; + int max = qmSettings.MatchFoundWaitSeconds * interval / ratio; progressBar.Maximum = max; progressBar.Value = max; - var actions = new List> { new(QmStrings.MatchupFoundConfirmYes, () => qmService.AcceptMatchAsync()), new(QmStrings.MatchupFoundConfirmNo, Disable) }; + var actions = new List> + { + new(QmStrings.MatchupFoundConfirmYes, () => AcceptMatchAsync(spawnResponse.Spawn)), + new(QmStrings.MatchupFoundConfirmNo, () => RejectMatchAsync(spawnResponse.Spawn)) + }; SetStatus(QmStrings.MatchupFoundConfirmMsg, actions, ProgressBarModeEnum.Determinate); + matchupFoundConfirmTimer.SetSpawn(spawnResponse.Spawn); matchupFoundConfirmTimer.Start(); } + private void AcceptMatchAsync(QmRequestSpawnResponseSpawn spawn) => qmService.AcceptMatchAsync(spawn); + + private void RejectMatchAsync(QmRequestSpawnResponseSpawn spawn) => qmService.RejectMatchAsync(spawn); + private void HandleCancelingMatchRequest() => SetStatus(QmStrings.CancelingMatchRequestStatus); private void CloseIfLastEventType(params Type[] lastEventType) @@ -164,6 +173,7 @@ private void ReduceMatchupFoundConfirmTimeLeft() matchupFoundConfirmTimer.Stop(); Disable(); + RejectMatchAsync(matchupFoundConfirmTimer.Spawn); } private void SetStatus(string message, Tuple button) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmNotReadyRequestMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmNotReadyRequestMatchEvent.cs new file mode 100644 index 000000000..e366759e5 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmNotReadyRequestMatchEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmNotReadyRequestMatchEvent : QmEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmReadyRequestMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmReadyRequestMatchEvent.cs new file mode 100644 index 000000000..5cd8fd0f6 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmReadyRequestMatchEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; + +public class QmReadyRequestMatchEvent : QmEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs index 99fbf8ba4..07b4adc89 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.Collections.Generic; +using Newtonsoft.Json; namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models { @@ -8,19 +9,19 @@ public class QmMatchRequest : QmRequest public string LanIP { get; set; } [JsonProperty("lan_port")] - public string LanPort { get; set; } + public int LanPort { get; set; } [JsonProperty("ipv6_address")] public string IPv6Address { get; set; } [JsonProperty("ipv6_port")] - public string IPv6Port { get; set; } + public int IPv6Port { get; set; } [JsonProperty("ip_address")] public string IPAddress { get; set; } [JsonProperty("ip_port")] - public string IPPort { get; set; } + public int IPPort { get; set; } [JsonProperty("side")] public int Side { get; set; } @@ -28,17 +29,14 @@ public class QmMatchRequest : QmRequest [JsonProperty("map_bitfield")] public string MapBitfield { get; set; } - [JsonProperty("version")] - public string Version { get; set; } = "2.0"; - [JsonProperty("platform")] public string Platform { get; set; } [JsonProperty("map_sides")] - public string[] MapSides { get; set; } + public IEnumerable MapSides { get; set; } [JsonProperty("ai_dat")] - public string CheatSeen { get; set; } + public bool CheatSeen { get; set; } [JsonProperty("exe_hash")] public string ExeHash { get; set; } @@ -52,6 +50,10 @@ public class QmMatchRequest : QmRequest public QmMatchRequest() { Type = QmRequestTypes.MatchMeUp; + MapBitfield = int.MaxValue.ToString(); + Platform = "win32"; + Session = string.Empty; + DDrawHash = "8a00ba609f7d030c67339e1f555199bdb4054b67"; } } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmNotReadyRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmNotReadyRequest.cs new file mode 100644 index 000000000..14a02dcdd --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmNotReadyRequest.cs @@ -0,0 +1,9 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmNotReadyRequest : QmUpdateRequest +{ + public QmNotReadyRequest(int seed) : base(seed) + { + Status = "NotReady"; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs index a31b84082..4fa1979d2 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs @@ -1,12 +1,9 @@ -using Newtonsoft.Json; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +public class QmQuitRequest : QmRequest { - public class QmQuitRequest : QmRequest + public QmQuitRequest() { - public QmQuitRequest() - { - Type = QmRequestTypes.Quit; - } + Type = QmRequestTypes.Quit; } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs index e2d2f2209..bec9c086a 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs @@ -1,22 +1,10 @@ -using Newtonsoft.Json; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +public class QmReadyRequest : QmUpdateRequest { - public abstract class QmReadyRequest - { - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("status")] - public string Status { get; private set; } - [JsonProperty("seed")] - public int Seed { get; set; } - - public QmReadyRequest() - { - Type = "update"; - Status = "ready"; - } + public QmReadyRequest(int seed) : base (seed) + { + Status = "Ready"; } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs index 6f09aa705..8873d66ef 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs @@ -1,16 +1,12 @@ using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models -{ - public abstract class QmRequest - { - [JsonProperty("type")] - public string Type { get; set; } +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; - [JsonIgnore] - public string Ladder { get; set; } +public abstract class QmRequest +{ + [JsonProperty("type")] + public string Type { get; set; } - [JsonIgnore] - public string PlayerName { get; set; } - } + [JsonProperty("version")] + public string Version { get; set; } = QmService.QmVersion; } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUpdateRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUpdateRequest.cs new file mode 100644 index 000000000..0bfe9e4e0 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUpdateRequest.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public abstract class QmUpdateRequest : QmRequest +{ + [JsonProperty("status")] + public string Status { get; protected set; } + + [JsonProperty("seed")] + public int Seed { get; set; } + + protected QmUpdateRequest(int seed) + { + Type = "update"; + Seed = seed; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs index 4eee4b0ff..1eb80ae23 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs @@ -9,156 +9,155 @@ using Newtonsoft.Json; using Rampastring.Tools; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public class QmApiService : IDisposable { - public class QmApiService : IDisposable - { - private HttpClient _httpClient; - private readonly QmSettings qmSettings; - private string _token; + private HttpClient _httpClient; + private readonly QmSettings qmSettings; + private string _token; - private static QmApiService instance; + private static QmApiService instance; - private QmApiService() - { - qmSettings = QmSettingsService.GetInstance().GetSettings(); - } + private QmApiService() + { + qmSettings = QmSettingsService.GetInstance().GetSettings(); + } - public static QmApiService GetInstance() => instance ??= new QmApiService(); + public static QmApiService GetInstance() => instance ??= new QmApiService(); - public void SetToken(string token) - { - _token = token; - HttpClient httpClient = GetHttpClient(); - httpClient.DefaultRequestHeaders.Clear(); - if (!string.IsNullOrEmpty(token)) - httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_token}"); - } + public void SetToken(string token) + { + _token = token; + HttpClient httpClient = GetHttpClient(); + httpClient.DefaultRequestHeaders.Clear(); + if (!string.IsNullOrEmpty(token)) + httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_token}"); + } - public async Task> LoadLadderMapsForAbbrAsync(string ladderAbbreviation) - { - HttpClient httpClient = GetHttpClient(); - string url = string.Format(qmSettings.GetLadderMapsUrlFormat, ladderAbbreviation); - HttpResponseMessage response = await httpClient.GetAsync(url); - if (!response.IsSuccessStatusCode) - throw new ClientException(string.Format(QmStrings.LoadingLadderMapsErrorFormat, response.ReasonPhrase)); + public async Task> LoadLadderMapsForAbbrAsync(string ladderAbbreviation) + { + HttpClient httpClient = GetHttpClient(); + string url = string.Format(qmSettings.GetLadderMapsUrlFormat, ladderAbbreviation); + HttpResponseMessage response = await httpClient.GetAsync(url); + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.LoadingLadderMapsErrorFormat, response.ReasonPhrase)); - return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); - } + return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + } - public async Task LoadLadderStatsForAbbrAsync(string ladderAbbreviation) - { - HttpClient httpClient = GetHttpClient(); - string url = string.Format(qmSettings.GetLadderStatsUrlFormat, ladderAbbreviation); - HttpResponseMessage response = await httpClient.GetAsync(url); - if (!response.IsSuccessStatusCode) - throw new ClientException(string.Format(QmStrings.LoadingLadderStatsErrorFormat, response.ReasonPhrase)); + public async Task LoadLadderStatsForAbbrAsync(string ladderAbbreviation) + { + HttpClient httpClient = GetHttpClient(); + string url = string.Format(qmSettings.GetLadderStatsUrlFormat, ladderAbbreviation); + HttpResponseMessage response = await httpClient.GetAsync(url); + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.LoadingLadderStatsErrorFormat, response.ReasonPhrase)); - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - } + return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + } - public async Task> LoadUserAccountsAsync() - { - HttpClient httpClient = GetHttpClient(); - HttpResponseMessage response = await httpClient.GetAsync(qmSettings.GetUserAccountsUrl); - if (!response.IsSuccessStatusCode) - throw new ClientException(string.Format(QmStrings.LoadingUserAccountsErrorFormat, response.ReasonPhrase)); + public async Task> LoadUserAccountsAsync() + { + HttpClient httpClient = GetHttpClient(); + HttpResponseMessage response = await httpClient.GetAsync(qmSettings.GetUserAccountsUrl); + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.LoadingUserAccountsErrorFormat, response.ReasonPhrase)); - return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); - } + return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + } - public async Task> LoadLaddersAsync() - { - HttpClient httpClient = GetHttpClient(); - HttpResponseMessage response = await httpClient.GetAsync(qmSettings.GetLaddersUrl); - if (!response.IsSuccessStatusCode) - throw new ClientException(string.Format(QmStrings.LoadingLaddersErrorFormat, response.ReasonPhrase)); + public async Task> LoadLaddersAsync() + { + HttpClient httpClient = GetHttpClient(); + HttpResponseMessage response = await httpClient.GetAsync(qmSettings.GetLaddersUrl); + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.LoadingLaddersErrorFormat, response.ReasonPhrase)); - return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); - } + return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + } - public async Task LoginAsync(string email, string password) - { - HttpClient httpClient = GetHttpClient(); - var postBodyContent = new StringContent(JsonConvert.SerializeObject(new QMLoginRequest() { Email = email, Password = password }), Encoding.Default, "application/json"); - var response = await httpClient.PostAsync(qmSettings.LoginUrl, postBodyContent); + public async Task LoginAsync(string email, string password) + { + HttpClient httpClient = GetHttpClient(); + var postBodyContent = new StringContent(JsonConvert.SerializeObject(new QMLoginRequest() { Email = email, Password = password }), Encoding.Default, "application/json"); + var response = await httpClient.PostAsync(qmSettings.LoginUrl, postBodyContent); - return await HandleLoginResponse(response, QmStrings.LoggingInUnknownErrorFormat); - } + return await HandleLoginResponse(response, QmStrings.LoggingInUnknownErrorFormat); + } - public async Task RefreshAsync() - { - HttpClient httpClient = GetHttpClient(); - HttpResponseMessage response = await httpClient.GetAsync(qmSettings.RefreshUrl); + public async Task RefreshAsync() + { + HttpClient httpClient = GetHttpClient(); + HttpResponseMessage response = await httpClient.GetAsync(qmSettings.RefreshUrl); - return await HandleLoginResponse(response, "Error refreshing token: {0}, {1}"); - } + return await HandleLoginResponse(response, "Error refreshing token: {0}, {1}"); + } - private async Task HandleLoginResponse(HttpResponseMessage response, string unknownErrorFormat) - { - if (!response.IsSuccessStatusCode) - return await HandleFailedLoginResponse(response, unknownErrorFormat); + private async Task HandleLoginResponse(HttpResponseMessage response, string unknownErrorFormat) + { + if (!response.IsSuccessStatusCode) + return await HandleFailedLoginResponse(response, unknownErrorFormat); - string responseBody = await response.Content.ReadAsStringAsync(); - QmAuthData authData = JsonConvert.DeserializeObject(responseBody); - if (authData == null) - throw new ClientException(responseBody); + string responseBody = await response.Content.ReadAsStringAsync(); + QmAuthData authData = JsonConvert.DeserializeObject(responseBody); + if (authData == null) + throw new ClientException(responseBody); - return authData; - } + return authData; + } - private async Task HandleFailedLoginResponse(HttpResponseMessage response, string unknownErrorFormat) + private async Task HandleFailedLoginResponse(HttpResponseMessage response, string unknownErrorFormat) + { + string responseBody = await response.Content.ReadAsStringAsync(); + string message; + switch (response.StatusCode) { - string responseBody = await response.Content.ReadAsStringAsync(); - string message; - switch (response.StatusCode) - { - case HttpStatusCode.BadGateway: - message = QmStrings.ServerUnreachableError; - break; - case HttpStatusCode.Unauthorized: - message = QmStrings.InvalidUsernamePasswordError; - break; - default: - message = string.Format(unknownErrorFormat, response.ReasonPhrase, responseBody); - break; - } - - throw new ClientRequestException(message, response.StatusCode); + case HttpStatusCode.BadGateway: + message = QmStrings.ServerUnreachableError; + break; + case HttpStatusCode.Unauthorized: + message = QmStrings.InvalidUsernamePasswordError; + break; + default: + message = string.Format(unknownErrorFormat, response.ReasonPhrase, responseBody); + break; } - public bool IsServerAvailable() - { - HttpClient httpClient = GetHttpClient(); - HttpResponseMessage response = httpClient.GetAsync(qmSettings.ServerStatusUrl).Result; - return response.IsSuccessStatusCode; - } + throw new ClientRequestException(message, response.StatusCode); + } - private HttpClient GetHttpClient() => - _httpClient ??= new HttpClient { BaseAddress = new Uri(qmSettings.BaseUrl), Timeout = TimeSpan.FromSeconds(10) }; + public bool IsServerAvailable() + { + HttpClient httpClient = GetHttpClient(); + HttpResponseMessage response = httpClient.GetAsync(qmSettings.ServerStatusUrl).Result; + return response.IsSuccessStatusCode; + } - public async Task QuickMatchRequestAsync(QmRequest qmRequest) - { - HttpClient httpClient = GetHttpClient(); - string url = string.Format(qmSettings.QuickMatchUrlFormat, qmRequest.Ladder, qmRequest.PlayerName); - HttpResponseMessage response = await httpClient.PostAsync(url, new StringContent(JsonConvert.SerializeObject(qmRequest), Encoding.Default, "application/json")); + private HttpClient GetHttpClient() => + _httpClient ??= new HttpClient { BaseAddress = new Uri(qmSettings.BaseUrl), Timeout = TimeSpan.FromSeconds(10) }; - string responseBody = await response.Content.ReadAsStringAsync(); - Logger.Log(responseBody); + public async Task QuickMatchRequestAsync(string ladder, string playerName, QmRequest qmRequest) + { + HttpClient httpClient = GetHttpClient(); + string url = string.Format(qmSettings.QuickMatchUrlFormat, ladder, playerName); + HttpResponseMessage response = await httpClient.PostAsync(url, new StringContent(JsonConvert.SerializeObject(qmRequest), Encoding.Default, "application/json")); - if (!response.IsSuccessStatusCode) - throw new ClientException(string.Format(QmStrings.RequestingMatchErrorFormat, response.ReasonPhrase)); + string responseBody = await response.Content.ReadAsStringAsync(); + Logger.Log(responseBody); - QmRequestResponse matchRequestResponse = JsonConvert.DeserializeObject(responseBody); - if (!(matchRequestResponse?.IsSuccessful ?? false)) - throw new ClientException(string.Format(QmStrings.RequestingMatchErrorFormat, matchRequestResponse?.Message ?? matchRequestResponse?.Description ?? "unknown")); + if (!response.IsSuccessStatusCode) + throw new ClientException(string.Format(QmStrings.RequestingMatchErrorFormat, response.ReasonPhrase)); - return matchRequestResponse; - } + QmRequestResponse matchRequestResponse = JsonConvert.DeserializeObject(responseBody); + if (!(matchRequestResponse?.IsSuccessful ?? false)) + throw new ClientException(string.Format(QmStrings.RequestingMatchErrorFormat, matchRequestResponse?.Message ?? matchRequestResponse?.Description ?? "unknown")); - public void Dispose() - { - _httpClient?.Dispose(); - } + return matchRequestResponse; + } + + public void Dispose() + { + _httpClient?.Dispose(); } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMatchFoundTimer.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMatchFoundTimer.cs new file mode 100644 index 000000000..132064083 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMatchFoundTimer.cs @@ -0,0 +1,25 @@ +using System; +using System.Timers; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public class QmMatchFoundTimer : Timer +{ + private const int MatchupFoundTimerInterval = 100; + public QmRequestSpawnResponseSpawn Spawn { get; set; } + + public QmMatchFoundTimer() : base(MatchupFoundTimerInterval) + { + AutoReset = true; + } + + public int GetInterval() => MatchupFoundTimerInterval; + + public void SetSpawn(QmRequestSpawnResponseSpawn spawn) + { + Spawn = spawn; + } + + public void SetElapsedAction(Action elapsedAction) => Elapsed += (_, _) => elapsedAction(); +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs index dccee908a..bddafd8c0 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs @@ -1,9 +1,8 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public static class QmRequestTypes { - public static class QmRequestTypes - { - public const string Quit = "quit"; - public const string Update = "update"; - public const string MatchMeUp = "match me up"; - } -} + public const string Quit = "quit"; + public const string Update = "update"; + public const string MatchMeUp = "match me up"; +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs index 6fcdfe7dd..f61436a01 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs @@ -1,12 +1,11 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public static class QmResponseTypes { - public static class QmResponseTypes - { - public const string Error = "error"; - public const string Fatal = "fatal"; - public const string Spawn = "spawn"; - public const string Update = "update"; - public const string Quit = "quit"; - public const string Wait = "please wait"; - } + public const string Error = "error"; + public const string Fatal = "fatal"; + public const string Spawn = "spawn"; + public const string Update = "update"; + public const string Quit = "quit"; + public const string Wait = "please wait"; } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs index 208a9963f..ec97d2bbe 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs @@ -9,6 +9,7 @@ using ClientCore.Exceptions; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Online; using JWT; using JWT.Algorithms; using JWT.Exceptions; @@ -19,6 +20,8 @@ namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; public class QmService : IDisposable { + public const string QmVersion = "2.0"; + private readonly QmUserSettingsService userSettingsService; private readonly QmApiService apiService; private readonly QmSettingsService settingsService; @@ -29,7 +32,8 @@ public class QmService : IDisposable private static QmService _instance; private readonly Timer retryRequestmatchTimer; - private QmMatchRequest qmMatchRequest; + private QmUserAccount userAccount; + private IEnumerable mapSides; private QmService() { @@ -44,8 +48,6 @@ private QmService() retryRequestmatchTimer = new Timer(); retryRequestmatchTimer.AutoReset = false; retryRequestmatchTimer.Elapsed += (_, _) => RetryRequestMatchAsync(); - - qmMatchRequest = new QmMatchRequest(); } public event EventHandler QmEvent; @@ -121,25 +123,22 @@ public bool HasToken() public void SetUserAccount(QmUserAccount userAccount) { string laddAbbr = userAccount?.Ladder?.Abbreviation; + this.userAccount = userAccount; qmUserSettings.Ladder = laddAbbr; - qmMatchRequest.Ladder = laddAbbr; - qmMatchRequest.PlayerName = userAccount?.Username; userSettingsService.SaveSettings(); QmEvent?.Invoke(this, new QmUserAccountSelectedEvent(userAccount)); } public void SetMasterSide(QmSide side) { - qmUserSettings.SideId = side?.LocalId; - qmMatchRequest.Side = side?.LocalId ?? -1; + qmUserSettings.SideId = side?.LocalId ?? -1; userSettingsService.SaveSettings(); QmEvent?.Invoke(this, new QmMasterSideSelected(side)); } - public void SetMapSides(string[] mapSides) + public void SetMapSides(IEnumerable mapSides) { - // TODO call this from the lobby panel - qmMatchRequest.MapSides = mapSides; + this.mapSides = mapSides; } public void LoadLaddersAndUserAccountsAsync() => @@ -192,15 +191,35 @@ public void LoadLadderStatsForAbbrAsync(string ladderAbbr) => public void RequestMatchAsync() => ExecuteRequest(new QmRequestingMatchEvent(CancelRequestMatchAsync), async () => { - QmRequestResponse response = await apiService.QuickMatchRequestAsync(qmMatchRequest); + QmRequestResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, GetMatchRequest()); HandleQuickMatchResponse(response); }); /// /// This is called when the user clicks the "I'm Ready" button in the match found dialog. /// - public void AcceptMatchAsync() + public void AcceptMatchAsync(QmRequestSpawnResponseSpawn spawn) { + ExecuteRequest(new QmReadyRequestMatchEvent(), async () => + { + var readyRequest = new QmReadyRequest(spawn.Settings.Seed); + QmRequestResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, readyRequest); + HandleQuickMatchResponse(response); + }); + } + + /// + /// This is called when the user clicks the "Cancel" button in the match found dialog. + /// + public void RejectMatchAsync(QmRequestSpawnResponseSpawn spawn) + { + ExecuteRequest(new QmNotReadyRequestMatchEvent(), async () => + { + var notReadyRequest = new QmNotReadyRequest(spawn.Settings.Seed); + QmRequestResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, notReadyRequest); + HandleQuickMatchResponse(response); + }); + CancelRequestMatchAsync(); } public void WriteSpawnIni(QmRequestSpawnResponse spawnResponse) @@ -261,6 +280,32 @@ public void Dispose() apiService.Dispose(); } + private QmMatchRequest GetMatchRequest() + { + if (userAccount == null) + throw new ClientException("No user account selected"); + + if (userAccount.Ladder == null) + throw new ClientException("No user account ladder selected"); + + if (!qmUserSettings.SideId.HasValue) + throw new ClientException("No side selected"); + + var fileHashCalculator = new FileHashCalculator(); + + return new QmMatchRequest() + { + IPv6Address = string.Empty, + IPAddress = "98.111.198.94", + IPPort = 51144, + LanIP = "192.168.86.200", + LanPort = 51144, + Side = qmUserSettings.SideId.Value, + MapSides = mapSides, + ExeHash = fileHashCalculator.GetCompleteHash() + }; + } + private void RetryRequestMatchAsync() => RequestMatchAsync(); @@ -307,8 +352,7 @@ private void CancelRequestMatchAsync() => ExecuteRequest(new QmCancelingRequestMatchEvent(), async () => { retryRequestmatchTimer.Stop(); - qmMatchRequest.Type = QmRequestTypes.Quit; - QmRequestResponse response = await apiService.QuickMatchRequestAsync(new QmQuitRequest()); + QmRequestResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, new QmQuitRequest()); QmEvent?.Invoke(this, new QmRequestResponseEvent(response)); }); diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs index de43e2d31..d33643820 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs @@ -6,108 +6,107 @@ using Rampastring.Tools; using Rampastring.XNAUI; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public class QmSettingsService { - public class QmSettingsService + private static QmSettingsService Instance; + private static readonly string SettingsFile = ClientConfiguration.Instance.QuickMatchPath; + + private const string BasicSectionKey = "Basic"; + private const string SoundsSectionKey = "Sounds"; + private const string HeaderLogosSectionKey = "HeaderLogos"; + + private const string BaseUrlKey = "BaseUrl"; + private const string LoginUrlKey = "LoginUrl"; + private const string RefreshUrlKey = "RefreshUrl"; + private const string ServerStatusUrlKey = "ServerStatusUrl"; + private const string GetUserAccountsUrlKey = "GetUserAccountsUrl"; + private const string GetLaddersUrlKey = "GetLaddersUrl"; + private const string GetLadderMapsUrlKey = "GetLadderMapsUrl"; + + private const string MatchFoundSoundFileKey = "MatchFoundSoundFile"; + private const string AllowedLaddersKey = "AllowedLadders"; + + private QmSettings qmSettings; + + private QmSettingsService() + { + } + + public static QmSettingsService GetInstance() => Instance ??= new QmSettingsService(); + + public QmSettings GetSettings() => qmSettings ??= LoadSettings(); + + private static QmSettings LoadSettings() + { + var settings = new QmSettings(); + if (!File.Exists(SettingsFile)) + SaveSettings(settings); // init the settings file + + var iniFile = new IniFile(SettingsFile); + LoadBasicSettings(iniFile, settings); + LoadSoundSettings(iniFile, settings); + LoadHeaderLogoSettings(iniFile, settings); + + return settings; + } + + private static void LoadBasicSettings(IniFile iniFile, QmSettings settings) + { + IniSection basicSection = iniFile.GetSection(BasicSectionKey); + if (basicSection == null) + return; + + settings.BaseUrl = basicSection.GetStringValue(BaseUrlKey, QmSettings.DefaultBaseUrl); + settings.LoginUrl = basicSection.GetStringValue(LoginUrlKey, QmSettings.DefaultLoginUrl); + settings.RefreshUrl = basicSection.GetStringValue(RefreshUrlKey, QmSettings.DefaultRefreshUrl); + settings.ServerStatusUrl = basicSection.GetStringValue(ServerStatusUrlKey, QmSettings.DefaultServerStatusUrl); + settings.GetUserAccountsUrl = basicSection.GetStringValue(GetUserAccountsUrlKey, QmSettings.DefaultGetUserAccountsUrl); + settings.GetLaddersUrl = basicSection.GetStringValue(GetLaddersUrlKey, QmSettings.DefaultGetLaddersUrl); + settings.GetLadderMapsUrlFormat = basicSection.GetStringValue(GetLadderMapsUrlKey, QmSettings.DefaultGetLadderMapsUrl); + settings.MatchFoundWaitSeconds = basicSection.GetIntValue(GetLadderMapsUrlKey, QmSettings.DefaultMatchFoundWaitSeconds); + settings.AllowedLadders = basicSection.GetStringValue(AllowedLaddersKey, string.Empty).Split(',').ToList(); + } + + private static void LoadSoundSettings(IniFile iniFile, QmSettings settings) + { + IniSection soundsSection = iniFile.GetSection(SoundsSectionKey); + if (soundsSection == null) + return; + + string matchFoundSoundFile = soundsSection.GetStringValue(MatchFoundSoundFileKey, null); + if (matchFoundSoundFile == null) + return; + + matchFoundSoundFile = SafePath.CombineFilePath("Resources", matchFoundSoundFile); + if (File.Exists(matchFoundSoundFile)) + settings.MatchFoundSoundFile = matchFoundSoundFile; + } + + private static void LoadHeaderLogoSettings(IniFile iniFile, QmSettings settings) + { + IniSection headerLogosSection = iniFile.GetSection(HeaderLogosSectionKey); + if (headerLogosSection == null) + return; + + foreach (KeyValuePair keyValuePair in headerLogosSection.Keys.Where(keyValuePair => AssetLoader.AssetExists(keyValuePair.Value))) + settings.HeaderLogos.Add(keyValuePair.Key, AssetLoader.LoadTexture(keyValuePair.Value)); + } + + public static void SaveSettings(QmSettings settings) { - private static QmSettingsService Instance; - private static readonly string SettingsFile = ClientConfiguration.Instance.QuickMatchPath; - - private const string BasicSectionKey = "Basic"; - private const string SoundsSectionKey = "Sounds"; - private const string HeaderLogosSectionKey = "HeaderLogos"; - - private const string BaseUrlKey = "BaseUrl"; - private const string LoginUrlKey = "LoginUrl"; - private const string RefreshUrlKey = "RefreshUrl"; - private const string ServerStatusUrlKey = "ServerStatusUrl"; - private const string GetUserAccountsUrlKey = "GetUserAccountsUrl"; - private const string GetLaddersUrlKey = "GetLaddersUrl"; - private const string GetLadderMapsUrlKey = "GetLadderMapsUrl"; - - private const string MatchFoundSoundFileKey = "MatchFoundSoundFile"; - private const string AllowedLaddersKey = "AllowedLadders"; - - private QmSettings qmSettings; - - private QmSettingsService() - { - } - - public static QmSettingsService GetInstance() => Instance ??= new QmSettingsService(); - - public QmSettings GetSettings() => qmSettings ??= LoadSettings(); - - private static QmSettings LoadSettings() - { - var settings = new QmSettings(); - if (!File.Exists(SettingsFile)) - SaveSettings(settings); // init the settings file - - var iniFile = new IniFile(SettingsFile); - LoadBasicSettings(iniFile, settings); - LoadSoundSettings(iniFile, settings); - LoadHeaderLogoSettings(iniFile, settings); - - return settings; - } - - private static void LoadBasicSettings(IniFile iniFile, QmSettings settings) - { - IniSection basicSection = iniFile.GetSection(BasicSectionKey); - if (basicSection == null) - return; - - settings.BaseUrl = basicSection.GetStringValue(BaseUrlKey, QmSettings.DefaultBaseUrl); - settings.LoginUrl = basicSection.GetStringValue(LoginUrlKey, QmSettings.DefaultLoginUrl); - settings.RefreshUrl = basicSection.GetStringValue(RefreshUrlKey, QmSettings.DefaultRefreshUrl); - settings.ServerStatusUrl = basicSection.GetStringValue(ServerStatusUrlKey, QmSettings.DefaultServerStatusUrl); - settings.GetUserAccountsUrl = basicSection.GetStringValue(GetUserAccountsUrlKey, QmSettings.DefaultGetUserAccountsUrl); - settings.GetLaddersUrl = basicSection.GetStringValue(GetLaddersUrlKey, QmSettings.DefaultGetLaddersUrl); - settings.GetLadderMapsUrlFormat = basicSection.GetStringValue(GetLadderMapsUrlKey, QmSettings.DefaultGetLadderMapsUrl); - settings.MatchFoundWaitSeconds = basicSection.GetIntValue(GetLadderMapsUrlKey, QmSettings.DefaultMatchFoundWaitSeconds); - settings.AllowedLadders = basicSection.GetStringValue(AllowedLaddersKey, string.Empty).Split(',').ToList(); - } - - private static void LoadSoundSettings(IniFile iniFile, QmSettings settings) - { - IniSection soundsSection = iniFile.GetSection(SoundsSectionKey); - if (soundsSection == null) - return; - - string matchFoundSoundFile = soundsSection.GetStringValue(MatchFoundSoundFileKey, null); - if (matchFoundSoundFile == null) - return; - - matchFoundSoundFile = SafePath.CombineFilePath("Resources", matchFoundSoundFile); - if (File.Exists(matchFoundSoundFile)) - settings.MatchFoundSoundFile = matchFoundSoundFile; - } - - private static void LoadHeaderLogoSettings(IniFile iniFile, QmSettings settings) - { - IniSection headerLogosSection = iniFile.GetSection(HeaderLogosSectionKey); - if (headerLogosSection == null) - return; - - foreach (KeyValuePair keyValuePair in headerLogosSection.Keys.Where(keyValuePair => AssetLoader.AssetExists(keyValuePair.Value))) - settings.HeaderLogos.Add(keyValuePair.Key, AssetLoader.LoadTexture(keyValuePair.Value)); - } - - public static void SaveSettings(QmSettings settings) - { - var iniFile = new IniFile(); - var basicSection = new IniSection(BasicSectionKey); - basicSection.AddKey(BaseUrlKey, settings.BaseUrl); - basicSection.AddKey(LoginUrlKey, settings.LoginUrl); - basicSection.AddKey(RefreshUrlKey, settings.RefreshUrl); - basicSection.AddKey(ServerStatusUrlKey, settings.ServerStatusUrl); - basicSection.AddKey(GetUserAccountsUrlKey, settings.GetUserAccountsUrl); - basicSection.AddKey(GetLaddersUrlKey, settings.GetLaddersUrl); - basicSection.AddKey(GetLadderMapsUrlKey, settings.GetLadderMapsUrlFormat); - - iniFile.AddSection(basicSection); - iniFile.WriteIniFile(SettingsFile); - } + var iniFile = new IniFile(); + var basicSection = new IniSection(BasicSectionKey); + basicSection.AddKey(BaseUrlKey, settings.BaseUrl); + basicSection.AddKey(LoginUrlKey, settings.LoginUrl); + basicSection.AddKey(RefreshUrlKey, settings.RefreshUrl); + basicSection.AddKey(ServerStatusUrlKey, settings.ServerStatusUrl); + basicSection.AddKey(GetUserAccountsUrlKey, settings.GetUserAccountsUrl); + basicSection.AddKey(GetLaddersUrlKey, settings.GetLaddersUrl); + basicSection.AddKey(GetLadderMapsUrlKey, settings.GetLadderMapsUrlFormat); + + iniFile.AddSection(basicSection); + iniFile.WriteIniFile(SettingsFile); } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs index 78a20bac8..e486bb8b1 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs @@ -1,24 +1,23 @@ using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public class QmUserSettingsService { - public class QmUserSettingsService - { - private QmUserSettings qmUserSettings; + private QmUserSettings qmUserSettings; - private static QmUserSettingsService instance; + private static QmUserSettingsService instance; - private QmUserSettingsService() - { + private QmUserSettingsService() + { - } + } - public static QmUserSettingsService GetInstance() => instance ??= new QmUserSettingsService(); + public static QmUserSettingsService GetInstance() => instance ??= new QmUserSettingsService(); - public QmUserSettings GetSettings() => qmUserSettings ??= QmUserSettings.Load(); + public QmUserSettings GetSettings() => qmUserSettings ??= QmUserSettings.Load(); - public void SaveSettings() => qmUserSettings.Save(); + public void SaveSettings() => qmUserSettings.Save(); - public void ClearAuthData() => qmUserSettings.ClearAuthData(); - } + public void ClearAuthData() => qmUserSettings.ClearAuthData(); } \ No newline at end of file From 34d74b1bfbf0073e2782a88bd2e1c6bea471fafe Mon Sep 17 00:00:00 2001 From: devo1929 Date: Tue, 8 Nov 2022 09:22:46 -0500 Subject: [PATCH 13/19] Create mock data for testing purposes --- DXMainClient/DXMainClient.csproj | 72 +++++ .../CnCNet/QuickMatch/QmApiService.cs | 21 +- .../CnCNet/QuickMatch/QmMockApiService.cs | 49 ++++ .../qm_find_match_fatal_response.json | 5 + .../qm_find_match_game_spawned_request.json | 14 + .../qm_find_match_game_spawned_response.json | 3 + .../qm_find_match_gamefinished_request.json | 6 + .../qm_find_match_gamefinished_response.json | 3 + .../qm_find_match_im_ready_request.json | 6 + .../qm_find_match_im_ready_response.json | 3 + .../qm_find_match_please_wait_response.json | 5 + .../qm_find_match_reached_request.json | 6 + .../qm_find_match_reached_response.json | 3 + .../QuickMatch/qm_find_match_request.json | 52 ++++ .../qm_find_match_spawn_response.json | 50 ++++ .../qm_ladder_maps_ra2_response.json | 76 +++++ .../qm_ladder_maps_yr_response.json | 76 +++++ .../QuickMatch/qm_ladder_stats_response.json | 12 + .../QuickMatch/qm_ladders_response.json | 273 ++++++++++++++++++ .../QuickMatch/qm_login_response.json | 5 + .../QuickMatch/qm_user_accounts_response.json | 38 +++ 21 files changed, 768 insertions(+), 10 deletions(-) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMockApiService.cs create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_fatal_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_game_spawned_request.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_game_spawned_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_gamefinished_request.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_gamefinished_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_im_ready_request.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_im_ready_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_please_wait_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_reached_request.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_reached_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_request.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_spawn_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_ladder_maps_ra2_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_ladder_maps_yr_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_ladder_stats_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_ladders_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_login_response.json create mode 100644 DXMainClient/MockData/QuickMatch/qm_user_accounts_response.json diff --git a/DXMainClient/DXMainClient.csproj b/DXMainClient/DXMainClient.csproj index 48792467c..889d00340 100644 --- a/DXMainClient/DXMainClient.csproj +++ b/DXMainClient/DXMainClient.csproj @@ -47,6 +47,78 @@ + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + + + + + + + + + + + + + + + + diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs index 1eb80ae23..2e5ed4d27 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs @@ -13,18 +13,19 @@ namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; public class QmApiService : IDisposable { + private static bool useMockService = true; private HttpClient _httpClient; private readonly QmSettings qmSettings; private string _token; private static QmApiService instance; - private QmApiService() + protected QmApiService() { qmSettings = QmSettingsService.GetInstance().GetSettings(); } - public static QmApiService GetInstance() => instance ??= new QmApiService(); + public static QmApiService GetInstance() => instance ??= useMockService ? new QmMockApiService() : new QmApiService(); public void SetToken(string token) { @@ -35,7 +36,7 @@ public void SetToken(string token) httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_token}"); } - public async Task> LoadLadderMapsForAbbrAsync(string ladderAbbreviation) + public virtual async Task> LoadLadderMapsForAbbrAsync(string ladderAbbreviation) { HttpClient httpClient = GetHttpClient(); string url = string.Format(qmSettings.GetLadderMapsUrlFormat, ladderAbbreviation); @@ -46,7 +47,7 @@ public async Task> LoadLadderMapsForAbbrAsync(string la return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); } - public async Task LoadLadderStatsForAbbrAsync(string ladderAbbreviation) + public virtual async Task LoadLadderStatsForAbbrAsync(string ladderAbbreviation) { HttpClient httpClient = GetHttpClient(); string url = string.Format(qmSettings.GetLadderStatsUrlFormat, ladderAbbreviation); @@ -57,7 +58,7 @@ public async Task LoadLadderStatsForAbbrAsync(string ladderAbbrev return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); } - public async Task> LoadUserAccountsAsync() + public virtual async Task> LoadUserAccountsAsync() { HttpClient httpClient = GetHttpClient(); HttpResponseMessage response = await httpClient.GetAsync(qmSettings.GetUserAccountsUrl); @@ -67,7 +68,7 @@ public async Task> LoadUserAccountsAsync() return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); } - public async Task> LoadLaddersAsync() + public virtual async Task> LoadLaddersAsync() { HttpClient httpClient = GetHttpClient(); HttpResponseMessage response = await httpClient.GetAsync(qmSettings.GetLaddersUrl); @@ -77,7 +78,7 @@ public async Task> LoadLaddersAsync() return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); } - public async Task LoginAsync(string email, string password) + public virtual async Task LoginAsync(string email, string password) { HttpClient httpClient = GetHttpClient(); var postBodyContent = new StringContent(JsonConvert.SerializeObject(new QMLoginRequest() { Email = email, Password = password }), Encoding.Default, "application/json"); @@ -86,7 +87,7 @@ public async Task LoginAsync(string email, string password) return await HandleLoginResponse(response, QmStrings.LoggingInUnknownErrorFormat); } - public async Task RefreshAsync() + public virtual async Task RefreshAsync() { HttpClient httpClient = GetHttpClient(); HttpResponseMessage response = await httpClient.GetAsync(qmSettings.RefreshUrl); @@ -127,7 +128,7 @@ private async Task HandleFailedLoginResponse(HttpResponseMessage res throw new ClientRequestException(message, response.StatusCode); } - public bool IsServerAvailable() + public virtual bool IsServerAvailable() { HttpClient httpClient = GetHttpClient(); HttpResponseMessage response = httpClient.GetAsync(qmSettings.ServerStatusUrl).Result; @@ -137,7 +138,7 @@ public bool IsServerAvailable() private HttpClient GetHttpClient() => _httpClient ??= new HttpClient { BaseAddress = new Uri(qmSettings.BaseUrl), Timeout = TimeSpan.FromSeconds(10) }; - public async Task QuickMatchRequestAsync(string ladder, string playerName, QmRequest qmRequest) + public virtual async Task QuickMatchRequestAsync(string ladder, string playerName, QmRequest qmRequest) { HttpClient httpClient = GetHttpClient(); string url = string.Format(qmSettings.QuickMatchUrlFormat, ladder, playerName); diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMockApiService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMockApiService.cs new file mode 100644 index 000000000..0485c378d --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMockApiService.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using ClientCore.Exceptions; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using Newtonsoft.Json; +using Rampastring.Tools; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; + +public class QmMockApiService : QmApiService +{ + public override async Task> LoadLadderMapsForAbbrAsync(string ladderAbbreviation) => LoadMockData>($"qm_ladder_maps_{ladderAbbreviation}_response.json"); + + public override async Task LoadLadderStatsForAbbrAsync(string ladderAbbreviation) => LoadMockData("qm_ladder_stats_response.json"); + + public override async Task> LoadUserAccountsAsync() => LoadMockData>("qm_user_accounts_response.json"); + + public override async Task> LoadLaddersAsync() => LoadMockData>("qm_ladders_response.json"); + + public override async Task LoginAsync(string email, string password) => LoadMockData("qm_login_response.json"); + + public override async Task RefreshAsync() => LoadMockData("qm_login_response.json"); + + public override async Task QuickMatchRequestAsync(string ladder, string playerName, QmRequest qmRequest) + { + const string responseType = QmResponseTypes.Wait; + return responseType switch + { + QmResponseTypes.Error => LoadMockData("qm_find_match_spawn_response.json"), + QmResponseTypes.Spawn => LoadMockData("qm_find_match_spawn_response.json"), + QmResponseTypes.Wait => LoadMockData("qm_find_match_please_wait_response.json"), + _ => throw new NotImplementedException("unknown mock response type") + }; + } + + public override bool IsServerAvailable() => true; + + private T LoadMockData(string mockDataFileName) + { + string content = File.ReadAllText($"MockData/QuickMatch/{mockDataFileName}"); + + return JsonConvert.DeserializeObject(content); + } +} \ No newline at end of file diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_fatal_response.json b/DXMainClient/MockData/QuickMatch/qm_find_match_fatal_response.json new file mode 100644 index 000000000..b00b676fe --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_fatal_response.json @@ -0,0 +1,5 @@ +{ + "type": "fatal", + "message": "fatal message", + "description": "fatal description" +} diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_game_spawned_request.json b/DXMainClient/MockData/QuickMatch/qm_find_match_game_spawned_request.json new file mode 100644 index 000000000..8f38d4325 --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_game_spawned_request.json @@ -0,0 +1,14 @@ +{ + "peers": [ + { + "address": "127.0.0.1", + "id": "1234567890", + "port": 51143, + "rtt": 59 + } + ], + "seed": 123, + "status": "GameSpawned", + "type": "update", + "version": "2.0" +} diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_game_spawned_response.json b/DXMainClient/MockData/QuickMatch/qm_find_match_game_spawned_response.json new file mode 100644 index 000000000..ad0bb6dfe --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_game_spawned_response.json @@ -0,0 +1,3 @@ +{ + "message": "update qm match: GameSpawned" +} \ No newline at end of file diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_gamefinished_request.json b/DXMainClient/MockData/QuickMatch/qm_find_match_gamefinished_request.json new file mode 100644 index 000000000..f1fe941cb --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_gamefinished_request.json @@ -0,0 +1,6 @@ +{ + "seed": 123, + "status": "GameFinished", + "type": "update", + "version": "1.74" +} diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_gamefinished_response.json b/DXMainClient/MockData/QuickMatch/qm_find_match_gamefinished_response.json new file mode 100644 index 000000000..e318f55ef --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_gamefinished_response.json @@ -0,0 +1,3 @@ +{ + "message": "update qm match: GameFinished" +} \ No newline at end of file diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_im_ready_request.json b/DXMainClient/MockData/QuickMatch/qm_find_match_im_ready_request.json new file mode 100644 index 000000000..cd8feb787 --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_im_ready_request.json @@ -0,0 +1,6 @@ +{ + "seed": 123, + "status": "Ready", + "type": "update", + "version": "2.0" +} diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_im_ready_response.json b/DXMainClient/MockData/QuickMatch/qm_find_match_im_ready_response.json new file mode 100644 index 000000000..ffd750548 --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_im_ready_response.json @@ -0,0 +1,3 @@ +{ + "message": "update qm match: Ready" +} \ No newline at end of file diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_please_wait_response.json b/DXMainClient/MockData/QuickMatch/qm_find_match_please_wait_response.json new file mode 100644 index 000000000..36f913e5f --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_please_wait_response.json @@ -0,0 +1,5 @@ +{ + "type": "please wait", + "checkback": 10, + "no_sooner_than": 5 +} diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_reached_request.json b/DXMainClient/MockData/QuickMatch/qm_find_match_reached_request.json new file mode 100644 index 000000000..5f80a94e0 --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_reached_request.json @@ -0,0 +1,6 @@ +{ + "seed": 123, + "status": "Reached", + "type": "update", + "version": "2.0" +} diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_reached_response.json b/DXMainClient/MockData/QuickMatch/qm_find_match_reached_response.json new file mode 100644 index 000000000..b7bafb661 --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_reached_response.json @@ -0,0 +1,3 @@ +{ + "message": "update qm match: Reached" +} \ No newline at end of file diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_request.json b/DXMainClient/MockData/QuickMatch/qm_find_match_request.json new file mode 100644 index 000000000..401c10d60 --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_request.json @@ -0,0 +1,52 @@ +{ + "ai_dat": false, + "ddraw": "38f9496b915547ea816a1cd716b65c0d", + "exe_hash": "f265a5c6ce05488884563a5de6e323d7", + "ip_address": "127.0.0.1", + "ip_port": 51143, + "ipv6_address": "", + "ipv6_port": 0, + "lan_ip": "127.0.0.1", + "lan_port": 51143, + "map_bitfield": 2147483647, + "map_sides": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "platform": "win32", + "session": "", + "side": 0, + "type": "match me up", + "version": "2.0" +} diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_spawn_response.json b/DXMainClient/MockData/QuickMatch/qm_find_match_spawn_response.json new file mode 100644 index 000000000..1b646331f --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_spawn_response.json @@ -0,0 +1,50 @@ +{ + "type": "spawn", + "gameID": 456, + "spawn": { + "SpawnLocations": { + "Multi2": -1, + "Multi1": -1 + }, + "Settings": { + "UIMapName": "Test Map", + "MapHash": "35f8f92ebbb94b62918326da5837fedd", + "Seed": 123, + "GameID": 456, + "WOLGameID": 456, + "Host": "No", + "IsSpectator": "No", + "Name": "TestUserMe", + "Port": 51143, + "Side": 0, + "Color": 1, + "GameSpeed": "0", + "Credits": "10000", + "UnitCount": "0", + "Superweapons": "Yes", + "Tournament": "1", + "ShortGame": "Yes", + "Bases": "Yes", + "MCVRedeploy": "Yes", + "MultipleFactory": "Yes", + "Crates": "No", + "GameMode": "Battle", + "FrameSendRate": "4", + "DisableSWvsYuri": "Yes" + }, + "Other1": { + "Name": "TestUser2", + "Side": 0, + "Color": 0, + "Ip": "127.0.0.1", + "Port": 51143, + "IPv6": "", + "PortV6": 0, + "LanIP": "", + "LanPort": 51143 + } + }, + "client": { + "show_map_preview": 1 + } +} diff --git a/DXMainClient/MockData/QuickMatch/qm_ladder_maps_ra2_response.json b/DXMainClient/MockData/QuickMatch/qm_ladder_maps_ra2_response.json new file mode 100644 index 000000000..9ee8f631e --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_ladder_maps_ra2_response.json @@ -0,0 +1,76 @@ +[ + { + "id": 2571, + "ladder_id": 0, + "map_id": 1412, + "description": "Across the Frost", + "bit_idx": 0, + "valid": 1, + "spawn_order": "0,0", + "created_at": "2022-10-31 23:07:40", + "updated_at": "2022-10-31 23:07:40", + "team1_spawn_order": " ", + "team2_spawn_order": "", + "allowed_sides": [ + 0, + 1, + 3, + 4, + 5, + 6, + 7, + 8, + 2, + 9, + -1 + ], + "admin_description": "Across the Frost", + "map_pool_id": 56, + "rejectable": 1, + "default_reject": 0, + "hash": "e13e94fc4b4d19e9be93682f5e31d8e416f762f5", + "map": { + "id": 1412, + "hash": "e13e94fc4b4d19e9be93682f5e31d8e416f762f5", + "name": "across_the_frost_e13e94fc4b4d19e9be93682f5e31d8e416f762f5", + "ladder_id": 1 + } + }, + { + "id": 2572, + "ladder_id": 0, + "map_id": 669, + "description": "Arabian Oasis", + "bit_idx": 1, + "valid": 1, + "spawn_order": "0,0", + "created_at": "2022-10-31 23:07:40", + "updated_at": "2022-10-31 23:07:40", + "team1_spawn_order": " ", + "team2_spawn_order": "", + "allowed_sides": [ + 0, + 1, + 3, + 4, + 5, + 6, + 7, + 8, + 2, + 9, + -1 + ], + "admin_description": "Arabian Oasis", + "map_pool_id": 56, + "rejectable": 1, + "default_reject": 0, + "hash": "37ee5d7c1e4127538979857c45b1747af36e8f64", + "map": { + "id": 669, + "hash": "37ee5d7c1e4127538979857c45b1747af36e8f64", + "name": "Arabian Oasis v2.2", + "ladder_id": 1 + } + } +] diff --git a/DXMainClient/MockData/QuickMatch/qm_ladder_maps_yr_response.json b/DXMainClient/MockData/QuickMatch/qm_ladder_maps_yr_response.json new file mode 100644 index 000000000..16a921bfb --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_ladder_maps_yr_response.json @@ -0,0 +1,76 @@ +[ + { + "id": 2573, + "ladder_id": 0, + "map_id": 680, + "description": "Cavern's Of Siberia", + "bit_idx": 2, + "valid": 1, + "spawn_order": "0,0", + "created_at": "2022-10-31 23:07:40", + "updated_at": "2022-10-31 23:07:40", + "team1_spawn_order": " ", + "team2_spawn_order": "", + "allowed_sides": [ + 0, + 1, + 3, + 4, + 5, + 6, + 7, + 8, + 2, + 9, + -1 + ], + "admin_description": "Cavern's Of Siberia", + "map_pool_id": 56, + "rejectable": 1, + "default_reject": 0, + "hash": "2f11a7459c9951fbfd6b5bbd73cccd7cf9b0bf5c", + "map": { + "id": 680, + "hash": "2f11a7459c9951fbfd6b5bbd73cccd7cf9b0bf5c", + "name": "4_caverns_v410", + "ladder_id": 1 + } + }, + { + "id": 2574, + "ladder_id": 1, + "map_id": 21, + "description": "Coldest Peak", + "bit_idx": 3, + "valid": 1, + "spawn_order": "0,0", + "created_at": "2022-10-31 23:07:40", + "updated_at": "2022-10-31 23:07:40", + "team1_spawn_order": " ", + "team2_spawn_order": "", + "allowed_sides": [ + 0, + 1, + 3, + 4, + 5, + 6, + 7, + 8, + 2, + 9, + -1 + ], + "admin_description": "Coldest Peak", + "map_pool_id": 56, + "rejectable": 1, + "default_reject": 0, + "hash": "ffe59ccf68fd743ee8849c615af84180396c8e67", + "map": { + "id": 21, + "hash": "ffe59ccf68fd743ee8849c615af84180396c8e67", + "name": "coldest.map", + "ladder_id": 1 + } + } +] diff --git a/DXMainClient/MockData/QuickMatch/qm_ladder_stats_response.json b/DXMainClient/MockData/QuickMatch/qm_ladder_stats_response.json new file mode 100644 index 000000000..0ef7b5b44 --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_ladder_stats_response.json @@ -0,0 +1,12 @@ +{ + "recentMatchedPlayers": 0, + "queuedPlayers": 2, + "past24hMatches": 0, + "recentMatches": 0, + "activeMatches": 0, + "time": { + "date": "2022-11-08 11:03:14.546747", + "timezone_type": 3, + "timezone": "UTC" + } +} diff --git a/DXMainClient/MockData/QuickMatch/qm_ladders_response.json b/DXMainClient/MockData/QuickMatch/qm_ladders_response.json new file mode 100644 index 000000000..06092657a --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_ladders_response.json @@ -0,0 +1,273 @@ +[ + { + "id": 1, + "name": "Ladder 1", + "abbreviation": "yr", + "game": "yr", + "created_at": "-0001-11-30 00:00:00", + "updated_at": "2022-11-01 00:16:36", + "clans_allowed": 0, + "game_object_schema_id": 1, + "map_pool_id": 56, + "private": 0, + "sides": [ + { + "id": 1, + "ladder_id": 1, + "local_id": 0, + "name": "America", + "created_at": "2017-07-28 09:58:22", + "updated_at": "2017-07-28 09:58:22" + }, + { + "id": 2, + "ladder_id": 1, + "local_id": 1, + "name": "Korea", + "created_at": "2017-07-28 09:58:22", + "updated_at": "2017-07-28 09:58:22" + }, + { + "id": 3, + "ladder_id": 1, + "local_id": 3, + "name": "Germany", + "created_at": "2017-07-28 09:58:22", + "updated_at": "2017-07-28 09:58:22" + }, + { + "id": 4, + "ladder_id": 1, + "local_id": 4, + "name": "Great Britain", + "created_at": "2017-07-28 09:58:22", + "updated_at": "2017-07-28 09:58:22" + }, + { + "id": 5, + "ladder_id": 1, + "local_id": 5, + "name": "Libya", + "created_at": "2017-07-28 09:58:22", + "updated_at": "2017-07-28 09:58:22" + }, + { + "id": 6, + "ladder_id": 1, + "local_id": 6, + "name": "Iraq", + "created_at": "2017-07-28 09:58:22", + "updated_at": "2017-07-28 09:58:22" + }, + { + "id": 7, + "ladder_id": 1, + "local_id": 7, + "name": "Cuba", + "created_at": "2017-07-28 09:58:22", + "updated_at": "2017-07-28 09:58:22" + }, + { + "id": 8, + "ladder_id": 1, + "local_id": 8, + "name": "Russia", + "created_at": "2017-07-28 09:58:22", + "updated_at": "2017-07-28 09:58:22" + }, + { + "id": 13, + "ladder_id": 1, + "local_id": 2, + "name": "France", + "created_at": "2017-09-04 05:41:03", + "updated_at": "2017-09-04 05:41:03" + }, + { + "id": 14, + "ladder_id": 1, + "local_id": 9, + "name": "Yuri", + "created_at": "2017-09-04 05:41:03", + "updated_at": "2017-09-04 05:41:03" + }, + { + "id": 24, + "ladder_id": 1, + "local_id": -1, + "name": "Random", + "created_at": "-0001-11-30 00:00:00", + "updated_at": "-0001-11-30 00:00:00" + } + ], + "vetoes": 6, + "allowed_sides": [ + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "current": "11-2022", + "qm_ladder_rules": { + "id": 1, + "ladder_id": 1, + "player_count": 2, + "map_vetoes": 6, + "max_difference": 498, + "all_sides": "0,1,2,3,4,5,6,7,8,9", + "allowed_sides": "-1,0,1,2,3,4,5,6,7,8,9", + "created_at": "2017-07-28 04:50:56", + "updated_at": "2022-11-01 21:54:13", + "bail_time": 0, + "bail_fps": 0, + "tier2_rating": 0, + "rating_per_second": 0.75, + "max_points_difference": 400, + "points_per_second": 1, + "use_elo_points": 1, + "wol_k": 64, + "show_map_preview": 1, + "reduce_map_repeats": 3, + "ladder_rules_message": "1. No pushing or intentionally feeding players points.\n2. Using multiple accounts to bypass the 1 nick restriction is not allowed.\n3. Disconnecting games is not allowed.\n4. Using a VPN to hide your identity is not allowed.\n5. No cancelling matches consecutively to block players from QMing.\n6. No abusing bugs on your opponents to gain an upper hand.\n7. Games that end in stalemate may be washed if photo\/video is provided as proof of the stalemate.\n8. Games that need to be washed must be reported within 1 week. On the last 2 days of the month old games will not be washed.\n9. Opening menu and lowering the game speed during a game is not allowed.\n10. You are not allowed to impersonate other players\n11. You are not allowed to quit matches at start of match if you don't like the opponent\/faction or whatever, this goes against competitive fair play. This is a competitive environment you should respect the game, your opponents, and the ladder or else don't play.\n12. If you are lagging games below 50fps you are subject to a cooldown.\n13. If a game is lagging you may report the game to be washed if you quit before 1min into the game. Fps should be below 50fps for it to be washed.", + "ladder_discord": "https:\/\/discord.com\/invite\/aJRJFe5" + } + }, + { + "id": 2, + "name": "Ladder 2", + "abbreviation": "ra2", + "game": "yr", + "created_at": "2022-04-22 02:26:06", + "updated_at": "2022-11-01 00:16:07", + "clans_allowed": 0, + "game_object_schema_id": 1, + "map_pool_id": 57, + "private": 0, + "sides": [ + { + "id": 36, + "ladder_id": 2, + "local_id": 0, + "name": "America", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-04-22 02:26:07" + }, + { + "id": 37, + "ladder_id": 2, + "local_id": 1, + "name": "Korea", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-04-22 02:26:07" + }, + { + "id": 38, + "ladder_id": 2, + "local_id": 2, + "name": "France", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-04-22 02:26:07" + }, + { + "id": 39, + "ladder_id": 2, + "local_id": 3, + "name": "Germany", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-04-22 02:26:07" + }, + { + "id": 40, + "ladder_id": 2, + "local_id": 4, + "name": "Great Britain", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-04-22 02:26:07" + }, + { + "id": 41, + "ladder_id": 2, + "local_id": 5, + "name": "Libya", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-04-22 02:26:07" + }, + { + "id": 42, + "ladder_id": 2, + "local_id": 6, + "name": "Iraq", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-04-22 02:26:07" + }, + { + "id": 43, + "ladder_id": 2, + "local_id": 7, + "name": "Cuba", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-04-22 02:26:07" + }, + { + "id": 44, + "ladder_id": 2, + "local_id": 8, + "name": "Russia", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-04-22 02:26:07" + }, + { + "id": 67, + "ladder_id": 2, + "local_id": -1, + "name": "Random", + "created_at": "2022-06-14 02:13:59", + "updated_at": "2022-06-14 02:13:59" + } + ], + "vetoes": 8, + "allowed_sides": [ + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + "current": "11-2022", + "qm_ladder_rules": { + "id": 4, + "ladder_id": 2, + "player_count": 2, + "map_vetoes": 8, + "max_difference": 400, + "all_sides": "0,1,2,3,4,5,6,7,8", + "allowed_sides": "-1,0,1,2,3,4,5,6,7,8", + "created_at": "2022-04-22 02:26:07", + "updated_at": "2022-11-05 23:03:52", + "bail_time": 0, + "bail_fps": 0, + "tier2_rating": 0, + "rating_per_second": 0.75, + "max_points_difference": 400, + "points_per_second": 2, + "use_elo_points": 1, + "wol_k": 64, + "show_map_preview": 1, + "reduce_map_repeats": 5, + "ladder_rules_message": "1. No pushing or intentionally feeding players points.\r\n2. Using multiple accounts to bypass the 1 nick restriction is not allowed.\r\n3. Disconnecting games is not allowed.\r\n4. Using a VPN to hide your identity is not allowed.\r\n5. No cancelling matches consecutively to block players from QMing.\r\n6. No abusing bugs on your opponents to gain an upper hand.\r\n7. Games that end in stalemate may be washed if photo\/video is provided as proof of the stalemate.\r\n8. Games that need to be washed must be reported within 1 week. On the last 2 days of the month old games will not be washed.\r\n9. Opening menu and lowering the game speed during a game is not allowed.\r\n10. You are not allowed to impersonate other players\r\n11. You are not allowed to quit matches at start of match if you don't like the opponent\/faction or whatever, this goes against competitive fair play. This is a competitive environment you should respect the game, your opponents, and the ladder or else don't play.\r\n12. If you are lagging games below 50fps you are subject to a cooldown.\r\n13. If a game is lagging you may report the game to be washed if you quit before 1min into the game. Fps should be below 50fps for it to be washed.", + "ladder_discord": "https:\/\/discord.com\/invite\/aJRJFe5" + } + } +] diff --git a/DXMainClient/MockData/QuickMatch/qm_login_response.json b/DXMainClient/MockData/QuickMatch/qm_login_response.json new file mode 100644 index 000000000..eced869e1 --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_login_response.json @@ -0,0 +1,5 @@ +{ + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtb2NrLWNuY25ldCIsImlhdCI6MTY2NzkxNDMwNywiZXhwIjoxOTIwMzc1MTA3LCJhdWQiOiJtb2NrLWNuY25ldC1xbSIsInN1YiI6IjEyMzQ1In0.-i3E9cVw8RRqXUGbUEtsp8Drx3Wx6lZ64i9LoCXKDnw", + "email": "test@gmail.com", + "name": "testuserme" +} diff --git a/DXMainClient/MockData/QuickMatch/qm_user_accounts_response.json b/DXMainClient/MockData/QuickMatch/qm_user_accounts_response.json new file mode 100644 index 000000000..3e2d1872e --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_user_accounts_response.json @@ -0,0 +1,38 @@ +[ + { + "id": 1, + "username": "testuserme", + "ladder_id": 1, + "card_id": 0, + "ladder": { + "id": 1, + "name": "Ladder 1", + "abbreviation": "yr", + "game": "yr", + "created_at": "2022-11-01 00:00:00", + "updated_at": "2022-11-01 00:00:00", + "clans_allowed": 0, + "game_object_schema_id": 1, + "map_pool_id": 1, + "private": 0 + } + }, + { + "id": 2, + "username": "testuserme", + "ladder_id": 2, + "card_id": 0, + "ladder": { + "id": 2, + "name": "Ladder 2", + "abbreviation": "ra2", + "game": "ra2", + "created_at": "2022-11-01 00:00:00", + "updated_at": "2022-11-01 00:00:00", + "clans_allowed": 0, + "game_object_schema_id": 2, + "map_pool_id": 2, + "private": 0 + } + } +] From 9e3b527b14b3e2adc3f71f8c7c49fd453396ea03 Mon Sep 17 00:00:00 2001 From: devo1929 Date: Wed, 9 Nov 2022 08:43:47 -0500 Subject: [PATCH 14/19] Refactoring --- DXMainClient.Tests/DXMainClient.Tests.csproj | 3 + .../QmRequestResponseConverterTests.cs | 5 +- .../QuickMatch/QuickMatchLobbyPanel.cs | 15 +- .../QuickMatch/QuickMatchLoginPanel.cs | 3 +- .../QuickMatch/QuickMatchMapList.cs | 3 +- .../QuickMatch/QuickMatchStatusOverlay.cs | 21 ++- .../QuickMatch/QuickMatchWindow.cs | 3 +- .../Converters/QmRequestResponseConverter.cs | 39 +++++ .../QmRequestSpawnResponseSpawnConverter.cs | 16 +- .../EventArgs/QmStatusMessageEventArgs.cs | 17 -- .../Events/IQmOverlayStatusEvent.cs | 5 + .../Events/QmCancelingRequestMatchEvent.cs | 6 + .../Events/QmErrorMessageEvent.cs | 4 +- .../CnCNet/QuickMatch/Events/QmEvent.cs | 5 + .../{Models => }/Events/QmLadderMapsEvent.cs | 3 +- .../{Models => }/Events/QmLadderStatsEvent.cs | 4 +- .../Events/QmLaddersAndUserAccountsEvent.cs | 3 +- .../Events/QmLoadingLadderMapsEvent.cs | 2 +- .../Events/QmLoadingLadderStatsEvent.cs | 5 + .../QmLoadingLaddersAndUserAccountsEvent.cs | 2 +- .../QuickMatch/Events/QmLoggingInEvent.cs | 5 + .../{Models => }/Events/QmLoginEvent.cs | 2 +- .../CnCNet/QuickMatch/Events/QmLogoutEvent.cs | 5 + .../Events/QmMasterSideSelected.cs | 4 +- .../Events/QmNotReadyRequestMatchEvent.cs | 5 + .../Events/QmReadyRequestMatchEvent.cs | 5 + .../Events/QmRequestingMatchEvent.cs | 2 +- .../QuickMatch/Events/QmResponseEvent.cs | 13 ++ .../Events/QmUserAccountSelectedEvent.cs | 4 +- .../Models/Events/IQmOverlayStatusEvent.cs | 5 - .../Events/QmCancelingRequestMatchEvent.cs | 6 - .../QuickMatch/Models/Events/QmEvent.cs | 6 - .../Events/QmLoadingLadderStatsEvent.cs | 5 - .../Models/Events/QmLoggingInEvent.cs | 5 - .../QuickMatch/Models/Events/QmLogoutEvent.cs | 5 - .../Events/QmNotReadyRequestMatchEvent.cs | 5 - .../Models/Events/QmReadyRequestMatchEvent.cs | 5 - .../Models/Events/QmRequestResponseEvent.cs | 11 -- .../CnCNet/QuickMatch/Models/QmAuthData.cs | 17 +- .../CnCNet/QuickMatch/Models/QmData.cs | 13 +- .../CnCNet/QuickMatch/Models/QmLadder.cs | 69 ++++---- .../CnCNet/QuickMatch/Models/QmLadderMap.cs | 85 +++++----- .../CnCNet/QuickMatch/Models/QmLadderRules.cs | 97 ++++++----- .../CnCNet/QuickMatch/Models/QmLadderStats.cs | 31 ++-- .../QuickMatch/Models/QmLoginRequest.cs | 15 +- .../CnCNet/QuickMatch/Models/QmMap.cs | 25 ++- .../QuickMatch/Models/QmMatchRequest.cs | 59 ------- .../CnCNet/QuickMatch/Models/QmQuitRequest.cs | 9 - .../CnCNet/QuickMatch/Models/QmRequest.cs | 12 -- .../Models/QmRequestErrorResponse.cs | 8 - .../Models/QmRequestFatalResponse.cs | 8 - .../Models/QmRequestQuitResponse.cs | 8 - .../QuickMatch/Models/QmRequestResponse.cs | 34 ---- .../Models/QmRequestSpawnResponse.cs | 10 -- .../Models/QmRequestUpdateResponse.cs | 8 - .../Models/QmRequestWaitResponse.cs | 13 -- .../CnCNet/QuickMatch/Models/QmSettings.cs | 57 ++++--- .../CnCNet/QuickMatch/Models/QmSide.cs | 34 ++-- ...equestSpawnResponseSpawn.cs => QmSpawn.cs} | 7 +- ...nResponseSpawnOther.cs => QmSpawnOther.cs} | 2 +- ...nseSpawnSettings.cs => QmSpawnSettings.cs} | 2 +- .../CnCNet/QuickMatch/Models/QmUserAccount.cs | 29 ++-- .../QuickMatch/Models/QmUserSettings.cs | 155 +++++++++--------- .../QuickMatch/QmRequestResponseConverter.cs | 38 ----- .../QuickMatch/Requests/QmMatchRequest.cs | 59 +++++++ .../{Models => Requests}/QmNotReadyRequest.cs | 2 +- .../QuickMatch/Requests/QmQuitRequest.cs | 11 ++ .../{Models => Requests}/QmReadyRequest.cs | 2 +- .../CnCNet/QuickMatch/Requests/QmRequest.cs | 16 ++ .../{Models => Requests}/QmUpdateRequest.cs | 7 +- .../QuickMatch/Responses/QmErrorResponse.cs | 5 + .../QuickMatch/Responses/QmFatalResponse.cs | 5 + .../QuickMatch/Responses/QmQuitResponse.cs | 5 + .../CnCNet/QuickMatch/Responses/QmResponse.cs | 39 +++++ .../QuickMatch/Responses/QmSpawnResponse.cs | 10 ++ .../QuickMatch/Responses/QmUpdateResponse.cs | 5 + .../QuickMatch/Responses/QmWaitResponse.cs | 12 ++ .../QuickMatch/{ => Services}/QmApiService.cs | 39 +++-- .../{ => Services}/QmMockApiService.cs | 30 ++-- .../QuickMatch/{ => Services}/QmService.cs | 31 ++-- .../{ => Services}/QmSettingsService.cs | 2 +- .../{ => Services}/QmUserSettingsService.cs | 3 +- .../{ => Utilities}/QmMatchFoundTimer.cs | 7 +- .../{ => Utilities}/QmRequestTypes.cs | 2 +- .../{ => Utilities}/QmResponseTypes.cs | 2 +- .../QuickMatch/{ => Utilities}/QmStrings.cs | 2 +- .../Utilities/QmUpdateRequestStatuses.cs | 9 + .../qm_find_match_quit_response.json | 3 + .../qm_find_match_spawn_response.json | 92 +++++------ .../qm_ladder_maps_yr_response.json | 22 +-- 90 files changed, 771 insertions(+), 758 deletions(-) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Converters/QmRequestResponseConverter.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Converters}/QmRequestSpawnResponseSpawnConverter.cs (57%) delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/EventArgs/QmStatusMessageEventArgs.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/IQmOverlayStatusEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmCancelingRequestMatchEvent.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmErrorMessageEvent.cs (70%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmEvent.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmLadderMapsEvent.cs (66%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmLadderStatsEvent.cs (60%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmLaddersAndUserAccountsEvent.cs (75%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmLoadingLadderMapsEvent.cs (50%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLadderStatsEvent.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmLoadingLaddersAndUserAccountsEvent.cs (53%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoggingInEvent.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmLoginEvent.cs (53%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLogoutEvent.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmMasterSideSelected.cs (55%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmNotReadyRequestMatchEvent.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmReadyRequestMatchEvent.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmRequestingMatchEvent.cs (80%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmResponseEvent.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => }/Events/QmUserAccountSelectedEvent.cs (62%) delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/IQmOverlayStatusEvent.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmCancelingRequestMatchEvent.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmEvent.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderStatsEvent.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoggingInEvent.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLogoutEvent.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmNotReadyRequestMatchEvent.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmReadyRequestMatchEvent.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestResponseEvent.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestErrorResponse.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestFatalResponse.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestQuitResponse.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponse.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponse.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestUpdateResponse.cs delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestWaitResponse.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/{QmRequestSpawnResponseSpawn.cs => QmSpawn.cs} (81%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/{QmRequestSpawnResponseSpawnOther.cs => QmSpawnOther.cs} (93%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/{QmRequestResponseSpawnSettings.cs => QmSpawnSettings.cs} (96%) delete mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestResponseConverter.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmMatchRequest.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => Requests}/QmNotReadyRequest.cs (67%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmQuitRequest.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => Requests}/QmReadyRequest.cs (66%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmRequest.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{Models => Requests}/QmUpdateRequest.cs (57%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmErrorResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmFatalResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmQuitResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmSpawnResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmUpdateResponse.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmWaitResponse.cs rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Services}/QmApiService.cs (87%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Services}/QmMockApiService.cs (56%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Services}/QmService.cs (90%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Services}/QmSettingsService.cs (98%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Services}/QmUserSettingsService.cs (88%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Utilities}/QmMatchFoundTimer.cs (73%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Utilities}/QmRequestTypes.cs (70%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Utilities}/QmResponseTypes.cs (80%) rename DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/{ => Utilities}/QmStrings.cs (98%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmUpdateRequestStatuses.cs create mode 100644 DXMainClient/MockData/QuickMatch/qm_find_match_quit_response.json diff --git a/DXMainClient.Tests/DXMainClient.Tests.csproj b/DXMainClient.Tests/DXMainClient.Tests.csproj index 7c4c0366c..af5adab58 100644 --- a/DXMainClient.Tests/DXMainClient.Tests.csproj +++ b/DXMainClient.Tests/DXMainClient.Tests.csproj @@ -33,6 +33,9 @@ 4 + + ..\DXMainClient\bin\YRWindowsDXDebug\YR\WindowsDX\net48\clientdx.exe + diff --git a/DXMainClient.Tests/QmRequestResponseConverterTests.cs b/DXMainClient.Tests/QmRequestResponseConverterTests.cs index 3e023873a..138a29569 100644 --- a/DXMainClient.Tests/QmRequestResponseConverterTests.cs +++ b/DXMainClient.Tests/QmRequestResponseConverterTests.cs @@ -1,6 +1,7 @@ using System; using System.IO; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; using Newtonsoft.Json; using NUnit.Framework; @@ -16,7 +17,7 @@ public void Test_SpawnRead() QmRequestResponse response = JsonConvert.DeserializeObject(json); - Assert.IsInstanceOf(response); + Assert.IsInstanceOf(response); } [Test] @@ -38,7 +39,7 @@ public void Test_PleaseWaitRead() QmRequestResponse response = JsonConvert.DeserializeObject(json); - Assert.IsInstanceOf(response); + Assert.IsInstanceOf(response); } private static string GetSpawnJson() => GetJson("qm_spawn_response"); diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs index f55526795..c122f293e 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLobbyPanel.cs @@ -1,19 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using ClientCore.Exceptions; using ClientGUI; using DTAClient.Domain.Multiplayer; -using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; -using Localization; -using Rampastring.Tools; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; using Rampastring.XNAUI; using Rampastring.XNAUI.XNAControls; -using Timer = System.Timers.Timer; namespace DTAClient.DXGUI.Multiplayer.QuickMatch { @@ -155,7 +152,7 @@ private void RequestQuickMatch() qmService.RequestMatchAsync(); } - private void HandleQuickMatchSpawnResponse(QmRequestResponse qmRequestResponse) + private void HandleQuickMatchSpawnResponse(QmResponse qmResponse) { XNAMessageBox.Show(WindowManager, QmStrings.GenericErrorTitle, "qm spawn"); } diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs index d4fca8e77..44d859615 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchLoginPanel.cs @@ -1,7 +1,8 @@ using System; using ClientGUI; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; -using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; using Rampastring.XNAUI; using Rampastring.XNAUI.XNAControls; diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs index dbdb9ac01..f7961bd14 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchMapList.cs @@ -3,8 +3,9 @@ using System.Linq; using ClientGUI; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; using Microsoft.Xna.Framework; using Rampastring.XNAUI; using Rampastring.XNAUI.XNAControls; diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs index 2bf949dcf..a0dbe4a1b 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchStatusOverlay.cs @@ -5,8 +5,11 @@ using ClientCore.Enums; using ClientGUI; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; using Microsoft.Xna.Framework; using Rampastring.XNAUI; using Rampastring.XNAUI.XNAControls; @@ -96,7 +99,7 @@ private void HandleQmEvent(object sender, QmEvent qmEvent) case QmErrorMessageEvent: Disable(); return; - case QmRequestResponseEvent e: + case QmResponseEvent e: HandleRequestResponseEvent(e); return; } @@ -119,14 +122,14 @@ private void HandleQmEvent(object sender, QmEvent qmEvent) private void HandleRequestingMatchEvent(QmRequestingMatchEvent e) => SetStatus(QmStrings.RequestingMatchStatus, e.CancelAction); - private void HandleRequestResponseEvent(QmRequestResponseEvent e) + private void HandleRequestResponseEvent(QmResponseEvent e) { - QmRequestResponse response = e.Response; + QmResponse response = e.Response; switch (true) { - case true when response is QmRequestWaitResponse: + case true when response is QmWaitResponse: return; // need to keep the overlay open while waiting - case true when response is QmRequestSpawnResponse spawnResponse: + case true when response is QmSpawnResponse spawnResponse: HandleSpawnResponseEvent(spawnResponse); return; default: @@ -135,7 +138,7 @@ private void HandleRequestResponseEvent(QmRequestResponseEvent e) } } - private void HandleSpawnResponseEvent(QmRequestSpawnResponse spawnResponse) + private void HandleSpawnResponseEvent(QmSpawnResponse spawnResponse) { int interval = matchupFoundConfirmTimer.GetInterval(); int ratio = 1000 / interval; @@ -152,9 +155,9 @@ private void HandleSpawnResponseEvent(QmRequestSpawnResponse spawnResponse) matchupFoundConfirmTimer.Start(); } - private void AcceptMatchAsync(QmRequestSpawnResponseSpawn spawn) => qmService.AcceptMatchAsync(spawn); + private void AcceptMatchAsync(QmSpawn spawn) => qmService.AcceptMatchAsync(spawn); - private void RejectMatchAsync(QmRequestSpawnResponseSpawn spawn) => qmService.RejectMatchAsync(spawn); + private void RejectMatchAsync(QmSpawn spawn) => qmService.RejectMatchAsync(spawn); private void HandleCancelingMatchRequest() => SetStatus(QmStrings.CancelingMatchRequestStatus); diff --git a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs index 85864af4d..b76797996 100644 --- a/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs +++ b/DXMainClient/DXGUI/Multiplayer/QuickMatch/QuickMatchWindow.cs @@ -5,8 +5,9 @@ using ClientCore.Exceptions; using ClientGUI; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; using Microsoft.Xna.Framework; using Rampastring.XNAUI; using Rampastring.XNAUI.XNAControls; diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Converters/QmRequestResponseConverter.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Converters/QmRequestResponseConverter.cs new file mode 100644 index 000000000..9ee5601a6 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Converters/QmRequestResponseConverter.cs @@ -0,0 +1,39 @@ +using System; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Converters; + +/// +/// The response from the Ladder api for a match request can come back in a few different response types: +/// +/// +public class QmRequestResponseConverter : JsonConverter +{ + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, QmResponse value, JsonSerializer serializer) => throw new NotImplementedException(); + + public override QmResponse ReadJson(JsonReader reader, Type objectType, QmResponse existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var token = JObject.Load(reader); + string responseType = token[QmResponse.TypeKey]?.ToString(); + + if (responseType == null) + return null; + + Type subType = QmResponse.GetSubType(responseType); + + existingValue ??= Activator.CreateInstance(subType) as QmResponse; + + if (existingValue == null) + return null; + + using JsonReader subReader = token.CreateReader(); + serializer.Populate(subReader, existingValue); + + return existingValue; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestSpawnResponseSpawnConverter.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Converters/QmRequestSpawnResponseSpawnConverter.cs similarity index 57% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestSpawnResponseSpawnConverter.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Converters/QmRequestSpawnResponseSpawnConverter.cs index 6bf1f2f76..45d25726c 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestSpawnResponseSpawnConverter.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Converters/QmRequestSpawnResponseSpawnConverter.cs @@ -5,41 +5,41 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Converters; -public class QmRequestSpawnResponseSpawnConverter : JsonConverter +public class QmRequestSpawnResponseSpawnConverter : JsonConverter { private const int MaxOtherSpawns = 7; - public override void WriteJson(JsonWriter writer, QmRequestSpawnResponseSpawn value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, QmSpawn value, JsonSerializer serializer) { var obj = new JObject(); - List others = value?.Others?.ToList() ?? new List(); + List others = value?.Others?.ToList() ?? new List(); for (int i = 0; i < others.Count; i++) obj.Add($"Other{i + 1}", JObject.FromObject(others[i])); obj.WriteTo(writer); } - public override QmRequestSpawnResponseSpawn ReadJson(JsonReader reader, Type objectType, QmRequestSpawnResponseSpawn existingValue, bool hasExistingValue, JsonSerializer serializer) + public override QmSpawn ReadJson(JsonReader reader, Type objectType, QmSpawn existingValue, bool hasExistingValue, JsonSerializer serializer) { var token = JObject.Load(reader); - existingValue ??= new QmRequestSpawnResponseSpawn(); + existingValue ??= new QmSpawn(); // populate base properties that require no specific conversions using JsonReader subReader = token.CreateReader(); serializer.Populate(subReader, existingValue); - var others = new List(); + var others = new List(); for (int i = 1; i <= MaxOtherSpawns; i++) { JToken otherN = token[$"Other{i}"]; if (otherN == null) break; - others.Add(JsonConvert.DeserializeObject(otherN.ToString())); + others.Add(JsonConvert.DeserializeObject(otherN.ToString())); } if (others.Any()) diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/EventArgs/QmStatusMessageEventArgs.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/EventArgs/QmStatusMessageEventArgs.cs deleted file mode 100644 index 3e441eb20..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/EventArgs/QmStatusMessageEventArgs.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch -{ - public class QmStatusMessageEventArgs : EventArgs - { - public string Message { get; } - - public Action CancelAction { get; } - - public QmStatusMessageEventArgs(string message, Action cancelAction = null) - { - Message = message; - CancelAction = cancelAction; - } - } -} diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/IQmOverlayStatusEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/IQmOverlayStatusEvent.cs new file mode 100644 index 000000000..16e3caf9f --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/IQmOverlayStatusEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; + +public interface IQmOverlayStatusEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmCancelingRequestMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmCancelingRequestMatchEvent.cs new file mode 100644 index 000000000..6c7d3fd51 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmCancelingRequestMatchEvent.cs @@ -0,0 +1,6 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; + +public class QmCancelingRequestMatchEvent : QmEvent +{ + +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmErrorMessageEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmErrorMessageEvent.cs similarity index 70% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmErrorMessageEvent.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmErrorMessageEvent.cs index 58e47bb1a..c3ba69eef 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmErrorMessageEvent.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmErrorMessageEvent.cs @@ -1,4 +1,6 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmErrorMessageEvent : QmEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmEvent.cs new file mode 100644 index 000000000..01fe05a01 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; + +public abstract class QmEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderMapsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLadderMapsEvent.cs similarity index 66% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderMapsEvent.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLadderMapsEvent.cs index 679ed4c1f..ae691225a 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderMapsEvent.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLadderMapsEvent.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmLadderMapsEvent : QmEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderStatsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLadderStatsEvent.cs similarity index 60% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderStatsEvent.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLadderStatsEvent.cs index e645ae13d..e8cf1ca56 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLadderStatsEvent.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLadderStatsEvent.cs @@ -1,4 +1,6 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmLadderStatsEvent : QmEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLaddersAndUserAccountsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLaddersAndUserAccountsEvent.cs similarity index 75% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLaddersAndUserAccountsEvent.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLaddersAndUserAccountsEvent.cs index 22faddafd..cdb9ec57a 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLaddersAndUserAccountsEvent.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLaddersAndUserAccountsEvent.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmLaddersAndUserAccountsEvent : QmEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderMapsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLadderMapsEvent.cs similarity index 50% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderMapsEvent.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLadderMapsEvent.cs index 4163206a7..a12be102f 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderMapsEvent.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLadderMapsEvent.cs @@ -1,4 +1,4 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmLoadingLadderMapsEvent : QmEvent, IQmOverlayStatusEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLadderStatsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLadderStatsEvent.cs new file mode 100644 index 000000000..85763c007 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLadderStatsEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; + +public class QmLoadingLadderStatsEvent : QmEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLaddersAndUserAccountsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLaddersAndUserAccountsEvent.cs similarity index 53% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLaddersAndUserAccountsEvent.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLaddersAndUserAccountsEvent.cs index 1cbb80635..4b04214ca 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLaddersAndUserAccountsEvent.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoadingLaddersAndUserAccountsEvent.cs @@ -1,4 +1,4 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmLoadingLaddersAndUserAccountsEvent : QmEvent, IQmOverlayStatusEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoggingInEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoggingInEvent.cs new file mode 100644 index 000000000..39a4f6cd9 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoggingInEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; + +public class QmLoggingInEvent : QmEvent, IQmOverlayStatusEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoginEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoginEvent.cs similarity index 53% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoginEvent.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoginEvent.cs index 42ad67a7c..bac4cdbd3 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoginEvent.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLoginEvent.cs @@ -1,4 +1,4 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmLoginEvent : QmEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLogoutEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLogoutEvent.cs new file mode 100644 index 000000000..7de212156 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmLogoutEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; + +public class QmLogoutEvent : QmEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmMasterSideSelected.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmMasterSideSelected.cs similarity index 55% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmMasterSideSelected.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmMasterSideSelected.cs index b3b42b7a5..71a2bc92b 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmMasterSideSelected.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmMasterSideSelected.cs @@ -1,4 +1,6 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmMasterSideSelected : QmEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmNotReadyRequestMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmNotReadyRequestMatchEvent.cs new file mode 100644 index 000000000..76f8f4609 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmNotReadyRequestMatchEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; + +public class QmNotReadyRequestMatchEvent : QmEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmReadyRequestMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmReadyRequestMatchEvent.cs new file mode 100644 index 000000000..a8f5257ed --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmReadyRequestMatchEvent.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; + +public class QmReadyRequestMatchEvent : QmEvent +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestingMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmRequestingMatchEvent.cs similarity index 80% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestingMatchEvent.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmRequestingMatchEvent.cs index c10907dbc..36f3d0591 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestingMatchEvent.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmRequestingMatchEvent.cs @@ -1,6 +1,6 @@ using System; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmRequestingMatchEvent : QmEvent, IQmOverlayStatusEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmResponseEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmResponseEvent.cs new file mode 100644 index 000000000..2bfaced4a --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmResponseEvent.cs @@ -0,0 +1,13 @@ +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; + +public class QmResponseEvent : QmEvent +{ + public QmResponse Response { get; } + + public QmResponseEvent(QmResponse response) + { + Response = response; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmUserAccountSelectedEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmUserAccountSelectedEvent.cs similarity index 62% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmUserAccountSelectedEvent.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmUserAccountSelectedEvent.cs index a1c341550..87a687f48 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmUserAccountSelectedEvent.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Events/QmUserAccountSelectedEvent.cs @@ -1,4 +1,6 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; public class QmUserAccountSelectedEvent : QmEvent { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/IQmOverlayStatusEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/IQmOverlayStatusEvent.cs deleted file mode 100644 index 0941850ee..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/IQmOverlayStatusEvent.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; - -public interface IQmOverlayStatusEvent -{ -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmCancelingRequestMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmCancelingRequestMatchEvent.cs deleted file mode 100644 index 90fb8cb40..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmCancelingRequestMatchEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; - -public class QmCancelingRequestMatchEvent : QmEvent -{ - -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmEvent.cs deleted file mode 100644 index 2e44336a1..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; - -public abstract class QmEvent -{ - -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderStatsEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderStatsEvent.cs deleted file mode 100644 index 508dc9d6b..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoadingLadderStatsEvent.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; - -public class QmLoadingLadderStatsEvent : QmEvent -{ -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoggingInEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoggingInEvent.cs deleted file mode 100644 index e49f9f1a5..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLoggingInEvent.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; - -public class QmLoggingInEvent : QmEvent, IQmOverlayStatusEvent -{ -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLogoutEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLogoutEvent.cs deleted file mode 100644 index a4ffa138f..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmLogoutEvent.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; - -public class QmLogoutEvent : QmEvent -{ -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmNotReadyRequestMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmNotReadyRequestMatchEvent.cs deleted file mode 100644 index e366759e5..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmNotReadyRequestMatchEvent.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; - -public class QmNotReadyRequestMatchEvent : QmEvent -{ -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmReadyRequestMatchEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmReadyRequestMatchEvent.cs deleted file mode 100644 index 5cd8fd0f6..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmReadyRequestMatchEvent.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; - -public class QmReadyRequestMatchEvent : QmEvent -{ -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestResponseEvent.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestResponseEvent.cs deleted file mode 100644 index 38dcb70ae..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/Events/QmRequestResponseEvent.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; - -public class QmRequestResponseEvent : QmEvent -{ - public QmRequestResponse Response { get; } - - public QmRequestResponseEvent(QmRequestResponse response) - { - Response = response; - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmAuthData.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmAuthData.cs index d5313ce49..e52154268 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmAuthData.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmAuthData.cs @@ -1,9 +1,10 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmAuthData { - public class QmAuthData - { - public string Token { get; set; } - public string Email { get; set; } - public string Name { get; set; } - } -} + public string Token { get; set; } + + public string Email { get; set; } + + public string Name { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmData.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmData.cs index 21afe7308..8c005f948 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmData.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmData.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmData { - public class QmData - { - public List Ladders { get; set; } + public List Ladders { get; set; } - public List UserAccounts { get; set; } - } -} + public List UserAccounts { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs index 679006f76..189475e4f 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadder.cs @@ -1,53 +1,52 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmLadder { - public class QmLadder - { - [JsonProperty("id")] - public long Id { get; set; } + [JsonProperty("id")] + public long Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } + [JsonProperty("name")] + public string Name { get; set; } - [JsonProperty("abbreviation")] - public string Abbreviation { get; set; } + [JsonProperty("abbreviation")] + public string Abbreviation { get; set; } - [JsonProperty("game")] - public string Game { get; set; } + [JsonProperty("game")] + public string Game { get; set; } - [JsonProperty("clans_allowed")] - private int clansAllowed { get; set; } + [JsonProperty("clans_allowed")] + private int clansAllowed { get; set; } - [JsonIgnore] - public bool ClansAllowed => clansAllowed == 1; + [JsonIgnore] + public bool ClansAllowed => clansAllowed == 1; - [JsonProperty("game_object_schema_id")] - public int GameObjectSchemaId { get; set; } + [JsonProperty("game_object_schema_id")] + public int GameObjectSchemaId { get; set; } - [JsonProperty("map_pool_id")] - public int? MapPoolId { get; set; } + [JsonProperty("map_pool_id")] + public int? MapPoolId { get; set; } - [JsonProperty("private")] - private int _private { get; set; } + [JsonProperty("private")] + private int _private { get; set; } - [JsonIgnore] - public bool IsPrivate => _private == 1; + [JsonIgnore] + public bool IsPrivate => _private == 1; - [JsonProperty("sides")] - public IEnumerable Sides { get; set; } + [JsonProperty("sides")] + public IEnumerable Sides { get; set; } - [JsonProperty("vetoes")] - public int VetoesRemaining { get; set; } + [JsonProperty("vetoes")] + public int VetoesRemaining { get; set; } - [JsonProperty("allowed_sides")] - public IEnumerable AllowedSideLocalIds { get; set; } + [JsonProperty("allowed_sides")] + public IEnumerable AllowedSideLocalIds { get; set; } - [JsonProperty("current")] - public string Current { get; set; } + [JsonProperty("current")] + public string Current { get; set; } - [JsonProperty("qm_ladder_rules")] - public QmLadderRules LadderRules { get; set; } - } -} + [JsonProperty("qm_ladder_rules")] + public QmLadderRules LadderRules { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs index f0c8d8b9d..87da585ec 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderMap.cs @@ -1,65 +1,64 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmLadderMap { - public class QmLadderMap - { - [JsonProperty("id")] - public int Id { get; set; } + [JsonProperty("id")] + public int Id { get; set; } - [JsonProperty("ladder_id")] - public int LadderId { get; set; } + [JsonProperty("ladder_id")] + public int LadderId { get; set; } - [JsonProperty("map_id")] - public int MapId { get; set; } + [JsonProperty("map_id")] + public int MapId { get; set; } - [JsonProperty("description")] - public string Description { get; set; } + [JsonProperty("description")] + public string Description { get; set; } - [JsonProperty("bit_idx")] - public int BitIndex { get; set; } + [JsonProperty("bit_idx")] + public int BitIndex { get; set; } - [JsonProperty("valid")] - private int valid { get; set; } + [JsonProperty("valid")] + private int valid { get; set; } - [JsonIgnore] - public bool IsValid => valid == 1; + [JsonIgnore] + public bool IsValid => valid == 1; - [JsonProperty("spawn_order")] - public string SpawnOrder { get; set; } + [JsonProperty("spawn_order")] + public string SpawnOrder { get; set; } - [JsonProperty("team1_spawn_order")] - public string Team1SpawnOrder { get; set; } + [JsonProperty("team1_spawn_order")] + public string Team1SpawnOrder { get; set; } - [JsonProperty("team2_spawn_order")] - public string Team2SpawnOrder { get; set; } + [JsonProperty("team2_spawn_order")] + public string Team2SpawnOrder { get; set; } - [JsonProperty("allowed_sides")] - public IEnumerable AllowedSideIds { get; set; } + [JsonProperty("allowed_sides")] + public IEnumerable AllowedSideIds { get; set; } - [JsonProperty("admin_description")] - public string AdminDescription { get; set; } + [JsonProperty("admin_description")] + public string AdminDescription { get; set; } - [JsonProperty("map_pool_id")] - public int? MapPoolId { get; set; } + [JsonProperty("map_pool_id")] + public int? MapPoolId { get; set; } - [JsonProperty("rejectable")] - private int rejectable { get; set; } + [JsonProperty("rejectable")] + private int rejectable { get; set; } - [JsonIgnore] - public bool IsRejectable => rejectable == 1; + [JsonIgnore] + public bool IsRejectable => rejectable == 1; - [JsonProperty("default_reject")] - private int defaultReject { get; set; } + [JsonProperty("default_reject")] + private int defaultReject { get; set; } - [JsonIgnore] - public bool IsDefaultReject => defaultReject == 1; + [JsonIgnore] + public bool IsDefaultReject => defaultReject == 1; - [JsonProperty("hash")] - public string Hash { get; set; } + [JsonProperty("hash")] + public string Hash { get; set; } - [JsonProperty("map")] - public QmMap Map { get; set; } - } -} + [JsonProperty("map")] + public QmMap Map { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs index 4df6b8d6f..63084b101 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderRules.cs @@ -2,74 +2,73 @@ using System.Linq; using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmLadderRules { - public class QmLadderRules - { - [JsonProperty("id")] - public int Id { get; set; } + [JsonProperty("id")] + public int Id { get; set; } - [JsonProperty("ladder_id")] - public int LadderId { get; set; } + [JsonProperty("ladder_id")] + public int LadderId { get; set; } - [JsonProperty("player_count")] - public int PlayerCount { get; set; } + [JsonProperty("player_count")] + public int PlayerCount { get; set; } - [JsonProperty("map_vetoes")] - public int MapVetoes { get; set; } + [JsonProperty("map_vetoes")] + public int MapVetoes { get; set; } - [JsonProperty("max_difference")] - public int MaxDifference { get; set; } + [JsonProperty("max_difference")] + public int MaxDifference { get; set; } - [JsonProperty("all_sides")] - private string allSides { get; set; } + [JsonProperty("all_sides")] + private string allSides { get; set; } - [JsonIgnore] - public IEnumerable AllSides => allSides?.Split(',').Select(int.Parse) ?? new List(); + [JsonIgnore] + public IEnumerable AllSides => allSides?.Split(',').Select(int.Parse) ?? new List(); - [JsonProperty("allowed_sides")] - private string allowedSides { get; set; } + [JsonProperty("allowed_sides")] + private string allowedSides { get; set; } - [JsonIgnore] - public IEnumerable AllowedSides => allSides?.Split(',').Select(int.Parse) ?? new List(); + [JsonIgnore] + public IEnumerable AllowedSides => allSides?.Split(',').Select(int.Parse) ?? new List(); - [JsonProperty("bail_time")] - public int BailTime { get; set; } + [JsonProperty("bail_time")] + public int BailTime { get; set; } - [JsonProperty("bail_fps")] - public int BailFps { get; set; } + [JsonProperty("bail_fps")] + public int BailFps { get; set; } - [JsonProperty("tier2_rating")] - public int Tier2Rating { get; set; } + [JsonProperty("tier2_rating")] + public int Tier2Rating { get; set; } - [JsonProperty("rating_per_second")] - public decimal RatingPerSecond { get; set; } + [JsonProperty("rating_per_second")] + public decimal RatingPerSecond { get; set; } - [JsonProperty("max_points_difference")] - public int MaxPointsDifference { get; set; } + [JsonProperty("max_points_difference")] + public int MaxPointsDifference { get; set; } - [JsonProperty("points_per_second")] - public decimal PointsPerSecond { get; set; } + [JsonProperty("points_per_second")] + public decimal PointsPerSecond { get; set; } - [JsonProperty("use_elo_points")] - private int useEloPoints { get; set; } + [JsonProperty("use_elo_points")] + private int useEloPoints { get; set; } - [JsonIgnore] - public bool UseEloPoints => useEloPoints == 1; + [JsonIgnore] + public bool UseEloPoints => useEloPoints == 1; - [JsonProperty("wol_k")] - public int WolK { get; set; } + [JsonProperty("wol_k")] + public int WolK { get; set; } - [JsonProperty("show_map_preview")] - private int showMapPreview { get; set; } + [JsonProperty("show_map_preview")] + private int showMapPreview { get; set; } - [JsonIgnore] - public bool ShowMapPreview => showMapPreview == 1; + [JsonIgnore] + public bool ShowMapPreview => showMapPreview == 1; - [JsonProperty("reduce_map_repeats")] - private int reduceMapRepeats { get; set; } + [JsonProperty("reduce_map_repeats")] + private int reduceMapRepeats { get; set; } - [JsonIgnore] - public bool ReduceMapRepeats => reduceMapRepeats == 1; - } -} + [JsonIgnore] + public bool ReduceMapRepeats => reduceMapRepeats == 1; +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs index f480cea38..04c5a2f5b 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLadderStats.cs @@ -1,26 +1,25 @@ using System; using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmLadderStats { - public class QmLadderStats - { - [JsonProperty("recentMatchedPlayers")] - public int RecentMatchedPlayerCount { get; set; } + [JsonProperty("recentMatchedPlayers")] + public int RecentMatchedPlayerCount { get; set; } - [JsonProperty("queuedPlayers")] - public int QueuedPlayerCount { get; set; } + [JsonProperty("queuedPlayers")] + public int QueuedPlayerCount { get; set; } - [JsonProperty("past24hMatches")] - public int Past24HourMatchCount { get; set; } + [JsonProperty("past24hMatches")] + public int Past24HourMatchCount { get; set; } - [JsonProperty("recentMatches")] - public int RecentMatchCount { get; set; } + [JsonProperty("recentMatches")] + public int RecentMatchCount { get; set; } - [JsonProperty("activeMatches")] - public int ActiveMatchCount { get; set; } + [JsonProperty("activeMatches")] + public int ActiveMatchCount { get; set; } - [JsonProperty("time")] - public QmLadderStatsTime Time { get; set; } - } + [JsonProperty("time")] + public QmLadderStatsTime Time { get; set; } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLoginRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLoginRequest.cs index 987ecbde7..78ab29adc 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLoginRequest.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmLoginRequest.cs @@ -1,13 +1,12 @@ using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QMLoginRequest { - public class QMLoginRequest - { - [JsonProperty("email")] - public string Email { get; set; } + [JsonProperty("email")] + public string Email { get; set; } - [JsonProperty("password")] - public string Password { get; set; } - } + [JsonProperty("password")] + public string Password { get; set; } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMap.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMap.cs index 406e2e967..a88df41f0 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMap.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMap.cs @@ -1,19 +1,18 @@ using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmMap { - public class QmMap - { - [JsonProperty("id")] - public int Id { get; set; } + [JsonProperty("id")] + public int Id { get; set; } - [JsonProperty("hash")] - public string Hash { get; set; } + [JsonProperty("hash")] + public string Hash { get; set; } - [JsonProperty("name")] - public string Name { get; set; } + [JsonProperty("name")] + public string Name { get; set; } - [JsonProperty("ladder_id")] - public int LadderId { get; set; } - } -} + [JsonProperty("ladder_id")] + public int LadderId { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs deleted file mode 100644 index 07b4adc89..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmMatchRequest.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models -{ - public class QmMatchRequest : QmRequest - { - [JsonProperty("lan_ip")] - public string LanIP { get; set; } - - [JsonProperty("lan_port")] - public int LanPort { get; set; } - - [JsonProperty("ipv6_address")] - public string IPv6Address { get; set; } - - [JsonProperty("ipv6_port")] - public int IPv6Port { get; set; } - - [JsonProperty("ip_address")] - public string IPAddress { get; set; } - - [JsonProperty("ip_port")] - public int IPPort { get; set; } - - [JsonProperty("side")] - public int Side { get; set; } - - [JsonProperty("map_bitfield")] - public string MapBitfield { get; set; } - - [JsonProperty("platform")] - public string Platform { get; set; } - - [JsonProperty("map_sides")] - public IEnumerable MapSides { get; set; } - - [JsonProperty("ai_dat")] - public bool CheatSeen { get; set; } - - [JsonProperty("exe_hash")] - public string ExeHash { get; set; } - - [JsonProperty("ddraw")] - public string DDrawHash { get; set; } - - [JsonProperty("session")] - public string Session { get; set; } - - public QmMatchRequest() - { - Type = QmRequestTypes.MatchMeUp; - MapBitfield = int.MaxValue.ToString(); - Platform = "win32"; - Session = string.Empty; - DDrawHash = "8a00ba609f7d030c67339e1f555199bdb4054b67"; - } - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs deleted file mode 100644 index 4fa1979d2..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmQuitRequest.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; - -public class QmQuitRequest : QmRequest -{ - public QmQuitRequest() - { - Type = QmRequestTypes.Quit; - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs deleted file mode 100644 index 8873d66ef..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequest.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; - -public abstract class QmRequest -{ - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("version")] - public string Version { get; set; } = QmService.QmVersion; -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestErrorResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestErrorResponse.cs deleted file mode 100644 index ff71c5257..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestErrorResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models -{ - public class QmRequestErrorResponse : QmRequestResponse - { - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestFatalResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestFatalResponse.cs deleted file mode 100644 index b533ef396..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestFatalResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models -{ - public class QmRequestFatalResponse : QmRequestErrorResponse - { - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestQuitResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestQuitResponse.cs deleted file mode 100644 index 1e3c2f60c..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestQuitResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models -{ - public class QmRequestQuitResponse : QmRequestResponse - { - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponse.cs deleted file mode 100644 index 872404741..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models -{ - [JsonConverter(typeof(QmRequestResponseConverter))] - public abstract class QmRequestResponse - { - public const string TypeKey = "type"; - - [JsonProperty("description")] - public string Description { get; set; } - - [JsonProperty("message")] - public string Message { get; set; } - - [JsonIgnore] - public bool IsSuccessful => this is not QmRequestErrorResponse; - - public static Type GetSubType(string type) - { - return type switch - { - QmResponseTypes.Wait => typeof(QmRequestWaitResponse), - QmResponseTypes.Spawn => typeof(QmRequestSpawnResponse), - QmResponseTypes.Error => typeof(QmRequestErrorResponse), - QmResponseTypes.Fatal => typeof(QmRequestFatalResponse), - QmResponseTypes.Update => typeof(QmRequestUpdateResponse), - QmResponseTypes.Quit => typeof(QmRequestQuitResponse), - _ => null - }; - } - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponse.cs deleted file mode 100644 index f5dc33685..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models -{ - public class QmRequestSpawnResponse : QmRequestResponse - { - [JsonProperty("spawn")] - public QmRequestSpawnResponseSpawn Spawn { get; set; } - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestUpdateResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestUpdateResponse.cs deleted file mode 100644 index d40498f2f..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestUpdateResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models -{ - public class QmRequestUpdateResponse : QmRequestResponse - { - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestWaitResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestWaitResponse.cs deleted file mode 100644 index 008e7834a..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestWaitResponse.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Newtonsoft.Json; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models -{ - public class QmRequestWaitResponse : QmRequestResponse - { - [JsonProperty("checkback")] - public int CheckBack { get; set; } - - [JsonProperty("no_sooner_than")] - public int NoSoonerThan { get; set; } - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs index 56af72783..44845485d 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSettings.cs @@ -1,48 +1,47 @@ using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmSettings { - public class QmSettings - { - public const string DefaultBaseUrl = "https://ladder.cncnet.org"; - public const string DefaultLoginUrl = "/api/v1/auth/login"; - public const string DefaultRefreshUrl = "/api/v1/auth/refresh"; - public const string DefaultServerStatusUrl = "/api/v1/ping"; - public const string DefaultGetUserAccountsUrl = "/api/v1/user/account"; - public const string DefaultGetLaddersUrl = "/api/v1/ladder"; - public const string DefaultGetLadderMapsUrl = "/api/v1/qm/ladder/{0}/maps"; - public const string DefaultGetLadderStatsUrl = "/api/v1/qm/ladder/{0}/stats"; - public const string DefaultQuickMatchUrl = "/api/v1/qm/{0}/{1}"; - public const int DefaultMatchFoundWaitSeconds = 20; + public const string DefaultBaseUrl = "https://ladder.cncnet.org"; + public const string DefaultLoginUrl = "/api/v1/auth/login"; + public const string DefaultRefreshUrl = "/api/v1/auth/refresh"; + public const string DefaultServerStatusUrl = "/api/v1/ping"; + public const string DefaultGetUserAccountsUrl = "/api/v1/user/account"; + public const string DefaultGetLaddersUrl = "/api/v1/ladder"; + public const string DefaultGetLadderMapsUrl = "/api/v1/qm/ladder/{0}/maps"; + public const string DefaultGetLadderStatsUrl = "/api/v1/qm/ladder/{0}/stats"; + public const string DefaultQuickMatchUrl = "/api/v1/qm/{0}/{1}"; + public const int DefaultMatchFoundWaitSeconds = 20; - public string BaseUrl { get; set; } = DefaultBaseUrl; + public string BaseUrl { get; set; } = DefaultBaseUrl; - public string LoginUrl { get; set; } = DefaultLoginUrl; + public string LoginUrl { get; set; } = DefaultLoginUrl; - public string RefreshUrl { get; set; } = DefaultRefreshUrl; + public string RefreshUrl { get; set; } = DefaultRefreshUrl; - public string ServerStatusUrl { get; set; } = DefaultServerStatusUrl; + public string ServerStatusUrl { get; set; } = DefaultServerStatusUrl; - public string GetUserAccountsUrl { get; set; } = DefaultGetUserAccountsUrl; + public string GetUserAccountsUrl { get; set; } = DefaultGetUserAccountsUrl; - public string GetLaddersUrl { get; set; } = DefaultGetLaddersUrl; + public string GetLaddersUrl { get; set; } = DefaultGetLaddersUrl; - public string GetLadderMapsUrlFormat { get; set; } = DefaultGetLadderMapsUrl; + public string GetLadderMapsUrlFormat { get; set; } = DefaultGetLadderMapsUrl; - public string GetLadderStatsUrlFormat { get; set; } = DefaultGetLadderStatsUrl; + public string GetLadderStatsUrlFormat { get; set; } = DefaultGetLadderStatsUrl; - public string QuickMatchUrlFormat { get; set; } = DefaultQuickMatchUrl; + public string QuickMatchUrlFormat { get; set; } = DefaultQuickMatchUrl; - public string MatchFoundSoundFile { get; set; } + public string MatchFoundSoundFile { get; set; } - public List AllowedLadders { get; set; } = new(); + public List AllowedLadders { get; set; } = new(); - public int MatchFoundWaitSeconds { get; set; } = DefaultMatchFoundWaitSeconds; + public int MatchFoundWaitSeconds { get; set; } = DefaultMatchFoundWaitSeconds; - public IDictionary HeaderLogos = new Dictionary(); + public IDictionary HeaderLogos = new Dictionary(); - public Texture2D GetLadderHeaderLogo(string ladder) - => !HeaderLogos.ContainsKey(ladder) ? null : HeaderLogos[ladder]; - } + public Texture2D GetLadderHeaderLogo(string ladder) + => !HeaderLogos.ContainsKey(ladder) ? null : HeaderLogos[ladder]; } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSide.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSide.cs index 89f784293..7307584f9 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSide.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSide.cs @@ -1,24 +1,24 @@ -using Newtonsoft.Json; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; +using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmSide { - public class QmSide - { - [JsonProperty("id")] - public int Id { get; set; } + [JsonProperty("id")] + public int Id { get; set; } - [JsonProperty("ladder_id")] - public int LadderId { get; set; } + [JsonProperty("ladder_id")] + public int LadderId { get; set; } - [JsonProperty("local_id")] - public int LocalId { get; set; } + [JsonProperty("local_id")] + public int LocalId { get; set; } - [JsonProperty("name")] - public string Name { get; set; } + [JsonProperty("name")] + public string Name { get; set; } - [JsonIgnore] - public bool IsRandom => Name == QmStrings.RandomSideName; + [JsonIgnore] + public bool IsRandom => Name == QmStrings.RandomSideName; - public static QmSide CreateRandomSide() => new() { Name = QmStrings.RandomSideName }; - } -} + public static QmSide CreateRandomSide() => new() { Name = QmStrings.RandomSideName }; +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSpawn.cs similarity index 81% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSpawn.cs index 294c4b3a9..ca7bf261c 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawn.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSpawn.cs @@ -1,16 +1,17 @@ using System.Collections.Generic; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Converters; using Newtonsoft.Json; namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; [JsonConverter(typeof(QmRequestSpawnResponseSpawnConverter))] -public class QmRequestSpawnResponseSpawn +public class QmSpawn { [JsonProperty("SpawnLocations")] public IDictionary SpawnLocations { get; set; } [JsonProperty("Settings")] - public QmRequestResponseSpawnSettings Settings { get; set; } + public QmSpawnSettings Settings { get; set; } /// /// This is NOT part of the typical JSON that is used to serialize/deserialize this class. @@ -21,5 +22,5 @@ public class QmRequestSpawnResponseSpawn /// into the list you see below. /// [JsonIgnore] - public List Others { get; set; } + public List Others { get; set; } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawnOther.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSpawnOther.cs similarity index 93% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawnOther.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSpawnOther.cs index 72001f707..b04491123 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestSpawnResponseSpawnOther.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSpawnOther.cs @@ -2,7 +2,7 @@ namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -public class QmRequestSpawnResponseSpawnOther +public class QmSpawnOther { [JsonProperty("Name")] public string Name { get; set; } diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSpawnSettings.cs similarity index 96% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSpawnSettings.cs index 81afccaf9..9aa60de7d 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmRequestResponseSpawnSettings.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmSpawnSettings.cs @@ -1,6 +1,6 @@ namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -public class QmRequestResponseSpawnSettings +public class QmSpawnSettings { public string UIMapName { get; set; } diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserAccount.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserAccount.cs index 2c189cb6e..bf74c0de2 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserAccount.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserAccount.cs @@ -1,22 +1,21 @@ using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmUserAccount { - public class QmUserAccount - { - [JsonProperty("id")] - public long Id { get; set; } + [JsonProperty("id")] + public long Id { get; set; } - [JsonProperty("username")] - public string Username { get; set; } + [JsonProperty("username")] + public string Username { get; set; } - [JsonProperty("ladder_id")] - public int LadderId { get; set; } + [JsonProperty("ladder_id")] + public int LadderId { get; set; } - [JsonProperty("card_id")] - public int CardId { get; set; } + [JsonProperty("card_id")] + public int CardId { get; set; } - [JsonProperty("ladder")] - public QmLadder Ladder { get; set; } - } -} + [JsonProperty("ladder")] + public QmLadder Ladder { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs index 983f30be7..5ba2f698d 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUserSettings.cs @@ -4,102 +4,101 @@ using Newtonsoft.Json; using Rampastring.Tools; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; + +public class QmUserSettings { - public class QmUserSettings - { - private static readonly string SettingsFile = $"{ProgramConstants.ClientUserFilesPath}QuickMatchSettings.ini"; + private static readonly string SettingsFile = $"{ProgramConstants.ClientUserFilesPath}QuickMatchSettings.ini"; - private const string BasicSectionKey = "Basic"; - private const string AuthSectionKey = "Auth"; - private const string AuthDataKey = "AuthData"; - private const string EmailKey = "Email"; - private const string LadderKey = "Ladder"; - private const string SideKey = "Side"; + private const string BasicSectionKey = "Basic"; + private const string AuthSectionKey = "Auth"; + private const string AuthDataKey = "AuthData"; + private const string EmailKey = "Email"; + private const string LadderKey = "Ladder"; + private const string SideKey = "Side"; - public string Email { get; set; } + public string Email { get; set; } - public string Ladder { get; set; } + public string Ladder { get; set; } - public int? SideId { get; set; } + public int? SideId { get; set; } - public QmAuthData AuthData { get; set; } + public QmAuthData AuthData { get; set; } - private QmUserSettings() - { - } + private QmUserSettings() + { + } - public static QmUserSettings Load() - { - var settings = new QmUserSettings(); - if (!File.Exists(SettingsFile)) - return settings; + public static QmUserSettings Load() + { + var settings = new QmUserSettings(); + if (!File.Exists(SettingsFile)) + return settings; - var iniFile = new IniFile(SettingsFile); - LoadAuthSettings(iniFile, settings); - LoadBasicSettings(iniFile, settings); + var iniFile = new IniFile(SettingsFile); + LoadAuthSettings(iniFile, settings); + LoadBasicSettings(iniFile, settings); - return settings; - } + return settings; + } - public void ClearAuthData() => AuthData = null; + public void ClearAuthData() => AuthData = null; - public void Save() - { - var iniFile = new IniFile(); - var authSection = new IniSection(AuthSectionKey); - authSection.AddKey(EmailKey, Email ?? string.Empty); - authSection.AddKey(AuthDataKey, JsonConvert.SerializeObject(AuthData)); - - var basicSection = new IniSection(BasicSectionKey); - basicSection.AddKey(LadderKey, Ladder ?? string.Empty); - basicSection.AddKey(SideKey, SideId?.ToString() ?? string.Empty); - - iniFile.AddSection(authSection); - iniFile.AddSection(basicSection); - iniFile.WriteIniFile(SettingsFile); - } + public void Save() + { + var iniFile = new IniFile(); + var authSection = new IniSection(AuthSectionKey); + authSection.AddKey(EmailKey, Email ?? string.Empty); + authSection.AddKey(AuthDataKey, JsonConvert.SerializeObject(AuthData)); + + var basicSection = new IniSection(BasicSectionKey); + basicSection.AddKey(LadderKey, Ladder ?? string.Empty); + basicSection.AddKey(SideKey, SideId?.ToString() ?? string.Empty); + + iniFile.AddSection(authSection); + iniFile.AddSection(basicSection); + iniFile.WriteIniFile(SettingsFile); + } - private static void LoadAuthSettings(IniFile iniFile, QmUserSettings settings) - { - IniSection authSection = iniFile.GetSection(AuthSectionKey); - if (authSection == null) - return; + private static void LoadAuthSettings(IniFile iniFile, QmUserSettings settings) + { + IniSection authSection = iniFile.GetSection(AuthSectionKey); + if (authSection == null) + return; - settings.AuthData = GetAuthData(authSection); - settings.Email = authSection.GetStringValue(EmailKey, null); - } + settings.AuthData = GetAuthData(authSection); + settings.Email = authSection.GetStringValue(EmailKey, null); + } - private static void LoadBasicSettings(IniFile iniFile, QmUserSettings settings) + private static void LoadBasicSettings(IniFile iniFile, QmUserSettings settings) + { + IniSection basicSection = iniFile.GetSection(BasicSectionKey); + if (basicSection == null) + return; + + settings.Ladder = basicSection.GetStringValue(LadderKey, null); + int sideId = basicSection.GetIntValue(SideKey, -1); + if (sideId != -1) + settings.SideId = sideId; + } + + private static QmAuthData GetAuthData(IniSection section) + { + if (!section.KeyExists(AuthDataKey)) + return null; + + string authDataValue = section.GetStringValue(AuthDataKey, null); + if (string.IsNullOrEmpty(authDataValue)) + return null; + + try { - IniSection basicSection = iniFile.GetSection(BasicSectionKey); - if (basicSection == null) - return; - - settings.Ladder = basicSection.GetStringValue(LadderKey, null); - int sideId = basicSection.GetIntValue(SideKey, -1); - if (sideId != -1) - settings.SideId = sideId; + return JsonConvert.DeserializeObject(authDataValue); } - - private static QmAuthData GetAuthData(IniSection section) + catch (Exception e) { - if (!section.KeyExists(AuthDataKey)) - return null; - - string authDataValue = section.GetStringValue(AuthDataKey, null); - if (string.IsNullOrEmpty(authDataValue)) - return null; - - try - { - return JsonConvert.DeserializeObject(authDataValue); - } - catch (Exception e) - { - Logger.Log(e.ToString()); - return null; - } + Logger.Log(e.ToString()); + return null; } } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestResponseConverter.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestResponseConverter.cs deleted file mode 100644 index e27113bbf..000000000 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestResponseConverter.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; - -/// -/// The response from the Ladder api for a match request can come back in a few different response types: -/// -/// -public class QmRequestResponseConverter : JsonConverter -{ - public override bool CanWrite => false; - - public override void WriteJson(JsonWriter writer, QmRequestResponse value, JsonSerializer serializer) => throw new NotImplementedException(); - - public override QmRequestResponse ReadJson(JsonReader reader, Type objectType, QmRequestResponse existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var token = JObject.Load(reader); - string responseType = token[QmRequestResponse.TypeKey]?.ToString(); - - if (responseType == null) - return null; - - Type subType = QmRequestResponse.GetSubType(responseType); - - existingValue ??= Activator.CreateInstance(subType) as QmRequestResponse; - - if (existingValue == null) - return null; - - using JsonReader subReader = token.CreateReader(); - serializer.Populate(subReader, existingValue); - - return existingValue; - } -} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmMatchRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmMatchRequest.cs new file mode 100644 index 000000000..6ef34737d --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmMatchRequest.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; + +public class QmMatchRequest : QmRequest +{ + [JsonProperty("lan_ip")] + public string LanIP { get; set; } + + [JsonProperty("lan_port")] + public int LanPort { get; set; } + + [JsonProperty("ipv6_address")] + public string IPv6Address { get; set; } + + [JsonProperty("ipv6_port")] + public int IPv6Port { get; set; } + + [JsonProperty("ip_address")] + public string IPAddress { get; set; } + + [JsonProperty("ip_port")] + public int IPPort { get; set; } + + [JsonProperty("side")] + public int Side { get; set; } + + [JsonProperty("map_bitfield")] + public string MapBitfield { get; set; } + + [JsonProperty("platform")] + public string Platform { get; set; } + + [JsonProperty("map_sides")] + public IEnumerable MapSides { get; set; } + + [JsonProperty("ai_dat")] + public bool CheatSeen { get; set; } + + [JsonProperty("exe_hash")] + public string ExeHash { get; set; } + + [JsonProperty("ddraw")] + public string DDrawHash { get; set; } + + [JsonProperty("session")] + public string Session { get; set; } + + public QmMatchRequest() + { + Type = QmRequestTypes.MatchMeUp; + MapBitfield = int.MaxValue.ToString(); + Platform = "win32"; + Session = string.Empty; + DDrawHash = "8a00ba609f7d030c67339e1f555199bdb4054b67"; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmNotReadyRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmNotReadyRequest.cs similarity index 67% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmNotReadyRequest.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmNotReadyRequest.cs index 14a02dcdd..a5b55bc08 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmNotReadyRequest.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmNotReadyRequest.cs @@ -1,4 +1,4 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; public class QmNotReadyRequest : QmUpdateRequest { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmQuitRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmQuitRequest.cs new file mode 100644 index 000000000..01b817f38 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmQuitRequest.cs @@ -0,0 +1,11 @@ +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; + +public class QmQuitRequest : QmRequest +{ + public QmQuitRequest() + { + Type = QmRequestTypes.Quit; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmReadyRequest.cs similarity index 66% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmReadyRequest.cs index bec9c086a..89d8d28d8 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmReadyRequest.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmReadyRequest.cs @@ -1,4 +1,4 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; public class QmReadyRequest : QmUpdateRequest { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmRequest.cs new file mode 100644 index 000000000..be5891def --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmRequest.cs @@ -0,0 +1,16 @@ +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; + +public class QmRequest +{ + [JsonIgnore] + public string Url { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("version")] + public string Version { get; set; } = QmService.QmVersion; +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUpdateRequest.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmUpdateRequest.cs similarity index 57% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUpdateRequest.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmUpdateRequest.cs index 0bfe9e4e0..0f4726e6f 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmUpdateRequest.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Requests/QmUpdateRequest.cs @@ -1,6 +1,7 @@ -using Newtonsoft.Json; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; +using Newtonsoft.Json; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; public abstract class QmUpdateRequest : QmRequest { @@ -12,7 +13,7 @@ public abstract class QmUpdateRequest : QmRequest protected QmUpdateRequest(int seed) { - Type = "update"; + Type = QmRequestTypes.Update; Seed = seed; } } \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmErrorResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmErrorResponse.cs new file mode 100644 index 000000000..48efc8ea1 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmErrorResponse.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; + +public class QmErrorResponse : QmResponse +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmFatalResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmFatalResponse.cs new file mode 100644 index 000000000..42030e0d8 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmFatalResponse.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; + +public class QmFatalResponse : QmErrorResponse +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmQuitResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmQuitResponse.cs new file mode 100644 index 000000000..7b4f701ff --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmQuitResponse.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; + +public class QmQuitResponse : QmResponse +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmResponse.cs new file mode 100644 index 000000000..79785f446 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmResponse.cs @@ -0,0 +1,39 @@ +using System; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Converters; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; + +[JsonConverter(typeof(QmRequestResponseConverter))] +public abstract class QmResponse +{ + public const string TypeKey = "type"; + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("message")] + public string Message { get; set; } + + [JsonIgnore] + public bool IsSuccessful => this is not QmErrorResponse; + + [JsonIgnore] + public QmRequest Request { get; set; } + + public static Type GetSubType(string type) + { + return type switch + { + QmResponseTypes.Wait => typeof(QmWaitResponse), + QmResponseTypes.Spawn => typeof(QmSpawnResponse), + QmResponseTypes.Error => typeof(QmErrorResponse), + QmResponseTypes.Fatal => typeof(QmFatalResponse), + QmResponseTypes.Update => typeof(QmUpdateResponse), + QmResponseTypes.Quit => typeof(QmQuitResponse), + _ => null + }; + } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmSpawnResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmSpawnResponse.cs new file mode 100644 index 000000000..4f59768ba --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmSpawnResponse.cs @@ -0,0 +1,10 @@ +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; + +public class QmSpawnResponse : QmResponse +{ + [JsonProperty("spawn")] + public QmSpawn Spawn { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmUpdateResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmUpdateResponse.cs new file mode 100644 index 000000000..69ac58ddb --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmUpdateResponse.cs @@ -0,0 +1,5 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; + +public class QmUpdateResponse : QmResponse +{ +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmWaitResponse.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmWaitResponse.cs new file mode 100644 index 000000000..ce011f1de --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Responses/QmWaitResponse.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; + +public class QmWaitResponse : QmResponse +{ + [JsonProperty("checkback")] + public int CheckBack { get; set; } + + [JsonProperty("no_sooner_than")] + public int NoSoonerThan { get; set; } +} \ No newline at end of file diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmApiService.cs similarity index 87% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmApiService.cs index 2e5ed4d27..7b1ea52de 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmApiService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmApiService.cs @@ -6,10 +6,13 @@ using System.Threading.Tasks; using ClientCore.Exceptions; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; using Newtonsoft.Json; using Rampastring.Tools; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; public class QmApiService : IDisposable { @@ -108,6 +111,19 @@ private async Task HandleLoginResponse(HttpResponseMessage response, return authData; } + public virtual bool IsServerAvailable() + { + HttpClient httpClient = GetHttpClient(); + HttpResponseMessage response = httpClient.GetAsync(qmSettings.ServerStatusUrl).Result; + return response.IsSuccessStatusCode; + } + + public virtual async Task QuickMatchRequestAsync(string ladder, string playerName, QmRequest qmRequest) + { + string url = string.Format(qmSettings.QuickMatchUrlFormat, ladder, playerName); + return await MakeRequest(new QmRequest { Url = url }); + } + private async Task HandleFailedLoginResponse(HttpResponseMessage response, string unknownErrorFormat) { string responseBody = await response.Content.ReadAsStringAsync(); @@ -128,21 +144,13 @@ private async Task HandleFailedLoginResponse(HttpResponseMessage res throw new ClientRequestException(message, response.StatusCode); } - public virtual bool IsServerAvailable() - { - HttpClient httpClient = GetHttpClient(); - HttpResponseMessage response = httpClient.GetAsync(qmSettings.ServerStatusUrl).Result; - return response.IsSuccessStatusCode; - } - private HttpClient GetHttpClient() => _httpClient ??= new HttpClient { BaseAddress = new Uri(qmSettings.BaseUrl), Timeout = TimeSpan.FromSeconds(10) }; - public virtual async Task QuickMatchRequestAsync(string ladder, string playerName, QmRequest qmRequest) + private async Task MakeRequest(QmRequest request) { HttpClient httpClient = GetHttpClient(); - string url = string.Format(qmSettings.QuickMatchUrlFormat, ladder, playerName); - HttpResponseMessage response = await httpClient.PostAsync(url, new StringContent(JsonConvert.SerializeObject(qmRequest), Encoding.Default, "application/json")); + HttpResponseMessage response = await httpClient.PostAsync(request.Url, new StringContent(JsonConvert.SerializeObject(request), Encoding.Default, "application/json")); string responseBody = await response.Content.ReadAsStringAsync(); Logger.Log(responseBody); @@ -150,11 +158,12 @@ public virtual async Task QuickMatchRequestAsync(string ladde if (!response.IsSuccessStatusCode) throw new ClientException(string.Format(QmStrings.RequestingMatchErrorFormat, response.ReasonPhrase)); - QmRequestResponse matchRequestResponse = JsonConvert.DeserializeObject(responseBody); - if (!(matchRequestResponse?.IsSuccessful ?? false)) - throw new ClientException(string.Format(QmStrings.RequestingMatchErrorFormat, matchRequestResponse?.Message ?? matchRequestResponse?.Description ?? "unknown")); + QmResponse matchResponse = JsonConvert.DeserializeObject(responseBody); + if (!(matchResponse?.IsSuccessful ?? false)) + throw new ClientException(string.Format(QmStrings.RequestingMatchErrorFormat, matchResponse?.Message ?? matchResponse?.Description ?? "unknown")); - return matchRequestResponse; + matchResponse.Request = request; + return matchResponse; } public void Dispose() diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMockApiService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmMockApiService.cs similarity index 56% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMockApiService.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmMockApiService.cs index 0485c378d..eefe87c13 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMockApiService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmMockApiService.cs @@ -1,16 +1,13 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; -using System.Net; -using System.Net.Http; -using System.Text; using System.Threading.Tasks; -using ClientCore.Exceptions; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; using Newtonsoft.Json; -using Rampastring.Tools; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; public class QmMockApiService : QmApiService { @@ -26,21 +23,20 @@ public class QmMockApiService : QmApiService public override async Task RefreshAsync() => LoadMockData("qm_login_response.json"); - public override async Task QuickMatchRequestAsync(string ladder, string playerName, QmRequest qmRequest) + public override async Task QuickMatchRequestAsync(string ladder, string playerName, QmRequest qmRequest) { - const string responseType = QmResponseTypes.Wait; - return responseType switch + return true switch { - QmResponseTypes.Error => LoadMockData("qm_find_match_spawn_response.json"), - QmResponseTypes.Spawn => LoadMockData("qm_find_match_spawn_response.json"), - QmResponseTypes.Wait => LoadMockData("qm_find_match_please_wait_response.json"), - _ => throw new NotImplementedException("unknown mock response type") + true when qmRequest.Type == QmRequestTypes.Quit => LoadMockData("qm_find_match_quit_response.json"), + true when qmRequest.Type == QmRequestTypes.MatchMeUp => LoadMockData("qm_find_match_spawn_response.json"), + // true when qmRequest.Type ==QmRequestTypes.Update && updateRequest?.Status == QmUpdateRequestStatuses.Ready => LoadMockData("qm_find_match_please_wait_response.json"), + _ => new QmUpdateResponse() { Message = "default response" } }; } - + public override bool IsServerAvailable() => true; - private T LoadMockData(string mockDataFileName) + private static T LoadMockData(string mockDataFileName) { string content = File.ReadAllText($"MockData/QuickMatch/{mockDataFileName}"); diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmService.cs similarity index 90% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmService.cs index ec97d2bbe..1e2da535d 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmService.cs @@ -7,8 +7,11 @@ using System.Timers; using ClientCore; using ClientCore.Exceptions; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Events; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models.Events; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Requests; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Responses; +using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; using DTAClient.Online; using JWT; using JWT.Algorithms; @@ -16,7 +19,7 @@ using JWT.Serializers; using Rampastring.Tools; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; public class QmService : IDisposable { @@ -191,19 +194,19 @@ public void LoadLadderStatsForAbbrAsync(string ladderAbbr) => public void RequestMatchAsync() => ExecuteRequest(new QmRequestingMatchEvent(CancelRequestMatchAsync), async () => { - QmRequestResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, GetMatchRequest()); + QmResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, GetMatchRequest()); HandleQuickMatchResponse(response); }); /// /// This is called when the user clicks the "I'm Ready" button in the match found dialog. /// - public void AcceptMatchAsync(QmRequestSpawnResponseSpawn spawn) + public void AcceptMatchAsync(QmSpawn spawn) { ExecuteRequest(new QmReadyRequestMatchEvent(), async () => { var readyRequest = new QmReadyRequest(spawn.Settings.Seed); - QmRequestResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, readyRequest); + QmResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, readyRequest); HandleQuickMatchResponse(response); }); } @@ -211,18 +214,18 @@ public void AcceptMatchAsync(QmRequestSpawnResponseSpawn spawn) /// /// This is called when the user clicks the "Cancel" button in the match found dialog. /// - public void RejectMatchAsync(QmRequestSpawnResponseSpawn spawn) + public void RejectMatchAsync(QmSpawn spawn) { ExecuteRequest(new QmNotReadyRequestMatchEvent(), async () => { var notReadyRequest = new QmNotReadyRequest(spawn.Settings.Seed); - QmRequestResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, notReadyRequest); + QmResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, notReadyRequest); HandleQuickMatchResponse(response); }); CancelRequestMatchAsync(); } - public void WriteSpawnIni(QmRequestSpawnResponse spawnResponse) + public void WriteSpawnIni(QmSpawnResponse spawnResponse) { IniFile spawnIni = CreateSpawnIniFile(); @@ -240,7 +243,7 @@ public void WriteSpawnIni(QmRequestSpawnResponse spawnResponse) { // Headers for OTHER# sections are 1-based index var otherSection = new IniSection($"Other{i + 1}"); - QmRequestSpawnResponseSpawnOther other = spawnResponse.Spawn.Others[i]; + QmSpawnOther other = spawnResponse.Spawn.Others[i]; foreach (PropertyInfo otherProp in other.GetType().GetProperties()) otherSection.SetStringValue(otherProp.Name, otherProp.GetValue(other).ToString()); @@ -309,17 +312,17 @@ private QmMatchRequest GetMatchRequest() private void RetryRequestMatchAsync() => RequestMatchAsync(); - private void HandleQuickMatchResponse(QmRequestResponse qmRequestResponse) + private void HandleQuickMatchResponse(QmResponse qmResponse) { switch (true) { - case true when qmRequestResponse is QmRequestWaitResponse waitResponse: + case true when qmResponse is QmWaitResponse waitResponse: retryRequestmatchTimer.Interval = waitResponse.CheckBack * 1000; retryRequestmatchTimer.Start(); break; } - QmEvent?.Invoke(this, new QmRequestResponseEvent(qmRequestResponse)); + QmEvent?.Invoke(this, new QmResponseEvent(qmResponse)); } /// @@ -352,8 +355,8 @@ private void CancelRequestMatchAsync() => ExecuteRequest(new QmCancelingRequestMatchEvent(), async () => { retryRequestmatchTimer.Stop(); - QmRequestResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, new QmQuitRequest()); - QmEvent?.Invoke(this, new QmRequestResponseEvent(response)); + QmResponse response = await apiService.QuickMatchRequestAsync(userAccount.Ladder.Abbreviation, userAccount.Username, new QmQuitRequest()); + QmEvent?.Invoke(this, new QmResponseEvent(response)); }); private void ExecuteLoginRequest(Func func) => diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmSettingsService.cs similarity index 98% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmSettingsService.cs index d33643820..23d6a1267 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmSettingsService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmSettingsService.cs @@ -6,7 +6,7 @@ using Rampastring.Tools; using Rampastring.XNAUI; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; public class QmSettingsService { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmUserSettingsService.cs similarity index 88% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmUserSettingsService.cs index e486bb8b1..e6cbee99a 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmUserSettingsService.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Services/QmUserSettingsService.cs @@ -1,6 +1,6 @@ using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Services; public class QmUserSettingsService { @@ -10,7 +10,6 @@ public class QmUserSettingsService private QmUserSettingsService() { - } public static QmUserSettingsService GetInstance() => instance ??= new QmUserSettingsService(); diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMatchFoundTimer.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmMatchFoundTimer.cs similarity index 73% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMatchFoundTimer.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmMatchFoundTimer.cs index 132064083..f4fa01302 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmMatchFoundTimer.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmMatchFoundTimer.cs @@ -2,12 +2,13 @@ using System.Timers; using DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Models; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; public class QmMatchFoundTimer : Timer { private const int MatchupFoundTimerInterval = 100; - public QmRequestSpawnResponseSpawn Spawn { get; set; } + + public QmSpawn Spawn { get; set; } public QmMatchFoundTimer() : base(MatchupFoundTimerInterval) { @@ -16,7 +17,7 @@ public QmMatchFoundTimer() : base(MatchupFoundTimerInterval) public int GetInterval() => MatchupFoundTimerInterval; - public void SetSpawn(QmRequestSpawnResponseSpawn spawn) + public void SetSpawn(QmSpawn spawn) { Spawn = spawn; } diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmRequestTypes.cs similarity index 70% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmRequestTypes.cs index bddafd8c0..d9a927f8f 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmRequestTypes.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmRequestTypes.cs @@ -1,4 +1,4 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; public static class QmRequestTypes { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmResponseTypes.cs similarity index 80% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmResponseTypes.cs index f61436a01..b9bf79208 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmResponseTypes.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmResponseTypes.cs @@ -1,4 +1,4 @@ -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; public static class QmResponseTypes { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmStrings.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmStrings.cs similarity index 98% rename from DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmStrings.cs rename to DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmStrings.cs index bb8244fc6..acba93b56 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/QmStrings.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmStrings.cs @@ -1,6 +1,6 @@ using Localization; -namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch; +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; public static class QmStrings { diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmUpdateRequestStatuses.cs b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmUpdateRequestStatuses.cs new file mode 100644 index 000000000..fa697e8e9 --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmUpdateRequestStatuses.cs @@ -0,0 +1,9 @@ +namespace DTAClient.Domain.Multiplayer.CnCNet.QuickMatch.Utilities; + +public static class QmUpdateRequestStatuses +{ + public const string Ready = "Ready"; + public const string NotReady = "NotReady"; + public const string GameFinished = "GameFinished"; + public const string Reached = "Reached"; +} \ No newline at end of file diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_quit_response.json b/DXMainClient/MockData/QuickMatch/qm_find_match_quit_response.json new file mode 100644 index 000000000..3116ae077 --- /dev/null +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_quit_response.json @@ -0,0 +1,3 @@ +{ + "type": "quit" +} diff --git a/DXMainClient/MockData/QuickMatch/qm_find_match_spawn_response.json b/DXMainClient/MockData/QuickMatch/qm_find_match_spawn_response.json index 1b646331f..025948a3f 100644 --- a/DXMainClient/MockData/QuickMatch/qm_find_match_spawn_response.json +++ b/DXMainClient/MockData/QuickMatch/qm_find_match_spawn_response.json @@ -1,50 +1,50 @@ { - "type": "spawn", - "gameID": 456, - "spawn": { - "SpawnLocations": { - "Multi2": -1, - "Multi1": -1 - }, - "Settings": { - "UIMapName": "Test Map", - "MapHash": "35f8f92ebbb94b62918326da5837fedd", - "Seed": 123, - "GameID": 456, - "WOLGameID": 456, - "Host": "No", - "IsSpectator": "No", - "Name": "TestUserMe", - "Port": 51143, - "Side": 0, - "Color": 1, - "GameSpeed": "0", - "Credits": "10000", - "UnitCount": "0", - "Superweapons": "Yes", - "Tournament": "1", - "ShortGame": "Yes", - "Bases": "Yes", - "MCVRedeploy": "Yes", - "MultipleFactory": "Yes", - "Crates": "No", - "GameMode": "Battle", - "FrameSendRate": "4", - "DisableSWvsYuri": "Yes" - }, - "Other1": { - "Name": "TestUser2", - "Side": 0, - "Color": 0, - "Ip": "127.0.0.1", - "Port": 51143, - "IPv6": "", - "PortV6": 0, - "LanIP": "", - "LanPort": 51143 - } + "type": "spawn", + "gameID": 603498, + "spawn": { + "SpawnLocations": { + "Multi1": -1, + "Multi2": -1 }, - "client": { - "show_map_preview": 1 + "Settings": { + "UIMapName": "Copacabana", + "MapHash": "d3797ec3d3e29cfa2ac81f9e08c9e7ebab142952", + "Seed": -1976956982, + "GameID": -1976956982, + "WOLGameID": -1976956982, + "Host": "No", + "IsSpectator": "No", + "Name": "devo1929", + "Port": 51143, + "Side": 0, + "Color": 0, + "GameSpeed": "0", + "Credits": "10000", + "UnitCount": "0", + "Superweapons": "Yes", + "Tournament": "1", + "ShortGame": "Yes", + "Bases": "Yes", + "MCVRedeploy": "Yes", + "MultipleFactory": "Yes", + "Crates": "No", + "GameMode": "Battle", + "FrameSendRate": "4", + "DisableSWvsYuri": "Yes" + }, + "Other1": { + "Name": "neogrant", + "Side": 0, + "Color": 1, + "Ip": "98.111.198.94", + "Port": 1024, + "IPv6": "", + "PortV6": 0, + "LanIP": "", + "LanPort": 51143 } + }, + "client": { + "show_map_preview": 1 + } } diff --git a/DXMainClient/MockData/QuickMatch/qm_ladder_maps_yr_response.json b/DXMainClient/MockData/QuickMatch/qm_ladder_maps_yr_response.json index 16a921bfb..3398e6b8a 100644 --- a/DXMainClient/MockData/QuickMatch/qm_ladder_maps_yr_response.json +++ b/DXMainClient/MockData/QuickMatch/qm_ladder_maps_yr_response.json @@ -37,16 +37,16 @@ } }, { - "id": 2574, - "ladder_id": 1, - "map_id": 21, - "description": "Coldest Peak", - "bit_idx": 3, + "id": 2575, + "ladder_id": 0, + "map_id": 1408, + "description": "Copacabana", + "bit_idx": 4, "valid": 1, "spawn_order": "0,0", "created_at": "2022-10-31 23:07:40", "updated_at": "2022-10-31 23:07:40", - "team1_spawn_order": " ", + "team1_spawn_order": " ", "team2_spawn_order": "", "allowed_sides": [ 0, @@ -61,15 +61,15 @@ 9, -1 ], - "admin_description": "Coldest Peak", + "admin_description": "Copacabana", "map_pool_id": 56, "rejectable": 1, "default_reject": 0, - "hash": "ffe59ccf68fd743ee8849c615af84180396c8e67", + "hash": "d3797ec3d3e29cfa2ac81f9e08c9e7ebab142952", "map": { - "id": 21, - "hash": "ffe59ccf68fd743ee8849c615af84180396c8e67", - "name": "coldest.map", + "id": 1408, + "hash": "d3797ec3d3e29cfa2ac81f9e08c9e7ebab142952", + "name": "copacabana_d3797ec3d3e29cfa2ac81f9e08c9e7ebab142952", "ladder_id": 1 } } From 3b4ec0863999beb684efda6e1ec6d59c07076ddd Mon Sep 17 00:00:00 2001 From: devo1929 Date: Sat, 12 Nov 2022 11:05:31 -0500 Subject: [PATCH 15/19] Overhaul/cleanup api communication --- DXMainClient.Tests/DXMainClient.Tests.csproj | 24 +-- .../QmRequestResponseConverterTests.cs | 11 +- .../QmResponses/qm_please_wait_response.json | 0 .../QmResponses/qm_spawn_response.json | 0 .../QuickMatch/QuickMatchLobbyPanel.cs | 8 +- .../QuickMatch/QuickMatchStatusOverlay.cs | 2 +- .../Converters/QmRequestResponseConverter.cs | 13 +- .../QuickMatch/Events/QmResponseEvent.cs | 7 +- .../QuickMatch/Models/QmResponseMessage.cs | 34 ++++ .../QuickMatch/Responses/QmErrorResponse.cs | 6 +- .../QuickMatch/Responses/QmQuitResponse.cs | 6 +- .../CnCNet/QuickMatch/Responses/QmResponse.cs | 60 ++++--- .../QuickMatch/Responses/QmSpawnResponse.cs | 2 +- .../QuickMatch/Responses/QmUpdateResponse.cs | 6 +- .../QuickMatch/Responses/QmWaitResponse.cs | 5 +- .../QuickMatch/Services/QmApiService.cs | 156 +++++------------- .../QuickMatch/Services/QmMockApiService.cs | 22 ++- .../CnCNet/QuickMatch/Services/QmService.cs | 123 ++++++++++---- .../QuickMatch/Utilities/QmHttpClient.cs | 63 +++++++ 19 files changed, 323 insertions(+), 225 deletions(-) rename DXMainClient.Tests/{ => QuickMatch}/QmRequestResponseConverterTests.cs (70%) rename DXMainClient.Tests/TestData/{ => QuickMatch}/QmResponses/qm_please_wait_response.json (100%) rename DXMainClient.Tests/TestData/{ => QuickMatch}/QmResponses/qm_spawn_response.json (100%) create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Models/QmResponseMessage.cs create mode 100644 DXMainClient/Domain/Multiplayer/CnCNet/QuickMatch/Utilities/QmHttpClient.cs diff --git a/DXMainClient.Tests/DXMainClient.Tests.csproj b/DXMainClient.Tests/DXMainClient.Tests.csproj index af5adab58..a2c7f2e28 100644 --- a/DXMainClient.Tests/DXMainClient.Tests.csproj +++ b/DXMainClient.Tests/DXMainClient.Tests.csproj @@ -33,9 +33,6 @@ 4 - - ..\DXMainClient\bin\YRWindowsDXDebug\YR\WindowsDX\net48\clientdx.exe - @@ -43,26 +40,29 @@ - + - - - PreserveNewest - - - PreserveNewest - - {97458c1e-2e6c-4c5c-93c7-16a6712802e9} DXMainClient + + + + + + Always + + + Always + +