TerrainTool.cs 16 KB

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