InfantryTool.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  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. // The Command & Conquer Map Editor and corresponding source code is distributed
  9. // in the hope that it will be useful, but with permitted additional restrictions
  10. // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
  11. // distributed with this program. You should have received a copy of the
  12. // GNU General Public License along with permitted additional restrictions
  13. // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
  14. using MobiusEditor.Controls;
  15. using MobiusEditor.Event;
  16. using MobiusEditor.Interface;
  17. using MobiusEditor.Model;
  18. using MobiusEditor.Render;
  19. using MobiusEditor.Utility;
  20. using MobiusEditor.Widgets;
  21. using System;
  22. using System.ComponentModel;
  23. using System.Drawing;
  24. using System.Linq;
  25. using System.Windows.Forms;
  26. namespace MobiusEditor.Tools
  27. {
  28. public class InfantryTool : ViewTool
  29. {
  30. private readonly TypeComboBox infantryTypeComboBox;
  31. private readonly MapPanel infantryTypeMapPanel;
  32. private readonly ObjectProperties objectProperties;
  33. private Map previewMap;
  34. protected override Map RenderMap => previewMap;
  35. private bool placementMode;
  36. private readonly Infantry mockInfantry;
  37. private Infantry selectedInfantry;
  38. private ObjectPropertiesPopup selectedObjectProperties;
  39. private InfantryType selectedInfantryType;
  40. private InfantryType SelectedInfantryType
  41. {
  42. get => selectedInfantryType;
  43. set
  44. {
  45. if (selectedInfantryType != value)
  46. {
  47. if (placementMode && (selectedInfantryType != null))
  48. {
  49. mapPanel.Invalidate(map, navigationWidget.MouseCell);
  50. }
  51. selectedInfantryType = value;
  52. infantryTypeComboBox.SelectedValue = selectedInfantryType;
  53. if (placementMode && (selectedInfantryType != null))
  54. {
  55. mapPanel.Invalidate(map, navigationWidget.MouseCell);
  56. }
  57. mockInfantry.Type = selectedInfantryType;
  58. RefreshMapPanel();
  59. }
  60. }
  61. }
  62. public InfantryTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, TypeComboBox infantryTypeComboBox, MapPanel infantryTypeMapPanel, ObjectProperties objectProperties, IGamePlugin plugin, UndoRedoList<UndoRedoEventArgs> url)
  63. : base(mapPanel, layers, statusLbl, plugin, url)
  64. {
  65. previewMap = map;
  66. mockInfantry = new Infantry(null)
  67. {
  68. Type = infantryTypeComboBox.Types.First() as InfantryType,
  69. House = map.Houses.First().Type,
  70. Strength = 256,
  71. Direction = map.DirectionTypes.Where(d => d.Equals(FacingType.South)).First(),
  72. Mission = map.MissionTypes.Where(m => m.Equals("Guard")).FirstOrDefault() ?? map.MissionTypes.First()
  73. };
  74. mockInfantry.PropertyChanged += MockInfantry_PropertyChanged;
  75. this.mapPanel.MouseDown += MapPanel_MouseDown;
  76. this.mapPanel.MouseUp += MapPanel_MouseUp;
  77. this.mapPanel.MouseDoubleClick += MapPanel_MouseDoubleClick;
  78. this.mapPanel.MouseMove += MapPanel_MouseMove;
  79. (this.mapPanel as Control).KeyDown += InfantryTool_KeyDown;
  80. (this.mapPanel as Control).KeyUp += InfantryTool_KeyUp;
  81. this.infantryTypeComboBox = infantryTypeComboBox;
  82. this.infantryTypeComboBox.SelectedIndexChanged += InfantryTypeComboBox_SelectedIndexChanged;
  83. this.infantryTypeMapPanel = infantryTypeMapPanel;
  84. this.infantryTypeMapPanel.BackColor = Color.White;
  85. this.infantryTypeMapPanel.MaxZoom = 1;
  86. this.objectProperties = objectProperties;
  87. this.objectProperties.Object = mockInfantry;
  88. navigationWidget.MouseCellChanged += MouseoverWidget_MouseCellChanged;
  89. SelectedInfantryType = this.infantryTypeComboBox.Types.First() as InfantryType;
  90. UpdateStatus();
  91. }
  92. private void MapPanel_MouseDoubleClick(object sender, MouseEventArgs e)
  93. {
  94. if (Control.ModifierKeys != Keys.None)
  95. {
  96. return;
  97. }
  98. if (map.Metrics.GetCell(navigationWidget.MouseCell, out int cell))
  99. {
  100. if (map.Technos[cell] is InfantryGroup infantryGroup)
  101. {
  102. var i = InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>().First();
  103. if (infantryGroup.Infantry[i] is Infantry infantry)
  104. {
  105. selectedInfantry = null;
  106. selectedObjectProperties?.Close();
  107. selectedObjectProperties = new ObjectPropertiesPopup(objectProperties.Plugin, infantry);
  108. selectedObjectProperties.Closed += (cs, ce) =>
  109. {
  110. navigationWidget.Refresh();
  111. };
  112. infantry.PropertyChanged += SelectedInfantry_PropertyChanged;
  113. selectedObjectProperties.Show(mapPanel, mapPanel.PointToClient(Control.MousePosition));
  114. UpdateStatus();
  115. }
  116. }
  117. }
  118. }
  119. private void MockInfantry_PropertyChanged(object sender, PropertyChangedEventArgs e)
  120. {
  121. RefreshMapPanel();
  122. }
  123. private void SelectedInfantry_PropertyChanged(object sender, PropertyChangedEventArgs e)
  124. {
  125. mapPanel.Invalidate(map, (sender as Infantry).InfantryGroup);
  126. }
  127. private void InfantryTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
  128. {
  129. SelectedInfantryType = infantryTypeComboBox.SelectedValue as InfantryType;
  130. }
  131. private void InfantryTool_KeyDown(object sender, KeyEventArgs e)
  132. {
  133. if (e.KeyCode == Keys.ShiftKey)
  134. {
  135. EnterPlacementMode();
  136. }
  137. }
  138. private void InfantryTool_KeyUp(object sender, KeyEventArgs e)
  139. {
  140. if (e.KeyCode == Keys.ShiftKey)
  141. {
  142. ExitPlacementMode();
  143. }
  144. }
  145. private void MapPanel_MouseMove(object sender, MouseEventArgs e)
  146. {
  147. if (!placementMode && (Control.ModifierKeys == Keys.Shift))
  148. {
  149. EnterPlacementMode();
  150. }
  151. else if (placementMode && (Control.ModifierKeys == Keys.None))
  152. {
  153. ExitPlacementMode();
  154. }
  155. if (placementMode)
  156. {
  157. mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1));
  158. }
  159. else if (selectedInfantry != null)
  160. {
  161. var oldLocation = map.Technos[selectedInfantry.InfantryGroup].Value;
  162. var oldStop = Array.IndexOf(selectedInfantry.InfantryGroup.Infantry, selectedInfantry);
  163. InfantryGroup infantryGroup = null;
  164. var techno = map.Technos[navigationWidget.MouseCell];
  165. if (techno == null)
  166. {
  167. infantryGroup = new InfantryGroup();
  168. map.Technos.Add(navigationWidget.MouseCell, infantryGroup);
  169. }
  170. else if (techno is InfantryGroup)
  171. {
  172. infantryGroup = techno as InfantryGroup;
  173. }
  174. if (infantryGroup != null)
  175. {
  176. foreach (var i in InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>())
  177. {
  178. if (infantryGroup.Infantry[i] == null)
  179. {
  180. selectedInfantry.InfantryGroup.Infantry[oldStop] = null;
  181. infantryGroup.Infantry[i] = selectedInfantry;
  182. if (infantryGroup != selectedInfantry.InfantryGroup)
  183. {
  184. mapPanel.Invalidate(map, selectedInfantry.InfantryGroup);
  185. if (selectedInfantry.InfantryGroup.Infantry.All(x => x == null))
  186. {
  187. map.Technos.Remove(selectedInfantry.InfantryGroup);
  188. }
  189. }
  190. selectedInfantry.InfantryGroup = infantryGroup;
  191. mapPanel.Invalidate(map, infantryGroup);
  192. plugin.Dirty = true;
  193. }
  194. if (infantryGroup == selectedInfantry.InfantryGroup)
  195. {
  196. break;
  197. }
  198. }
  199. }
  200. }
  201. }
  202. private void MapPanel_MouseDown(object sender, MouseEventArgs e)
  203. {
  204. if (placementMode)
  205. {
  206. if (e.Button == MouseButtons.Left)
  207. {
  208. AddInfantry(navigationWidget.MouseCell);
  209. }
  210. else if (e.Button == MouseButtons.Right)
  211. {
  212. RemoveInfantry(navigationWidget.MouseCell);
  213. }
  214. }
  215. else if (e.Button == MouseButtons.Left)
  216. {
  217. SelectInfantry(navigationWidget.MouseCell);
  218. }
  219. else if (e.Button == MouseButtons.Right)
  220. {
  221. PickInfantry(navigationWidget.MouseCell);
  222. }
  223. }
  224. private void MapPanel_MouseUp(object sender, MouseEventArgs e)
  225. {
  226. if (selectedInfantry != null)
  227. {
  228. selectedInfantry = null;
  229. UpdateStatus();
  230. }
  231. }
  232. private void MouseoverWidget_MouseCellChanged(object sender, MouseCellChangedEventArgs e)
  233. {
  234. if (placementMode)
  235. {
  236. if (SelectedInfantryType != null)
  237. {
  238. mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(e.OldCell, new Size(1, 1)), 1, 1));
  239. mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(e.NewCell, new Size(1, 1)), 1, 1));
  240. }
  241. }
  242. }
  243. private void AddInfantry(Point location)
  244. {
  245. if (SelectedInfantryType != null)
  246. {
  247. if (map.Metrics.GetCell(location, out int cell))
  248. {
  249. InfantryGroup infantryGroup = null;
  250. var techno = map.Technos[cell];
  251. if (techno == null)
  252. {
  253. infantryGroup = new InfantryGroup();
  254. map.Technos.Add(cell, infantryGroup);
  255. }
  256. else if (techno is InfantryGroup)
  257. {
  258. infantryGroup = techno as InfantryGroup;
  259. }
  260. if (infantryGroup != null)
  261. {
  262. foreach (var i in InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>())
  263. {
  264. if (infantryGroup.Infantry[i] == null)
  265. {
  266. var infantry = mockInfantry.Clone();
  267. infantryGroup.Infantry[i] = infantry;
  268. infantry.InfantryGroup = infantryGroup;
  269. mapPanel.Invalidate(map, infantryGroup);
  270. plugin.Dirty = true;
  271. break;
  272. }
  273. }
  274. }
  275. }
  276. }
  277. }
  278. private void RemoveInfantry(Point location)
  279. {
  280. if (map.Metrics.GetCell(location, out int cell))
  281. {
  282. if (map.Technos[cell] is InfantryGroup infantryGroup)
  283. {
  284. foreach (var i in InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>())
  285. {
  286. if (infantryGroup.Infantry[i] != null)
  287. {
  288. infantryGroup.Infantry[i] = null;
  289. mapPanel.Invalidate(map, infantryGroup);
  290. plugin.Dirty = true;
  291. break;
  292. }
  293. }
  294. if (infantryGroup.Infantry.All(i => i == null))
  295. {
  296. map.Technos.Remove(infantryGroup);
  297. }
  298. }
  299. }
  300. }
  301. private void EnterPlacementMode()
  302. {
  303. if (placementMode)
  304. {
  305. return;
  306. }
  307. placementMode = true;
  308. navigationWidget.MouseoverSize = Size.Empty;
  309. if (SelectedInfantryType != null)
  310. {
  311. mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1));
  312. }
  313. UpdateStatus();
  314. }
  315. private void ExitPlacementMode()
  316. {
  317. if (!placementMode)
  318. {
  319. return;
  320. }
  321. placementMode = false;
  322. navigationWidget.MouseoverSize = new Size(1, 1);
  323. if (SelectedInfantryType != null)
  324. {
  325. mapPanel.Invalidate(map, Rectangle.Inflate(new Rectangle(navigationWidget.MouseCell, new Size(1, 1)), 1, 1));
  326. }
  327. UpdateStatus();
  328. }
  329. private void PickInfantry(Point location)
  330. {
  331. if (map.Metrics.GetCell(location, out int cell))
  332. {
  333. if (map.Technos[cell] is InfantryGroup infantryGroup)
  334. {
  335. var i = InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>().First();
  336. if (infantryGroup.Infantry[i] is Infantry infantry)
  337. {
  338. SelectedInfantryType = infantry.Type;
  339. mockInfantry.House = infantry.House;
  340. mockInfantry.Strength = infantry.Strength;
  341. mockInfantry.Direction = infantry.Direction;
  342. mockInfantry.Mission = infantry.Mission;
  343. mockInfantry.Trigger = infantry.Trigger;
  344. }
  345. }
  346. }
  347. }
  348. private void SelectInfantry(Point location)
  349. {
  350. if (map.Metrics.GetCell(location, out int cell))
  351. {
  352. selectedInfantry = null;
  353. if (map.Technos[cell] is InfantryGroup infantryGroup)
  354. {
  355. var i = InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>().First();
  356. if (infantryGroup.Infantry[i] is Infantry infantry)
  357. {
  358. selectedInfantry = infantry;
  359. }
  360. }
  361. }
  362. UpdateStatus();
  363. }
  364. private void RefreshMapPanel()
  365. {
  366. if (mockInfantry.Type != null)
  367. {
  368. var infantryPreview = new Bitmap(Globals.TileWidth, Globals.TileHeight);
  369. using (var g = Graphics.FromImage(infantryPreview))
  370. {
  371. MapRenderer.Render(map.Theater, Point.Empty, Globals.TileSize, mockInfantry, InfantryStoppingType.Center).Item2(g);
  372. }
  373. infantryTypeMapPanel.MapImage = infantryPreview;
  374. }
  375. else
  376. {
  377. infantryTypeMapPanel.MapImage = null;
  378. }
  379. }
  380. private void UpdateStatus()
  381. {
  382. if (placementMode)
  383. {
  384. statusLbl.Text = "Left-Click to place infantry, Right-Click to remove infantry";
  385. }
  386. else if (selectedInfantry != null)
  387. {
  388. statusLbl.Text = "Drag mouse to move infantry";
  389. }
  390. else
  391. {
  392. statusLbl.Text = "Shift to enter placement mode, Left-Click drag to move infantry, Double-Click update infantry properties, Right-Click to pick infantry";
  393. }
  394. }
  395. protected override void PreRenderMap()
  396. {
  397. base.PreRenderMap();
  398. previewMap = map.Clone();
  399. if (placementMode)
  400. {
  401. var location = navigationWidget.MouseCell;
  402. if (SelectedInfantryType != null)
  403. {
  404. if (previewMap.Metrics.GetCell(location, out int cell))
  405. {
  406. InfantryGroup infantryGroup = null;
  407. var techno = previewMap.Technos[cell];
  408. if (techno == null)
  409. {
  410. infantryGroup = new InfantryGroup();
  411. previewMap.Technos.Add(cell, infantryGroup);
  412. }
  413. else if (techno is InfantryGroup)
  414. {
  415. infantryGroup = techno as InfantryGroup;
  416. }
  417. if (infantryGroup != null)
  418. {
  419. foreach (var i in InfantryGroup.ClosestStoppingTypes(navigationWidget.MouseSubPixel).Cast<int>())
  420. {
  421. if (infantryGroup.Infantry[i] == null)
  422. {
  423. var infantry = mockInfantry.Clone();
  424. infantry.Tint = Color.FromArgb(128, Color.White);
  425. infantryGroup.Infantry[i] = infantry;
  426. break;
  427. }
  428. }
  429. }
  430. }
  431. }
  432. }
  433. }
  434. protected override void PostRenderMap(Graphics graphics)
  435. {
  436. base.PostRenderMap(graphics);
  437. var infantryPen = new Pen(Color.Green, 4.0f);
  438. foreach (var (topLeft, _) in map.Technos.OfType<InfantryGroup>())
  439. {
  440. var bounds = new Rectangle(new Point(topLeft.X * Globals.TileWidth, topLeft.Y * Globals.TileHeight), Globals.TileSize);
  441. graphics.DrawRectangle(infantryPen, bounds);
  442. }
  443. }
  444. #region IDisposable Support
  445. private bool disposedValue = false;
  446. protected override void Dispose(bool disposing)
  447. {
  448. if (!disposedValue)
  449. {
  450. if (disposing)
  451. {
  452. mapPanel.MouseDown -= MapPanel_MouseDown;
  453. mapPanel.MouseUp -= MapPanel_MouseUp;
  454. mapPanel.MouseDoubleClick -= MapPanel_MouseDoubleClick;
  455. mapPanel.MouseMove -= MapPanel_MouseMove;
  456. (mapPanel as Control).KeyDown -= InfantryTool_KeyDown;
  457. (mapPanel as Control).KeyUp -= InfantryTool_KeyUp;
  458. infantryTypeComboBox.SelectedIndexChanged -= InfantryTypeComboBox_SelectedIndexChanged;
  459. navigationWidget.MouseCellChanged -= MouseoverWidget_MouseCellChanged;
  460. }
  461. disposedValue = true;
  462. }
  463. base.Dispose(disposing);
  464. }
  465. #endregion
  466. }
  467. }