├── icon.png ├── .gitignore ├── BDTHPlugin ├── PluginException.cs ├── BDTHPlugin.json ├── packages.lock.json ├── BDTHPlugin.csproj ├── Interface │ ├── PluginUI.cs │ ├── Windows │ │ ├── DebugWindow.cs │ │ ├── FurnitureList.cs │ │ └── MainWindow.cs │ ├── Gizmo.cs │ └── Components │ │ └── ItemControls.cs ├── Configuration.cs ├── Structs.cs ├── AtkManager.cs ├── Util.cs ├── Plugin.cs └── PluginMemory.cs ├── .vscode └── tasks.json ├── BDTHPlugin.sln ├── README.md └── .github └── workflows └── build.yml /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeonBlade/BDTHPlugin/HEAD/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | */obj/ 3 | */bin/ 4 | *.user 5 | */packages.lock.json 6 | -------------------------------------------------------------------------------- /BDTHPlugin/PluginException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BDTHPlugin 4 | { 5 | public class PluginException : Exception 6 | { 7 | public PluginException() { } 8 | public PluginException(string message) : base($"BDTH Exception: {message}") { } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BDTHPlugin/BDTHPlugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "Author": "LeonBlade", 3 | "Name": "Burning Down the House", 4 | "Description": "Move around housing items with coordinates and allows you to place anywhere.", 5 | "Punchline": "House decorating made easy!", 6 | "InternalName": "BDTHPlugin", 7 | "AssemblyVersion": "1.7.2", 8 | "RepoUrl": "https://github.com/LeonBlade/BDTHPlugin", 9 | "ApplicableVersion": "any", 10 | "Tags": ["bdth", "housing", "float", "place", "glitch", "furnishing", "LeonBlade"], 11 | "DalamudApiLevel": 14, 12 | "IconUrl": "https://github.com/LeonBlade/BDTHPlugin/raw/main/icon.png" 13 | } 14 | -------------------------------------------------------------------------------- /BDTHPlugin/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net10.0-windows7.0": { 5 | "DalamudPackager": { 6 | "type": "Direct", 7 | "requested": "[14.0.1, )", 8 | "resolved": "14.0.1", 9 | "contentHash": "y0WWyUE6dhpGdolK3iKgwys05/nZaVf4ZPtIjpLhJBZvHxkkiE23zYRo7K7uqAgoK/QvK5cqF6l3VG5AbgC6KA==" 10 | }, 11 | "DotNet.ReproducibleBuilds": { 12 | "type": "Direct", 13 | "requested": "[1.2.39, )", 14 | "resolved": "1.2.39", 15 | "contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg==" 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /BDTHPlugin/BDTHPlugin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.7.2 4 | net10.0-windows 5 | true 6 | latest 7 | false 8 | false 9 | false 10 | $(NoWarn);MSB3277 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | -------------------------------------------------------------------------------- /BDTHPlugin/Interface/PluginUI.cs: -------------------------------------------------------------------------------- 1 | using BDTHPlugin.Interface.Windows; 2 | 3 | using Dalamud.Interface.Windowing; 4 | 5 | namespace BDTHPlugin.Interface 6 | { 7 | public class PluginUI 8 | { 9 | private readonly WindowSystem Windows = new("BDTH"); 10 | 11 | private readonly Gizmo Gizmo = new(); 12 | 13 | public readonly MainWindow Main; 14 | public readonly DebugWindow Debug = new(); 15 | public readonly FurnitureList Furniture = new(); 16 | 17 | public PluginUI() 18 | { 19 | Main = new MainWindow(Gizmo); 20 | Windows.AddWindow(Main); 21 | Windows.AddWindow(Debug); 22 | Windows.AddWindow(Furniture); 23 | } 24 | 25 | public void Draw() 26 | { 27 | Gizmo.Draw(); 28 | Windows.Draw(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BDTHPlugin/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Configuration; 2 | using System; 3 | 4 | namespace BDTHPlugin 5 | { 6 | [Serializable] 7 | public class Configuration : IPluginConfiguration 8 | { 9 | public int Version { get; set; } = 0; 10 | public bool UseGizmo { get; set; } = false; 11 | public bool DoSnap { get; set; } = false; 12 | public float Drag { get; set; } = 0.05f; 13 | public bool SortByDistance { get; set; } = false; 14 | public bool AutoVisible { get; set; } = true; 15 | public bool PlaceAnywhere { get; set; } = false; 16 | public bool DisplayFurnishingList { get; set; } = true; 17 | public bool DisplayInventory { get; set; } = true; 18 | 19 | public void Save() 20 | { 21 | Plugin.PluginInterface.SavePluginConfig(this); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build Debug", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/BDTHPlugin/BDTHPlugin.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary", 13 | "/p:Platform=x64" 14 | ], 15 | "problemMatcher": "$msCompile", 16 | "group": { 17 | "kind": "build" 18 | } 19 | }, 20 | { 21 | "label": "Build Release", 22 | "command": "dotnet", 23 | "type": "process", 24 | "args": [ 25 | "build", 26 | "${workspaceFolder}/BDTHPlugin/BDTHPlugin.csproj", 27 | "/property:GenerateFullPaths=true", 28 | "/consoleloggerparameters:NoSummary", 29 | "/p:Platform=x64", 30 | "-c", 31 | "Release" 32 | ], 33 | "problemMatcher": "$msCompile", 34 | "group": { 35 | "kind": "build" 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /BDTHPlugin.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29709.97 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDTHPlugin", "BDTHPlugin\BDTHPlugin.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.ActiveCfg = Debug|x64 15 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.Build.0 = Debug|x64 16 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.ActiveCfg = Release|x64 17 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {B625BDA5-76F0-4AC1-82DF-E58652669F7B} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /BDTHPlugin/Interface/Windows/DebugWindow.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Interface.Windowing; 2 | 3 | using Dalamud.Bindings.ImGui; 4 | 5 | namespace BDTHPlugin.Interface.Windows 6 | { 7 | public class DebugWindow : Window 8 | { 9 | private static PluginMemory Memory => Plugin.GetMemory(); 10 | 11 | public DebugWindow() : base("BDTH Debug") 12 | { 13 | 14 | } 15 | 16 | public unsafe override void Draw() 17 | { 18 | ImGui.Text($"Gamepad Mode: {PluginMemory.GamepadMode}"); 19 | ImGui.Text($"CanEditItem: {Memory.CanEditItem()}"); 20 | ImGui.Text($"IsHousingOpen: {Memory.IsHousingOpen()}"); 21 | ImGui.Separator(); 22 | ImGui.Text($"LayoutWorld: {(ulong)Memory.Layout:X}"); 23 | ImGui.Text($"Housing Structure: {(ulong)Memory.HousingStructure:X}"); 24 | ImGui.Text($"Mode: {Memory.HousingStructure->Mode}"); 25 | ImGui.Text($"State: {Memory.HousingStructure->State}"); 26 | ImGui.Text($"State2: {Memory.HousingStructure->State2}"); 27 | ImGui.Text($"Active: {(ulong)Memory.HousingStructure->ActiveItem:X}"); 28 | ImGui.Text($"Hover: {(ulong)Memory.HousingStructure->HoverItem:X}"); 29 | ImGui.Text($"Rotating: {Memory.HousingStructure->Rotating}"); 30 | ImGui.Separator(); 31 | ImGui.Text($"Housing Module: {(ulong)Memory.HousingModule:X}"); 32 | ImGui.Text($"Current Territory: {(ulong)Memory.HousingModule->CurrentTerritory:X}"); 33 | ImGui.Text($"Outdoor Territory: {(ulong)Memory.HousingModule->OutdoorTerritory:X}"); 34 | ImGui.Text($"Indoor Territory: {(ulong)Memory.HousingModule->IndoorTerritory:X}"); 35 | var active = Memory.HousingStructure->ActiveItem; 36 | if (active != null) 37 | { 38 | ImGui.Separator(); 39 | var pos = Memory.HousingStructure->ActiveItem->Position; 40 | ImGui.Text($"Position: {pos.X}, {pos.Y}, {pos.Z}"); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BDTHPlugin 2 | 3 | ## What is it? 4 | BDTHPlugin is a Dalamud plugin for FFXIV which gives you more control over placing housing items. 5 | 6 | ## What do I need to run it? 7 | First, you must use the [FFXIVQuickLauncher](https://github.com/goatcorp/FFXIVQuickLauncher) to run your game. The Quick Launcher allows for custom plugins to be installed to add new functionality to the game. To install, you first need to have the Quick Launcher setup for your game, instructions can be found on the [FFXIVQuickLauncher](https://github.com/goatcorp/FFXIVQuickLauncher) page. 8 | 9 | ## How do I install it? 10 | ### **[Please visit this page on how to install my plugin via third party repository support.](https://github.com/LeonBlade/DalamudPlugins)** 11 | 12 | ### **ONLY ASK FOR HELP FOR THIS PLUGIN FROM ME OR ON MY DISCORD SERVER** 13 | 14 | ## How do I use it? 15 | `/bdth` brings up the window to move items around. You **MUST** be in rotate mode for it to work. 16 | 17 | Enabling the "Place Anywhere" checkbox will remove restrictions on placing items down for most cases. 18 | 19 | Enabling the "Gizmo" checkbox will give you access to a movement tool in game to manipulate the selected object on all three axis. 20 | 21 | Enabling the "Snap" checkbox will lock your gizmo movements to the interval defined in the "drag" box at the bottom. 22 | 23 | Clicking on the image button will change the movement mode to world and local movement. 24 | 25 | Click and drag on the boxes to change their values based on the drag input, alternatively, use the + and - buttons on the other inputs to adjust by the drag value as well. You can also enter numbers in here if you like by typing them. 26 | 27 | `/bdth list` opens a furnishing list used for indoors. Allows you to sort objects by distance to refine the list of items in item dense house. 28 | 29 | ## FAQ 30 | Please check out the [FAQ](https://github.com/LeonBlade/BDTHPlugin/wiki/FAQ) page to see if your issue might be listed here. 31 | 32 | ## Final message 33 | Thank you for using my tool, I'm very grateful to everyone who uses my tools and I enjoy seeing what people do with them. If you wish to support me, you can do so at any of the links below. You can also join at the Discord to ask questions or share your creations. 34 | 35 | **Ko-Fi:** https://ko-fi.com/LeonBlade 36 | 37 | **Patreon:** https://patreon.com/LeonBlade 38 | 39 | **Discord:** https://discord.gg/H4sSAyb25r 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: [main] 11 | 12 | env: 13 | PROJECT_NAME: BDTHPlugin 14 | 15 | jobs: 16 | build: 17 | runs-on: windows-latest 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | with: 23 | submodules: recursive 24 | 25 | - name: Setup MSBuild 26 | uses: microsoft/setup-msbuild@v2 27 | 28 | - name: Download Dalamud 29 | run: | 30 | Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip 31 | Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev\" 32 | 33 | - name: Build 34 | run: | 35 | dotnet restore 36 | dotnet build --configuration Release --nologo --property:OutputPath=D:/build 37 | env: 38 | DOTNET_CLI_TELEMETRY_OUTPUT: true 39 | 40 | - name: Create artifact 41 | run: | 42 | Compress-Archive -Path "D:/build/*" -DestinationPath D:/build/BDTHPlugin.zip 43 | 44 | - name: Upload artifact 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: BDTHPlugin 48 | path: D:/build/BDTHPlugin.zip 49 | if-no-files-found: error 50 | 51 | release: 52 | needs: build 53 | runs-on: windows-latest 54 | if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' 55 | 56 | steps: 57 | - name: Checkout repo 58 | uses: actions/checkout@v4 59 | with: 60 | fetch-depth: 0 61 | 62 | - name: Download artifact 63 | uses: actions/download-artifact@v4 64 | with: 65 | name: BDTHPlugin 66 | 67 | - name: Get tag name 68 | id: tag 69 | uses: WyriHaximus/github-action-get-previous-tag@v1 70 | 71 | - name: Create release 72 | uses: softprops/action-gh-release@v2 73 | with: 74 | name: ${{ env.PROJECT_NAME }} ${{ steps.tag.outputs.tag }} 75 | tag_name: ${{ steps.tag.outputs.tag }} 76 | body: ${{ github.events.commits[0].message }} 77 | files: BDTHPlugin.zip 78 | 79 | - name: Trigger plugin repo update 80 | uses: peter-evans/repository-dispatch@v1 81 | with: 82 | token: ${{ secrets.PAT }} 83 | repository: LeonBlade/DalamudPlugins 84 | event-type: new-release 85 | -------------------------------------------------------------------------------- /BDTHPlugin/Structs.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace BDTHPlugin 5 | { 6 | public enum HousingLayoutMode 7 | { 8 | None, 9 | Move, 10 | Rotate, 11 | Store, 12 | Place, 13 | Remove = 6 14 | } 15 | 16 | public enum ItemState 17 | { 18 | None = 0, 19 | Hover, 20 | SoftSelect, 21 | Active 22 | } 23 | 24 | public enum ItemState2 25 | { 26 | None = 0, 27 | SoftSelect = 3, 28 | Active = 5 29 | } 30 | 31 | [StructLayout(LayoutKind.Explicit)] 32 | public unsafe struct HousingObjectManager 33 | { 34 | [FieldOffset(0x8980)] public fixed ulong Objects[400]; 35 | [FieldOffset(0x96E8)] public HousingGameObject* IndoorActiveObject2; 36 | [FieldOffset(0x96F0)] public HousingGameObject* IndoorHoverObject; 37 | [FieldOffset(0x96F8)] public HousingGameObject* IndoorActiveObject; 38 | [FieldOffset(0x9AB8)] public HousingGameObject* OutdoorActiveObject2; 39 | [FieldOffset(0x9AC0)] public HousingGameObject* OutdoorHoverObject; 40 | [FieldOffset(0x9AC8)] public HousingGameObject* OutdoorActiveObject; 41 | } 42 | 43 | [StructLayout(LayoutKind.Explicit)] 44 | public unsafe struct HousingModule 45 | { 46 | [FieldOffset(0x0)] public HousingObjectManager* CurrentTerritory; 47 | [FieldOffset(0x8)] public HousingObjectManager* OutdoorTerritory; 48 | [FieldOffset(0x10)] public HousingObjectManager* IndoorTerritory; 49 | 50 | public HousingObjectManager* GetCurrentManager() 51 | => OutdoorTerritory != null ? OutdoorTerritory : IndoorTerritory; 52 | } 53 | 54 | [StructLayout(LayoutKind.Explicit)] 55 | public unsafe struct HousingGameObject 56 | { 57 | [FieldOffset(0x30)] public fixed byte Name[64]; 58 | [FieldOffset(0x84)] public uint HousingRowId; 59 | [FieldOffset(0xB0)] public float X; 60 | [FieldOffset(0xB4)] public float Y; 61 | [FieldOffset(0xB8)] public float Z; 62 | [FieldOffset(0x108)] public HousingItem* Item; 63 | } 64 | 65 | [StructLayout(LayoutKind.Explicit)] 66 | public unsafe struct LayoutWorld 67 | { 68 | [FieldOffset(0x40)] public HousingStructure* HousingStruct; 69 | } 70 | 71 | [StructLayout(LayoutKind.Explicit)] 72 | public unsafe struct HousingStructure 73 | { 74 | [FieldOffset(0x0)] public HousingLayoutMode Mode; 75 | [FieldOffset(0x4)] public HousingLayoutMode LastMode; 76 | [FieldOffset(0x8)] public ItemState State; 77 | [FieldOffset(0xC)] public ItemState2 State2; 78 | [FieldOffset(0x10)] public HousingItem* HoverItem; 79 | [FieldOffset(0x18)] public HousingItem* ActiveItem; 80 | [FieldOffset(0xB8)] public bool Rotating; 81 | } 82 | 83 | [StructLayout(LayoutKind.Explicit)] 84 | public unsafe struct HousingItem 85 | { 86 | [FieldOffset(0x50)] public Vector3 Position; 87 | [FieldOffset(0x60)] public Quaternion Rotation; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /BDTHPlugin/AtkManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Dalamud.Game.NativeWrapper; 4 | using FFXIVClientStructs.FFXIV.Component.GUI; 5 | 6 | namespace BDTHPlugin 7 | { 8 | public class AtkManager 9 | { 10 | private static InventoryType inventoryType; 11 | 12 | enum InventoryType 13 | { 14 | Base, 15 | Large, 16 | Expanded 17 | } 18 | 19 | public static unsafe AtkUnitBasePtr HousingGoods => Plugin.GameGui.GetAddonByName("HousingGoods", 1); 20 | public static unsafe AtkUnitBasePtr Inventory => Plugin.GameGui.GetAddonByName("Inventory", 1); 21 | public static unsafe AtkUnitBasePtr InventoryLarge => Plugin.GameGui.GetAddonByName("InventoryLarge", 1); 22 | public static unsafe AtkUnitBasePtr InventoryExpansion => Plugin.GameGui.GetAddonByName("InventoryExpansion", 1); 23 | 24 | private static readonly unsafe Dictionary> Atks = new() 25 | { 26 | [InventoryType.Base] = [ 27 | Inventory, 28 | Plugin.GameGui.GetAddonByName("InventoryGrid", 1), 29 | Plugin.GameGui.GetAddonByName("InventoryGridCrystal", 1) 30 | ], 31 | [InventoryType.Large] = [ 32 | InventoryLarge, 33 | Plugin.GameGui.GetAddonByName("InventoryEventGrid0", 1), 34 | Plugin.GameGui.GetAddonByName("InventoryEventGrid1", 1), 35 | Plugin.GameGui.GetAddonByName("InventoryEventGrid2", 1), 36 | Plugin.GameGui.GetAddonByName("InventoryCrystalGrid", 1) 37 | ], 38 | [InventoryType.Expanded] = [ 39 | InventoryExpansion, 40 | Plugin.GameGui.GetAddonByName("InventoryGrid0E", 1), 41 | Plugin.GameGui.GetAddonByName("InventoryGrid1E", 1), 42 | Plugin.GameGui.GetAddonByName("InventoryGrid2E", 1), 43 | Plugin.GameGui.GetAddonByName("InventoryGrid3E", 1), 44 | Plugin.GameGui.GetAddonByName("InventoryEventGrid0E", 1), 45 | Plugin.GameGui.GetAddonByName("InventoryEventGrid1E", 1), 46 | Plugin.GameGui.GetAddonByName("InventoryEventGrid2E", 1), 47 | Plugin.GameGui.GetAddonByName("InventoryCrystalGrid", 2) 48 | ] 49 | }; 50 | 51 | public static unsafe bool InventoryVisible 52 | { 53 | get => (InventoryExpansion != null && InventoryExpansion.IsVisible) || 54 | (InventoryLarge != null && InventoryLarge.IsVisible) || 55 | (Inventory != null && Inventory.IsVisible); 56 | 57 | set 58 | { 59 | try 60 | { 61 | if (HousingGoods == null || InventoryExpansion == null || InventoryLarge == null || Inventory == null) 62 | return; 63 | 64 | // Determine which inventory is open assuming it's visible 65 | if (InventoryExpansion.IsVisible || InventoryLarge.IsVisible || Inventory.IsVisible) 66 | { 67 | if (InventoryExpansion.IsVisible) inventoryType = InventoryType.Expanded; 68 | else if (InventoryLarge.IsVisible) inventoryType = InventoryType.Large; 69 | else if (Inventory.IsVisible) inventoryType = InventoryType.Base; 70 | } 71 | 72 | Atks[inventoryType].ForEach((atk) => SetVisible(atk, value)); 73 | } 74 | catch (Exception ex) 75 | { 76 | Plugin.Log.Error("Could not set visibility", ex); 77 | } 78 | } 79 | } 80 | 81 | public static unsafe void ShowFurnishingList(bool state) 82 | { 83 | if (HousingGoods != null) 84 | SetVisible(HousingGoods, state); 85 | } 86 | 87 | private static unsafe void SetVisible(AtkUnitBasePtr ptr, bool visible) => ((AtkUnitBase*)ptr.Address)->IsVisible = visible; 88 | 89 | public static void ShowInventory(bool state) => InventoryVisible = state; 90 | } 91 | } -------------------------------------------------------------------------------- /BDTHPlugin/Util.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | 4 | namespace BDTHPlugin 5 | { 6 | class Util 7 | { 8 | private const float Deg2Rad = (float)(Math.PI * 2) / 360; 9 | private const float Rad2Deg = (float)(360 / (Math.PI * 2)); 10 | 11 | public static Quaternion ToQ(Vector3 euler) 12 | { 13 | var xOver2 = euler.X * Deg2Rad * 0.5f; 14 | var yOver2 = euler.Y * Deg2Rad * 0.5f; 15 | var zOver2 = euler.Z * Deg2Rad * 0.5f; 16 | 17 | var sinXOver2 = (float)Math.Sin(xOver2); 18 | var cosXOver2 = (float)Math.Cos(xOver2); 19 | var sinYOver2 = (float)Math.Sin(yOver2); 20 | var cosYOver2 = (float)Math.Cos(yOver2); 21 | var sinZOver2 = (float)Math.Sin(zOver2); 22 | var cosZOver2 = (float)Math.Cos(zOver2); 23 | 24 | Quaternion result; 25 | result.X = cosYOver2 * sinXOver2 * cosZOver2 + sinYOver2 * cosXOver2 * sinZOver2; 26 | result.Y = sinYOver2 * cosXOver2 * cosZOver2 - cosYOver2 * sinXOver2 * sinZOver2; 27 | result.Z = cosYOver2 * cosXOver2 * sinZOver2 - sinYOver2 * sinXOver2 * cosZOver2; 28 | result.W = cosYOver2 * cosXOver2 * cosZOver2 + sinYOver2 * sinXOver2 * sinZOver2; 29 | return result; 30 | } 31 | 32 | public static Vector3 FromQ(Quaternion q2) 33 | { 34 | Quaternion q = new Quaternion(q2.W, q2.Z, q2.X, q2.Y); 35 | Vector3 pitchYawRoll = new Vector3 36 | { 37 | Y = (float)Math.Atan2(2f * q.X * q.W + 2f * q.Y * q.Z, 1 - 2f * (q.Z * q.Z + q.W * q.W)), 38 | X = (float)Math.Asin(2f * (q.X * q.Z - q.W * q.Y)), 39 | Z = (float)Math.Atan2(2f * q.X * q.Y + 2f * q.Z * q.W, 1 - 2f * (q.Y * q.Y + q.Z * q.Z)) 40 | }; 41 | 42 | pitchYawRoll.X *= Rad2Deg; 43 | pitchYawRoll.Y *= Rad2Deg; 44 | pitchYawRoll.Z *= Rad2Deg; 45 | 46 | return pitchYawRoll; 47 | } 48 | 49 | public static float DistanceFromPlayer(HousingGameObject obj, Vector3 playerPos) 50 | => Vector3.Distance(playerPos, new(obj.X, obj.Y, obj.Z)); 51 | } 52 | 53 | public static class QuaternionExtensions 54 | { 55 | public static float ComputeXAngle(this Quaternion q) 56 | { 57 | float sinr_cosp = 2 * (q.W * q.X + q.Y * q.Z); 58 | float cosr_cosp = 1 - 2 * (q.X * q.X + q.Y * q.Y); 59 | return (float)Math.Atan2(sinr_cosp, cosr_cosp); 60 | } 61 | 62 | public static float ComputeYAngle(this Quaternion q) 63 | { 64 | float sinp = 2 * (q.W * q.Y - q.Z * q.X); 65 | if (Math.Abs(sinp) >= 1) 66 | return (float)Math.PI / 2 * Math.Sign(sinp); // use 90 degrees if out of range 67 | else 68 | return (float)Math.Asin(sinp); 69 | } 70 | 71 | public static float ComputeZAngle(this Quaternion q) 72 | { 73 | float siny_cosp = 2 * (q.W * q.Z + q.X * q.Y); 74 | float cosy_cosp = 1 - 2 * (q.Y * q.Y + q.Z * q.Z); 75 | return (float)Math.Atan2(siny_cosp, cosy_cosp); 76 | } 77 | 78 | public static Vector3 ComputeAngles(this Quaternion q) 79 | { 80 | return new Vector3(ComputeXAngle(q), ComputeYAngle(q), ComputeZAngle(q)); 81 | } 82 | 83 | public static Quaternion FromAngles(Vector3 angles) 84 | { 85 | 86 | float cy = (float)Math.Cos(angles.Z * 0.5f); 87 | float sy = (float)Math.Sin(angles.Z * 0.5f); 88 | float cp = (float)Math.Cos(angles.Y * 0.5f); 89 | float sp = (float)Math.Sin(angles.Y * 0.5f); 90 | float cr = (float)Math.Cos(angles.X * 0.5f); 91 | float sr = (float)Math.Sin(angles.X * 0.5f); 92 | 93 | Quaternion q = new Quaternion 94 | { 95 | W = cr * cp * cy + sr * sp * sy, 96 | X = sr * cp * cy - cr * sp * sy, 97 | Y = cr * sp * cy + sr * cp * sy, 98 | Z = cr * cp * sy - sr * sp * cy 99 | }; 100 | 101 | return q; 102 | 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /BDTHPlugin/Interface/Gizmo.cs: -------------------------------------------------------------------------------- 1 | using System.Net.WebSockets; 2 | using System.Numerics; 3 | using Dalamud.Interface.Utility; 4 | using Dalamud.Bindings.ImGui; 5 | using Dalamud.Bindings.ImGuizmo; 6 | 7 | namespace BDTHPlugin.Interface 8 | { 9 | public class Gizmo 10 | { 11 | private static PluginMemory Memory => Plugin.GetMemory(); 12 | private static Configuration Configuration => Plugin.GetConfiguration(); 13 | 14 | private static unsafe bool CanEdit => Configuration.UseGizmo && Memory.CanEditItem() && Memory.HousingStructure->ActiveItem != null; 15 | 16 | public ImGuizmoMode Mode = ImGuizmoMode.Local; 17 | 18 | private Vector3 translate; 19 | private Vector3 rotation; 20 | private Vector3 scale = Vector3.One; 21 | 22 | private Matrix4x4 matrix = Matrix4x4.Identity; 23 | 24 | private ImGuiIOPtr Io; 25 | private Vector2 Wp; 26 | 27 | public void Draw() 28 | { 29 | if (!CanEdit) 30 | return; 31 | 32 | ImGuiHelpers.ForceNextWindowMainViewport(); 33 | ImGuiHelpers.SetNextWindowPosRelativeMainViewport(new Vector2(0, 0)); 34 | 35 | ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0)); 36 | 37 | const ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoInputs; 38 | if (!ImGui.Begin("BDTHGizmo", windowFlags)) 39 | return; 40 | 41 | Io = ImGui.GetIO(); 42 | ImGui.SetWindowSize(Io.DisplaySize); 43 | 44 | Wp = ImGui.GetWindowPos(); 45 | 46 | try 47 | { 48 | DrawGizmo(Wp, new Vector2(Io.DisplaySize.X, Io.DisplaySize.Y)); 49 | } 50 | finally 51 | { 52 | ImGui.PopStyleVar(); 53 | ImGui.End(); 54 | } 55 | } 56 | 57 | private unsafe void DrawGizmo(Vector2 pos, Vector2 size) 58 | { 59 | ImGuizmo.BeginFrame(); 60 | 61 | var cam = Memory.Camera->RenderCamera; 62 | var view = Memory.Camera->ViewMatrix; 63 | var proj = cam->ProjectionMatrix; 64 | 65 | var far = cam->FarPlane; 66 | var near = cam->NearPlane; 67 | var clip = far / (far - near); 68 | 69 | proj.M43 = -(clip * near); 70 | proj.M33 = -((far + near) / (far - near)); 71 | view.M44 = 1.0f; 72 | 73 | ImGuizmo.SetDrawlist(); 74 | 75 | ImGuizmo.Enable(Memory.HousingStructure->Rotating); 76 | ImGuizmo.SetID((int)ImGui.GetID("BDTHPlugin")); 77 | ImGuizmo.SetOrthographic(false); 78 | 79 | ImGuizmo.SetRect(pos.X, pos.Y, size.X, size.Y); 80 | 81 | ComposeMatrix(); 82 | 83 | var snap = Configuration.DoSnap ? new(Configuration.Drag, Configuration.Drag, Configuration.Drag) : Vector3.Zero; 84 | 85 | if (Manipulate(ref view.M11, ref proj.M11, ImGuizmoOperation.Translate, Mode, ref matrix.M11, ref snap.X)) 86 | WriteMatrix(); 87 | 88 | ImGuizmo.SetID(-1); 89 | } 90 | 91 | private void ComposeMatrix() 92 | { 93 | try 94 | { 95 | translate = Memory.ReadPosition(); 96 | rotation = Memory.ReadRotation(); 97 | ImGuizmo.RecomposeMatrixFromComponents(ref translate.X, ref rotation.X, ref scale.X, ref matrix.M11); 98 | } 99 | catch 100 | { 101 | } 102 | } 103 | 104 | private void WriteMatrix() 105 | { 106 | ImGuizmo.DecomposeMatrixToComponents(ref matrix.M11, ref translate.X, ref rotation.X, ref scale.X); 107 | Memory.WritePosition(translate); 108 | } 109 | 110 | private unsafe bool Manipulate(ref float view, ref float proj, ImGuizmoOperation op, ImGuizmoMode mode, ref float matrix, ref float snap) 111 | { 112 | fixed (float* native_view = &view) 113 | { 114 | fixed (float* native_proj = &proj) 115 | { 116 | fixed (float* native_matrix = &matrix) 117 | { 118 | fixed (float* native_snap = &snap) 119 | { 120 | return ImGuizmo.Manipulate( 121 | native_view, 122 | native_proj, 123 | op, 124 | mode, 125 | native_matrix, 126 | null, 127 | native_snap, 128 | null, 129 | null 130 | ) != false; 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /BDTHPlugin/Interface/Windows/FurnitureList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | 4 | using Dalamud.Interface.Windowing; 5 | 6 | using Dalamud.Bindings.ImGui; 7 | using Dalamud.Game.ClientState; 8 | using Dalamud.Game.ClientState.Objects; 9 | 10 | namespace BDTHPlugin.Interface.Windows 11 | { 12 | public class FurnitureList : Window 13 | { 14 | private static PluginMemory Memory => Plugin.GetMemory(); 15 | private static Configuration Configuration => Plugin.GetConfiguration(); 16 | 17 | private ulong? lastActiveItem; 18 | private byte renderCount; 19 | 20 | public FurnitureList() : base("Furnishing List") 21 | { 22 | 23 | } 24 | 25 | public override void PreDraw() 26 | { 27 | // Only allows furnishing list when the housing window is open. 28 | // Disallows the ability to open furnishing list outdoors. 29 | IsOpen &= Memory.IsHousingOpen() && !Plugin.IsOutdoors(); 30 | } 31 | 32 | public unsafe override void Draw() 33 | { 34 | var fontScale = ImGui.GetIO().FontGlobalScale; 35 | var hasActiveItem = Memory.HousingStructure->ActiveItem != null; 36 | 37 | SizeConstraints = new WindowSizeConstraints 38 | { 39 | MinimumSize = new Vector2(120 * fontScale, 100 * fontScale), 40 | MaximumSize = new Vector2(400 * fontScale, 1000 * fontScale) 41 | }; 42 | 43 | var sortByDistance = Configuration.SortByDistance; 44 | if (ImGui.Checkbox("Sort by distance", ref sortByDistance)) 45 | { 46 | Configuration.SortByDistance = sortByDistance; 47 | Configuration.Save(); 48 | } 49 | 50 | ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 8)); 51 | ImGui.Separator(); 52 | ImGui.PopStyleVar(); 53 | 54 | ImGui.BeginChild("FurnishingList"); 55 | 56 | if (Plugin.ObjectTable.LocalPlayer == null) 57 | return; 58 | 59 | var playerPos = Plugin.ObjectTable.LocalPlayer.Position; 60 | // An active item is being selected. 61 | // var hasActiveItem = Memory.HousingStructure->ActiveItem != null; 62 | 63 | if (ImGui.BeginTable("FurnishingListItems", 3)) 64 | { 65 | ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthFixed, 0f); 66 | ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 0f); 67 | ImGui.TableSetupColumn("Distance", ImGuiTableColumnFlags.WidthFixed, 0f); 68 | 69 | try 70 | { 71 | if (Memory.GetFurnishings(out var items, playerPos, sortByDistance)) 72 | { 73 | for (var i = 0; i < items.Count; i++) 74 | { 75 | ImGui.TableNextRow(ImGuiTableRowFlags.None, 28 * fontScale); 76 | ImGui.TableNextColumn(); 77 | ImGui.AlignTextToFramePadding(); 78 | 79 | var name = ""; 80 | ushort icon = 0; 81 | 82 | if (Plugin.TryGetYardObject(items[i].HousingRowId, out var yardObject)) 83 | { 84 | name = yardObject.Item.Value.Name.ToString(); 85 | icon = yardObject.Item.Value.Icon; 86 | } 87 | 88 | if (Plugin.TryGetFurnishing(items[i].HousingRowId, out var furnitureObject)) 89 | { 90 | name = furnitureObject.Item.Value.Name.ToString(); 91 | icon = furnitureObject.Item.Value.Icon; 92 | } 93 | 94 | // Skip item if we can't find a name or item icon. 95 | if (name == string.Empty || icon == 0) 96 | continue; 97 | 98 | // The currently selected item. 99 | var thisActive = hasActiveItem && items[i].Item == Memory.HousingStructure->ActiveItem; 100 | 101 | ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0f, 4f)); 102 | if (ImGui.Selectable($"##Item{i}", thisActive, ImGuiSelectableFlags.SpanAllColumns, new(0, 20 * fontScale))) 103 | Memory.SelectItem((IntPtr)Memory.HousingStructure, (IntPtr)items[i].Item); 104 | ImGui.PopStyleVar(); 105 | 106 | if (thisActive) 107 | ImGui.SetItemDefaultFocus(); 108 | 109 | // Scroll if the active item has changed from last time. 110 | if (thisActive && lastActiveItem != (ulong)Memory.HousingStructure->ActiveItem) 111 | { 112 | ImGui.SetScrollHereY(); 113 | } 114 | 115 | ImGui.SameLine(); 116 | Plugin.DrawIcon(icon, new Vector2(24 * fontScale, 24 * fontScale)); 117 | var distance = Util.DistanceFromPlayer(items[i], playerPos); 118 | 119 | ImGui.TableNextColumn(); 120 | ImGui.SetNextItemWidth(-1); 121 | ImGui.Text(name); 122 | 123 | ImGui.TableNextColumn(); 124 | ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(.5f, .5f, .5f, 1)); 125 | ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetColumnWidth() - ImGui.CalcTextSize(distance.ToString("F2")).X - ImGui.GetScrollX() - 2 * ImGui.GetStyle().ItemSpacing.X); 126 | ImGui.Text($"{distance:F2}"); 127 | ImGui.PopStyleColor(); 128 | } 129 | 130 | if (renderCount >= 10) 131 | lastActiveItem = (ulong)Memory.HousingStructure->ActiveItem; 132 | if (renderCount != 10) 133 | renderCount++; 134 | } 135 | } 136 | catch (Exception ex) 137 | { 138 | Plugin.Log.Error(ex, ex.Source ?? "No source found"); 139 | } 140 | finally 141 | { 142 | ImGui.EndTable(); 143 | ImGui.EndChild(); 144 | } 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /BDTHPlugin/Interface/Components/ItemControls.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | using Dalamud.Interface; 4 | using Dalamud.Interface.Components; 5 | 6 | using Dalamud.Bindings.ImGui; 7 | 8 | namespace BDTHPlugin.Interface.Components 9 | { 10 | public class ItemControls 11 | { 12 | private static PluginMemory Memory => Plugin.GetMemory(); 13 | private static Configuration Configuration => Plugin.GetConfiguration(); 14 | 15 | private static float Drag => Configuration.Drag; 16 | 17 | private float? lockX; 18 | private float? lockY; 19 | private float? lockZ; 20 | 21 | private Vector3? copyPosition; 22 | private float? copyRotation; 23 | 24 | private Vector4 RED = new(1, .2f, .2f, 1); 25 | private Vector4 GREEN = new(.2f, 1, .2f, 1); 26 | private Vector4 BLUE = new(.2f, .2f, 1, 1); 27 | 28 | public unsafe void Draw() 29 | { 30 | if (Memory.HousingStructure->ActiveItem != null) 31 | { 32 | Memory.position = Memory.ReadPosition(); 33 | // Handle lock logic. 34 | if (lockX != null) 35 | Memory.position.X = (float)lockX; 36 | if (lockY != null) 37 | Memory.position.Y = (float)lockY; 38 | if (lockZ != null) 39 | Memory.position.Z = (float)lockZ; 40 | Memory.WritePosition(Memory.position); 41 | } 42 | 43 | ImGui.BeginGroup(); 44 | { 45 | ImGui.PushItemWidth(73f); 46 | { 47 | DrawDragCoord("##bdth-xdrag", ref Memory.position.X); 48 | DrawDragCoord("##bdth-ydrag", ref Memory.position.Y); 49 | DrawDragCoord("##bdth-zdrag", ref Memory.position.Z); 50 | ImGui.Text("position"); 51 | 52 | DrawDragRotate("##bdth-rydrag", ref Memory.rotation.Y); 53 | ImGui.Text("rotation"); 54 | } 55 | ImGui.PopItemWidth(); 56 | } 57 | ImGui.EndGroup(); 58 | 59 | if (ImGui.IsItemHovered()) 60 | { 61 | ImGui.BeginTooltip(); 62 | ImGui.Text("Click and drag each to move the selected item."); 63 | ImGui.Text("Change the drag option below to influence how much it moves as you drag."); 64 | ImGui.EndTooltip(); 65 | } 66 | 67 | ImGui.SameLine(); 68 | ImGui.BeginGroup(); 69 | { 70 | if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) 71 | { 72 | copyPosition = Memory.position; 73 | copyRotation = Memory.rotation.Y; 74 | } 75 | if (ImGui.IsItemHovered()) 76 | ImGui.SetTooltip("Copy Position & Rotation"); 77 | 78 | ImGui.BeginDisabled(copyPosition == null || copyRotation == null); 79 | { 80 | if (ImGuiComponents.IconButton(FontAwesomeIcon.Paste) && copyPosition != null && copyRotation != null) 81 | { 82 | Memory.WritePosition(copyPosition.Value); 83 | Memory.WriteRotation(Memory.rotation with { Y = copyRotation.Value }); 84 | } 85 | if (ImGui.IsItemHovered()) 86 | ImGui.SetTooltip("Paste Position & Rotation"); 87 | } 88 | ImGui.EndDisabled(); 89 | } 90 | 91 | ImGui.EndGroup(); 92 | 93 | DrawInputCoord("x coord##bdth-x", ref Memory.position.X, ref lockX, RED); 94 | DrawInputCoord("y coord##bdth-y", ref Memory.position.Y, ref lockY, GREEN); 95 | DrawInputCoord("z coord##bdth-z", ref Memory.position.Z, ref lockZ, BLUE); 96 | DrawInputRotate("ry degree##bdth-ry", ref Memory.rotation.Y); 97 | } 98 | 99 | private void HandleScrollInput(ref float f) 100 | { 101 | if (ImGui.IsMouseHoveringRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax())) 102 | { 103 | var delta = ImGui.GetIO().MouseWheel * Drag; 104 | if (delta != 0) 105 | { 106 | f += delta; 107 | Memory.WritePosition(Memory.position); 108 | } 109 | } 110 | } 111 | 112 | private bool DrawDrag(string name, ref float f) 113 | { 114 | var changed = ImGui.DragFloat(name, ref f, Drag); 115 | ImGui.SameLine(0, 4); 116 | HandleScrollInput(ref f); 117 | return changed; 118 | } 119 | 120 | private void DrawDragCoord(string name, ref float f) 121 | { 122 | if (DrawDrag(name, ref f)) 123 | Memory.WritePosition(Memory.position); 124 | } 125 | 126 | private void DrawDragRotate(string name, ref float f) 127 | { 128 | if (DrawDrag(name, ref f)) 129 | Memory.WriteRotation(Memory.rotation); 130 | } 131 | 132 | private bool DrawInput(string name, ref float f) 133 | { 134 | var changed = ImGui.InputFloat(name, ref f, Drag); 135 | HandleScrollInput(ref f); 136 | ImGui.SameLine(); 137 | return changed; 138 | } 139 | 140 | private void DrawInputCoord(string name, ref float f) 141 | { 142 | if (DrawInput(name, ref f)) 143 | Memory.WritePosition(Memory.position); 144 | } 145 | 146 | private void DrawInputRotate(string name, ref float f) 147 | { 148 | if (DrawInput(name, ref f)) 149 | Memory.WriteRotation(Memory.rotation); 150 | } 151 | 152 | static void DrawCircle(Vector4 color) 153 | { 154 | ImGui.SameLine(); 155 | var pos = ImGui.GetCursorScreenPos(); 156 | var center = pos.Y + ImGui.GetTextLineHeight() / 2f + 3f; 157 | ImGui.GetWindowDrawList().AddCircleFilled(new Vector2(pos.X + 12, center), 8, ImGui.GetColorU32(color)); 158 | ImGui.Dummy(new Vector2(24, 24)); 159 | } 160 | 161 | private void DrawInputCoord(string name, ref float f, ref float? locked, Vector4 color) 162 | { 163 | DrawInputCoord(name, ref f); 164 | if (ImGuiComponents.IconButton((int)ImGui.GetID(name), locked == null ? FontAwesomeIcon.Unlock : FontAwesomeIcon.Lock)) 165 | locked = locked == null ? f : null; 166 | DrawCircle(color); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /BDTHPlugin/Interface/Windows/MainWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | 4 | using Dalamud.Interface.Components; 5 | using Dalamud.Interface.Windowing; 6 | 7 | using Dalamud.Bindings.ImGui; 8 | using Dalamud.Bindings.ImGuizmo; 9 | 10 | using BDTHPlugin.Interface.Components; 11 | 12 | namespace BDTHPlugin.Interface.Windows 13 | { 14 | public class MainWindow : Window 15 | { 16 | private static PluginMemory Memory => Plugin.GetMemory(); 17 | private static Configuration Configuration => Plugin.GetConfiguration(); 18 | 19 | private static readonly Vector4 RED_COLOR = new(1, 0, 0, 1); 20 | 21 | private readonly Gizmo Gizmo; 22 | private readonly ItemControls ItemControls = new(); 23 | 24 | public bool Reset; 25 | 26 | public MainWindow(Gizmo gizmo) : base( 27 | "Burning Down the House##BDTH", 28 | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoResize | 29 | ImGuiWindowFlags.AlwaysAutoResize 30 | ) 31 | { 32 | Gizmo = gizmo; 33 | } 34 | 35 | public override void PreDraw() 36 | { 37 | if (Reset) 38 | { 39 | Reset = false; 40 | ImGui.SetNextWindowPos(new Vector2(69, 69), ImGuiCond.Always); 41 | } 42 | } 43 | 44 | public unsafe override void Draw() 45 | { 46 | ImGui.BeginGroup(); 47 | 48 | var placeAnywhere = Configuration.PlaceAnywhere; 49 | if (ImGui.Checkbox("Place Anywhere", ref placeAnywhere)) 50 | { 51 | // Set the place anywhere based on the checkbox state. 52 | Memory.SetPlaceAnywhere(placeAnywhere); 53 | Configuration.PlaceAnywhere = placeAnywhere; 54 | Configuration.Save(); 55 | } 56 | DrawTooltip("Allows the placement of objects without limitation from the game engine."); 57 | 58 | ImGui.SameLine(); 59 | 60 | // Checkbox is clicked, set the configuration and save. 61 | var useGizmo = Configuration.UseGizmo; 62 | if (ImGui.Checkbox("Gizmo", ref useGizmo)) 63 | { 64 | Configuration.UseGizmo = useGizmo; 65 | Configuration.Save(); 66 | } 67 | DrawTooltip("Displays a movement gizmo on the selected item to allow for in-game movement on all axis."); 68 | 69 | ImGui.SameLine(); 70 | 71 | // Checkbox is clicked, set the configuration and save. 72 | var doSnap = Configuration.DoSnap; 73 | if (ImGui.Checkbox("Snap", ref doSnap)) 74 | { 75 | Configuration.DoSnap = doSnap; 76 | Configuration.Save(); 77 | } 78 | DrawTooltip("Enables snapping of gizmo movement based on the drag value set below."); 79 | 80 | ImGui.SameLine(); 81 | if (ImGuiComponents.IconButton(1, Gizmo.Mode == ImGuizmoMode.Local ? Dalamud.Interface.FontAwesomeIcon.ArrowsAlt : Dalamud.Interface.FontAwesomeIcon.Globe)) 82 | Gizmo.Mode = Gizmo.Mode == ImGuizmoMode.Local ? ImGuizmoMode.World : ImGuizmoMode.Local; 83 | 84 | DrawTooltip( 85 | [ 86 | $"Mode: {(Gizmo.Mode == ImGuizmoMode.Local ? "Local" : "World")}", 87 | "Changes gizmo mode between local and world movement." 88 | ]); 89 | 90 | ImGui.Separator(); 91 | 92 | if (Memory.HousingStructure->Mode == HousingLayoutMode.None) 93 | DrawError("Enter housing mode to get started"); 94 | else if (PluginMemory.GamepadMode) 95 | DrawError("Does not support Gamepad"); 96 | else if (Memory.HousingStructure->ActiveItem == null || Memory.HousingStructure->Mode != HousingLayoutMode.Rotate) 97 | { 98 | DrawError("Select a housing item in Rotate mode"); 99 | ImGuiComponents.HelpMarker("Are you doing everything right? Try using the /bdth debug command and report this issue in Discord!"); 100 | } 101 | else 102 | ItemControls.Draw(); 103 | 104 | ImGui.Separator(); 105 | 106 | // Drag amount for the inputs. 107 | var drag = Configuration.Drag; 108 | if (ImGui.InputFloat("drag", ref drag, 0.05f)) 109 | { 110 | drag = Math.Min(Math.Max(0.001f, drag), 10f); 111 | Configuration.Drag = drag; 112 | Configuration.Save(); 113 | } 114 | DrawTooltip("Sets the amount to change when dragging the controls, also influences the gizmo snap feature."); 115 | 116 | var dummyHousingGoods = AtkManager.HousingGoods != null && AtkManager.HousingGoods.IsVisible; 117 | var dummyInventory = AtkManager.InventoryVisible; 118 | 119 | if (ImGui.Checkbox("Display in-game list", ref dummyHousingGoods)) 120 | { 121 | AtkManager.ShowFurnishingList(dummyHousingGoods); 122 | 123 | Configuration.DisplayFurnishingList = dummyHousingGoods; 124 | Configuration.Save(); 125 | } 126 | ImGui.SameLine(); 127 | 128 | if (ImGui.Checkbox("Display inventory", ref dummyInventory)) 129 | { 130 | AtkManager.ShowInventory(dummyInventory); 131 | 132 | Configuration.DisplayInventory = dummyInventory; 133 | Configuration.Save(); 134 | } 135 | 136 | if (ImGui.Button("Open Furnishing List")) 137 | Plugin.CommandManager.ProcessCommand("/bdth list"); 138 | DrawTooltip( 139 | [ 140 | "Opens a furnishing list that you can use to sort by distance and click to select objects.", 141 | "NOTE: Does not currently work outdoors!" 142 | ]); 143 | 144 | var autoVisible = Configuration.AutoVisible; 145 | if (ImGui.Checkbox("Auto Open", ref autoVisible)) 146 | { 147 | Configuration.AutoVisible = autoVisible; 148 | Configuration.Save(); 149 | } 150 | 151 | ImGui.EndGroup(); 152 | } 153 | 154 | private static void DrawTooltip(string[] text) 155 | { 156 | if (ImGui.IsItemHovered()) 157 | { 158 | ImGui.BeginTooltip(); 159 | foreach (var t in text) 160 | ImGui.Text(t); 161 | ImGui.EndTooltip(); 162 | } 163 | } 164 | 165 | private static void DrawTooltip(string text) 166 | { 167 | DrawTooltip([text]); 168 | } 169 | 170 | private static void DrawError(string text) 171 | { 172 | ImGui.PushStyleColor(ImGuiCol.Text, RED_COLOR); 173 | ImGui.Text(text); 174 | ImGui.PopStyleColor(); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /BDTHPlugin/Plugin.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game; 2 | using Dalamud.Game.ClientState.Conditions; 3 | using Dalamud.Game.ClientState; 4 | using Dalamud.Game.ClientState.Objects; 5 | using Dalamud.Game.Command; 6 | using Dalamud.IoC; 7 | using Dalamud.Plugin; 8 | using Dalamud.Plugin.Services; 9 | using Dalamud.Bindings.ImGui; 10 | using Dalamud.Bindings.ImGuizmo; 11 | using Lumina.Excel.Sheets; 12 | using System; 13 | using System.Collections.Generic; 14 | using System.Globalization; 15 | using System.Linq; 16 | using System.Numerics; 17 | 18 | using BDTHPlugin.Interface; 19 | using Dalamud.Interface.Textures; 20 | 21 | namespace BDTHPlugin 22 | { 23 | public class Plugin : IDalamudPlugin 24 | { 25 | private const string commandName = "/bdth"; 26 | 27 | [PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!; 28 | [PluginService] public static IDataManager Data { get; private set; } = null!; 29 | [PluginService] public static ICommandManager CommandManager { get; private set; } = null!; 30 | [PluginService] public static IClientState ClientState { get; private set; } = null!; 31 | [PluginService] public static IObjectTable ObjectTable { get; private set; } = null!; 32 | [PluginService] public static IPlayerState PlayerState { get; private set; } = null!; 33 | [PluginService] public static IFramework Framework { get; private set; } = null!; 34 | [PluginService] public static IChatGui Chat { get; private set; } = null!; 35 | [PluginService] public static IGameGui GameGui { get; private set; } = null!; 36 | [PluginService] public static ISigScanner TargetModuleScanner { get; private set; } = null!; 37 | [PluginService] public static ICondition Condition { get; private set; } = null!; 38 | [PluginService] public static ITextureProvider TextureProvider { get; private set; } = null!; 39 | [PluginService] public static IPluginLog Log { get; private set; } = null!; 40 | 41 | private static Configuration Configuration = null!; 42 | private static PluginUI Ui = null!; 43 | private static PluginMemory Memory = null!; 44 | 45 | // Sheets used to get housing item info. 46 | private static Dictionary FurnitureDict = []; 47 | private static Dictionary YardObjectDict = []; 48 | 49 | public Plugin() 50 | { 51 | Configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); 52 | Ui = new(); 53 | Memory = new(); 54 | 55 | FurnitureDict = Data.GetExcelSheet()!.ToDictionary(row => row.RowId, row => row); 56 | YardObjectDict = Data.GetExcelSheet()!.ToDictionary(row => row.RowId, row => row); 57 | 58 | CommandManager.AddHandler(commandName, new CommandInfo(OnCommand) 59 | { 60 | HelpMessage = "Opens the controls for Burning Down the House plugin." 61 | }); 62 | 63 | // Set the ImGui context 64 | ImGuizmo.SetImGuiContext(ImGui.GetCurrentContext()); 65 | 66 | PluginInterface.UiBuilder.Draw += Ui.Draw; 67 | PluginInterface.UiBuilder.OpenMainUi += OpenMainUI; 68 | Condition.ConditionChange += Condition_ConditionChange; 69 | Framework.Update += Framework_Update; 70 | } 71 | 72 | public static Configuration GetConfiguration() 73 | { 74 | return Configuration; 75 | } 76 | 77 | public static PluginMemory GetMemory() 78 | { 79 | return Memory; 80 | } 81 | 82 | public static PluginUI GetUi() 83 | { 84 | return Ui; 85 | } 86 | 87 | private void Framework_Update(IFramework framework) 88 | { 89 | Memory.Update(); 90 | } 91 | 92 | private void Condition_ConditionChange(ConditionFlag flag, bool value) 93 | { 94 | if (Configuration.AutoVisible && flag == ConditionFlag.UsingHousingFunctions) 95 | Ui.Main.IsOpen = value; 96 | } 97 | 98 | public void Dispose() 99 | { 100 | PluginInterface.UiBuilder.Draw -= Ui.Draw; 101 | 102 | PluginInterface.UiBuilder.OpenMainUi -= OpenMainUI; 103 | Condition.ConditionChange -= Condition_ConditionChange; 104 | Framework.Update -= Framework_Update; 105 | 106 | // Dispose for stuff in Plugin Memory class. 107 | Memory.Dispose(); 108 | 109 | CommandManager.RemoveHandler(commandName); 110 | 111 | GC.SuppressFinalize(this); 112 | } 113 | 114 | private void OpenMainUI() 115 | { 116 | Ui.Main.IsOpen = true; 117 | } 118 | 119 | /// 120 | /// Draws icon from game data. 121 | /// 122 | /// 123 | /// 124 | public static void DrawIcon(ushort icon, Vector2 size) 125 | { 126 | if (icon < 65000) 127 | { 128 | var iconTexture = TextureProvider.GetFromGameIcon(new GameIconLookup(icon)); 129 | ImGui.Image(iconTexture.GetWrapOrEmpty().Handle, size); 130 | } 131 | } 132 | 133 | public unsafe static bool IsOutdoors() => Memory.HousingModule->OutdoorTerritory != null; 134 | 135 | public static bool TryGetFurnishing(uint id, out HousingFurniture furniture) => FurnitureDict.TryGetValue(id, out furniture); 136 | public static bool TryGetYardObject(uint id, out HousingYardObject furniture) => YardObjectDict.TryGetValue(id, out furniture); 137 | 138 | private unsafe void OnCommand(string command, string args) 139 | { 140 | args = args.Trim().ToLower(); 141 | 142 | // Arguments are being passed in. 143 | if (!string.IsNullOrEmpty(args)) 144 | { 145 | // Split the arguments into an array. 146 | var argArray = args.Split(' '); 147 | 148 | // Check valid state for modifying memory. 149 | var disabled = !(Memory.CanEditItem() && Memory.HousingStructure->ActiveItem != null); 150 | 151 | // Show/Hide the furnishing list. 152 | if (argArray.Length == 1) 153 | { 154 | var opt = argArray[0].ToLower(); 155 | if (opt.Equals("list")) 156 | { 157 | // Only allow furnishing list when the housing window is open. 158 | if (!Memory.IsHousingOpen()) 159 | { 160 | Chat.PrintError("Cannot open furnishing list unless housing menu is open."); 161 | Ui.Furniture.IsOpen = false; 162 | return; 163 | } 164 | 165 | // Disallow the ability to open furnishing list outdoors. 166 | if (IsOutdoors()) 167 | { 168 | Chat.PrintError("Cannot open furnishing outdoors currently."); 169 | Ui.Furniture.IsOpen = false; 170 | return; 171 | } 172 | 173 | Ui.Furniture.Toggle(); 174 | } 175 | 176 | if (opt.Equals("debug")) 177 | Ui.Debug.Toggle(); 178 | 179 | if (opt.Equals("reset")) 180 | Ui.Main.Reset = true; 181 | } 182 | 183 | // Position or rotation values are being passed in, and we're not disabled. 184 | if (argArray.Length >= 3 && !disabled) 185 | { 186 | try 187 | { 188 | // Parse the coordinates into floats. 189 | var x = float.Parse(argArray[0], NumberStyles.Any, CultureInfo.InvariantCulture); 190 | var y = float.Parse(argArray[1], NumberStyles.Any, CultureInfo.InvariantCulture); 191 | var z = float.Parse(argArray[2], NumberStyles.Any, CultureInfo.InvariantCulture); 192 | 193 | // Set the position in the memory object. 194 | Memory.position.X = x; 195 | Memory.position.Y = y; 196 | Memory.position.Z = z; 197 | 198 | // Write the position. 199 | Memory.WritePosition(Memory.position); 200 | 201 | // Specifying the rotation as well. 202 | if (argArray.Length == 4) 203 | { 204 | // Parse and write the rotation. 205 | Memory.rotation.Y = (float)(float.Parse(argArray[3], NumberStyles.Any, CultureInfo.InvariantCulture) * 180 / Math.PI); 206 | Memory.WriteRotation(Memory.rotation); 207 | } 208 | } 209 | catch (Exception ex) 210 | { 211 | Log.Error(ex, "Error when positioning with command"); 212 | } 213 | } 214 | } 215 | else 216 | { 217 | // Hide or show the UI. 218 | Ui.Main.Toggle(); 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /BDTHPlugin/PluginMemory.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.NativeWrapper; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Numerics; 6 | using System.Runtime.InteropServices; 7 | 8 | using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; 9 | using CameraManager = FFXIVClientStructs.FFXIV.Client.Game.Control.CameraManager; 10 | 11 | namespace BDTHPlugin 12 | { 13 | public class PluginMemory 14 | { 15 | private bool isHousingOpen = false; 16 | 17 | private enum InventoryType 18 | { 19 | Normal, 20 | Large, 21 | Expanded 22 | } 23 | 24 | // Pointers to modify assembly to enable place anywhere. 25 | public IntPtr placeAnywhere; 26 | public IntPtr wallAnywhere; 27 | public IntPtr wallmountAnywhere; 28 | // public IntPtr showcaseAnywhereRotate; 29 | // public IntPtr showcaseAnywherePlace; 30 | 31 | // Layout and housing module pointers. 32 | private readonly IntPtr layoutWorldPtr; 33 | private readonly IntPtr housingModulePtr; 34 | 35 | public unsafe LayoutWorld* Layout => (LayoutWorld*)layoutWorldPtr; 36 | public unsafe HousingStructure* HousingStructure => Layout->HousingStruct; 37 | public unsafe HousingModule* HousingModule => housingModulePtr != IntPtr.Zero ? (HousingModule*)Marshal.ReadIntPtr(housingModulePtr) : null; 38 | public unsafe HousingObjectManager* CurrentManager => HousingModule->GetCurrentManager(); 39 | public unsafe Camera* Camera => &CameraManager.Instance()->GetActiveCamera()->CameraBase.SceneCamera; 40 | 41 | public static unsafe AtkUnitBasePtr HousingLayout => Plugin.GameGui.GetAddonByName("HousingLayout", 1); 42 | public static unsafe bool GamepadMode => !(HousingLayout != null && HousingLayout.IsVisible); 43 | 44 | 45 | // Local references to position and rotation to use to free them when an item isn't selected but to keep the UI bound to a reference. 46 | public Vector3 position; 47 | public Vector3 rotation; 48 | 49 | // Function for selecting an item, usually used when clicking on one in game. 50 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 51 | public delegate void SelectItemDelegate(IntPtr housingStruct, IntPtr item); 52 | private readonly IntPtr selectItemAddress; 53 | public SelectItemDelegate SelectItem = null!; 54 | 55 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 56 | public delegate void PlaceHousingItemDelegate(IntPtr item, Vector3 position); 57 | private readonly IntPtr placeHousingItemAddress; 58 | public PlaceHousingItemDelegate PlaceHousingItem = null!; 59 | 60 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 61 | public delegate void HousingLayoutModelUpdateDelegate(IntPtr item); 62 | private readonly IntPtr housingLayoutModelUpdateAddress; 63 | public HousingLayoutModelUpdateDelegate HousingLayoutModelUpdate = null!; 64 | 65 | public PluginMemory() 66 | { 67 | try 68 | { 69 | // Assembly address for asm rewrites. 70 | placeAnywhere = Plugin.TargetModuleScanner.ScanText("C6 ?? ?? ?? 00 00 00 8B FE 48 89") + 6; 71 | wallAnywhere = Plugin.TargetModuleScanner.ScanText("48 85 C0 74 ?? C6 87 ?? ?? 00 00 00") + 11; 72 | wallmountAnywhere = Plugin.TargetModuleScanner.ScanText("c6 87 83 01 00 00 00 48 83 c4 ??") + 6; 73 | // showcaseAnywhereRotate = Plugin.TargetModuleScanner.ScanText("88 87 98 02 00 00 48 8b 9c ?? ?? 00 00 00 4C 8B"); 74 | // showcaseAnywherePlace = Plugin.TargetModuleScanner.ScanText("88 87 98 02 00 00 48 8B"); 75 | 76 | // Pointers for housing structures. 77 | layoutWorldPtr = Plugin.TargetModuleScanner.GetStaticAddressFromSig("48 8B D1 48 8B 0D ?? ?? ?? ?? 48 85 C9 74 0A", 3); 78 | housingModulePtr = Plugin.TargetModuleScanner.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 8B 52"); 79 | 80 | // Read the pointers. 81 | layoutWorldPtr = Marshal.ReadIntPtr(layoutWorldPtr); 82 | 83 | // Select housing item. 84 | selectItemAddress = Plugin.TargetModuleScanner.ScanText("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"); 85 | SelectItem = Marshal.GetDelegateForFunctionPointer(selectItemAddress); 86 | 87 | // Address for the place item function. 88 | placeHousingItemAddress = Plugin.TargetModuleScanner.ScanText("40 53 48 83 EC 20 8B 02 48 8B D9 89 41 50 8B 42 04 89 41 54 8B 42 08 89 41 58 48 83 E9 80"); 89 | PlaceHousingItem = Marshal.GetDelegateForFunctionPointer(placeHousingItemAddress); 90 | 91 | // Housing item model update. 92 | housingLayoutModelUpdateAddress = Plugin.TargetModuleScanner.ScanText("48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 50 48 8B E9 48 8B 49"); 93 | HousingLayoutModelUpdate = Marshal.GetDelegateForFunctionPointer(housingLayoutModelUpdateAddress); 94 | 95 | var config = Plugin.GetConfiguration(); 96 | 97 | if (config.PlaceAnywhere) 98 | SetPlaceAnywhere(Plugin.GetConfiguration().PlaceAnywhere); 99 | } 100 | catch (Exception ex) 101 | { 102 | Plugin.Log.Error(ex, "Error while calling PluginMemory.ctor()"); 103 | } 104 | } 105 | 106 | /// 107 | /// Dispose for the memory functions. 108 | /// 109 | public unsafe void Dispose() 110 | { 111 | try 112 | { 113 | // Disable the place anywhere in case it's on. 114 | SetPlaceAnywhere(false); 115 | AtkManager.ShowFurnishingList(true); 116 | } 117 | catch (Exception ex) 118 | { 119 | Plugin.Log.Error(ex, "Error while calling PluginMemory.Dispose()"); 120 | } 121 | } 122 | 123 | public unsafe int GetHousingObjectSelectedIndex() 124 | { 125 | for (var i = 0; i < 400; i++) 126 | { 127 | if (HousingModule->GetCurrentManager()->Objects[i] == 0) 128 | continue; 129 | if ((ulong)HousingModule->GetCurrentManager()->IndoorActiveObject == HousingModule->GetCurrentManager()->Objects[i]) 130 | return i; 131 | } 132 | return -1; 133 | } 134 | 135 | /// 136 | /// Is the housing menu open. 137 | /// 138 | /// Boolean state. 139 | public unsafe bool IsHousingOpen() 140 | { 141 | if (HousingStructure == null) 142 | return false; 143 | 144 | // Anything other than none means the housing menu is open. 145 | return HousingStructure->Mode != HousingLayoutMode.None; 146 | } 147 | 148 | /// 149 | /// Checks if you can edit a housing item, specifically checks that rotate mode is active. 150 | /// 151 | /// Boolean state if housing menu is on or off. 152 | public unsafe bool CanEditItem() 153 | { 154 | try 155 | { 156 | if (HousingStructure == null) 157 | return false; 158 | 159 | // Rotate mode only. 160 | return HousingStructure->Mode == HousingLayoutMode.Rotate; 161 | } 162 | catch 163 | { 164 | return false; 165 | } 166 | } 167 | 168 | /// 169 | /// Read the position of the active item. 170 | /// 171 | /// Vector3 of the position. 172 | public unsafe Vector3 ReadPosition() 173 | { 174 | // Ensure that we're hooked and have the housing structure address. 175 | if (HousingStructure == null) 176 | throw new PluginException("Housing structure is invalid!"); 177 | 178 | // Ensure active item pointer isn't null. 179 | var item = HousingStructure->ActiveItem; 180 | if (item == null) 181 | throw new PluginException("No valid item selected!"); 182 | 183 | // Return the position vector. 184 | return item->Position; 185 | } 186 | 187 | /// 188 | /// Reads the rotation of the item. 189 | /// 190 | /// 191 | public unsafe Vector3 ReadRotation() 192 | { 193 | // Ensure that we're hooked and have the housing structure address. 194 | if (HousingStructure == null) 195 | throw new PluginException("Housing structure is invalid!"); 196 | 197 | // Ensure active item pointer isn't null. 198 | var item = HousingStructure->ActiveItem; 199 | if (item == null) 200 | throw new PluginException("No valid item selected!"); 201 | 202 | // Return the rotation radian. 203 | return Util.FromQ(item->Rotation); 204 | } 205 | 206 | /// 207 | /// Writes the position vector to memory. 208 | /// 209 | /// Position vector to write. 210 | public unsafe void WritePosition(Vector3 newPosition) 211 | { 212 | // Don't write if housing mode isn't on. 213 | if (!CanEditItem()) 214 | return; 215 | 216 | try 217 | { 218 | var item = HousingStructure->ActiveItem; 219 | if (item == null) 220 | return; 221 | 222 | // Set the position. 223 | item->Position = newPosition; 224 | } 225 | catch (Exception ex) 226 | { 227 | Plugin.Log.Error(ex, "Error occured while writing position!"); 228 | } 229 | } 230 | 231 | public unsafe void WriteRotation(Vector3 newRotation) 232 | { 233 | // Don't write if housing mode isn't on. 234 | if (!CanEditItem()) 235 | return; 236 | 237 | try 238 | { 239 | var item = HousingStructure->ActiveItem; 240 | if (item == null) 241 | return; 242 | 243 | // Convert into a quaternion. 244 | item->Rotation = Util.ToQ(newRotation); 245 | } 246 | catch (Exception ex) 247 | { 248 | Plugin.Log.Error(ex, "Error occured while writing rotation!"); 249 | } 250 | } 251 | 252 | /// 253 | /// Thread loop for reading memory. 254 | /// 255 | public unsafe void Update() 256 | { 257 | try 258 | { 259 | var lastIsHousingOpen = isHousingOpen; 260 | isHousingOpen = IsHousingOpen(); 261 | 262 | // Just perform once when housing is opened 263 | if (lastIsHousingOpen != isHousingOpen && isHousingOpen) 264 | { 265 | var config = Plugin.GetConfiguration(); 266 | if (!config.DisplayFurnishingList) 267 | AtkManager.ShowFurnishingList(false); 268 | if (!config.DisplayInventory) 269 | AtkManager.ShowInventory(false); 270 | } 271 | 272 | if (CanEditItem()) 273 | { 274 | // Don't really need to load position if we're reading it in the UI thread anyway, but leaving it for now for redudency... 275 | position = ReadPosition(); 276 | rotation = ReadRotation(); 277 | 278 | // Update the model of active item, the game doesn't do this for wall mounted and outside in rotate mode 279 | var item = HousingStructure->ActiveItem; 280 | if (item != null) 281 | HousingLayoutModelUpdate((IntPtr)item + 0x80); 282 | } 283 | } 284 | catch (PluginException) 285 | { 286 | position = Vector3.Zero; 287 | rotation = Vector3.Zero; 288 | } 289 | catch (Exception ex) 290 | { 291 | Plugin.Log.Error(ex, "Unknown exception"); 292 | position = Vector3.Zero; 293 | rotation = Vector3.Zero; 294 | } 295 | } 296 | 297 | /// 298 | /// Get furnishings as they appear in the array in memory. 299 | /// 300 | /// 301 | /// 302 | public unsafe bool GetFurnishings(out List objects, Vector3 point, bool sortByDistance = false) 303 | { 304 | if (sortByDistance == true) 305 | return GetFurnishingByDistance(out objects, point); 306 | 307 | objects = []; 308 | 309 | if (HousingModule == null || HousingModule->GetCurrentManager() == null || HousingModule->GetCurrentManager()->Objects == null) 310 | return false; 311 | 312 | for (var i = 0; i < 400; i++) 313 | { 314 | var oPtr = HousingModule->GetCurrentManager()->Objects[i]; 315 | if (oPtr == 0) 316 | continue; 317 | 318 | objects.Add(*(HousingGameObject*)oPtr); 319 | } 320 | return true; 321 | } 322 | 323 | /// 324 | /// Get furnishings and sort by distance to a given point. 325 | /// 326 | /// 327 | /// 328 | /// 329 | public unsafe bool GetFurnishingByDistance(out List objects, Vector3 point) 330 | { 331 | objects = []; 332 | 333 | if (HousingModule == null || HousingModule->GetCurrentManager() == null || HousingModule->GetCurrentManager()->Objects == null) 334 | return false; 335 | 336 | var tmpObjects = new List<(HousingGameObject gObj, float distance)>(); 337 | objects = new List(); 338 | for (var i = 0; i < 400; i++) 339 | { 340 | var oPtr = HousingModule->GetCurrentManager()->Objects[i]; 341 | if (oPtr == 0) 342 | continue; 343 | var o = *(HousingGameObject*)oPtr; 344 | tmpObjects.Add((o, Util.DistanceFromPlayer(o, point))); 345 | } 346 | 347 | tmpObjects.Sort((obj1, obj2) => obj1.distance.CompareTo(obj2.distance)); 348 | objects = tmpObjects.Select(obj => obj.gObj).ToList(); 349 | 350 | return true; 351 | } 352 | 353 | private static void WriteProtectedBytes(IntPtr addr, byte[] b) 354 | { 355 | if (addr == IntPtr.Zero) return; 356 | VirtualProtect(addr, 1, Protection.PAGE_EXECUTE_READWRITE, out var oldProtection); 357 | Marshal.Copy(b, 0, addr, b.Length); 358 | VirtualProtect(addr, 1, oldProtection, out _); 359 | } 360 | 361 | private static void WriteProtectedBytes(IntPtr addr, byte b) 362 | { 363 | if (addr == IntPtr.Zero) return; 364 | WriteProtectedBytes(addr, [b]); 365 | } 366 | 367 | /// 368 | /// Sets the flag for place anywhere in memory. 369 | /// 370 | /// Boolean state for if you can place anywhere. 371 | public void SetPlaceAnywhere(bool state) 372 | { 373 | if (placeAnywhere == IntPtr.Zero || wallAnywhere == IntPtr.Zero || wallmountAnywhere == IntPtr.Zero) 374 | return; 375 | 376 | // The byte state from boolean. 377 | var bstate = (byte)(state ? 1 : 0); 378 | 379 | // Write the bytes for place anywhere. 380 | WriteProtectedBytes(placeAnywhere, bstate); 381 | WriteProtectedBytes(wallAnywhere, bstate); 382 | WriteProtectedBytes(wallmountAnywhere, bstate); 383 | 384 | // Which bytes to write. 385 | // byte[] showcaseBytes = state ? [0x90, 0x90, 0x90, 0x90, 0x90, 0x90] : [0x88, 0x87, 0x98, 0x02, 0x00, 0x00]; 386 | 387 | // // Write bytes for showcase anywhere (nop or original bytes). 388 | // WriteProtectedBytes(showcaseAnywhereRotate, showcaseBytes); 389 | // WriteProtectedBytes(showcaseAnywherePlace, showcaseBytes); 390 | } 391 | 392 | #region Kernel32 393 | 394 | [DllImport("kernel32.dll", SetLastError = true)] 395 | private static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, Protection flNewProtect, out Protection lpflOldProtect); 396 | 397 | public enum Protection 398 | { 399 | PAGE_NOACCESS = 0x01, 400 | PAGE_READONLY = 0x02, 401 | PAGE_READWRITE = 0x04, 402 | PAGE_WRITECOPY = 0x08, 403 | PAGE_EXECUTE = 0x10, 404 | PAGE_EXECUTE_READ = 0x20, 405 | PAGE_EXECUTE_READWRITE = 0x40, 406 | PAGE_EXECUTE_WRITECOPY = 0x80, 407 | PAGE_GUARD = 0x100, 408 | PAGE_NOCACHE = 0x200, 409 | PAGE_WRITECOMBINE = 0x400 410 | } 411 | 412 | #endregion 413 | } 414 | } 415 | --------------------------------------------------------------------------------