diff --git a/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs b/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs index 70f694a81..45f613c45 100644 --- a/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs +++ b/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs @@ -122,6 +122,7 @@ public CnCNetLobby(WindowManager windowManager, CnCNetManager connectionManager, private PasswordRequestWindow passwordRequestWindow; private bool isInGameRoom = false; + private string gameRoomChannelName = ""; private bool updateDenied = false; private string localGameID; @@ -439,7 +440,7 @@ private bool HostedGameMatches(GenericHostedGame hg) string textUpper = tbGameSearch?.Text?.ToUpperInvariant(); - string translatedGameMode = string.IsNullOrEmpty(hg.GameMode) + string translatedGameMode = string.IsNullOrEmpty(hg.GameMode) ? "Unknown".L10N("Client:Main:Unknown") : hg.GameMode.L10N($"INI:GameModes:{hg.GameMode}:UIName", notify: false); @@ -746,6 +747,7 @@ private void GameLoadingLobby_GameLeft(object sender, EventArgs e) { topBar.SwitchToSecondary(); isInGameRoom = false; + gameRoomChannelName = ""; SetLogOutButtonText(); // keep the friends window up to date so it can disable the Invite option @@ -756,6 +758,7 @@ private void GameLobby_GameLeft(object sender, EventArgs e) { topBar.SwitchToSecondary(); isInGameRoom = false; + gameRoomChannelName = ""; SetLogOutButtonText(); // keep the friends window up to date so it can disable the Invite option @@ -782,7 +785,7 @@ private void SetLogOutButtonText() private void BtnJoinGame_LeftClick(object sender, EventArgs e) => JoinSelectedGame(); private void LbGameList_DoubleLeftClick(object sender, EventArgs e) => JoinSelectedGame(); - + private void LbGameList_RightClick(object sender, EventArgs e) { lbGameList.SelectedIndex = lbGameList.HoveredIndex; @@ -996,6 +999,7 @@ private void GameChannel_UserAdded(object sender, Online.ChannelUserEventArgs e) if (e.User.IRCUser.Name == ProgramConstants.PLAYERNAME) { ClearGameChannelEvents(gameChannel); + gameRoomChannelName = gameChannel.ChannelName; gameLobby.OnJoined(); isInGameRoom = true; SetLogOutButtonText(); @@ -1102,9 +1106,10 @@ private void GameLoadingChannel_UserAdded(object sender, ChannelUserEventArgs e) { gameLoadingChannel.UserAdded -= GameLoadingChannel_UserAdded; gameLoadingChannel.InvalidPasswordEntered -= GameChannel_InvalidPasswordEntered_LoadedGame; - + gameLoadingLobby.OnJoined(); isInGameRoom = true; + gameRoomChannelName = gameLoadingChannel.ChannelName; isJoiningGame = false; } } @@ -1241,6 +1246,9 @@ private void HandleGameInviteCommand(string sender, string argumentsString) if (!CanReceiveInvitationMessagesFrom(sender)) return; + if (channelName == gameRoomChannelName) + return; + var gameIndex = lbGameList.HostedGames.FindIndex(hg => ((HostedCnCNetGame)hg).ChannelName == channelName); // also enforce user preference on whether to accept invitations from non-friends @@ -1270,47 +1278,55 @@ private void HandleGameInviteCommand(string sender, string argumentsString) return; } - var gameInviteChoiceBox = new ChoiceNotificationBox(WindowManager); - - WindowManager.AddAndInitializeControl(gameInviteChoiceBox); - - // show the invitation at top left; it will remain until it is acted upon or the target game is closed - gameInviteChoiceBox.Show( - "GAME INVITATION".L10N("Client:Main:GameInviteTitle"), - GetUserTexture(sender), - sender, - string.Format("Join {0}?".L10N("Client:Main:GameInviteText"), gameName), - "Yes".L10N("Client:Main:ButtonYes"), "No".L10N("Client:Main:ButtonNo"), 0); + GameInvitePanel panelGameInvite; + panelGameInvite = new GameInvitePanel(WindowManager, mapLoader); + panelGameInvite.Name = nameof(panelGameInvite); + panelGameInvite.BackgroundTexture = AssetLoader.LoadTexture("cncnetlobbypanelbg.png"); + panelGameInvite.DrawMode = ControlDrawMode.UNIQUE_RENDER_TARGET; + panelGameInvite.Initialize(); + panelGameInvite.Enable(); + panelGameInvite.InputEnabled = true; + panelGameInvite.Alpha = 0.5f; + panelGameInvite.AlphaRate = 0.5f; + + var hostedGame = lbGameList.HostedGames[lbGameList.HostedGames.FindIndex(hg => ((HostedCnCNetGame)hg).ChannelName == channelName)]; + WindowManager.AddAndInitializeControl(panelGameInvite); + WindowManager.CenterControlOnScreen(panelGameInvite); + panelGameInvite.SetInfo(hostedGame); // add the invitation to the index so we can remove it if the target game is closed // also lets us silently ignore new invitations from the same person while this one is still outstanding invitationIndex[invitationIdentity] = - new WeakReference(gameInviteChoiceBox); + new WeakReference(panelGameInvite); - gameInviteChoiceBox.AffirmativeClickedAction = delegate (ChoiceNotificationBox choiceBox) + panelGameInvite.AcceptInvite += () => { - // if we're currently in a game lobby, first leave that channel - if (isInGameRoom) + if (channelName != gameRoomChannelName) { - gameLobby.LeaveGameLobby(); - } - - // JoinGameByIndex does bounds checking so we're safe to pass -1 if the game doesn't exist - if (!JoinGameByIndex(lbGameList.HostedGames.FindIndex(hg => ((HostedCnCNetGame)hg).ChannelName == channelName), password)) - { - XNAMessageBox.Show(WindowManager, - "Failed to join".L10N("Client:Main:JoinFailedTitle"), - string.Format("Unable to join {0}'s game. The game may be locked or closed.".L10N("Client:Main:JoinFailedText"), sender)); + // if we're currently in a game lobby that differs to the invite, first leave that channel + if (isInGameRoom) + { + gameLobby.LeaveGameLobby(); + } + // JoinGameByIndex does bounds checking so we're safe to pass -1 if the game doesn't exist + if (!JoinGameByIndex(lbGameList.HostedGames.FindIndex(hg => ((HostedCnCNetGame)hg).ChannelName == channelName), password)) + { + XNAMessageBox.Show(WindowManager, + "Failed to join".L10N("Client:Main:JoinFailedTitle"), + string.Format("Unable to join {0}'s game. The game may be locked, closed, or on a different version.".L10N("Client:Main:JoinFailedText"), sender)); + } } // clean up the index as this invitation no longer exists invitationIndex.Remove(invitationIdentity); + WindowManager.RemoveControl(panelGameInvite); }; - gameInviteChoiceBox.NegativeClickedAction = delegate (ChoiceNotificationBox choiceBox) + panelGameInvite.DeclineInvite += () => { - // clean up the index as this invitation no longer exists + // Handle decline invite logic invitationIndex.Remove(invitationIdentity); + WindowManager.RemoveControl(panelGameInvite); }; sndGameInviteReceived.Play(); @@ -1358,7 +1374,7 @@ private void DdCurrentChannel_SelectedIndexChanged(object sender, EventArgs e) currentChatChannel = (Channel)ddCurrentChannel.SelectedItem?.Tag; if (currentChatChannel == null) throw new Exception("Current selected chat channel is null. This should not happen."); - + currentChatChannel.UserAdded += RefreshPlayerList; currentChatChannel.UserLeft += RefreshPlayerList; currentChatChannel.UserQuitIRC += RefreshPlayerList; @@ -1569,12 +1585,13 @@ private void GameBroadcastChannel_CTCPReceived(object sender, ChannelCTCPEventAr } // Seek for the game in the internal game list based on the name of its host; - // if found, then refresh that game's information, otherwise add as new game + // if found, then refresh that game's information and invites, otherwise add as new game int gameIndex = lbGameList.HostedGames.FindIndex(hg => hg.HostName == e.UserName); if (gameIndex > -1) { lbGameList.HostedGames[gameIndex] = game; + UpdateInvitations(); } else { @@ -1666,6 +1683,24 @@ private Texture2D GetUserTexture(string username) return senderGameIcon; } + private void UpdateInvitations() + { + foreach (var invitation in invitationIndex) + { + var userChannelPair = invitation.Key; + if (invitation.Value.Target is GameInvitePanel invitationNotification) + { + var game = lbGameList.HostedGames + .OfType() + .FirstOrDefault(hg => hg.HostName == userChannelPair.Item1 && hg.ChannelName == userChannelPair.Item2); + + if (game != null) + { + invitationNotification.SetInfo(game); + } + } + } + } private void DismissInvalidInvitations() { @@ -1694,7 +1729,7 @@ private void DismissInvitation(UserChannelPair invitationIdentity) { if (invitationIndex.ContainsKey(invitationIdentity)) { - var invitationNotification = invitationIndex[invitationIdentity].Target as ChoiceNotificationBox; + var invitationNotification = invitationIndex[invitationIdentity].Target as GameInvitePanel; if (invitationNotification != null) { diff --git a/DXMainClient/DXGUI/Multiplayer/GameInvitePanel.cs b/DXMainClient/DXGUI/Multiplayer/GameInvitePanel.cs new file mode 100644 index 000000000..95dad198f --- /dev/null +++ b/DXMainClient/DXGUI/Multiplayer/GameInvitePanel.cs @@ -0,0 +1,110 @@ +using System; +using System.Diagnostics; +using System.Windows.Forms; + +using ClientCore; +using ClientCore.Extensions; + +using ClientGUI; + +using DTAClient.Domain.Multiplayer; + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace DTAClient.DXGUI.Multiplayer +{ + /// + /// A UI panel that displays information about a hosted CnCNet or LAN game. + /// + public class GameInvitePanel : XNAPanel + { + public GameInvitePanel(WindowManager windowManager, MapLoader mapLoader) + : base(windowManager) + { + this.mapLoader = mapLoader; + DrawMode = ControlDrawMode.UNIQUE_RENDER_TARGET; + } + + private readonly MapLoader mapLoader; + + private GenericHostedGame game = null; + + private const int buttonWidth = UIDesignConstants.BUTTON_WIDTH_92; + private const int buttonHeight = UIDesignConstants.BUTTON_HEIGHT; + private const int padding = 12; + + private XNALabel lblInviteHeading; + private XNAClientButton btnInviteAccept; + private XNAClientButton btnInviteDecline; + private GameInformationPanel panelGameInformation; + + public event Action AcceptInvite; + public event Action DeclineInvite; + + public override void Initialize() + { + panelGameInformation = new GameInformationPanel(WindowManager, mapLoader); + panelGameInformation.Name = nameof(panelGameInformation); + panelGameInformation.BackgroundTexture = AssetLoader.LoadTexture("cncnetlobbypanelbg.png"); + panelGameInformation.DrawMode = ControlDrawMode.UNIQUE_RENDER_TARGET; + panelGameInformation.Initialize(); + panelGameInformation.ClearInfo(); + panelGameInformation.Disable(); + panelGameInformation.InputEnabled = false; + panelGameInformation.DrawBorders = false; + AddChild(panelGameInformation); + + lblInviteHeading = new XNALabel(WindowManager); + AddChild(lblInviteHeading); + + ClientRectangle = new Rectangle(0, 0, panelGameInformation.Width + 2, padding + lblInviteHeading.Height + panelGameInformation.Height + padding + buttonHeight + padding); //...+2 to account for panelGameInformation border width + BackgroundTexture = AssetLoader.CreateTexture(new Color(0, 0, 0, 255), 1, 1); + PanelBackgroundDrawMode = PanelBackgroundImageDrawMode.STRETCHED; + + panelGameInformation.X = 1; //border width + + btnInviteAccept = new XNAClientButton(WindowManager); + btnInviteAccept.Text = "Accept".L10N("Client:Main:InviteAccept"); + btnInviteAccept.ClientRectangle = new Rectangle(ClientRectangle.Width / 2 - buttonWidth - (padding / 2), panelGameInformation.Y + panelGameInformation.Height + padding, buttonWidth, buttonHeight); + btnInviteAccept.LeftClick += (s, e) => AcceptInvite?.Invoke(); + btnInviteAccept.Visible = true; + btnInviteAccept.Name = nameof(btnInviteAccept); + AddChild(btnInviteAccept); + + btnInviteDecline = new XNAClientButton(WindowManager); + btnInviteDecline.Text = "Decline".L10N("Client:Main:InviteDecline"); + btnInviteDecline.ClientRectangle = new Rectangle(ClientRectangle.Width / 2 + (padding / 2), btnInviteAccept.Y, buttonWidth, buttonHeight); + btnInviteDecline.LeftClick += (s, e) => DeclineInvite?.Invoke(); + btnInviteDecline.Visible = true; + btnInviteDecline.Name = nameof(btnInviteDecline); + AddChild(btnInviteDecline); + base.Initialize(); + } + + public void SetInfo(GenericHostedGame game) + { + if (game != null) + { + this.game = game; + lblInviteHeading.FontIndex = 1; + lblInviteHeading.Text = string.Format("{0} IS INVITING YOU TO PLAY".L10N("Client:Main:InviteHeading"), game.HostName); + lblInviteHeading.CenterOnParentHorizontally(); + lblInviteHeading.Y = ((padding + lblInviteHeading.Height + padding) / 2) - (lblInviteHeading.Height / 2); + panelGameInformation.Y = lblInviteHeading.Y + lblInviteHeading.Height + padding; + panelGameInformation.SetInfo(game); + panelGameInformation.Enable(); + }; + } + + public override void Draw(GameTime gameTime) + { + base.Draw(gameTime); + + DrawChildren(gameTime); + } + } +}