diff --git a/NHSE.Core/Save/Files/MainSave.cs b/NHSE.Core/Save/Files/MainSave.cs index 73914ee..110790d 100644 --- a/NHSE.Core/Save/Files/MainSave.cs +++ b/NHSE.Core/Save/Files/MainSave.cs @@ -64,5 +64,17 @@ public void SetAcreBytes(byte[] data) public TerrainTile[] GetTerrain() => TerrainTile.GetArray(Data.Slice(Offsets.Terrain, TerrainManager.TileCount * TerrainTile.SIZE)); public void SetTerrain(IReadOnlyList array) => TerrainTile.SetArray(array).CopyTo(Data, Offsets.Terrain); + + public uint PlazaX + { + get => BitConverter.ToUInt32(Data, Offsets.Acres + AcreSizeAll + 4); + set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.Acres + AcreSizeAll + 4); + } + + public uint PlazaY + { + get => BitConverter.ToUInt32(Data, Offsets.Acres + AcreSizeAll + 8); + set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.Acres + AcreSizeAll + 8); + } } } \ No newline at end of file diff --git a/NHSE.Sprites/TerrainSprite.cs b/NHSE.Sprites/TerrainSprite.cs index 624091e..a5613b1 100644 --- a/NHSE.Sprites/TerrainSprite.cs +++ b/NHSE.Sprites/TerrainSprite.cs @@ -88,26 +88,40 @@ private static Bitmap DrawReticle(Bitmap map, int x, int y, int scale) return map; } - public static Bitmap GetMapWithBuildings(TerrainManager mgr, IReadOnlyList buildings, 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) { - // 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. - const int buildingShift = TerrainManager.GridWidth; var map = CreateMap(mgr, scale); using var gfx = Graphics.FromImage(map); + gfx.DrawPlaza(plazaX, plazaY, scale); + gfx.DrawBuildings(buildings, f, scale, index); + return map; + } + + private static void DrawPlaza(this Graphics gfx, ushort px, ushort py, int scale) + { + var plaza = Brushes.RosyBrown; + GetBuildingCoordinate(px, py, scale, out var x, out var y); + + var width = scale * 2 * 6; + var height = scale * 2 * 5; + + gfx.FillRectangle(plaza, x, y, width, height); + } + + private static void DrawBuildings(this Graphics gfx, 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 }; + 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; - var x = (int)(((b.X / 2f) - buildingShift) * scale); - var y = (int)(((b.Y / 2f) - buildingShift) * scale); + 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); @@ -115,8 +129,15 @@ public static Bitmap GetMapWithBuildings(TerrainManager mgr, IReadOnlyList Buildings; + private readonly MainSave SAV; private readonly TerrainManager Terrain; private static readonly IReadOnlyDictionary HelpDictionary = StructureUtil.GetStructureHelpList(); public BuildingEditor(IReadOnlyList buildings, MainSave sav) { InitializeComponent(); + SAV = sav; Buildings = buildings; - DialogResult = DialogResult.Cancel; + Terrain = new TerrainManager(sav.GetTerrain()); + + NUD_PlazaX.Value = sav.PlazaX; + NUD_PlazaY.Value = sav.PlazaY; foreach (var obj in buildings) LB_Items.Items.Add(obj.ToString()); - Terrain = new TerrainManager(sav.GetTerrain()); - LB_Items.SelectedIndex = 0; foreach (var entry in HelpDictionary) CB_StructureType.Items.Add(entry.Key); CB_StructureType.SelectedIndex = 0; + + DialogResult = DialogResult.Cancel; } private void B_Cancel_Click(object sender, EventArgs e) => Close(); private void B_Save_Click(object sender, EventArgs e) { + SAV.PlazaX = (uint)NUD_PlazaX.Value; + SAV.PlazaY = (uint)NUD_PlazaY.Value; + DialogResult = DialogResult.OK; Close(); } private int Index; private bool Loading; - private void DrawMap(in int index) => PB_Map.Image = TerrainSprite.GetMapWithBuildings(Terrain, Buildings, B_Save.Font, 4, index); + + private void DrawMap(in int index) + { + var font = B_Save.Font; + const int scale = 4; + var px = (ushort) NUD_PlazaX.Value; + var py = (ushort) NUD_PlazaY.Value; + PB_Map.Image = TerrainSprite.GetMapWithBuildings(Terrain, Buildings, px, py, font, scale, index); + } private void LB_Items_SelectedIndexChanged(object sender, EventArgs e) { @@ -98,5 +114,7 @@ private void CB_StructureType_SelectedIndexChanged(object sender, EventArgs e) CB_StructureValues.Items.Add(item); CB_StructureValues.SelectedIndex = 0; } + + private void NUD_PlazaCoordinate_ValueChanged(object sender, EventArgs e) => DrawMap(Index); } } diff --git a/NHSE.WinForms/Subforms/TerrainEditor.Designer.cs b/NHSE.WinForms/Subforms/TerrainEditor.Designer.cs index d5deb00..d22ea4f 100644 --- a/NHSE.WinForms/Subforms/TerrainEditor.Designer.cs +++ b/NHSE.WinForms/Subforms/TerrainEditor.Designer.cs @@ -53,6 +53,7 @@ private void InitializeComponent() this.CM_Picture = new System.Windows.Forms.ContextMenuStrip(this.components); this.Menu_SavePNG = new System.Windows.Forms.ToolStripMenuItem(); this.CHK_SnapToAcre = new System.Windows.Forms.CheckBox(); + this.L_Coordinates = new System.Windows.Forms.Label(); this.CM_Click.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.PB_Map)).BeginInit(); this.CM_Picture.SuspendLayout(); @@ -250,7 +251,7 @@ private void InitializeComponent() // this.PB_Map.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.PB_Map.ContextMenuStrip = this.CM_Picture; - this.PB_Map.Location = new System.Drawing.Point(821, 348); + this.PB_Map.Location = new System.Drawing.Point(821, 343); this.PB_Map.Name = "PB_Map"; this.PB_Map.Size = new System.Drawing.Size(226, 194); this.PB_Map.TabIndex = 23; @@ -278,18 +279,29 @@ private void InitializeComponent() this.CHK_SnapToAcre.AutoSize = true; this.CHK_SnapToAcre.Checked = true; this.CHK_SnapToAcre.CheckState = System.Windows.Forms.CheckState.Checked; - this.CHK_SnapToAcre.Location = new System.Drawing.Point(820, 325); + this.CHK_SnapToAcre.Location = new System.Drawing.Point(820, 320); this.CHK_SnapToAcre.Name = "CHK_SnapToAcre"; this.CHK_SnapToAcre.Size = new System.Drawing.Size(167, 17); this.CHK_SnapToAcre.TabIndex = 24; this.CHK_SnapToAcre.Text = "Snap to nearest Acre on Click"; this.CHK_SnapToAcre.UseVisualStyleBackColor = true; // + // L_Coordinates + // + this.L_Coordinates.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.L_Coordinates.Location = new System.Drawing.Point(876, 540); + this.L_Coordinates.Name = "L_Coordinates"; + this.L_Coordinates.Size = new System.Drawing.Size(173, 15); + this.L_Coordinates.TabIndex = 25; + this.L_Coordinates.Text = "(000,000) = (0x00,0x00)"; + this.L_Coordinates.TextAlign = System.Drawing.ContentAlignment.TopRight; + // // TerrainEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1061, 822); + this.Controls.Add(this.L_Coordinates); this.Controls.Add(this.CHK_SnapToAcre); this.Controls.Add(this.PB_Map); this.Controls.Add(this.B_Down); @@ -349,5 +361,6 @@ private void InitializeComponent() private System.Windows.Forms.ContextMenuStrip CM_Picture; private System.Windows.Forms.ToolStripMenuItem Menu_SavePNG; private System.Windows.Forms.CheckBox CHK_SnapToAcre; + private System.Windows.Forms.Label L_Coordinates; } } \ No newline at end of file diff --git a/NHSE.WinForms/Subforms/TerrainEditor.cs b/NHSE.WinForms/Subforms/TerrainEditor.cs index c072a4e..6975e44 100644 --- a/NHSE.WinForms/Subforms/TerrainEditor.cs +++ b/NHSE.WinForms/Subforms/TerrainEditor.cs @@ -335,12 +335,10 @@ private void Menu_SavePNG_Click(object sender, EventArgs e) private void ClickMapAt(MouseEventArgs e, bool skipLagCheck) { - const int maxX = ((TerrainManager.AcreWidth - 1) * GridWidth); - const int maxY = ((TerrainManager.AcreHeight - 1) * GridHeight); - - int center = CHK_SnapToAcre.Checked ? 0 : GridWidth / 2; - var x = Math.Max(0, Math.Min((e.X / MapScale) - center, maxX)); - var y = Math.Max(0, Math.Min((e.Y / MapScale) - center, maxY)); + int mX = e.X; + int mY = e.Y; + bool centerReticle = CHK_SnapToAcre.Checked; + GetViewAnchorCoordinates(mX, mY, out var x, out var y, centerReticle); var acre = TerrainManager.GetAcre(x, y); bool sameAcre = AcreIndex == acre; @@ -371,11 +369,48 @@ private void ClickMapAt(MouseEventArgs e, bool skipLagCheck) CB_Acre.SelectedIndex = acre; } + private static void GetCursorCoordinates(int mX, int mY, out int x, out int y) + { + x = mX / MapScale; + y = mY / MapScale; + } + + private static void GetViewAnchorCoordinates(int mX, int mY, out int x, out int y, bool centerReticle) + { + GetCursorCoordinates(mX, mY, out x, out y); + + // Clamp to viewport dimensions, and center to nearest acre if desired. + const int maxX = ((TerrainManager.AcreWidth - 1) * GridWidth); + const int maxY = ((TerrainManager.AcreHeight - 1) * GridHeight); + + // If we aren't snapping the reticle to the nearest acre + // we want to put the middle of the reticle rectangle where the cursor is. + // Adjust the view coordinate + if (!centerReticle) + { + // Reticle size is GridWidth, center = /2 + x -= GridWidth / 2; + y -= GridWidth / 2; + } + + // Clamp to boundaries so that we always have 16x16 to view. + x = Math.Max(0, Math.Min(x, maxX)); + y = Math.Max(0, Math.Min(y, maxY)); + } + private void PB_Map_MouseMove(object sender, MouseEventArgs e) { - if (e.Button != MouseButtons.Left) - return; - ClickMapAt(e, false); + if (e.Button == MouseButtons.Left) + { + ClickMapAt(e, false); + } + else + { + int mX = e.X; + int mY = e.Y; + GetCursorCoordinates(mX, mY, out var x, out var y); + L_Coordinates.Text = $"({x:000},{y:000}) = (0x{x:X2},0x{y:X2})"; + } } } }