UnitTool.cs (14343B)
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 UnitTool : ViewTool 31 { 32 private readonly TypeComboBox unitTypeComboBox; 33 private readonly MapPanel unitTypeMapPanel; 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 Unit mockUnit; 42 43 private Unit selectedUnit; 44 private ObjectPropertiesPopup selectedObjectProperties; 45 46 private UnitType selectedUnitType; 47 private UnitType SelectedUnitType 48 { 49 get => selectedUnitType; 50 set 51 { 52 if (selectedUnitType != value) 53 { 54 if (placementMode && (selectedUnitType != null)) 55 { 56 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1)); 57 } 58 59 selectedUnitType = value; 60 unitTypeComboBox.SelectedValue = selectedUnitType; 61 62 if (placementMode && (selectedUnitType != null)) 63 { 64 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1)); 65 } 66 67 mockUnit.Type = selectedUnitType; 68 69 RefreshMapPanel(); 70 } 71 } 72 } 73 74 public UnitTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, TypeComboBox unitTypeComboBox, MapPanel unitTypeMapPanel, ObjectProperties objectProperties, IGamePlugin plugin, UndoRedoList<UndoRedoEventArgs> url) 75 : base(mapPanel, layers, statusLbl, plugin, url) 76 { 77 previewMap = map; 78 79 mockUnit = new Unit() 80 { 81 Type = unitTypeComboBox.Types.First() as UnitType, 82 House = map.Houses.First().Type, 83 Strength = 256, 84 Direction = map.DirectionTypes.Where(d => d.Equals(FacingType.North)).First(), 85 Mission = map.MissionTypes.Where(m => m.Equals("Guard")).FirstOrDefault() ?? map.MissionTypes.First() 86 }; 87 mockUnit.PropertyChanged += MockUnit_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.unitTypeComboBox = unitTypeComboBox; 97 this.unitTypeComboBox.SelectedIndexChanged += UnitTypeComboBox_SelectedIndexChanged; 98 99 this.unitTypeMapPanel = unitTypeMapPanel; 100 this.unitTypeMapPanel.BackColor = Color.White; 101 this.unitTypeMapPanel.MaxZoom = 1; 102 103 this.objectProperties = objectProperties; 104 this.objectProperties.Object = mockUnit; 105 106 navigationWidget.MouseCellChanged += MouseoverWidget_MouseCellChanged; 107 108 SelectedUnitType = mockUnit.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 Unit unit) 123 { 124 selectedUnit = null; 125 126 selectedObjectProperties?.Close(); 127 selectedObjectProperties = new ObjectPropertiesPopup(objectProperties.Plugin, unit); 128 selectedObjectProperties.Closed += (cs, ce) => 129 { 130 navigationWidget.Refresh(); 131 }; 132 133 unit.PropertyChanged += SelectedUnit_PropertyChanged; 134 135 selectedObjectProperties.Show(mapPanel, mapPanel.PointToClient(Control.MousePosition)); 136 137 UpdateStatus(); 138 } 139 } 140 } 141 142 private void MockUnit_PropertyChanged(object sender, PropertyChangedEventArgs e) 143 { 144 RefreshMapPanel(); 145 } 146 147 private void SelectedUnit_PropertyChanged(object sender, PropertyChangedEventArgs e) 148 { 149 mapPanel.Invalidate(map, sender as Unit); 150 } 151 152 private void UnitTypeComboBox_SelectedIndexChanged(object sender, EventArgs e) 153 { 154 SelectedUnitType = unitTypeComboBox.SelectedValue as UnitType; 155 } 156 157 private void UnitTool_KeyDown(object sender, KeyEventArgs e) 158 { 159 if (e.KeyCode == Keys.ShiftKey) 160 { 161 EnterPlacementMode(); 162 } 163 } 164 165 private void UnitTool_KeyUp(object sender, KeyEventArgs e) 166 { 167 if (e.KeyCode == Keys.ShiftKey) 168 { 169 ExitPlacementMode(); 170 } 171 } 172 173 private void MapPanel_MouseMove(object sender, MouseEventArgs e) 174 { 175 if (!placementMode && (Control.ModifierKeys == Keys.Shift)) 176 { 177 EnterPlacementMode(); 178 } 179 else if (placementMode && (Control.ModifierKeys == Keys.None)) 180 { 181 ExitPlacementMode(); 182 } 183 } 184 185 private void MapPanel_MouseDown(object sender, MouseEventArgs e) 186 { 187 if (placementMode) 188 { 189 if (e.Button == MouseButtons.Left) 190 { 191 AddUnit(navigationWidget.MouseCell); 192 } 193 else if (e.Button == MouseButtons.Right) 194 { 195 RemoveUnit(navigationWidget.MouseCell); 196 } 197 } 198 else if (e.Button == MouseButtons.Left) 199 { 200 SelectUnit(navigationWidget.MouseCell); 201 } 202 else if (e.Button == MouseButtons.Right) 203 { 204 PickUnit(navigationWidget.MouseCell); 205 } 206 } 207 208 private void MapPanel_MouseUp(object sender, MouseEventArgs e) 209 { 210 if (selectedUnit != null) 211 { 212 selectedUnit = null; 213 214 UpdateStatus(); 215 } 216 } 217 218 private void MouseoverWidget_MouseCellChanged(object sender, MouseCellChangedEventArgs e) 219 { 220 if (placementMode) 221 { 222 if (SelectedUnitType != null) 223 { 224 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(e.OldCell, new Size(1, 1)), 1, 1)); 225 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(e.NewCell, new Size(1, 1)), 1, 1)); 226 } 227 } 228 else if (selectedUnit != null) 229 { 230 var oldLocation = map.Technos[selectedUnit].Value; 231 mapPanel.Invalidate(map, selectedUnit); 232 map.Technos.Remove(selectedUnit); 233 if (map.Technos.Add(e.NewCell, selectedUnit)) 234 { 235 mapPanel.Invalidate(map, selectedUnit); 236 plugin.Dirty = true; 237 } 238 else 239 { 240 map.Technos.Add(oldLocation, selectedUnit); 241 } 242 } 243 } 244 245 private void AddUnit(Point location) 246 { 247 if (SelectedUnitType != null) 248 { 249 var unit = mockUnit.Clone(); 250 if (map.Technos.Add(location, unit)) 251 { 252 mapPanel.Invalidate(map, unit); 253 plugin.Dirty = true; 254 } 255 } 256 } 257 258 private void RemoveUnit(Point location) 259 { 260 if (map.Technos[location] is Unit unit) 261 { 262 mapPanel.Invalidate(map, unit); 263 map.Technos.Remove(unit); 264 plugin.Dirty = true; 265 } 266 } 267 268 private void EnterPlacementMode() 269 { 270 if (placementMode) 271 { 272 return; 273 } 274 275 placementMode = true; 276 277 navigationWidget.MouseoverSize = Size.Empty; 278 279 if (SelectedUnitType != null) 280 { 281 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1)); 282 } 283 284 UpdateStatus(); 285 } 286 287 private void ExitPlacementMode() 288 { 289 if (!placementMode) 290 { 291 return; 292 } 293 294 placementMode = false; 295 296 navigationWidget.MouseoverSize = new Size(1, 1); 297 298 if (SelectedUnitType != null) 299 { 300 mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1)); 301 } 302 303 UpdateStatus(); 304 } 305 306 private void PickUnit(Point location) 307 { 308 if (map.Metrics.GetCell(location, out int cell)) 309 { 310 if (map.Technos[cell] is Unit unit) 311 { 312 SelectedUnitType = unit.Type; 313 mockUnit.House = unit.House; 314 mockUnit.Strength = unit.Strength; 315 mockUnit.Direction = unit.Direction; 316 mockUnit.Mission = unit.Mission; 317 mockUnit.Trigger = unit.Trigger; 318 } 319 } 320 } 321 322 private void SelectUnit(Point location) 323 { 324 if (map.Metrics.GetCell(location, out int cell)) 325 { 326 selectedUnit = map.Technos[cell] as Unit; 327 } 328 329 UpdateStatus(); 330 } 331 332 private void RefreshMapPanel() 333 { 334 if (mockUnit.Type != null) 335 { 336 var unitPreview = new Bitmap(Globals.TileWidth * 3, Globals.TileHeight * 3); 337 using (var g = Graphics.FromImage(unitPreview)) 338 { 339 MapRenderer.Render(plugin.GameType, map.Theater, new Point(1, 1), Globals.TileSize, mockUnit).Item2(g); 340 } 341 unitTypeMapPanel.MapImage = unitPreview; 342 } 343 else 344 { 345 unitTypeMapPanel.MapImage = null; 346 } 347 } 348 349 private void UpdateStatus() 350 { 351 if (placementMode) 352 { 353 statusLbl.Text = "Left-Click to place unit, Right-Click to remove unit"; 354 } 355 else if (selectedUnit != null) 356 { 357 statusLbl.Text = "Drag mouse to move unit"; 358 } 359 else 360 { 361 statusLbl.Text = "Shift to enter placement mode, Left-Click drag to move unit, Double-Click update unit properties, Right-Click to pick unit"; 362 } 363 } 364 365 protected override void PreRenderMap() 366 { 367 base.PreRenderMap(); 368 369 previewMap = map.Clone(); 370 if (placementMode) 371 { 372 var location = navigationWidget.MouseCell; 373 if (SelectedUnitType != null) 374 { 375 var unit = mockUnit.Clone(); 376 unit.Tint = Color.FromArgb(128, Color.White); 377 if (previewMap.Technos.Add(location, unit)) 378 { 379 mapPanel.Invalidate(previewMap, unit); 380 } 381 } 382 } 383 } 384 385 protected override void PostRenderMap(Graphics graphics) 386 { 387 base.PostRenderMap(graphics); 388 389 var unitPen = new Pen(Color.Green, 4.0f); 390 foreach (var (topLeft, _) in map.Technos.OfType<Unit>()) 391 { 392 var bounds = new Rectangle(new Point(topLeft.X * Globals.TileWidth, topLeft.Y * Globals.TileHeight), Globals.TileSize); 393 graphics.DrawRectangle(unitPen, bounds); 394 } 395 } 396 397 #region IDisposable Support 398 private bool disposedValue = false; 399 400 protected override void Dispose(bool disposing) 401 { 402 if (!disposedValue) 403 { 404 if (disposing) 405 { 406 selectedObjectProperties?.Close(); 407 408 mapPanel.MouseDown -= MapPanel_MouseDown; 409 mapPanel.MouseUp -= MapPanel_MouseUp; 410 mapPanel.MouseDoubleClick -= MapPanel_MouseDoubleClick; 411 mapPanel.MouseMove -= MapPanel_MouseMove; 412 (mapPanel as Control).KeyDown -= UnitTool_KeyDown; 413 (mapPanel as Control).KeyUp -= UnitTool_KeyUp; 414 415 unitTypeComboBox.SelectedIndexChanged -= UnitTypeComboBox_SelectedIndexChanged; 416 417 navigationWidget.MouseCellChanged -= MouseoverWidget_MouseCellChanged; 418 } 419 disposedValue = true; 420 } 421 422 base.Dispose(disposing); 423 } 424 #endregion 425 } 426 }