diff --git a/DXMainClient/DXGUI/Multiplayer/CnCNet/TeamStartMappingPresetsWindow.cs b/DXMainClient/DXGUI/Multiplayer/CnCNet/TeamStartMappingPresetsWindow.cs new file mode 100644 index 000000000..99217b1a2 --- /dev/null +++ b/DXMainClient/DXGUI/Multiplayer/CnCNet/TeamStartMappingPresetsWindow.cs @@ -0,0 +1,304 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ClientGUI; +using DTAClient.Domain.Multiplayer; +using DTAClient.Online.EventArguments; +using Localization; +using Microsoft.Xna.Framework; +using Rampastring.XNAUI; +using Rampastring.XNAUI.XNAControls; + +namespace DTAClient.DXGUI.Multiplayer.CnCNet +{ + public class TeamStartMappingPresetsWindow : XNAWindow + { + private readonly XNALabel lblHeader; + + private readonly XNADropDownItem ddiCreatePresetItem; + + private readonly XNADropDownItem ddiSelectPresetItem; + + private readonly XNAClientButton btnSave; + + private readonly XNAClientButton btnDelete; + + private readonly XNAClientDropDown ddPresetSelect; + + private readonly XNALabel lblNewPresetName; + + private readonly XNATextBox tbNewPresetName; + + private readonly XNAClientCheckBox chkBoxSetDefault; + + public EventHandler PresetSaved; + + public EventHandler PresetDeleted; + + private Map _map; + + public TeamStartMappingPresetsWindow(WindowManager windowManager) : base(windowManager) + { + ClientRectangle = new Rectangle(0, 0, 325, 185); + + const int margin = 10; + + lblHeader = new XNALabel(WindowManager); + lblHeader.Name = nameof(lblHeader); + lblHeader.FontIndex = 1; + lblHeader.Text = "Edit Preset".L10N("UI:AutoAllyPresetWindow:EditPreset"); + lblHeader.ClientRectangle = new Rectangle( + margin, margin, + 150, 22 + ); + + var lblPresetName = new XNALabel(WindowManager); + lblPresetName.Name = nameof(lblPresetName); + lblPresetName.Text = "Preset".L10N("UI:AutoAllyPresetWindow:Preset"); + lblPresetName.ClientRectangle = new Rectangle( + margin, lblHeader.Bottom + margin, + 150, 18 + ); + + ddiCreatePresetItem = new XNADropDownItem(); + ddiCreatePresetItem.Text = "[Create New]".L10N("UI:AutoAllyPresetWindow:CreateNewPreset"); + + ddiSelectPresetItem = new XNADropDownItem(); + ddiSelectPresetItem.Text = "[Select Preset]".L10N("UI:AutoAllyPresetWindow:SelectPreset"); + ddiSelectPresetItem.Selectable = false; + + ddPresetSelect = new XNAClientDropDown(WindowManager); + ddPresetSelect.Name = nameof(ddPresetSelect); + ddPresetSelect.ClientRectangle = new Rectangle( + 10, lblPresetName.Bottom + 2, + 150, 22 + ); + ddPresetSelect.SelectedIndexChanged += DropDownPresetSelect_SelectedIndexChanged; + + chkBoxSetDefault = new XNAClientCheckBox(WindowManager); + chkBoxSetDefault.Name = nameof(chkBoxSetDefault); + chkBoxSetDefault.ClientRectangle = new Rectangle(ddPresetSelect.Right + 12, ddPresetSelect.Y + 2, 100, 22); + chkBoxSetDefault.Text = "Default for Map".L10N("UI:AutoAllyPresetWindow:SetDefaultCheckBox"); + + lblNewPresetName = new XNALabel(WindowManager); + lblNewPresetName.Name = nameof(lblNewPresetName); + lblNewPresetName.Text = "New Preset Name".L10N("UI:AutoAllyPresetWindow:NewPresetName"); + lblNewPresetName.ClientRectangle = new Rectangle( + margin, ddPresetSelect.Bottom + margin, + 150, 18 + ); + + tbNewPresetName = new XNATextBox(WindowManager); + tbNewPresetName.Name = nameof(tbNewPresetName); + tbNewPresetName.ClientRectangle = new Rectangle( + 10, lblNewPresetName.Bottom + 2, + 150, 22 + ); + tbNewPresetName.TextChanged += (sender, args) => RefreshUI(); + + btnSave = new XNAClientButton(WindowManager); + btnSave.Name = nameof(btnSave); + btnSave.LeftClick += BtnSave_LeftClick; + btnSave.Text = "Save".L10N("UI:AutoAllyPresetWindow:ButtonSave"); + btnSave.ClientRectangle = new Rectangle( + margin, + Height - UIDesignConstants.BUTTON_HEIGHT - margin, + UIDesignConstants.BUTTON_WIDTH_92, + UIDesignConstants.BUTTON_HEIGHT + ); + + btnDelete = new XNAClientButton(WindowManager); + btnDelete.Name = nameof(btnDelete); + btnDelete.Text = "Delete".L10N("UI:AutoAllyPresetWindow:ButtonDelete"); + btnDelete.LeftClick += BtnDelete_LeftClick; + btnDelete.ClientRectangle = new Rectangle( + btnSave.Right + margin, + btnSave.Y, + UIDesignConstants.BUTTON_WIDTH_92, + UIDesignConstants.BUTTON_HEIGHT + ); + + var btnCancel = new XNAClientButton(WindowManager); + btnCancel.Name = nameof(btnCancel); + btnCancel.Text = "Cancel".L10N("UI:AutoAllyPresetWindow:ButtonCancel"); + btnCancel.ClientRectangle = new Rectangle( + btnDelete.Right + margin, + btnSave.Y, + UIDesignConstants.BUTTON_WIDTH_92, + UIDesignConstants.BUTTON_HEIGHT + ); + btnCancel.LeftClick += (sender, args) => Disable(); + + AddChild(lblHeader); + AddChild(lblPresetName); + AddChild(ddPresetSelect); + AddChild(chkBoxSetDefault); + AddChild(lblNewPresetName); + AddChild(tbNewPresetName); + AddChild(btnSave); + AddChild(btnDelete); + AddChild(btnCancel); + + Disable(); + } + + public override void Initialize() + { + base.Initialize(); + + PanelBackgroundDrawMode = PanelBackgroundImageDrawMode.STRETCHED; + BackgroundTexture = AssetLoader.CreateTexture(new Color(0, 0, 0, 255), 1, 1); + } + + /// + /// Show the window. + /// + public void Show(Map map, TeamStartMappingPreset preset) + { + _map = map; + + LoadPresets(preset); + tbNewPresetName.Text = string.Empty; + + RefreshUI(); + RefreshDefaultCheckBox(); + CenterOnParent(); + Enable(); + } + + /// + /// Refresh the state of the load/save button + /// + private void RefreshUI() + { + btnSave.Enabled = !IsCreatePresetSelected || !IsNewPresetNameFieldEmpty; + btnDelete.Enabled = !IsCreatePresetSelected && !IsSelectPresetSelected; + + RefreshNewPresetNameTextBox(); + } + + private void RefreshNewPresetNameTextBox() + { + lblNewPresetName.Disable(); + tbNewPresetName.Disable(); + + if (!IsCreatePresetSelected) + return; + + lblNewPresetName.Enable(); + tbNewPresetName.Enable(); + } + + private void RefreshDefaultCheckBox() + { + chkBoxSetDefault.Checked = SelectedTeamStartMappingPreset?.IsDefaultForMap ?? false; + } + + private bool IsCreatePresetSelected => ddPresetSelect.SelectedItem == ddiCreatePresetItem; + private bool IsSelectPresetSelected => ddPresetSelect.SelectedItem == ddiSelectPresetItem; + private bool IsNewPresetNameFieldEmpty => string.IsNullOrWhiteSpace(tbNewPresetName.Text); + + private TeamStartMappingPreset SelectedTeamStartMappingPreset => ddPresetSelect.SelectedItem?.Tag as TeamStartMappingPreset; + + private List AllPresets + => ddPresetSelect.Items + .Select(i => i.Tag as TeamStartMappingPreset) + .Where(preset => preset != null) + .ToList(); + + private XNADropDownItem CreateItem(TeamStartMappingPreset preset) + => new XNADropDownItem + { + Text = preset.DisplayName, + Tag = preset + }; + + + /// + /// Populate the preset drop down from saved presets + /// + private void LoadPresets(TeamStartMappingPreset initialPreset) + { + ddPresetSelect.Items.Clear(); + ddPresetSelect.Items.Add(ddiCreatePresetItem); + + ddPresetSelect.Items.AddRange(_map.TeamStartMappingPresets + .Select(CreateItem) + ); + + int initialIndex = ddPresetSelect.Items.FindIndex(i => i.Tag == initialPreset); + ddPresetSelect.SelectedIndex = initialIndex == -1 ? 0 : initialIndex; + } + + private void BtnSave_LeftClick(object sender, EventArgs e) + { + var selectedItem = ddPresetSelect.Items[ddPresetSelect.SelectedIndex]; + var preset = IsCreatePresetSelected + ? new TeamStartMappingPreset + { + Name = tbNewPresetName.Text, + IsUserDefined = true + } + : selectedItem.Tag as TeamStartMappingPreset; + + if (preset == null) + return; + + UpdateDefault(preset); + + PresetSaved?.Invoke(this, new TeamStartMappingPresetEventArgs(_map, preset)); + + Disable(); + } + + private void UpdateDefault(TeamStartMappingPreset preset) + { + preset.IsDefaultForMap = chkBoxSetDefault.Checked; + if (!preset.IsDefaultForMap) + return; + + // clear the default flag for all others + foreach (TeamStartMappingPreset teamStartMappingPreset in AllPresets.Where(p => p != preset)) + teamStartMappingPreset.IsDefaultForMap = false; + } + + private void BtnDelete_LeftClick(object sender, EventArgs e) + { + if (IsCreatePresetSelected) + return; + + var selectedItem = ddPresetSelect.Items[ddPresetSelect.SelectedIndex]; + var messageBox = XNAMessageBox.ShowYesNoDialog(WindowManager, + "Confirm Preset Delete".L10N("UI:AutoAllyPresetWindow:ConfirmPresetDeleteTitle"), + "Are you sure you want to delete this preset?".L10N("UI:AutoAllyPresetWindow:ConfirmPresetDeleteText") + "\n\n" + selectedItem.Text); + messageBox.YesClickedAction = box => + { + PresetDeleted?.Invoke(this, new TeamStartMappingPresetEventArgs(_map, selectedItem.Tag as TeamStartMappingPreset)); + ddPresetSelect.Items.Remove(selectedItem); + ddPresetSelect.SelectedIndex = 0; + }; + } + + /// + /// Callback when the Preset drop down selection has changed + /// + private void DropDownPresetSelect_SelectedIndexChanged(object sender, EventArgs eventArgs) + { + if (IsCreatePresetSelected) + { + // show the field to specify a new name when "create" option is selected in drop down + tbNewPresetName.Enable(); + lblNewPresetName.Enable(); + } + else + { + // hide the field to specify a new name when an existing preset is selected + tbNewPresetName.Disable(); + lblNewPresetName.Disable(); + } + + RefreshUI(); + RefreshDefaultCheckBox(); + } + } +} diff --git a/DXMainClient/DXGUI/Multiplayer/PlayerExtraOptionsPanel.cs b/DXMainClient/DXGUI/Multiplayer/PlayerExtraOptionsPanel.cs index 8323ba28c..c98216d06 100644 --- a/DXMainClient/DXGUI/Multiplayer/PlayerExtraOptionsPanel.cs +++ b/DXMainClient/DXGUI/Multiplayer/PlayerExtraOptionsPanel.cs @@ -3,6 +3,8 @@ using System.Linq; using ClientGUI; using DTAClient.Domain.Multiplayer; +using DTAClient.DXGUI.Multiplayer.CnCNet; +using DTAClient.Online.EventArguments; using Localization; using Microsoft.Xna.Framework; using Rampastring.XNAUI; @@ -17,7 +19,6 @@ public class PlayerExtraOptionsPanel : XNAPanel private const int defaultTeamStartMappingX = UIDesignConstants.EMPTY_SPACE_SIDES; private const int teamMappingPanelWidth = 50; private const int teamMappingPanelHeight = 22; - private readonly string customPresetName = "Custom".L10N("UI:Main:CustomPresetName"); private XNAClientCheckBox chkBoxForceRandomSides; private XNAClientCheckBox chkBoxForceRandomTeams; @@ -25,7 +26,9 @@ public class PlayerExtraOptionsPanel : XNAPanel private XNAClientCheckBox chkBoxForceRandomStarts; private XNAClientCheckBox chkBoxUseTeamStartMappings; private XNAClientDropDown ddTeamStartMappingPreset; + private XNAClientButton btnEditCustomPreset; private TeamStartMappingsPanel teamStartMappingsPanel; + private TeamStartMappingPresetsWindow teamStartMappingPresetsWindow; private bool _isHost; private bool ignoreMappingChanges; @@ -116,43 +119,80 @@ private void RefreshTeamStartMappingPanels() continue; teamStartMappingPanel.EnableControls(_isHost && chkBoxUseTeamStartMappings.Checked && i < _map?.MaxPlayers); - RefreshTeamStartMappingPresets(_map?.TeamStartMappingPresets); + RefreshTeamStartMappingPresets(_map); } + + RefreshSaveTeamStartPresetEditBtn(); + } + + private void RefreshSaveTeamStartPresetEditBtn() + { + btnEditCustomPreset.Enabled = CanEditTeamStartMappingPreset(); + string editBtnTexture = btnEditCustomPreset.Enabled ? "settingsBtnActive.png" : "settingsBtnInactive.png"; + btnEditCustomPreset.IdleTexture = AssetLoader.LoadTexture(editBtnTexture); + btnEditCustomPreset.HoverTexture = AssetLoader.LoadTexture(editBtnTexture); } - private void RefreshTeamStartMappingPresets(List teamStartMappingPresets) + private void RefreshTeamStartMappingPresets(Map map) { ddTeamStartMappingPreset.Items.Clear(); ddTeamStartMappingPreset.AddItem(new XNADropDownItem { - Text = customPresetName, - Tag = new List() + Text = "Custom".L10N("UI:Main:CustomPresetName"), + Tag = new TeamStartMappingPreset() + { + IsCustom = true + } }); ddTeamStartMappingPreset.SelectedIndex = 0; - if (!(teamStartMappingPresets?.Any() ?? false)) return; + if (map == null) + return; + + var mapPresets = map.TeamStartMappingPresets ?? new List(); - teamStartMappingPresets.ForEach(preset => ddTeamStartMappingPreset.AddItem(new XNADropDownItem + mapPresets.ForEach(preset => ddTeamStartMappingPreset.AddItem(new XNADropDownItem { - Text = preset.Name, - Tag = preset.TeamStartMappings + Text = preset.DisplayName, + Tag = preset })); - ddTeamStartMappingPreset.SelectedIndex = 1; + + if (!mapPresets.Any()) + { + ddTeamStartMappingPreset.SelectedIndex = 0; + return; + } + + var defaultPresetItem = ddTeamStartMappingPreset.Items + .FirstOrDefault(i => (i.Tag as TeamStartMappingPreset)?.IsDefaultForMap ?? false); + + ddTeamStartMappingPreset.SelectedIndex = defaultPresetItem == null ? 0 : ddTeamStartMappingPreset.Items.IndexOf(defaultPresetItem); } private void DdTeamMappingPreset_SelectedIndexChanged(object sender, EventArgs e) { - var selectedItem = ddTeamStartMappingPreset.SelectedItem; - if (selectedItem?.Text == customPresetName) + var selectedPreset = GetSelectedTeamStartMappingPreset(); + RefreshSaveTeamStartPresetEditBtn(); + if (selectedPreset?.IsCustom ?? true) return; - var teamStartMappings = selectedItem?.Tag as List; - ignoreMappingChanges = true; - teamStartMappingsPanel.SetTeamStartMappings(teamStartMappings); + teamStartMappingsPanel.SetTeamStartMappings(selectedPreset.TeamStartMappings); ignoreMappingChanges = false; } + private TeamStartMappingPreset GetSelectedTeamStartMappingPreset() + => ddTeamStartMappingPreset.SelectedItem?.Tag as TeamStartMappingPreset; + + private bool CanEditTeamStartMappingPreset() + { + if (_map == null || !_isHost || !chkBoxUseTeamStartMappings.Checked) + return false; + + var preset = GetSelectedTeamStartMappingPreset(); + return preset != null; + } + private void RefreshPresetDropdown() => ddTeamStartMappingPreset.AllowDropDown = _isHost && chkBoxUseTeamStartMappings.Checked; public override void Initialize() @@ -227,15 +267,30 @@ public override void Initialize() ddTeamStartMappingPreset = new XNAClientDropDown(WindowManager); ddTeamStartMappingPreset.Name = nameof(ddTeamStartMappingPreset); - ddTeamStartMappingPreset.ClientRectangle = new Rectangle(lblPreset.X + 50, lblPreset.Y - 2, 160, 0); + ddTeamStartMappingPreset.ClientRectangle = new Rectangle(lblPreset.X + 40, lblPreset.Y - 2, 146, 0); ddTeamStartMappingPreset.SelectedIndexChanged += DdTeamMappingPreset_SelectedIndexChanged; ddTeamStartMappingPreset.AllowDropDown = true; AddChild(ddTeamStartMappingPreset); + btnEditCustomPreset = new XNAClientButton(WindowManager); + btnEditCustomPreset.Name = nameof(btnEditCustomPreset); + btnEditCustomPreset.ClientRectangle = new Rectangle(ddTeamStartMappingPreset.Right + 2, ddTeamStartMappingPreset.Y, 22, 22); + btnEditCustomPreset.SetToolTipText("Edit".L10N("UI:Main:BtnEditAutoAllyPresetTooltip")); + btnEditCustomPreset.IdleTexture = AssetLoader.LoadTexture("editActive.png"); + btnEditCustomPreset.HoverTexture = AssetLoader.LoadTexture("editActive.png"); + btnEditCustomPreset.LeftClick += EditPresetButton_LeftClick; + AddChild(btnEditCustomPreset); + teamStartMappingsPanel = new TeamStartMappingsPanel(WindowManager); teamStartMappingsPanel.ClientRectangle = new Rectangle(lblPreset.X, ddTeamStartMappingPreset.Bottom + 8, Width, Height - ddTeamStartMappingPreset.Bottom + 4); AddChild(teamStartMappingsPanel); + teamStartMappingPresetsWindow = new TeamStartMappingPresetsWindow(WindowManager); + teamStartMappingPresetsWindow.Name = nameof(teamStartMappingPresetsWindow); + teamStartMappingPresetsWindow.PresetSaved += (sender, s) => HandleAutoAllyPresetSaveCommand(s); + teamStartMappingPresetsWindow.PresetDeleted += (sender, s) => HandleAutoAllyPresetDeleteCommand(s); + AddChild(teamStartMappingPresetsWindow); + AddLocationAssignments(); base.Initialize(); @@ -243,15 +298,65 @@ public override void Initialize() RefreshTeamStartMappingsPanel(); } + private void HandleAutoAllyPresetSaveCommand(TeamStartMappingPresetEventArgs teamStartMappingPresetEventArgs) + { + if (teamStartMappingPresetEventArgs.Preset == null) + return; + + var teamStartMappings = GetTeamStartMappings(); + if (!teamStartMappings.Any()) + { + XNAMessageBox.Show(WindowManager, "Cannot Save Presets", "Cannot save auto ally presets without any locations assigned."); + return; + } + + var teamStartMappingPreset = teamStartMappingPresetEventArgs.Preset; + teamStartMappingPreset.TeamStartMappings = teamStartMappings; + + TeamStartMappingUserPresets.Instance.AddOrUpdate(_map, teamStartMappingPreset); + + RefreshTeamStartMappingPresets(_map); + ddTeamStartMappingPreset.SelectedIndex = ddTeamStartMappingPreset.Items.FindIndex(i => i.Tag == teamStartMappingPreset); + } + + private void HandleAutoAllyPresetDeleteCommand(TeamStartMappingPresetEventArgs teamStartMappingPresetEventArgs) + { + if (teamStartMappingPresetEventArgs.Preset == null) + return; + + TeamStartMappingUserPresets.Instance.DeletePreset(_map, teamStartMappingPresetEventArgs.Preset); + + if (GetSelectedTeamStartMappingPreset() != teamStartMappingPresetEventArgs.Preset) + ddTeamStartMappingPreset.SelectedIndex = 0; + + RefreshTeamStartMappingsPanel(); + } + + private void EditPresetButton_LeftClick(object sender, EventArgs args) + { + if (_map == null) + return; + + teamStartMappingPresetsWindow.Show(_map, GetSelectedTeamStartMappingPreset()); + } + private void BtnHelp_LeftClick(object sender, EventArgs args) { XNAMessageBox.Show(WindowManager, "Auto Allying".L10N("UI:Main:AutoAllyingTitle"), ("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" + - "Manually specified starts will take precedence.\n\n").L10N("UI:Main:AutoAllyingText1") + - $"{TeamStartMapping.NO_TEAM} : " + "Block this location from being assigned to a player.".L10N("UI:Main:AutoAllyingTextNoTeam") + "\n" + - $"{TeamStartMapping.RANDOM_TEAM} : "+"Allow a player here, but don't assign a team.".L10N("UI:Main:AutoAllyingTextRandomTeam") + "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" + + "Manually specified starts will take precedence.\n\n").L10N("UI:Main:AutoAllyingText1") + + $"{TeamStartMapping.NO_TEAM} : " + + "Block this location from being assigned to a player.".L10N("UI:Main:AutoAllyingTextNoTeam") + "\n" + + $"{TeamStartMapping.RANDOM_TEAM} : " + + "Allow a player here, but don't assign a team.".L10N("UI:Main:AutoAllyingTextRandomTeam") + "\n\n" + + "Custom Auto Ally Presets:".L10N("UI:Main:AutoAllyingTextCustomPresetLabel") + "\n\n" + + ("The settings button can be used to create, edit, or delete custom auto ally presets for maps that do not have them predefined.\n" + + "Presets can be marked as the 'default' for each map. When the map is changed, this preset will be auto selected when\n" + + "auto ally is enabled.\n\n" + + "Custom auto ally presets are map specific.\n" + + $"{TeamStartMappingPreset.UserDefinedPrefx} : User Preset").L10N("UI:Main:AutoAllyingTextCustomPresetText") ); } @@ -262,12 +367,12 @@ public void UpdateForMap(Map map) _map = map; + teamStartMappingPresetsWindow.Disable(); RefreshTeamStartMappingPanels(); } public List GetTeamStartMappings() - => chkBoxUseTeamStartMappings.Checked ? - teamStartMappingsPanel.GetTeamStartMappings() : new List(); + => chkBoxUseTeamStartMappings.Checked ? teamStartMappingsPanel.GetTeamStartMappings() : new List(); public void EnableControls(bool enable) { diff --git a/DXMainClient/DXMainClient.csproj b/DXMainClient/DXMainClient.csproj index c7ea5236f..20ca52a26 100644 --- a/DXMainClient/DXMainClient.csproj +++ b/DXMainClient/DXMainClient.csproj @@ -451,6 +451,7 @@ + @@ -480,6 +481,7 @@ + @@ -527,6 +529,7 @@ + diff --git a/DXMainClient/Domain/Multiplayer/Map.cs b/DXMainClient/Domain/Multiplayer/Map.cs index 475793e4d..4cd92e567 100644 --- a/DXMainClient/Domain/Multiplayer/Map.cs +++ b/DXMainClient/Domain/Multiplayer/Map.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using Newtonsoft.Json; using Utilities = Rampastring.Tools.Utilities; @@ -47,6 +48,9 @@ public Map(string baseFilePath, string customMapFilePath = null) [JsonProperty] public string Name { get; private set; } + [JsonIgnore] + public string IniSafeName => Regex.Replace(Name, "[^a-zA-Z0-9]", string.Empty); + /// /// The maximum amount of players supported by the map. /// diff --git a/DXMainClient/Domain/Multiplayer/MapLoader.cs b/DXMainClient/Domain/Multiplayer/MapLoader.cs index 4ca76a55d..653d29a31 100644 --- a/DXMainClient/Domain/Multiplayer/MapLoader.cs +++ b/DXMainClient/Domain/Multiplayer/MapLoader.cs @@ -108,6 +108,7 @@ private void LoadMultiMaps(IniFile mpMapsIni) foreach (Map map in maps) { AddMapToGameModes(map, false); + AddUserDefinedTeamStartMappingPresets(map); } } @@ -186,6 +187,7 @@ private void LoadCustomMaps() foreach (Map map in customMapCache.Values) { AddMapToGameModes(map, false); + AddUserDefinedTeamStartMappingPresets(map); } } @@ -267,6 +269,7 @@ public Map LoadCustomMap(string mapPath, out string resultMessage) Logger.Log("LoadCustomMap: Map " + mapPath + " added succesfully."); AddMapToGameModes(map, true); + AddUserDefinedTeamStartMappingPresets(map); var gameModes = GameModes.Where(gm => gm.Maps.Contains(map)); GameModeMaps.AddRange(gameModes.Select(gm => new GameModeMap(gm, map, false))); @@ -323,5 +326,19 @@ private void AddMapToGameModes(Map map, bool enableLogging) } } } + + private static void AddUserDefinedTeamStartMappingPresets(Map map) + { + var teamStartMappingPresets = TeamStartMappingUserPresets.Instance.GetPresets(map); + foreach (TeamStartMappingPreset teamStartMappingPreset in teamStartMappingPresets) + map.TeamStartMappingPresets.Add(teamStartMappingPreset); + + string defaultPresetName = TeamStartMappingUserPresets.Instance.GetDefaultPresetName(map); + if (string.IsNullOrEmpty(defaultPresetName)) + return; + + foreach (TeamStartMappingPreset mapTeamStartMappingPreset in map.TeamStartMappingPresets) + mapTeamStartMappingPreset.IsDefaultForMap = string.Equals(defaultPresetName, mapTeamStartMappingPreset.Name); + } } } diff --git a/DXMainClient/Domain/Multiplayer/TeamStartMappingPreset.cs b/DXMainClient/Domain/Multiplayer/TeamStartMappingPreset.cs index 223f22c4f..bf7a801ae 100644 --- a/DXMainClient/Domain/Multiplayer/TeamStartMappingPreset.cs +++ b/DXMainClient/Domain/Multiplayer/TeamStartMappingPreset.cs @@ -5,10 +5,26 @@ namespace DTAClient.Domain.Multiplayer { public class TeamStartMappingPreset { + public const string UserDefinedPrefx = "[U]"; + [JsonProperty("n")] public string Name { get; set; } - + + [JsonIgnore] + public string DisplayName => $"{(IsUserDefined ? $"{UserDefinedPrefx} " : string.Empty)}{Name}"; + [JsonProperty("m")] public List TeamStartMappings { get; set; } + + public bool IsCustom { get; set; } + + public bool IsUserDefined { get; set; } + + public bool IsDefaultForMap { get; set; } + + public TeamStartMappingPreset() + { + TeamStartMappings = new List(); + } } } diff --git a/DXMainClient/Domain/Multiplayer/TeamStartMappingUserPresets.cs b/DXMainClient/Domain/Multiplayer/TeamStartMappingUserPresets.cs new file mode 100644 index 000000000..5c1d42c8f --- /dev/null +++ b/DXMainClient/Domain/Multiplayer/TeamStartMappingUserPresets.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ClientCore; +using Rampastring.Tools; + +namespace DTAClient.Domain.Multiplayer +{ + public class TeamStartMappingUserPresets + { + private const string IniFileName = "AutoAllyUserPresets.ini"; + private static readonly string FullIniPath = ProgramConstants.ClientUserFilesPath + IniFileName; + private const string PresetDefinitionsSectionName = "Presets"; + private const string DefaultPresetKey = "$$Default"; + + private IniFile teamStartMappingsPresetsIni; + private Dictionary> TeamStartMappingPresets; + private Dictionary TeamStartMappingPresetDefaultNames; + + private TeamStartMappingUserPresets() + { + } + + private static TeamStartMappingUserPresets _instance; + + public static TeamStartMappingUserPresets Instance + { + get + { + if (_instance == null) + _instance = new TeamStartMappingUserPresets(); + + return _instance; + } + } + + private void LoadIniIfNotInitialized() + { + if (teamStartMappingsPresetsIni == null) + Load(); + } + + private void Load() + { + TeamStartMappingPresets = new Dictionary>(); + TeamStartMappingPresetDefaultNames = new Dictionary(); + if (!File.Exists(FullIniPath)) + return; + + teamStartMappingsPresetsIni = new IniFile(FullIniPath); + + var presetsSection = teamStartMappingsPresetsIni.GetSection(PresetDefinitionsSectionName); + var mapNames = presetsSection.Keys.Select(key => key.Value); + + foreach (string mapName in mapNames) + { + var mapSection = teamStartMappingsPresetsIni.GetSection(mapName); + if (mapSection == null) + continue; + + if (mapSection.KeyExists(DefaultPresetKey)) + AddDefaultPresetName(mapName, mapSection.GetStringValue(DefaultPresetKey, null)); + + foreach (var mapSectionKey in mapSection.Keys.Where(k => k.Key != DefaultPresetKey)) + { + try + { + AddOrUpdate(mapName, new TeamStartMappingPreset + { + Name = mapSectionKey.Key, + TeamStartMappings = TeamStartMapping.FromListString(mapSectionKey.Value), + IsUserDefined = true + }); + } + catch (Exception e) + { + Logger.Log($"Unable to read user defined team/start mapping: {mapName}, {mapSectionKey.Key}, {mapSectionKey.Value}\n{e.Message}"); + } + } + } + } + + private void Save() + { + teamStartMappingsPresetsIni = new IniFile(); + var presetsSection = new IniSection(PresetDefinitionsSectionName); + teamStartMappingsPresetsIni.AddSection(presetsSection); + List mapNames = TeamStartMappingPresets.Keys.ToList(); + for (int i = 0; i < mapNames.Count; i++) + { + string mapName = mapNames[i]; + presetsSection.AddKey(i.ToString(), mapName); + var mapPresets = TeamStartMappingPresets[mapName]; + var defaultPreset = mapPresets.FirstOrDefault(p => p.IsDefaultForMap); + + var mapSection = new IniSection(mapName); + if (defaultPreset != null) + mapSection.AddKey(DefaultPresetKey, defaultPreset.Name); + + foreach (var teamStartMappingPreset in mapPresets.Where(p => p.IsUserDefined && p.Name != DefaultPresetKey)) + mapSection.AddKey(teamStartMappingPreset.Name, TeamStartMapping.ToListString(teamStartMappingPreset.TeamStartMappings)); + + teamStartMappingsPresetsIni.AddSection(mapSection); + } + + teamStartMappingsPresetsIni.WriteIniFile(FullIniPath); + // Load(); + } + + private void AddDefaultPresetName(string mapName, string defaultPresetName) + { + if (TeamStartMappingPresetDefaultNames.ContainsKey(mapName)) + TeamStartMappingPresetDefaultNames[mapName] = defaultPresetName; + else + TeamStartMappingPresetDefaultNames.Add(mapName, defaultPresetName); + } + + public void AddOrUpdate(Map map, TeamStartMappingPreset preset) + { + LoadIniIfNotInitialized(); + AddOrUpdate(map.IniSafeName, preset); + if (!map.TeamStartMappingPresets.Contains(preset)) + map.TeamStartMappingPresets.Add(preset); + Save(); + } + + private void AddOrUpdate(string mapName, TeamStartMappingPreset preset) + { + if (!TeamStartMappingPresets.ContainsKey(mapName)) + TeamStartMappingPresets.Add(mapName, new List()); + + var existingPreset = TeamStartMappingPresets[mapName].FirstOrDefault(p => p.Name == preset.Name); + + if (existingPreset == null) + { + TeamStartMappingPresets[mapName].Add(preset); + } + else + { + existingPreset.IsDefaultForMap = preset.IsDefaultForMap; + existingPreset.TeamStartMappings = preset.TeamStartMappings; + } + } + + public void DeletePreset(Map map, TeamStartMappingPreset preset) + { + LoadIniIfNotInitialized(); + if (preset == null) + return; + + map.TeamStartMappingPresets.Remove(preset); + + string iniSafeName = map.IniSafeName; + if (!TeamStartMappingPresets.ContainsKey(iniSafeName)) + return; + + TeamStartMappingPresets[iniSafeName].Remove(preset); + + Save(); + } + + public List GetPresets(Map map) + { + LoadIniIfNotInitialized(); + string iniSafeName = map.IniSafeName; + + return TeamStartMappingPresets.ContainsKey(iniSafeName) ? TeamStartMappingPresets[iniSafeName] : new List(); + } + + public string GetDefaultPresetName(Map map) + { + LoadIniIfNotInitialized(); + string iniSafeName = map.IniSafeName; + return TeamStartMappingPresetDefaultNames.ContainsKey(iniSafeName) ? TeamStartMappingPresetDefaultNames[iniSafeName] : null; + } + } +} diff --git a/DXMainClient/Online/EventArguments/TeamStartMappingPresetEventArgs.cs b/DXMainClient/Online/EventArguments/TeamStartMappingPresetEventArgs.cs new file mode 100644 index 000000000..719c07e92 --- /dev/null +++ b/DXMainClient/Online/EventArguments/TeamStartMappingPresetEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using DTAClient.Domain.Multiplayer; + +namespace DTAClient.Online.EventArguments +{ + public class TeamStartMappingPresetEventArgs : EventArgs + { + public Map Map { get; set; } + + public TeamStartMappingPreset Preset { get; } + + public TeamStartMappingPresetEventArgs(Map map, TeamStartMappingPreset preset) + { + Map = map; + Preset = preset; + } + } +}