CnC_Remastered_Collection

Command and Conquer: Red Alert
Log | Files | Refs | README | LICENSE

MainForm.cs (50215B)


      1 //
      2 // Copyright 2020 Electronic Arts Inc.
      3 //
      4 // The Command & Conquer Map Editor and corresponding source code is free 
      5 // software: you can redistribute it and/or modify it under the terms of 
      6 // the GNU General Public License as published by the Free Software Foundation, 
      7 // either version 3 of the License, or (at your option) any later version.
      8 
      9 // The Command & Conquer Map Editor and corresponding source code is distributed 
     10 // in the hope that it will be useful, but with permitted additional restrictions 
     11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 
     12 // distributed with this program. You should have received a copy of the 
     13 // GNU General Public License along with permitted additional restrictions 
     14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
     15 using MobiusEditor.Dialogs;
     16 using MobiusEditor.Event;
     17 using MobiusEditor.Interface;
     18 using MobiusEditor.Model;
     19 using MobiusEditor.Tools;
     20 using MobiusEditor.Tools.Dialogs;
     21 using MobiusEditor.Utility;
     22 using Steamworks;
     23 using System;
     24 using System.Collections.Generic;
     25 using System.ComponentModel;
     26 using System.Data;
     27 using System.Diagnostics;
     28 using System.Drawing;
     29 using System.IO;
     30 using System.Linq;
     31 using System.Text;
     32 using System.Windows.Forms;
     33 
     34 namespace MobiusEditor
     35 {
     36     public partial class MainForm : Form
     37     {
     38         [Flags]
     39         private enum ToolType
     40         {
     41             None        = 0,
     42             Map         = 1 << 0,
     43             Smudge      = 1 << 1,
     44             Overlay     = 1 << 2,
     45             Terrain     = 1 << 3,
     46             Infantry    = 1 << 4,
     47             Unit        = 1 << 5,
     48             Building    = 1 << 6,
     49             Resources   = 1 << 7,
     50             Wall        = 1 << 8,
     51             Waypoint    = 1 << 9,
     52             CellTrigger = 1 << 10
     53         }
     54 
     55         private static readonly ToolType[] toolTypes;
     56 
     57         private ToolType availableToolTypes = ToolType.None;
     58 
     59         private ToolType activeToolType = ToolType.None;
     60         private ToolType ActiveToolType
     61         {
     62             get => activeToolType;
     63             set
     64             {
     65                 var firstAvailableTool = value;
     66                 if ((availableToolTypes & firstAvailableTool) == ToolType.None)
     67                 {
     68                     var otherAvailableToolTypes = toolTypes.Where(t => (availableToolTypes & t) != ToolType.None);
     69                     firstAvailableTool = otherAvailableToolTypes.Any() ? otherAvailableToolTypes.First() : ToolType.None;
     70                 }
     71 
     72                 if (activeToolType != firstAvailableTool)
     73                 {
     74                     activeToolType = firstAvailableTool;
     75                     RefreshActiveTool();
     76                 }
     77             }
     78         }
     79 
     80         private MapLayerFlag activeLayers;
     81         public MapLayerFlag ActiveLayers
     82         {
     83             get => activeLayers;
     84             set
     85             {
     86                 if (activeLayers != value)
     87                 {
     88                     activeLayers = value;
     89                     if (activeTool != null)
     90                     {
     91                         activeTool.Layers = ActiveLayers;
     92                     }
     93                 }
     94             }
     95         }
     96 
     97         private ITool activeTool;
     98         private Form activeToolForm;
     99 
    100         private IGamePlugin plugin;
    101         private string filename;
    102 
    103         private readonly MRU mru;
    104 
    105         private readonly UndoRedoList<UndoRedoEventArgs> url = new UndoRedoList<UndoRedoEventArgs>();
    106 
    107         private readonly Timer steamUpdateTimer = new Timer();
    108 
    109         static MainForm()
    110         {
    111             toolTypes = ((IEnumerable<ToolType>)Enum.GetValues(typeof(ToolType))).Where(t => t != ToolType.None).ToArray();
    112         }
    113 
    114         public MainForm()
    115         {
    116             InitializeComponent();
    117 
    118             mru = new MRU("Software\\Petroglyph\\CnCRemasteredEditor", 10, fileRecentFilesMenuItem);
    119             mru.FileSelected += Mru_FileSelected;
    120 
    121             foreach (ToolStripButton toolStripButton in mainToolStrip.Items)
    122             {
    123                 toolStripButton.MouseMove += mainToolStrip_MouseMove;
    124             }
    125 
    126 #if !DEVELOPER
    127             fileExportMenuItem.Visible = false;
    128             developerToolStripMenuItem.Visible = false;
    129 #endif
    130 
    131             url.Tracked += UndoRedo_Updated;
    132             url.Undone += UndoRedo_Updated;
    133             url.Redone += UndoRedo_Updated;
    134             UpdateUndoRedo();
    135 
    136             steamUpdateTimer.Interval = 500;
    137             steamUpdateTimer.Tick += SteamUpdateTimer_Tick;
    138         }
    139 
    140         private void SteamUpdateTimer_Tick(object sender, EventArgs e)
    141         {
    142             if (SteamworksUGC.IsInit)
    143             {
    144                 SteamworksUGC.Service();
    145             }
    146         }
    147 
    148         protected override void OnLoad(EventArgs e)
    149         {
    150             base.OnLoad(e);
    151 
    152             RefreshAvailableTools();
    153             UpdateVisibleLayers();
    154 
    155             filePublishMenuItem.Visible = SteamworksUGC.IsInit;
    156 
    157             steamUpdateTimer.Start();
    158         }
    159 
    160         protected override void OnClosed(EventArgs e)
    161         {
    162             base.OnClosed(e);
    163 
    164             steamUpdateTimer.Stop();
    165             steamUpdateTimer.Dispose();
    166         }
    167 
    168         protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    169         {
    170             if (keyData == Keys.Q)
    171             {
    172                 mapToolStripButton.PerformClick();
    173                 return true;
    174             }
    175             else if (keyData == Keys.W)
    176             {
    177                 smudgeToolStripButton.PerformClick();
    178                 return true;
    179             }
    180             else if (keyData == Keys.E)
    181             {
    182                 overlayToolStripButton.PerformClick();
    183                 return true;
    184             }
    185             else if (keyData == Keys.R)
    186             {
    187                 terrainToolStripButton.PerformClick();
    188                 return true;
    189             }
    190             else if (keyData == Keys.T)
    191             {
    192                 infantryToolStripButton.PerformClick();
    193                 return true;
    194             }
    195             else if (keyData == Keys.Y)
    196             {
    197                 unitToolStripButton.PerformClick();
    198                 return true;
    199             }
    200             else if (keyData == Keys.A)
    201             {
    202                 buildingToolStripButton.PerformClick();
    203                 return true;
    204             }
    205             else if (keyData == Keys.S)
    206             {
    207                 resourcesToolStripButton.PerformClick();
    208                 return true;
    209             }
    210             else if (keyData == Keys.D)
    211             {
    212                 wallsToolStripButton.PerformClick();
    213                 return true;
    214             }
    215             else if (keyData == Keys.F)
    216             {
    217                 waypointsToolStripButton.PerformClick();
    218                 return true;
    219             }
    220             else if (keyData == Keys.G)
    221             {
    222                 cellTriggersToolStripButton.PerformClick();
    223                 return true;
    224             }
    225             else if (keyData == (Keys.Control | Keys.Z))
    226             {
    227                 if (editUndoMenuItem.Enabled)
    228                 {
    229                     editUndoMenuItem_Click(this, new EventArgs());
    230                 }
    231                 return true;
    232             }
    233             else if (keyData == (Keys.Control | Keys.Y))
    234             {
    235                 if (editRedoMenuItem.Enabled)
    236                 {
    237                     editRedoMenuItem_Click(this, new EventArgs());
    238                 }
    239                 return true;
    240             }
    241 
    242             return base.ProcessCmdKey(ref msg, keyData);
    243         }
    244 
    245         private void UpdateUndoRedo()
    246         {
    247             editUndoMenuItem.Enabled = url.CanUndo;
    248             editRedoMenuItem.Enabled = url.CanRedo;
    249         }
    250 
    251         private void UndoRedo_Updated(object sender, EventArgs e)
    252         {
    253             UpdateUndoRedo();
    254         }
    255 
    256         private void fileNewMenuItem_Click(object sender, EventArgs e)
    257         {
    258             if (!PromptSaveMap())
    259             {
    260                 return;
    261             }
    262 
    263             NewMapDialog nmd = new NewMapDialog();
    264             if (nmd.ShowDialog() == DialogResult.OK)
    265             {
    266                 if (plugin != null)
    267                 {
    268                     plugin.Map.Triggers.CollectionChanged -= Triggers_CollectionChanged;
    269                     plugin.Dispose();
    270                 }
    271                 plugin = null;
    272 
    273                 Globals.TheTilesetManager.Reset();
    274                 Globals.TheTextureManager.Reset();
    275 
    276                 if (nmd.GameType == GameType.TiberianDawn)
    277                 {
    278                     Globals.TheTeamColorManager.Reset();
    279                     Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML");
    280 
    281                     plugin = new TiberianDawn.GamePlugin();
    282                     plugin.New(nmd.TheaterName);
    283                 }
    284                 else if (nmd.GameType == GameType.RedAlert)
    285                 {
    286                     Globals.TheTeamColorManager.Reset();
    287                     Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML");
    288 
    289                     plugin = new RedAlert.GamePlugin();
    290                     plugin.New(nmd.TheaterName);
    291                 }
    292 
    293                 if (SteamworksUGC.IsInit)
    294                 {
    295                     plugin.Map.BasicSection.Author = SteamFriends.GetPersonaName();
    296                 }
    297 
    298                 plugin.Map.Triggers.CollectionChanged += Triggers_CollectionChanged;
    299                 mapPanel.MapImage = plugin.MapImage;
    300 
    301                 filename = null;
    302                 Text = "CnC TDRA Map Editor";
    303                 url.Clear();
    304 
    305                 ClearActiveTool();
    306                 RefreshAvailableTools();
    307                 RefreshActiveTool();
    308             }
    309         }
    310 
    311         private void fileOpenMenuItem_Click(object sender, EventArgs e)
    312         {
    313             if (!PromptSaveMap())
    314             {
    315                 return;
    316             }
    317 
    318             var pgmFilter =
    319 #if DEVELOPER
    320                 "|PGM files (*.pgm)|*.pgm"
    321 #else
    322                 string.Empty
    323 #endif
    324             ;
    325 
    326             OpenFileDialog ofd = new OpenFileDialog
    327             {
    328                 AutoUpgradeEnabled = false,
    329                 RestoreDirectory = true
    330             };
    331             ofd.Filter = "Tiberian Dawn files (*.ini;*.bin)|*.ini;*.bin|Red Alert files (*.mpr)|*.mpr" + pgmFilter  + "|All files (*.*)|*.*";
    332             if (plugin != null)
    333             {
    334                 switch (plugin.GameType)
    335                 {
    336                     case GameType.TiberianDawn:
    337                         ofd.InitialDirectory = TiberianDawn.Constants.SaveDirectory;
    338                         ofd.FilterIndex = 1;
    339                         break;
    340                     case GameType.RedAlert:
    341                         ofd.InitialDirectory = RedAlert.Constants.SaveDirectory;
    342                         ofd.FilterIndex = 2;
    343                         break;
    344                 }
    345             }
    346             else
    347             {
    348                 ofd.InitialDirectory = Globals.RootSaveDirectory;
    349             }
    350             if (ofd.ShowDialog() == DialogResult.OK)
    351             {
    352                 var fileInfo = new FileInfo(ofd.FileName);
    353                 if (LoadFile(fileInfo.FullName))
    354                 {
    355                     mru.Add(fileInfo);
    356                 }
    357                 else
    358                 {
    359                     mru.Remove(fileInfo);
    360                     MessageBox.Show(string.Format("Error loading {0}.", ofd.FileName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    361                 }
    362             }
    363         }
    364 
    365         private void fileSaveMenuItem_Click(object sender, EventArgs e)
    366         {
    367             if (plugin == null)
    368             {
    369                 return;
    370             }
    371 
    372             if (string.IsNullOrEmpty(filename))
    373             {
    374                 fileSaveAsMenuItem.PerformClick();
    375             }
    376             else
    377             {
    378                 var fileInfo = new FileInfo(filename);
    379                 if (SaveFile(fileInfo.FullName))
    380                 {
    381                     mru.Add(fileInfo);
    382                 }
    383                 else
    384                 {
    385                     mru.Remove(fileInfo);
    386                 }
    387             }
    388         }
    389 
    390         private void fileSaveAsMenuItem_Click(object sender, EventArgs e)
    391         {
    392             if (plugin == null)
    393             {
    394                 return;
    395             }
    396 
    397             SaveFileDialog sfd = new SaveFileDialog
    398             {
    399                 AutoUpgradeEnabled = false,
    400                 RestoreDirectory = true
    401             };
    402             var filters = new List<string>();
    403             switch (plugin.GameType)
    404             {
    405                 case GameType.TiberianDawn:
    406                     filters.Add("Tiberian Dawn files (*.ini;*.bin)|*.ini;*.bin");
    407                     sfd.InitialDirectory = TiberianDawn.Constants.SaveDirectory;
    408                     break;
    409                 case GameType.RedAlert:
    410                     filters.Add("Red Alert files (*.mpr)|*.mpr");
    411                     sfd.InitialDirectory = RedAlert.Constants.SaveDirectory;
    412                     break;
    413             }
    414             filters.Add("All files (*.*)|*.*");
    415 
    416             sfd.Filter = string.Join("|", filters);
    417             if (!string.IsNullOrEmpty(filename))
    418             {
    419                 sfd.InitialDirectory = Path.GetDirectoryName(filename);
    420                 sfd.FileName = Path.GetFileName(filename);
    421             }
    422             if (sfd.ShowDialog() == DialogResult.OK)
    423             {
    424                 var fileInfo = new FileInfo(sfd.FileName);
    425                 if (SaveFile(fileInfo.FullName))
    426                 {
    427                     mru.Add(fileInfo);
    428                 }
    429                 else
    430                 {
    431                     mru.Remove(fileInfo);
    432                 }
    433             }
    434         }
    435 
    436         private void fileExportMenuItem_Click(object sender, EventArgs e)
    437         {
    438             if (plugin == null)
    439             {
    440                 return;
    441             }
    442 
    443             SaveFileDialog sfd = new SaveFileDialog
    444             {
    445                 AutoUpgradeEnabled = false,
    446                 RestoreDirectory = true
    447             };
    448             sfd.Filter = "MEG files (*.meg)|*.meg";
    449             if (sfd.ShowDialog() == DialogResult.OK)
    450             {
    451                 plugin.Save(sfd.FileName, FileType.MEG);
    452             }
    453         }
    454 
    455         private void fileExitMenuItem_Click(object sender, EventArgs e)
    456         {
    457             Close();
    458         }
    459 
    460         private void editUndoMenuItem_Click(object sender, EventArgs e)
    461         {
    462             if (url.CanUndo)
    463             {
    464                 url.Undo(new UndoRedoEventArgs(mapPanel, plugin.Map));
    465             }
    466         }
    467 
    468         private void editRedoMenuItem_Click(object sender, EventArgs e)
    469         {
    470             if (url.CanRedo)
    471             {
    472                 url.Redo(new UndoRedoEventArgs(mapPanel, plugin.Map));
    473             }
    474         }
    475 
    476         private void settingsMapSettingsMenuItem_Click(object sender, EventArgs e)
    477         {
    478             if (plugin == null)
    479             {
    480                 return;
    481             }
    482 
    483             var basicSettings = new PropertyTracker<BasicSection>(plugin.Map.BasicSection);
    484             var briefingSettings = new PropertyTracker<BriefingSection>(plugin.Map.BriefingSection);
    485             var houseSettingsTrackers = plugin.Map.Houses.ToDictionary(h => h, h => new PropertyTracker<House>(h));
    486 
    487             MapSettingsDialog msd = new MapSettingsDialog(plugin, basicSettings, briefingSettings, houseSettingsTrackers);
    488             if (msd.ShowDialog() == DialogResult.OK)
    489             {
    490                 basicSettings.Commit();
    491                 briefingSettings.Commit();
    492                 foreach (var houseSettingsTracker in houseSettingsTrackers.Values)
    493                 {
    494                     houseSettingsTracker.Commit();
    495                 }
    496                 plugin.Dirty = true;
    497             }
    498         }
    499 
    500         private void settingsTeamTypesMenuItem_Click(object sender, EventArgs e)
    501         {
    502             if (plugin == null)
    503             {
    504                 return;
    505             }
    506 
    507             int maxTeams = 0;
    508             switch (plugin.GameType)
    509             {
    510                 case GameType.TiberianDawn:
    511                     {
    512                         maxTeams = TiberianDawn.Constants.MaxTeams;
    513                     }
    514                     break;
    515                 case GameType.RedAlert:
    516                     {
    517                         maxTeams = RedAlert.Constants.MaxTeams;
    518                     }
    519                     break;
    520             }
    521 
    522             TeamTypesDialog ttd = new TeamTypesDialog(plugin, maxTeams);
    523             if (ttd.ShowDialog() == DialogResult.OK)
    524             {
    525                 plugin.Map.TeamTypes.Clear();
    526                 plugin.Map.TeamTypes.AddRange(ttd.TeamTypes.Select(t => t.Clone()));
    527                 plugin.Dirty = true;
    528             }
    529         }
    530 
    531         private void settingsTriggersMenuItem_Click(object sender, EventArgs e)
    532         {
    533             if (plugin == null)
    534             {
    535                 return;
    536             }
    537 
    538             int maxTriggers = 0;
    539             switch (plugin.GameType)
    540             {
    541                 case GameType.TiberianDawn:
    542                     {
    543                         maxTriggers = TiberianDawn.Constants.MaxTriggers;
    544                     }
    545                     break;
    546                 case GameType.RedAlert:
    547                     {
    548                         maxTriggers = RedAlert.Constants.MaxTriggers;
    549                     }
    550                     break;
    551             }
    552 
    553             TriggersDialog td = new TriggersDialog(plugin, maxTriggers);
    554             if (td.ShowDialog() == DialogResult.OK)
    555             {
    556                 var oldTriggers =
    557                     from leftTrigger in plugin.Map.Triggers
    558                     join rightTrigger in td.Triggers
    559                     on leftTrigger.Name equals rightTrigger.Name into result
    560                     where result.Count() == 0
    561                     select leftTrigger;
    562                 var newTriggers =
    563                     from leftTrigger in td.Triggers
    564                     join rightTrigger in plugin.Map.Triggers
    565                     on leftTrigger.Name equals rightTrigger.Name into result
    566                     where result.Count() == 0
    567                     select leftTrigger;
    568                 var sameTriggers =
    569                     from leftTrigger in plugin.Map.Triggers
    570                     join rightTrigger in td.Triggers
    571                     on leftTrigger.Name equals rightTrigger.Name
    572                     select new
    573                     {
    574                         OldTrigger = leftTrigger,
    575                         NewTrigger = rightTrigger
    576                     };
    577 
    578                 foreach (var oldTrigger in oldTriggers.ToArray())
    579                 {
    580                     plugin.Map.Triggers.Remove(oldTrigger);
    581                 }
    582 
    583                 foreach (var newTrigger in newTriggers.ToArray())
    584                 {
    585                     plugin.Map.Triggers.Add(newTrigger.Clone());
    586                 }
    587 
    588                 foreach (var item in sameTriggers.ToArray())
    589                 {
    590                     plugin.Map.Triggers.Add(item.NewTrigger.Clone());
    591                     plugin.Map.Triggers.Remove(item.OldTrigger);
    592                 }
    593 
    594                 plugin.Dirty = true;
    595             }
    596         }
    597 
    598         private void Mru_FileSelected(object sender, FileInfo e)
    599         {
    600             if (!PromptSaveMap())
    601             {
    602                 return;
    603             }
    604 
    605             if (LoadFile(e.FullName))
    606             {
    607                 mru.Add(e);
    608             }
    609             else
    610             {
    611                 mru.Remove(e);
    612                 MessageBox.Show(string.Format("Error loading {0}.", e.FullName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    613             }
    614         }
    615 
    616         private void mapPanel_MouseMove(object sender, MouseEventArgs e)
    617         {
    618             if (plugin != null)
    619             {
    620                 var mapPoint = mapPanel.ClientToMap(e.Location);
    621                 var location = new Point((int)Math.Floor((double)mapPoint.X / Globals.TileWidth), (int)Math.Floor((double)mapPoint.Y / Globals.TileHeight));
    622                 if (plugin.Map.Metrics.GetCell(location, out int cell))
    623                 {
    624                     var sb = new StringBuilder();
    625                     sb.AppendFormat("X = {0}, Y = {1}, Cell = {2}", location.X, location.Y, cell);
    626 
    627                     var template = plugin.Map.Templates[cell];
    628                     var templateType = template?.Type;  
    629                     if (templateType != null)
    630                     {
    631                         sb.AppendFormat(", Template = {0} ({1})", templateType.DisplayName, template.Icon);
    632                     }
    633 
    634                     var smudge = plugin.Map.Smudge[cell];
    635                     var smudgeType = smudge?.Type;
    636                     if (smudgeType != null)
    637                     {
    638                         sb.AppendFormat(", Smudge = {0}", smudgeType.DisplayName);
    639                     }
    640 
    641                     var overlay = plugin.Map.Overlay[cell];
    642                     var overlayType = overlay?.Type;
    643                     if (overlayType != null)
    644                     {
    645                         sb.AppendFormat(", Overlay = {0}", overlayType.DisplayName);
    646                     }
    647 
    648                     var terrain = plugin.Map.Technos[location] as Terrain;
    649                     var terrainType = terrain?.Type;
    650                     if (terrainType != null)
    651                     {
    652                         sb.AppendFormat(", Terrain = {0}", terrainType.DisplayName);
    653                     }
    654 
    655                     if (plugin.Map.Technos[location] is InfantryGroup infantryGroup)
    656                     {
    657                         var subPixel = new Point(
    658                             (mapPoint.X * Globals.PixelWidth / Globals.TileWidth) % Globals.PixelWidth,
    659                             (mapPoint.Y * Globals.PixelHeight / Globals.TileHeight) % Globals.PixelHeight
    660                         );
    661 
    662                         var i = InfantryGroup.ClosestStoppingTypes(subPixel).Cast<int>().First();
    663                         if (infantryGroup.Infantry[i] != null)
    664                         {
    665                             sb.AppendFormat(", Infantry = {0}", infantryGroup.Infantry[i].Type.DisplayName);
    666                         }
    667                     }
    668 
    669                     var unit = plugin.Map.Technos[location] as Unit;
    670                     var unitType = unit?.Type;
    671                     if (unitType != null)
    672                     {
    673                         sb.AppendFormat(", Unit = {0}", unitType.DisplayName);
    674                     }
    675 
    676                     var building = plugin.Map.Technos[location] as Building;
    677                     var buildingType = building?.Type;
    678                     if (buildingType != null)
    679                     {
    680                         sb.AppendFormat(", Building = {0}", buildingType.DisplayName);
    681                     }
    682 
    683                     cellStatusLabel.Text = sb.ToString();
    684                 }
    685                 else
    686                 {
    687                     cellStatusLabel.Text = string.Empty;
    688                 }
    689             }
    690         }
    691 
    692         private bool LoadFile(string loadFilename)
    693         {
    694             FileType fileType = FileType.None;
    695             switch (Path.GetExtension(loadFilename).ToLower())
    696             {
    697                 case ".ini":
    698                 case ".mpr":
    699                     fileType = FileType.INI;
    700                     break;
    701                 case ".bin":
    702                     fileType = FileType.BIN;
    703                     break;
    704 #if DEVELOPER
    705                 case ".pgm":
    706                     fileType = FileType.PGM;
    707                     break;
    708 #endif
    709             }
    710 
    711             if (fileType == FileType.None)
    712             {
    713                 return false;
    714             }
    715 
    716             GameType gameType = GameType.None;
    717             switch (fileType)
    718             {
    719                 case FileType.INI:
    720                     {
    721                         var ini = new INI();
    722                         try
    723                         {
    724                             using (var reader = new StreamReader(loadFilename))
    725                             {
    726                                 ini.Parse(reader);
    727                             }
    728                         }
    729                         catch (FileNotFoundException)
    730                         {
    731                             return false;
    732                         }
    733                         gameType = File.Exists(Path.ChangeExtension(loadFilename, ".bin")) ? GameType.TiberianDawn : GameType.RedAlert;
    734                     }
    735                     break;
    736                 case FileType.BIN:
    737                     gameType = GameType.TiberianDawn;
    738                     break;
    739 #if DEVELOPER
    740                 case FileType.PGM:
    741                     {
    742                         try
    743                         {
    744                             using (var megafile = new Megafile(loadFilename))
    745                             {
    746                                 if (megafile.Any(f => Path.GetExtension(f).ToLower() == ".mpr"))
    747                                 {
    748                                     gameType = GameType.RedAlert;
    749                                 }
    750                                 else
    751                                 {
    752                                     gameType = GameType.TiberianDawn;
    753                                 }
    754                             }
    755                         }
    756                         catch (FileNotFoundException)
    757                         {
    758                             return false;
    759                         }
    760                     }
    761                     break;
    762 #endif
    763             }
    764 
    765             if (gameType == GameType.None)
    766             {
    767                 return false;
    768             }
    769 
    770             if (plugin != null)
    771             {
    772                 plugin.Map.Triggers.CollectionChanged -= Triggers_CollectionChanged;
    773                 plugin.Dispose();
    774             }
    775             plugin = null;
    776 
    777             Globals.TheTilesetManager.Reset();
    778             Globals.TheTextureManager.Reset();
    779 
    780             switch (gameType)
    781             {
    782                 case GameType.TiberianDawn:
    783                     {
    784                         Globals.TheTeamColorManager.Reset();
    785                         Globals.TheTeamColorManager.Load(@"DATA\XML\CNCTDTEAMCOLORS.XML");
    786                         plugin = new TiberianDawn.GamePlugin();
    787                     }
    788                     break;
    789                 case GameType.RedAlert:
    790                     {
    791                         Globals.TheTeamColorManager.Reset();
    792                         Globals.TheTeamColorManager.Load(@"DATA\XML\CNCRATEAMCOLORS.XML");
    793                         plugin = new RedAlert.GamePlugin();
    794                     }
    795                     break;
    796             }
    797 
    798             try
    799             {
    800                 var errors = plugin.Load(loadFilename, fileType).ToArray();
    801                 if (errors.Length > 0)
    802                 {
    803                     ErrorMessageBox errorMessageBox = new ErrorMessageBox { Errors = errors };
    804                     errorMessageBox.ShowDialog();
    805                 }
    806             }
    807             catch (Exception)
    808             {
    809 #if DEVELOPER
    810                 throw;
    811 #else
    812                 return false;
    813 #endif
    814             }
    815 
    816             plugin.Map.Triggers.CollectionChanged += Triggers_CollectionChanged;
    817             mapPanel.MapImage = plugin.MapImage;
    818 
    819             plugin.Dirty = false;
    820             filename = loadFilename;
    821             Text = string.Format("CnC TDRA Map Editor - {0}", filename);
    822 
    823             url.Clear();
    824 
    825             ClearActiveTool();
    826             RefreshAvailableTools();
    827             RefreshActiveTool();
    828 
    829             return true;
    830         }
    831 
    832         private bool SaveFile(string saveFilename)
    833         {
    834             FileType fileType = FileType.None;
    835             switch (Path.GetExtension(saveFilename).ToLower())
    836             {
    837                 case ".ini":
    838                 case ".mpr":
    839                     fileType = FileType.INI;
    840                     break;
    841                 case ".bin":
    842                     fileType = FileType.BIN;
    843                     break;
    844             }
    845 
    846             if (fileType == FileType.None)
    847             {
    848                 return false;
    849             }
    850 
    851             if (string.IsNullOrEmpty(plugin.Map.SteamSection.Title))
    852             {
    853                 plugin.Map.SteamSection.Title = plugin.Map.BasicSection.Name;
    854             }
    855 
    856             if (!plugin.Save(saveFilename, fileType))
    857             {
    858                 return false;
    859             }
    860 
    861             if (new FileInfo(saveFilename).Length > Globals.MaxMapSize)
    862             {
    863                 MessageBox.Show(string.Format("Map file exceeds the maximum size of {0} bytes.", Globals.MaxMapSize), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    864             }
    865 
    866             plugin.Dirty = false;
    867             filename = saveFilename;
    868             Text = string.Format("CnC TDRA Map Editor - {0}", filename);
    869 
    870             return true;
    871         }
    872 
    873         private void RefreshAvailableTools()
    874         {
    875             availableToolTypes = ToolType.None;
    876             if (plugin != null)
    877             {
    878                 availableToolTypes |= ToolType.Waypoint;
    879 
    880                 if (plugin.Map.TemplateTypes.Any()) availableToolTypes |= ToolType.Map;
    881                 if (plugin.Map.SmudgeTypes.Any()) availableToolTypes |= ToolType.Smudge;
    882                 if (plugin.Map.OverlayTypes.Any(t => t.IsPlaceable && ((t.Theaters == null) || t.Theaters.Contains(plugin.Map.Theater)))) availableToolTypes |= ToolType.Overlay;
    883                 if (plugin.Map.TerrainTypes.Any(t => t.Theaters.Contains(plugin.Map.Theater))) availableToolTypes |= ToolType.Terrain;
    884                 if (plugin.Map.InfantryTypes.Any()) availableToolTypes |= ToolType.Infantry;
    885                 if (plugin.Map.UnitTypes.Any()) availableToolTypes |= ToolType.Unit;
    886                 if (plugin.Map.BuildingTypes.Any()) availableToolTypes |= ToolType.Building;
    887                 if (plugin.Map.OverlayTypes.Any(t => t.IsResource)) availableToolTypes |= ToolType.Resources;
    888                 if (plugin.Map.OverlayTypes.Any(t => t.IsWall)) availableToolTypes |= ToolType.Wall;
    889                 if (plugin.Map.Triggers.Any()) availableToolTypes |= ToolType.CellTrigger;
    890             }
    891 
    892             mapToolStripButton.Enabled = (availableToolTypes & ToolType.Map) != ToolType.None;
    893             smudgeToolStripButton.Enabled = (availableToolTypes & ToolType.Smudge) != ToolType.None;
    894             overlayToolStripButton.Enabled = (availableToolTypes & ToolType.Overlay) != ToolType.None;
    895             terrainToolStripButton.Enabled = (availableToolTypes & ToolType.Terrain) != ToolType.None;
    896             infantryToolStripButton.Enabled = (availableToolTypes & ToolType.Infantry) != ToolType.None;
    897             unitToolStripButton.Enabled = (availableToolTypes & ToolType.Unit) != ToolType.None;
    898             buildingToolStripButton.Enabled = (availableToolTypes & ToolType.Building) != ToolType.None;
    899             resourcesToolStripButton.Enabled = (availableToolTypes & ToolType.Resources) != ToolType.None;
    900             wallsToolStripButton.Enabled = (availableToolTypes & ToolType.Wall) != ToolType.None;
    901             waypointsToolStripButton.Enabled = (availableToolTypes & ToolType.Waypoint) != ToolType.None;
    902             cellTriggersToolStripButton.Enabled = (availableToolTypes & ToolType.CellTrigger) != ToolType.None;
    903 
    904             ActiveToolType = activeToolType;
    905         }
    906 
    907         private void ClearActiveTool()
    908         {
    909             activeTool?.Dispose();
    910             activeTool = null;
    911 
    912             if (activeToolForm != null)
    913             {
    914                 activeToolForm.ResizeEnd -= ActiveToolForm_ResizeEnd;
    915                 activeToolForm.Close();
    916                 activeToolForm = null;
    917             }
    918 
    919             toolStatusLabel.Text = string.Empty;
    920         }
    921 
    922         private void RefreshActiveTool()
    923         {
    924             if (plugin == null)
    925             {
    926                 return;
    927             }
    928 
    929             if (activeTool == null)
    930             {
    931                 activeLayers = MapLayerFlag.None;
    932             }
    933 
    934             ClearActiveTool();
    935 
    936             switch (ActiveToolType)
    937             {
    938                 case ToolType.Map:
    939                     {
    940                         TemplateToolDialog toolDialog = new TemplateToolDialog();
    941 
    942                         activeTool = new TemplateTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.TemplateTypeListView, toolDialog.TemplateTypeMapPanel, mouseToolTip, plugin, url);
    943                         activeToolForm = toolDialog;
    944                         activeToolForm.Show(this);
    945                     } break;
    946                 case ToolType.Smudge:
    947                     {
    948                         GenericToolDialog toolDialog = new GenericToolDialog
    949                         {
    950                             Text = "Smudge"
    951                         };
    952 
    953                         toolDialog.GenericTypeComboBox.Types = plugin.Map.SmudgeTypes.Where(t => (t.Flag & SmudgeTypeFlag.Bib) == SmudgeTypeFlag.None).OrderBy(t => t.Name);
    954 
    955                         activeTool = new SmudgeTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.GenericTypeComboBox, toolDialog.GenericTypeMapPanel, plugin, url);
    956                         activeToolForm = toolDialog;
    957                         activeToolForm.Show(this);
    958                     }
    959                     break;
    960                 case ToolType.Overlay:
    961                     {
    962                         GenericToolDialog toolDialog = new GenericToolDialog
    963                         {
    964                             Text = "Overlay"
    965                         };
    966 
    967                         toolDialog.GenericTypeComboBox.Types = plugin.Map.OverlayTypes.Where(t => t.IsPlaceable && ((t.Theaters == null) || t.Theaters.Contains(plugin.Map.Theater))).OrderBy(t => t.Name);
    968 
    969                         activeTool = new OverlaysTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.GenericTypeComboBox, toolDialog.GenericTypeMapPanel, plugin, url);
    970                         activeToolForm = toolDialog;
    971                         activeToolForm.Show(this);
    972                     }
    973                     break;
    974                 case ToolType.Resources:
    975                     {
    976                         ResourcesToolDialog toolDialog = new ResourcesToolDialog();
    977 
    978                         activeTool = new ResourcesTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.TotalResourcesLbl, toolDialog.ResourceBrushSizeNud, toolDialog.GemsCheckBox, plugin, url);
    979                         activeToolForm = toolDialog;
    980                         activeToolForm.Show(this);
    981                     }
    982                     break;
    983                 case ToolType.Terrain:
    984                     {
    985                         TerrainToolDialog toolDialog = new TerrainToolDialog(plugin);
    986 
    987                         toolDialog.TerrainTypeComboBox.Types = plugin.Map.TerrainTypes.Where(t => t.Theaters.Contains(plugin.Map.Theater)).OrderBy(t => t.Name);
    988 
    989                         activeTool = new TerrainTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.TerrainTypeComboBox, toolDialog.TerrainTypeMapPanel, toolDialog.TerrainProperties, plugin, url);
    990                         activeToolForm = toolDialog;
    991                         activeToolForm.Show(this);
    992                     }
    993                     break;
    994                 case ToolType.Infantry:
    995                     {
    996                         ObjectToolDialog toolDialog = new ObjectToolDialog(plugin)
    997                         {
    998                             Text = "Infantry"
    999                         };
   1000 
   1001                         toolDialog.ObjectTypeComboBox.Types = plugin.Map.InfantryTypes.OrderBy(t => t.Name);
   1002 
   1003                         activeTool = new InfantryTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.ObjectTypeComboBox, toolDialog.ObjectTypeMapPanel, toolDialog.ObjectProperties, plugin, url);
   1004                         activeToolForm = toolDialog;
   1005                         activeToolForm.Show(this);
   1006                     }
   1007                     break;
   1008                 case ToolType.Unit:
   1009                     {
   1010                         ObjectToolDialog toolDialog = new ObjectToolDialog(plugin)
   1011                         {
   1012                             Text = "Units"
   1013                         };
   1014 
   1015                         toolDialog.ObjectTypeComboBox.Types = plugin.Map.UnitTypes
   1016                             .Where(t => !t.IsFixedWing)
   1017                             .OrderBy(t => t.Name);
   1018 
   1019                         activeTool = new UnitTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.ObjectTypeComboBox, toolDialog.ObjectTypeMapPanel, toolDialog.ObjectProperties, plugin, url);
   1020                         activeToolForm = toolDialog;
   1021                         activeToolForm.Show(this);
   1022                     }
   1023                     break;
   1024                 case ToolType.Building:
   1025                     {
   1026                         ObjectToolDialog toolDialog = new ObjectToolDialog(plugin)
   1027                         {
   1028                             Text = "Structures"
   1029                         };
   1030 
   1031                         toolDialog.ObjectTypeComboBox.Types = plugin.Map.BuildingTypes
   1032                             .Where(t => (t.Theaters == null) || t.Theaters.Contains(plugin.Map.Theater))
   1033                             .OrderBy(t => t.IsFake)
   1034                             .ThenBy(t => t.Name);
   1035 
   1036                         activeTool = new BuildingTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.ObjectTypeComboBox, toolDialog.ObjectTypeMapPanel, toolDialog.ObjectProperties, plugin, url);
   1037                         activeToolForm = toolDialog;
   1038                         activeToolForm.Show(this);
   1039                     }
   1040                     break;
   1041                 case ToolType.Wall:
   1042                     {
   1043                         GenericToolDialog toolDialog = new GenericToolDialog
   1044                         {
   1045                             Text = "Walls"
   1046                         };
   1047 
   1048                         toolDialog.GenericTypeComboBox.Types = plugin.Map.OverlayTypes.Where(t => t.IsWall).OrderBy(t => t.Name);
   1049 
   1050                         activeTool = new WallsTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.GenericTypeComboBox, toolDialog.GenericTypeMapPanel, plugin, url);
   1051                         activeToolForm = toolDialog;
   1052                         activeToolForm.Show(this);
   1053                     }
   1054                     break;
   1055                 case ToolType.Waypoint:
   1056                     {
   1057                         WaypointsToolDialog toolDialog = new WaypointsToolDialog();
   1058 
   1059                         toolDialog.WaypointCombo.DataSource = plugin.Map.Waypoints.Select(w => w.Name).ToArray();
   1060 
   1061                         activeTool = new WaypointsTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.WaypointCombo, plugin, url);
   1062                         activeToolForm = toolDialog;
   1063                         activeToolForm.Show(this);
   1064                     }
   1065                     break;
   1066                 case ToolType.CellTrigger:
   1067                     {
   1068                         CellTriggersToolDialog toolDialog = new CellTriggersToolDialog();
   1069 
   1070                         toolDialog.TriggerCombo.DataSource = plugin.Map.Triggers.Select(t => t.Name).ToArray();
   1071 
   1072                         activeTool = new CellTriggersTool(mapPanel, ActiveLayers, toolStatusLabel, toolDialog.TriggerCombo, plugin, url);
   1073                         activeToolForm = toolDialog;
   1074                         activeToolForm.Show(this);
   1075                     }
   1076                     break;
   1077             }
   1078 
   1079             if (activeToolForm != null)
   1080             {
   1081                 activeToolForm.ResizeEnd += ActiveToolForm_ResizeEnd;
   1082                 clampActiveToolForm();
   1083             }
   1084 
   1085             switch (plugin.GameType)
   1086             {
   1087                 case GameType.TiberianDawn:
   1088                     mapPanel.MaxZoom = 8;
   1089                     mapPanel.ZoomStep = 1;
   1090                     break;
   1091                 case GameType.RedAlert:
   1092                     mapPanel.MaxZoom = 16;
   1093                     mapPanel.ZoomStep = 2;
   1094                     break;
   1095             }
   1096 
   1097             mapToolStripButton.Checked = ActiveToolType == ToolType.Map;
   1098             smudgeToolStripButton.Checked = ActiveToolType == ToolType.Smudge;
   1099             overlayToolStripButton.Checked = ActiveToolType == ToolType.Overlay;
   1100             terrainToolStripButton.Checked = ActiveToolType == ToolType.Terrain;
   1101             infantryToolStripButton.Checked = ActiveToolType == ToolType.Infantry;
   1102             unitToolStripButton.Checked = ActiveToolType == ToolType.Unit;
   1103             buildingToolStripButton.Checked = ActiveToolType == ToolType.Building;
   1104             resourcesToolStripButton.Checked = ActiveToolType == ToolType.Resources;
   1105             wallsToolStripButton.Checked = ActiveToolType == ToolType.Wall;
   1106             waypointsToolStripButton.Checked = ActiveToolType == ToolType.Waypoint;
   1107             cellTriggersToolStripButton.Checked = ActiveToolType == ToolType.CellTrigger;
   1108 
   1109             Focus();
   1110 
   1111             UpdateVisibleLayers();
   1112             mapPanel.Invalidate();
   1113         }
   1114 
   1115         private void clampActiveToolForm()
   1116         {
   1117             if (activeToolForm == null)
   1118             {
   1119                 return;
   1120             }
   1121 
   1122             Rectangle bounds = activeToolForm.DesktopBounds;
   1123             Rectangle workingArea = Screen.FromControl(this).WorkingArea;
   1124             if (bounds.Right > workingArea.Right)
   1125             {
   1126                 bounds.X = workingArea.Right - bounds.Width;
   1127             }
   1128             if (bounds.X < workingArea.Left)
   1129             {
   1130                 bounds.X = workingArea.Left;
   1131             }
   1132             if (bounds.Bottom > workingArea.Bottom)
   1133             {
   1134                 bounds.Y = workingArea.Bottom - bounds.Height;
   1135             }
   1136             if (bounds.Y < workingArea.Top)
   1137             {
   1138                 bounds.Y = workingArea.Top;
   1139             }
   1140             activeToolForm.DesktopBounds = bounds;
   1141         }
   1142 
   1143         private void ActiveToolForm_ResizeEnd(object sender, EventArgs e)
   1144         {
   1145             clampActiveToolForm();
   1146         }
   1147 
   1148         private void Triggers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
   1149         {
   1150             RefreshAvailableTools();
   1151         }
   1152 
   1153         private void mainToolStripButton_Click(object sender, EventArgs e)
   1154         {
   1155             if (plugin == null)
   1156             {
   1157                 return;
   1158             }
   1159 
   1160             if (sender == mapToolStripButton)
   1161             {
   1162                 ActiveToolType = ToolType.Map;
   1163             }
   1164             else if (sender == smudgeToolStripButton)
   1165             {
   1166                 ActiveToolType = ToolType.Smudge;
   1167             }
   1168             else if (sender == overlayToolStripButton)
   1169             {
   1170                 ActiveToolType = ToolType.Overlay;
   1171             }
   1172             else if (sender == terrainToolStripButton)
   1173             {
   1174                 ActiveToolType = ToolType.Terrain;
   1175             }
   1176             else if (sender == infantryToolStripButton)
   1177             {
   1178                 ActiveToolType = ToolType.Infantry;
   1179             }
   1180             else if (sender == unitToolStripButton)
   1181             {
   1182                 ActiveToolType = ToolType.Unit;
   1183             }
   1184             else if (sender == buildingToolStripButton)
   1185             {
   1186                 ActiveToolType = ToolType.Building;
   1187             }
   1188             else if (sender == resourcesToolStripButton)
   1189             {
   1190                 ActiveToolType = ToolType.Resources;
   1191             }
   1192             else if (sender == wallsToolStripButton)
   1193             {
   1194                 ActiveToolType = ToolType.Wall;
   1195             }
   1196             else if (sender == waypointsToolStripButton)
   1197             {
   1198                 ActiveToolType = ToolType.Waypoint;
   1199             }
   1200             else if (sender == cellTriggersToolStripButton)
   1201             {
   1202                 ActiveToolType = ToolType.CellTrigger;
   1203             }
   1204         }
   1205 
   1206         private void UpdateVisibleLayers()
   1207         {
   1208             MapLayerFlag layers = MapLayerFlag.All;
   1209             if (!viewLayersBoundariesMenuItem.Checked)
   1210             {
   1211                 layers &= ~MapLayerFlag.Boundaries;
   1212             }
   1213             if (!viewLayersOverlayMenuItem.Checked)
   1214             {
   1215                 layers &= ~MapLayerFlag.OverlayAll;
   1216             }
   1217             if (!viewLayersTerrainMenuItem.Checked)
   1218             {
   1219                 layers &= ~MapLayerFlag.Terrain;
   1220             }
   1221             if (!viewLayersWaypointsMenuItem.Checked)
   1222             {
   1223                 layers &= ~MapLayerFlag.Waypoints;
   1224             }
   1225             if (!viewLayersCellTriggersMenuItem.Checked)
   1226             {
   1227                 layers &= ~MapLayerFlag.CellTriggers;
   1228             }
   1229             if (!viewLayersObjectTriggersMenuItem.Checked)
   1230             {
   1231                 layers &= ~MapLayerFlag.TechnoTriggers;
   1232             }
   1233             ActiveLayers = layers;
   1234         }
   1235 
   1236         private void viewLayersMenuItem_CheckedChanged(object sender, EventArgs e)
   1237         {
   1238             UpdateVisibleLayers();
   1239         }
   1240 
   1241         private void toolTabControl_Selected(object sender, TabControlEventArgs e)
   1242         {
   1243             if (plugin == null)
   1244             {
   1245                 return;
   1246             }
   1247         }
   1248 
   1249         private void developerGenerateMapPreviewMenuItem_Click(object sender, EventArgs e)
   1250         {
   1251 #if DEVELOPER
   1252             if ((plugin == null) || string.IsNullOrEmpty(filename))
   1253             {
   1254                 return;
   1255             }
   1256 
   1257             plugin.Map.GenerateMapPreview().Save(Path.ChangeExtension(filename, ".tga"));
   1258 #endif
   1259         }
   1260 
   1261         private void developerGoToINIMenuItem_Click(object sender, EventArgs e)
   1262         {
   1263 #if DEVELOPER
   1264             if ((plugin == null) || string.IsNullOrEmpty(filename))
   1265             {
   1266                 return;
   1267             }
   1268 
   1269             var path = Path.ChangeExtension(filename, ".mpr");
   1270             if (!File.Exists(path))
   1271             {
   1272                 path = Path.ChangeExtension(filename, ".ini");
   1273             }
   1274 
   1275             try
   1276             {
   1277                 Process.Start(path);
   1278             }
   1279             catch (Win32Exception)
   1280             {
   1281                 Process.Start("notepad.exe", path);
   1282             }
   1283             catch (Exception) { }
   1284 #endif
   1285         }
   1286 
   1287         private void developerGenerateMapPreviewDirectoryMenuItem_Click(object sender, EventArgs e)
   1288         {
   1289 #if DEVELOPER
   1290             FolderBrowserDialog fbd = new FolderBrowserDialog
   1291             {
   1292                 ShowNewFolderButton = false
   1293             };
   1294             if (fbd.ShowDialog() == DialogResult.OK)
   1295             {
   1296                 var extensions = new string[] { ".ini", ".mpr" };
   1297                 foreach (var file in Directory.EnumerateFiles(fbd.SelectedPath).Where(file => extensions.Contains(Path.GetExtension(file).ToLower())))
   1298                 {
   1299                     GameType gameType = GameType.None;
   1300 
   1301                     var ini = new INI();
   1302                     using (var reader = new StreamReader(file))
   1303                     {
   1304                         ini.Parse(reader);
   1305                     }
   1306                     gameType = ini.Sections.Contains("MapPack") ? GameType.RedAlert : GameType.TiberianDawn;
   1307 
   1308                     if (gameType == GameType.None)
   1309                     {
   1310                         continue;
   1311                     }
   1312 
   1313                     IGamePlugin plugin = null;
   1314                     switch (gameType)
   1315                     {
   1316                         case GameType.TiberianDawn:
   1317                             {
   1318                                 plugin = new TiberianDawn.GamePlugin(false);
   1319                             }
   1320                             break;
   1321                         case GameType.RedAlert:
   1322                             {
   1323                                 plugin = new RedAlert.GamePlugin(false);
   1324                             }
   1325                             break;
   1326                     }
   1327 
   1328                     plugin.Load(file, FileType.INI);
   1329                     plugin.Map.GenerateMapPreview().Save(Path.ChangeExtension(file, ".tga"));
   1330                     plugin.Dispose();
   1331                 }
   1332             }
   1333 #endif
   1334         }
   1335 
   1336         private void developerDebugShowOverlapCellsMenuItem_CheckedChanged(object sender, EventArgs e)
   1337         {
   1338 #if DEVELOPER
   1339             Globals.Developer.ShowOverlapCells = developerDebugShowOverlapCellsMenuItem.Checked;
   1340 #endif
   1341         }
   1342 
   1343         private void filePublishMenuItem_Click(object sender, EventArgs e)
   1344         {
   1345             if (plugin == null)
   1346             {
   1347                 return;
   1348             }
   1349 
   1350             if (!PromptSaveMap())
   1351             {
   1352                 return;
   1353             }
   1354 
   1355             if (plugin.Dirty)
   1356             {
   1357                 MessageBox.Show("Map must be saved before publishing.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
   1358                 return;
   1359             }
   1360 
   1361             if (new FileInfo(filename).Length > Globals.MaxMapSize)
   1362             {
   1363                 return;
   1364             }
   1365 
   1366             using (var sd = new SteamDialog(plugin))
   1367             {
   1368                 sd.ShowDialog();
   1369             }
   1370 
   1371             fileSaveMenuItem.PerformClick();
   1372         }
   1373 
   1374         private void mainToolStrip_MouseMove(object sender, MouseEventArgs e)
   1375         {
   1376             mainToolStrip.Focus();
   1377         }
   1378 
   1379         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
   1380         {
   1381             e.Cancel = !PromptSaveMap();
   1382         }
   1383 
   1384         private bool PromptSaveMap()
   1385         {
   1386             bool cancel = false;
   1387             if (plugin?.Dirty ?? false)
   1388             {
   1389                 var message = string.IsNullOrEmpty(filename) ? "Save new map?" : string.Format("Save map '{0}'?", filename);
   1390                 var result = MessageBox.Show(message, "Save", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
   1391                 switch (result)
   1392                 {
   1393                     case DialogResult.Yes:
   1394                         {
   1395                             if (string.IsNullOrEmpty(filename))
   1396                             {
   1397                                 fileSaveAsMenuItem.PerformClick();
   1398                             }
   1399                             else
   1400                             {
   1401                                 fileSaveMenuItem.PerformClick();
   1402                             }
   1403                         }
   1404                         break;
   1405                     case DialogResult.No:
   1406                         break;
   1407                     case DialogResult.Cancel:
   1408                         cancel = true;
   1409                         break;
   1410                 }
   1411             }
   1412             return !cancel;
   1413         }
   1414     }
   1415 }