├── 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 |
--------------------------------------------------------------------------------