From d21b99978a4b0ecc4fc4e4017f55a63c5fdfedd5 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 19 Apr 2020 16:23:59 -0700 Subject: [PATCH] Add player & villager house dump/load share your house, but not your toothbrush! --- NHSE.Core/Save/Files/MainSave.cs | 28 +++++++++ NHSE.Core/Save/Offsets/MainSaveOffsets.cs | 5 ++ NHSE.Core/Save/Offsets/MainSaveOffsets10.cs | 8 ++- NHSE.Core/Save/Offsets/MainSaveOffsets11.cs | 12 +++- NHSE.Core/Structures/GameFileDumper.cs | 56 +++++++++++++++-- .../Structures/Villager/GSaveItemName.cs | 21 +++++++ NHSE.Core/Structures/Villager/IHouseInfo.cs | 29 +++++++++ NHSE.Core/Structures/Villager/PlayerHouse.cs | 23 +++++++ .../Structures/Villager/VillagerHouse.cs | 21 +------ NHSE.Tests/MarshalTests.cs | 1 + .../Controls/VillagerEditor.Designer.cs | 30 ++++++++- NHSE.WinForms/Controls/VillagerEditor.cs | 62 ++++++++++++++++++- NHSE.WinForms/Editor.Designer.cs | 60 ++++++++++++++++++ NHSE.WinForms/Editor.cs | 60 +++++++++++++++++- 14 files changed, 386 insertions(+), 30 deletions(-) create mode 100644 NHSE.Core/Structures/Villager/GSaveItemName.cs create mode 100644 NHSE.Core/Structures/Villager/IHouseInfo.cs create mode 100644 NHSE.Core/Structures/Villager/PlayerHouse.cs diff --git a/NHSE.Core/Save/Files/MainSave.cs b/NHSE.Core/Save/Files/MainSave.cs index 555b8b1..c878ad8 100644 --- a/NHSE.Core/Save/Files/MainSave.cs +++ b/NHSE.Core/Save/Files/MainSave.cs @@ -44,6 +44,34 @@ public IReadOnlyList Buildings set => Building.SetArray(value).CopyTo(Data, Offsets.Buildings); } + public PlayerHouse GetPlayerHouse(int index) + { + if ((uint)index >= MainSaveOffsets.PlayerCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return Data.Slice(Offsets.PlayerHouseList + (index * PlayerHouse.SIZE), PlayerHouse.SIZE).ToClass(); + } + + public void SetPlayerHouse(PlayerHouse h, int index) + { + if ((uint)index >= MainSaveOffsets.PlayerCount) + throw new ArgumentOutOfRangeException(nameof(index)); + h.ToBytesClass().CopyTo(Data, Offsets.PlayerHouseList + (index * PlayerHouse.SIZE)); + } + + public VillagerHouse GetVillagerHouse(int index) + { + if ((uint)index >= MainSaveOffsets.VillagerCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return Data.Slice(Offsets.NpcHouseList + (index * VillagerHouse.SIZE), VillagerHouse.SIZE).ToClass(); + } + + public void SetVillagerHouse(VillagerHouse h, int index) + { + if ((uint)index >= MainSaveOffsets.VillagerCount) + throw new ArgumentOutOfRangeException(nameof(index)); + h.ToBytesClass().CopyTo(Data, Offsets.NpcHouseList + (index * VillagerHouse.SIZE)); + } + public TurnipStonk Turnips { get => Data.Slice(Offsets.TurnipExchange, TurnipStonk.SIZE).ToClass(); diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets.cs index 3e9147e..2d931be 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets.cs @@ -7,6 +7,8 @@ namespace NHSE.Core /// public abstract class MainSaveOffsets { + public const int PlayerCount = 8; + public abstract int Villager { get; } public const int VillagerSize = 0x12AB0; public const int VillagerCount = 10; @@ -22,9 +24,12 @@ public abstract class MainSaveOffsets public abstract int TurnipExchange { get; } + public abstract int EventFlagLand { get; } public abstract int FieldItem { get; } public abstract int Acres { get; } public abstract int Terrain { get; } + public abstract int PlayerHouseList { get; } + public abstract int NpcHouseList { get; } public static MainSaveOffsets GetOffsets(FileHeaderInfo Info) { diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs index e8cb65d..33e0b3c 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs @@ -7,10 +7,16 @@ public class MainSaveOffsets10 : MainSaveOffsets { public override int Villager => 0x110; public override int Patterns => 0x1D72F0; + + public override int EventFlagLand => FieldItem - 0x800; public override int FieldItem => 0x2018FC; - public override int Terrain => Buildings - 0x24C00; // dunno where exactly it starts??? + public override int Terrain => Buildings - 0x24C00; public override int Buildings => 0x2D0EFC; public override int Acres => 0x2D1294; + + public override int PlayerHouseList => FieldItem + 0xDAA2C; + public override int NpcHouseList => PlayerHouseList + (PlayerCount * PlayerHouse.SIZE); + public override int TurnipExchange => 0x4118C0; public override int RecycleBin => 0xABC000; // yep. } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs index 4bb9ba3..5e42936 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs @@ -7,10 +7,18 @@ public class MainSaveOffsets11 : MainSaveOffsets { public override int Villager => 0x120; public override int Patterns => 0x1D7310; // +0x20 from v1.0 - public override int FieldItem => 0x20191C; // +0x20 from v1.0 - public override int Terrain => Buildings - 0x24C00; // dunno where exactly it starts??? // +0x20 from v1.0 + + public override int EventFlagLand => FieldItem - 0x800; + + // GSaveMainField + public override int FieldItem => 0x20191C; // +0x20 from v1.0 // Layer 1&2, and 2*0x1500 + public override int Terrain => Buildings - 0x24C00;// +0x20 from v1.0 // LandMakingMap public override int Buildings => 0x2D0F1C; // +0x20 from v1.0 public override int Acres => 0x2D12B4; // +0x20 from v1.0 + + public override int PlayerHouseList => FieldItem + 0xDAA2C; + public override int NpcHouseList => PlayerHouseList + (PlayerCount * PlayerHouse.SIZE); + public override int TurnipExchange => 0x412060; // +0x7A0 from v1.0 public override int RecycleBin => 0xABDE70; // +0x1E70 from v1.0 } diff --git a/NHSE.Core/Structures/GameFileDumper.cs b/NHSE.Core/Structures/GameFileDumper.cs index 286822d..f1f76e0 100644 --- a/NHSE.Core/Structures/GameFileDumper.cs +++ b/NHSE.Core/Structures/GameFileDumper.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; namespace NHSE.Core @@ -16,7 +17,7 @@ public static class GameFileDumper /// Path to dump to public static void Dump(this HorizonSave sav, string path) { - sav.Main.Dump(path); + sav.Main.DumpVillagerHouses(path); foreach (var p in sav.Players) { var dir = Path.Combine(path, p.DirectoryName); @@ -53,7 +54,54 @@ private static void Dump(string path, byte[] data, string name) } /// - /// Dumps all villagers in their decrypted state to the requested . + /// Dumps all villager houses to the requested . + /// + /// Save Data to dump from + /// Path to dump to + public static void DumpPlayerHouses(this HorizonSave sav, string path) + { + var count = Math.Min(sav.Players.Length, MainSaveOffsets.PlayerCount); + for (int i = 0; i < count; i++) + { + var p = sav.Players[i]; + var h = sav.Main.GetPlayerHouse(i); + h.Dump(path, p.Personal); + } + } + + private static void Dump(this PlayerHouse h, string path, Personal p) + { + var name = GameInfo.Strings.GetVillager(p.PlayerName); + var dest = Path.Combine(path, $"{name}.nhph"); + var data = h.ToBytesClass(); + File.WriteAllBytes(dest, data); + } + + /// + /// Dumps all villager houses to the requested . + /// + /// Save Data to dump from + /// Path to dump to + public static void DumpVillagerHouses(this MainSave sav, string path) + { + for (int i = 0; i < MainSaveOffsets.VillagerCount; i++) + { + var v = sav.GetVillager(i); + var h = sav.GetVillagerHouse(i); + h.Dump(path, v); + } + } + + private static void Dump(this VillagerHouse h, string path, Villager v) + { + var name = GameInfo.Strings.GetVillager(v.InternalName); + var dest = Path.Combine(path, $"{name}.nhvh"); + var data = h.ToBytesClass(); + File.WriteAllBytes(dest, data); + } + + /// + /// Dumps all villagers to the requested . /// /// Data to dump from /// Path to dump to @@ -71,7 +119,7 @@ private static void DumpVillager(this Villager v, string path) } /// - /// Dumps all villagers in their decrypted state to the requested . + /// Dumps all designs to the requested . /// /// Save Data to dump from /// Path to dump to diff --git a/NHSE.Core/Structures/Villager/GSaveItemName.cs b/NHSE.Core/Structures/Villager/GSaveItemName.cs new file mode 100644 index 0000000..710b8d6 --- /dev/null +++ b/NHSE.Core/Structures/Villager/GSaveItemName.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace NHSE.Core +{ + [StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 1)] + public struct GSaveItemName + { + public const int SIZE = 0x08; + [field: FieldOffset(0x00)] public ushort UniqueID { get; set; } + [field: FieldOffset(0x02)] public byte SystemParam { get; set; } + [field: FieldOffset(0x03)] public byte AdditionalParam { get; set; } + [field: FieldOffset(0x04)] public uint FreeParam { get; set; } + + // ReSharper disable once NonReadonlyMemberInGetHashCode + public override int GetHashCode() => UniqueID; + public override bool Equals(object obj) => obj is GSaveItemName i && i.Equals(this); + public bool Equals(GSaveItemName obj) => obj.UniqueID == UniqueID && obj.SystemParam == SystemParam && obj.AdditionalParam == AdditionalParam; + public static bool operator ==(GSaveItemName left, GSaveItemName right) => left.Equals(right); + public static bool operator !=(GSaveItemName left, GSaveItemName right) => !(left == right); + } +} diff --git a/NHSE.Core/Structures/Villager/IHouseInfo.cs b/NHSE.Core/Structures/Villager/IHouseInfo.cs new file mode 100644 index 0000000..a7af586 --- /dev/null +++ b/NHSE.Core/Structures/Villager/IHouseInfo.cs @@ -0,0 +1,29 @@ +namespace NHSE.Core +{ + public interface IHouseInfo + { + uint HouseLevel { get; set; } + ushort WallUniqueID { get; set; } + ushort RoofUniqueID { get; set; } + ushort DoorUniqueID { get; set; } + ushort OrderWallUniqueID { get; set; } + ushort OrderRoofUniqueID { get; set; } + ushort OrderDoorUniqueID { get; set; } + GSaveItemName DoorDecoItemName { get; set; } + } + + public static class HouseInfoExtensions + { + public static void CopyFrom(this IHouseInfo dest, IHouseInfo src) + { + dest.HouseLevel = src.HouseLevel; + dest.WallUniqueID = src.WallUniqueID; + dest.RoofUniqueID = src.RoofUniqueID; + dest.DoorUniqueID = src.DoorUniqueID; + dest.OrderWallUniqueID = src.OrderWallUniqueID; + dest.OrderRoofUniqueID = src.OrderRoofUniqueID; + dest.OrderDoorUniqueID = src.OrderDoorUniqueID; + dest.DoorDecoItemName = src.DoorDecoItemName; + } + } +} diff --git a/NHSE.Core/Structures/Villager/PlayerHouse.cs b/NHSE.Core/Structures/Villager/PlayerHouse.cs new file mode 100644 index 0000000..05a4e82 --- /dev/null +++ b/NHSE.Core/Structures/Villager/PlayerHouse.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace NHSE.Core +{ + [StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 4)] + public class PlayerHouse : IHouseInfo + { + public const int SIZE = 0x26400; + + [field: FieldOffset(0x000)] public uint HouseLevel { get; set; } + [field: FieldOffset(0x004)] public ushort WallUniqueID { get; set; } + [field: FieldOffset(0x006)] public ushort RoofUniqueID { get; set; } + [field: FieldOffset(0x008)] public ushort DoorUniqueID { get; set; } + [field: FieldOffset(0x00A)] public ushort OrderWallUniqueID { get; set; } + [field: FieldOffset(0x00C)] public ushort OrderRoofUniqueID { get; set; } + [field: FieldOffset(0x00E)] public ushort OrderDoorUniqueID { get; set; } + + [field: FieldOffset(0x263D4)] public GSaveItemName DoorDecoItemName { get; set; } + + [field: FieldOffset(0x263E0)] public GSaveItemName PostItemName { get; set; } + [field: FieldOffset(0x263E8)] public GSaveItemName OrderPostItemName { get; set; } + } +} diff --git a/NHSE.Core/Structures/Villager/VillagerHouse.cs b/NHSE.Core/Structures/Villager/VillagerHouse.cs index 9d31450..1509dd1 100644 --- a/NHSE.Core/Structures/Villager/VillagerHouse.cs +++ b/NHSE.Core/Structures/Villager/VillagerHouse.cs @@ -3,8 +3,8 @@ namespace NHSE.Core { [StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 4)] - public class VillagerHouse - { + public class VillagerHouse : IHouseInfo +{ public const int SIZE = 0x1D4; public const int ItemCount = 36; @@ -23,21 +23,4 @@ public class VillagerHouse [field: FieldOffset(0x1C8)] public GSaveItemName DoorDecoItemName { get; set; } [field: FieldOffset(0x1D0)] public sbyte BuildPlayer { get; set; } } - - [StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 1)] - public struct GSaveItemName - { - public const int SIZE = 0x08; - [field: FieldOffset(0x00)] public ushort UniqueID { get; set; } - [field: FieldOffset(0x02)] public byte SystemParam { get; set; } - [field: FieldOffset(0x03)] public byte AdditionalParam { get; set; } - [field: FieldOffset(0x04)] public uint FreeParam { get; set; } - - // ReSharper disable once NonReadonlyMemberInGetHashCode - public override int GetHashCode() => UniqueID; - public override bool Equals(object obj) => obj is GSaveItemName i && i.Equals(this); - public bool Equals(GSaveItemName obj) => obj.UniqueID == UniqueID && obj.SystemParam == SystemParam && obj.AdditionalParam == AdditionalParam; - public static bool operator ==(GSaveItemName left, GSaveItemName right) => left.Equals(right); - public static bool operator !=(GSaveItemName left, GSaveItemName right) => !(left == right); - } } diff --git a/NHSE.Tests/MarshalTests.cs b/NHSE.Tests/MarshalTests.cs index a434ac4..e2a41ff 100644 --- a/NHSE.Tests/MarshalTests.cs +++ b/NHSE.Tests/MarshalTests.cs @@ -12,6 +12,7 @@ public class MarshalTests [Fact] public void MarshalTurnip() => MarshalTest(TurnipStonk.SIZE); [Fact] public void MarshalBuilding() => MarshalTest(Building.SIZE); [Fact] public void MarshalVillagerHouse() => MarshalTest(VillagerHouse.SIZE); + [Fact] public void MarshalPlayerHouse() => MarshalTest(PlayerHouse.SIZE); [Fact] public void MarshalVillagerHouseItem() => MarshalTestS(VillagerHouseItem.SIZE); [Fact] public void MarshalGSaveItemName() => MarshalTestS(GSaveItemName.SIZE); diff --git a/NHSE.WinForms/Controls/VillagerEditor.Designer.cs b/NHSE.WinForms/Controls/VillagerEditor.Designer.cs index fa3dfdd..31261e7 100644 --- a/NHSE.WinForms/Controls/VillagerEditor.Designer.cs +++ b/NHSE.WinForms/Controls/VillagerEditor.Designer.cs @@ -46,6 +46,8 @@ private void InitializeComponent() this.PB_Villager = new System.Windows.Forms.PictureBox(); this.L_VillagerID = new System.Windows.Forms.Label(); this.NUD_Villager = new System.Windows.Forms.NumericUpDown(); + this.B_LoadHouse = new System.Windows.Forms.Button(); + this.B_DumpHouse = new System.Windows.Forms.Button(); ((System.ComponentModel.ISupportInitialize)(this.NUD_Variant)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.NUD_Species)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.PB_Villager)).BeginInit(); @@ -54,7 +56,7 @@ private void InitializeComponent() // // B_EditVillagerFlags // - this.B_EditVillagerFlags.Location = new System.Drawing.Point(297, 165); + this.B_EditVillagerFlags.Location = new System.Drawing.Point(297, 119); this.B_EditVillagerFlags.Name = "B_EditVillagerFlags"; this.B_EditVillagerFlags.Size = new System.Drawing.Size(92, 40); this.B_EditVillagerFlags.TabIndex = 46; @@ -74,7 +76,7 @@ private void InitializeComponent() // // B_EditFurniture // - this.B_EditFurniture.Location = new System.Drawing.Point(199, 165); + this.B_EditFurniture.Location = new System.Drawing.Point(199, 119); this.B_EditFurniture.Name = "B_EditFurniture"; this.B_EditFurniture.Size = new System.Drawing.Size(92, 40); this.B_EditFurniture.TabIndex = 44; @@ -242,10 +244,32 @@ private void InitializeComponent() 0}); this.NUD_Villager.ValueChanged += new System.EventHandler(this.LoadVillager); // + // B_LoadHouse + // + this.B_LoadHouse.Location = new System.Drawing.Point(297, 165); + this.B_LoadHouse.Name = "B_LoadHouse"; + this.B_LoadHouse.Size = new System.Drawing.Size(92, 40); + this.B_LoadHouse.TabIndex = 48; + this.B_LoadHouse.Text = "Load House"; + this.B_LoadHouse.UseVisualStyleBackColor = true; + this.B_LoadHouse.Click += new System.EventHandler(this.B_LoadHouse_Click); + // + // B_DumpHouse + // + this.B_DumpHouse.Location = new System.Drawing.Point(199, 165); + this.B_DumpHouse.Name = "B_DumpHouse"; + this.B_DumpHouse.Size = new System.Drawing.Size(92, 40); + this.B_DumpHouse.TabIndex = 47; + this.B_DumpHouse.Text = "Dump House"; + this.B_DumpHouse.UseVisualStyleBackColor = true; + this.B_DumpHouse.Click += new System.EventHandler(this.B_DumpHouse_Click); + // // VillagerEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.B_LoadHouse); + this.Controls.Add(this.B_DumpHouse); this.Controls.Add(this.B_EditVillagerFlags); this.Controls.Add(this.CHK_VillagerMovingOut); this.Controls.Add(this.B_EditFurniture); @@ -295,5 +319,7 @@ private void InitializeComponent() private System.Windows.Forms.PictureBox PB_Villager; private System.Windows.Forms.Label L_VillagerID; private System.Windows.Forms.NumericUpDown NUD_Villager; + private System.Windows.Forms.Button B_LoadHouse; + private System.Windows.Forms.Button B_DumpHouse; } } diff --git a/NHSE.WinForms/Controls/VillagerEditor.cs b/NHSE.WinForms/Controls/VillagerEditor.cs index 4250ccb..fc254ec 100644 --- a/NHSE.WinForms/Controls/VillagerEditor.cs +++ b/NHSE.WinForms/Controls/VillagerEditor.cs @@ -10,14 +10,19 @@ public partial class VillagerEditor : UserControl { public Villager[] Villagers; public IVillagerOrigin Origin; + private readonly MainSave SAV; private int VillagerIndex = -1; - public VillagerEditor(Villager[] villagers, IVillagerOrigin origin) + public VillagerEditor(Villager[] villagers, IVillagerOrigin origin, MainSave sav, bool hasHouses) { Villagers = villagers; Origin = origin; + SAV = sav; InitializeComponent(); LoadVillagers(); + + if (!hasHouses) + B_DumpHouse.Visible = B_LoadHouse.Visible = false; } public void Save() => SaveVillager(VillagerIndex); @@ -161,5 +166,60 @@ private void ChangeVillager() L_ExternalName.Text = GameInfo.Strings.GetVillager(name); PB_Villager.Image = VillagerSprite.GetVillagerSprite(name); } + + private void B_DumpHouse_Click(object sender, EventArgs e) + { + if (ModifierKeys == Keys.Shift) + { + using var fbd = new FolderBrowserDialog(); + if (fbd.ShowDialog() != DialogResult.OK) + return; + + var dir = Path.GetDirectoryName(fbd.SelectedPath); + if (dir == null || !Directory.Exists(dir)) + return; + SAV.DumpVillagerHouses(fbd.SelectedPath); + return; + } + + var name = L_ExternalName.Text; + using var sfd = new SaveFileDialog + { + Filter = "New Horizons Villager House (*.nhvh)|*.nhvh|All files (*.*)|*.*", + FileName = $"{name}.nhvh", + }; + if (sfd.ShowDialog() != DialogResult.OK) + return; + + var h = SAV.GetVillagerHouse(VillagerIndex); + var data = h.ToBytesClass(); + File.WriteAllBytes(sfd.FileName, data); + } + + private void B_LoadHouse_Click(object sender, EventArgs e) + { + var name = L_ExternalName.Text; + using var ofd = new OpenFileDialog + { + Filter = "New Horizons Villager House (*.nhvh)|*.nhvh|All files (*.*)|*.*", + FileName = $"{name}.nhvh", + }; + if (ofd.ShowDialog() != DialogResult.OK) + return; + + var file = ofd.FileName; + var fi = new FileInfo(file); + const int expectLength = VillagerHouse.SIZE; + if (fi.Length != expectLength) + { + var msg = $"Imported villager house's data length (0x{fi.Length:X}) does not match the required length (0x{expectLength:X})."; + WinFormsUtil.Error("Cancelling:", msg); + return; + } + + var data = File.ReadAllBytes(file); + var h = data.ToClass(); + SAV.SetVillagerHouse(h, VillagerIndex); + } } } diff --git a/NHSE.WinForms/Editor.Designer.cs b/NHSE.WinForms/Editor.Designer.cs index 73050e7..292253a 100644 --- a/NHSE.WinForms/Editor.Designer.cs +++ b/NHSE.WinForms/Editor.Designer.cs @@ -77,6 +77,10 @@ private void InitializeComponent() this.B_EditTurnipExchange = new System.Windows.Forms.Button(); this.B_EditBuildings = new System.Windows.Forms.Button(); this.B_RecycleBin = new System.Windows.Forms.Button(); + this.B_LoadHouse = new System.Windows.Forms.Button(); + this.B_DumpHouse = new System.Windows.Forms.Button(); + this.NUD_PlayerHouse = new System.Windows.Forms.NumericUpDown(); + this.L_PlayerHouse = new System.Windows.Forms.Label(); this.Menu_Editor.SuspendLayout(); this.TC_Editors.SuspendLayout(); this.Tab_Players.SuspendLayout(); @@ -90,6 +94,7 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.NUD_PatternIndex)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.PB_Pattern)).BeginInit(); this.Tab_Map.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.NUD_PlayerHouse)).BeginInit(); this.SuspendLayout(); // // Menu_Editor @@ -518,6 +523,10 @@ private void InitializeComponent() // // Tab_Map // + this.Tab_Map.Controls.Add(this.L_PlayerHouse); + this.Tab_Map.Controls.Add(this.NUD_PlayerHouse); + this.Tab_Map.Controls.Add(this.B_LoadHouse); + this.Tab_Map.Controls.Add(this.B_DumpHouse); this.Tab_Map.Controls.Add(this.B_EditFieldItems); this.Tab_Map.Controls.Add(this.B_EditTerrain); this.Tab_Map.Controls.Add(this.B_EditAcres); @@ -592,6 +601,52 @@ private void InitializeComponent() this.B_RecycleBin.UseVisualStyleBackColor = true; this.B_RecycleBin.Click += new System.EventHandler(this.B_RecycleBin_Click); // + // B_LoadHouse + // + this.B_LoadHouse.Location = new System.Drawing.Point(106, 29); + this.B_LoadHouse.Name = "B_LoadHouse"; + this.B_LoadHouse.Size = new System.Drawing.Size(92, 40); + this.B_LoadHouse.TabIndex = 50; + this.B_LoadHouse.Text = "Load House"; + this.B_LoadHouse.UseVisualStyleBackColor = true; + this.B_LoadHouse.Click += new System.EventHandler(this.B_LoadHouse_Click); + // + // B_DumpHouse + // + this.B_DumpHouse.Location = new System.Drawing.Point(8, 29); + this.B_DumpHouse.Name = "B_DumpHouse"; + this.B_DumpHouse.Size = new System.Drawing.Size(92, 40); + this.B_DumpHouse.TabIndex = 49; + this.B_DumpHouse.Text = "Dump House"; + this.B_DumpHouse.UseVisualStyleBackColor = true; + this.B_DumpHouse.Click += new System.EventHandler(this.B_DumpHouse_Click); + // + // numericUpDown1 + // + this.NUD_PlayerHouse.Location = new System.Drawing.Point(106, 6); + this.NUD_PlayerHouse.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.NUD_PlayerHouse.Name = "NUD_PlayerHouse"; + this.NUD_PlayerHouse.Size = new System.Drawing.Size(45, 20); + this.NUD_PlayerHouse.TabIndex = 51; + this.NUD_PlayerHouse.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + // + // L_PlayerHouse + // + this.L_PlayerHouse.Location = new System.Drawing.Point(16, 6); + this.L_PlayerHouse.Name = "L_PlayerHouse"; + this.L_PlayerHouse.Size = new System.Drawing.Size(84, 20); + this.L_PlayerHouse.TabIndex = 52; + this.L_PlayerHouse.Text = "Player House:"; + this.L_PlayerHouse.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // // Editor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -621,6 +676,7 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.NUD_PatternIndex)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.PB_Pattern)).EndInit(); this.Tab_Map.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.NUD_PlayerHouse)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -675,6 +731,10 @@ private void InitializeComponent() private System.Windows.Forms.Button B_EditTerrain; private System.Windows.Forms.Button B_EditPlayerFlags; private System.Windows.Forms.Button B_EditFieldItems; + private System.Windows.Forms.NumericUpDown NUD_PlayerHouse; + private System.Windows.Forms.Button B_LoadHouse; + private System.Windows.Forms.Button B_DumpHouse; + private System.Windows.Forms.Label L_PlayerHouse; } } diff --git a/NHSE.WinForms/Editor.cs b/NHSE.WinForms/Editor.cs index d7fb3de..303691e 100644 --- a/NHSE.WinForms/Editor.cs +++ b/NHSE.WinForms/Editor.cs @@ -124,7 +124,7 @@ private VillagerEditor LoadVillagers() { var p0 = SAV.Players[0].Personal; var villagers = SAV.Main.GetVillagers(); - var v = new VillagerEditor(villagers, p0) {Dock = DockStyle.Fill}; + var v = new VillagerEditor(villagers, p0, SAV.Main, true) {Dock = DockStyle.Fill}; Tab_Villagers.Controls.Add(v); return v; } @@ -146,6 +146,7 @@ private void LoadPlayers() PlayerIndex = -1; CB_Players.SelectedIndex = 0; + NUD_PlayerHouse.Maximum = CB_Players.Items.Count; } private void LoadMain() @@ -439,5 +440,62 @@ private void B_EditFieldItems_Click(object sender, EventArgs e) using var editor = new FieldItemEditor(SAV.Main); editor.ShowDialog(); } + + private void B_DumpHouse_Click(object sender, EventArgs e) + { + if (ModifierKeys == Keys.Shift) + { + using var fbd = new FolderBrowserDialog(); + if (fbd.ShowDialog() != DialogResult.OK) + return; + + var dir = Path.GetDirectoryName(fbd.SelectedPath); + if (dir == null || !Directory.Exists(dir)) + return; + SAV.DumpPlayerHouses(fbd.SelectedPath); + return; + } + + var index = (int)(NUD_PlayerHouse.Value - 1); + var name = CB_Players.Items[index].ToString(); + using var sfd = new SaveFileDialog + { + Filter = "New Horizons Player House (*.nhph)|*.nhph|All files (*.*)|*.*", + FileName = $"{name}.nhph", + }; + if (sfd.ShowDialog() != DialogResult.OK) + return; + + var h = SAV.Main.GetPlayerHouse(index); + var data = h.ToBytesClass(); + File.WriteAllBytes(sfd.FileName, data); + } + + private void B_LoadHouse_Click(object sender, EventArgs e) + { + var index = (int)(NUD_PlayerHouse.Value - 1); + var name = CB_Players.Items[index].ToString(); + using var ofd = new OpenFileDialog + { + Filter = "New Horizons Player House (*.nhph)|*.nhph|All files (*.*)|*.*", + FileName = $"{name}.nhph", + }; + if (ofd.ShowDialog() != DialogResult.OK) + return; + + var file = ofd.FileName; + var fi = new FileInfo(file); + const int expectLength = PlayerHouse.SIZE; + if (fi.Length != expectLength) + { + var msg = $"Imported player house's data length (0x{fi.Length:X}) does not match the required length (0x{expectLength:X})."; + WinFormsUtil.Error("Cancelling:", msg); + return; + } + + var data = File.ReadAllBytes(file); + var h = data.ToClass(); + SAV.Main.SetPlayerHouse(h, index); + } } }