diff --git a/DisPlacePlugin.json b/DisPlacePlugin.json index 672b8f8..722a4fd 100644 --- a/DisPlacePlugin.json +++ b/DisPlacePlugin.json @@ -2,7 +2,7 @@ "Author": "Drakansoul", "Name": "DisPlace Plugin", "InternalName": "DisPlacePlugin", - "AssemblyVersion": "3.6.0", + "AssemblyVersion":"3.7.0", "Punchline": "Automatically save & load furniture layouts in ANY house.", "Description": "Automatically save & load the positions of house furniture. Copy other housing layouts. Import/export layouts from the MakePlace program.", "ApplicableVersion": "any", @@ -19,9 +19,9 @@ "LoadPriority": 0, "IconUrl": "https://raw.githubusercontent.com/Drakansoul/DisPlaced/master/icon.png", "DownloadCount": 0, - "DownloadLinkInstall": "https://github.com/Drakansoul/DisPlaced/releases/download/v3.6.0/DisPlacePlugin.zip", - "DownloadLinkTesting": "https://github.com/Drakansoul/DisPlaced/releases/download/v3.6.0/DisPlacePlugin.zip", - "DownloadLinkUpdate": "https://github.com/Drakansoul/DisPlaced/releases/download/v3.6.0/DisPlacePlugin.zip", + "DownloadLinkInstall": "https://github.com/Drakansoul/DisPlaced/releases/download/v3.7.0/DisPlacePlugin.zip", + "DownloadLinkTesting": "https://github.com/Drakansoul/DisPlaced/releases/download/v3.7.0/DisPlacePlugin.zip", + "DownloadLinkUpdate": "https://github.com/Drakansoul/DisPlaced/releases/download/v3.7.0/DisPlacePlugin.zip", "_isDip17Plugin": false, "_Dip17Channel": null }] diff --git a/DisPlacePlugin/DisPlacePlugin.cs b/DisPlacePlugin/DisPlacePlugin.cs index 113d1bf..ffcc059 100644 --- a/DisPlacePlugin/DisPlacePlugin.cs +++ b/DisPlacePlugin/DisPlacePlugin.cs @@ -11,6 +11,7 @@ using System.Threading; using static DisPlacePlugin.Memory; using HousingFurniture = Lumina.Excel.GeneratedSheets.HousingFurniture; +using Lumina.Excel; namespace DisPlacePlugin { @@ -29,9 +30,10 @@ public sealed class DisPlacePlugin : IDalamudPlugin // Function for selecting an item, usually used when clicking on one in game. public delegate void SelectItemDelegate(IntPtr housingStruct, IntPtr item); private static HookWrapper SelectItemHook; + - public delegate void PlaceItemDelegate(IntPtr housingStruct, IntPtr item); - private static HookWrapper PlaceItemHook; + public delegate void ClickItemDelegate(IntPtr housingStruct, IntPtr item); + private static HookWrapper ClickItemHook; public static bool CurrentlyPlacingItems = false; @@ -88,7 +90,7 @@ public DisPlacePlugin(IDalamudPluginInterface pi) Memory.Instance.SetPlaceAnywhere(true); LayoutManager = new SaveLayoutManager(this, Config); - DalamudApi.PluginLog.Info("DisPlace Plugin v3.6.0 initialized"); + DalamudApi.PluginLog.Info("DisPlace Plugin v3.7.0 initialized"); } public void Initialize() { @@ -97,7 +99,7 @@ public void Initialize() SelectItemHook = HookManager.Hook("48 85 D2 0F 84 49 09 00 00 53 41 56 48 83 EC 48 48 89 6C 24 60 48 8B DA 48 89 74 24 70 4C 8B F1", SelectItemDetour); - PlaceItemHook = HookManager.Hook("48 89 5C 24 10 48 89 74 24 18 57 48 83 EC 20 4c 8B 41 18 33 FF 0F B6 F2", PlaceItemDetour); + ClickItemHook = HookManager.Hook("48 89 5C 24 10 48 89 74 24 18 57 48 83 EC 20 4c 8B 41 18 33 FF 0F B6 F2", ClickItemDetour); UpdateYardObjHook = HookManager.Hook("48 89 74 24 18 57 48 83 ec 20 b8 dc 02 00 00 0f b7 f2 ??", UpdateYardObj); @@ -107,8 +109,15 @@ public void Initialize() GetYardIndexHook = HookManager.Hook("48 89 6c 24 18 56 48 83 ec 20 0f b6 ?? 0f b6 ?? ?? ?? ?? ?? ?? ?? ??", GetYardIndex); + MaybePlaceh = HookManager.Hook("40 55 56 57 48 8D AC 24 70 FF FF FF 48 81 EC 90", MaybePlace); // MaybePlace0 + ResetItemPlacementh = HookManager.Hook("48 89 5C 24 08 57 48 83 EC 20 48 83 79 18 00 0F", Hc1); // reset item to previous position on failed placement + FinalizeHousingh = HookManager.Hook("40 55 56 41 56 48 83 EC 20 48 63 EA 48 8B F1 8B", FinalizeHousing); // finalize housing placement on interface close + PlaceCallh = HookManager.Hook("40 53 48 83 Ec 20 48 8B 51 18 48 8B D9 48 85 D2 0F 84 B1 00 00 00", PlaceCall); // branch b place + } + + internal delegate ushort GetIndexDelegate(byte type, byte objStruct); internal static HookWrapper GetYardIndexHook; internal static ushort GetYardIndex(byte plotNumber, byte inventoryIndex) @@ -155,7 +164,56 @@ unsafe static public void SelectItem(IntPtr item) SelectItemDetour((IntPtr)Memory.Instance.HousingStructure, item); } - unsafe static public void PlaceItemDetour(IntPtr housing, IntPtr item) + + internal delegate void MaybePlaced(IntPtr housingPtr,IntPtr itemPtr, Int64 a); // @@@@@ + internal static HookWrapper MaybePlaceh; + unsafe static public void MaybePlacedt(IntPtr housingPtr,IntPtr itemPtr, Int64 a) + { + DalamudApi.PluginLog.Verbose(string.Format("maybe place {0} {1} {2}",(housingPtr+24).ToString(),itemPtr.ToString(),a.ToString())); + MaybePlaceh.Original(housingPtr,itemPtr,a); + } + unsafe static public void MaybePlace(IntPtr housingPtr,IntPtr itemPtr, Int64 a) // #### + { + MaybePlacedt(housingPtr,itemPtr,a); + } + + internal delegate void ResetItemPlacementd(IntPtr housingPtr,Int64 a); // @@@@@ + internal static HookWrapper ResetItemPlacementh; + unsafe static public void ResetItemPlacementdt(IntPtr housingPtr, Int64 a) + { + DalamudApi.PluginLog.Debug(string.Format("Return item to previous location if placemnt is canceled {0}",housingPtr+24.ToString())); + ResetItemPlacementh.Original(housingPtr,a); + } + unsafe static public void Hc1(IntPtr housingPtr,Int64 a) // #### + { + ResetItemPlacementdt(housingPtr,a); + } + + internal delegate void FinalizeHousingd(IntPtr housingPtr,Int64 a,IntPtr b); // @@@@@ + internal static HookWrapper FinalizeHousingh; + unsafe static public void FinalizeHousingdt(IntPtr housingPtr, Int64 a, IntPtr b) + { + DalamudApi.PluginLog.Verbose(string.Format("Finalize housing {0} {1} {2}",(housingPtr+24).ToString(),a.ToString(),b.ToString())); + FinalizeHousingh.Original(housingPtr,a,b); + } + unsafe static public void FinalizeHousing(IntPtr housingPtr,Int64 a,IntPtr b) // #### + { + FinalizeHousingdt(housingPtr,a,b); + } + + internal delegate void PlaceCalld(IntPtr housingPtr,Int64 a,IntPtr b); // @@@@@ + internal static HookWrapper PlaceCallh; + unsafe static public void PlaceCalldt(IntPtr housingPtr, Int64 a, IntPtr b) + { + DalamudApi.PluginLog.Verbose(string.Format("placeCall item {0} {1} {2}",(housingPtr+24).ToString(),a.ToString(),b.ToString())); + PlaceCallh.Original(housingPtr,a,b); + } + unsafe static public void PlaceCall(IntPtr housingPtr,Int64 a,IntPtr b) // #### + { + PlaceCalldt(housingPtr,a,b); + } + + unsafe static public void ClickItemDetour(IntPtr housing, IntPtr item) { /* The call made by the XIV client has some strange behaviour. @@ -163,14 +221,14 @@ It can either place the item pointer passed to it or it retrieves the activeItem I tried passing the active item but I believe that doing so led to more crashes. As such I've just defaulted to the easier path of just passing in a zero pointer so that the call populates itself form the housing object. */ - DalamudApi.PluginLog.Debug(string.Format("placing item {0}",(housing+24).ToString())); - PlaceItemHook.Original(housing, item); + DalamudApi.PluginLog.Verbose(string.Format("attempting to place item {0}",(housing+24).ToString())); + ClickItemHook.Original(housing, item); } - unsafe static public void PlaceItem(IntPtr item) + unsafe static public void ClickItem(IntPtr item) { - PlaceItemDetour((IntPtr)Memory.Instance.HousingStructure, item); + ClickItemDetour((IntPtr)Memory.Instance.HousingStructure, item); } @@ -266,7 +324,7 @@ unsafe public static void SetItemPosition(HousingItem rowItem) MemInstance.WritePosition(position); MemInstance.WriteRotation(rotation); - PlaceItem(nint.Zero); + ClickItem(nint.Zero); rowItem.CorrectLocation = true; rowItem.CorrectRotation = true; @@ -277,7 +335,7 @@ public void ApplyLayout() { if (CurrentlyPlacingItems) { - Log($"Already placing items"); + LogError($"Already placing items"); return; } @@ -371,7 +429,8 @@ public unsafe void MatchLayout() case HousingArea.Outdoors: GetPlotLocation(); - allObjects = Mem.GetExteriorPlacedObjects(); + //Mem.GetExteriorPlacedObjects(out allObjects); + Mem.TryGetNameSortedHousingGameObjectList(out allObjects); ExteriorItemList.ForEach(item => { item.ItemStruct = IntPtr.Zero; @@ -504,9 +563,22 @@ public unsafe void MatchLayout() public unsafe void GetPlotLocation() { var mgr = Memory.Instance.HousingModule->outdoorTerritory; + var plotNumber = mgr->Plot + 1; + if (plotNumber == 256) { + LogError("Not inside a valid Plot"); + PlotLocation = new Location(); + return; + } else { + + DalamudApi.PluginLog.Debug($"Housing plot: {plotNumber}"); + } var territoryId = Memory.Instance.GetTerritoryTypeId(); - var row = DalamudApi.DataManager.GetExcelSheet().GetRow(territoryId); - + TerritoryType row = null; + try { + row = DalamudApi.DataManager.GetExcelSheet().GetRow(territoryId); + } catch (Exception e) { + LogError($"Error: {e.Message}", e.StackTrace); + } if (row == null) { LogError("Plugin Cannot identify territory"); @@ -514,103 +586,78 @@ public unsafe void GetPlotLocation() } var placeName = row.Name.ToString(); - - PlotLocation = Plots.Map[placeName][mgr->Plot + 1]; + + DalamudApi.PluginLog.Info($"Loading Plot Number: {plotNumber}"); + PlotLocation = Plots.Map[placeName][plotNumber]; } public unsafe void LoadExterior() { - SaveLayoutManager.LoadExteriorFixtures(); + List objects; + var playerPos = DalamudApi.ClientState.LocalPlayer.Position; + Memory.Instance.GetExteriorPlacedObjects(out objects, playerPos); ExteriorItemList.Clear(); - - var mgr = Memory.Instance.HousingModule->outdoorTerritory; - - var outdoorMgrAddr = (IntPtr)mgr; - var objectListAddr = outdoorMgrAddr + 0x10; - var activeObjList = objectListAddr + 0x8968; - - var exteriorItems = Memory.GetContainer(InventoryType.HousingExteriorPlacedItems); - GetPlotLocation(); + DalamudApi.PluginLog.Info($"Plot size: {PlotLocation.size}"); + DalamudApi.PluginLog.Info($"Plot x: {PlotLocation.x}"); + DalamudApi.PluginLog.Info($"Plot y: {PlotLocation.y}"); + DalamudApi.PluginLog.Info($"Plot z: {PlotLocation.z}"); + DalamudApi.PluginLog.Info($"Plot rot: {PlotLocation.rotation}"); + DalamudApi.PluginLog.Info($"Plot enter: {PlotLocation.entranceLayout}"); + + DalamudApi.PluginLog.Debug($"Player location within Plot: ({playerPos.X-PlotLocation.x},{playerPos.Y-PlotLocation.y},{playerPos.Z-PlotLocation.z})"); - var rotateVector = Quaternion.CreateFromAxisAngle(Vector3.UnitY, PlotLocation.rotation); - - switch (PlotLocation.size) + foreach (var gameObject in objects) { - case "s": - Layout.houseSize = "Small"; + uint furnitureKey = gameObject.housingRowId; + var furniture = DalamudApi.DataManager.GetExcelSheet().GetRow(furnitureKey); + Item item = furniture?.Item?.Value; + if (item == null) continue; + if (item.RowId == 0) continue; + + // I could probably do this better if I was fully rested and wanted to do vector math properly but I'm just going to do it the easy way. Drakansoul is very tired. + var xMax = 0; + var yMax = 7; + var zMax = 0; + switch (PlotLocation.size){ + case "l": + xMax = 29; + zMax = 29; break; - case "m": - Layout.houseSize = "Medium"; + case "m": + xMax = 16; + zMax = 16; break; - case "l": - Layout.houseSize = "Large"; + case "s": + xMax = 12; + zMax = 12; + break; + default: + yMax = 0; break; - - } - - Layout.exteriorScale = 1; - Layout.properties["entranceLayout"] = PlotLocation.entranceLayout; - - for (int i = 0; i < exteriorItems->Size; i++) - { - var item = exteriorItems->GetInventorySlot(i); - if (item == null || item->ItemId == 0) continue; - - var itemRow = DalamudApi.DataManager.GetExcelSheet().GetRow(item->ItemId); - if (itemRow == null) continue; - - var itemInfoIndex = GetYardIndex(mgr->Plot, (byte)i); - - var itemInfo = HousingObjectManager.GetItemInfo(mgr, itemInfoIndex); - if (itemInfo == null) - { - continue; } - var location = new Vector3(itemInfo->X, itemInfo->Y, itemInfo->Z); + var housingItem = new HousingItem(item, gameObject); + housingItem.ItemStruct = (IntPtr)gameObject.Item; + + var location = new Vector3(housingItem.X, housingItem.Y, housingItem.Z); + var rotateVector = Quaternion.CreateFromAxisAngle(Vector3.UnitY, PlotLocation.rotation); var newLocation = Vector3.Transform(location - PlotLocation.ToVector(), rotateVector); - var housingItem = new HousingItem( - itemRow, - item->Stains[0], - newLocation.X, - newLocation.Y, - newLocation.Z, - itemInfo->Rotation + PlotLocation.rotation - ); - - - var gameObj = (HousingGameObject*)GetObjectFromIndex(activeObjList, itemInfo->ObjectIndex); - - if (gameObj == null) - { - gameObj = (HousingGameObject*)GetGameObject(objectListAddr, itemInfoIndex); - - if (gameObj != null) - { - - location = new Vector3(gameObj->X, gameObj->Y, gameObj->Z); + housingItem.X = newLocation.X; + housingItem.Y = newLocation.Y; + housingItem.Z = newLocation.Z; + housingItem.Rotate += PlotLocation.rotation; - newLocation = Vector3.Transform(location - PlotLocation.ToVector(), rotateVector); - - - housingItem.X = newLocation.X; - housingItem.Y = newLocation.Y; - housingItem.Z = newLocation.Z; - } + if (!(Math.Abs(housingItem.X) > xMax|| Math.Abs(housingItem.Y) > yMax|| Math.Abs(housingItem.Z) > zMax)) { + ExteriorItemList.Add(housingItem); + } else { + DalamudApi.PluginLog.Verbose($"Discarding item: {housingItem.Name} at ({housingItem.X},{housingItem.Y},{housingItem.Z})"); } - - if (gameObj != null) - { - housingItem.ItemStruct = (IntPtr)gameObj->Item; - } - - ExteriorItemList.Add(housingItem); } Config.Save(); @@ -699,9 +746,9 @@ public unsafe void LoadIsland() public void GetGameLayout() { - Memory Mem = Memory.Instance; var currentTerritory = Mem.GetCurrentTerritory(); + DalamudApi.PluginLog.Debug($"Ter ID: {currentTerritory}"); var itemList = currentTerritory == HousingArea.Indoors ? InteriorItemList : ExteriorItemList; itemList.Clear(); diff --git a/DisPlacePlugin/DisPlacePlugin.csproj b/DisPlacePlugin/DisPlacePlugin.csproj index 66e1a4f..378ec76 100644 --- a/DisPlacePlugin/DisPlacePlugin.csproj +++ b/DisPlacePlugin/DisPlacePlugin.csproj @@ -2,7 +2,7 @@ $(AppData)\XIVLauncher\addon\Hooks\dev\ - 3.6.0 + 3.7.0 diff --git a/DisPlacePlugin/Memory.cs b/DisPlacePlugin/Memory.cs index aa495e1..32910d8 100644 --- a/DisPlacePlugin/Memory.cs +++ b/DisPlacePlugin/Memory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Numerics; +using System.Linq; using System.Runtime.InteropServices; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.MJI; @@ -165,49 +166,34 @@ public CommonFixture[] GetExteriorCommonFixtures(int plotId) return ret; } - - public unsafe List GetExteriorPlacedObjects() + public unsafe bool GetExteriorPlacedObjects(out List objects, Vector3 playerPos) + /* + this misses interactable objects such as mailboxes and the cold knights cookfire. + I'll probably just leave it that way unless people really want the functionality since it's probably a hassle to actually track down where these items are stored. + Additionally, HouseMate also does not tag these kinds of items so I presume that adding it also wasn;t a priority for them either. + */ { + objects = null; + if (HousingModule == null || + HousingModule->GetCurrentManager() == null || + HousingModule->GetCurrentManager()->Objects == null) + return false; - var mgr = Memory.Instance.HousingModule->outdoorTerritory; - - var outdoorMgrAddr = (IntPtr)mgr; - var objectListAddr = outdoorMgrAddr + 0x10; - var activeObjList = objectListAddr + 0x8968; - - - var exteriorItems = Memory.GetContainer(InventoryType.HousingExteriorPlacedItems); - - if (exteriorItems == null) throw new Exception("Unable to get inventory for exterior"); - - var placedObjects = new List(); - - for (int i = 0; i < exteriorItems->Size; i++) + var tmpObjects = new List<(HousingGameObject gObject, float distance)>(); + objects = new List(); + var curMgr = HousingModule->outdoorTerritory; + for (var i = 0; i < 400; i++) { - var item = exteriorItems->GetInventorySlot(i); - if (item == null || item->ItemId == 0) continue; - - var itemInfoIndex = GetYardIndex(mgr->Plot, (byte)i); - var itemInfo = HousingObjectManager.GetItemInfo(mgr, itemInfoIndex); - - if (itemInfo == null) continue; - - var gameObj = (HousingGameObject*)GetObjectFromIndex(activeObjList, itemInfo->ObjectIndex); - - if (gameObj == null) - { - gameObj = (HousingGameObject*)GetGameObject(objectListAddr, itemInfoIndex); - } - - if (gameObj != null) - { - placedObjects.Add(*gameObj); - } + var oPtr = curMgr->Objects[i]; + if (oPtr == 0) + continue; + var o = *(HousingGameObject*) oPtr; + tmpObjects.Add((o, Utils.DistanceFromPlayer(o, playerPos))); } - - - return placedObjects; + tmpObjects.Sort((obj1, obj2) => obj1.distance.CompareTo(obj2.distance)); + objects = tmpObjects.Select(obj => obj.gObject).ToList(); + return true; } public unsafe bool TryGetIslandGameObjectList(out List objects) @@ -236,15 +222,12 @@ public unsafe bool TryGetNameSortedHousingGameObjectList(out List(); - for (var i = 0; i < 400; i++) { var oPtr = HousingModule->GetCurrentManager()->Objects[i]; if (oPtr == 0) continue; - var o = *(HousingGameObject*)oPtr; - objects.Add(o); } @@ -254,9 +237,12 @@ public unsafe bool TryGetNameSortedHousingGameObjectList(out List().GetRow(GetTerritoryTypeId()); - if (territoryRow == null) + if (territoryRow == null || territoryRow.Name.ToString().Equals("r1i5")) // blacklist company workshop from editing since it's not actually a housing area { return HousingArea.None; } + //DalamudApi.PluginLog.Debug(territoryRow.Name.ToString()); if (territoryRow.Name.ToString().Equals("h1m2")) { diff --git a/DisPlacePlugin/SaveLayoutManager.cs b/DisPlacePlugin/SaveLayoutManager.cs index 1505a37..ee74766 100644 --- a/DisPlacePlugin/SaveLayoutManager.cs +++ b/DisPlacePlugin/SaveLayoutManager.cs @@ -552,7 +552,6 @@ public void ExportLayout() { throw new Exception("Save file not specified"); } - Layout save = Plugin.Layout; save.playerTransform = new Transform(); @@ -570,11 +569,11 @@ public void ExportLayout() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, WriteIndented = true, - Converters = { new ObjectToInferredTypesConverter() } + Converters = { new ObjectToInferredTypesConverter() }, + NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals //throwing this on here because it makes it easier to debug. This also allows for exporting incorrect values but whatever. }; string jsonString = JsonSerializer.Serialize(save, options); - string pattern = @"\s+(-?(?:[0-9]*[.])?[0-9]+(?:E-[0-9]+)?,?)\s*(?=\s[-\d\]])"; string result = Regex.Replace(jsonString, pattern, " $1"); diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..6f155b8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +- [ ] Add Teamcraft export support +- [ ] Update windows to use proper classes +- [ ] Refactor structure +- [ ] Reverse engineer the more of the housing code \ No newline at end of file