// // Copyright 2020 Electronic Arts Inc. // // The Command & Conquer Map Editor and corresponding source code is free // software: you can redistribute it and/or modify it under the terms of // the GNU General Public License as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // The Command & Conquer Map Editor and corresponding source code is distributed // in the hope that it will be useful, but with permitted additional restrictions // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT // distributed with this program. You should have received a copy of the // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection using MobiusEditor.Dialogs; using MobiusEditor.Event; using MobiusEditor.Interface; using MobiusEditor.Model; using MobiusEditor.Tools; using MobiusEditor.Tools.Dialogs; using MobiusEditor.Utility; using Steamworks; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; namespace MobiusEditor { public partial class MainForm : Form { [Flags] private enum ToolType { None = 0, Map = 1 << 0, Smudge = 1 << 1, Overlay = 1 << 2, Terrain = 1 << 3, Infantry = 1 << 4, Unit = 1 << 5, Building = 1 << 6, Resources = 1 << 7, Wall = 1 << 8, Waypoint = 1 << 9, CellTrigger = 1 << 10 } private static readonly ToolType[] toolTypes; private ToolType availableToolTypes = ToolType.None; private ToolType activeToolType = ToolType.None; private ToolType ActiveToolType { get => activeToolType; set { var firstAvailableTool = value; if ((availableToolTypes & firstAvailableTool) == ToolType.None) { var otherAvailableToolTypes = toolTypes.Where(t => (availableToolTypes & t) != ToolType.None); firstAvailableTool = otherAvailableToolTypes.Any() ? otherAvailableToolTypes.First() : ToolType.None; } if (activeToolType != firstAvailableTool) { activeToolType = firstAvailableTool; RefreshActiveTool(); } } } private MapLayerFlag activeLayers; public MapLayerFlag ActiveLayers { get => activeLayers; set { if (activeLayers != value) { activeLayers = value; if (activeTool != null) { activeTool.Layers = ActiveLayers; } } } } private ITool activeTool; private Form activeToolForm; private IGamePlugin plugin; private string filename; private readonly MRU mru; private readonly UndoRedoList url = new UndoRedoList(); private readonly Timer steamUpdateTimer = new Timer(); static MainForm() { toolTypes = ((IEnumerable)Enum.GetValues(typeof(ToolType))).Where(t => t != ToolType.None).ToArray(); } public MainForm() { InitializeComponent(); mru = new MRU("Software\\Petroglyph\\CnCRemasteredEditor", 10, fileRecentFilesMenuItem); mru.FileSelected += Mru_FileSelected; foreach (ToolStripButton toolStripButton in mainToolStrip.Items) { toolStripButton.MouseMove += mainToolStrip_MouseMove; } #if !DEVELOPER fileExportMenuItem.Visible = false; developerToolStripMenuItem.Visible = false; #endif url.Tracked += UndoRedo_Updated; url.Undone += UndoRedo_Updated; url.Redone += UndoRedo_Updated; UpdateUndoRedo(); steamUpdateTimer.Interval = 500; steamUpdateTimer.Tick += SteamUpdateTimer_Tick; } private void SteamUpdateTimer_Tick(object sender, EventArgs e) { if (SteamworksUGC.IsInit) { SteamworksUGC.Service(); } } protected override void OnLoad(EventArgs e) { base.OnLoad(e); RefreshAvailableTools(); UpdateVisibleLayers(); filePublishMenuItem.Visible = SteamworksUGC.IsInit; steamUpdateTimer.Start(); } protected override void OnClosed(EventArgs e) { base.OnClosed(e); steamUpdateTimer.Stop(); steamUpdateTimer.Dispose(); } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == Keys.Q) { mapToolStripButton.PerformClick(); return true; } else if (keyData == Keys.W) { smudgeToolStripButton.PerformClick(); return true; } else if (keyData == Keys.E) { overlayToolStripButton.PerformClick(); return true; } else if (keyData == Keys.R) { terrainToolStripButton.PerformClick(); return true; } else if (keyData == Keys.T) { infantryToolStripButton.PerformClick(); return true; } else if (keyData == Keys.Y) { unitToolStripButton.PerformClick(); return true; } else if (keyData == Keys.A) { buildingToolStripButton.PerformClick(); return true; } else if (keyData == Keys.S) { resourcesToolStripButton.PerformClick(); return true; } else if (keyData == Keys.D) { wallsToolStripButton.PerformClick(); return true; } else if (keyData == Keys.F) { waypointsToolStripButton.PerformClick(); return true; } else if (keyData == Keys.G) { cellTriggersToolStripButton.PerformClick(); return true; } else if (keyData == (Keys.Control | Keys.Z)) { if (editUndoMenuItem.Enabled) { editUndoMenuItem_Click(this, new EventArgs()); } return true; } else if (keyData == (Keys.Control | Keys.Y)) { if (editRedoMenuItem.Enabled) { editRedoMenuItem_Click(this, new EventArgs()); } return true; } return base.ProcessCmdKey(ref msg, keyData); } private void UpdateUndoRedo() { editUndoMenuItem.Enabled = url.CanUndo; editRedoMenuItem.Enabled = url.CanRedo; } private void UndoRedo_Updated(object sender, EventArgs e) { UpdateUndoRedo(); } private void fileNewMenuItem_Click(object sender, EventArgs e) { if (!PromptSaveMap()) { return; } NewMapDialog nmd = new NewMapDialog(); if (nmd.ShowDialog() == DialogResult.OK) { if (plugin != null) { plugin.Map.Triggers.CollectionChanged -= Triggers_CollectionChanged; plugin.Dispose(); } plugin = null; Globals.TheTilesetManager.Reset(); Globals.TheTextureManager.Reset(); if (nmd.GameType == GameType.TiberianDawn) { Globals.TheTeamColorManager.Reset(); Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML"); plugin = new TiberianDawn.GamePlugin(); plugin.New(nmd.TheaterName); } else if (nmd.GameType == GameType.RedAlert) { Globals.TheTeamColorManager.Reset(); Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML"); plugin = new RedAlert.GamePlugin(); plugin.New(nmd.TheaterName); } if (SteamworksUGC.IsInit) { plugin.Map.BasicSection.Author = SteamFriends.GetPersonaName(); } plugin.Map.Triggers.CollectionChanged += Triggers_CollectionChanged; mapPanel.MapImage = plugin.MapImage; filename = null; Text = "CnC TDRA Map Editor"; url.Clear(); ClearActiveTool(); RefreshAvailableTools(); RefreshActiveTool(); } } private void fileOpenMenuItem_Click(object sender, EventArgs e) { if (!PromptSaveMap()) { return; } var pgmFilter = #if DEVELOPER "|PGM files (*.pgm)|*.pgm" #else string.Empty #endif ; OpenFileDialog ofd = new OpenFileDialog { AutoUpgradeEnabled = false, RestoreDirectory = true }; ofd.Filter = "Tiberian Dawn files (*.ini;*.bin)|*.ini;*.bin|Red Alert files (*.mpr)|*.mpr" + pgmFilter + "|All files (*.*)|*.*"; if (plugin != null) { switch (plugin.GameType) { case GameType.TiberianDawn: ofd.InitialDirectory = TiberianDawn.Constants.SaveDirectory; ofd.FilterIndex = 1; break; case GameType.RedAlert: ofd.InitialDirectory = RedAlert.Constants.SaveDirectory; ofd.FilterIndex = 2; break; } } else { ofd.InitialDirectory = Globals.RootSaveDirectory; } if (ofd.ShowDialog() == DialogResult.OK) { var fileInfo = new FileInfo(ofd.FileName); if (LoadFile(fileInfo.FullName)) { mru.Add(fileInfo); } else { mru.Remove(fileInfo); MessageBox.Show(string.Format("Error loading {0}.", ofd.FileName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } private void fileSaveMenuItem_Click(object sender, EventArgs e) { if (plugin == null) { return; } if (string.IsNullOrEmpty(filename)) { fileSaveAsMenuItem.PerformClick(); } else { var fileInfo = new FileInfo(filename); if (SaveFile(fileInfo.FullName)) { mru.Add(fileInfo); } else { mru.Remove(fileInfo); } } } private void fileSaveAsMenuItem_Click(object sender, EventArgs e) { if (plugin == null) { return; } SaveFileDialog sfd = new SaveFileDialog { AutoUpgradeEnabled = false, RestoreDirectory = true }; var filters = new List(); switch (plugin.GameType) { case GameType.TiberianDawn: filters.Add("Tiberian Dawn files (*.ini;*.bin)|*.ini;*.bin"); sfd.InitialDirectory = TiberianDawn.Constants.SaveDirectory; break; case GameType.RedAlert: filters.Add("Red Alert files (*.mpr)|*.mpr"); sfd.InitialDirectory = RedAlert.Constants.SaveDirectory; break; } filters.Add("All files (*.*)|*.*"); sfd.Filter = string.Join("|", filters); if (!string.IsNullOrEmpty(filename)) { sfd.InitialDirectory = Path.GetDirectoryName(filename); sfd.FileName = Path.GetFileName(filename); } if (sfd.ShowDialog() == DialogResult.OK) { var fileInfo = new FileInfo(sfd.FileName); if (SaveFile(fileInfo.FullName)) { mru.Add(fileInfo); } else { mru.Remove(fileInfo); } } } private void fileExportMenuItem_Click(object sender, EventArgs e) { if (plugin == null) { return; } SaveFileDialog sfd = new SaveFileDialog { AutoUpgradeEnabled = false, RestoreDirectory = true }; sfd.Filter = "MEG files (*.meg)|*.meg"; if (sfd.ShowDialog() == DialogResult.OK) { plugin.Save(sfd.FileName, FileType.MEG); } } private void fileExitMenuItem_Click(object sender, EventArgs e) { Close(); } private void editUndoMenuItem_Click(object sender, EventArgs e) { if (url.CanUndo) { url.Undo(new UndoRedoEventArgs(mapPanel, plugin.Map)); } } private void editRedoMenuItem_Click(object sender, EventArgs e) { if (url.CanRedo) { url.Redo(new UndoRedoEventArgs(mapPanel, plugin.Map)); } } private void settingsMapSettingsMenuItem_Click(object sender, EventArgs e) { if (plugin == null) { return; } var basicSettings = new PropertyTracker(plugin.Map.BasicSection); var briefingSettings = new PropertyTracker(plugin.Map.BriefingSection); var houseSettingsTrackers = plugin.Map.Houses.ToDictionary(h => h, h => new PropertyTracker(h)); MapSettingsDialog msd = new MapSettingsDialog(plugin, basicSettings, briefingSettings, houseSettingsTrackers); if (msd.ShowDialog() == DialogResult.OK) { basicSettings.Commit(); briefingSettings.Commit(); foreach (var houseSettingsTracker in houseSettingsTrackers.Values) { houseSettingsTracker.Commit(); } plugin.Dirty = true; } } private void settingsTeamTypesMenuItem_Click(object sender, EventArgs e) { if (plugin == null) { return; } int maxTeams = 0; switch (plugin.GameType) { case GameType.TiberianDawn: { maxTeams = TiberianDawn.Constants.MaxTeams; } break; case GameType.RedAlert: { maxTeams = RedAlert.Constants.MaxTeams; } break; } TeamTypesDialog ttd = new TeamTypesDialog(plugin, maxTeams); if (ttd.ShowDialog() == DialogResult.OK) { plugin.Map.TeamTypes.Clear(); plugin.Map.TeamTypes.AddRange(ttd.TeamTypes.Select(t => t.Clone())); plugin.Dirty = true; } } private void settingsTriggersMenuItem_Click(object sender, EventArgs e) { if (plugin == null) { return; } int maxTriggers = 0; switch (plugin.GameType) { case GameType.TiberianDawn: { maxTriggers = TiberianDawn.Constants.MaxTriggers; } break; case GameType.RedAlert: { maxTriggers = RedAlert.Constants.MaxTriggers; } break; } TriggersDialog td = new TriggersDialog(plugin, maxTriggers); if (td.ShowDialog() == DialogResult.OK) { var oldTriggers = from leftTrigger in plugin.Map.Triggers join rightTrigger in td.Triggers on leftTrigger.Name equals rightTrigger.Name into result where result.Count() == 0 select leftTrigger; var newTriggers = from leftTrigger in td.Triggers join rightTrigger in plugin.Map.Triggers on leftTrigger.Name equals rightTrigger.Name into result where result.Count() == 0 select leftTrigger; var sameTriggers = from leftTrigger in plugin.Map.Triggers join rightTrigger in td.Triggers on leftTrigger.Name equals rightTrigger.Name select new { OldTrigger = leftTrigger, NewTrigger = rightTrigger }; foreach (var oldTrigger in oldTriggers.ToArray()) { plugin.Map.Triggers.Remove(oldTrigger); } foreach (var newTrigger in newTriggers.ToArray()) { plugin.Map.Triggers.Add(newTrigger.Clone()); } foreach (var item in sameTriggers.ToArray()) { plugin.Map.Triggers.Add(item.NewTrigger.Clone()); plugin.Map.Triggers.Remove(item.OldTrigger); } plugin.Dirty = true; } } private void Mru_FileSelected(object sender, FileInfo e) { if (!PromptSaveMap()) { return; } if (LoadFile(e.FullName)) { mru.Add(e); } else { mru.Remove(e); MessageBox.Show(string.Format("Error loading {0}.", e.FullName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void mapPanel_MouseMove(object sender, MouseEventArgs e) { if (plugin != null) { var mapPoint = mapPanel.ClientToMap(e.Location); var location = new Point((int)Math.Floor((double)mapPoint.X / Globals.TileWidth), (int)Math.Floor((double)mapPoint.Y / Globals.TileHeight)); if (plugin.Map.Metrics.GetCell(location, out int cell)) { var sb = new StringBuilder(); sb.AppendFormat("X = {0}, Y = {1}, Cell = {2}", location.X, location.Y, cell); var template = plugin.Map.Templates[cell]; var templateType = template?.Type; if (templateType != null) { sb.AppendFormat(", Template = {0} ({1})", templateType.DisplayName, template.Icon); } var smudge = plugin.Map.Smudge[cell]; var smudgeType = smudge?.Type; if (smudgeType != null) { sb.AppendFormat(", Smudge = {0}", smudgeType.DisplayName); } var overlay = plugin.Map.Overlay[cell]; var overlayType = overlay?.Type; if (overlayType != null) { sb.AppendFormat(", Overlay = {0}", overlayType.DisplayName); } var terrain = plugin.Map.Technos[location] as Terrain; var terrainType = terrain?.Type; if (terrainType != null) { sb.AppendFormat(", Terrain = {0}", terrainType.DisplayName); } if (plugin.Map.Technos[location] is InfantryGroup infantryGroup) { var subPixel = new Point( (mapPoint.X * Globals.PixelWidth / Globals.TileWidth) % Globals.PixelWidth, (mapPoint.Y * Globals.PixelHeight / Globals.TileHeight) % Globals.PixelHeight ); var i = InfantryGroup.ClosestStoppingTypes(subPixel).Cast().First(); if (infantryGroup.Infantry[i] != null) { sb.AppendFormat(", Infantry = {0}", infantryGroup.Infantry[i].Type.DisplayName); } } var unit = plugin.Map.Technos[location] as Unit; var unitType = unit?.Type; if (unitType != null) { sb.AppendFormat(", Unit = {0}", unitType.DisplayName); } var building = plugin.Map.Technos[location] as Building; var buildingType = building?.Type; if (buildingType != null) { sb.AppendFormat(", Building = {0}", buildingType.DisplayName); } cellStatusLabel.Text = sb.ToString(); } else { cellStatusLabel.Text = string.Empty; } } } private bool LoadFile(string loadFilename) { FileType fileType = FileType.None; switch (Path.GetExtension(loadFilename).ToLower()) { case ".ini": case ".mpr": fileType = FileType.INI; break; case ".bin": fileType = FileType.BIN; break; #if DEVELOPER case ".pgm": fileType = FileType.PGM; break; #endif } if (fileType == FileType.None) { return false; } GameType gameType = GameType.None; switch (fileType) { case FileType.INI: { var ini = new INI(); try { using (var reader = new StreamReader(loadFilename)) { ini.Parse(reader); } } catch (FileNotFoundException) { return false; } gameType = File.Exists(Path.ChangeExtension(loadFilename, ".bin")) ? GameType.TiberianDawn : GameType.RedAlert; } break; case FileType.BIN: gameType = GameType.TiberianDawn; break; #if DEVELOPER case FileType.PGM: { try { using (var megafile = new Megafile(loadFilename)) { if (megafile.Any(f => Path.GetExtension(f).ToLower() == ".mpr")) { gameType = GameType.RedAlert; } else { gameType = GameType.TiberianDawn; } } } catch (FileNotFoundException) { return false; } } break; #endif } if (gameType == GameType.None) { return false; } if (plugin != null) { plugin.Map.Triggers.CollectionChanged -= Triggers_CollectionChanged; plugin.Dispose(); } plugin = null; Globals.TheTilesetManager.Reset(); Globals.TheTextureManager.Reset(); switch (gameType) { case GameType.TiberianDawn: { Globals.TheTeamColorManager.Reset(); Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML"); plugin = new TiberianDawn.GamePlugin(); } break; case GameType.RedAlert: { Globals.TheTeamColorManager.Reset(); Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML"); plugin = new RedAlert.GamePlugin(); } break; } try { var errors = plugin.Load(loadFilename, fileType).ToArray(); if (errors.Length > 0) { ErrorMessageBox errorMessageBox = new ErrorMessageBox { Errors = errors }; errorMessageBox.ShowDialog(); } } catch (Exception) { #if DEVELOPER throw; #else return false; #endif } plugin.Map.Triggers.CollectionChanged += Triggers_CollectionChanged; mapPanel.MapImage = plugin.MapImage; plugin.Dirty = false; filename = loadFilename; Text = string.Format("CnC TDRA Map Editor - {0}", filename); url.Clear(); ClearActiveTool(); RefreshAvailableTools(); RefreshActiveTool(); return true; } private bool SaveFile(string saveFilename) { FileType fileType = FileType.None; switch (Path.GetExtension(saveFilename).ToLower()) { case ".ini": case ".mpr": fileType = FileType.INI; break; case ".bin": fileType = FileType.BIN; break; } if (fileType == FileType.None) { return false; } if (string.IsNullOrEmpty(plugin.Map.SteamSection.Title)) { plugin.Map.SteamSection.Title = plugin.Map.BasicSection.Name; } if (!plugin.Save(saveFilename, fileType)) { return false; } if (new FileInfo(saveFilename).Length > Globals.MaxMapSize) { MessageBox.Show(string.Format("Map file exceeds the maximum size of {0} bytes.", Globals.MaxMapSize), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } plugin.Dirty = false; filename = saveFilename; Text = string.Format("CnC TDRA Map Editor - {0}", filename); return true; } private void RefreshAvailableTools() { availableToolTypes = ToolType.None; if (plugin != null) { availableToolTypes |= ToolType.Waypoint; if (plugin.Map.TemplateTypes.Any()) availableToolTypes |= ToolType.Map; if (plugin.Map.SmudgeTypes.Any()) availableToolTypes |= ToolType.Smudge; if (plugin.Map.OverlayTypes.Any(t => t.IsPlaceable && ((t.Theaters == null) || t.Theaters.Contains(plugin.Map.Theater)))) availableToolTypes |= ToolType.Overlay; if (plugin.Map.TerrainTypes.Any(t => t.Theaters.Contains(plugin.Map.Theater))) availableToolTypes |= ToolType.Terrain; if (plugin.Map.InfantryTypes.Any()) availableToolTypes |= ToolType.Infantry; if (plugin.Map.UnitTypes.Any()) availableToolTypes |= ToolType.Unit; if (plugin.Map.BuildingTypes.Any()) availableToolTypes |= ToolType.Building; if (plugin.Map.OverlayTypes.Any(t => t.IsResource)) availableToolTypes |= ToolType.Resources; if (plugin.Map.OverlayTypes.Any(t => t.IsWall)) availableToolTypes |= ToolType.Wall; if (plugin.Map.Triggers.Any()) availableToolTypes |= ToolType.CellTrigger; } mapToolStripButton.Enabled = (availableToolTypes & ToolType.Map) != ToolType.None; smudgeToolStripButton.Enabled = (availableToolTypes & ToolType.Smudge) != ToolType.None; overlayToolStripButton.Enabled = (availableToolTypes & ToolType.Overlay) != ToolType.None; terrainToolStripButton.Enabled = (availableToolTypes & ToolType.Terrain) != ToolType.None; infantryToolStripButton.Enabled = (availableToolTypes & ToolType.Infantry) != ToolType.None; unitToolStripButton.Enabled = (availableToolTypes & ToolType.Unit) != ToolType.None; buildingToolStripButton.Enabled = (availableToolTypes & ToolType.Building) != ToolType.None; resourcesToolStripButton.Enabled = (availableToolTypes & ToolType.Resources) != ToolType.None; wallsToolStripButton.Enabled = (availableToolTypes & ToolType.Wall) != ToolType.None; waypointsToolStripButton.Enabled = (availableToolTypes & ToolType.Waypoint) != ToolType.None; cellTriggersToolStripButton.Enabled = (availableToolTypes & ToolType.CellTrigger) != ToolType.None; ActiveToolType = activeToolType; } private void ClearActiveTool() { activeTool?.Dispose(); activeTool = null; if (activeToolForm != null) { activeToolForm.ResizeEnd -= ActiveToolForm_ResizeEnd; activeToolForm.Close(); activeToolForm = null; } toolStatusLabel.Text = string.Empty; } private void RefreshActiveTool() { if (plugin == null) { return; } if (activeTool == null) { activeLayers = MapLayerFlag.None; } ClearActiveTool(); switch (ActiveToolType) { case ToolType.Map: { TemplateToolDialog toolDialog = new TemplateToolDialog(); activeTool = new TemplateTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.TemplateTypeListView, toolDialog.TemplateTypeMapPanel, mouseToolTip, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.Smudge: { GenericToolDialog toolDialog = new GenericToolDialog { Text = "Smudge" }; toolDialog.GenericTypeComboBox.Types = plugin.Map.SmudgeTypes.Where(t => (t.Flag & SmudgeTypeFlag.Bib) == SmudgeTypeFlag.None).OrderBy(t => t.Name); activeTool = new SmudgeTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.GenericTypeComboBox, toolDialog.GenericTypeMapPanel, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.Overlay: { GenericToolDialog toolDialog = new GenericToolDialog { Text = "Overlay" }; toolDialog.GenericTypeComboBox.Types = plugin.Map.OverlayTypes.Where(t => t.IsPlaceable && ((t.Theaters == null) || t.Theaters.Contains(plugin.Map.Theater))).OrderBy(t => t.Name); activeTool = new OverlaysTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.GenericTypeComboBox, toolDialog.GenericTypeMapPanel, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.Resources: { ResourcesToolDialog toolDialog = new ResourcesToolDialog(); activeTool = new ResourcesTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.TotalResourcesLbl, toolDialog.ResourceBrushSizeNud, toolDialog.GemsCheckBox, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.Terrain: { TerrainToolDialog toolDialog = new TerrainToolDialog(plugin); toolDialog.TerrainTypeComboBox.Types = plugin.Map.TerrainTypes.Where(t => t.Theaters.Contains(plugin.Map.Theater)).OrderBy(t => t.Name); activeTool = new TerrainTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.TerrainTypeComboBox, toolDialog.TerrainTypeMapPanel, toolDialog.TerrainProperties, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.Infantry: { ObjectToolDialog toolDialog = new ObjectToolDialog(plugin) { Text = "Infantry" }; toolDialog.ObjectTypeComboBox.Types = plugin.Map.InfantryTypes.OrderBy(t => t.Name); activeTool = new InfantryTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.ObjectTypeComboBox, toolDialog.ObjectTypeMapPanel, toolDialog.ObjectProperties, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.Unit: { ObjectToolDialog toolDialog = new ObjectToolDialog(plugin) { Text = "Units" }; toolDialog.ObjectTypeComboBox.Types = plugin.Map.UnitTypes .Where(t => !t.IsFixedWing) .OrderBy(t => t.Name); activeTool = new UnitTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.ObjectTypeComboBox, toolDialog.ObjectTypeMapPanel, toolDialog.ObjectProperties, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.Building: { ObjectToolDialog toolDialog = new ObjectToolDialog(plugin) { Text = "Structures" }; toolDialog.ObjectTypeComboBox.Types = plugin.Map.BuildingTypes .Where(t => (t.Theaters == null) || t.Theaters.Contains(plugin.Map.Theater)) .OrderBy(t => t.IsFake) .ThenBy(t => t.Name); activeTool = new BuildingTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.ObjectTypeComboBox, toolDialog.ObjectTypeMapPanel, toolDialog.ObjectProperties, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.Wall: { GenericToolDialog toolDialog = new GenericToolDialog { Text = "Walls" }; toolDialog.GenericTypeComboBox.Types = plugin.Map.OverlayTypes.Where(t => t.IsWall).OrderBy(t => t.Name); activeTool = new WallsTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.GenericTypeComboBox, toolDialog.GenericTypeMapPanel, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.Waypoint: { WaypointsToolDialog toolDialog = new WaypointsToolDialog(); toolDialog.WaypointCombo.DataSource = plugin.Map.Waypoints.Select(w => w.Name).ToArray(); activeTool = new WaypointsTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.WaypointCombo, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; case ToolType.CellTrigger: { CellTriggersToolDialog toolDialog = new CellTriggersToolDialog(); toolDialog.TriggerCombo.DataSource = plugin.Map.Triggers.Select(t => t.Name).ToArray(); activeTool = new CellTriggersTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.TriggerCombo, plugin, url); activeToolForm = toolDialog; activeToolForm.Show(this); } break; } if (activeToolForm != null) { activeToolForm.ResizeEnd += ActiveToolForm_ResizeEnd; clampActiveToolForm(); } switch (plugin.GameType) { case GameType.TiberianDawn: mapPanel.MaxZoom = 8; mapPanel.ZoomStep = 1; break; case GameType.RedAlert: mapPanel.MaxZoom = 16; mapPanel.ZoomStep = 2; break; } mapToolStripButton.Checked = ActiveToolType == ToolType.Map; smudgeToolStripButton.Checked = ActiveToolType == ToolType.Smudge; overlayToolStripButton.Checked = ActiveToolType == ToolType.Overlay; terrainToolStripButton.Checked = ActiveToolType == ToolType.Terrain; infantryToolStripButton.Checked = ActiveToolType == ToolType.Infantry; unitToolStripButton.Checked = ActiveToolType == ToolType.Unit; buildingToolStripButton.Checked = ActiveToolType == ToolType.Building; resourcesToolStripButton.Checked = ActiveToolType == ToolType.Resources; wallsToolStripButton.Checked = ActiveToolType == ToolType.Wall; waypointsToolStripButton.Checked = ActiveToolType == ToolType.Waypoint; cellTriggersToolStripButton.Checked = ActiveToolType == ToolType.CellTrigger; Focus(); UpdateVisibleLayers(); mapPanel.Invalidate(); } private void clampActiveToolForm() { if (activeToolForm == null) { return; } Rectangle bounds = activeToolForm.DesktopBounds; Rectangle workingArea = Screen.FromControl(this).WorkingArea; if (bounds.Right > workingArea.Right) { bounds.X = workingArea.Right - bounds.Width; } if (bounds.X < workingArea.Left) { bounds.X = workingArea.Left; } if (bounds.Bottom > workingArea.Bottom) { bounds.Y = workingArea.Bottom - bounds.Height; } if (bounds.Y < workingArea.Top) { bounds.Y = workingArea.Top; } activeToolForm.DesktopBounds = bounds; } private void ActiveToolForm_ResizeEnd(object sender, EventArgs e) { clampActiveToolForm(); } private void Triggers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { RefreshAvailableTools(); } private void mainToolStripButton_Click(object sender, EventArgs e) { if (plugin == null) { return; } if (sender == mapToolStripButton) { ActiveToolType = ToolType.Map; } else if (sender == smudgeToolStripButton) { ActiveToolType = ToolType.Smudge; } else if (sender == overlayToolStripButton) { ActiveToolType = ToolType.Overlay; } else if (sender == terrainToolStripButton) { ActiveToolType = ToolType.Terrain; } else if (sender == infantryToolStripButton) { ActiveToolType = ToolType.Infantry; } else if (sender == unitToolStripButton) { ActiveToolType = ToolType.Unit; } else if (sender == buildingToolStripButton) { ActiveToolType = ToolType.Building; } else if (sender == resourcesToolStripButton) { ActiveToolType = ToolType.Resources; } else if (sender == wallsToolStripButton) { ActiveToolType = ToolType.Wall; } else if (sender == waypointsToolStripButton) { ActiveToolType = ToolType.Waypoint; } else if (sender == cellTriggersToolStripButton) { ActiveToolType = ToolType.CellTrigger; } } private void UpdateVisibleLayers() { MapLayerFlag layers = MapLayerFlag.All; if (!viewLayersBoundariesMenuItem.Checked) { layers &= ~MapLayerFlag.Boundaries; } if (!viewLayersOverlayMenuItem.Checked) { layers &= ~MapLayerFlag.OverlayAll; } if (!viewLayersTerrainMenuItem.Checked) { layers &= ~MapLayerFlag.Terrain; } if (!viewLayersWaypointsMenuItem.Checked) { layers &= ~MapLayerFlag.Waypoints; } if (!viewLayersCellTriggersMenuItem.Checked) { layers &= ~MapLayerFlag.CellTriggers; } if (!viewLayersObjectTriggersMenuItem.Checked) { layers &= ~MapLayerFlag.TechnoTriggers; } ActiveLayers = layers; } private void viewLayersMenuItem_CheckedChanged(object sender, EventArgs e) { UpdateVisibleLayers(); } private void toolTabControl_Selected(object sender, TabControlEventArgs e) { if (plugin == null) { return; } } private void developerGenerateMapPreviewMenuItem_Click(object sender, EventArgs e) { #if DEVELOPER if ((plugin == null) || string.IsNullOrEmpty(filename)) { return; } plugin.Map.GenerateMapPreview().Save(Path.ChangeExtension(filename, ".tga")); #endif } private void developerGoToINIMenuItem_Click(object sender, EventArgs e) { #if DEVELOPER if ((plugin == null) || string.IsNullOrEmpty(filename)) { return; } var path = Path.ChangeExtension(filename, ".mpr"); if (!File.Exists(path)) { path = Path.ChangeExtension(filename, ".ini"); } try { Process.Start(path); } catch (Win32Exception) { Process.Start("notepad.exe", path); } catch (Exception) { } #endif } private void developerGenerateMapPreviewDirectoryMenuItem_Click(object sender, EventArgs e) { #if DEVELOPER FolderBrowserDialog fbd = new FolderBrowserDialog { ShowNewFolderButton = false }; if (fbd.ShowDialog() == DialogResult.OK) { var extensions = new string[] { ".ini", ".mpr" }; foreach (var file in Directory.EnumerateFiles(fbd.SelectedPath).Where(file => extensions.Contains(Path.GetExtension(file).ToLower()))) { GameType gameType = GameType.None; var ini = new INI(); using (var reader = new StreamReader(file)) { ini.Parse(reader); } gameType = ini.Sections.Contains("MapPack") ? GameType.RedAlert : GameType.TiberianDawn; if (gameType == GameType.None) { continue; } IGamePlugin plugin = null; switch (gameType) { case GameType.TiberianDawn: { plugin = new TiberianDawn.GamePlugin(false); } break; case GameType.RedAlert: { plugin = new RedAlert.GamePlugin(false); } break; } plugin.Load(file, FileType.INI); plugin.Map.GenerateMapPreview().Save(Path.ChangeExtension(file, ".tga")); plugin.Dispose(); } } #endif } private void developerDebugShowOverlapCellsMenuItem_CheckedChanged(object sender, EventArgs e) { #if DEVELOPER Globals.Developer.ShowOverlapCells = developerDebugShowOverlapCellsMenuItem.Checked; #endif } private void filePublishMenuItem_Click(object sender, EventArgs e) { if (plugin == null) { return; } if (!PromptSaveMap()) { return; } if (plugin.Dirty) { MessageBox.Show("Map must be saved before publishing.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (new FileInfo(filename).Length > Globals.MaxMapSize) { return; } using (var sd = new SteamDialog(plugin)) { sd.ShowDialog(); } fileSaveMenuItem.PerformClick(); } private void mainToolStrip_MouseMove(object sender, MouseEventArgs e) { mainToolStrip.Focus(); } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { e.Cancel = !PromptSaveMap(); } private bool PromptSaveMap() { bool cancel = false; if (plugin?.Dirty ?? false) { var message = string.IsNullOrEmpty(filename) ? "Save new map?" : string.Format("Save map '{0}'?", filename); var result = MessageBox.Show(message, "Save", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); switch (result) { case DialogResult.Yes: { if (string.IsNullOrEmpty(filename)) { fileSaveAsMenuItem.PerformClick(); } else { fileSaveMenuItem.PerformClick(); } } break; case DialogResult.No: break; case DialogResult.Cancel: cancel = true; break; } } return !cancel; } } }