// // Copyright 2020 Electronic Arts Inc. // // The Command & Conquer Map Editor and corresponding source code is free // software: you can redistribute it and/or modify it under the terms of // the GNU General Public License as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // The Command & Conquer Map Editor and corresponding source code is distributed // in the hope that it will be useful, but with permitted additional restrictions // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT // distributed with this program. You should have received a copy of the // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection using MobiusEditor.Interface; using MobiusEditor.Model; using MobiusEditor.Utility; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Linq; namespace MobiusEditor.Render { public static class MapRenderer { private static readonly int[] Facing16 = new int[256] { 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, 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, 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, 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, 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, 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, 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, 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 }; private static readonly int[] Facing32 = new int[256] { 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, 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, 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, 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, 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, 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, 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, 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 }; private static readonly int[] HumanShape = new int[32] { 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 }; private static readonly int[] BodyShape = new int[32] { 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 }; private static readonly Point[] TurretAdjust = new Point[] { new Point(1, 2), // N new Point(-1, 1), new Point(-2, 0), new Point(-3, 0), new Point(-3, 1), // NW new Point(-4, -1), new Point(-4, -1), new Point(-5, -2), new Point(-5, -3), // W new Point(-5, -3), new Point(-3, -3), new Point(-3, -4), new Point(-3, -4), // SW new Point(-3, -5), new Point(-2, -5), new Point(-1, -5), new Point(0, -5), // S new Point(1, -6), new Point(2, -5), new Point(3, -5), new Point(4, -5), // SE new Point(6, -4), new Point(6, -3), new Point(6, -3), new Point(6, -3), // E new Point(5, -1), new Point(5, -1), new Point(4, 0), new Point(3, 0), // NE new Point(2, 0), new Point(2, 1), new Point(1, 2) }; private static readonly int[] tiberiumCounts = new int[] { 0, 1, 3, 4, 6, 7, 8, 10, 11 }; private static readonly int randomSeed; static MapRenderer() { randomSeed = Guid.NewGuid().GetHashCode(); } public static void Render(GameType gameType, Map map, Graphics graphics, ISet locations, MapLayerFlag layers, int tileScale) { var tileSize = new Size(Globals.OriginalTileWidth / tileScale, Globals.OriginalTileHeight / tileScale); var tiberiumOrGoldTypes = map.OverlayTypes.Where(t => t.IsTiberiumOrGold).Select(t => t).ToArray(); var gemTypes = map.OverlayTypes.Where(t => t.IsGem).ToArray(); var overlappingRenderList = new List<(Rectangle, Action)>(); Func> renderLocations = null; if (locations != null) { renderLocations = () => locations; } else { IEnumerable allCells() { for (var y = 0; y < map.Metrics.Height; ++y) { for (var x = 0; x < map.Metrics.Width; ++x) { yield return new Point(x, y); } } } renderLocations = allCells; } if ((layers & MapLayerFlag.Template) != MapLayerFlag.None) { foreach (var topLeft in renderLocations()) { map.Metrics.GetCell(topLeft, out int cell); var template = map.Templates[topLeft]; var name = template?.Type.Name ?? map.TemplateTypes.Where(t => t.Equals("clear1")).FirstOrDefault().Name; var icon = template?.Icon ?? ((cell & 0x03) | ((cell >> 4) & 0x0C)); if (Globals.TheTilesetManager.GetTileData(map.Theater.Tilesets, name, icon, out Tile tile)) { var renderBounds = new Rectangle(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height, tileSize.Width, tileSize.Height); graphics.DrawImage(tile.Image, renderBounds); } else { Debug.Print(string.Format("Template {0} ({1}) not found", name, icon)); } } } if ((layers & MapLayerFlag.Smudge) != MapLayerFlag.None) { foreach (var topLeft in renderLocations()) { var smudge = map.Smudge[topLeft]; if (smudge != null) { Render(map.Theater, topLeft, tileSize, smudge).Item2(graphics); } } } if ((layers & MapLayerFlag.OverlayAll) != MapLayerFlag.None) { foreach (var topLeft in renderLocations()) { var overlay = map.Overlay[topLeft]; if (overlay == null) { continue; } if ((overlay.Type.IsResource && ((layers & MapLayerFlag.Resources) != MapLayerFlag.None)) || (overlay.Type.IsWall && ((layers & MapLayerFlag.Walls) != MapLayerFlag.None)) || ((layers & MapLayerFlag.Overlay) != MapLayerFlag.None)) { Render(map.Theater, tiberiumOrGoldTypes, gemTypes, topLeft, tileSize, tileScale, overlay).Item2(graphics); } } } if ((layers & MapLayerFlag.Terrain) != MapLayerFlag.None) { foreach (var (topLeft, terrain) in map.Technos.OfType()) { if ((locations != null) && !locations.Contains(topLeft)) { continue; } string tileName = terrain.Type.Name; if ((terrain.Type.TemplateType & TemplateTypeFlag.OreMine) != TemplateTypeFlag.None) { tileName = "OREMINE"; } if (Globals.TheTilesetManager.GetTileData(map.Theater.Tilesets, tileName, terrain.Icon, out Tile tile)) { var tint = terrain.Tint; var imageAttributes = new ImageAttributes(); if (tint != Color.White) { var colorMatrix = new ColorMatrix(new float[][] { new float[] {tint.R / 255.0f, 0, 0, 0, 0}, new float[] {0, tint.G / 255.0f, 0, 0, 0}, new float[] {0, 0, tint.B / 255.0f, 0, 0}, new float[] {0, 0, 0, tint.A / 255.0f, 0}, new float[] {0, 0, 0, 0, 1}, } ); imageAttributes.SetColorMatrix(colorMatrix); } var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height); var size = new Size(tile.Image.Width / tileScale, tile.Image.Height / tileScale); var terrainBounds = new Rectangle(location, size); overlappingRenderList.Add((terrainBounds, g => g.DrawImage(tile.Image, terrainBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes))); } else { Debug.Print(string.Format("Terrain {0} ({1}) not found", tileName, terrain.Icon)); } } } if ((layers & MapLayerFlag.Buildings) != MapLayerFlag.None) { foreach (var (topLeft, building) in map.Buildings.OfType()) { if ((locations != null) && !locations.Contains(topLeft)) { continue; } overlappingRenderList.Add(Render(gameType, map.Theater, topLeft, tileSize, tileScale, building)); } } if ((layers & MapLayerFlag.Infantry) != MapLayerFlag.None) { foreach (var (topLeft, infantryGroup) in map.Technos.OfType()) { if ((locations != null) && !locations.Contains(topLeft)) { continue; } for (int i = 0; i < infantryGroup.Infantry.Length; ++i) { var infantry = infantryGroup.Infantry[i]; if (infantry == null) { continue; } overlappingRenderList.Add(Render(map.Theater, topLeft, tileSize, infantry, (InfantryStoppingType)i)); } } } if ((layers & MapLayerFlag.Units) != MapLayerFlag.None) { foreach (var (topLeft, unit) in map.Technos.OfType()) { if ((locations != null) && !locations.Contains(topLeft)) { continue; } overlappingRenderList.Add(Render(gameType, map.Theater, topLeft, tileSize, unit)); } } foreach (var (location, renderer) in overlappingRenderList.Where(x => !x.Item1.IsEmpty).OrderBy(x => x.Item1.Bottom)) { renderer(graphics); } } public static void Render(GameType gameType, Map map, Graphics graphics, ISet locations, MapLayerFlag layers) { Render(gameType, map, graphics, locations, layers, Globals.TileScale); } public static (Rectangle, Action) Render(TheaterType theater, Point topLeft, Size tileSize, Smudge smudge) { var tint = smudge.Tint; var imageAttributes = new ImageAttributes(); if (tint != Color.White) { var colorMatrix = new ColorMatrix(new float[][] { new float[] {tint.R / 255.0f, 0, 0, 0, 0}, new float[] {0, tint.G / 255.0f, 0, 0, 0}, new float[] {0, 0, tint.B / 255.0f, 0, 0}, new float[] {0, 0, 0, tint.A / 255.0f, 0}, new float[] {0, 0, 0, 0, 1}, } ); imageAttributes.SetColorMatrix(colorMatrix); } if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, smudge.Type.Name, smudge.Icon, out Tile tile)) { var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height); var smudgeBounds = new Rectangle(location, smudge.Type.RenderSize); void render(Graphics g) { g.DrawImage(tile.Image, smudgeBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes); } return (smudgeBounds, render); } else { Debug.Print(string.Format("Smudge {0} ({1}) not found", smudge.Type.Name, smudge.Icon)); return (Rectangle.Empty, (g) => { }); } } public static (Rectangle, Action) Render(TheaterType theater, OverlayType[] tiberiumOrGoldTypes, OverlayType[] gemTypes, Point topLeft, Size tileSize, int tileScale, Overlay overlay) { var name = overlay.Type.Name; if (overlay.Type.IsGem) { name = gemTypes[new Random(randomSeed ^ topLeft.GetHashCode()).Next(tiberiumOrGoldTypes.Length)].Name; } else if (overlay.Type.IsTiberiumOrGold) { name = tiberiumOrGoldTypes[new Random(randomSeed ^ topLeft.GetHashCode()).Next(tiberiumOrGoldTypes.Length)].Name; } if (Globals.TheTilesetManager.GetTileData(theater.Tilesets, name, overlay.Icon, out Tile tile)) { var size = (overlay.Type.IsCrate || overlay.Type.IsFlag) ? new Size(tile.Image.Width / tileScale, tile.Image.Height / tileScale) : tileSize; var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height) + new Size(tileSize.Width / 2, tileSize.Height / 2) - new Size(size.Width / 2, size.Height / 2); var overlayBounds = new Rectangle(location, size); var tint = overlay.Tint; void render(Graphics g) { var imageAttributes = new ImageAttributes(); if (tint != Color.White) { var colorMatrix = new ColorMatrix(new float[][] { new float[] {tint.R / 255.0f, 0, 0, 0, 0}, new float[] {0, tint.G / 255.0f, 0, 0, 0}, new float[] {0, 0, tint.B / 255.0f, 0, 0}, new float[] {0, 0, 0, tint.A / 255.0f, 0}, new float[] {0, 0, 0, 0, 1}, } ); imageAttributes.SetColorMatrix(colorMatrix); } g.DrawImage(tile.Image, overlayBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes); } return (overlayBounds, render); } else { Debug.Print(string.Format("Overlay {0} ({1}) not found", overlay.Type.Name, overlay.Icon)); return (Rectangle.Empty, (g) => { }); } } public static (Rectangle, Action) Render(GameType gameType, TheaterType theater, Point topLeft, Size tileSize, int tileScale, Building building) { var tint = building.Tint; var stringFormat = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; var fakeBackgroundBrush = new SolidBrush(Color.FromArgb(building.Tint.A / 2, Color.Black)); var fakeTextBrush = new SolidBrush(Color.FromArgb(building.Tint.A, Color.White)); var baseBackgroundBrush = new SolidBrush(Color.FromArgb(building.Tint.A / 2, Color.Black)); var baseTextBrush = new SolidBrush(Color.FromArgb(building.Tint.A, Color.Red)); var icon = 0; if (building.Type.HasTurret) { icon = BodyShape[Facing32[building.Direction.ID]]; if (building.Strength < 128) { switch (gameType) { case GameType.TiberianDawn: icon += 64; break; case GameType.RedAlert: icon += building.Type.Equals("sam") ? 35 : 64; break; } } } else { if (building.Strength <= 1) { icon = -1; } else if (building.Strength < 128) { icon = -2; if (building.Type.Equals("weap") || building.Type.Equals("weaf")) { icon = 1; } else if ((gameType == GameType.TiberianDawn) && building.Type.Equals("proc")) { icon = 30; } else if (building.Type.Equals("eye")) { icon = 16; } else if (building.Type.Equals("silo")) { icon = 5; } else if (building.Type.Equals("fix")) { icon = 7; } else if (building.Type.Equals("v19")) { icon = 14; } } } if (Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, building.Type.Tilename, icon, Globals.TheTeamColorManager[building.House.BuildingTeamColor], out Tile tile)) { var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height); var size = new Size(tile.Image.Width / tileScale, tile.Image.Height / tileScale); var maxSize = new Size(building.Type.Size.Width * tileSize.Width, building.Type.Size.Height * tileSize.Height); if ((size.Width >= size.Height) && (size.Width > maxSize.Width)) { size.Height = size.Height * maxSize.Width / size.Width; size.Width = maxSize.Width; } else if ((size.Height >= size.Width) && (size.Height > maxSize.Height)) { size.Width = size.Width * maxSize.Height / size.Height; size.Height = maxSize.Height; } var buildingBounds = new Rectangle(location, size); Tile factoryOverlayTile = null; if (building.Type.FactoryOverlay != null) { int overlayIcon = 0; if (building.Strength < 128) { switch (gameType) { case GameType.TiberianDawn: overlayIcon = 10; break; case GameType.RedAlert: overlayIcon = 4; break; } } Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, building.Type.FactoryOverlay, overlayIcon, Globals.TheTeamColorManager[building.House.BuildingTeamColor], out factoryOverlayTile); } void render(Graphics g) { var imageAttributes = new ImageAttributes(); if (tint != Color.White) { var colorMatrix = new ColorMatrix(new float[][] { new float[] {tint.R / 255.0f, 0, 0, 0, 0}, new float[] {0, tint.G / 255.0f, 0, 0, 0}, new float[] {0, 0, tint.B / 255.0f, 0, 0}, new float[] {0, 0, 0, tint.A / 255.0f, 0}, new float[] {0, 0, 0, 0, 1}, } ); imageAttributes.SetColorMatrix(colorMatrix); } if (factoryOverlayTile != null) { g.DrawImage(tile.Image, buildingBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes); g.DrawImage(factoryOverlayTile.Image, buildingBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes); } else { g.DrawImage(tile.Image, buildingBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes); } if (building.Type.IsFake) { var text = Globals.TheGameTextManager["TEXT_UI_FAKE"]; var textSize = g.MeasureString(text, SystemFonts.CaptionFont) + new SizeF(6.0f, 6.0f); var textBounds = new RectangleF(buildingBounds.Location, textSize); g.FillRectangle(fakeBackgroundBrush, textBounds); g.DrawString(text, SystemFonts.CaptionFont, fakeTextBrush, textBounds, stringFormat); } if (building.BasePriority >= 0) { var text = building.BasePriority.ToString(); var textSize = g.MeasureString(text, SystemFonts.CaptionFont) + new SizeF(6.0f, 6.0f); var textBounds = new RectangleF(buildingBounds.Location + new Size((int)((buildingBounds.Width - textSize.Width) / 2.0f), (int)(buildingBounds.Height - textSize.Height)), textSize ); g.FillRectangle(baseBackgroundBrush, textBounds); g.DrawString(text, SystemFonts.CaptionFont, baseTextBrush, textBounds, stringFormat); } } return (buildingBounds, render); } else { Debug.Print(string.Format("Building {0} (0) not found", building.Type.Name)); return (Rectangle.Empty, (g) => { }); } } public static (Rectangle, Action) Render(TheaterType theater, Point topLeft, Size tileSize, Infantry infantry, InfantryStoppingType infantryStoppingType) { var icon = HumanShape[Facing32[infantry.Direction.ID]]; string teamColor = infantry.House?.UnitTeamColor; if (Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, infantry.Type.Name, icon, Globals.TheTeamColorManager[teamColor], out Tile tile)) { var baseLocation = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height) + new Size(tileSize.Width / 2, tileSize.Height / 2); var offset = Point.Empty; switch (infantryStoppingType) { case InfantryStoppingType.UpperLeft: offset.X = -tileSize.Width / 4; offset.Y = -tileSize.Height / 4; break; case InfantryStoppingType.UpperRight: offset.X = tileSize.Width / 4; offset.Y = -tileSize.Height / 4; break; case InfantryStoppingType.LowerLeft: offset.X = -tileSize.Width / 4; offset.Y = tileSize.Height / 4; break; case InfantryStoppingType.LowerRight: offset.X = tileSize.Width / 4; offset.Y = tileSize.Height / 4; break; } baseLocation.Offset(offset); var virtualBounds = new Rectangle( new Point(baseLocation.X - (tile.OpaqueBounds.Width / 2), baseLocation.Y - tile.OpaqueBounds.Height), tile.OpaqueBounds.Size ); var renderBounds = new Rectangle( baseLocation - new Size(infantry.Type.RenderSize.Width / 2, infantry.Type.RenderSize.Height / 2), infantry.Type.RenderSize ); var tint = infantry.Tint; void render(Graphics g) { var imageAttributes = new ImageAttributes(); if (tint != Color.White) { var colorMatrix = new ColorMatrix(new float[][] { new float[] {tint.R / 255.0f, 0, 0, 0, 0}, new float[] {0, tint.G / 255.0f, 0, 0, 0}, new float[] {0, 0, tint.B / 255.0f, 0, 0}, new float[] {0, 0, 0, tint.A / 255.0f, 0}, new float[] {0, 0, 0, 0, 1}, } ); imageAttributes.SetColorMatrix(colorMatrix); } g.DrawImage(tile.Image, renderBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes); } return (virtualBounds, render); } else { Debug.Print(string.Format("Infantry {0} ({1}) not found", infantry.Type.Name, icon)); return (Rectangle.Empty, (g) => { }); } } public static (Rectangle, Action) Render(GameType gameType, TheaterType theater, Point topLeft, Size tileSize, Unit unit) { int icon = 0; if (gameType == GameType.TiberianDawn) { if (unit.Type == TiberianDawn.UnitTypes.GunBoat) { switch (unit.Direction.Facing) { case FacingType.NorthEast: case FacingType.East: case FacingType.SouthEast: icon = 96; break; default: icon = 0; break; } } else if ((unit.Type == TiberianDawn.UnitTypes.Tric) || (unit.Type == TiberianDawn.UnitTypes.Trex) || (unit.Type == TiberianDawn.UnitTypes.Rapt) || (unit.Type == TiberianDawn.UnitTypes.Steg)) { var facing = ((unit.Direction.ID + 0x10) & 0xFF) >> 5; icon = BodyShape[facing + ((facing > 0) ? 24 : 0)]; } else if ((unit.Type == TiberianDawn.UnitTypes.Hover) || (unit.Type == TiberianDawn.UnitTypes.Visceroid)) { icon = 0; } else { icon = BodyShape[Facing32[unit.Direction.ID]]; } } else if (gameType == GameType.RedAlert) { if (unit.Type.IsAircraft) { if ((unit.Type == RedAlert.UnitTypes.Tran) || (unit.Type == RedAlert.UnitTypes.Heli) || (unit.Type == RedAlert.UnitTypes.Hind)) { icon = BodyShape[Facing32[unit.Direction.ID]]; } else { icon = BodyShape[Facing16[unit.Direction.ID] * 2] / 2; } } else if (unit.Type.IsVessel) { if ((unit.Type == RedAlert.UnitTypes.Transport) || (unit.Type == RedAlert.UnitTypes.Carrier)) { icon = 0; } else { icon = BodyShape[Facing16[unit.Direction.ID] * 2] >> 1; } } else { if ((unit.Type == RedAlert.UnitTypes.Ant1) || (unit.Type == RedAlert.UnitTypes.Ant2) || (unit.Type == RedAlert.UnitTypes.Ant3)) { icon = ((BodyShape[Facing32[unit.Direction.ID]] + 2) / 4) & 0x07; } else { icon = BodyShape[Facing32[unit.Direction.ID]]; } } } string teamColor = null; if (unit.House != null) { if (!unit.House.OverrideTeamColors.TryGetValue(unit.Type.Name, out teamColor)) { teamColor = unit.House.UnitTeamColor; } } if (Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, unit.Type.Name, icon, Globals.TheTeamColorManager[teamColor], out Tile tile)) { var location = new Point(topLeft.X * tileSize.Width, topLeft.Y * tileSize.Height) + new Size(tileSize.Width / 2, tileSize.Height / 2); var renderBounds = new Rectangle( location - new Size(unit.Type.RenderSize.Width / 2, unit.Type.RenderSize.Height / 2), unit.Type.RenderSize ); Tile radarTile = null; if ((unit.Type == RedAlert.UnitTypes.MGG) || (unit.Type == RedAlert.UnitTypes.MRJammer) || (unit.Type == RedAlert.UnitTypes.Tesla)) { Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, unit.Type.Name, 32, Globals.TheTeamColorManager[teamColor], out radarTile); } Tile turretTile = null; if (unit.Type.HasTurret) { var turretName = unit.Type.Name; var turretIcon = icon + 32; if (unit.Type == RedAlert.UnitTypes.Phase) { turretIcon += 6; } #if TODO else if (unit.Type == RedAlert.UnitTypes.Cruiser) { turretName = "TURR"; turretIcon = BodyShape[Facing32[unit.Direction.ID]]; } else if (unit.Type == RedAlert.UnitTypes.Destroyer) { turretName = "SSAM"; turretIcon = BodyShape[Facing32[unit.Direction.ID]]; } else if (unit.Type == RedAlert.UnitTypes.PTBoat) { turretName = "MGUN"; turretIcon = BodyShape[Facing32[unit.Direction.ID]]; } #endif Globals.TheTilesetManager.GetTeamColorTileData(theater.Tilesets, turretName, turretIcon, Globals.TheTeamColorManager[teamColor], out turretTile); } var tint = unit.Tint; void render(Graphics g) { var imageAttributes = new ImageAttributes(); if (tint != Color.White) { var colorMatrix = new ColorMatrix(new float[][] { new float[] {tint.R / 255.0f, 0, 0, 0, 0}, new float[] {0, tint.G / 255.0f, 0, 0, 0}, new float[] {0, 0, tint.B / 255.0f, 0, 0}, new float[] {0, 0, 0, tint.A / 255.0f, 0}, new float[] {0, 0, 0, 0, 1}, } ); imageAttributes.SetColorMatrix(colorMatrix); } g.DrawImage(tile.Image, renderBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes); if (radarTile != null) { Point turretAdjust = Point.Empty; if (unit.Type == RedAlert.UnitTypes.MGG) { turretAdjust = TurretAdjust[Facing32[unit.Direction.ID]]; } else if (unit.Type != RedAlert.UnitTypes.Tesla) { turretAdjust.Y = -5; } var radarBounds = renderBounds; radarBounds.Offset( turretAdjust.X * tileSize.Width / Globals.PixelWidth, turretAdjust.Y * tileSize.Height / Globals.PixelHeight ); g.DrawImage(radarTile.Image, radarBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes); } if (turretTile != null) { Point turretAdjust = Point.Empty; if (gameType == GameType.RedAlert) { if (unit.Type.IsVessel) { } else if (unit.Type == RedAlert.UnitTypes.Jeep) { turretAdjust.Y = -4; } } else if (gameType == GameType.TiberianDawn) { if ((unit.Type == TiberianDawn.UnitTypes.Jeep) || (unit.Type == TiberianDawn.UnitTypes.Buggy)) { turretAdjust.Y = -4; } else if ((unit.Type == TiberianDawn.UnitTypes.SAM) || (unit.Type == TiberianDawn.UnitTypes.MLRS)) { turretAdjust = TurretAdjust[Facing32[unit.Direction.ID]]; } } var turretBounds = renderBounds; turretBounds.Offset( turretAdjust.X * tileSize.Width / Globals.PixelWidth, turretAdjust.Y * tileSize.Height / Globals.PixelHeight ); g.DrawImage(turretTile.Image, turretBounds, 0, 0, tile.Image.Width, tile.Image.Height, GraphicsUnit.Pixel, imageAttributes); } } return (renderBounds, render); } else { Debug.Print(string.Format("Unit {0} ({1}) not found", unit.Type.Name, icon)); return (Rectangle.Empty, (g) => { }); } } } }