diff --git a/ClientGUI/ClientGUI.csproj b/ClientGUI/ClientGUI.csproj index 4074ae8e7..a496bdef5 100644 --- a/ClientGUI/ClientGUI.csproj +++ b/ClientGUI/ClientGUI.csproj @@ -134,17 +134,21 @@ + + + + diff --git a/ClientGUI/XNAAdvancedContextMenu.cs b/ClientGUI/XNAAdvancedContextMenu.cs new file mode 100644 index 000000000..3b29c2f71 --- /dev/null +++ b/ClientGUI/XNAAdvancedContextMenu.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ClientGUI; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace ClientGUI +{ + public class XNAAdvancedContextMenu : XNAPanel + { + public XNAAdvancedContextMenu(WindowManager windowManager) : base(windowManager) + { + } + + // protected override int DrawItem(int index, Point point) + // { + // XNAContextMenuItem xnaContextMenuItem = Items[index]; + // + // switch (xnaContextMenuItem) + // { + // case XNAClientContextMenuDividerItem dividerItem: + // return DrawDivider(dividerItem, point); + // case XNAClientContextSubMenuItem subMenuItem: + // return DrawSubMenuItem(subMenuItem, index, point); + // default: + // return base.DrawItem(index, point); + // ; + // } + // } + + // private int DrawDivider(XNAClientContextMenuDividerItem dividerItem, Point point) + // { + // int lineY = dividerItem.GetLineY(point.Y); + // FillRectangle(new Rectangle(point.X, point.Y, Width - 2, dividerItem.Height ?? ItemHeight), BackColor); + // DrawLine(new Vector2(0, lineY), new Vector2(Width, lineY), BorderColor); + // return dividerItem.Height ?? ItemHeight; + // } + + // private int DrawSubMenuItem(XNAClientContextSubMenuItem subMenuItem, int index, Point point) + // { + // int arrowContainerSize = subMenuItem.Height ?? ItemHeight; + // + // var containerRectangle = new Rectangle(Width - arrowContainerSize, point.Y, arrowContainerSize, arrowContainerSize); + // var topLeftVector = new Vector2(Width - arrowContainerSize + subMenuItem.ArrowGap, point.Y + subMenuItem.ArrowGap); + // var bottomLeftVector = new Vector2(Width - arrowContainerSize + subMenuItem.ArrowGap, point.Y + arrowContainerSize - subMenuItem.ArrowGap); + // var rightVector = new Vector2(Width - subMenuItem.ArrowGap, point.Y + (arrowContainerSize / 2)); + // bool mouseInContainer = containerRectangle.Contains(GetCursorPoint()); + // + // DrawLine(topLeftVector, rightVector, BorderColor, subMenuItem.ArrowThickness); + // DrawLine(bottomLeftVector, rightVector, BorderColor, subMenuItem.ArrowThickness); + // DrawLine(topLeftVector, bottomLeftVector, BorderColor, subMenuItem.ArrowThickness); + // + // if (mouseInContainer) + // FillRectangle(containerRectangle, new Color(BorderColor, 0.1f)); + // else if (HoveredIndex == index) + // FillRectangle(new Rectangle(point.X, point.Y, Width - 2 - arrowContainerSize, arrowContainerSize), FocusColor); + // else + // FillRectangle(new Rectangle(point.X, point.Y, Width - 2 - arrowContainerSize, arrowContainerSize), BackColor); + // + // int x1 = point.X + TextHorizontalPadding; + // if (subMenuItem.Texture != null) + // { + // Renderer.DrawTexture(subMenuItem.Texture, new Rectangle(point.X + 1, point.Y + 1, subMenuItem.Texture.Width, subMenuItem.Texture.Height), Color.White); + // x1 += subMenuItem.Texture.Width + 2; + // } + // + // Color color = subMenuItem.Selectable ? GetItemTextColor(subMenuItem) : DisabledItemColor; + // DrawStringWithShadow(subMenuItem.Text, FontIndex, new Vector2(x1, point.Y + TextVerticalPadding), color); + // if (subMenuItem.HintText != null) + // { + // int x2 = Width - TextHorizontalPadding - (int)Renderer.GetTextDimensions(subMenuItem.HintText, HintFontIndex).X; + // DrawStringWithShadow(subMenuItem.HintText, HintFontIndex, new Vector2(x2, point.Y + TextVerticalPadding), color); + // } + // + // return subMenuItem.Height ?? ItemHeight; + // } + } +} diff --git a/ClientGUI/XNAAdvancedContextMenuItem.cs b/ClientGUI/XNAAdvancedContextMenuItem.cs new file mode 100644 index 000000000..9bb5bea42 --- /dev/null +++ b/ClientGUI/XNAAdvancedContextMenuItem.cs @@ -0,0 +1,90 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace ClientGUI +{ + public class XNAAdvancedContextMenuItem : XNAAdvancedContextMenuItem + { + public XNAAdvancedContextMenuItem(WindowManager windowManager) : base(windowManager) + { + } + } + + public class XNAAdvancedContextMenuItem : XNAPanel + { + public T Item { get; set; } + + /// The text of the context menu item. + public string Text { get; set; } + + /// + /// The hint text of the context menu item. + /// Drawn in the end of the item. + /// + public string HintText { get; set; } + + /// + /// Determines whether the context menu item is enabled + /// (can be clicked on). + /// + public bool Selectable { get; set; } = true; + + /// Determines whether the context menu item is visible. + public bool Visible { get; set; } = true; + + /// + /// The height of the context menu item. + /// If null, the common item height is used. + /// + public int? Height { get; set; } + + /// + /// The font index of the context menu item. + /// If null, the common font index is used. + /// + public int? FontIndex { get; set; } + + /// The texture of the context menu item. + public Texture2D Texture { get; set; } + + /// + /// The background color of the context menu item. + /// If null, the common background color is used. + /// + public Color? BackgroundColor { get; set; } + + /// + /// The color of the context menu item's text. + /// If null, the common text color is used. + /// + public Color? TextColor { get; set; } + + /// The method that is called when the item is selected. + public Action SelectAction { get; set; } + + /// + /// When the context menu is shown, this function is called + /// to determine whether this item should be selectable. + /// If null, the value of the Enabled property is not changed. + /// + public Func SelectableChecker { get; set; } + + /// + /// When the context menu is shown, this function is called + /// to determine whether this item should be visible. + /// If null, the value of the Visible property is not changed. + /// + public Func VisibilityChecker { get; set; } + + /// The Y position of the item's text. + public float TextY { get; set; } + + public XNAAdvancedContextMenuItem(WindowManager windowManager) : base(windowManager) + { + Height = 22; + } + } +} diff --git a/ClientGUI/XNAClientContextMenuDividerItem.cs b/ClientGUI/XNAClientContextMenuDividerItem.cs new file mode 100644 index 000000000..0329eac1b --- /dev/null +++ b/ClientGUI/XNAClientContextMenuDividerItem.cs @@ -0,0 +1,17 @@ +using System; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace ClientGUI +{ + public class XNAClientContextMenuDividerItem : XNAPanel + { + public XNAClientContextMenuDividerItem(WindowManager windowManager) : base(windowManager) + { + Height = 4; + Text = string.Empty; + } + + public int GetLineY(int relativeY) => relativeY + (int)Math.Ceiling((double)Height / 2); + } +} diff --git a/ClientGUI/XNAClientContextSubMenuItem.cs b/ClientGUI/XNAClientContextSubMenuItem.cs new file mode 100644 index 000000000..d2e87005c --- /dev/null +++ b/ClientGUI/XNAClientContextSubMenuItem.cs @@ -0,0 +1,17 @@ +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace ClientGUI +{ + public class XNAClientContextSubMenuItem : XNAAdvancedContextMenuItem + { + public XNAAdvancedContextMenu Menu { get; set; } + + public int ArrowGap { get; set; } = 6; + public int ArrowThickness = 2; + + public XNAClientContextSubMenuItem(WindowManager windowManager) : base(windowManager) + { + } + } +} diff --git a/DXMainClient/DXGUI/Generic/TeamStartMappingPresetMenu.cs b/DXMainClient/DXGUI/Generic/TeamStartMappingPresetMenu.cs new file mode 100644 index 000000000..9d82fd39b --- /dev/null +++ b/DXMainClient/DXGUI/Generic/TeamStartMappingPresetMenu.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using clientdx.DXGUI.Generic; +using ClientGUI; +using DTAClient.Domain.Multiplayer; +using Microsoft.Xna.Framework; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace DTAClient.DXGUI.Generic +{ + public class TeamStartMappingPresetMenu : XNAAdvancedContextMenu + { + private XNAAdvancedContextMenuItem savePresetItem; + private XNAAdvancedContextMenuItem saveAsPresetItem; + + public TeamStartMappingPresetMenu(WindowManager windowManager) : base(windowManager) + { + + DrawBorders = true; + BackgroundTexture = AssetLoader.CreateTexture(Color.Black, 1, 1); + PanelBackgroundDrawMode = PanelBackgroundImageDrawMode.TILED; + savePresetItem = new XNAAdvancedContextMenuItem(windowManager) + { + Text = "Save" + }; + + saveAsPresetItem = new XNAAdvancedContextMenuItem(windowManager) + { + Text = "Save As" + }; + } + + private void ClearItems() + { + var children = Children.ToList(); + foreach (XNAControl xnaControl in children) + RemoveChild(xnaControl); + } + + public void ReinitItems() + { + ClearItems(); + AddChild(savePresetItem); + AddChild(saveAsPresetItem); + } + + public void AddPresetList(List presets, string headerLabel, Action selectAction) + { + if (!presets.Any()) + return; + + AddChild(new XNAClientContextMenuDividerItem(WindowManager)); + AddChild(new XNAAdvancedContextMenuItem(WindowManager) + { + Text = headerLabel, + Selectable = false + }); + presets.ForEach(preset => AddChild(new TeamStartMappingPresetMenuItem(WindowManager) + { + Item = preset, + Text = $" {preset.Name}", + SelectAction = selectAction + })); + } + + public List GetPresetItems() => + Children + .OfType() + .ToList(); + + // + public List GetPresets() => + GetPresetItems() + .Select(i => i.Item) + .ToList(); + + public void Open(Point point, TeamStartMappingPreset currentMappingPreset) + { + if (Visible) + { + Attach(); + Disable(); + return; + } + + + Detach(); + + + savePresetItem.Selectable = currentMappingPreset?.CanSave ?? false; + + ClientRectangle = new Rectangle(point.X, point.Y, 100, 100); + Enable(); + } + + public override void Draw(GameTime gameTime) + { + var children = Children.ToList(); + Height = 0; + children.ForEach(child => Height += child.Height); + + FillRectangle(ClientRectangle, Color.Black); + base.Draw(gameTime); + } + + public override void OnLeftClick() + { + base.OnLeftClick(); + + Disable(); + } + } +} diff --git a/DXMainClient/DXGUI/Generic/TeamStartMappingPresetMenuItem.cs b/DXMainClient/DXGUI/Generic/TeamStartMappingPresetMenuItem.cs new file mode 100644 index 000000000..f7d3eea60 --- /dev/null +++ b/DXMainClient/DXGUI/Generic/TeamStartMappingPresetMenuItem.cs @@ -0,0 +1,22 @@ +using System; +using ClientGUI; +using DTAClient.Domain.Multiplayer; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace clientdx.DXGUI.Generic +{ + public class TeamStartMappingPresetMenuItem : XNAClientContextSubMenuItem + { + public Action SelectAction; + + public TeamStartMappingPresetMenuItem(WindowManager windowManager) : base(windowManager) + { + } + + public override void OnLeftClick() + { + SelectAction?.Invoke(Item); + } + } +} diff --git a/DXMainClient/DXGUI/Multiplayer/CnCNet/LoadOrSaveGameOptionPresetWindow.cs b/DXMainClient/DXGUI/Multiplayer/CnCNet/PresetsWindow.cs similarity index 86% rename from DXMainClient/DXGUI/Multiplayer/CnCNet/LoadOrSaveGameOptionPresetWindow.cs rename to DXMainClient/DXGUI/Multiplayer/CnCNet/PresetsWindow.cs index 40987d800..522d13ef7 100644 --- a/DXMainClient/DXGUI/Multiplayer/CnCNet/LoadOrSaveGameOptionPresetWindow.cs +++ b/DXMainClient/DXGUI/Multiplayer/CnCNet/PresetsWindow.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using ClientGUI; using DTAClient.Domain.Multiplayer; @@ -9,7 +10,7 @@ namespace DTAClient.DXGUI.Multiplayer.CnCNet { - public class LoadOrSaveGameOptionPresetWindow : XNAWindow + public class PresetsWindow : XNAWindow { private bool _isLoad; @@ -29,11 +30,13 @@ public class LoadOrSaveGameOptionPresetWindow : XNAWindow private readonly XNATextBox tbNewPresetName; - public EventHandler PresetLoaded; + public EventHandler PresetLoaded; - public EventHandler PresetSaved; + public EventHandler PresetSaved; - public LoadOrSaveGameOptionPresetWindow(WindowManager windowManager) : base(windowManager) + public EventHandler PresetDeleted; + + public PresetsWindow(WindowManager windowManager) : base(windowManager) { ClientRectangle = new Rectangle(0, 0, 325, 185); @@ -41,6 +44,7 @@ public LoadOrSaveGameOptionPresetWindow(WindowManager windowManager) : base(wind lblHeader = new XNALabel(WindowManager); lblHeader.Name = nameof(lblHeader); + lblHeader.Text = "Presets"; lblHeader.FontIndex = 1; lblHeader.ClientRectangle = new Rectangle( margin, margin, @@ -140,17 +144,19 @@ public override void Initialize() /// /// Show the window. /// + /// + /// /// The "mode" for the window: load vs save. - public void Show(bool isLoad) + public void Show(string headerLabel, List presetNames, bool isLoad = false) { _isLoad = isLoad; - lblHeader.Text = $"{(_isLoad ? "Load" : "Save")} Preset"; + lblHeader.Text = headerLabel; btnLoadSave.Text = _isLoad ? "Load" : "Save"; if (_isLoad) - ShowLoad(); + ShowLoad(presetNames); else - ShowSave(); + ShowSave(presetNames); RefreshButtons(); CenterOnParent(); @@ -207,14 +213,13 @@ private void RefreshButtons() /// /// Populate the preset drop down from saved presets /// - private void LoadPresets() + private void LoadPresets(List presetNames) { ddPresetSelect.Items.Clear(); ddPresetSelect.Items.Add(_isLoad ? ddiSelectPresetItem : ddiCreatePresetItem); ddPresetSelect.SelectedIndex = 0; - ddPresetSelect.Items.AddRange(GameOptionPresets.Instance - .GetPresetNames() + ddPresetSelect.Items.AddRange(presetNames .OrderBy(name => name) .Select(name => new XNADropDownItem() { @@ -225,9 +230,9 @@ private void LoadPresets() /// /// Show the current window in the "load" mode context /// - private void ShowLoad() + private void ShowLoad(List presetNames) { - LoadPresets(); + LoadPresets(presetNames); // do not show fields to specify a preset name during "load" mode lblNewPresetName.Disable(); @@ -237,9 +242,9 @@ private void ShowLoad() /// /// Show the current window in the "save" mode context /// - private void ShowSave() + private void ShowSave(List presetNames) { - LoadPresets(); + LoadPresets(presetNames); // show fields to specify a preset name during "save" mode lblNewPresetName.Enable(); @@ -252,12 +257,12 @@ private void BtnLoadSave_LeftClick(object sender, EventArgs e) var selectedItem = ddPresetSelect.Items[ddPresetSelect.SelectedIndex]; if (_isLoad) { - PresetLoaded?.Invoke(this, new GameOptionPresetEventArgs(selectedItem.Text)); + PresetLoaded?.Invoke(this, new PresetWindowEventArgs(selectedItem.Text)); } else { - var presetName = IsCreatePresetSelected ? tbNewPresetName.Text : selectedItem.Text; - PresetSaved?.Invoke(this, new GameOptionPresetEventArgs(presetName)); + string presetName = IsCreatePresetSelected ? tbNewPresetName.Text : selectedItem.Text; + PresetSaved?.Invoke(this, new PresetWindowEventArgs(presetName, IsCreatePresetSelected)); } Disable(); @@ -269,7 +274,7 @@ private void BtnDelete_LeftClick(object sender, EventArgs e) var messageBox = XNAMessageBox.ShowYesNoDialog(WindowManager, "Confirm Preset Delete", $"Are you sure you want to delete this preset?\n\n{selectedItem.Text}"); messageBox.YesClickedAction = box => { - GameOptionPresets.Instance.DeletePreset(selectedItem.Text); + PresetDeleted?.Invoke(this, new PresetWindowEventArgs(selectedItem.Text)); ddPresetSelect.Items.Remove(selectedItem); ddPresetSelect.SelectedIndex = 0; }; diff --git a/DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs b/DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs index 75f752388..5f2467eb8 100644 --- a/DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs +++ b/DXMainClient/DXGUI/Multiplayer/GameLobby/GameLobbyBase.cs @@ -50,10 +50,10 @@ public abstract class GameLobbyBase : XNAWindow /// /// public GameLobbyBase( - WindowManager windowManager, + WindowManager windowManager, string iniName, - MapLoader mapLoader, - bool isMultiplayer, + MapLoader mapLoader, + bool isMultiplayer, DiscordHandler discordHandler ) : base(windowManager) { @@ -113,7 +113,7 @@ protected GameModeMap GameModeMap protected XNAClientButton btnPlayerExtraOptionsOpen; protected PlayerExtraOptionsPanel PlayerExtraOptionsPanel; - + protected XNALabel lblName; protected XNALabel lblSide; protected XNALabel lblColor; @@ -175,12 +175,12 @@ protected GameModeMap GameModeMap /// protected bool RemoveStartingLocations { get; set; } = false; protected IniFile GameOptionsIni { get; private set; } - + protected XNAClientButton BtnSaveLoadGameOptions { get; set; } - + private XNAContextMenu loadSaveGameOptionsMenu { get; set; } - - private LoadOrSaveGameOptionPresetWindow loadOrSaveGameOptionPresetWindow; + + private PresetsWindow gameOptionPresetWindow; public override void Initialize() { @@ -213,7 +213,7 @@ public override void Initialize() PlayerOptionsPanel.PanelBackgroundDrawMode = PanelBackgroundImageDrawMode.STRETCHED; InitializePlayerExtraOptionsPanel(); - + btnLeaveGame = new XNAClientButton(WindowManager); btnLeaveGame.Name = "btnLeaveGame"; btnLeaveGame.ClientRectangle = new Rectangle(Width - 143, Height - 28, UIDesignConstants.BUTTON_WIDTH_133, UIDesignConstants.BUTTON_HEIGHT); @@ -350,7 +350,7 @@ public override void Initialize() AddChild(GameOptionsPanel); AddChild(BtnSaveLoadGameOptions); AddChild(loadSaveGameOptionsMenu); - AddChild(loadOrSaveGameOptionPresetWindow); + AddChild(gameOptionPresetWindow); string[] checkBoxes = GameOptionsIni.GetStringValue(_iniSectionName, "CheckBoxes", String.Empty).Split(','); @@ -404,17 +404,31 @@ private static XNADropDownItem CreateGameFilterItem(string text, GameModeMapFilt protected bool IsFavoriteMapsSelected() => ddGameModeMapFilter.SelectedItem?.Text == FavoriteMapsLabel; - private List GetFavoriteGameModeMaps() => - GameModeMaps.Where(gmm => gmm.IsFavorite).ToList(); + private List GetFavoriteGameModeMaps() + => GameModeMaps.Where(gmm => gmm.IsFavorite).ToList(); - private Func> GetGameModeMaps(GameMode gm) => () => - GameModeMaps.Where(gmm => gmm.GameMode == gm).ToList(); + private Func> GetGameModeMaps(GameMode gm) + => () => GameModeMaps.Where(gmm => gmm.GameMode == gm).ToList(); private void InitializePlayerExtraOptionsPanel() { PlayerExtraOptionsPanel = new PlayerExtraOptionsPanel(WindowManager); PlayerExtraOptionsPanel.ClientRectangle = new Rectangle(PlayerOptionsPanel.X, PlayerOptionsPanel.Y, PlayerOptionsPanel.Width, PlayerOptionsPanel.Height); PlayerExtraOptionsPanel.OptionsChanged += PlayerExtraOptions_OptionsChanged; + PlayerExtraOptionsPanel.PresetSaved += PlayerExtraOptions_PresetSaved; + PlayerExtraOptionsPanel.PresetDeleted += PlayerExtraOptions_PresetDeleted; + } + + private void PlayerExtraOptions_PresetSaved(object sender, UserDefinedTeamStartMappingPresetEventArgs args) + { + MapLoader.AddUserDefinedTeamMappingPreset(Map, args.Preset); + PlayerExtraOptionsPanel.RefreshTeamStartMappingPanels(); + } + + private void PlayerExtraOptions_PresetDeleted(object sender, string presetName) + { + MapLoader.DeleteUserDefinedTeamMappingPreset(Map, presetName); + PlayerExtraOptionsPanel.RefreshTeamStartMappingPanels(); } private void RefreshBthPlayerExtraOptionsOpenTexture() @@ -432,20 +446,21 @@ private void InitializeGameOptionsPanel() GameOptionsPanel.BackgroundTexture = AssetLoader.CreateTexture(new Color(0, 0, 0, 192), 1, 1); GameOptionsPanel.PanelBackgroundDrawMode = PanelBackgroundImageDrawMode.STRETCHED; - loadOrSaveGameOptionPresetWindow = new LoadOrSaveGameOptionPresetWindow(WindowManager); - loadOrSaveGameOptionPresetWindow.Name = nameof(loadOrSaveGameOptionPresetWindow); - loadOrSaveGameOptionPresetWindow.PresetLoaded += (sender, s) => HandleGameOptionPresetLoadCommand(s); - loadOrSaveGameOptionPresetWindow.PresetSaved += (sender, s) => HandleGameOptionPresetSaveCommand(s); - loadOrSaveGameOptionPresetWindow.Disable(); + gameOptionPresetWindow = new PresetsWindow(WindowManager); + gameOptionPresetWindow.Name = nameof(gameOptionPresetWindow); + gameOptionPresetWindow.PresetLoaded += (sender, s) => HandleGameOptionPresetLoadCommand(s); + gameOptionPresetWindow.PresetSaved += (sender, s) => HandleGameOptionPresetSaveCommand(s); + gameOptionPresetWindow.PresetDeleted += (sender, s) => HandleGameOptionPresetDeleteCommand(s); + gameOptionPresetWindow.Disable(); var loadConfigMenuItem = new XNAContextMenuItem() { Text = "Load", - SelectAction = () => loadOrSaveGameOptionPresetWindow.Show(true) + SelectAction = () => ShowGameOptionPresetsWindow(true) }; var saveConfigMenuItem = new XNAContextMenuItem() { Text = "Save", - SelectAction = () => loadOrSaveGameOptionPresetWindow.Show(false) + SelectAction = () => ShowGameOptionPresetsWindow(false) }; loadSaveGameOptionsMenu = new XNAContextMenu(WindowManager); @@ -453,7 +468,7 @@ private void InitializeGameOptionsPanel() loadSaveGameOptionsMenu.ClientRectangle = new Rectangle(0, 0, 75, 0); loadSaveGameOptionsMenu.Items.Add(loadConfigMenuItem); loadSaveGameOptionsMenu.Items.Add(saveConfigMenuItem); - + BtnSaveLoadGameOptions = new XNAClientButton(WindowManager); BtnSaveLoadGameOptions.Name = nameof(BtnSaveLoadGameOptions); BtnSaveLoadGameOptions.ClientRectangle = new Rectangle(Width - 12, 14, 18, 22); @@ -465,16 +480,23 @@ private void InitializeGameOptionsPanel() }; } - protected void HandleGameOptionPresetSaveCommand(GameOptionPresetEventArgs e) => HandleGameOptionPresetSaveCommand(e.PresetName); - + private void ShowGameOptionPresetsWindow(bool isLoad) + => gameOptionPresetWindow.Show("Game Option Presets", GameOptionPresets.Instance.GetPresetNames(), isLoad); + + protected void HandleGameOptionPresetSaveCommand(PresetWindowEventArgs e) => HandleGameOptionPresetSaveCommand(e.PresetName); + protected void HandleGameOptionPresetSaveCommand(string presetName) { string error = AddGameOptionPreset(presetName); if (!string.IsNullOrEmpty(error)) AddNotice(error); + else + AddNotice("Game option preset saved succesfully."); } - protected void HandleGameOptionPresetLoadCommand(GameOptionPresetEventArgs e) => HandleGameOptionPresetLoadCommand(e.PresetName); + protected void HandleGameOptionPresetDeleteCommand(PresetWindowEventArgs e) => GameOptionPresets.Instance.DeletePreset(e.PresetName); + + protected void HandleGameOptionPresetLoadCommand(PresetWindowEventArgs e) => HandleGameOptionPresetLoadCommand(e.PresetName); protected void HandleGameOptionPresetLoadCommand(string presetName) { @@ -487,7 +509,7 @@ protected void HandleGameOptionPresetLoadCommand(string presetName) protected void AddNotice(string message) => AddNotice(message, Color.White); protected abstract void AddNotice(string message, Color color); - + private void BtnPickRandomMap_LeftClick(object sender, EventArgs e) => PickRandomMap(); private void TbMapSearch_InputReceived(object sender, EventArgs e) => ListMaps(); @@ -616,9 +638,9 @@ protected void ListMaps() var mapNameText = gameModeMap.Map.Name; if (isFavoriteMapsSelected) mapNameText += $" - {gameModeMap.GameMode.UIName}"; - + mapNameItem.Text = Renderer.GetSafeString(mapNameText, lbGameModeMapList.FontIndex); - + if ((gameModeMap.Map.MultiplayerOnly || gameModeMap.GameMode.MultiplayerOnly) && !isMultiplayer) mapNameItem.TextColor = UISettings.ActiveSettings.DisabledItemColor; mapNameItem.Tag = gameModeMap; @@ -650,14 +672,14 @@ private void LbGameModeMapList_RightClick(object sender, EventArgs e) { if (lbGameModeMapList.HoveredIndex < 0 || lbGameModeMapList.HoveredIndex >= lbGameModeMapList.ItemCount) return; - + lbGameModeMapList.SelectedIndex = lbGameModeMapList.HoveredIndex; if (!mapContextMenu.Items.Any(i => i.VisibilityChecker == null || i.VisibilityChecker())) return; toggleFavoriteItem.Text = GameModeMap.IsFavorite ? "Remove Favorite" : "Add Favorite"; - + mapContextMenu.Open(GetCursorPoint()); } @@ -692,7 +714,7 @@ protected void RefreshForFavoriteMapRemoved() LoadDefaultGameModeMap(); return; } - + ListMaps(); if(IsFavoriteMapsSelected()) lbGameModeMapList.SelectedIndex = 0; // the map was removed while viewing favorites @@ -948,14 +970,14 @@ protected void InitPlayerOptionDropdowns() CheckDisallowedSides(); } - + protected virtual void PlayerExtraOptions_OptionsChanged(object sender, EventArgs e) { var playerExtraOptions = GetPlayerExtraOptions(); - + for (int i = 0; i < ddPlayerSides.Length; i++) EnablePlayerOptionDropDown(ddPlayerSides[i], i, !playerExtraOptions.IsForceRandomSides); - + for (int i = 0; i < ddPlayerTeams.Length; i++) EnablePlayerOptionDropDown(ddPlayerTeams[i], i, !playerExtraOptions.IsForceRandomTeams); @@ -990,7 +1012,7 @@ protected PlayerInfo GetPlayerInfoForIndex(int playerIndex) } protected PlayerExtraOptions GetPlayerExtraOptions() => PlayerExtraOptionsPanel.GetPlayerExtraOptions(); - + protected void SetPlayerExtraOptions(PlayerExtraOptions playerExtraOptions) => PlayerExtraOptionsPanel?.SetPlayerExtraOptions(playerExtraOptions); protected string GetTeamMappingsError() => GetPlayerExtraOptions()?.GetTeamMappingsError(); @@ -1143,7 +1165,7 @@ protected void CheckDisallowedSides() foreach (XNADropDown dd in ddPlayerSides) dd.Items[i + RandomSelectorCount].Selectable = false; - // Change the sides of players that use the disabled + // Change the sides of players that use the disabled // side to the default side foreach (PlayerInfo pInfo in playerInfos) { @@ -1626,7 +1648,7 @@ private void ManipulateStartingLocations(IniFile mapIni, PlayerHouseInfo[] house // As an additional restriction, players can only start from waypoints 0 to 7. // That means that if the map already has too many starting waypoints, - // we need to move existing (but un-occupied) starting waypoints to point + // we need to move existing (but un-occupied) starting waypoints to point // to the stacked locations so we can spawn the players there. @@ -1639,7 +1661,7 @@ private void ManipulateStartingLocations(IniFile mapIni, PlayerHouseInfo[] house { startingLocationUsed[houseInfo.RealStartingWaypoint] = true; - // If assigned starting waypoint is unknown while the real + // If assigned starting waypoint is unknown while the real // starting location is known, it means that // the location is shared with another player if (houseInfo.StartingWaypoint == -1) @@ -1666,7 +1688,7 @@ private void ManipulateStartingLocations(IniFile mapIni, PlayerHouseInfo[] house // For each player, check if they're sharing the starting location // with someone else - // If they are, find an unused waypoint and assign their + // If they are, find an unused waypoint and assign their // starting location to match that for (int pId = 0; pId < houseInfos.Length; pId++) { @@ -1834,7 +1856,7 @@ private void MultiplayerName_RightClick(object sender, EventArgs e) var selectedPlayer = ((XNADropDown) sender).SelectedItem; if (!CanRightClickMultiplayer(selectedPlayer)) return; - + if (selectedPlayer == null || selectedPlayer.Text == ProgramConstants.PLAYERNAME) { @@ -1853,7 +1875,7 @@ protected virtual void CopyPlayerDataToUI() bool allowOptionsChange = AllowPlayerOptionsChange(); var playerExtraOptions = GetPlayerExtraOptions(); - + // Human players for (int pId = 0; pId < Players.Count; pId++) { @@ -1987,7 +2009,7 @@ protected virtual void ChangeMap(GameModeMap gameModeMap) GameModeMap = gameModeMap; if (GameMode == null || Map == null) - { + { lblMapName.Text = "Map: Unknown"; lblMapAuthor.Text = "By Unknown Author"; lblGameMode.Text = "Game mode: Unknown"; @@ -2064,7 +2086,7 @@ protected virtual void ChangeMap(GameModeMap gameModeMap) // Check if AI players allowed - bool AIAllowed = !(Map.MultiplayerOnly || GameMode.MultiplayerOnly) || + bool AIAllowed = !(Map.MultiplayerOnly || GameMode.MultiplayerOnly) || !(Map.HumanPlayersOnly || GameMode.HumanPlayersOnly); foreach (var ddName in ddPlayerNames) { @@ -2081,7 +2103,7 @@ protected virtual void ChangeMap(GameModeMap gameModeMap) foreach (PlayerInfo pInfo in concatPlayerList) { - if (pInfo.StartingLocation > Map.MaxPlayers || + if (pInfo.StartingLocation > Map.MaxPlayers || (!Map.IsCoop && (Map.ForceRandomStartLocations || GameMode.ForceRandomStartLocations))) pInfo.StartingLocation = 0; if (!Map.IsCoop && (Map.ForceNoTeams || GameMode.ForceNoTeams)) @@ -2270,7 +2292,7 @@ protected int GetRank() if (lowestEnemyAILevel < highestAllyAILevel) { - // Check that the player's AI allies aren't stronger + // Check that the player's AI allies aren't stronger return RANK_NONE; } @@ -2304,7 +2326,7 @@ protected int GetRank() if (lowestEnemyAILevel < highestAllyAILevel) { - // Check that the player's AI allies aren't stronger + // Check that the player's AI allies aren't stronger return RANK_NONE; } @@ -2364,6 +2386,8 @@ protected string AddGameOptionPreset(string name) preset.AddDropDownValue(dropDown.Name, dropDown.SelectedIndex); } + preset.PlayerExtraOptions = GetPlayerExtraOptions(); + GameOptionPresets.Instance.AddPreset(preset); return null; } @@ -2394,6 +2418,9 @@ public bool LoadGameOptionPreset(string name) disableGameOptionUpdateBroadcast = false; OnGameOptionChanged(); + + PlayerExtraOptionsPanel.SetPlayerExtraOptions(preset.PlayerExtraOptions); + return true; } diff --git a/DXMainClient/DXGUI/Multiplayer/PlayerExtraOptionsPanel.cs b/DXMainClient/DXGUI/Multiplayer/PlayerExtraOptionsPanel.cs index 26e5bf0eb..60c17feda 100644 --- a/DXMainClient/DXGUI/Multiplayer/PlayerExtraOptionsPanel.cs +++ b/DXMainClient/DXGUI/Multiplayer/PlayerExtraOptionsPanel.cs @@ -1,8 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Windows.Forms; +using clientdx.DXGUI.Generic; using ClientGUI; using DTAClient.Domain.Multiplayer; +using DTAClient.DXGUI.Generic; +using DTAClient.DXGUI.Multiplayer.CnCNet; +using DTAClient.Extensions; +using DTAClient.Online.EventArguments; using Microsoft.Xna.Framework; using Rampastring.XNAUI; using Rampastring.XNAUI.XNAControls; @@ -16,20 +22,23 @@ public class PlayerExtraOptionsPanel : XNAPanel private const int defaultTeamStartMappingX = 200; private const int teamMappingPanelWidth = 50; private const int teamMappingPanelHeight = 22; - private const string customPresetName = "Custom"; private XNAClientCheckBox chkBoxForceRandomSides; private XNAClientCheckBox chkBoxForceRandomTeams; private XNAClientCheckBox chkBoxForceRandomColors; private XNAClientCheckBox chkBoxForceRandomStarts; private XNAClientCheckBox chkBoxUseTeamStartMappings; - private XNAClientDropDown ddTeamStartMappingPreset; + private XNATextBox tbTeamStartMappingPreset; + private TeamStartMappingPresetMenu menuTeamStratMappingPresets; + private XNAClientButton btnTeamStartMappingPresetMenu; private TeamStartMappingsPanel teamStartMappingsPanel; + private PresetsWindow teamMappingPresetWindow; private bool _isHost; - private bool ignoreMappingChanges; public EventHandler OptionsChanged; public EventHandler OnClose; + public EventHandler PresetSaved; + public EventHandler PresetDeleted; private Map _map; @@ -48,10 +57,8 @@ public PlayerExtraOptionsPanel(WindowManager windowManager) : base(windowManager private void Mapping_Changed(object sender, EventArgs e) { Options_Changed(sender, e); - if (ignoreMappingChanges) - return; - ddTeamStartMappingPreset.SelectedIndex = 0; + tbTeamStartMappingPreset.Text = TeamStartMappingPreset.CustomPresetName; } private void ChkBoxUseTeamStartMappings_Changed(object sender, EventArgs e) @@ -59,6 +66,7 @@ private void ChkBoxUseTeamStartMappings_Changed(object sender, EventArgs e) RefreshTeamStartMappingsPanel(); chkBoxForceRandomTeams.Checked = chkBoxForceRandomTeams.Checked || chkBoxUseTeamStartMappings.Checked; chkBoxForceRandomTeams.AllowChecking = !chkBoxUseTeamStartMappings.Checked; + btnTeamStartMappingPresetMenu.AllowClick = chkBoxUseTeamStartMappings.Checked; // chkBoxForceRandomStarts.Checked = chkBoxForceRandomStarts.Checked || chkBoxUseTeamStartMappings.Checked; // chkBoxForceRandomStarts.AllowChecking = !chkBoxUseTeamStartMappings.Checked; @@ -103,7 +111,7 @@ private Rectangle GetTeamMappingPanelRectangle(int index) private void ClearTeamStartMappingSelections() => teamStartMappingsPanel.GetTeamStartMappingPanels().ForEach(panel => panel.ClearSelections()); - private void RefreshTeamStartMappingPanels() + public void RefreshTeamStartMappingPanels() { ClearTeamStartMappingSelections(); var teamStartMappingPanels = teamStartMappingsPanel.GetTeamStartMappingPanels(); @@ -119,40 +127,49 @@ private void RefreshTeamStartMappingPanels() } } + private TeamStartMappingPreset FindFirstPreset() + => menuTeamStratMappingPresets.GetPresets() + .FirstOrDefault(preset => !preset.IsCustom); + private void RefreshTeamStartMappingPresets(List teamStartMappingPresets) { - ddTeamStartMappingPreset.Items.Clear(); - ddTeamStartMappingPreset.AddItem(new XNADropDownItem - { - Text = customPresetName, - Tag = new List() - }); - ddTeamStartMappingPreset.SelectedIndex = 0; + menuTeamStratMappingPresets.ReinitItems(); + tbTeamStartMappingPreset.Text = TeamStartMappingPreset.CustomPresetName; if (!(teamStartMappingPresets?.Any() ?? false)) return; - teamStartMappingPresets.ForEach(preset => ddTeamStartMappingPreset.AddItem(new XNADropDownItem - { - Text = preset.Name, - Tag = preset.TeamStartMappings - })); - ddTeamStartMappingPreset.SelectedIndex = 1; + var mapPresets = teamStartMappingPresets.Where(p => !p.IsUserDefined).ToList(); + var userDefinedPresets = teamStartMappingPresets.Except(mapPresets).ToList(); + + menuTeamStratMappingPresets.AddPresetList(userDefinedPresets, "User Presets:", TeamStartMappingPresetSelected); + menuTeamStratMappingPresets.AddPresetList(mapPresets, "Map Presets:", TeamStartMappingPresetSelected); + + // this will select either the first Map preset or UserDefined preset + tbTeamStartMappingPreset.Text = FindFirstPreset()?.Name ?? TeamStartMappingPreset.CustomPresetName; } - private void DdTeamMappingPreset_SelectedIndexChanged(object sender, EventArgs e) + private List GetTeamStartMappingPresets() + => menuTeamStratMappingPresets.GetPresets(); + + private void TeamStartMappingPresetSelected(TeamStartMappingPreset teamStartMappingPreset) { - var selectedItem = ddTeamStartMappingPreset.SelectedItem; - if (selectedItem?.Text == customPresetName) + if (teamStartMappingPreset == null) return; - var teamStartMappings = selectedItem?.Tag as List; + tbTeamStartMappingPreset.Text = teamStartMappingPreset.Name; + teamStartMappingsPanel.MappingChanged -= Mapping_Changed; + teamStartMappingsPanel.SetTeamStartMappings(teamStartMappingPreset.TeamStartMappings); + teamStartMappingsPanel.MappingChanged += Mapping_Changed; + } - ignoreMappingChanges = true; - teamStartMappingsPanel.SetTeamStartMappings(teamStartMappings); - ignoreMappingChanges = false; + private void BtnTeamStartMappingPresetMenu_LeftClick(object sender, EventArgs e) + { + var point = new Point(btnTeamStartMappingPresetMenu.X - menuTeamStratMappingPresets.Width + 1, btnTeamStartMappingPresetMenu.Y + btnTeamStartMappingPresetMenu.Height - 1); + menuTeamStratMappingPresets.Open(point, GetTeamStartMappingPreset()); } - private void RefreshPresetDropdown() => ddTeamStartMappingPreset.AllowDropDown = _isHost && chkBoxUseTeamStartMappings.Checked; + private void RefreshPresetDropdown() + => btnTeamStartMappingPresetMenu.AllowClick = _isHost && chkBoxUseTeamStartMappings.Checked; public override void Initialize() { @@ -224,27 +241,72 @@ public override void Initialize() lblPreset.ClientRectangle = new Rectangle(chkBoxUseTeamStartMappings.X, chkBoxUseTeamStartMappings.Bottom + 8, 0, 0); AddChild(lblPreset); - ddTeamStartMappingPreset = new XNAClientDropDown(WindowManager); - ddTeamStartMappingPreset.Name = nameof(ddTeamStartMappingPreset); - ddTeamStartMappingPreset.ClientRectangle = new Rectangle(lblPreset.X + 50, lblPreset.Y - 2, 160, 0); - ddTeamStartMappingPreset.SelectedIndexChanged += DdTeamMappingPreset_SelectedIndexChanged; - ddTeamStartMappingPreset.AllowDropDown = true; - AddChild(ddTeamStartMappingPreset); + tbTeamStartMappingPreset = new XNATextBox(WindowManager); + tbTeamStartMappingPreset.Name = nameof(tbTeamStartMappingPreset); + tbTeamStartMappingPreset.InputEnabled = false; + tbTeamStartMappingPreset.ClientRectangle = new Rectangle(lblPreset.X + 50, lblPreset.Y - 2, 143, 22); + AddChild(tbTeamStartMappingPreset); + + btnTeamStartMappingPresetMenu = new XNAClientButton(WindowManager); + btnTeamStartMappingPresetMenu.IdleTexture = AssetLoader.LoadTexture("menu-sm.png"); + btnTeamStartMappingPresetMenu.HoverTexture = AssetLoader.LoadTexture("menu-sm.png"); + btnTeamStartMappingPresetMenu.Name = nameof(btnTeamStartMappingPresetMenu); + btnTeamStartMappingPresetMenu.ClientRectangle = new Rectangle(tbTeamStartMappingPreset.Right - 1, tbTeamStartMappingPreset.Y, 20, 22); + btnTeamStartMappingPresetMenu.LeftClick += BtnTeamStartMappingPresetMenu_LeftClick; + btnTeamStartMappingPresetMenu.AllowClick = false; + AddChild(btnTeamStartMappingPresetMenu); + + menuTeamStratMappingPresets = new TeamStartMappingPresetMenu(WindowManager); + menuTeamStratMappingPresets.Name = nameof(menuTeamStratMappingPresets); + // menuTeamStratMappingPresets.ItemHeight = 20; + menuTeamStratMappingPresets.ClientRectangle = new Rectangle(0, 0, tbTeamStartMappingPreset.Width, 0); + AddChild(menuTeamStratMappingPresets); teamStartMappingsPanel = new TeamStartMappingsPanel(WindowManager); - teamStartMappingsPanel.ClientRectangle = new Rectangle(200, ddTeamStartMappingPreset.Bottom + 8, Width, Height - ddTeamStartMappingPreset.Bottom + 4); + teamStartMappingsPanel.ClientRectangle = new Rectangle(200, tbTeamStartMappingPreset.Bottom + 8, Width, Height - tbTeamStartMappingPreset.Bottom + 4); AddChild(teamStartMappingsPanel); AddLocationAssignments(); + AddTeamStartMappingPresetWindow(); base.Initialize(); RefreshTeamStartMappingsPanel(); } + private void AddTeamStartMappingPresetWindow() + { + teamMappingPresetWindow = new PresetsWindow(WindowManager); + teamMappingPresetWindow.PresetSaved += AddUserDefinedTeamStartMappingPreset; + teamMappingPresetWindow.PresetDeleted += (sender, presetArgs) => PresetDeleted?.Invoke(sender, presetArgs.PresetName); + AddChild(teamMappingPresetWindow); + } + + private void AddUserDefinedTeamStartMappingPreset(object sender, PresetWindowEventArgs presetArgs) + { + if (string.IsNullOrWhiteSpace(presetArgs.PresetName)) + return; + + if (presetArgs.IsNew && GetTeamStartMappingPresets().Any(p => p.Name == presetArgs.PresetName)) + { + XNAMessageBox.Show(WindowManager, "Warning", "A preset with this name already exists"); + return; + } + + PresetSaved?.Invoke(this, new UserDefinedTeamStartMappingPresetEventArgs + { + Preset = new TeamStartMappingPreset + { + Name = presetArgs.PresetName, + TeamStartMappings = GetTeamStartMappings(), + IsUserDefined = true + } + }); + } + private void BtnHelp_LeftClick(object sender, EventArgs args) { - XNAMessageBox.Show(WindowManager, "Auto Allying", + XNAMessageBox.Show(WindowManager, "Auto Allying", "Auto allying allows the host to assign starting locations to teams, not players.\n" + "When players are assigned to spawn locations, they will be auto assigned to teams based on these mappings.\n" + "This is best used with random teams and random starts. However, only random teams is required.\n" + @@ -264,9 +326,16 @@ public void UpdateForMap(Map map) RefreshTeamStartMappingPanels(); } - public List GetTeamStartMappings() - => chkBoxUseTeamStartMappings.Checked ? - teamStartMappingsPanel.GetTeamStartMappings() : new List(); + public List GetTeamStartMappings() + => chkBoxUseTeamStartMappings.Checked ? teamStartMappingsPanel.GetTeamStartMappings() : new List(); + + public TeamStartMappingPreset GetTeamStartMappingPreset() + { + if (!IsUseTeamStartMappings()) + return null; + + return GetTeamStartMappingPresets().First(p => p.Name == tbTeamStartMappingPreset.Text); + } public void EnableControls(bool enable) { @@ -279,7 +348,7 @@ public void EnableControls(bool enable) teamStartMappingsPanel.EnableControls(enable && chkBoxUseTeamStartMappings.Checked); } - public PlayerExtraOptions GetPlayerExtraOptions() + public PlayerExtraOptions GetPlayerExtraOptions() => new PlayerExtraOptions() { IsForceRandomSides = IsForcedRandomSides(), @@ -298,6 +367,16 @@ public void SetPlayerExtraOptions(PlayerExtraOptions playerExtraOptions) chkBoxForceRandomStarts.Checked = playerExtraOptions.IsForceRandomStarts; chkBoxUseTeamStartMappings.Checked = playerExtraOptions.IsUseTeamStartMappings; teamStartMappingsPanel.SetTeamStartMappings(playerExtraOptions.TeamStartMappings); + SetTeamStartMappingsPreset(playerExtraOptions.TeamStartMappings); + } + + private void SetTeamStartMappingsPreset(List teamStartMappings) + { + var preset = GetTeamStartMappingPresets() + .FirstOrDefault(p => p.TeamStartMappings.EqualsMappings(teamStartMappings)); + + if (preset != null) + tbTeamStartMappingPreset.Text = preset.Name; } public void SetIsHost(bool isHost) diff --git a/DXMainClient/DXMainClient.csproj b/DXMainClient/DXMainClient.csproj index 528cb0ece..2e894fc09 100644 --- a/DXMainClient/DXMainClient.csproj +++ b/DXMainClient/DXMainClient.csproj @@ -464,7 +464,9 @@ + + @@ -473,7 +475,7 @@ - + @@ -524,18 +526,20 @@ + - + + diff --git a/DXMainClient/Domain/Multiplayer/GameOptionPresets.cs b/DXMainClient/Domain/Multiplayer/GameOptionPresets.cs index fb3896244..882b927e4 100644 --- a/DXMainClient/Domain/Multiplayer/GameOptionPresets.cs +++ b/DXMainClient/Domain/Multiplayer/GameOptionPresets.cs @@ -37,6 +37,7 @@ public static string IsNameValid(string name) private Dictionary checkBoxValues = new Dictionary(); private Dictionary dropDownValues = new Dictionary(); + public PlayerExtraOptions PlayerExtraOptions { get; set; } private void AddValues(IniSection section, string keyName, Dictionary dictionary, Converter converter) { @@ -77,6 +78,21 @@ public void Read(IniSection section) AddValues(section, "CheckBoxValues", checkBoxValues, s => s == "1"); AddValues(section, "DropDownValues", dropDownValues, s => Conversions.IntFromString(s, 0)); + + ReadPlayerExtraOptions(section); + } + + private void ReadPlayerExtraOptions(IniSection section) + { + PlayerExtraOptions = new PlayerExtraOptions() + { + IsForceRandomSides = section.GetBooleanValue("IsForceRandomSides", false), + IsForceRandomColors = section.GetBooleanValue("IsForceRandomColors", false), + IsForceRandomTeams = section.GetBooleanValue("IsForceRandomTeams", false), + IsForceRandomStarts = section.GetBooleanValue("IsForceRandomStarts", false), + IsUseTeamStartMappings = section.GetBooleanValue("IsUseTeamStartMappings", false), + TeamStartMappings = TeamStartMapping.FromListString(section.GetStringValue("TeamStartMapping", string.Empty)) + }; } public void Write(IniSection section) @@ -85,6 +101,21 @@ public void Write(IniSection section) checkBoxValues.Select(s => $"{ s.Key }:{ (s.Value ? "1" : "0") }"))); section.SetStringValue("DropDownValues", string.Join(",", dropDownValues.Select(s => $"{ s.Key }:{ s.Value.ToString() }"))); + + WritePlayerExtraOptions(section); + } + + private void WritePlayerExtraOptions(IniSection section) + { + if (PlayerExtraOptions == null) + return; + + section.SetBooleanValue("IsForceRandomSides", PlayerExtraOptions.IsForceRandomSides); + section.SetBooleanValue("IsForceRandomColors", PlayerExtraOptions.IsForceRandomColors); + section.SetBooleanValue("IsForceRandomTeams", PlayerExtraOptions.IsForceRandomTeams); + section.SetBooleanValue("IsForceRandomStarts", PlayerExtraOptions.IsForceRandomStarts); + section.SetBooleanValue("IsUseTeamStartMappings", PlayerExtraOptions.IsUseTeamStartMappings); + section.SetStringValue("TeamStartMapping", TeamStartMapping.ToListString(PlayerExtraOptions.TeamStartMappings)); } } @@ -119,7 +150,7 @@ public GameOptionPreset GetPreset(string name) if (presets.TryGetValue(name, out GameOptionPreset value)) { - + return value; } diff --git a/DXMainClient/Domain/Multiplayer/MapLoader.cs b/DXMainClient/Domain/Multiplayer/MapLoader.cs index 4ca76a55d..c8453439a 100644 --- a/DXMainClient/Domain/Multiplayer/MapLoader.cs +++ b/DXMainClient/Domain/Multiplayer/MapLoader.cs @@ -16,10 +16,12 @@ public class MapLoader public const string MAP_FILE_EXTENSION = ".map"; private const string CUSTOM_MAPS_DIRECTORY = "Maps/Custom"; private static readonly string CUSTOM_MAPS_CACHE = ProgramConstants.ClientUserFilesPath + "custom_map_cache"; + private static readonly string USER_DEFINED_ALLY_MAP_PRESETS = ProgramConstants.ClientUserFilesPath + "user_ally_map_presets"; private const string MultiMapsSection = "MultiMaps"; private const string GameModesSection = "GameModes"; private const string GameModeAliasesSection = "GameModeAliases"; private const int CurrentCustomMapCacheVersion = 1; + private Dictionary> UserDefinedAllyMapPresets; /// /// List of game modes. @@ -35,7 +37,7 @@ public class MapLoader /// /// A list of game mode aliases. - /// Every game mode entry that exists in this dictionary will get + /// Every game mode entry that exists in this dictionary will get /// replaced by the game mode entries of the value string array /// when map is added to game mode map lists. /// @@ -64,6 +66,7 @@ public void LoadMaps() IniFile mpMapsIni = new IniFile(ProgramConstants.GamePath + ClientConfiguration.Instance.MPMapsIniPath); + LoadUserDefinedPresets(); LoadGameModes(mpMapsIni); LoadGameModeAliases(mpMapsIni); LoadMultiMaps(mpMapsIni); @@ -75,6 +78,40 @@ public void LoadMaps() MapLoadingComplete?.Invoke(this, EventArgs.Empty); } + private void LoadUserDefinedPresets() + { + try + { + UserDefinedAllyMapPresets = JsonConvert.DeserializeObject>>(File.ReadAllText(USER_DEFINED_ALLY_MAP_PRESETS)) ?? + new Dictionary>(); + } + catch (Exception e) + { + Logger.Log($"Error reading user defined map presets: {e.Message}"); + UserDefinedAllyMapPresets = new Dictionary>(); + } + + foreach (string customPresetsKey in UserDefinedAllyMapPresets.Keys) + UserDefinedAllyMapPresets[customPresetsKey].ForEach(preset => preset.IsUserDefined = true); + } + + private void SaveCustomPresets() + { + try + { + File.WriteAllText(USER_DEFINED_ALLY_MAP_PRESETS, JsonConvert.SerializeObject(UserDefinedAllyMapPresets)); + } + catch (Exception e) + { + Logger.Log($"Error writing custom map presets file: {e.Message}"); + } + } + + private IEnumerable GetUserDefinedPresetsForMap(string mapName) + { + return UserDefinedAllyMapPresets.ContainsKey(mapName) ? UserDefinedAllyMapPresets[mapName] : new List(); + } + private void LoadMultiMaps(IniFile mpMapsIni) { List keys = mpMapsIni.GetSectionKeys(MultiMapsSection); @@ -101,6 +138,7 @@ private void LoadMultiMaps(IniFile mpMapsIni) if (!map.SetInfoFromMpMapsINI(mpMapsIni)) continue; + map.TeamStartMappingPresets.AddRange(GetUserDefinedPresetsForMap(map.Name)); maps.Add(map); } @@ -137,7 +175,7 @@ private void LoadGameModeAliases(IniFile mpMapsIni) foreach (string key in gmAliases) { GameModeAliases.Add(key, mpMapsIni.GetStringValue(GameModeAliasesSection, key, string.Empty).Split( - new char[] {','}, StringSplitOptions.RemoveEmptyEntries)); + new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)); } } } @@ -167,8 +205,11 @@ private void LoadCustomMaps() Map map = new Map(baseFilePath, mapFile); map.CalculateSHA(); localMapSHAs.Add(map.SHA1); - if (!customMapCache.ContainsKey(map.SHA1) && map.SetInfoFromCustomMap()) - customMapCache.TryAdd(map.SHA1, map); + if (customMapCache.ContainsKey(map.SHA1) || !map.SetInfoFromCustomMap()) + return; + + customMapCache.TryAdd(map.SHA1, map); + map.TeamStartMappingPresets.AddRange(GetUserDefinedPresetsForMap(map.Name)); })); } @@ -323,5 +364,37 @@ private void AddMapToGameModes(Map map, bool enableLogging) } } } + + public void AddUserDefinedTeamMappingPreset(Map map, TeamStartMappingPreset preset) + { + preset.IsUserDefined = true; + if (!UserDefinedAllyMapPresets.ContainsKey(map.Name)) + UserDefinedAllyMapPresets.Add(map.Name, new List()); + + int existingPresetIndex = UserDefinedAllyMapPresets[map.Name].FindIndex(p => p.Name == preset.Name); + if (existingPresetIndex != -1) + UserDefinedAllyMapPresets[map.Name][existingPresetIndex] = preset; + else + UserDefinedAllyMapPresets[map.Name].Add(preset); + + if(map.TeamStartMappingPresets.All(p => p.Name != preset.Name)) + map.TeamStartMappingPresets.Add(preset); + + SaveCustomPresets(); + } + + public void DeleteUserDefinedTeamMappingPreset(Map map, string presetName) + { + if (!UserDefinedAllyMapPresets.ContainsKey(map.Name)) + return; + + var teamMappingPreset = UserDefinedAllyMapPresets[map.Name].FirstOrDefault(p => p.Name == presetName); + if (teamMappingPreset == null) + return; + + UserDefinedAllyMapPresets[map.Name].Remove(teamMappingPreset); + map.TeamStartMappingPresets.Remove(teamMappingPreset); + SaveCustomPresets(); + } } } diff --git a/DXMainClient/Domain/Multiplayer/PlayerExtraOptions.cs b/DXMainClient/Domain/Multiplayer/PlayerExtraOptions.cs index a0378d363..6efff7a42 100644 --- a/DXMainClient/Domain/Multiplayer/PlayerExtraOptions.cs +++ b/DXMainClient/Domain/Multiplayer/PlayerExtraOptions.cs @@ -28,7 +28,7 @@ public string GetTeamMappingsError() { if (!IsUseTeamStartMappings) return null; - + var distinctStartLocations = TeamStartMappings.Select(m => m.Start).Distinct(); if (distinctStartLocations.Count() != TeamStartMappings.Count) return MULTIPLE_MAPPINGS_ASSIGNED_TO_SAME_START; // multiple mappings are using the same spawn location diff --git a/DXMainClient/Domain/Multiplayer/TeamStartMappingPreset.cs b/DXMainClient/Domain/Multiplayer/TeamStartMappingPreset.cs index 223f22c4f..ece449962 100644 --- a/DXMainClient/Domain/Multiplayer/TeamStartMappingPreset.cs +++ b/DXMainClient/Domain/Multiplayer/TeamStartMappingPreset.cs @@ -5,10 +5,29 @@ namespace DTAClient.Domain.Multiplayer { public class TeamStartMappingPreset { + public const string CustomPresetName = "Custom"; + [JsonProperty("n")] public string Name { get; set; } - + [JsonProperty("m")] public List TeamStartMappings { get; set; } + + [JsonIgnore] + public bool IsUserDefined { get; set; } + + [JsonIgnore] + public bool IsCustom => Name == CustomPresetName; + + [JsonIgnore] + public bool CanSave => IsCustom || IsUserDefined; + + [JsonIgnore] + public bool CanDelete => IsUserDefined; + + public TeamStartMappingPreset() + { + TeamStartMappings = new List(); + } } } diff --git a/DXMainClient/Extensions/TeamStartMappingListExtensions.cs b/DXMainClient/Extensions/TeamStartMappingListExtensions.cs new file mode 100644 index 000000000..2d7181675 --- /dev/null +++ b/DXMainClient/Extensions/TeamStartMappingListExtensions.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; +using DTAClient.Domain.Multiplayer; + +namespace DTAClient.Extensions +{ + public static class TeamStartMappingListExtensions + { + public static bool EqualsMappings(this List teamStartMappings, List otherTeamStartMappings) + { + if (teamStartMappings.Count != otherTeamStartMappings.Count) + return false; + + // compare team at each mapping at each index + return !teamStartMappings.Where((t, i) => t.Team != otherTeamStartMappings[i].Team).Any(); + } + } +} diff --git a/DXMainClient/Online/EventArguments/GameOptionPresetEventArgs.cs b/DXMainClient/Online/EventArguments/GameOptionPresetEventArgs.cs deleted file mode 100644 index e326024ac..000000000 --- a/DXMainClient/Online/EventArguments/GameOptionPresetEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace DTAClient.Online.EventArguments -{ - public class GameOptionPresetEventArgs : EventArgs - { - public string PresetName { get; } - - public GameOptionPresetEventArgs(string presetName) - { - PresetName = presetName; - } - } -} diff --git a/DXMainClient/Online/EventArguments/PresetWindowEventArgs.cs b/DXMainClient/Online/EventArguments/PresetWindowEventArgs.cs new file mode 100644 index 000000000..3454cd3c2 --- /dev/null +++ b/DXMainClient/Online/EventArguments/PresetWindowEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace DTAClient.Online.EventArguments +{ + public class PresetWindowEventArgs : EventArgs + { + public string PresetName { get; } + + public bool IsNew { get; set; } + + public PresetWindowEventArgs(string presetName, bool isNew = false) + { + PresetName = presetName; + IsNew = isNew; + } + } +} diff --git a/DXMainClient/Online/EventArguments/UserDefinedTeamStartMappingPresetEventArgs.cs b/DXMainClient/Online/EventArguments/UserDefinedTeamStartMappingPresetEventArgs.cs new file mode 100644 index 000000000..ad45489e2 --- /dev/null +++ b/DXMainClient/Online/EventArguments/UserDefinedTeamStartMappingPresetEventArgs.cs @@ -0,0 +1,9 @@ +using DTAClient.Domain.Multiplayer; + +namespace DTAClient.Online.EventArguments +{ + public class UserDefinedTeamStartMappingPresetEventArgs + { + public TeamStartMappingPreset Preset { get; set; } + } +}