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 }