CnC_Remastered_Collection

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

InfantryTool.cs (20191B)


      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.Render;
     20 using MobiusEditor.Utility;
     21 using MobiusEditor.Widgets;
     22 using System;
     23 using System.ComponentModel;
     24 using System.Drawing;
     25 using System.Linq;
     26 using System.Windows.Forms;
     27 
     28 namespace MobiusEditor.Tools
     29 {
     30     public class InfantryTool : ViewTool
     31     {
     32         private readonly TypeComboBox infantryTypeComboBox;
     33         private readonly MapPanel infantryTypeMapPanel;
     34         private readonly ObjectProperties objectProperties;
     35 
     36         private Map previewMap;
     37         protected override Map RenderMap => previewMap;
     38 
     39         private bool placementMode;
     40 
     41         private readonly Infantry mockInfantry;
     42 
     43         private Infantry selectedInfantry;
     44         private ObjectPropertiesPopup selectedObjectProperties;
     45 
     46         private InfantryType selectedInfantryType;
     47         private InfantryType SelectedInfantryType
     48         {
     49             get => selectedInfantryType;
     50             set
     51             {
     52                 if (selectedInfantryType != value)
     53                 {
     54                     if (placementMode && (selectedInfantryType != null))
     55                     {
     56                         mapPanel.Invalidate(map, navigationWidget.MouseCell);
     57                     }
     58 
     59                     selectedInfantryType = value;
     60                     infantryTypeComboBox.SelectedValue = selectedInfantryType;
     61 
     62                     if (placementMode && (selectedInfantryType != null))
     63                     {
     64                         mapPanel.Invalidate(map, navigationWidget.MouseCell);
     65                     }
     66 
     67                     mockInfantry.Type = selectedInfantryType;
     68 
     69                     RefreshMapPanel();
     70                 }
     71             }
     72         }
     73 
     74         public InfantryTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, TypeComboBox infantryTypeComboBox, MapPanel infantryTypeMapPanel, ObjectProperties objectProperties, IGamePlugin plugin, UndoRedoList<UndoRedoEventArgs> url)
     75             : base(mapPanel, layers, statusLbl, plugin, url)
     76         {
     77             previewMap = map;
     78 
     79             mockInfantry = new Infantry(null)
     80             {
     81                 Type = infantryTypeComboBox.Types.First() as InfantryType,
     82                 House = map.Houses.First().Type,
     83                 Strength = 256,
     84                 Direction = map.DirectionTypes.Where(d => d.Equals(FacingType.South)).First(),
     85                 Mission = map.MissionTypes.Where(m => m.Equals("Guard")).FirstOrDefault() ?? map.MissionTypes.First()
     86             };
     87             mockInfantry.PropertyChanged += MockInfantry_PropertyChanged;
     88 
     89             this.mapPanel.MouseDown += MapPanel_MouseDown;
     90             this.mapPanel.MouseUp += MapPanel_MouseUp;
     91             this.mapPanel.MouseDoubleClick += MapPanel_MouseDoubleClick;
     92             this.mapPanel.MouseMove += MapPanel_MouseMove;
     93             (this.mapPanel as Control).KeyDown += InfantryTool_KeyDown;
     94             (this.mapPanel as Control).KeyUp += InfantryTool_KeyUp;
     95 
     96             this.infantryTypeComboBox = infantryTypeComboBox;
     97             this.infantryTypeComboBox.SelectedIndexChanged += InfantryTypeComboBox_SelectedIndexChanged;
     98 
     99             this.infantryTypeMapPanel = infantryTypeMapPanel;
    100             this.infantryTypeMapPanel.BackColor = Color.White;
    101             this.infantryTypeMapPanel.MaxZoom = 1;
    102 
    103             this.objectProperties = objectProperties;
    104             this.objectProperties.Object = mockInfantry;
    105 
    106             navigationWidget.MouseCellChanged += MouseoverWidget_MouseCellChanged;
    107 
    108             SelectedInfantryType = this.infantryTypeComboBox.Types.First() as InfantryType;
    109 
    110             UpdateStatus();
    111         }
    112 
    113         private void MapPanel_MouseDoubleClick(object sender, MouseEventArgs e)
    114         {
    115             if (Control.ModifierKeys != Keys.None)
    116             {
    117                 return;
    118             }
    119 
    120             if (map.Metrics.GetCell(navigationWidget.MouseCell, out int cell))
    121             {
    122                 if (map.Technos[cell] is InfantryGroup infantryGroup)
    123                 {
    124                     var i = InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>().First();
    125                     if (infantryGroup.Infantry[i] is Infantry infantry)
    126                     {
    127                         selectedInfantry = null;
    128 
    129                         selectedObjectProperties?.Close();
    130                         selectedObjectProperties = new ObjectPropertiesPopup(objectProperties.Plugin, infantry);
    131                         selectedObjectProperties.Closed += (cs, ce) =>
    132                         {
    133                             navigationWidget.Refresh();
    134                         };
    135 
    136                         infantry.PropertyChanged += SelectedInfantry_PropertyChanged;
    137 
    138                         selectedObjectProperties.Show(mapPanel, mapPanel.PointToClient(Control.MousePosition));
    139 
    140                         UpdateStatus();
    141                     }
    142                 }
    143             }
    144         }
    145 
    146         private void MockInfantry_PropertyChanged(object sender, PropertyChangedEventArgs e)
    147         {
    148             RefreshMapPanel();
    149         }
    150 
    151         private void SelectedInfantry_PropertyChanged(object sender, PropertyChangedEventArgs e)
    152         {
    153             mapPanel.Invalidate(map, (sender as Infantry).InfantryGroup);
    154         }
    155 
    156         private void InfantryTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
    157         {
    158             SelectedInfantryType = infantryTypeComboBox.SelectedValue as InfantryType;
    159         }
    160 
    161         private void InfantryTool_KeyDown(object sender, KeyEventArgs e)
    162         {
    163             if (e.KeyCode == Keys.ShiftKey)
    164             {
    165                 EnterPlacementMode();
    166             }
    167         }
    168 
    169         private void InfantryTool_KeyUp(object sender, KeyEventArgs e)
    170         {
    171             if (e.KeyCode == Keys.ShiftKey)
    172             {
    173                 ExitPlacementMode();
    174             }
    175         }
    176 
    177         private void MapPanel_MouseMove(object sender, MouseEventArgs e)
    178         {
    179             if (!placementMode && (Control.ModifierKeys == Keys.Shift))
    180             {
    181                 EnterPlacementMode();
    182             }
    183             else if (placementMode && (Control.ModifierKeys == Keys.None))
    184             {
    185                 ExitPlacementMode();
    186             }
    187 
    188             if (placementMode)
    189             {
    190                 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1));
    191             }
    192             else if (selectedInfantry != null)
    193             {
    194                 var oldLocation = map.Technos[selectedInfantry.InfantryGroup].Value;
    195                 var oldStop = Array.IndexOf(selectedInfantry.InfantryGroup.Infantry, selectedInfantry);
    196 
    197                 InfantryGroup infantryGroup = null;
    198                 var techno = map.Technos[navigationWidget.MouseCell];
    199                 if (techno == null)
    200                 {
    201                     infantryGroup = new InfantryGroup();
    202                     map.Technos.Add(navigationWidget.MouseCell, infantryGroup);
    203                 }
    204                 else if (techno is InfantryGroup)
    205                 {
    206                     infantryGroup = techno as InfantryGroup;
    207                 }
    208 
    209                 if (infantryGroup != null)
    210                 {
    211                     foreach (var i in InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>())
    212                     {
    213                         if (infantryGroup.Infantry[i] == null)
    214                         {
    215                             selectedInfantry.InfantryGroup.Infantry[oldStop] = null;
    216                             infantryGroup.Infantry[i] = selectedInfantry;
    217                             
    218                             if (infantryGroup != selectedInfantry.InfantryGroup)
    219                             {
    220                                 mapPanel.Invalidate(map, selectedInfantry.InfantryGroup);
    221                                 if (selectedInfantry.InfantryGroup.Infantry.All(x => x == null))
    222                                 {
    223                                     map.Technos.Remove(selectedInfantry.InfantryGroup);
    224                                 }
    225                             }
    226                             selectedInfantry.InfantryGroup = infantryGroup;
    227 
    228                             mapPanel.Invalidate(map, infantryGroup);
    229 
    230                             plugin.Dirty = true;
    231                         }
    232 
    233                         if (infantryGroup == selectedInfantry.InfantryGroup)
    234                         {
    235                             break;
    236                         }
    237                     }
    238                 }
    239             }
    240         }
    241 
    242         private void MapPanel_MouseDown(object sender, MouseEventArgs e)
    243         {
    244             if (placementMode)
    245             {
    246                 if (e.Button == MouseButtons.Left)
    247                 {
    248                     AddInfantry(navigationWidget.MouseCell);
    249                 }
    250                 else if (e.Button == MouseButtons.Right)
    251                 {
    252                     RemoveInfantry(navigationWidget.MouseCell);
    253                 }
    254             }
    255             else if (e.Button == MouseButtons.Left)
    256             {
    257                 SelectInfantry(navigationWidget.MouseCell);
    258             }
    259             else if (e.Button == MouseButtons.Right)
    260             {
    261                 PickInfantry(navigationWidget.MouseCell);
    262             }
    263         }
    264 
    265         private void MapPanel_MouseUp(object sender, MouseEventArgs e)
    266         {
    267             if (selectedInfantry != null)
    268             {
    269                 selectedInfantry = null;
    270                 UpdateStatus();
    271             }
    272         }
    273 
    274         private void MouseoverWidget_MouseCellChanged(object sender, MouseCellChangedEventArgs e)
    275         {
    276             if (placementMode)
    277             {
    278                 if (SelectedInfantryType != null)
    279                 {
    280                     mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(e.OldCell, new Size(1, 1)), 1, 1));
    281                     mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(e.NewCell, new Size(1, 1)), 1, 1));
    282                 }
    283             }
    284         }
    285 
    286         private void AddInfantry(Point location)
    287         {
    288             if (SelectedInfantryType != null)
    289             {
    290                 if (map.Metrics.GetCell(location, out int cell))
    291                 {
    292                     InfantryGroup infantryGroup = null;
    293 
    294                     var techno = map.Technos[cell];
    295                     if (techno == null)
    296                     {
    297                         infantryGroup = new InfantryGroup();
    298                         map.Technos.Add(cell, infantryGroup);
    299                     }
    300                     else if (techno is InfantryGroup)
    301                     {
    302                         infantryGroup = techno as InfantryGroup;
    303                     }
    304 
    305                     if (infantryGroup != null)
    306                     {
    307                         foreach (var i in InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>())
    308                         {
    309                             if (infantryGroup.Infantry[i] == null)
    310                             {
    311                                 var infantry = mockInfantry.Clone();
    312                                 infantryGroup.Infantry[i] = infantry;
    313                                 infantry.InfantryGroup = infantryGroup;
    314                                 mapPanel.Invalidate(map, infantryGroup);
    315                                 plugin.Dirty = true;
    316                                 break;
    317                             }
    318                         }
    319                     }
    320                 }
    321             }
    322         }
    323 
    324         private void RemoveInfantry(Point location)
    325         {
    326             if (map.Metrics.GetCell(location, out int cell))
    327             {
    328                 if (map.Technos[cell] is InfantryGroup infantryGroup)
    329                 {
    330                     foreach (var i in InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>())
    331                     {
    332                         if (infantryGroup.Infantry[i] != null)
    333                         {
    334                             infantryGroup.Infantry[i] = null;
    335                             mapPanel.Invalidate(map, infantryGroup);
    336                             plugin.Dirty = true;
    337                             break;
    338                         }
    339                     }
    340                     if (infantryGroup.Infantry.All(i => i == null))
    341                     {
    342                         map.Technos.Remove(infantryGroup);
    343                     }
    344                 }
    345             }
    346         }
    347 
    348         private void EnterPlacementMode()
    349         {
    350             if (placementMode)
    351             {
    352                 return;
    353             }
    354 
    355             placementMode = true;
    356 
    357             navigationWidget.MouseoverSize = Size.Empty;
    358 
    359             if (SelectedInfantryType != null)
    360             {
    361                 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1));
    362             }
    363 
    364             UpdateStatus();
    365         }
    366 
    367         private void ExitPlacementMode()
    368         {
    369             if (!placementMode)
    370             {
    371                 return;
    372             }
    373 
    374             placementMode = false;
    375 
    376             navigationWidget.MouseoverSize = new Size(1, 1);
    377 
    378             if (SelectedInfantryType != null)
    379             {
    380                 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1));
    381             }
    382 
    383             UpdateStatus();
    384         }
    385 
    386         private void PickInfantry(Point location)
    387         {
    388             if (map.Metrics.GetCell(location, out int cell))
    389             {
    390                 if (map.Technos[cell] is InfantryGroup infantryGroup)
    391                 {
    392                     var i = InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>().First();
    393                     if (infantryGroup.Infantry[i] is Infantry infantry)
    394                     {
    395                         SelectedInfantryType = infantry.Type;
    396                         mockInfantry.House = infantry.House;
    397                         mockInfantry.Strength = infantry.Strength;
    398                         mockInfantry.Direction = infantry.Direction;
    399                         mockInfantry.Mission = infantry.Mission;
    400                         mockInfantry.Trigger = infantry.Trigger;
    401                     }
    402                 }
    403             }
    404         }
    405 
    406         private void SelectInfantry(Point location)
    407         {
    408             if (map.Metrics.GetCell(location, out int cell))
    409             {
    410                 selectedInfantry = null;
    411                 if (map.Technos[cell] is InfantryGroup infantryGroup)
    412                 {
    413                     var i = InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>().First();
    414                     if (infantryGroup.Infantry[i] is Infantry infantry)
    415                     {
    416                         selectedInfantry = infantry;
    417                     }
    418                 }
    419             }
    420 
    421             UpdateStatus();
    422         }
    423 
    424         private void RefreshMapPanel()
    425         {
    426             if (mockInfantry.Type != null)
    427             {
    428                 var infantryPreview = new Bitmap(Globals.TileWidth, Globals.TileHeight);
    429                 using (var g = Graphics.FromImage(infantryPreview))
    430                 {
    431                     MapRenderer.Render(map.Theater, Point.Empty, Globals.TileSize, mockInfantry, InfantryStoppingType.Center).Item2(g);
    432                 }
    433                 infantryTypeMapPanel.MapImage = infantryPreview;
    434             }
    435             else
    436             {
    437                 infantryTypeMapPanel.MapImage = null;
    438             }
    439         }
    440 
    441         private void UpdateStatus()
    442         {
    443             if (placementMode)
    444             {
    445                 statusLbl.Text = "Left-Click to place infantry, Right-Click to remove infantry";
    446             }
    447             else if (selectedInfantry != null)
    448             {
    449                 statusLbl.Text = "Drag mouse to move infantry";
    450             }
    451             else
    452             {
    453                 statusLbl.Text = "Shift to enter placement mode, Left-Click drag to move infantry, Double-Click update infantry properties, Right-Click to pick infantry";
    454             }
    455         }
    456 
    457         protected override void PreRenderMap()
    458         {
    459             base.PreRenderMap();
    460 
    461             previewMap = map.Clone();
    462             if (placementMode)
    463             {
    464                 var location = navigationWidget.MouseCell;
    465                 if (SelectedInfantryType != null)
    466                 {
    467                     if (previewMap.Metrics.GetCell(location, out int cell))
    468                     {
    469                         InfantryGroup infantryGroup = null;
    470 
    471                         var techno = previewMap.Technos[cell];
    472                         if (techno == null)
    473                         {
    474                             infantryGroup = new InfantryGroup();
    475                             previewMap.Technos.Add(cell, infantryGroup);
    476                         }
    477                         else if (techno is InfantryGroup)
    478                         {
    479                             infantryGroup = techno as InfantryGroup;
    480                         }
    481 
    482                         if (infantryGroup != null)
    483                         {
    484                             foreach (var i in InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>())
    485                             {
    486                                 if (infantryGroup.Infantry[i] == null)
    487                                 {
    488                                     var infantry = mockInfantry.Clone();
    489                                     infantry.Tint = Color.FromArgb(128, Color.White);
    490                                     infantryGroup.Infantry[i] = infantry;
    491                                     break;
    492                                 }
    493                             }
    494                         }
    495                     }
    496                 }
    497             }
    498         }
    499 
    500         protected override void PostRenderMap(Graphics graphics)
    501         {
    502             base.PostRenderMap(graphics);
    503 
    504             var infantryPen = new Pen(Color.Green, 4.0f);
    505             foreach (var (topLeft, _) in map.Technos.OfType<InfantryGroup>())
    506             {
    507                 var bounds = new Rectangle(new Point(topLeft.X * Globals.TileWidth, topLeft.Y * Globals.TileHeight), Globals.TileSize);
    508                 graphics.DrawRectangle(infantryPen, bounds);
    509             }
    510         }
    511 
    512         #region IDisposable Support
    513         private bool disposedValue = false;
    514 
    515         protected override void Dispose(bool disposing)
    516         {
    517             if (!disposedValue)
    518             {
    519                 if (disposing)
    520                 {
    521                     mapPanel.MouseDown -= MapPanel_MouseDown;
    522                     mapPanel.MouseUp -= MapPanel_MouseUp;
    523                     mapPanel.MouseDoubleClick -= MapPanel_MouseDoubleClick;
    524                     mapPanel.MouseMove -= MapPanel_MouseMove;
    525                     (mapPanel as Control).KeyDown -= InfantryTool_KeyDown;
    526                     (mapPanel as Control).KeyUp -= InfantryTool_KeyUp;
    527 
    528                     infantryTypeComboBox.SelectedIndexChanged -= InfantryTypeComboBox_SelectedIndexChanged;
    529 
    530                     navigationWidget.MouseCellChanged -= MouseoverWidget_MouseCellChanged;
    531                 }
    532                 disposedValue = true;
    533             }
    534 
    535             base.Dispose(disposing);
    536         }
    537         #endregion
    538     }
    539 }