WallsTool.cs (13179B)
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.Collections.Generic; 23 using System.Drawing; 24 using System.Linq; 25 using System.Windows.Forms; 26 27 namespace MobiusEditor.Tools 28 { 29 public class WallsTool : ViewTool 30 { 31 private readonly TypeComboBox wallTypeComboBox; 32 private readonly MapPanel wallTypeMapPanel; 33 34 private readonly Dictionary<int, Overlay> undoOverlays = new Dictionary<int, Overlay>(); 35 private readonly Dictionary<int, Overlay> redoOverlays = new Dictionary<int, Overlay>(); 36 37 private Map previewMap; 38 protected override Map RenderMap => previewMap; 39 40 private bool placementMode; 41 42 private OverlayType selectedWallType; 43 private OverlayType SelectedWallType 44 { 45 get => selectedWallType; 46 set 47 { 48 if (selectedWallType != value) 49 { 50 if (placementMode && (selectedWallType != null)) 51 { 52 mapPanel.Invalidate(map, navigationWidget.MouseCell); 53 } 54 55 selectedWallType = value; 56 wallTypeComboBox.SelectedValue = selectedWallType; 57 58 RefreshMapPanel(); 59 } 60 } 61 } 62 63 public WallsTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, TypeComboBox wallTypeComboBox, MapPanel wallTypeMapPanel, IGamePlugin plugin, UndoRedoList<UndoRedoEventArgs> url) 64 : base(mapPanel, layers, statusLbl, plugin, url) 65 { 66 previewMap = map; 67 68 this.mapPanel.MouseDown += MapPanel_MouseDown; 69 this.mapPanel.MouseUp += MapPanel_MouseUp; 70 this.mapPanel.MouseMove += MapPanel_MouseMove; 71 (this.mapPanel as Control).KeyDown += WallTool_KeyDown; 72 (this.mapPanel as Control).KeyUp += WallTool_KeyUp; 73 74 this.wallTypeComboBox = wallTypeComboBox; 75 this.wallTypeComboBox.SelectedIndexChanged += WallTypeComboBox_SelectedIndexChanged; 76 77 this.wallTypeMapPanel = wallTypeMapPanel; 78 this.wallTypeMapPanel.BackColor = Color.White; 79 this.wallTypeMapPanel.MaxZoom = 1; 80 81 navigationWidget.MouseCellChanged += MouseoverWidget_MouseCellChanged; 82 83 SelectedWallType = this.wallTypeComboBox.Types.First() as OverlayType; 84 85 UpdateStatus(); 86 } 87 88 private void WallTypeComboBox_SelectedIndexChanged(object sender, EventArgs e) 89 { 90 SelectedWallType = wallTypeComboBox.SelectedValue as OverlayType; 91 } 92 93 private void WallTool_KeyDown(object sender, KeyEventArgs e) 94 { 95 if (e.KeyCode == Keys.ShiftKey) 96 { 97 EnterPlacementMode(); 98 } 99 } 100 101 private void WallTool_KeyUp(object sender, KeyEventArgs e) 102 { 103 if (e.KeyCode == Keys.ShiftKey) 104 { 105 ExitPlacementMode(); 106 } 107 } 108 109 private void MapPanel_MouseDown(object sender, MouseEventArgs e) 110 { 111 if (placementMode) 112 { 113 if (e.Button == MouseButtons.Left) 114 { 115 AddWall(navigationWidget.MouseCell); 116 } 117 else if (e.Button == MouseButtons.Right) 118 { 119 RemoveWall(navigationWidget.MouseCell); 120 } 121 } 122 else if ((e.Button == MouseButtons.Left) || (e.Button == MouseButtons.Right)) 123 { 124 PickWall(navigationWidget.MouseCell); 125 } 126 } 127 128 private void MapPanel_MouseUp(object sender, MouseEventArgs e) 129 { 130 if ((undoOverlays.Count > 0) || (redoOverlays.Count > 0)) 131 { 132 CommitChange(); 133 } 134 } 135 136 private void MapPanel_MouseMove(object sender, MouseEventArgs e) 137 { 138 if (!placementMode && (Control.ModifierKeys == Keys.Shift)) 139 { 140 EnterPlacementMode(); 141 } 142 else if (placementMode && (Control.ModifierKeys == Keys.None)) 143 { 144 ExitPlacementMode(); 145 } 146 } 147 148 private void MouseoverWidget_MouseCellChanged(object sender, MouseCellChangedEventArgs e) 149 { 150 if (placementMode) 151 { 152 if (Control.MouseButtons == MouseButtons.Left) 153 { 154 AddWall(e.NewCell); 155 } 156 else if (Control.MouseButtons == MouseButtons.Right) 157 { 158 RemoveWall(e.NewCell); 159 } 160 161 if (SelectedWallType != null) 162 { 163 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(e.OldCell, new Size(1, 1)), 1, 1)); 164 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(e.NewCell, new Size(1, 1)), 1, 1)); 165 } 166 } 167 } 168 169 private void AddWall(Point location) 170 { 171 if (map.Metrics.GetCell(location, out int cell)) 172 { 173 if (SelectedWallType != null) 174 { 175 var overlay = new Overlay { Type = SelectedWallType, Icon = 0 }; 176 if (map.Technos.CanAdd(cell, overlay) && map.Buildings.CanAdd(cell, overlay)) 177 { 178 if (!undoOverlays.ContainsKey(cell)) 179 { 180 undoOverlays[cell] = map.Overlay[cell]; 181 } 182 183 map.Overlay[cell] = overlay; 184 redoOverlays[cell] = overlay; 185 186 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(location, new Size(1, 1)), 1, 1)); 187 188 plugin.Dirty = true; 189 } 190 } 191 } 192 } 193 194 private void RemoveWall(Point location) 195 { 196 if (map.Metrics.GetCell(location, out int cell)) 197 { 198 var overlay = map.Overlay[cell]; 199 if (overlay?.Type.IsWall ?? false) 200 { 201 if (!undoOverlays.ContainsKey(cell)) 202 { 203 undoOverlays[cell] = map.Overlay[cell]; 204 } 205 206 map.Overlay[cell] = null; 207 redoOverlays[cell] = null; 208 209 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(location, new Size(1, 1)), 1, 1)); 210 211 plugin.Dirty = true; 212 } 213 } 214 } 215 216 private void CommitChange() 217 { 218 var undoOverlays2 = new Dictionary<int, Overlay>(undoOverlays); 219 void undoAction(UndoRedoEventArgs e) 220 { 221 foreach (var kv in undoOverlays2) 222 { 223 e.Map.Overlay[kv.Key] = kv.Value; 224 } 225 e.MapPanel.Invalidate(e.Map, undoOverlays2.Keys.Select(k => 226 { 227 e.Map.Metrics.GetLocation(k, out Point location); 228 return Rectangle.Inflate(new Rectangle(location, new Size(1, 1)), 1, 1); 229 })); 230 } 231 232 var redoOverlays2 = new Dictionary<int, Overlay>(redoOverlays); 233 void redoAction(UndoRedoEventArgs e) 234 { 235 foreach (var kv in redoOverlays2) 236 { 237 e.Map.Overlay[kv.Key] = kv.Value; 238 } 239 e.MapPanel.Invalidate(e.Map, redoOverlays2.Keys.Select(k => 240 { 241 e.Map.Metrics.GetLocation(k, out Point location); 242 return Rectangle.Inflate(new Rectangle(location, new Size(1, 1)), 1, 1); 243 })); 244 } 245 246 undoOverlays.Clear(); 247 redoOverlays.Clear(); 248 249 url.Track(undoAction, redoAction); 250 } 251 252 private void EnterPlacementMode() 253 { 254 if (placementMode) 255 { 256 return; 257 } 258 259 placementMode = true; 260 261 navigationWidget.MouseoverSize = Size.Empty; 262 263 if (SelectedWallType != null) 264 { 265 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1)); 266 } 267 268 UpdateStatus(); 269 } 270 271 private void ExitPlacementMode() 272 { 273 if (!placementMode) 274 { 275 return; 276 } 277 278 placementMode = false; 279 280 navigationWidget.MouseoverSize = new Size(1, 1); 281 282 if (SelectedWallType != null) 283 { 284 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1)); 285 } 286 287 UpdateStatus(); 288 } 289 290 private void PickWall(Point location) 291 { 292 if (map.Metrics.GetCell(location, out int cell)) 293 { 294 var overlay = map.Overlay[cell]; 295 if ((overlay != null) && overlay.Type.IsWall) 296 { 297 SelectedWallType = overlay.Type; 298 } 299 } 300 } 301 302 private void RefreshMapPanel() 303 { 304 wallTypeMapPanel.MapImage = SelectedWallType?.Thumbnail; 305 } 306 307 private void UpdateStatus() 308 { 309 if (placementMode) 310 { 311 statusLbl.Text = "Left-Click drag to add walls, Right-Click drag to remove walls"; 312 } 313 else 314 { 315 statusLbl.Text = "Shift to enter placement mode, Left-Click or Right-Click to pick wall"; 316 } 317 } 318 319 protected override void PreRenderMap() 320 { 321 base.PreRenderMap(); 322 323 previewMap = map.Clone(); 324 if (placementMode) 325 { 326 var location = navigationWidget.MouseCell; 327 if (SelectedWallType != null) 328 { 329 if (previewMap.Metrics.GetCell(location, out int cell)) 330 { 331 var overlay = new Overlay { Type = SelectedWallType, Icon = 0, Tint = Color.FromArgb(128, Color.White) }; 332 if (previewMap.Technos.CanAdd(cell, overlay) && previewMap.Buildings.CanAdd(cell, overlay)) 333 { 334 previewMap.Overlay[cell] = overlay; 335 mapPanel.Invalidate(previewMap, Rectangle.Inflate(new Rectangle(location, new Size(1, 1)), 1, 1)); 336 } 337 } 338 } 339 } 340 } 341 342 protected override void PostRenderMap(Graphics graphics) 343 { 344 base.PostRenderMap(graphics); 345 346 var wallPen = new Pen(Color.Green, 4.0f); 347 foreach (var (cell, overlay) in previewMap.Overlay) 348 { 349 if (overlay.Type.IsWall) 350 { 351 previewMap.Metrics.GetLocation(cell, out Point topLeft); 352 var bounds = new Rectangle(new Point(topLeft.X * Globals.TileWidth, topLeft.Y * Globals.TileHeight), Globals.TileSize); 353 graphics.DrawRectangle(wallPen, bounds); 354 } 355 } 356 } 357 358 #region IDisposable Support 359 private bool disposedValue = false; 360 361 protected override void Dispose(bool disposing) 362 { 363 if (!disposedValue) 364 { 365 if (disposing) 366 { 367 mapPanel.MouseDown -= MapPanel_MouseDown; 368 mapPanel.MouseUp -= MapPanel_MouseUp; 369 mapPanel.MouseMove -= MapPanel_MouseMove; 370 (mapPanel as Control).KeyDown -= WallTool_KeyDown; 371 (mapPanel as Control).KeyUp -= WallTool_KeyUp; 372 373 wallTypeComboBox.SelectedIndexChanged -= WallTypeComboBox_SelectedIndexChanged; 374 375 navigationWidget.MouseCellChanged -= MouseoverWidget_MouseCellChanged; 376 } 377 disposedValue = true; 378 } 379 380 base.Dispose(disposing); 381 } 382 #endregion 383 } 384 }