CnC_Remastered_Collection

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

BuildingTool.cs (18790B)


      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 BuildingTool : ViewTool
     31     {
     32         private readonly TypeComboBox buildingTypeComboBox;
     33         private readonly MapPanel buildingTypeMapPanel;
     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 Building mockBuilding;
     42 
     43         private Building selectedBuilding;
     44         private ObjectPropertiesPopup selectedObjectProperties;
     45         private Point selectedBuildingPivot;
     46 
     47         private BuildingType selectedBuildingType;
     48         private BuildingType SelectedBuildingType
     49         {
     50             get => selectedBuildingType;
     51             set
     52             {
     53                 if (selectedBuildingType != value)
     54                 {
     55                     if (placementMode && (selectedBuildingType != null))
     56                     {
     57                         mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedBuildingType.OverlapBounds.Size));
     58                     }
     59 
     60                     selectedBuildingType = value;
     61                     buildingTypeComboBox.SelectedValue = selectedBuildingType;
     62 
     63                     if (placementMode && (selectedBuildingType != null))
     64                     {
     65                         mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, selectedBuildingType.OverlapBounds.Size));
     66                     }
     67 
     68                     mockBuilding.Type = selectedBuildingType;
     69 
     70                     RefreshMapPanel();
     71                 }
     72             }
     73         }
     74 
     75         public BuildingTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, TypeComboBox buildingTypeComboBox, MapPanel buildingTypeMapPanel, ObjectProperties objectProperties, IGamePlugin plugin, UndoRedoList<UndoRedoEventArgs> url)
     76             : base(mapPanel, layers, statusLbl, plugin, url)
     77         {
     78             previewMap = map;
     79 
     80             mockBuilding = new Building()
     81             {
     82                 Type = buildingTypeComboBox.Types.First() as BuildingType,
     83                 House = map.Houses.First().Type,
     84                 Strength = 256,
     85                 Direction = map.DirectionTypes.Where(d => d.Equals(FacingType.North)).First()
     86             };
     87             mockBuilding.PropertyChanged += MockBuilding_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 += UnitTool_KeyDown;
     94             (this.mapPanel as Control).KeyUp += UnitTool_KeyUp;
     95 
     96             this.buildingTypeComboBox = buildingTypeComboBox;
     97             this.buildingTypeComboBox.SelectedIndexChanged += UnitTypeComboBox_SelectedIndexChanged;
     98 
     99             this.buildingTypeMapPanel = buildingTypeMapPanel;
    100             this.buildingTypeMapPanel.BackColor = Color.White;
    101             this.buildingTypeMapPanel.MaxZoom = 1;
    102 
    103             this.objectProperties = objectProperties;
    104             this.objectProperties.Object = mockBuilding;
    105 
    106             navigationWidget.MouseCellChanged += MouseoverWidget_MouseCellChanged;
    107 
    108             SelectedBuildingType = mockBuilding.Type;
    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 Building building)
    123                 {
    124                     selectedBuilding = null;
    125                     selectedBuildingPivot = Point.Empty;
    126 
    127                     selectedObjectProperties?.Close();
    128                     selectedObjectProperties = new ObjectPropertiesPopup(objectProperties.Plugin, building);
    129                     selectedObjectProperties.Closed += (cs, ce) =>
    130                     {
    131                         navigationWidget.Refresh();
    132                     };
    133 
    134                     building.PropertyChanged += SelectedBuilding_PropertyChanged;
    135 
    136                     selectedObjectProperties.Show(mapPanel, mapPanel.PointToClient(Control.MousePosition));
    137 
    138                     UpdateStatus();
    139                 }
    140             }
    141         }
    142 
    143         private void MockBuilding_PropertyChanged(object sender, PropertyChangedEventArgs e)
    144         {
    145             if ((mockBuilding.Type == null) || !mockBuilding.Type.HasTurret)
    146             {
    147                 mockBuilding.Direction = map.DirectionTypes.Where(d => d.Equals(FacingType.North)).First();
    148             }
    149 
    150             RefreshMapPanel();
    151         }
    152 
    153         private void SelectedBuilding_PropertyChanged(object sender, PropertyChangedEventArgs e)
    154         {
    155             mapPanel.Invalidate(map, sender as Building);
    156         }
    157 
    158         private void UnitTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
    159         {
    160             SelectedBuildingType = buildingTypeComboBox.SelectedValue as BuildingType;
    161         }
    162 
    163         private void UnitTool_KeyDown(object sender, KeyEventArgs e)
    164         {
    165             if (e.KeyCode == Keys.ShiftKey)
    166             {
    167                 EnterPlacementMode();
    168             }
    169         }
    170 
    171         private void UnitTool_KeyUp(object sender, KeyEventArgs e)
    172         {
    173             if (e.KeyCode == Keys.ShiftKey)
    174             {
    175                 ExitPlacementMode();
    176             }
    177         }
    178 
    179         private void MapPanel_MouseMove(object sender, MouseEventArgs e)
    180         {
    181             if (!placementMode && (Control.ModifierKeys == Keys.Shift))
    182             {
    183                 EnterPlacementMode();
    184             }
    185             else if (placementMode && (Control.ModifierKeys == Keys.None))
    186             {
    187                 ExitPlacementMode();
    188             }
    189         }
    190 
    191         private void MapPanel_MouseDown(object sender, MouseEventArgs e)
    192         {
    193             if (placementMode)
    194             {
    195                 if (e.Button == MouseButtons.Left)
    196                 {
    197                     AddBuilding(navigationWidget.MouseCell);
    198                 }
    199                 else if (e.Button == MouseButtons.Right)
    200                 {
    201                     RemoveBuilding(navigationWidget.MouseCell);
    202                 }
    203             }
    204             else if (e.Button == MouseButtons.Left)
    205             {
    206                 SelectBuilding(navigationWidget.MouseCell);
    207             }
    208             else if (e.Button == MouseButtons.Right)
    209             {
    210                 PickBuilding(navigationWidget.MouseCell);
    211             }
    212         }
    213 
    214         private void MapPanel_MouseUp(object sender, MouseEventArgs e)
    215         {
    216             if (selectedBuilding != null)
    217             {
    218                 selectedBuilding = null;
    219                 selectedBuildingPivot = Point.Empty;
    220 
    221                 UpdateStatus();
    222             }
    223         }
    224 
    225         private void MouseoverWidget_MouseCellChanged(object sender, MouseCellChangedEventArgs e)
    226         {
    227             if (placementMode)
    228             {
    229                 if (SelectedBuildingType != null)
    230                 {
    231                     mapPanel.Invalidate(map, new Rectangle(e.OldCell, SelectedBuildingType.OverlapBounds.Size));
    232                     mapPanel.Invalidate(map, new Rectangle(e.NewCell, SelectedBuildingType.OverlapBounds.Size));
    233                 }
    234             }
    235             else if (selectedBuilding != null)
    236             {
    237                 var oldLocation = map.Technos[selectedBuilding].Value;
    238                 var newLocation = new Point(Math.Max(0, e.NewCell.X - selectedBuildingPivot.X), Math.Max(0, e.NewCell.Y - selectedBuildingPivot.Y));
    239                 mapPanel.Invalidate(map, selectedBuilding);
    240                 map.Buildings.Remove(selectedBuilding);
    241                 if (map.Technos.CanAdd(newLocation, selectedBuilding, selectedBuilding.Type.BaseOccupyMask) &&
    242                     map.Buildings.Add(newLocation, selectedBuilding))
    243                 {
    244                     mapPanel.Invalidate(map, selectedBuilding);
    245                     plugin.Dirty = true;
    246                 }
    247                 else
    248                 {
    249                     map.Buildings.Add(oldLocation, selectedBuilding);
    250                 }
    251             }
    252         }
    253 
    254         private void AddBuilding(Point location)
    255         {
    256             if (SelectedBuildingType != null)
    257             {
    258                 var building = mockBuilding.Clone();
    259                 if (map.Technos.CanAdd(location, building, building.Type.BaseOccupyMask) && map.Buildings.Add(location, building))
    260                 {
    261                     if (building.BasePriority >= 0)
    262                     {
    263                         foreach (var baseBuilding in map.Buildings.OfType<Building>().Select(x => x.Occupier).Where(x => x.BasePriority >= 0))
    264                         {
    265                             if ((building != baseBuilding) && (baseBuilding.BasePriority >= building.BasePriority))
    266                             {
    267                                 baseBuilding.BasePriority++;
    268                             }
    269                         }
    270 
    271                         var baseBuildings = map.Buildings.OfType<Building>().Select(x => x.Occupier).Where(x => x.BasePriority >= 0).OrderBy(x => x.BasePriority).ToArray();
    272                         for (var i = 0; i < baseBuildings.Length; ++i)
    273                         {
    274                             baseBuildings[i].BasePriority = i;
    275                         }
    276 
    277                         foreach (var baseBuilding in map.Buildings.OfType<Building>().Select(x => x.Occupier).Where(x => x.BasePriority >= 0))
    278                         {
    279                             mapPanel.Invalidate(map, baseBuilding);
    280                         }
    281                     }
    282 
    283                     mapPanel.Invalidate(map, building);
    284 
    285                     plugin.Dirty = true;
    286                 }
    287             }
    288         }
    289 
    290         private void RemoveBuilding(Point location)
    291         {
    292             if (map.Technos[location] is Building building)
    293             {
    294                 mapPanel.Invalidate(map, building);
    295                 map.Buildings.Remove(building);
    296 
    297                 if (building.BasePriority >= 0)
    298                 {
    299                     var baseBuildings = map.Buildings.OfType<Building>().Select(x => x.Occupier).Where(x => x.BasePriority >= 0).OrderBy(x => x.BasePriority).ToArray();
    300                     for (var i = 0; i < baseBuildings.Length; ++i)
    301                     {
    302                         baseBuildings[i].BasePriority = i;
    303                     }
    304 
    305                     foreach (var baseBuilding in map.Buildings.OfType<Building>().Select(x => x.Occupier).Where(x => x.BasePriority >= 0))
    306                     {
    307                         mapPanel.Invalidate(map, baseBuilding);
    308                     }
    309                 }
    310 
    311                 plugin.Dirty = true;
    312             }
    313         }
    314 
    315         private void EnterPlacementMode()
    316         {
    317             if (placementMode)
    318             {
    319                 return;
    320             }
    321 
    322             placementMode = true;
    323 
    324             navigationWidget.MouseoverSize = Size.Empty;
    325 
    326             if (SelectedBuildingType != null)
    327             {
    328                 mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, SelectedBuildingType.OverlapBounds.Size));
    329             }
    330 
    331             UpdateStatus();
    332         }
    333 
    334         private void ExitPlacementMode()
    335         {
    336             if (!placementMode)
    337             {
    338                 return;
    339             }
    340 
    341             placementMode = false;
    342 
    343             navigationWidget.MouseoverSize = new Size(1, 1);
    344 
    345             if (SelectedBuildingType != null)
    346             {
    347                 mapPanel.Invalidate(map, new Rectangle(navigationWidget.MouseCell, SelectedBuildingType.OverlapBounds.Size));
    348             }
    349 
    350             UpdateStatus();
    351         }
    352 
    353         private void PickBuilding(Point location)
    354         {
    355             if (map.Metrics.GetCell(location, out int cell))
    356             {
    357                 if (map.Technos[cell] is Building building)
    358                 {
    359                     SelectedBuildingType = building.Type;
    360                     mockBuilding.House = building.House;
    361                     mockBuilding.Strength = building.Strength;
    362                     mockBuilding.Direction = building.Direction;
    363                     mockBuilding.Trigger = building.Trigger;
    364                     mockBuilding.BasePriority = building.BasePriority;
    365                     mockBuilding.IsPrebuilt = building.IsPrebuilt;
    366                     mockBuilding.Sellable = building.Sellable;
    367                     mockBuilding.Rebuild = building.Rebuild;
    368                 }
    369             }
    370         }
    371 
    372         private void SelectBuilding(Point location)
    373         {
    374             if (map.Metrics.GetCell(location, out int cell))
    375             {
    376                 selectedBuilding = map.Technos[cell] as Building;
    377                 selectedBuildingPivot = (selectedBuilding != null) ? (location - (Size)map.Technos[selectedBuilding].Value) : Point.Empty;
    378             }
    379 
    380             UpdateStatus();
    381         }
    382 
    383         private void RefreshMapPanel()
    384         {
    385             if (mockBuilding.Type != null)
    386             {
    387                 var render = MapRenderer.Render(plugin.GameType, map.Theater, new Point(0, 0), Globals.TileSize, Globals.TileScale, mockBuilding);
    388                 if (!render.Item1.IsEmpty)
    389                 {
    390                     var buildingPreview = new Bitmap(render.Item1.Width, render.Item1.Height);
    391                     using (var g = Graphics.FromImage(buildingPreview))
    392                     {
    393                         render.Item2(g);
    394                     }
    395                     buildingTypeMapPanel.MapImage = buildingPreview;
    396                 }
    397                 else
    398                 {
    399                     buildingTypeMapPanel.MapImage = null;
    400                 }
    401             }
    402             else
    403             {
    404                 buildingTypeMapPanel.MapImage = null;
    405             }
    406         }
    407 
    408         private void UpdateStatus()
    409         {
    410             if (placementMode)
    411             {
    412                 statusLbl.Text = "Left-Click to place building, Right-Click to remove building";
    413             }
    414             else if (selectedBuilding != null)
    415             {
    416                 statusLbl.Text = "Drag mouse to move building";
    417             }
    418             else
    419             {
    420                 statusLbl.Text = "Shift to enter placement mode, Left-Click drag to move building, Double-Click update building properties, Right-Click to pick building";
    421             }
    422         }
    423 
    424         protected override void PreRenderMap()
    425         {
    426             base.PreRenderMap();
    427 
    428             previewMap = map.Clone();
    429             if (placementMode)
    430             {
    431                 var location = navigationWidget.MouseCell;
    432                 if (SelectedBuildingType != null)
    433                 {
    434                     var building = mockBuilding.Clone();
    435                     building.Tint = Color.FromArgb(128, Color.White);
    436                     if (previewMap.Technos.CanAdd(location, building, building.Type.BaseOccupyMask) && previewMap.Buildings.Add(location, building))
    437                     {
    438                         mapPanel.Invalidate(previewMap, building);
    439                     }
    440                 }
    441             }
    442         }
    443 
    444         protected override void PostRenderMap(Graphics graphics)
    445         {
    446             base.PostRenderMap(graphics);
    447 
    448             var buildingPen = new Pen(Color.Green, 4.0f);
    449             var occupyPen = new Pen(Color.Red, 2.0f);
    450             foreach (var (topLeft, building) in map.Buildings.OfType<Building>())
    451             {
    452                 var bounds = new Rectangle(
    453                     new Point(topLeft.X * Globals.TileWidth, topLeft.Y * Globals.TileHeight),
    454                     new Size(building.Type.Size.Width * Globals.TileWidth, building.Type.Size.Height * Globals.TileHeight)
    455                 );
    456                 graphics.DrawRectangle(buildingPen, bounds);
    457 
    458                 for (var y = 0; y < building.Type.BaseOccupyMask.GetLength(0); ++y)
    459                 {
    460                     for (var x = 0; x < building.Type.BaseOccupyMask.GetLength(1); ++x)
    461                     {
    462                         if (building.Type.BaseOccupyMask[y, x])
    463                         {
    464                             var occupyBounds = new Rectangle(
    465                                 new Point((topLeft.X + x) * Globals.TileWidth, (topLeft.Y + y) * Globals.TileHeight),
    466                                 Globals.TileSize
    467                             );
    468                             graphics.DrawRectangle(occupyPen, occupyBounds);
    469                         }
    470                     }
    471                 }
    472             }
    473         }
    474 
    475         #region IDisposable Support
    476         private bool disposedValue = false;
    477 
    478         protected override void Dispose(bool disposing)
    479         {
    480             if (!disposedValue)
    481             {
    482                 if (disposing)
    483                 {
    484                     mapPanel.MouseDown -= MapPanel_MouseDown;
    485                     mapPanel.MouseUp -= MapPanel_MouseUp;
    486                     mapPanel.MouseDoubleClick -= MapPanel_MouseDoubleClick;
    487                     mapPanel.MouseMove -= MapPanel_MouseMove;
    488                     (mapPanel as Control).KeyDown -= UnitTool_KeyDown;
    489                     (mapPanel as Control).KeyUp -= UnitTool_KeyUp;
    490 
    491                     buildingTypeComboBox.SelectedIndexChanged -= UnitTypeComboBox_SelectedIndexChanged;
    492 
    493                     navigationWidget.MouseCellChanged -= MouseoverWidget_MouseCellChanged;
    494                 }
    495                 disposedValue = true;
    496             }
    497 
    498             base.Dispose(disposing);
    499         }
    500         #endregion
    501     }
    502 }