diff --git a/NHSE.Core/Structures/Acres/Terrain/TerrainManager.cs b/NHSE.Core/Structures/Acres/Terrain/TerrainManager.cs index c761bc0..3876d2d 100644 --- a/NHSE.Core/Structures/Acres/Terrain/TerrainManager.cs +++ b/NHSE.Core/Structures/Acres/Terrain/TerrainManager.cs @@ -82,5 +82,34 @@ public void SetAll(TerrainTile tile, in bool interiorOnly) t.CopyFrom(tile); } } + + public void GetBuildingCoordinate(ushort bx, ushort by, int scale, out int x, out int y) + { + // Although there is terrain in the Top Row and Left Column, no buildings can be placed there. + // Adjust the building coordinates down-right by an acre. + int buildingShift = GridWidth; + x = (int)(((bx / 2f) - buildingShift) * scale); + y = (int)(((by / 2f) - buildingShift) * scale); + } + + public bool GetBuildingRelativeCoordinate(int topX, int topY, int acreScale, ushort bx, ushort by, out int relX, out int relY) + { + GetBuildingCoordinate(bx, by, acreScale, out var x, out var y); + relX = x - (topX * acreScale); + relY = y - (topY * acreScale); + + return IsWithinGrid(acreScale, relX, relY); + } + + private bool IsWithinGrid(int acreScale, int relX, int relY) + { + if ((uint)relX >= GridWidth * acreScale) + return false; + + if ((uint)relY >= GridHeight * acreScale) + return false; + + return true; + } } } diff --git a/NHSE.Sprites/Field/TerrainSprite.cs b/NHSE.Sprites/Field/TerrainSprite.cs index 451acc7..fcea424 100644 --- a/NHSE.Sprites/Field/TerrainSprite.cs +++ b/NHSE.Sprites/Field/TerrainSprite.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Drawing; using NHSE.Core; @@ -7,40 +6,15 @@ namespace NHSE.Sprites { public static class TerrainSprite { - public static Color GetTileColor(TerrainTile tile) - { - if (tile.UnitModelRoad.IsRoad()) - return Color.RosyBrown; - var baseColor = GetTileDefaultColor(tile.UnitModel); - if (tile.Elevation == 0) - return baseColor; + private static readonly Brush Selected = Brushes.Red; + private static readonly Brush Others = Brushes.Yellow; + private static readonly Brush Text = Brushes.White; + private static readonly Brush Plaza = Brushes.RosyBrown; + private static readonly StringFormat BuildingTextFormat = new StringFormat + { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; - return ColorUtil.Blend(baseColor, Color.White, 1d / (tile.Elevation + 1)); - } - - private static readonly Color CliffBase = ColorUtil.Blend(Color.ForestGreen, Color.Black, 0.6d); - - private static Color GetTileDefaultColor(TerrainUnitModel mdl) - { - if (mdl.IsRiver()) - return Color.DeepSkyBlue; - if (mdl.IsFall()) - return Color.DarkBlue; - if (mdl.IsCliff()) - return CliffBase; - return Color.ForestGreen; - } - - private static readonly char[] Numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; - - public static string GetTileName(TerrainTile tile) - { - var name = tile.UnitModel.ToString(); - var num = name.IndexOfAny(Numbers); - if (num < 0) - return name; - return name.Substring(0, num) + Environment.NewLine + name.Substring(num); - } + private const int PlazaWidth = 6 * 2; + private const int PlazaHeight = 5 * 2; public static Bitmap CreateMap(TerrainManager mgr) { @@ -50,7 +24,7 @@ public static Bitmap CreateMap(TerrainManager mgr) for (int y = 0; y < mgr.MapHeight; y++) { var tile = mgr.GetTile(x, y); - var color = GetTileColor(tile); + var color = TerrainTileColor.GetTileColor(tile); bmp.SetPixel(x, y, color); } } @@ -91,7 +65,7 @@ private static Bitmap DrawReticle(Bitmap map, MapGrid mgr, int x, int y, int sca return map; } - public static Bitmap GetMapWithBuildings(TerrainManager mgr, IReadOnlyList buildings, ushort plazaX, ushort plazaY, Font f, int scale = 4, int index = -1) + public static Bitmap GetMapWithBuildings(TerrainManager mgr, IReadOnlyList buildings, ushort plazaX, ushort plazaY, Font? f, int scale = 4, int index = -1) { var map = CreateMap(mgr, scale); using var gfx = Graphics.FromImage(map); @@ -101,49 +75,42 @@ public static Bitmap GetMapWithBuildings(TerrainManager mgr, IReadOnlyList buildings, Font f, int scale, int index = -1) + private static void DrawBuildings(this Graphics gfx, TerrainManager g, IReadOnlyList buildings, Font? f, int scale, int index = -1) { - var selected = Brushes.Red; - var others = Brushes.Yellow; - var text = Brushes.White; - var stringFormat = new StringFormat {Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center}; - for (int i = 0; i < buildings.Count; i++) { var b = buildings[i]; if (b.BuildingType == 0) continue; - GetBuildingCoordinate(g,b.X, b.Y, scale, out var x, out var y); + g.GetBuildingCoordinate(b.X, b.Y, scale, out var x, out var y); - var pen = index == i ? selected : others; - gfx.FillRectangle(pen, x - scale, y - scale, scale * 2, scale * 2); - - var name = b.BuildingType.ToString(); - gfx.DrawString(name, f, text, new PointF(x, y - (scale * 2)), stringFormat); + var pen = index == i ? Selected : Others; + DrawBuilding(gfx, f, scale, pen, x, y, b, Text); } } - private static void GetBuildingCoordinate(MapGrid g, ushort bx, ushort by, int scale, out int x, out int y) + private static void DrawBuilding(Graphics gfx, Font? f, int scale, Brush pen, int x, int y, Building b, Brush text) { - // Although there is terrain in the Top Row and Left Column, no buildings can be placed there. - // Adjust the building coordinates down-right by an acre. - int buildingShift = g.GridWidth; - x = (int) (((bx / 2f) - buildingShift) * scale); - y = (int) (((by / 2f) - buildingShift) * scale); + gfx.FillRectangle(pen, x - scale, y - scale, scale * 2, scale * 2); + + if (f != null) + { + var name = b.BuildingType.ToString(); + gfx.DrawString(name, f, text, new PointF(x, y - (scale * 2)), BuildingTextFormat); + } } - public static Image GetAcre(in int topX, in int topY, TerrainManager terrain, int acreScale) + public static Image GetAcre(in int topX, in int topY, TerrainManager t, int acreScale) { int[] data = new int[16 * 16]; int index = 0; @@ -152,13 +119,46 @@ public static Image GetAcre(in int topX, in int topY, TerrainManager terrain, in var yi = y + topY; for (int x = 0; x < 16; x++, index++) { - var tile = terrain.GetTile(x + topX, yi); - data[index] = GetTileColor(tile).ToArgb(); + var tile = t.GetTile(x + topX, yi); + data[index] = TerrainTileColor.GetTileColor(tile).ToArgb(); } } var final = ImageUtil.ScalePixelImage(data, acreScale, 16, 16, out int fw, out int fh); return ImageUtil.GetBitmap(final, fw, fh); } + + public static Image GetAcre(in int topX, in int topY, TerrainManager t, int acreScale, IReadOnlyList buildings, ushort plazaX, ushort plazaY) + { + var img = GetAcre(topX, topY, t, acreScale); + using var gfx = Graphics.FromImage(img); + + gfx.DrawAcrePlaza(t, topX, topY, plazaX, plazaY, acreScale); + + foreach (var b in buildings) + { + if (!t.GetBuildingRelativeCoordinate(topX, topY, acreScale, b.X, b.Y, out var x, out var y)) + { + // Draw the rectangle anyways. The graphics object will write the cropped rectangle correctly! + } + + DrawBuilding(gfx, null, acreScale, Others, x, y, b, Text); + } + + return img; + } + + private static void DrawAcrePlaza(this Graphics gfx, TerrainManager g, int topX, int topY, ushort px, ushort py, int scale) + { + if (!g.GetBuildingRelativeCoordinate(topX, topY, scale, px, py, out var x, out var y)) + { + // Draw the rectangle anyways. The graphics object will write the cropped rectangle correctly! + } + + var width = scale * PlazaWidth; + var height = scale * PlazaHeight; + + gfx.FillRectangle(Plaza, x, y, width, height); + } } } diff --git a/NHSE.Sprites/Field/TerrainTileColor.cs b/NHSE.Sprites/Field/TerrainTileColor.cs new file mode 100644 index 0000000..066459d --- /dev/null +++ b/NHSE.Sprites/Field/TerrainTileColor.cs @@ -0,0 +1,44 @@ +using System; +using System.Drawing; +using NHSE.Core; + +namespace NHSE.Sprites +{ + public static class TerrainTileColor + { + public static Color GetTileColor(TerrainTile tile) + { + if (tile.UnitModelRoad.IsRoad()) + return Color.RosyBrown; + var baseColor = GetTileDefaultColor(tile.UnitModel); + if (tile.Elevation == 0) + return baseColor; + + return ColorUtil.Blend(baseColor, Color.White, 1d / (tile.Elevation + 1)); + } + + private static readonly Color CliffBase = ColorUtil.Blend(Color.ForestGreen, Color.Black, 0.6d); + + private static Color GetTileDefaultColor(TerrainUnitModel mdl) + { + if (mdl.IsRiver()) + return Color.DeepSkyBlue; + if (mdl.IsFall()) + return Color.DarkBlue; + if (mdl.IsCliff()) + return CliffBase; + return Color.ForestGreen; + } + + private static readonly char[] Numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; + + public static string GetTileName(TerrainTile tile) + { + var name = tile.UnitModel.ToString(); + var num = name.IndexOfAny(Numbers); + if (num < 0) + return name; + return name.Substring(0, num) + Environment.NewLine + name.Substring(num); + } + } +} diff --git a/NHSE.WinForms/Subforms/Map/FieldItemEditor.cs b/NHSE.WinForms/Subforms/Map/FieldItemEditor.cs index 3f366a7..dd8ea5f 100644 --- a/NHSE.WinForms/Subforms/Map/FieldItemEditor.cs +++ b/NHSE.WinForms/Subforms/Map/FieldItemEditor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; @@ -25,6 +26,9 @@ public partial class FieldItemEditor : Form private readonly Bitmap MapReticle; private readonly TerrainManager Terrain; + private readonly IReadOnlyList Buildings; + private readonly uint PlazaX; + private readonly uint PlazaY; private int X; private int Y; @@ -46,7 +50,10 @@ public FieldItemEditor(MainSave sav) MapReticle = new Bitmap(l1.MapWidth * MapScale, l1.MapHeight * MapScale); Terrain = new TerrainManager(sav.GetTerrain()); - PB_Map.BackgroundImage = TerrainSprite.CreateMap(Terrain, 2); + Buildings = sav.Buildings; + PlazaX = sav.PlazaX; + PlazaY = sav.PlazaY; + PB_Map.BackgroundImage = TerrainSprite.GetMapWithBuildings(Terrain, Buildings, (ushort)PlazaX, (ushort)PlazaY, null, 2); foreach (var acre in MapGrid.Acres) CB_Acre.Items.Add(acre.Name); @@ -85,7 +92,7 @@ private void LoadGrid(int topX, int topY) private void ReloadBackground(int topX, int topY) { - PB_Acre.BackgroundImage = TerrainSprite.GetAcre(topX/2, topY/2, Terrain, AcreScale * 2); + PB_Acre.BackgroundImage = TerrainSprite.GetAcre(topX/2, topY/2, Terrain, AcreScale * 2, Buildings, (ushort)PlazaX, (ushort)PlazaY); } private void ReloadGrid(FieldItemLayer layer, int topX, int topY) diff --git a/NHSE.WinForms/Subforms/Map/TerrainEditor.cs b/NHSE.WinForms/Subforms/Map/TerrainEditor.cs index 4b3f542..f312f7b 100644 --- a/NHSE.WinForms/Subforms/Map/TerrainEditor.cs +++ b/NHSE.WinForms/Subforms/Map/TerrainEditor.cs @@ -193,8 +193,8 @@ private TerrainTile GetTile(int index) private static void RefreshTile(Control button, TerrainTile tile) { - button.Text = TerrainSprite.GetTileName(tile); - button.BackColor = TerrainSprite.GetTileColor(tile); + button.Text = TerrainTileColor.GetTileName(tile); + button.BackColor = TerrainTileColor.GetTileColor(tile); } private void B_Up_Click(object sender, EventArgs e) => CB_Acre.SelectedIndex -= MapGrid.AcreWidth;