CnC_Remastered_Collection

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

TerrainTool.cs (15892B)


      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.Controls;
     16 using MobiusEditor.Event;
     17 using MobiusEditor.Interface;
     18 using MobiusEditor.Model;
     19 using MobiusEditor.Utility;
     20 using MobiusEditor.Widgets;
     21 using System;
     22 using System.ComponentModel;
     23 using System.Drawing;
     24 using System.Linq;
     25 using System.Windows.Forms;
     26 
     27 namespace MobiusEditor.Tools
     28 {
     29     public class TerrainTool : ViewTool
     30     {
     31         private readonly TypeComboBox terrainTypeComboBox;
     32         private readonly MapPanel terrainTypeMapPanel;
     33         private readonly TerrainProperties terrainProperties;
     34 
     35         private Map previewMap;
     36         protected override Map RenderMap => previewMap;
     37 
     38         private bool placementMode;
     39 
     40         private readonly Terrain mockTerrain;
     41 
     42         private Terrain selectedTerrain;
     43         private Point selectedTerrainPivot;
     44 
     45         private TerrainType selectedTerrainType;
     46         private TerrainPropertiesPopup selectedTerrainProperties;
     47         private TerrainType SelectedTerrainType
     48         {
     49             get => selectedTerrainType;
     50             set
     51             {
     52                 if (selectedTerrainType != value)
     53                 {
     54                     if (placementMode && (selectedTerrainType != null))
     55                     {
     56                         mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedTerrainType.OverlapBounds.Size));
     57                     }
     58 
     59                     selectedTerrainType = value;
     60                     terrainTypeComboBox.SelectedValue = selectedTerrainType;
     61 
     62                     if (placementMode && (selectedTerrainType != null))
     63                     {
     64                         mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedTerrainType.OverlapBounds.Size));
     65                     }
     66 
     67                     mockTerrain.Type = selectedTerrainType;
     68                     mockTerrain.Icon = selectedTerrainType.IsTransformable ? 22 : 0;
     69 
     70                     RefreshMapPanel();
     71                 }
     72             }
     73         }
     74 
     75         public TerrainTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, TypeComboBox terrainTypeComboBox, MapPanel terrainTypeMapPanel, TerrainProperties terrainProperties, IGamePlugin plugin, UndoRedoList<UndoRedoEventArgs> url)
     76             : base(mapPanel, layers, statusLbl, plugin, url)
     77         {
     78             previewMap = map;
     79 
     80             mockTerrain = new Terrain();
     81             mockTerrain.PropertyChanged += MockTerrain_PropertyChanged;
     82 
     83             this.mapPanel.MouseDown += MapPanel_MouseDown;
     84             this.mapPanel.MouseMove += MapPanel_MouseMove;
     85             this.mapPanel.MouseUp += MapPanel_MouseUp;
     86             this.mapPanel.MouseDoubleClick += MapPanel_MouseDoubleClick;
     87             (this.mapPanel as Control).KeyDown += TerrainTool_KeyDown;
     88             (this.mapPanel as Control).KeyUp += TerrainTool_KeyUp;
     89 
     90             this.terrainTypeComboBox = terrainTypeComboBox;
     91             this.terrainTypeComboBox.SelectedIndexChanged += TerrainTypeCombo_SelectedIndexChanged;
     92 
     93             this.terrainTypeMapPanel = terrainTypeMapPanel;
     94             this.terrainTypeMapPanel.BackColor = Color.White;
     95             this.terrainTypeMapPanel.MaxZoom = 1;
     96 
     97             this.terrainProperties = terrainProperties;
     98             this.terrainProperties.Terrain = mockTerrain;
     99             this.terrainProperties.Visible = plugin.GameType == GameType.TiberianDawn;
    100 
    101             navigationWidget.MouseCellChanged += MouseoverWidget_MouseCellChanged;
    102 
    103             SelectedTerrainType = terrainTypeComboBox.Types.First() as TerrainType;
    104 
    105             UpdateStatus();
    106         }
    107 
    108         private void MapPanel_MouseDoubleClick(object sender, MouseEventArgs e)
    109         {
    110             if (Control.ModifierKeys != Keys.None)
    111             {
    112                 return;
    113             }
    114 
    115             if (map.Metrics.GetCell(navigationWidget.MouseCell, out int cell))
    116             {
    117                 if (map.Technos[cell] is Terrain terrain)
    118                 {
    119                     selectedTerrain = null;
    120 
    121                     selectedTerrainProperties?.Close();
    122                     selectedTerrainProperties = new TerrainPropertiesPopup(terrainProperties.Plugin, terrain);
    123                     selectedTerrainProperties.Closed += (cs, ce) =>
    124                     {
    125                         navigationWidget.Refresh();
    126                     };
    127 
    128                     selectedTerrainProperties.Show(mapPanel, mapPanel.PointToClient(Control.MousePosition));
    129 
    130                     UpdateStatus();
    131                 }
    132             }
    133         }
    134 
    135         private void MockTerrain_PropertyChanged(object sender, PropertyChangedEventArgs e)
    136         {
    137             RefreshMapPanel();
    138         }
    139 
    140         private void TerrainTypeCombo_SelectedIndexChanged(object sender, EventArgs e)
    141         {
    142             SelectedTerrainType = terrainTypeComboBox.SelectedValue as TerrainType;
    143         }
    144 
    145         private void TerrainTool_KeyDown(object sender, KeyEventArgs e)
    146         {
    147             if (e.KeyCode == Keys.ShiftKey)
    148             {
    149                 EnterPlacementMode();
    150             }
    151         }
    152 
    153         private void TerrainTool_KeyUp(object sender, KeyEventArgs e)
    154         {
    155             if (e.KeyCode == Keys.ShiftKey)
    156             {
    157                 ExitPlacementMode();
    158             }
    159         }
    160 
    161         private void MapPanel_MouseDown(object sender, MouseEventArgs e)
    162         {
    163             if (placementMode)
    164             {
    165                 if (e.Button == MouseButtons.Left)
    166                 {
    167                     AddTerrain(navigationWidget.MouseCell);
    168                 }
    169                 else if (e.Button == MouseButtons.Right)
    170                 {
    171                     RemoveTerrain(navigationWidget.MouseCell);
    172                 }
    173             }
    174             else if (e.Button == MouseButtons.Left)
    175             {
    176                 SelectTerrain(navigationWidget.MouseCell);
    177             }
    178             else if (e.Button == MouseButtons.Right)
    179             {
    180                 PickTerrain(navigationWidget.MouseCell);
    181             }
    182         }
    183 
    184         private void MapPanel_MouseUp(object sender, MouseEventArgs e)
    185         {
    186             if (selectedTerrain != null)
    187             {
    188                 selectedTerrain = null;
    189                 selectedTerrainPivot = Point.Empty;
    190 
    191                 UpdateStatus();
    192             }
    193         }
    194 
    195         private void MapPanel_MouseMove(object sender, MouseEventArgs e)
    196         {
    197             if (!placementMode && (Control.ModifierKeys == Keys.Shift))
    198             {
    199                 EnterPlacementMode();
    200             }
    201             else if (placementMode && (Control.ModifierKeys == Keys.None))
    202             {
    203                 ExitPlacementMode();
    204             }
    205         }
    206 
    207         private void MouseoverWidget_MouseCellChanged(object sender, MouseCellChangedEventArgs e)
    208         {
    209             if (placementMode)
    210             {
    211                 if (SelectedTerrainType != null)
    212                 {
    213                     mapPanel.Invalidate(map, new Rectangle(e.OldCell, SelectedTerrainType.OverlapBounds.Size));
    214                     mapPanel.Invalidate(map, new Rectangle(e.NewCell, SelectedTerrainType.OverlapBounds.Size));
    215                 }
    216             }
    217             else if (selectedTerrain != null)
    218             {
    219                 var oldLocation = map.Technos[selectedTerrain].Value;
    220                 var newLocation = new Point(Math.Max(0, e.NewCell.X - selectedTerrainPivot.X), Math.Max(0, e.NewCell.Y - selectedTerrainPivot.Y));
    221                 mapPanel.Invalidate(map, selectedTerrain);
    222                 map.Technos.Remove(selectedTerrain);
    223                 if (map.Technos.Add(newLocation, selectedTerrain))
    224                 {
    225                     mapPanel.Invalidate(map, selectedTerrain);
    226                 }
    227                 else
    228                 {
    229                     map.Technos.Add(oldLocation, selectedTerrain);
    230                 }
    231             }
    232         }
    233 
    234         private void AddTerrain(Point location)
    235         {
    236             if (!map.Metrics.Contains(location))
    237             {
    238                 return;
    239             }
    240 
    241             if (SelectedTerrainType != null)
    242             {
    243                 var terrain = mockTerrain.Clone();
    244                 if (map.Technos.Add(location, terrain))
    245                 {
    246                     mapPanel.Invalidate(map, terrain);
    247 
    248                     void undoAction(UndoRedoEventArgs e)
    249                     {
    250                         e.MapPanel.Invalidate(e.Map, location);
    251                         e.Map.Technos.Remove(terrain);
    252                     }
    253 
    254                     void redoAction(UndoRedoEventArgs e)
    255                     {
    256                         e.Map.Technos.Add(location, terrain);
    257                         e.MapPanel.Invalidate(e.Map, location);
    258                     }
    259 
    260                     url.Track(undoAction, redoAction);
    261 
    262                     plugin.Dirty = true;
    263                 }
    264             }
    265         }
    266 
    267         private void RemoveTerrain(Point location)
    268         {
    269             if (map.Technos[location] is Terrain terrain)
    270             {
    271                 mapPanel.Invalidate(map, terrain);
    272                 map.Technos.Remove(location);
    273 
    274                 void undoAction(UndoRedoEventArgs e)
    275                 {
    276                     e.Map.Technos.Add(location, terrain);
    277                     e.MapPanel.Invalidate(e.Map, location);
    278                 }
    279 
    280                 void redoAction(UndoRedoEventArgs e)
    281                 {
    282                     e.MapPanel.Invalidate(e.Map, location);
    283                     e.Map.Technos.Remove(terrain);
    284                 }
    285 
    286                 url.Track(undoAction, redoAction);
    287 
    288                 plugin.Dirty = true;
    289             }
    290         }
    291 
    292         private void EnterPlacementMode()
    293         {
    294             if (placementMode)
    295             {
    296                 return;
    297             }
    298 
    299             placementMode = true;
    300 
    301             navigationWidget.MouseoverSize = Size.Empty;
    302 
    303             if (SelectedTerrainType != null)
    304             {
    305                 mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedTerrainType.OverlapBounds.Size));
    306             }
    307 
    308             UpdateStatus();
    309         }
    310 
    311         private void ExitPlacementMode()
    312         {
    313             if (!placementMode)
    314             {
    315                 return;
    316             }
    317 
    318             placementMode = false;
    319 
    320             navigationWidget.MouseoverSize = new Size(1, 1);
    321 
    322             if (SelectedTerrainType != null)
    323             {
    324                 mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedTerrainType.OverlapBounds.Size));
    325             }
    326 
    327             UpdateStatus();
    328         }
    329 
    330         private void PickTerrain(Point location)
    331         {
    332             if (map.Metrics.GetCell(location, out int cell))
    333             {
    334                 if (map.Technos[cell] is Terrain terrain)
    335                 {
    336                     SelectedTerrainType = terrain.Type;
    337                     mockTerrain.Trigger = terrain.Trigger;
    338                 }
    339             }
    340         }
    341 
    342         private void SelectTerrain(Point location)
    343         {
    344             if (map.Metrics.GetCell(location, out int cell))
    345             {
    346                 selectedTerrain = map.Technos[cell] as Terrain;
    347                 selectedTerrainPivot = (selectedTerrain != null) ? (location - (Size)map.Technos[selectedTerrain].Value) : Point.Empty;
    348             }
    349 
    350             UpdateStatus();
    351         }
    352 
    353         private void RefreshMapPanel()
    354         {
    355             terrainTypeMapPanel.MapImage = mockTerrain.Type.Thumbnail;
    356         }
    357 
    358         private void UpdateStatus()
    359         {
    360             if (placementMode)
    361             {
    362                 statusLbl.Text = "Left-Click to place terrain, Right-Click to remove terrain";
    363             }
    364             else if (selectedTerrain != null)
    365             {
    366                 statusLbl.Text = "Drag mouse to move terrain";
    367             }
    368             else
    369             {
    370                 statusLbl.Text = "Shift to enter placement mode, Left-Click drag to move terrain, Double-Click update terrain properties, Right-Click to pick terrain";
    371             }
    372         }
    373 
    374         protected override void PreRenderMap()
    375         {
    376             base.PreRenderMap();
    377 
    378             previewMap = map.Clone();
    379             if (placementMode)
    380             {
    381                 var location = navigationWidget.MouseCell;
    382                 if (SelectedTerrainType != null)
    383                 {
    384                     if (previewMap.Metrics.Contains(location))
    385                     {
    386                         var terrain = new Terrain
    387                         {
    388                             Type = SelectedTerrainType,
    389                             Icon = SelectedTerrainType.IsTransformable ? 22 : 0,
    390                             Tint = Color.FromArgb(128, Color.White)
    391                         };
    392                         previewMap.Technos.Add(location, terrain);
    393                     }
    394                 }
    395             }
    396         }
    397 
    398         protected override void PostRenderMap(Graphics graphics)
    399         {
    400             base.PostRenderMap(graphics);
    401 
    402             var terrainPen = new Pen(Color.Green, 4.0f);
    403             var occupyPen = new Pen(Color.Red, 2.0f);
    404             foreach (var (topLeft, terrain) in previewMap.Technos.OfType<Terrain>())
    405             {
    406                 var bounds = new Rectangle(new Point(topLeft.X * Globals.TileWidth, topLeft.Y * Globals.TileHeight), terrain.Type.RenderSize);
    407                 graphics.DrawRectangle(terrainPen, bounds);
    408 
    409                 for (var y = 0; y < terrain.Type.OccupyMask.GetLength(0); ++y)
    410                 {
    411                     for (var x = 0; x < terrain.Type.OccupyMask.GetLength(1); ++x)
    412                     {
    413                         if (terrain.Type.OccupyMask[y, x])
    414                         {
    415                             var occupyBounds = new Rectangle(
    416                                 new Point((topLeft.X + x) * Globals.TileWidth, (topLeft.Y + y) * Globals.TileHeight),
    417                                 Globals.TileSize
    418                             );
    419                             graphics.DrawRectangle(occupyPen, occupyBounds);
    420                         }
    421                     }
    422                 }
    423             }
    424         }
    425 
    426         #region IDisposable Support
    427         private bool disposedValue = false;
    428 
    429         protected override void Dispose(bool disposing)
    430         {
    431             if (!disposedValue)
    432             {
    433                 if (disposing)
    434                 {
    435                     selectedTerrainProperties?.Close();
    436 
    437                     mapPanel.MouseDown -= MapPanel_MouseDown;
    438                     mapPanel.MouseMove -= MapPanel_MouseMove;
    439                     mapPanel.MouseUp -= MapPanel_MouseUp;
    440                     mapPanel.MouseDoubleClick -= MapPanel_MouseDoubleClick;
    441                     (mapPanel as Control).KeyDown -= TerrainTool_KeyDown;
    442                     (mapPanel as Control).KeyUp -= TerrainTool_KeyUp;
    443 
    444                     terrainTypeComboBox.SelectedIndexChanged -= TerrainTypeCombo_SelectedIndexChanged;
    445 
    446                     navigationWidget.MouseCellChanged -= MouseoverWidget_MouseCellChanged;
    447                 }
    448                 disposedValue = true;
    449             }
    450 
    451             base.Dispose(disposing);
    452         }
    453         #endregion
    454     }
    455 }