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 }