From ff5f14ca88b25bb3f9d77d76d7c9a17e73193712 Mon Sep 17 00:00:00 2001 From: Grant Bartlett Date: Fri, 3 Jan 2025 22:20:58 +0000 Subject: [PATCH] Select & show preferred skill level when making a game (#598) --- ClientCore/ClientConfiguration.cs | 2 + ClientCore/ProgramConstants.cs | 2 +- .../DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs | 10 +++-- .../CnCNet/GameCreationEventArgs.cs | 4 +- .../Multiplayer/CnCNet/GameCreationWindow.cs | 41 +++++++++++++++--- .../DXGUI/Multiplayer/GameInformationPanel.cs | 20 +++++++-- DXMainClient/DXGUI/Multiplayer/GameListBox.cs | 32 ++++++++++++++ .../Multiplayer/GameLobby/CnCNetGameLobby.cs | 8 +++- .../Domain/Multiplayer/GenericHostedGame.cs | 2 + DXMainClient/Resources/DTA/skillLevel1.png | Bin 0 -> 431 bytes DXMainClient/Resources/DTA/skillLevel2.png | Bin 0 -> 597 bytes DXMainClient/Resources/DTA/skillLevel3.png | Bin 0 -> 711 bytes 12 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 DXMainClient/Resources/DTA/skillLevel1.png create mode 100644 DXMainClient/Resources/DTA/skillLevel2.png create mode 100644 DXMainClient/Resources/DTA/skillLevel3.png diff --git a/ClientCore/ClientConfiguration.cs b/ClientCore/ClientConfiguration.cs index baf3fa739..036a7f172 100644 --- a/ClientCore/ClientConfiguration.cs +++ b/ClientCore/ClientConfiguration.cs @@ -330,6 +330,8 @@ private List ParseTranslationGameFiles() public string AllowedCustomGameModes => clientDefinitionsIni.GetStringValue(SETTINGS, "AllowedCustomGameModes", "Standard,Custom Map"); + public string SkillLevelOptions => clientDefinitionsIni.GetStringValue(SETTINGS, "SkillLevelOptions", "Any,Beginner,Intermediate,Pro"); + public string GetGameExecutableName() { string[] exeNames = clientDefinitionsIni.GetStringValue(SETTINGS, "GameExecutableNames", "Game.exe").Split(','); diff --git a/ClientCore/ProgramConstants.cs b/ClientCore/ProgramConstants.cs index f14fdb0f9..951e34573 100644 --- a/ClientCore/ProgramConstants.cs +++ b/ClientCore/ProgramConstants.cs @@ -26,7 +26,7 @@ public static class ProgramConstants public const string QRES_EXECUTABLE = "qres.dat"; - public const string CNCNET_PROTOCOL_REVISION = "R11"; + public const string CNCNET_PROTOCOL_REVISION = "R12"; public const string LAN_PROTOCOL_REVISION = "RL7"; public const int LAN_PORT = 1234; public const int LAN_INGAME_PORT = 1234; diff --git a/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs b/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs index f793cf940..70f694a81 100644 --- a/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs +++ b/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs @@ -937,7 +937,7 @@ private void _JoinGame(HostedCnCNetGame hg, string password) } else { - gameLobby.SetUp(gameChannel, false, hg.MaxPlayers, hg.TunnelServer, hg.HostName, hg.Passworded); + gameLobby.SetUp(gameChannel, false, hg.MaxPlayers, hg.TunnelServer, hg.HostName, hg.Passworded, hg.SkillLevel); gameChannel.UserAdded += GameChannel_UserAdded; gameChannel.InvalidPasswordEntered += GameChannel_InvalidPasswordEntered_NewGame; gameChannel.InviteOnlyErrorOnJoin += GameChannel_InviteOnlyErrorOnJoin; @@ -1049,7 +1049,7 @@ private void Gcw_GameCreated(object sender, GameCreationEventArgs e) Channel gameChannel = connectionManager.CreateChannel(e.GameRoomName, channelName, false, true, password); connectionManager.AddChannel(gameChannel); - gameLobby.SetUp(gameChannel, true, e.MaxPlayers, e.Tunnel, ProgramConstants.PLAYERNAME, isCustomPassword); + gameLobby.SetUp(gameChannel, true, e.MaxPlayers, e.Tunnel, ProgramConstants.PLAYERNAME, isCustomPassword, e.GameDifficulty); gameChannel.UserAdded += GameChannel_UserAdded; //gameChannel.MessageAdded += GameChannel_MessageAdded; connectionManager.SendCustomMessage(new QueuedMessage("JOIN " + channelName + " " + password, @@ -1496,7 +1496,7 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr string msg = e.Message.Substring(5); // Cut out GAME part string[] splitMessage = msg.Split(new char[] { ';' }); - if (splitMessage.Length != 11) + if (splitMessage.Length != 12) { Logger.Log("Ignoring CTCP game message because of an invalid amount of parameters."); return; @@ -1526,6 +1526,9 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr int tunnelPort = int.Parse(tunnelAddressAndPort[1]); string loadedGameId = splitMessage[10]; + int gameDifficulty = int.Parse(splitMessage[11]); + + Logger.Log("GameDifficulty ** Received game difficulty in ctcp: " + gameDifficulty); CnCNetGame cncnetGame = gameCollection.GameList.Find(g => g.GameBroadcastChannel == channel.ChannelName); @@ -1548,6 +1551,7 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr game.Locked = locked || (game.IsLoadedGame && !game.Players.Contains(ProgramConstants.PLAYERNAME)); game.Incompatible = cncnetGame == localGame && game.GameVersion != ProgramConstants.GAME_VERSION; game.TunnelServer = tunnel; + game.SkillLevel = gameDifficulty; if (isClosed) { diff --git a/DXMainClient/DXGUI/Multiplayer/CnCNet/GameCreationEventArgs.cs b/DXMainClient/DXGUI/Multiplayer/CnCNet/GameCreationEventArgs.cs index c64bd7710..4795147d6 100644 --- a/DXMainClient/DXGUI/Multiplayer/CnCNet/GameCreationEventArgs.cs +++ b/DXMainClient/DXGUI/Multiplayer/CnCNet/GameCreationEventArgs.cs @@ -6,17 +6,19 @@ namespace DTAClient.DXGUI.Multiplayer.CnCNet class GameCreationEventArgs : EventArgs { public GameCreationEventArgs(string roomName, int maxPlayers, - string password, CnCNetTunnel tunnel) + string password, CnCNetTunnel tunnel, int gameDifficulty) { GameRoomName = roomName; MaxPlayers = maxPlayers; Password = password; Tunnel = tunnel; + GameDifficulty = gameDifficulty; } public string GameRoomName { get; private set; } public int MaxPlayers { get; private set; } public string Password { get; private set; } public CnCNetTunnel Tunnel { get; private set; } + public int GameDifficulty { get; private set; } } } diff --git a/DXMainClient/DXGUI/Multiplayer/CnCNet/GameCreationWindow.cs b/DXMainClient/DXGUI/Multiplayer/CnCNet/GameCreationWindow.cs index bc2fa60c6..1bcc189d3 100644 --- a/DXMainClient/DXGUI/Multiplayer/CnCNet/GameCreationWindow.cs +++ b/DXMainClient/DXGUI/Multiplayer/CnCNet/GameCreationWindow.cs @@ -28,10 +28,12 @@ public GameCreationWindow(WindowManager windowManager, TunnelHandler tunnelHandl private XNATextBox tbGameName; private XNAClientDropDown ddMaxPlayers; + private XNAClientDropDown ddSkillLevel; private XNATextBox tbPassword; private XNALabel lblRoomName; private XNALabel lblMaxPlayers; + private XNALabel lblSkillLevel; private XNALabel lblPassword; private XNALabel lblTunnelServer; @@ -44,11 +46,15 @@ public GameCreationWindow(WindowManager windowManager, TunnelHandler tunnelHandl private TunnelHandler tunnelHandler; + private string[] SkillLevelOptions; + public override void Initialize() { lbTunnelList = new TunnelListBox(WindowManager, tunnelHandler); lbTunnelList.Name = nameof(lbTunnelList); + SkillLevelOptions = ClientConfiguration.Instance.SkillLevelOptions.Split(','); + Name = "GameCreationWindow"; Width = lbTunnelList.Width + UIDesignConstants.EMPTY_SPACE_SIDES * 2 + UIDesignConstants.CONTROL_HORIZONTAL_MARGIN * 2; @@ -82,10 +88,31 @@ public override void Initialize() UIDesignConstants.CONTROL_HORIZONTAL_MARGIN, ddMaxPlayers.Y + 1, 0, 0); lblMaxPlayers.Text = "Maximum number of players:".L10N("Client:Main:GameMaxPlayerCount"); + // Skill Level selector + ddSkillLevel = new XNAClientDropDown(WindowManager); + ddSkillLevel.Name = nameof(ddSkillLevel); + ddSkillLevel.ClientRectangle = new Rectangle(tbGameName.X, ddMaxPlayers.Bottom + 20, + tbGameName.Width, 21); + + for (int i = 0; i < SkillLevelOptions.Length; i++) + { + string skillLevel = SkillLevelOptions[i]; + string localizedSkillLevel = skillLevel.L10N($"INI:ClientDefinitions:SkillLevel:{i}"); + ddSkillLevel.AddItem(localizedSkillLevel); + } + + ddSkillLevel.SelectedIndex = 0; + + lblSkillLevel = new XNALabel(WindowManager); + lblSkillLevel.Name = nameof(lblSkillLevel); + lblSkillLevel.ClientRectangle = new Rectangle(UIDesignConstants.EMPTY_SPACE_SIDES + + UIDesignConstants.CONTROL_HORIZONTAL_MARGIN, ddSkillLevel.Y + 1, 0, 0); + lblSkillLevel.Text = "Select preferred skill level of players:".L10N("Client:Main:SelectSkillLevel"); + tbPassword = new XNATextBox(WindowManager); tbPassword.Name = nameof(tbPassword); tbPassword.MaximumTextLength = 20; - tbPassword.ClientRectangle = new Rectangle(tbGameName.X, ddMaxPlayers.Bottom + 20, + tbPassword.ClientRectangle = new Rectangle(tbGameName.X, ddSkillLevel.Bottom + 20, tbGameName.Width, 21); lblPassword = new XNALabel(WindowManager); @@ -142,6 +169,8 @@ public override void Initialize() AddChild(lblRoomName); AddChild(ddMaxPlayers); AddChild(lblMaxPlayers); + AddChild(ddSkillLevel); + AddChild(lblSkillLevel); AddChild(tbPassword); AddChild(lblPassword); AddChild(btnDisplayAdvancedOptions); @@ -203,7 +232,7 @@ private void BtnLoadMPGame_LeftClick(object sender, EventArgs e) GameCreationEventArgs ea = new GameCreationEventArgs(gameName, spawnSGIni.GetIntValue("Settings", "PlayerCount", 2), password, - tunnelHandler.Tunnels[lbTunnelList.SelectedIndex]); + tunnelHandler.Tunnels[lbTunnelList.SelectedIndex], ddSkillLevel.SelectedIndex); LoadedGameCreated?.Invoke(this, ea); } @@ -229,9 +258,11 @@ private void BtnCreateGame_LeftClick(object sender, EventArgs e) return; } - GameCreated?.Invoke(this, new GameCreationEventArgs(gameName, - int.Parse(ddMaxPlayers.SelectedItem.Text), tbPassword.Text, - tunnelHandler.Tunnels[lbTunnelList.SelectedIndex])); + GameCreated?.Invoke(this, + new GameCreationEventArgs(gameName,int.Parse(ddMaxPlayers.SelectedItem.Text), + tbPassword.Text,tunnelHandler.Tunnels[lbTunnelList.SelectedIndex], + ddSkillLevel.SelectedIndex) + ); } private void BtnDisplayAdvancedOptions_LeftClick(object sender, EventArgs e) diff --git a/DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs b/DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs index 8a92876c8..b64569a8f 100644 --- a/DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs +++ b/DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs @@ -3,6 +3,7 @@ using Microsoft.Xna.Framework; using DTAClient.Domain.Multiplayer; using ClientCore.Extensions; +using ClientCore; namespace DTAClient.DXGUI.Multiplayer { @@ -29,8 +30,11 @@ public GameInformationPanel(WindowManager windowManager, MapLoader mapLoader) private XNALabel lblHost; private XNALabel lblPing; private XNALabel lblPlayers; + private XNALabel lblSkillLevel; private XNALabel[] lblPlayerNames; + private string[] SkillLevelOptions; + public override void Initialize() { ClientRectangle = new Rectangle(0, 0, 235, 264); @@ -56,8 +60,11 @@ public override void Initialize() lblPing = new XNALabel(WindowManager); lblPing.ClientRectangle = new Rectangle(6, 126, 0, 0); + lblSkillLevel = new XNALabel(WindowManager); + lblSkillLevel.ClientRectangle = new Rectangle(6, 150, 0, 0); + lblPlayers = new XNALabel(WindowManager); - lblPlayers.ClientRectangle = new Rectangle(6, 150, 0, 0); + lblPlayers.ClientRectangle = new Rectangle(6, 178, 0, 0); lblPlayerNames = new XNALabel[MAX_PLAYERS]; for (int i = 0; i < lblPlayerNames.Length / 2; i++) @@ -84,22 +91,25 @@ public override void Initialize() AddChild(lblPing); AddChild(lblPlayers); AddChild(lblGameInformation); + AddChild(lblSkillLevel); lblGameInformation.CenterOnParent(); lblGameInformation.ClientRectangle = new Rectangle(lblGameInformation.X, 6, lblGameInformation.Width, lblGameInformation.Height); + SkillLevelOptions = ClientConfiguration.Instance.SkillLevelOptions.Split(','); + base.Initialize(); } public void SetInfo(GenericHostedGame game) { // we don't have the ID of a map here - string translatedMapName = string.IsNullOrEmpty(game.Map) + string translatedMapName = string.IsNullOrEmpty(game.Map) ? "Unknown".L10N("Client:Main:Unknown") : mapLoader.TranslatedMapNames.ContainsKey(game.Map) ? mapLoader.TranslatedMapNames[game.Map] : game.Map; - string translatedGameModeName = string.IsNullOrEmpty(game.GameMode) + string translatedGameModeName = string.IsNullOrEmpty(game.GameMode) ? "Unknown".L10N("Client:Main:Unknown") : game.GameMode.L10N($"INI:GameModes:{game.GameMode}:UIName", notify: false); lblGameMode.Text = Renderer.GetStringWithLimitedWidth("Game mode:".L10N("Client:Main:GameInfoGameMode") + " " + Renderer.GetSafeString(translatedGameModeName, lblGameMode.FontIndex), @@ -132,6 +142,10 @@ public void SetInfo(GenericHostedGame game) { lblPlayerNames[i].Visible = false; } + + string skillLevel = SkillLevelOptions[game.SkillLevel]; + string localizedSkillLevel = skillLevel.L10N($"INI:ClientDefinitions:SkillLevel:{game.SkillLevel}"); + lblSkillLevel.Text = "Preferred Skill Level:".L10N("Client:Main:GameInfoSkillLevel") + " " + localizedSkillLevel; } public void ClearInfo() diff --git a/DXMainClient/DXGUI/Multiplayer/GameListBox.cs b/DXMainClient/DXGUI/Multiplayer/GameListBox.cs index c7a3d4c2a..d2a256c5a 100644 --- a/DXMainClient/DXGUI/Multiplayer/GameListBox.cs +++ b/DXMainClient/DXGUI/Multiplayer/GameListBox.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; + using ClientCore; using ClientCore.Enums; using ClientCore.Extensions; using DTAClient.Domain.Multiplayer; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; + +using Rampastring.Tools; using Rampastring.XNAUI; using Rampastring.XNAUI.XNAControls; @@ -21,6 +25,7 @@ public class GameListBox : XNAListBox private const int ICON_MARGIN = 2; private const int FONT_INDEX = 0; private static string LOADED_GAME_TEXT => " (" + "Loaded Game".L10N("Client:Main:LoadedGame") + ")"; + private string[] SkillLevelIcons; public GameListBox(WindowManager windowManager, MapLoader mapLoader, string localGameIdentifier, Predicate gameMatchesFilter = null) @@ -29,8 +34,12 @@ public GameListBox(WindowManager windowManager, MapLoader mapLoader, this.mapLoader = mapLoader; this.localGameIdentifier = localGameIdentifier; GameMatchesFilter = gameMatchesFilter; + + SkillLevelIcons = ClientConfiguration.Instance.SkillLevelOptions.Split(','); } + private List txSkillLevelIcons = new List(); + private int loadedGameTextWidth; public List HostedGames = new(); @@ -185,6 +194,18 @@ public override void Initialize() ClientConfiguration.Instance.HoverOnGameColor); loadedGameTextWidth = (int)Renderer.GetTextDimensions(LOADED_GAME_TEXT, FontIndex).X; + + InitSkillLevelIcons(); + } + + private void InitSkillLevelIcons() + { + for (int i = 0; i < SkillLevelIcons.Length; i++) + { + Texture2D gd = AssetLoader.LoadTexture($"skillLevel{i}.png"); + if (gd != null) + txSkillLevelIcons.Add(gd); + } } private bool IsValidGameIndex(int index) @@ -332,6 +353,17 @@ public override void Draw(GameTime gameTime) height, txPasswordedGame.Width, txPasswordedGame.Height), Color.White); } + else + { + Texture2D txSkillLevelIcon = txSkillLevelIcons[hostedGame.SkillLevel]; + if (txSkillLevelIcon != null && hostedGame.SkillLevel != 0) + { + DrawTexture(txSkillLevelIcon, + new Rectangle(Width - txSkillLevelIcon.Width - TextBorderDistance - (scrollBarDrawn ? ScrollBar.Width : 0), + height, txSkillLevelIcon.Width, txSkillLevelIcon.Height), + Color.White); + } + } var text = lbItem.Text; if (hostedGame.IsLoadedGame) diff --git a/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs b/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs index f36d6b4f5..9971ff053 100644 --- a/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs +++ b/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs @@ -132,6 +132,8 @@ PrivateMessagingWindow pmWindow private bool closed = false; + private int gameDifficulty = 0; + private bool isCustomPassword = false; private string gameFilesHash; @@ -215,7 +217,8 @@ private void MultiplayerName_RightClick(object sender, MultiplayerNameRightClick private void GameBroadcastTimer_TimeElapsed(object sender, EventArgs e) => BroadcastGame(); public void SetUp(Channel channel, bool isHost, int playerLimit, - CnCNetTunnel tunnel, string hostName, bool isCustomPassword) + CnCNetTunnel tunnel, string hostName, bool isCustomPassword, + int gameDifficulty) { this.channel = channel; channel.MessageAdded += Channel_MessageAdded; @@ -230,6 +233,7 @@ public void SetUp(Channel channel, bool isHost, int playerLimit, this.hostName = hostName; this.playerLimit = playerLimit; this.isCustomPassword = isCustomPassword; + this.gameDifficulty = gameDifficulty; if (isHost) { @@ -1929,6 +1933,8 @@ private void BroadcastGame() sb.Append(tunnelHandler.CurrentTunnel.Address + ":" + tunnelHandler.CurrentTunnel.Port); sb.Append(";"); sb.Append(0); // LoadedGameId + sb.Append(";"); + sb.Append(gameDifficulty); // SkillLevel broadcastChannel.SendCTCPMessage(sb.ToString(), QueuedMessageType.SYSTEM_MESSAGE, 20); } diff --git a/DXMainClient/Domain/Multiplayer/GenericHostedGame.cs b/DXMainClient/Domain/Multiplayer/GenericHostedGame.cs index 34c6b4848..fda2f8abd 100644 --- a/DXMainClient/Domain/Multiplayer/GenericHostedGame.cs +++ b/DXMainClient/Domain/Multiplayer/GenericHostedGame.cs @@ -27,6 +27,8 @@ public abstract class GenericHostedGame: IEquatable public DateTime LastRefreshTime { get; set; } + public int SkillLevel { get; set; } + public virtual bool Equals(GenericHostedGame other) => string.Equals(RoomName, other?.RoomName, StringComparison.InvariantCultureIgnoreCase); } diff --git a/DXMainClient/Resources/DTA/skillLevel1.png b/DXMainClient/Resources/DTA/skillLevel1.png new file mode 100644 index 0000000000000000000000000000000000000000..a5f9bb07eeb52ccef6ad4ac9950cf368108ff8d6 GIT binary patch literal 431 zcmV;g0Z{&lP)$O-+IF{ynQD?O+^PahVx#d;|~( zh;J0d=FLXVo9kS2nSn@bodRYl4s-&B2H2Mejk)5nLi-v8Q>Wy%XBt~NMJKCZVL3zF zsp7*Yzat&DH3E)0nN}lCRd^{&!INC&==r@JoB3quH-V6b0B}P-*}2I7Wjr1-Q$cDI z1QWPuo#t1Yzv2cHh8li!WHDud!ut-V#a8}*IcG1dfJCvSo6L^g`r1krFIh0)f)`}1 z-q$!R)?IqP)|JLPbYtL2ytZ1tIJj zA#8{g2_j+%OK{%Lur@SA91&B^@9oTcvjG3F`nzhp@>a^UI;9mZykF`lD%Dl_SSzgu z|CT)e;an3@uOkDZ>tM`=JEz`k9SIJ0R|2vocCG|#Hi1OQzz0m;#opr6UMe`)y&PCj zF*_iVZd&yEWRif9+HQ*hscfTndV2teIiLL;1{0c#l_*Okijl@@#ef%2up$i=BbWD+ zyceI@3;jPPFYR3mDPz}xxdN;rYYRgXW&?lMEl?=X_?9tpiIHMj9h|tojsBNg5svXtwCc|^KIM8$00000NkvXXu0mjfDDMO> literal 0 HcmV?d00001 diff --git a/DXMainClient/Resources/DTA/skillLevel3.png b/DXMainClient/Resources/DTA/skillLevel3.png new file mode 100644 index 0000000000000000000000000000000000000000..36aad5fa95073bc29046eb991a034a9d0299475b GIT binary patch literal 711 zcmV;&0yzDNP)S&6$kNLx=atQXEEnO!?0vv|`Ie%R`!8MtNj$ z!vLByUU&Zmobsw=nGFK`ua0of%y@eY{=QaEU{?1{(9Fc&iujP;fv$O1HQ1ideA`k! z@o+t`RSzmRKo7s;Sgz(kk78#+r*RAH)FJ4U$hIu;1~++L+px56uFU%{A{=;_9fz&y zVN>HaxDIFyeLcNg3EePbgK!T0GXcK9}i6 zfrpI=S!2OCDe^G(gs|#U*N?6=ySy6kcp&()Anpe|8(2!U zz_FA%7KtJ3Ii;=$ttNX$WTd?+W!ud#HErc*#?p~t*0W@J&pic~st`4Wi!H6Nt-`55 z5)0nQAetEVbzjd+Zn87N0~K^x3uU?5WwpfH5uOZG<(9TFazskCIL?Sv#OOp_ literal 0 HcmV?d00001