MapRenderer.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  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.Interface;
  15. using MobiusEditor.Model;
  16. using MobiusEditor.Utility;
  17. using System;
  18. using System.Collections.Generic;
  19. using System.Diagnostics;
  20. using System.Drawing;
  21. using System.Drawing.Imaging;
  22. using System.Linq;
  23. namespace MobiusEditor.Render
  24. {
  25. public static class MapRenderer
  26. {
  27. private static readonly int[] Facing16 = new int[256]
  28. {
  29. 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,
  30. 2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,
  31. 4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,
  32. 6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8,
  33. 8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,
  34. 10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,
  35. 12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,
  36. 14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0
  37. };
  38. private static readonly int[] Facing32 = new int[256]
  39. {
  40. 0,0,0,0,0,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,
  41. 3,4,4,4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6,6,6,7,7,7,7,7,7,7,8,8,8,8,
  42. 8,8,8,9,9,9,9,9,9,9,10,10,10,10,10,10,10,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,
  43. 13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,
  44. 16,16,16,16,16,17,17,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19,
  45. 19,20,20,20,20,20,20,21,21,21,21,21,21,21,22,22,22,22,22,22,22,23,23,23,23,23,23,23,24,24,24,24,
  46. 24,24,24,25,25,25,25,25,25,25,26,26,26,26,26,26,26,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,
  47. 29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,31,31,31,31,31,31,31,31,31,0,0,0,0,0,0
  48. };
  49. private static readonly int[] HumanShape = new int[32]
  50. {
  51. 0,0,7,7,7,7,6,6,6,6,5,5,5,5,5,4,4,4,3,3,3,3,2,2,2,2,1,1,1,1,1,0
  52. };
  53. private static readonly int[] BodyShape = new int[32]
  54. {
  55. 0,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1
  56. };
  57. private static readonly Point[] TurretAdjust = new Point[]
  58. {
  59. new Point(1, 2), // N
  60. new Point(-1, 1),
  61. new Point(-2, 0),
  62. new Point(-3, 0),
  63. new Point(-3, 1), // NW
  64. new Point(-4, -1),
  65. new Point(-4, -1),
  66. new Point(-5, -2),
  67. new Point(-5, -3), // W
  68. new Point(-5, -3),
  69. new Point(-3, -3),
  70. new Point(-3, -4),
  71. new Point(-3, -4), // SW
  72. new Point(-3, -5),
  73. new Point(-2, -5),
  74. new Point(-1, -5),
  75. new Point(0, -5), // S
  76. new Point(1, -6),
  77. new Point(2, -5),
  78. new Point(3, -5),
  79. new Point(4, -5), // SE
  80. new Point(6, -4),
  81. new Point(6, -3),
  82. new Point(6, -3),
  83. new Point(6, -3), // E
  84. new Point(5, -1),
  85. new Point(5, -1),
  86. new Point(4, 0),
  87. new Point(3, 0), // NE
  88. new Point(2, 0),
  89. new Point(2, 1),
  90. new Point(1, 2)
  91. };
  92. private static readonly int[] tiberiumCounts = new int[] { 0, 1, 3, 4, 6, 7, 8, 10, 11 };
  93. private static readonly int randomSeed;
  94. static MapRenderer()
  95. {
  96. randomSeed = Guid.NewGuid().GetHashCode();
  97. }
  98. public static void Render(GameType gameType, Map map, Graphics graphics, ISet<Point> locations, MapLayerFlag layers, int tileScale)
  99. {
  100. var tileSize = new Size(Globals.OriginalTileWidth / tileScale, Globals.OriginalTileHeight / tileScale);
  101. var tiberiumOrGoldTypes = map.OverlayTypes.Where(t => t.IsTiberiumOrGold).Select(t => t).ToArray();
  102. var gemTypes = map.OverlayTypes.Where(t => t.IsGem).ToArray();
  103. var overlappingRenderList = new List<(Rectangle, Action<Graphics>)>();
  104. Func<IEnumerable<Point>> renderLocations = null;
  105. if (locations != null)
  106. {
  107. renderLocations = () => locations;
  108. }
  109. else
  110. {
  111. IEnumerable<Point> allCells()
  112. {
  113. for (var y = 0; y < map.Metrics.Height; ++y)
  114. {
  115. for (var x = 0; x < map.Metrics.Width; ++x)
  116. {
  117. yield return new Point(x, y);
  118. }
  119. }
  120. }
  121. renderLocations = allCells;
  122. }
  123. if ((layers & MapLayerFlag.Template) != MapLayerFlag.None)
  124. {
  125. foreach (var topLeft in renderLocations())
  126. {
  127. map.Metrics.GetCell(topLeft, out int cell);
  128. var template = map.Templates[topLeft];
  129. var name = template?.Type.Name ?? map.TemplateTypes.Where(t => t.Equals("clear1")).FirstOrDefault().Name;
  130. var icon = template?.Icon ?? ((cell & 0x03) | ((cell >> 4) & 0x0C));
  131. if (Globals.TheTilesetManager.GetTileData(map.Theater.Tilesets, name, icon, out Tile tile))
  132. {
  133. var renderBounds = new Rectangle(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height, tileSize.Width, tileSize.Height);
  134. graphics.DrawImage(tile.Image, renderBounds);
  135. }
  136. else
  137. {
  138. Debug.Print(string.Format("Template {0} ({1}) not found", name, icon));
  139. }
  140. }
  141. }
  142. if ((layers & MapLayerFlag.Smudge) != MapLayerFlag.None)
  143. {
  144. foreach (var topLeft in renderLocations())
  145. {
  146. var smudge = map.Smudge[topLeft];
  147. if (smudge != null)
  148. {
  149. Render(map.Theater, topLeft, tileSize, smudge).Item2(graphics);
  150. }
  151. }
  152. }
  153. if ((layers & MapLayerFlag.OverlayAll) != MapLayerFlag.None)
  154. {
  155. foreach (var topLeft in renderLocations())
  156. {
  157. var overlay = map.Overlay[topLeft];
  158. if (overlay == null)
  159. {
  160. continue;
  161. }
  162. if ((overlay.Type.IsResource && ((layers & MapLayerFlag.Resources) != MapLayerFlag.None)) ||
  163. (overlay.Type.IsWall && ((layers & MapLayerFlag.Walls) != MapLayerFlag.None)) ||
  164. ((layers & MapLayerFlag.Overlay) != MapLayerFlag.None))
  165. {
  166. Render(map.Theater, tiberiumOrGoldTypes, gemTypes, topLeft, tileSize, tileScale, overlay).Item2(graphics);
  167. }
  168. }
  169. }
  170. if ((layers & MapLayerFlag.Terrain) != MapLayerFlag.None)
  171. {
  172. foreach (var (topLeft, terrain) in map.Technos.OfType<Terrain>())
  173. {
  174. if ((locations != null) && !locations.Contains(topLeft))
  175. {
  176. continue;
  177. }
  178. string tileName = terrain.Type.Name;
  179. if ((terrain.Type.TemplateType & TemplateTypeFlag.OreMine) != TemplateTypeFlag.None)
  180. {
  181. tileName = "OREMINE";
  182. }
  183. if (Globals.TheTilesetManager.GetTileData(map.Theater.Tilesets, tileName, terrain.Icon, out Tile tile))
  184. {
  185. var tint = terrain.Tint;
  186. var imageAttributes = new ImageAttributes();
  187. if (tint != Color.White)
  188. {
  189. var colorMatrix = new ColorMatrix(new float[][]
  190. {
  191. new float[] {tint.R / 255.0f, 0, 0, 0, 0},
  192. new float[] {0, tint.G / 255.0f, 0, 0, 0},
  193. new float[] {0, 0, tint.B / 255.0f, 0, 0},
  194. new float[] {0, 0, 0, tint.A / 255.0f, 0},
  195. new float[] {0, 0, 0, 0, 1},
  196. }
  197. );
  198. imageAttributes.SetColorMatrix(colorMatrix);
  199. }
  200. var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height);
  201. var size = new Size(tile.Image.Width / tileScale, tile.Image.Height / tileScale);
  202. var terrainBounds = new Rectangle(location, size);
  203. overlappingRenderList.Add((terrainBounds, g => g.DrawImage(tile.Image, terrainBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes)));
  204. }
  205. else
  206. {
  207. Debug.Print(string.Format("Terrain {0} ({1}) not found", tileName, terrain.Icon));
  208. }
  209. }
  210. }
  211. if ((layers & MapLayerFlag.Buildings) != MapLayerFlag.None)
  212. {
  213. foreach (var (topLeft, building) in map.Buildings.OfType<Building>())
  214. {
  215. if ((locations != null) && !locations.Contains(topLeft))
  216. {
  217. continue;
  218. }
  219. overlappingRenderList.Add(Render(gameType, map.Theater, topLeft, tileSize, tileScale, building));
  220. }
  221. }
  222. if ((layers & MapLayerFlag.Infantry) != MapLayerFlag.None)
  223. {
  224. foreach (var (topLeft, infantryGroup) in map.Technos.OfType<InfantryGroup>())
  225. {
  226. if ((locations != null) && !locations.Contains(topLeft))
  227. {
  228. continue;
  229. }
  230. for (int i = 0; i < infantryGroup.Infantry.Length; ++i)
  231. {
  232. var infantry = infantryGroup.Infantry[i];
  233. if (infantry == null)
  234. {
  235. continue;
  236. }
  237. overlappingRenderList.Add(Render(map.Theater, topLeft, tileSize, infantry, (InfantryStoppingType)i));
  238. }
  239. }
  240. }
  241. if ((layers & MapLayerFlag.Units) != MapLayerFlag.None)
  242. {
  243. foreach (var (topLeft, unit) in map.Technos.OfType<Unit>())
  244. {
  245. if ((locations != null) && !locations.Contains(topLeft))
  246. {
  247. continue;
  248. }
  249. overlappingRenderList.Add(Render(gameType, map.Theater, topLeft, tileSize, unit));
  250. }
  251. }
  252. foreach (var (location, renderer) in overlappingRenderList.Where(x => !x.Item1.IsEmpty).OrderBy(x => x.Item1.Bottom))
  253. {
  254. renderer(graphics);
  255. }
  256. }
  257. public static void Render(GameType gameType, Map map, Graphics graphics, ISet<Point> locations, MapLayerFlag layers)
  258. {
  259. Render(gameType, map, graphics, locations, layers, Globals.TileScale);
  260. }
  261. public static (Rectangle, Action<Graphics>) Render(TheaterType theater, Point topLeft, Size tileSize, Smudge smudge)
  262. {
  263. var tint = smudge.Tint;
  264. var imageAttributes = new ImageAttributes();
  265. if (tint != Color.White)
  266. {
  267. var colorMatrix = new ColorMatrix(new float[][]
  268. {
  269. new float[] {tint.R / 255.0f, 0, 0, 0, 0},
  270. new float[] {0, tint.G / 255.0f, 0, 0, 0},
  271. new float[] {0, 0, tint.B / 255.0f, 0, 0},
  272. new float[] {0, 0, 0, tint.A / 255.0f, 0},
  273. new float[] {0, 0, 0, 0, 1},
  274. }
  275. );
  276. imageAttributes.SetColorMatrix(colorMatrix);
  277. }
  278. if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, smudge.Type.Name, smudge.Icon, out Tile tile))
  279. {
  280. var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height);
  281. var smudgeBounds = new Rectangle(location, smudge.Type.RenderSize);
  282. void render(Graphics g)
  283. {
  284. g.DrawImage(tile.Image, smudgeBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
  285. }
  286. return (smudgeBounds, render);
  287. }
  288. else
  289. {
  290. Debug.Print(string.Format("Smudge {0} ({1}) not found", smudge.Type.Name, smudge.Icon));
  291. return (Rectangle.Empty, (g) => { });
  292. }
  293. }
  294. public static (Rectangle, Action<Graphics>) Render(TheaterType theater, OverlayType[] tiberiumOrGoldTypes, OverlayType[] gemTypes, Point topLeft, Size tileSize, int tileScale, Overlay overlay)
  295. {
  296. var name = overlay.Type.Name;
  297. if (overlay.Type.IsGem)
  298. {
  299. name = gemTypes[new Random(randomSeed ^ topLeft.GetHashCode()).Next(tiberiumOrGoldTypes.Length)].Name;
  300. }
  301. else if (overlay.Type.IsTiberiumOrGold)
  302. {
  303. name = tiberiumOrGoldTypes[new Random(randomSeed ^ topLeft.GetHashCode()).Next(tiberiumOrGoldTypes.Length)].Name;
  304. }
  305. if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, name, overlay.Icon, out Tile tile))
  306. {
  307. var size = (overlay.Type.IsCrate || overlay.Type.IsFlag) ? new Size(tile.Image.Width / tileScale, tile.Image.Height / tileScale) : tileSize;
  308. var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height)
  309. + new Size(tileSize.Width / 2, tileSize.Height / 2)
  310. - new Size(size.Width / 2, size.Height / 2);
  311. var overlayBounds = new Rectangle(location, size);
  312. var tint = overlay.Tint;
  313. void render(Graphics g)
  314. {
  315. var imageAttributes = new ImageAttributes();
  316. if (tint != Color.White)
  317. {
  318. var colorMatrix = new ColorMatrix(new float[][]
  319. {
  320. new float[] {tint.R / 255.0f, 0, 0, 0, 0},
  321. new float[] {0, tint.G / 255.0f, 0, 0, 0},
  322. new float[] {0, 0, tint.B / 255.0f, 0, 0},
  323. new float[] {0, 0, 0, tint.A / 255.0f, 0},
  324. new float[] {0, 0, 0, 0, 1},
  325. }
  326. );
  327. imageAttributes.SetColorMatrix(colorMatrix);
  328. }
  329. g.DrawImage(tile.Image, overlayBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
  330. }
  331. return (overlayBounds, render);
  332. }
  333. else
  334. {
  335. Debug.Print(string.Format("Overlay {0} ({1}) not found", overlay.Type.Name, overlay.Icon));
  336. return (Rectangle.Empty, (g) => { });
  337. }
  338. }
  339. public static (Rectangle, Action<Graphics>) Render(GameType gameType, TheaterType theater, Point topLeft, Size tileSize, int tileScale, Building building)
  340. {
  341. var tint = building.Tint;
  342. var stringFormat = new StringFormat
  343. {
  344. Alignment = StringAlignment.Center,
  345. LineAlignment = StringAlignment.Center
  346. };
  347. var fakeBackgroundBrush = new SolidBrush(Color.FromArgb(building.Tint.A / 2, Color.Black));
  348. var fakeTextBrush = new SolidBrush(Color.FromArgb(building.Tint.A, Color.White));
  349. var baseBackgroundBrush = new SolidBrush(Color.FromArgb(building.Tint.A / 2, Color.Black));
  350. var baseTextBrush = new SolidBrush(Color.FromArgb(building.Tint.A, Color.Red));
  351. var icon = 0;
  352. if (building.Type.HasTurret)
  353. {
  354. icon = BodyShape[Facing32[building.Direction.ID]];
  355. if (building.Strength < 128)
  356. {
  357. switch (gameType)
  358. {
  359. case GameType.TiberianDawn:
  360. icon += 64;
  361. break;
  362. case GameType.RedAlert:
  363. icon += building.Type.Equals("sam") ? 35 : 64;
  364. break;
  365. }
  366. }
  367. }
  368. else
  369. {
  370. if (building.Strength <= 1)
  371. {
  372. icon = -1;
  373. }
  374. else if (building.Strength < 128)
  375. {
  376. icon = -2;
  377. if (building.Type.Equals("weap") || building.Type.Equals("weaf"))
  378. {
  379. icon = 1;
  380. }
  381. else if ((gameType == GameType.TiberianDawn) && building.Type.Equals("proc"))
  382. {
  383. icon = 30;
  384. }
  385. else if (building.Type.Equals("eye"))
  386. {
  387. icon = 16;
  388. }
  389. else if (building.Type.Equals("silo"))
  390. {
  391. icon = 5;
  392. }
  393. else if (building.Type.Equals("fix"))
  394. {
  395. icon = 7;
  396. }
  397. else if (building.Type.Equals("v19"))
  398. {
  399. icon = 14;
  400. }
  401. }
  402. }
  403. if (Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, building.Type.Tilename, icon, Globals.TheTeamColorManager[building.House.BuildingTeamColor], out Tile tile))
  404. {
  405. var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height);
  406. var size = new Size(tile.Image.Width / tileScale, tile.Image.Height / tileScale);
  407. var maxSize = new Size(building.Type.Size.Width * tileSize.Width, building.Type.Size.Height * tileSize.Height);
  408. if ((size.Width >= size.Height) && (size.Width > maxSize.Width))
  409. {
  410. size.Height = size.Height * maxSize.Width / size.Width;
  411. size.Width = maxSize.Width;
  412. }
  413. else if ((size.Height >= size.Width) && (size.Height > maxSize.Height))
  414. {
  415. size.Width = size.Width * maxSize.Height / size.Height;
  416. size.Height = maxSize.Height;
  417. }
  418. var buildingBounds = new Rectangle(location, size);
  419. Tile factoryOverlayTile = null;
  420. if (building.Type.FactoryOverlay != null)
  421. {
  422. int overlayIcon = 0;
  423. if (building.Strength < 128)
  424. {
  425. switch (gameType)
  426. {
  427. case GameType.TiberianDawn:
  428. overlayIcon = 10;
  429. break;
  430. case GameType.RedAlert:
  431. overlayIcon = 4;
  432. break;
  433. }
  434. }
  435. Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, building.Type.FactoryOverlay, overlayIcon, Globals.TheTeamColorManager[building.House.BuildingTeamColor], out factoryOverlayTile);
  436. }
  437. void render(Graphics g)
  438. {
  439. var imageAttributes = new ImageAttributes();
  440. if (tint != Color.White)
  441. {
  442. var colorMatrix = new ColorMatrix(new float[][]
  443. {
  444. new float[] {tint.R / 255.0f, 0, 0, 0, 0},
  445. new float[] {0, tint.G / 255.0f, 0, 0, 0},
  446. new float[] {0, 0, tint.B / 255.0f, 0, 0},
  447. new float[] {0, 0, 0, tint.A / 255.0f, 0},
  448. new float[] {0, 0, 0, 0, 1},
  449. }
  450. );
  451. imageAttributes.SetColorMatrix(colorMatrix);
  452. }
  453. if (factoryOverlayTile != null)
  454. {
  455. g.DrawImage(tile.Image, buildingBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
  456. g.DrawImage(factoryOverlayTile.Image, buildingBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
  457. }
  458. else
  459. {
  460. g.DrawImage(tile.Image, buildingBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
  461. }
  462. if (building.Type.IsFake)
  463. {
  464. var text = Globals.TheGameTextManager["TEXT_UI_FAKE"];
  465. var textSize = g.MeasureString(text, SystemFonts.CaptionFont) + new SizeF(6.0f, 6.0f);
  466. var textBounds = new RectangleF(buildingBounds.Location, textSize);
  467. g.FillRectangle(fakeBackgroundBrush, textBounds);
  468. g.DrawString(text, SystemFonts.CaptionFont, fakeTextBrush, textBounds, stringFormat);
  469. }
  470. if (building.BasePriority >= 0)
  471. {
  472. var text = building.BasePriority.ToString();
  473. var textSize = g.MeasureString(text, SystemFonts.CaptionFont) + new SizeF(6.0f, 6.0f);
  474. var textBounds = new RectangleF(buildingBounds.Location +
  475. new Size((int)((buildingBounds.Width - textSize.Width) / 2.0f), (int)(buildingBounds.Height - textSize.Height)),
  476. textSize
  477. );
  478. g.FillRectangle(baseBackgroundBrush, textBounds);
  479. g.DrawString(text, SystemFonts.CaptionFont, baseTextBrush, textBounds, stringFormat);
  480. }
  481. }
  482. return (buildingBounds, render);
  483. }
  484. else
  485. {
  486. Debug.Print(string.Format("Building {0} (0) not found", building.Type.Name));
  487. return (Rectangle.Empty, (g) => { });
  488. }
  489. }
  490. public static (Rectangle, Action<Graphics>) Render(TheaterType theater, Point topLeft, Size tileSize, Infantry infantry, InfantryStoppingType infantryStoppingType)
  491. {
  492. var icon = HumanShape[Facing32[infantry.Direction.ID]];
  493. string teamColor = infantry.House?.UnitTeamColor;
  494. if (Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, infantry.Type.Name, icon, Globals.TheTeamColorManager[teamColor], out Tile tile))
  495. {
  496. var baseLocation = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height)
  497. + new Size(tileSize.Width / 2, tileSize.Height / 2);
  498. var offset = Point.Empty;
  499. switch (infantryStoppingType)
  500. {
  501. case InfantryStoppingType.UpperLeft:
  502. offset.X = -tileSize.Width / 4;
  503. offset.Y = -tileSize.Height / 4;
  504. break;
  505. case InfantryStoppingType.UpperRight:
  506. offset.X = tileSize.Width / 4;
  507. offset.Y = -tileSize.Height / 4;
  508. break;
  509. case InfantryStoppingType.LowerLeft:
  510. offset.X = -tileSize.Width / 4;
  511. offset.Y = tileSize.Height / 4;
  512. break;
  513. case InfantryStoppingType.LowerRight:
  514. offset.X = tileSize.Width / 4;
  515. offset.Y = tileSize.Height / 4;
  516. break;
  517. }
  518. baseLocation.Offset(offset);
  519. var virtualBounds = new Rectangle(
  520. new Point(baseLocation.X - (tile.OpaqueBounds.Width / 2), baseLocation.Y - tile.OpaqueBounds.Height),
  521. tile.OpaqueBounds.Size
  522. );
  523. var renderBounds = new Rectangle(
  524. baseLocation - new Size(infantry.Type.RenderSize.Width / 2, infantry.Type.RenderSize.Height / 2),
  525. infantry.Type.RenderSize
  526. );
  527. var tint = infantry.Tint;
  528. void render(Graphics g)
  529. {
  530. var imageAttributes = new ImageAttributes();
  531. if (tint != Color.White)
  532. {
  533. var colorMatrix = new ColorMatrix(new float[][]
  534. {
  535. new float[] {tint.R / 255.0f, 0, 0, 0, 0},
  536. new float[] {0, tint.G / 255.0f, 0, 0, 0},
  537. new float[] {0, 0, tint.B / 255.0f, 0, 0},
  538. new float[] {0, 0, 0, tint.A / 255.0f, 0},
  539. new float[] {0, 0, 0, 0, 1},
  540. }
  541. );
  542. imageAttributes.SetColorMatrix(colorMatrix);
  543. }
  544. g.DrawImage(tile.Image, renderBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
  545. }
  546. return (virtualBounds, render);
  547. }
  548. else
  549. {
  550. Debug.Print(string.Format("Infantry {0} ({1}) not found", infantry.Type.Name, icon));
  551. return (Rectangle.Empty, (g) => { });
  552. }
  553. }
  554. public static (Rectangle, Action<Graphics>) Render(GameType gameType, TheaterType theater, Point topLeft, Size tileSize, Unit unit)
  555. {
  556. int icon = 0;
  557. if (gameType == GameType.TiberianDawn)
  558. {
  559. if (unit.Type == TiberianDawn.UnitTypes.GunBoat)
  560. {
  561. switch (unit.Direction.Facing)
  562. {
  563. case FacingType.NorthEast:
  564. case FacingType.East:
  565. case FacingType.SouthEast:
  566. icon = 96;
  567. break;
  568. default:
  569. icon = 0;
  570. break;
  571. }
  572. }
  573. else if ((unit.Type == TiberianDawn.UnitTypes.Tric) ||
  574. (unit.Type == TiberianDawn.UnitTypes.Trex) ||
  575. (unit.Type == TiberianDawn.UnitTypes.Rapt) ||
  576. (unit.Type == TiberianDawn.UnitTypes.Steg))
  577. {
  578. var facing = ((unit.Direction.ID + 0x10) & 0xFF) >> 5;
  579. icon = BodyShape[facing + ((facing > 0) ? 24 : 0)];
  580. }
  581. else if ((unit.Type == TiberianDawn.UnitTypes.Hover) ||
  582. (unit.Type == TiberianDawn.UnitTypes.Visceroid))
  583. {
  584. icon = 0;
  585. }
  586. else
  587. {
  588. icon = BodyShape[Facing32[unit.Direction.ID]];
  589. }
  590. }
  591. else if (gameType == GameType.RedAlert)
  592. {
  593. if (unit.Type.IsAircraft)
  594. {
  595. if ((unit.Type == RedAlert.UnitTypes.Tran) ||
  596. (unit.Type == RedAlert.UnitTypes.Heli) ||
  597. (unit.Type == RedAlert.UnitTypes.Hind))
  598. {
  599. icon = BodyShape[Facing32[unit.Direction.ID]];
  600. }
  601. else
  602. {
  603. icon = BodyShape[Facing16[unit.Direction.ID] * 2] / 2;
  604. }
  605. }
  606. else if (unit.Type.IsVessel)
  607. {
  608. if ((unit.Type == RedAlert.UnitTypes.Transport) ||
  609. (unit.Type == RedAlert.UnitTypes.Carrier))
  610. {
  611. icon = 0;
  612. }
  613. else
  614. {
  615. icon = BodyShape[Facing16[unit.Direction.ID] * 2] >> 1;
  616. }
  617. }
  618. else
  619. {
  620. if ((unit.Type == RedAlert.UnitTypes.Ant1) ||
  621. (unit.Type == RedAlert.UnitTypes.Ant2) ||
  622. (unit.Type == RedAlert.UnitTypes.Ant3))
  623. {
  624. icon = ((BodyShape[Facing32[unit.Direction.ID]] + 2) / 4) & 0x07;
  625. }
  626. else
  627. {
  628. icon = BodyShape[Facing32[unit.Direction.ID]];
  629. }
  630. }
  631. }
  632. string teamColor = null;
  633. if (unit.House != null)
  634. {
  635. if (!unit.House.OverrideTeamColors.TryGetValue(unit.Type.Name, out teamColor))
  636. {
  637. teamColor = unit.House.UnitTeamColor;
  638. }
  639. }
  640. if (Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, unit.Type.Name, icon, Globals.TheTeamColorManager[teamColor], out Tile tile))
  641. {
  642. var location =
  643. new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height) +
  644. new Size(tileSize.Width / 2, tileSize.Height / 2);
  645. var renderBounds = new Rectangle(
  646. location - new Size(unit.Type.RenderSize.Width / 2, unit.Type.RenderSize.Height / 2),
  647. unit.Type.RenderSize
  648. );
  649. Tile radarTile = null;
  650. if ((unit.Type == RedAlert.UnitTypes.MGG) ||
  651. (unit.Type == RedAlert.UnitTypes.MRJammer) ||
  652. (unit.Type == RedAlert.UnitTypes.Tesla))
  653. {
  654. Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, unit.Type.Name, 32, Globals.TheTeamColorManager[teamColor], out radarTile);
  655. }
  656. Tile turretTile = null;
  657. if (unit.Type.HasTurret)
  658. {
  659. var turretName = unit.Type.Name;
  660. var turretIcon = icon + 32;
  661. if (unit.Type == RedAlert.UnitTypes.Phase)
  662. {
  663. turretIcon += 6;
  664. }
  665. #if TODO
  666. else if (unit.Type == RedAlert.UnitTypes.Cruiser)
  667. {
  668. turretName = "TURR";
  669. turretIcon = BodyShape[Facing32[unit.Direction.ID]];
  670. }
  671. else if (unit.Type == RedAlert.UnitTypes.Destroyer)
  672. {
  673. turretName = "SSAM";
  674. turretIcon = BodyShape[Facing32[unit.Direction.ID]];
  675. }
  676. else if (unit.Type == RedAlert.UnitTypes.PTBoat)
  677. {
  678. turretName = "MGUN";
  679. turretIcon = BodyShape[Facing32[unit.Direction.ID]];
  680. }
  681. #endif
  682. Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, turretName, turretIcon, Globals.TheTeamColorManager[teamColor], out turretTile);
  683. }
  684. var tint = unit.Tint;
  685. void render(Graphics g)
  686. {
  687. var imageAttributes = new ImageAttributes();
  688. if (tint != Color.White)
  689. {
  690. var colorMatrix = new ColorMatrix(new float[][]
  691. {
  692. new float[] {tint.R / 255.0f, 0, 0, 0, 0},
  693. new float[] {0, tint.G / 255.0f, 0, 0, 0},
  694. new float[] {0, 0, tint.B / 255.0f, 0, 0},
  695. new float[] {0, 0, 0, tint.A / 255.0f, 0},
  696. new float[] {0, 0, 0, 0, 1},
  697. }
  698. );
  699. imageAttributes.SetColorMatrix(colorMatrix);
  700. }
  701. g.DrawImage(tile.Image, renderBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
  702. if (radarTile != null)
  703. {
  704. Point turretAdjust = Point.Empty;
  705. if (unit.Type == RedAlert.UnitTypes.MGG)
  706. {
  707. turretAdjust = TurretAdjust[Facing32[unit.Direction.ID]];
  708. }
  709. else if (unit.Type != RedAlert.UnitTypes.Tesla)
  710. {
  711. turretAdjust.Y = -5;
  712. }
  713. var radarBounds = renderBounds;
  714. radarBounds.Offset(
  715. turretAdjust.X * tileSize.Width / Globals.PixelWidth,
  716. turretAdjust.Y * tileSize.Height / Globals.PixelHeight
  717. );
  718. g.DrawImage(radarTile.Image, radarBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
  719. }
  720. if (turretTile != null)
  721. {
  722. Point turretAdjust = Point.Empty;
  723. if (gameType == GameType.RedAlert)
  724. {
  725. if (unit.Type.IsVessel)
  726. {
  727. }
  728. else if (unit.Type == RedAlert.UnitTypes.Jeep)
  729. {
  730. turretAdjust.Y = -4;
  731. }
  732. }
  733. else if (gameType == GameType.TiberianDawn)
  734. {
  735. if ((unit.Type == TiberianDawn.UnitTypes.Jeep) ||
  736. (unit.Type == TiberianDawn.UnitTypes.Buggy))
  737. {
  738. turretAdjust.Y = -4;
  739. }
  740. else if ((unit.Type == TiberianDawn.UnitTypes.SAM) ||
  741. (unit.Type == TiberianDawn.UnitTypes.MLRS))
  742. {
  743. turretAdjust = TurretAdjust[Facing32[unit.Direction.ID]];
  744. }
  745. }
  746. var turretBounds = renderBounds;
  747. turretBounds.Offset(
  748. turretAdjust.X * tileSize.Width / Globals.PixelWidth,
  749. turretAdjust.Y * tileSize.Height / Globals.PixelHeight
  750. );
  751. g.DrawImage(turretTile.Image, turretBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes);
  752. }
  753. }
  754. return (renderBounds, render);
  755. }
  756. else
  757. {
  758. Debug.Print(string.Format("Unit {0} ({1}) not found", unit.Type.Name, icon));
  759. return (Rectangle.Empty, (g) => { });
  760. }
  761. }
  762. }
  763. }