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