first commit
Some checks failed
Build and Release / Build (push) Failing after 46s

This commit is contained in:
2025-08-16 20:46:04 +03:00
commit dfb8cd7215
22 changed files with 2897 additions and 0 deletions

52
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: Build and Release
on:
push:
tags:
- "*.*.*.*"
jobs:
Build:
runs-on: ubuntu-latest
env:
DALAMUD_HOME: /tmp/dalamud
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 9.0.x
- name: Download Dalamud Latest
run: |
wget https://goatcorp.github.io/dalamud-distrib/latest.zip -O ${{ env.DALAMUD_HOME }}.zip
unzip ${{ env.DALAMUD_HOME }}.zip -d ${{ env.DALAMUD_HOME }}
- name: Restore Project
run: dotnet restore
- name: Build Project
run: dotnet build --configuration Release HouseStealerPlugin/HouseStealerPlugin.csproj -p:AssemblyVersion=${{ github.ref_name }}
- name: Create Release
uses: actions/create-release@v1
id: create_release
with:
draft: false
prerelease: false
release_name: ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Upload Release Asset with curl
run: |
curl \
-X POST \
-H "Authorization: token ${{ gitea.token }}" \
-H "Content-Type: application/zip" \
--data-binary @bin/Release/HouseStealerPlugin/latest.zip \
"${{ steps.create_release.outputs.upload_url }}?name=latest.zip"

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.vs/
obj/
bin/
*.user
.idea/

31
HouseStealerPlugin.sln Normal file
View File

@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35507.96
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HouseStealerPlugin", "HouseStealerPlugin\HouseStealerPlugin.csproj", "{479ADEA9-780A-4A8E-98A1-82C0399176E4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{479ADEA9-780A-4A8E-98A1-82C0399176E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{479ADEA9-780A-4A8E-98A1-82C0399176E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{479ADEA9-780A-4A8E-98A1-82C0399176E4}.Debug|x64.ActiveCfg = Release|x64
{479ADEA9-780A-4A8E-98A1-82C0399176E4}.Debug|x64.Build.0 = Release|x64
{479ADEA9-780A-4A8E-98A1-82C0399176E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{479ADEA9-780A-4A8E-98A1-82C0399176E4}.Release|Any CPU.Build.0 = Release|Any CPU
{479ADEA9-780A-4A8E-98A1-82C0399176E4}.Release|x64.ActiveCfg = Release|x64
{479ADEA9-780A-4A8E-98A1-82C0399176E4}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0D7BC4A9-5CCC-47C8-9525-9597FCB81275}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using Dalamud.Configuration;
namespace HouseStealerPlugin
{
[Serializable]
public class Configuration : IPluginConfiguration
{
public int Version { get; set; } = 0;
public bool DrawScreen = false;
public float DrawDistance = 0;
public List<int> HiddenScreenItemHistory = new List<int>();
public List<int> GroupingList = new List<int>();
public bool Basement = true;
public bool GroundFloor = true;
public bool UpperFloor = true;
public int LoadInterval = 400;
public bool ApplyLayout = true;
public string SaveLocation = null;
public void Save()
{
DalamudApi.PluginInterface.SavePluginConfig(this);
}
public void ResetRecord()
{
HiddenScreenItemHistory.Clear();
GroupingList.Clear();
Save();
}
}
}

View File

@@ -0,0 +1,40 @@
using Dalamud.Game;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
namespace HouseStealerPlugin;
public class DalamudApi
{
public static void Initialize(IDalamudPluginInterface pluginInterface) => pluginInterface.Create<DalamudApi>();
// [PluginService] public static IAetheryteList AetheryteList { get; private set; } = null;
// [PluginService] public static IBuddyList BuddyList { get; private set; } = null;
[PluginService] public static IChatGui ChatGui { get; private set; } = null;
[PluginService] public static IClientState ClientState { get; private set; } = null;
[PluginService] public static ICommandManager CommandManager { get; private set; } = null;
// [PluginService] public static ICondition Condition { get; private set; } = null;
[PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null;
[PluginService] public static IDataManager DataManager { get; private set; } = null;
[PluginService] public static ITextureProvider TextureProvider { get; private set; }
// [PluginService] public static IDtrBar DtrBar { get; private set; } = null;
// [PluginService] public static IFateTable FateTable { get; private set; } = null;
// [PluginService] public static IFlyTextGui FlyTextGui { get; private set; } = null;
[PluginService] public static IFramework Framework { get; private set; } = null;
[PluginService] public static IGameGui GameGui { get; private set; } = null;
// [PluginService] public static IGameNetwork GameNetwork { get; private set; } = null;
// [PluginService] public static IGamepadState GamePadState { get; private set; } = null;
// [PluginService] public static IJobGauges JobGauges { get; private set; } = null;
// [PluginService] public static IKeyState KeyState { get; private set; } = null;
// [PluginService] public static ILibcFunction LibcFunction { get; private set; } = null;
//[PluginService] public static IObjectTable ObjectTable { get; private set; } = null;
// [PluginService] public static IPartyFinderGui PartyFinderGui { get; private set; } = null;
// [PluginService] public static IPartyList PartyList { get; private set; } = null;
[PluginService] public static ISigScanner SigScanner { get; private set; } = null;
// [PluginService] public static ITargetManager TargetManager { get; private set; } = null;
// [PluginService] public static IToastGui ToastGui { get; private set; } = null;
[PluginService] public static IGameInteropProvider Hooks { get; private set; } = null;
[PluginService] public static IPluginLog PluginLog { get; private set; } = null;
// [PluginService] public static ITitleScreenMenu TitleScreenMenu { get; private set; } = null;
}

View File

@@ -0,0 +1,440 @@
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Textures;
using Dalamud.Utility;
using Dalamud.Bindings.ImGui;
using Lumina.Excel.Sheets;
using HouseStealerPlugin.Objects;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using static HouseStealerPlugin.HouseStealerPlugin;
namespace HouseStealerPlugin.Gui
{
public class ConfigurationWindow : Window<HouseStealerPlugin>
{
public Configuration Config => Plugin.Config;
private string CustomTag = string.Empty;
private readonly Dictionary<uint, uint> iconToFurniture = new() { };
private readonly Vector4 PURPLE = new(0.26275f, 0.21569f, 0.56863f, 1f);
private readonly Vector4 PURPLE_ALPHA = new(0.26275f, 0.21569f, 0.56863f, 0.5f);
private FileDialogManager FileDialogManager { get; }
public ConfigurationWindow(HouseStealerPlugin plugin) : base(plugin)
{
this.FileDialogManager = new FileDialogManager
{
AddedWindowFlags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking,
};
}
protected void DrawAllUi()
{
if (!ImGui.Begin(Plugin.Name, ref WindowVisible, ImGuiWindowFlags.NoScrollWithMouse))
{
return;
}
if (ImGui.BeginChild("##SettingsRegion"))
{
DrawGeneralSettings();
if (ImGui.BeginChild("##ItemListRegion"))
{
ImGui.PushStyleColor(ImGuiCol.Header, PURPLE_ALPHA);
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, PURPLE);
ImGui.PushStyleColor(ImGuiCol.HeaderActive, PURPLE);
if (ImGui.CollapsingHeader("Interior Furniture", ImGuiTreeNodeFlags.DefaultOpen))
{
ImGui.PushID("interior");
DrawItemList(Plugin.InteriorItemList);
ImGui.PopID();
}
if (ImGui.CollapsingHeader("Interior Fixtures", ImGuiTreeNodeFlags.DefaultOpen))
{
ImGui.PushID("interiorFixture");
DrawFixtureList(Plugin.Layout.interiorFixture);
ImGui.PopID();
}
ImGui.PopStyleColor(3);
ImGui.EndChild();
}
ImGui.EndChild();
}
this.FileDialogManager.Draw();
}
protected override void DrawUi()
{
ImGui.PushStyleColor(ImGuiCol.TitleBgActive, PURPLE);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, PURPLE_ALPHA);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, PURPLE_ALPHA);
ImGui.SetNextWindowSize(new Vector2(530, 450), ImGuiCond.FirstUseEver);
DrawAllUi();
ImGui.PopStyleColor(3);
ImGui.End();
}
#region Helper Functions
public void DrawIcon(ushort icon, Vector2 size)
{
if (icon < 65000)
{
var iconTexture = DalamudApi.TextureProvider.GetFromGameIcon(new GameIconLookup(icon));
ImGui.Image(iconTexture.GetWrapOrEmpty().Handle, size);
}
}
#endregion
#region Basic UI
private void SaveLayoutToFile()
{
try
{
Plugin.GetGameLayout();
HouseStealerPlugin.LayoutManager.ExportLayout();
}
catch (Exception e)
{
LogError($"Save Error: {e.Message}", e.StackTrace);
}
}
unsafe private void DrawGeneralSettings()
{
ImGui.Text("Layout");
if (!Config.SaveLocation.IsNullOrEmpty())
{
ImGui.Text($"Current file location: {Config.SaveLocation}");
if (ImGui.Button("Save"))
{
SaveLayoutToFile();
}
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Save layout to current file location");
ImGui.SameLine();
}
if (ImGui.Button("Save As"))
{
string saveName = "save";
if (!Config.SaveLocation.IsNullOrEmpty()) saveName = Path.GetFileNameWithoutExtension(Config.SaveLocation);
FileDialogManager.SaveFileDialog("Select a Save Location", ".json", saveName, "json", (bool ok, string res) =>
{
if (!ok)
{
return;
}
Config.SaveLocation = res;
Config.Save();
SaveLayoutToFile();
}, Path.GetDirectoryName(Config.SaveLocation));
}
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Save layout to file");
ImGui.Dummy(new Vector2(0, 15));
bool hasFloors = false;
try {
hasFloors = Memory.Instance.GetCurrentTerritory() == Memory.HousingArea.Indoors && !Memory.Instance.GetIndoorHouseSize().Equals("Apartment");
} catch (NullReferenceException){
// Thanks zbee
}
if (hasFloors)
{
ImGui.Text("Selected Floors");
if (ImGui.Checkbox("Basement", ref Config.Basement))
{
Config.Save();
}
ImGui.SameLine(); ImGui.Dummy(new Vector2(10, 0)); ImGui.SameLine();
if (ImGui.Checkbox("Ground Floor", ref Config.GroundFloor))
{
Config.Save();
}
ImGui.SameLine(); ImGui.Dummy(new Vector2(10, 0)); ImGui.SameLine();
if (Memory.Instance.HasUpperFloor() && ImGui.Checkbox("Upper Floor", ref Config.UpperFloor))
{
Config.Save();
}
ImGui.Dummy(new Vector2(0, 15));
}
ImGui.Dummy(new Vector2(0, 15));
}
private void DrawRow(int i, HousingItem housingItem, bool showSetPosition = true, int childIndex = -1)
{
if (!housingItem.CorrectLocation) ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.5f, 0.5f, 0.5f, 1));
ImGui.Text($"{housingItem.X:N4}, {housingItem.Y:N4}, {housingItem.Z:N4}");
if (!housingItem.CorrectLocation) ImGui.PopStyleColor();
ImGui.NextColumn();
if (!housingItem.CorrectRotation) ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.5f, 0.5f, 0.5f, 1));
ImGui.Text($"{Utils.radToDeg(housingItem.Rotate):N3}"); ImGui.NextColumn();
if (!housingItem.CorrectRotation) ImGui.PopStyleColor();
var stain = DalamudApi.DataManager.GetExcelSheet<Stain>().GetRowOrDefault(housingItem.Stain);
var colorName = stain?.Name;
if (housingItem.Stain != 0)
{
Utils.StainButton("dye_" + i, stain.Value, new Vector2(20));
ImGui.SameLine();
if (!housingItem.DyeMatch) ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.5f, 0.5f, 0.5f, 1));
ImGui.Text($"{colorName}");
if (!housingItem.DyeMatch) ImGui.PopStyleColor();
}
else if (housingItem.MaterialItemKey != 0)
{
if (DalamudApi.DataManager.GetExcelSheet<Item>().TryGetRow(housingItem.MaterialItemKey, out var item))
{
if (!housingItem.DyeMatch) ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.5f, 0.5f, 0.5f, 1));
DrawIcon(item.Icon, new Vector2(20, 20));
ImGui.SameLine();
ImGui.Text(item.Name.ToString());
if (!housingItem.DyeMatch) ImGui.PopStyleColor();
}
}
ImGui.NextColumn();
}
private void DrawFixtureList(List<Fixture> fixtureList)
{
try
{
if (ImGui.Button("Clear"))
{
fixtureList.Clear();
Config.Save();
}
ImGui.Columns(3, "FixtureList", true);
ImGui.Separator();
ImGui.Text("Level"); ImGui.NextColumn();
ImGui.Text("Fixture"); ImGui.NextColumn();
ImGui.Text("Item"); ImGui.NextColumn();
ImGui.Separator();
foreach (var fixture in fixtureList)
{
ImGui.Text(fixture.level); ImGui.NextColumn();
ImGui.Text(fixture.type); ImGui.NextColumn();
if (DalamudApi.DataManager.GetExcelSheet<Item>().TryGetRow(fixture.itemId, out var item))
{
DrawIcon(item.Icon, new Vector2(20, 20));
ImGui.SameLine();
}
ImGui.Text(fixture.name); ImGui.NextColumn();
ImGui.Separator();
}
ImGui.Columns(1);
}
catch (Exception e)
{
LogError(e.Message, e.StackTrace);
}
}
private void DrawItemList(List<HousingItem> itemList, bool isUnused = false)
{
if (ImGui.Button("Sort"))
{
itemList.Sort((x, y) =>
{
if (x.Name.CompareTo(y.Name) != 0)
return x.Name.CompareTo(y.Name);
if (x.X.CompareTo(y.X) != 0)
return x.X.CompareTo(y.X);
if (x.Y.CompareTo(y.Y) != 0)
return x.Y.CompareTo(y.Y);
if (x.Z.CompareTo(y.Z) != 0)
return x.Z.CompareTo(y.Z);
if (x.Rotate.CompareTo(y.Rotate) != 0)
return x.Rotate.CompareTo(y.Rotate);
return 0;
});
Config.Save();
}
ImGui.SameLine();
if (ImGui.Button("Clear"))
{
itemList.Clear();
Config.Save();
}
if (!isUnused)
{
ImGui.SameLine();
ImGui.Text("Note: Missing items, incorrect dyes, and items on unselected floors are grayed out");
}
// name, position, r, color, set
int columns = 4;
ImGui.Columns(columns, "ItemList", true);
ImGui.Separator();
ImGui.Text("Item"); ImGui.NextColumn();
ImGui.Text("Position (X,Y,Z)"); ImGui.NextColumn();
ImGui.Text("Rotation"); ImGui.NextColumn();
ImGui.Text("Dye/Material"); ImGui.NextColumn();
ImGui.Separator();
for (int i = 0; i < itemList.Count(); i++)
{
var housingItem = itemList[i];
var displayName = housingItem.Name;
if (DalamudApi.DataManager.GetExcelSheet<Item>().TryGetRow(housingItem.ItemKey, out var item))
{
DrawIcon(item.Icon, new Vector2(20, 20));
ImGui.SameLine();
}
if (housingItem.ItemStruct == IntPtr.Zero)
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.5f, 0.5f, 0.5f, 1));
}
ImGui.Text(displayName);
ImGui.NextColumn();
// Make sure we don't try and draw a row for a housing item that doesn't exist. If it does get passed it corrupts all ImGui interfaces managed by dalamud.
DrawRow(i, housingItem, !isUnused);
if (housingItem.ItemStruct == IntPtr.Zero)
{
ImGui.PopStyleColor();
}
ImGui.Separator();
}
ImGui.Columns(1);
}
#endregion
#region Draw Screen
protected override void DrawScreen()
{
if (Config.DrawScreen)
{
DrawItemOnScreen();
}
}
private unsafe void DrawItemOnScreen()
{
if (Memory.Instance == null) return;
var itemList = Plugin.InteriorItemList;
for (int i = 0; i < itemList.Count(); i++)
{
var playerPos = DalamudApi.ClientState.LocalPlayer.Position;
var housingItem = itemList[i];
if (housingItem.ItemStruct == IntPtr.Zero) continue;
var itemStruct = (HousingItemStruct*)housingItem.ItemStruct;
var itemPos = new Vector3(itemStruct->Position.X, itemStruct->Position.Y, itemStruct->Position.Z);
if (Config.HiddenScreenItemHistory.IndexOf(i) >= 0) continue;
if (Config.DrawDistance > 0 && (playerPos - itemPos).Length() > Config.DrawDistance)
continue;
var displayName = housingItem.Name;
if (DalamudApi.GameGui.WorldToScreen(itemPos, out var screenCoords))
{
ImGui.PushID("HousingItemWindow" + i);
ImGui.SetNextWindowPos(new Vector2(screenCoords.X, screenCoords.Y));
ImGui.SetNextWindowBgAlpha(0.8f);
if (ImGui.Begin("HousingItem" + i,
ImGuiWindowFlags.NoDecoration |
ImGuiWindowFlags.AlwaysAutoResize |
ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoMove |
ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNav))
{
ImGui.Text(displayName);
ImGui.SameLine();
ImGui.End();
}
ImGui.PopID();
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,44 @@
using Dalamud.Plugin;
namespace HouseStealerPlugin.Gui
{
public abstract class Window<T> where T : IDalamudPlugin
{
protected bool WindowVisible;
protected bool WindowCanUpload;
protected bool WindowCanImport;
public virtual bool Visible
{
get => WindowVisible;
set => WindowVisible = value;
}
public virtual bool CanUpload
{
get => WindowCanUpload;
set => WindowCanUpload = value;
}
public virtual bool CanImport
{
get => WindowCanImport;
set => WindowCanImport = value;
}
protected T Plugin { get; }
protected Window(T plugin)
{
Plugin = plugin;
}
public void Draw()
{
if (Visible)
{
DrawUi();
}
DrawScreen();
}
protected abstract void DrawUi();
protected abstract void DrawScreen();
}
}

View File

@@ -0,0 +1,327 @@
using Dalamud.Game.Command;
using Dalamud.Plugin;
using Lumina.Excel.Sheets;
using HouseStealerPlugin.Objects;
using HouseStealerPlugin.Util;
using System;
using System.Collections.Generic;
using static HouseStealerPlugin.Memory;
using HousingFurniture = Lumina.Excel.Sheets.HousingFurniture;
namespace HouseStealerPlugin
{
public class HouseStealerPlugin : IDalamudPlugin
{
public string Name => $"House Stealer";
private string[] commandNames = ["housesteal"];
public PluginUi Gui { get; private set; }
public Configuration Config { get; private set; }
public static List<HousingItem> ItemsToPlace = new List<HousingItem>();
private delegate bool UpdateLayoutDelegate(IntPtr a1);
private HookWrapper<UpdateLayoutDelegate> IsSaveLayoutHook;
// Function for selecting an item, usually used when clicking on one in game.
public delegate void SelectItemDelegate(IntPtr housingStruct, IntPtr item);
private static HookWrapper<SelectItemDelegate> SelectItemHook;
public static SaveLayoutManager LayoutManager;
public static bool logHousingDetour = false;
internal static Location PlotLocation = new Location();
public Layout Layout = new Layout();
public List<HousingItem> InteriorItemList = new List<HousingItem>();
public void Dispose()
{
HookManager.Dispose();
DalamudApi.ClientState.TerritoryChanged -= TerritoryChanged;
foreach (string commandName in commandNames){
DalamudApi.CommandManager.RemoveHandler($"/{commandName}");
}
Gui?.Dispose();
}
public HouseStealerPlugin(IDalamudPluginInterface pi)
{
DalamudApi.Initialize(pi);
Config = DalamudApi.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
Config.Save();
Initialize();
foreach (string commandName in commandNames){
DalamudApi.CommandManager.AddHandler($"/{commandName}", new CommandInfo(CommandHandler)
{
HelpMessage = "load config window."
});
}
Gui = new PluginUi(this);
DalamudApi.ClientState.TerritoryChanged += TerritoryChanged;
HousingData.Init(this);
Memory.Init();
LayoutManager = new SaveLayoutManager(this, Config);
DalamudApi.PluginLog.Info($"{Name} initialized");
}
public void Initialize()
{
IsSaveLayoutHook = HookManager.Hook<UpdateLayoutDelegate>("40 53 48 83 ec 20 48 8b d9 48 8b 0d ?? ?? ?? ?? e8 ?? ?? ?? ?? 33 d2 48 8b c8 e8 ?? ?? ?? ?? 84 c0 75 ?? 38 83 ?? 01 00 00", IsSaveLayoutDetour);
SelectItemHook = HookManager.Hook<SelectItemDelegate>("48 85 D2 0F 84 49 09 00 00 53 41 56 48 83 EC 48 48 89 6C 24 60 48 8B DA 48 89 74 24 70 4C 8B F1", SelectItemDetour);
UpdateYardObjHook = HookManager.Hook<UpdateYardDelegate>("48 89 74 24 18 57 48 83 ec 20 b8 dc 02 00 00 0f b7 f2 ??", UpdateYardObj);
GetGameObjectHook = HookManager.Hook<GetObjectDelegate>("48 89 5c 24 08 48 89 74 24 10 57 48 83 ec 20 0f b7 f2 33 db 0f 1f 40 00 0f 1f 84 00 00 00 00 00", GetGameObject);
GetObjectFromIndexHook = HookManager.Hook<GetActiveObjectDelegate>("81 fa 90 01 00 00 75 08 48 8b 81 88 0c 00 00 c3 0f b7 81 90 0c 00 00 3b d0 72 03 33 c0 c3", GetObjectFromIndex);
GetYardIndexHook = HookManager.Hook<GetIndexDelegate>("48 89 5c 24 10 57 48 83 ec 20 0f b6 d9", GetYardIndex);
}
internal delegate ushort GetIndexDelegate(byte type, byte objStruct);
internal static HookWrapper<GetIndexDelegate> GetYardIndexHook;
internal static ushort GetYardIndex(byte plotNumber, byte inventoryIndex)
{
var result = GetYardIndexHook.Original(plotNumber, inventoryIndex);
return result;
}
internal delegate IntPtr GetActiveObjectDelegate(IntPtr ObjList, uint index);
internal static IntPtr GetObjectFromIndex(IntPtr ObjList, uint index)
{
var result = GetObjectFromIndexHook.Original(ObjList, index);
return result;
}
internal delegate IntPtr GetObjectDelegate(IntPtr ObjList, ushort index);
internal static HookWrapper<GetObjectDelegate> GetGameObjectHook;
internal static HookWrapper<GetActiveObjectDelegate> GetObjectFromIndexHook;
internal static IntPtr GetGameObject(IntPtr ObjList, ushort index)
{
return GetGameObjectHook.Original(ObjList, index);
}
public delegate void UpdateYardDelegate(IntPtr housingStruct, ushort index);
private static HookWrapper<UpdateYardDelegate> UpdateYardObjHook;
private void UpdateYardObj(IntPtr objectList, ushort index)
{
UpdateYardObjHook.Original(objectList, index);
}
unsafe static public void SelectItemDetour(IntPtr housing, IntPtr item)
{
SelectItemHook.Original(housing, item);
}
unsafe static public void SelectItem(IntPtr item)
{
SelectItemDetour((IntPtr)Memory.Instance.HousingStructure, item);
}
public bool MatchItem(HousingItem item, uint itemKey)
{
if (item.ItemStruct != IntPtr.Zero) return false; // this item is already matched. We can skip
return item.ItemKey == itemKey && IsSelectedFloor(item.Y);
}
public unsafe bool MatchExactItem(HousingItem item, uint itemKey, HousingGameObject obj)
{
if (!MatchItem(item, itemKey)) return false;
if (item.Stain != obj.color) return false;
var matNumber = obj.Item->MaterialManager->MaterialSlot1;
if (item.MaterialItemKey == 0 && matNumber == 0) return true;
else if (item.MaterialItemKey != 0 && matNumber == 0) return false;
var matItemKey = HousingData.Instance.GetMaterialItemKey(item.ItemKey, matNumber);
if (matItemKey == 0) return true;
return matItemKey == item.MaterialItemKey;
}
public unsafe void GetPlotLocation()
{
var mgr = Memory.Instance.HousingModule->outdoorTerritory;
var territoryId = Memory.Instance.GetTerritoryTypeId();
if (!DalamudApi.DataManager.GetExcelSheet<TerritoryType>().TryGetRow(territoryId, out var row))
{
LogError($"Cannot identify territory: {territoryId}");
return;
}
var placeName = row.Name.ToString();
PlotLocation = Plots.Map[placeName][mgr->Plot + 1];
}
public bool IsSelectedFloor(float y)
{
if (Memory.Instance.GetCurrentTerritory() != Memory.HousingArea.Indoors || Memory.Instance.GetIndoorHouseSize().Equals("Apartment")) return true;
if (y < -0.001) return Config.Basement;
if (y >= -0.001 && y < 6.999) return Config.GroundFloor;
if (y >= 6.999)
{
if (Memory.Instance.HasUpperFloor()) return Config.UpperFloor;
else return Config.GroundFloor;
}
return false;
}
public unsafe void LoadInterior()
{
SaveLayoutManager.LoadInteriorFixtures();
List<HousingGameObject> dObjects;
Memory.Instance.TryGetNameSortedHousingGameObjectList(out dObjects);
InteriorItemList.Clear();
foreach (var gameObject in dObjects)
{
uint furnitureKey = gameObject.housingRowId;
if (!DalamudApi.DataManager.GetExcelSheet<HousingFurniture>().TryGetRow(furnitureKey, out var furniture)) continue;
if (!furniture.Item.IsValid) continue;
Item item = furniture.Item.Value;
if (item.RowId == 0) continue;
if (!IsSelectedFloor(gameObject.Y)) continue;
var housingItem = new HousingItem(item, gameObject);
housingItem.ItemStruct = (IntPtr)gameObject.Item;
if (gameObject.Item != null && gameObject.Item->MaterialManager != null)
{
ushort material = gameObject.Item->MaterialManager->MaterialSlot1;
housingItem.MaterialItemKey = HousingData.Instance.GetMaterialItemKey(item.RowId, material);
}
InteriorItemList.Add(housingItem);
}
Config.Save();
}
public void GetGameLayout()
{
Memory Mem = Memory.Instance;
var currentTerritory = Mem.GetCurrentTerritory();
if(currentTerritory != HousingArea.Indoors)
{
throw new Exception("Can only load interior layout");
}
if(Mem.GetIndoorHouseSize() == null)
{
LogError("Cannot load layout in this territory");
throw new Exception("Cannot load layout in this territory");
}
var itemList = InteriorItemList;
itemList.Clear();
LoadInterior();
DalamudApi.PluginLog.Debug(String.Format("Loaded {0} furniture items", itemList.Count));
Config.HiddenScreenItemHistory = new List<int>();
Config.Save();
}
public bool IsSaveLayoutDetour(IntPtr housingStruct)
{
var result = IsSaveLayoutHook.Original(housingStruct);
return result;
}
private void TerritoryChanged(ushort e)
{
Config.DrawScreen = false;
Config.Save();
}
public unsafe void CommandHandler(string command, string arguments)
{
var args = arguments.Trim().Replace("\"", string.Empty);
try
{
if (string.IsNullOrEmpty(args) || args.Equals("config", StringComparison.OrdinalIgnoreCase))
{
Gui.ConfigWindow.Visible = !Gui.ConfigWindow.Visible;
}
}
catch (Exception e)
{
LogError(e.Message, e.StackTrace);
}
}
public static void Log(string message, string detail_message = "")
{
var msg = $"{message}";
DalamudApi.PluginLog.Info(detail_message == "" ? msg : detail_message);
DalamudApi.ChatGui.Print(msg);
}
public static void LogError(string message, string detail_message = "")
{
var msg = $"{message}";
DalamudApi.PluginLog.Error(msg);
if (detail_message.Length > 0) DalamudApi.PluginLog.Error(detail_message);
DalamudApi.ChatGui.PrintError(msg);
}
}
}

View File

@@ -0,0 +1,66 @@
<Project Sdk="Dalamud.NET.Sdk/13.0.0">
<PropertyGroup>
<PluginVersion>1.0.0</PluginVersion>
<TargetFramework>net9.0-windows</TargetFramework>
<LangVersion>latest</LangVersion>
<AssemblyTitle>HouseStealerPlugin</AssemblyTitle>
<Product>HouseStealerPlugin</Product>
<Version>$(PluginVersion)</Version>
<FileVersion>$(PluginVersion)</FileVersion>
<AssemblyVersion>$(PluginVersion)</AssemblyVersion>
<OutputPath>..\bin\</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="13.0.0" />
<PackageReference Include="Lumina.Excel" Version="7.3.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
{
"Author": "Lozy",
"Name": "HouseStealer Plugin",
"InternalName": "HouseStealerPlugin",
"Description": "Pirating houses was never this easy before.",
"Tags": [
"housing",
"furniture"
],
"Punchline": "irating houses was never this easy before"
}

View File

@@ -0,0 +1,133 @@
using Lumina.Excel.Sheets;
using System.Collections.Generic;
using System.Linq;
namespace HouseStealerPlugin
{
public class HousingData
{
private readonly Dictionary<uint, HousingFurniture> _furnitureDict;
private readonly Dictionary<uint, Item> _itemDict;
private readonly Dictionary<uint, Stain> _stainDict;
private readonly Dictionary<uint, uint> _unitedDict;
private readonly Dictionary<ushort, uint> _wallpaper;
private readonly Dictionary<ushort, uint> _smallFishprint;
private readonly Dictionary<ushort, uint> _mediumFishprint;
private readonly Dictionary<ushort, uint> _largeFishprint;
private readonly Dictionary<ushort, uint> _extraLargeFishprint;
private readonly Dictionary<ushort, uint> _painting;
private static HouseStealerPlugin Plugin;
private HousingData()
{
var sheet = DalamudApi.DataManager.GetExcelSheet<HousingLandSet>();
uint[] terriKeys = { 339, 340, 341, 641 };
var unitedExteriorSheet = DalamudApi.DataManager.GetExcelSheet<HousingUnitedExterior>();
_unitedDict = new Dictionary<uint, uint>();
foreach (var row in unitedExteriorSheet)
foreach (var type in unitedExteriorSheet.Columns)
_unitedDict[type.Offset] = row.RowId;
_itemDict = DalamudApi.DataManager.GetExcelSheet<Item>()
.Where(item => item.AdditionalData.RowId != 0 && (item.ItemSearchCategory.RowId == 65 || item.ItemSearchCategory.RowId == 66))
.ToDictionary(row => row.AdditionalData.RowId, row => row);
_stainDict = DalamudApi.DataManager.GetExcelSheet<Stain>().ToDictionary(row => row.RowId, row => row);
_furnitureDict = DalamudApi.DataManager.GetExcelSheet<HousingFurniture>().ToDictionary(row => row.RowId, row => row);
DalamudApi.PluginLog.Info($"Loaded {_furnitureDict.Keys.Count} furniture");
DalamudApi.PluginLog.Info($"Loaded {_unitedDict.Keys.Count} united parts");
DalamudApi.PluginLog.Info($"Loaded {_stainDict.Keys.Count} dyes");
DalamudApi.PluginLog.Info($"Loaded {_itemDict.Keys.Count} items with AdditionalData");
_wallpaper = new Dictionary<ushort, uint>();
_smallFishprint = new Dictionary<ushort, uint>();
_mediumFishprint = new Dictionary<ushort, uint>();
_largeFishprint = new Dictionary<ushort, uint>();
_extraLargeFishprint = new Dictionary<ushort, uint>();
_painting = new Dictionary<ushort, uint>();
var materialSheet = DalamudApi.DataManager.GetExcelSheet<VaseFlower>();
foreach (var row in materialSheet)
{
var id = row.RowId;
if (id < 1000) continue;
else if (id > 1000 && id < 2000) _painting.TryAdd(row.Unknown0, row.Item.RowId);
else if (id > 2000 && id < 3000) _wallpaper.TryAdd(row.Unknown0, row.Item.RowId);
else if (id > 3000 && id < 4000) _smallFishprint.TryAdd(row.Unknown0, row.Item.RowId);
else if (id > 4000 && id < 5000) _mediumFishprint.TryAdd(row.Unknown0, row.Item.RowId);
else if (id > 5000 && id < 6000) _largeFishprint.TryAdd(row.Unknown0, row.Item.RowId);
else if (id > 6000 && id < 7000) _extraLargeFishprint.TryAdd(row.Unknown0, row.Item.RowId);
}
}
public static HousingData Instance { get; private set; }
public static void Init(HouseStealerPlugin plugin)
{
Plugin = plugin;
Instance = new HousingData();
}
public bool TryGetFurniture(uint id, out HousingFurniture furniture)
{
return _furnitureDict.TryGetValue(id, out furniture);
}
public bool IsUnitedExteriorPart(uint id, out Item item)
{
item = new Item();
if (!_unitedDict.TryGetValue(id, out var unitedId))
return false;
if (!_itemDict.TryGetValue(unitedId, out item))
return false;
return true;
}
public bool TryGetItem(uint id, out Item item)
{
return _itemDict.TryGetValue(id, out item);
}
public bool TryGetStain(uint id, out Stain stain)
{
return _stainDict.TryGetValue(id, out stain);
}
public uint GetMaterialItemKey(uint itemId, ushort material)
{
if (material == 0) return 0;
// Angler Canvas
if (itemId == 28931) return _smallFishprint.GetValueOrDefault(material);
else if (itemId == 28932) return _mediumFishprint.GetValueOrDefault(material);
else if (itemId == 28933) return _largeFishprint.GetValueOrDefault(material);
else if (itemId == 28934) return _extraLargeFishprint.GetValueOrDefault(material);
if (itemId >= 16935 && itemId <= 16937)
{
// Picture Frame
return _painting.GetValueOrDefault(material);
}
return _wallpaper.GetValueOrDefault(material);
}
}
}

View File

@@ -0,0 +1,230 @@
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.MJI;
using Lumina.Excel.Sheets;
using Microsoft.VisualBasic;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
using static HouseStealerPlugin.HouseStealerPlugin;
namespace HouseStealerPlugin
{
public unsafe class Memory
{
// Pointers to modify assembly to enable place anywhere.
// public IntPtr placeAnywhere;
// public IntPtr wallAnywhere;
// public IntPtr wallmountAnywhere;
public delegate InventoryContainer* GetInventoryContainerDelegate(IntPtr inventoryManager, InventoryType inventoryType);
private Memory()
{
try
{
// placeAnywhere = DalamudApi.SigScanner.ScanText("C6 ?? ?? ?? 00 00 00 8B FE 48 89") + 6;
// wallAnywhere = DalamudApi.SigScanner.ScanText("48 85 C0 74 ?? C6 87 ?? ?? 00 00 00") + 11;
// wallmountAnywhere = DalamudApi.SigScanner.ScanText("c6 87 83 01 00 00 00 48 83 c4 ??") + 6;
HousingModulePtr = DalamudApi.SigScanner.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 8B 52");
LayoutWorldPtr = DalamudApi.SigScanner.GetStaticAddressFromSig("48 8B D1 48 8B 0D ?? ?? ?? ?? 48 85 C9 74 0A", 3);
}
catch (Exception e)
{
DalamudApi.PluginLog.Error(e, "Could not load housing memory!!");
}
}
public static Memory Instance { get; private set; }
private IntPtr HousingModulePtr { get; }
private IntPtr LayoutWorldPtr { get; }
public unsafe HousingModule* HousingModule => HousingModulePtr != IntPtr.Zero ? (HousingModule*)Marshal.ReadIntPtr(HousingModulePtr) : null;
public unsafe LayoutWorld* LayoutWorld => LayoutWorldPtr != IntPtr.Zero ? (LayoutWorld*)Marshal.ReadIntPtr(LayoutWorldPtr) : null;
public unsafe HousingObjectManager* CurrentManager => HousingModule->currentTerritory;
public unsafe HousingStructure* HousingStructure => LayoutWorld->HousingStruct;
public static void Init()
{
Instance = new Memory();
}
public static InventoryContainer* GetContainer(InventoryType inventoryType)
{
return InventoryManager.Instance()->GetInventoryContainer(inventoryType);
}
public uint GetTerritoryTypeId()
{
if (!GetActiveLayout(out var manager)) return 0;
return manager.TerritoryTypeId;
}
public bool HasUpperFloor()
{
var houseSize = GetIndoorHouseSize();
return houseSize.Equals("Medium") || houseSize.Equals("Large");
}
public string GetIndoorHouseSize()
{
var territoryId = Memory.Instance.GetTerritoryTypeId();
if (!DalamudApi.DataManager.GetExcelSheet<TerritoryType>().TryGetRow(territoryId, out var row)) return null;
var placeName = row.Name.ToString();
var sizeName = placeName.Substring(1, 3);
switch (sizeName)
{
case "1i1":
return "Small";
case "1i2":
return "Medium";
case "1i3":
return "Large";
case "1i4":
return "Apartment";
default:
return null;
}
}
public float GetInteriorLightLevel()
{
if (GetCurrentTerritory() != HousingArea.Indoors) return 0f;
if (!GetActiveLayout(out var manager)) return 0f;
if (!manager.IndoorAreaData.HasValue) return 0f;
return manager.IndoorAreaData.Value.LightLevel;
}
public CommonFixture[] GetInteriorCommonFixtures(int floorId)
{
if (GetCurrentTerritory() != HousingArea.Indoors) return [];
if (!GetActiveLayout(out var manager)) return [];
if (!manager.IndoorAreaData.HasValue) return [];
var floor = manager.IndoorAreaData.Value.GetFloor(floorId);
var ret = new CommonFixture[IndoorFloorData.PartsMax];
for (var i = 0; i < IndoorFloorData.PartsMax; i++)
{
var key = floor.GetPart(i);
if (!HousingData.Instance.TryGetItem(unchecked((uint)key), out var item))
HousingData.Instance.IsUnitedExteriorPart(unchecked((uint)key), out item);
ret[i] = new CommonFixture(
false,
i,
key,
null,
item);
}
return ret;
}
public unsafe bool TryGetNameSortedHousingGameObjectList(out List<HousingGameObject> objects)
{
objects = null;
if (HousingModule == null ||
HousingModule->GetCurrentManager() == null ||
HousingModule->GetCurrentManager()->Objects == null)
return false;
objects = new List<HousingGameObject>();
for (var i = 0; i < 400; i++)
{
var oPtr = HousingModule->GetCurrentManager()->Objects[i];
if (oPtr == 0)
continue;
var o = *(HousingGameObject*)oPtr;
objects.Add(o);
}
objects.Sort(
(obj1, obj2) =>
{
string name1 = "", name2 = "";
if (HousingData.Instance.TryGetFurniture(obj1.housingRowId, out var furniture1))
name1 = furniture1.Item.Value.Name.ToString();
if (HousingData.Instance.TryGetFurniture(obj2.housingRowId, out var furniture2))
name2 = furniture2.Item.Value.Name.ToString();
return string.Compare(name1, name2, StringComparison.Ordinal);
});
return true;
}
public unsafe bool GetActiveLayout(out LayoutManager manager)
{
manager = new LayoutManager();
if (LayoutWorld == null ||
LayoutWorld->ActiveLayout == null)
return false;
manager = *LayoutWorld->ActiveLayout;
return true;
}
public bool GetHousingController(out HousingController controller)
{
controller = new HousingController();
if (!GetActiveLayout(out var manager) ||
!manager.HousingController.HasValue)
return false;
controller = manager.HousingController.Value;
return true;
}
public enum HousingArea
{
Indoors,
Outdoors,
Island,
None
}
public unsafe HousingArea GetCurrentTerritory()
{
if (!DalamudApi.DataManager.GetExcelSheet<TerritoryType>().TryGetRow(GetTerritoryTypeId(), out var territoryRow))
{
var territoryRowId = GetTerritoryTypeId();
if (territoryRowId != 0) { DalamudApi.PluginLog.Debug($"Invalid territory row: {territoryRowId}"); }
return HousingArea.None;
}
if (territoryRow.Equals(null) || territoryRow.Name.ToString().Equals("r1i5")) // blacklist company workshop from editing since it's not actually a housing area
{
return HousingArea.None;
}
if (territoryRow.Name.ToString().Equals("h1m2"))
{
return HousingArea.Island;
}
if (HousingModule == null) return HousingArea.None;
if (HousingModule->IsOutdoors()) return HousingArea.Outdoors;
else return HousingArea.Indoors;
}
}
}

View File

@@ -0,0 +1,47 @@
using Lumina.Excel.Sheets;
using System;
using System.Numerics;
namespace HouseStealerPlugin.Objects
{
public class HousingItem
{
public uint ItemKey;
public byte Stain;
public uint MaterialItemKey = 0;
public float X;
public float Y;
public float Z;
public float Rotate;
public string Name = "";
public IntPtr ItemStruct = IntPtr.Zero;
public bool DyeMatch = true;
public bool CorrectLocation = true;
public bool CorrectRotation = true;
public bool IsTableOrWallMounted = false;
public HousingItem(Item item, byte stain, float x, float y, float z, float rotate)
{
ItemKey = item.RowId;
Name = item.Name.ExtractText();
IsTableOrWallMounted = item.ItemUICategory.Value.RowId == 78 || item.ItemUICategory.Value.RowId == 79;
Stain = stain;
X = x;
Y = y;
Z = z;
Rotate = rotate;
}
public HousingItem(Item item, HousingGameObject gameObject)
: this(item, gameObject.color, gameObject.X, gameObject.Y, gameObject.Z, gameObject.rotation)
{
}
public Vector3 GetLocation()
{
return new Vector3(X, Y, Z);
}
}
}

View File

@@ -0,0 +1,358 @@
using Lumina.Excel.Sheets;
using System;
using System.Numerics;
using System.Runtime.InteropServices;
namespace HouseStealerPlugin
{
public enum SortType
{
Distance,
Name
}
public enum ExteriorPartsType
{
None = -1,
Roof = 0,
Walls,
Windows,
Door,
RoofOpt,
WallOpt,
SignOpt,
Fence
}
public enum InteriorFloor
{
None = -1,
Ground = 0,
Upstairs,
Basement,
External
}
public enum InteriorPartsType
{
None = -1,
Walls,
Windows,
Door,
Floor,
Light
}
public struct CommonFixture
{
public bool IsExterior;
public int FixtureType;
public int FixtureKey;
public Stain? Stain;
public Item? Item;
public CommonFixture(bool isExterior, int fixtureType, int fixtureKey, Stain? stain, Item item)
{
IsExterior = isExterior;
FixtureType = fixtureType;
FixtureKey = fixtureKey;
Stain = stain;
Item = item;
}
}
public enum HousingLayoutMode
{
None,
Move,
Rotate,
Store,
Place,
Remove = 6
}
public enum ItemState
{
None = 0,
Hover,
SoftSelect,
Active
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct HousingItemStruct
{
[FieldOffset(0x50)] public Vector3 Position;
[FieldOffset(0x60)] public Quaternion Rotation;
[FieldOffset(0xF8)] public ItemMaterialManager* MaterialManager;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct ItemMaterialManager
{
[FieldOffset(0xcc)] public ushort MaterialSlot1;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct HousingStructure
{
[FieldOffset(0x0)] public HousingLayoutMode Mode;
[FieldOffset(0x4)] public HousingLayoutMode LastMode;
[FieldOffset(0x8)] public ItemState State;
[FieldOffset(0x10)] public HousingItemStruct* HoverItem;
[FieldOffset(0x18)] public HousingItemStruct* ActiveItem;
[FieldOffset(0xB8)] public bool Rotating;
}
// This is just a GameObject
[StructLayout(LayoutKind.Explicit, Size = 0x1D0)]
public unsafe struct HousingGameObject
{
[FieldOffset(0x030)] public fixed byte name[64];
[FieldOffset(0x084)] public uint housingRowId;
[FieldOffset(0x088)] public uint OwnerID;
[FieldOffset(0x0B0)] public float X;
[FieldOffset(0x0B4)] public float Y;
[FieldOffset(0x0B8)] public float Z;
[FieldOffset(0x108)] public HousingItemStruct* Item;
[FieldOffset(0x0C0)] public float rotation;
[FieldOffset(0x1A8)] public uint housingRowId2;
[FieldOffset(0x1B0)] public byte color;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct HousingObjectManager
{
[FieldOffset(0x0010)] public IntPtr ObjectList;
[FieldOffset(0x8980)] public fixed ulong Objects[400];
[FieldOffset(0x96A2)] public byte Ward;
[FieldOffset(0x96A8)] public byte Plot;
[FieldOffset(0x96E8)] public HousingGameObject* IndoorActiveObject2;
[FieldOffset(0x96F0)] public HousingGameObject* IndoorHoverObject;
[FieldOffset(0x96F8)] public HousingGameObject* IndoorActiveObject;
[FieldOffset(0x9AB8)] public HousingGameObject* OutdoorActiveObject2;
[FieldOffset(0x9AC0)] public HousingGameObject* OutdoorHoverObject;
[FieldOffset(0x9AC8)] public HousingGameObject* OutdoorActiveObject;
public static FFXIVClientStructs.FFXIV.Client.Game.HousingFurniture* GetItemInfo(HousingObjectManager* mgr, int index)
{
var objectListAddr = (IntPtr)mgr + 0x10;
return (FFXIVClientStructs.FFXIV.Client.Game.HousingFurniture*)(objectListAddr + (0x30 * index));
}
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct HousingModule
{
[FieldOffset(0x0)] public HousingObjectManager* currentTerritory;
[FieldOffset(0x8)] public HousingObjectManager* outdoorTerritory;
[FieldOffset(0x10)] public HousingObjectManager* indoorTerritory;
// [FieldOffset(0x9704)] public uint CurrentIndoorFloor;
public HousingObjectManager* GetCurrentManager()
{
return outdoorTerritory != null ? outdoorTerritory : indoorTerritory;
}
public bool IsOutdoors()
{
return outdoorTerritory != null;
}
public bool IsIndoors()
{
return indoorTerritory != null;
}
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct LayoutWorld
{
[FieldOffset(0x20)] public LayoutManager* ActiveLayout;
[FieldOffset(0x40)] public HousingStructure* HousingStruct;
}
[StructLayout(LayoutKind.Explicit)]
public struct LayoutManager
{
[FieldOffset(0x20)] public uint TerritoryTypeId;
[FieldOffset(0x80)] private readonly IntPtr _housingController;
[FieldOffset(0x90)] private readonly IntPtr _indoorAreaData;
public HousingController? HousingController
{
get
{
if (_housingController == IntPtr.Zero) return null;
// return Unsafe.Read<HousingController>(_housingController.ToPointer());
// return Marshal.PtrToStructure<HousingController>(_housingController);
// return *(HousingController*) _housingController;
return global::HouseStealerPlugin.HousingController.Get(_housingController);
}
}
public IndoorAreaData? IndoorAreaData
{
get
{
if (_indoorAreaData == IntPtr.Zero) return null;
return global::HouseStealerPlugin.IndoorAreaData.Get(_indoorAreaData);
}
}
}
public unsafe struct IndoorAreaData
{
public static IndoorAreaData Get(IntPtr address)
{
return new(address);
}
private IndoorAreaData(IntPtr thisPtr)
{
this._thisPtr = thisPtr;
}
public const int FloorMax = 4;
private readonly IntPtr _thisPtr;
public IndoorFloorData GetFloor(InteriorFloor index)
{
return IndoorFloorData.Get(new IntPtr((byte*)_thisPtr + (0x28 + (int)index * 0x14)));
}
public IndoorFloorData GetFloor(int index)
{
return IndoorFloorData.Get(new IntPtr((byte*)_thisPtr + (0x28 + index * 0x14)));
}
public float LightLevel => *(float*)((byte*)_thisPtr + 0x80);
}
public unsafe struct IndoorFloorData
{
public static IndoorFloorData Get(IntPtr address)
{
return new(address);
}
private IndoorFloorData(IntPtr thisPtr)
{
this._thisPtr = thisPtr;
}
public const int PartsMax = 5;
private readonly IntPtr _thisPtr;
public int GetPart(InteriorPartsType index)
{
return *(int*)((byte*)_thisPtr + (int)index * 4);
}
// returns key in sheet
public int GetPart(int index)
{
return *(int*)(byte*)(_thisPtr + index * 4);
}
}
// [StructLayout(LayoutKind.Explicit, Size = 28336)]
public unsafe struct HousingController
{
public static HousingController Get(IntPtr address)
{
return new(address);
}
private HousingController(IntPtr thisPtr)
{
this._thisPtr = thisPtr;
}
public const int HousesMax = 60;
private readonly IntPtr _thisPtr;
//[FieldOffset(0x8)]
public uint AreaType => *(uint*)(byte*)_thisPtr + 0x8;
// [FieldOffset(0x1F0)]
// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 60)]
// The size of the parent type of HouseCustomize is 464 even though HouseCustomize is 352
public HouseCustomize Houses(int index)
{
return HouseCustomize.Get(new IntPtr((byte*)_thisPtr + (0x1F0 + index * 464)));
}
}
// [StructLayout(LayoutKind.Explicit, Size = 352)]
public unsafe struct HouseCustomize
{
public static HouseCustomize Get(IntPtr address)
{
return new(address);
}
private HouseCustomize(IntPtr thisPtr)
{
this._thisPtr = thisPtr;
}
public const int PartsMax = 8;
private readonly IntPtr _thisPtr;
//[FieldOffset(0x10)]
public int Size => *(int*)(byte*)_thisPtr + 0x10;
public HousePart GetPart(int type)
{
return *(HousePart*)((byte*)_thisPtr + (0x20 + type * 40));
}
public HousePart GetPart(ExteriorPartsType type)
{
return *(HousePart*)((byte*)_thisPtr + (0x20 + (int)type * 40));
}
}
[StructLayout(LayoutKind.Explicit, Size = 40)]
public unsafe struct HousePart
{
[FieldOffset(0x00)] public int Category;
[FieldOffset(0x04)] private readonly int Unknown1;
[FieldOffset(0x08)] public ushort FixtureKey;
[FieldOffset(0x0A)] public byte Color;
[FieldOffset(0x0B)] private readonly byte Padding;
[FieldOffset(0x0C)] private readonly int Unknown2;
[FieldOffset(0x10)] private readonly void* Unknown3;
}
// This is just MJIManager
[StructLayout(LayoutKind.Explicit)]
public unsafe struct MjiManagerExtended
{
[FieldOffset(0x160)] public IslandObjectManager* ObjectManager;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct IslandObjectManager
{
[FieldOffset(0x78)] public IslandFurnitureManager* FurnitureManager;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct IslandFurnitureManager
{
[FieldOffset(0x1698)] public IntPtr ObjectList;
[FieldOffset(0x16A0)] public fixed ulong Objects[400];
}
}

View File

@@ -0,0 +1,39 @@
using System;
using HouseStealerPlugin.Gui;
namespace HouseStealerPlugin
{
public class PluginUi : IDisposable
{
private readonly HouseStealerPlugin _plugin;
public ConfigurationWindow ConfigWindow { get; }
public PluginUi(HouseStealerPlugin plugin)
{
ConfigWindow = new ConfigurationWindow(plugin);
_plugin = plugin;
DalamudApi.PluginInterface.UiBuilder.Draw += Draw;
DalamudApi.PluginInterface.UiBuilder.OpenConfigUi += OnOpenConfigUi;
DalamudApi.PluginInterface.UiBuilder.OpenMainUi += OnOpenConfigUi;
}
private void Draw()
{
ConfigWindow.Draw();
}
private void OnOpenConfigUi()
{
ConfigWindow.Visible = true;
ConfigWindow.CanUpload = false;
ConfigWindow.CanImport = false;
}
public void Dispose()
{
DalamudApi.PluginInterface.UiBuilder.Draw -= Draw;
DalamudApi.PluginInterface.UiBuilder.OpenConfigUi -= OnOpenConfigUi;
DalamudApi.PluginInterface.UiBuilder.OpenMainUi -= OnOpenConfigUi;
}
}
}

View File

@@ -0,0 +1,342 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Numerics;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using Lumina.Excel.Sheets;
using HouseStealerPlugin.Objects;
using static HouseStealerPlugin.HouseStealerPlugin;
namespace HouseStealerPlugin
{
public class Transform
{
public List<float> location { get; set; } = new List<float> { 0, 0, 0 };
public List<float> rotation { get; set; } = new List<float> { 0, 0, 0, 1 };
public List<float> scale { get; set; } = new List<float> { 1, 1, 1 };
}
public class BasicItem
{
public string name { get; set; } = "";
public uint itemId { get; set; } = 0;
}
public class Fixture : BasicItem
{
public string level { get; set; } = "";
public string type { get; set; } = "";
public Fixture() { }
public Fixture(string inType)
{
type = inType;
}
public Fixture(string inType, string inName) : this(inType)
{
name = inName;
}
public Fixture(string inType, string inName, string inLevel) : this(inType, inName)
{
level = inLevel;
}
}
public class Furniture : BasicItem
{
public Transform transform { get; set; } = new Transform();
public Dictionary<string, object> properties { get; set; } = new Dictionary<string, object>();
public List<Furniture> attachments { get; set; } = new List<Furniture>();
public Color GetColor()
{
if (properties.TryGetValue("color", out object colorObj))
{
var color = (string)colorObj;
return System.Drawing.ColorTranslator.FromHtml("#" + color.Substring(0, 6));
}
return Color.Empty;
}
public BasicItem GetMaterial()
{
if (properties.TryGetValue("material", out object materialObj))
{
if (materialObj is JsonElement materialJson)
{
return materialJson.Deserialize<BasicItem>();
}
}
return new BasicItem();
}
int ColorDiff(Color c1, Color c2)
{
return (int)Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R)
+ (c1.G - c2.G) * (c1.G - c2.G)
+ (c1.B - c2.B) * (c1.B - c2.B));
}
public uint GetClosestStain(List<(Color, uint)> colorList)
{
var color = GetColor();
var minDist = 2000;
uint closestStain = 0;
foreach (var testTuple in colorList)
{
var currentDist = ColorDiff(testTuple.Item1, color);
if (currentDist < minDist)
{
minDist = currentDist;
closestStain = testTuple.Item2;
}
}
return closestStain;
}
}
public class Layout
{
public Transform playerTransform { get; set; } = new Transform();
public string houseSize { get; set; } = "";
public float interiorScale { get; set; } = 1;
public List<Fixture> interiorFixture { get; set; } = new List<Fixture>();
public List<Furniture> interiorFurniture { get; set; } = new List<Furniture>();
}
public class ObjectToInferredTypesConverter : JsonConverter<object>
{
public override object Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) => reader.TokenType switch
{
JsonTokenType.True => true,
JsonTokenType.False => false,
JsonTokenType.Number when reader.TryGetInt64(out long l) => l,
JsonTokenType.Number => reader.GetDouble(),
JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime,
JsonTokenType.String => reader.GetString()!,
_ => JsonDocument.ParseValue(ref reader).RootElement.Clone()
};
public override void Write(
Utf8JsonWriter writer,
object objectToWrite,
JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options);
}
public class SaveLayoutManager
{
public static Configuration Config;
public static HouseStealerPlugin Plugin;
public static List<(Color, uint)> ColorList;
public SaveLayoutManager(HouseStealerPlugin plugin, Configuration config)
{
Config = config;
Plugin = plugin;
}
public static float layoutScale = 1;
private float scale(float i)
{
return checkZero(i);
}
private float checkZero(float i)
{
if (Math.Abs(i) < 0.001) return 0;
return i;
}
List<float> RotationToQuat(float rotation)
{
Quaternion q = Quaternion.CreateFromYawPitchRoll(0, 0, rotation);
return new List<float> { checkZero(q.X), checkZero(q.Y), checkZero(q.Z), checkZero(q.W) };
}
public static void LoadInteriorFixtures()
{
var layout = Plugin.Layout;
layout.interiorFixture.Clear();
for (var i = 0; i < IndoorAreaData.FloorMax; i++)
{
var fixtures = Memory.Instance.GetInteriorCommonFixtures(i);
if (fixtures.Length == 0) continue;
for (var j = 0; j < IndoorFloorData.PartsMax; j++)
{
if (fixtures[j].FixtureKey == -1 || fixtures[j].FixtureKey == 0) continue;
if (!fixtures[j].Item.HasValue) continue;
var item = fixtures[j].Item.Value;
if (item.RowId == 0) continue;
var fixture = new Fixture();
fixture.type = Utils.GetInteriorPartDescriptor((InteriorPartsType)j);
fixture.level = Utils.GetFloorDescriptor((InteriorFloor)i);
fixture.name = item.Name.ExtractText();
fixture.itemId = item.RowId;
layout.interiorFixture.Add(fixture);
}
}
layout.houseSize = Memory.Instance.GetIndoorHouseSize();
var territoryId = Memory.Instance.GetTerritoryTypeId();
if (DalamudApi.DataManager.GetExcelSheet<TerritoryType>().TryGetRow(territoryId, out var row))
{
var placeName = row.Name.ToString();
var district = new Fixture();
district.type = "District";
var districtName = placeName.Substring(0, 2);
switch (districtName)
{
case "s1":
district.name = "Mist";
break;
case "f1":
district.name = "Lavender Beds";
break;
case "w1":
district.name = "Goblet";
break;
case "e1":
district.name = "Shirogane";
break;
case "r1":
district.name = "Empyreum";
break;
case "h1":
district.name = "Minimalist";
break;
default:
break;
}
layout.interiorFixture.Add(district);
}
}
void RecordFurniture(List<Furniture> furnitureList, List<HousingItem> itemList)
{
HousingData Data = HousingData.Instance;
furnitureList.Clear();
foreach (HousingItem gameObject in itemList)
{
var furniture = new Furniture();
furniture.name = gameObject.Name;
furniture.itemId = gameObject.ItemKey;
furniture.transform.location = new List<float> { scale(gameObject.X), scale(gameObject.Z), scale(gameObject.Y) };
furniture.transform.rotation = RotationToQuat(-gameObject.Rotate);
if (gameObject.Stain != 0 && Data.TryGetStain(gameObject.Stain, out var stainColor))
{
var color = Utils.StainToVector4(stainColor.Color);
var cr = (int)(color.X * 255);
var cg = (int)(color.Y * 255);
var cb = (int)(color.Z * 255);
var ca = (int)(color.W * 255);
furniture.properties.Add("color", $"{cr:X2}{cg:X2}{cb:X2}{ca:X2}");
}
else if (gameObject.MaterialItemKey != 0)
{
if (DalamudApi.DataManager.GetExcelSheet<Item>().TryGetRow(gameObject.MaterialItemKey, out var item))
{
var basicItem = new BasicItem();
basicItem.name = item.Name.ToString();
basicItem.itemId = gameObject.MaterialItemKey;
furniture.properties.Add("material", basicItem);
}
}
furnitureList.Add(furniture);
}
}
public void ExportLayout()
{
if (Directory.Exists(Config.SaveLocation))
{
throw new Exception("Save file not specified");
}
Layout save = Plugin.Layout;
save.playerTransform = new Transform();
save.interiorScale = 1;
RecordFurniture(save.interiorFurniture, Plugin.InteriorItemList);
var encoderSettings = new TextEncoderSettings();
encoderSettings.AllowCharacters('\'');
encoderSettings.AllowRange(UnicodeRanges.BasicLatin);
var options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true,
Converters = { new ObjectToInferredTypesConverter() }
};
string jsonString = JsonSerializer.Serialize(save, options);
string pattern = @"\s+(-?(?:[0-9]*[.])?[0-9]+(?:E-[0-9]+)?,?)\s*(?=\s[-\d\]])";
string result = Regex.Replace(jsonString, pattern, " $1");
File.WriteAllText(Config.SaveLocation, result);
Log("Finished exporting layout");
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace HouseStealerPlugin
{
public class HookManager
{
public static List<IHookWrapper> HookList = new();
public static void Dispose()
{
foreach (var hook in HookList.Where(hook => !hook.IsDisposed))
{
if (hook.IsEnabled)
hook.Disable();
hook.Dispose();
}
HookList.Clear();
}
public static HookWrapper<T> Hook<T>(string signature, T detour, bool enable = true, int addressOffset = 0)
where T : Delegate
{
var addr = DalamudApi.SigScanner.ScanText(signature);
return HookAddress(addr, detour, enable, addressOffset);
}
public static HookWrapper<T> HookAddress<T>(IntPtr addr, T detour, bool enable = true, int addressOffset = 0) where T : Delegate
{
DalamudApi.PluginLog.Info($"Hooking {detour.Method.Name} at {addr.ToString("X")}");
var h = DalamudApi.Hooks.HookFromAddress(addr + addressOffset, detour);
var wh = new HookWrapper<T>(h);
if (enable) wh.Enable();
HookList.Add(wh);
return wh;
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using Dalamud.Hooking;
namespace HouseStealerPlugin
{
// based on https://github.com/Caraxi/SimpleTweaksPlugin/blob/main/Helper/HookWrapper.cs
public interface IHookWrapper : IDisposable
{
public bool IsEnabled { get; }
public bool IsDisposed { get; }
public void Enable();
public void Disable();
}
public class HookWrapper<T> : IHookWrapper where T : Delegate
{
private bool disposed;
private readonly Hook<T> wrappedHook;
public HookWrapper(Hook<T> hook)
{
wrappedHook = hook;
}
public T Original => wrappedHook.Original;
public void Enable()
{
if (disposed) return;
wrappedHook?.Enable();
}
public void Disable()
{
if (disposed) return;
wrappedHook?.Disable();
}
public void Dispose()
{
DalamudApi.PluginLog.Info("Disposing of {cdelegate}", typeof(T).Name);
Disable();
disposed = true;
wrappedHook?.Dispose();
}
public IntPtr Address => wrappedHook.Address;
public bool IsEnabled => wrappedHook.IsEnabled;
public bool IsDisposed => wrappedHook.IsDisposed;
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Numerics;
namespace HouseStealerPlugin
{
public static class QuaternionExtensions
{
public static float ComputeXAngle(this Quaternion q)
{
float sinr_cosp = 2 * (q.W * q.X + q.Y * q.Z);
float cosr_cosp = 1 - 2 * (q.X * q.X + q.Y * q.Y);
return (float)Math.Atan2(sinr_cosp, cosr_cosp);
}
public static float ComputeYAngle(this Quaternion q)
{
float sinp = 2 * (q.W * q.Y - q.Z * q.X);
if (Math.Abs(sinp) >= 1)
return (float)Math.PI / 2 * Math.Sign(sinp); // use 90 degrees if out of range
else
return (float)Math.Asin(sinp);
}
public static float ComputeZAngle(this Quaternion q)
{
float siny_cosp = 2 * (q.W * q.Z + q.X * q.Y);
float cosy_cosp = 1 - 2 * (q.Y * q.Y + q.Z * q.Z);
return (float)Math.Atan2(siny_cosp, cosy_cosp);
}
public static Vector3 ComputeAngles(this Quaternion q)
{
return new Vector3(ComputeXAngle(q), ComputeYAngle(q), ComputeZAngle(q));
}
public static Quaternion FromAngles(Vector3 angles)
{
float cy = (float)Math.Cos(angles.Z * 0.5f);
float sy = (float)Math.Sin(angles.Z * 0.5f);
float cp = (float)Math.Cos(angles.Y * 0.5f);
float sp = (float)Math.Sin(angles.Y * 0.5f);
float cr = (float)Math.Cos(angles.X * 0.5f);
float sr = (float)Math.Sin(angles.X * 0.5f);
Quaternion q = new Quaternion
{
W = cr * cp * cy + sr * sp * sy,
X = sr * cp * cy - cr * sp * sy,
Y = cr * sp * cy + sr * cp * sy,
Z = cr * cp * sy - sr * sp * cy
};
return q;
}
}
}

View File

@@ -0,0 +1,349 @@
using System.Collections.Generic;
using System.Numerics;
namespace HouseStealerPlugin.Util
{
struct Location
{
public float x;
public float y;
public float z;
public float rotation;
public string size;
public string entranceLayout;
public Location(float _x, float _y, float _z, float rot, string _size, string layout)
{
x = _x;
y = _y;
z = _z;
rotation = rot;
size = _size;
entranceLayout = layout;
}
public Vector3 ToVector()
{
return new Vector3(x, y, z);
}
}
class Plots
{
public static Dictionary<string, Dictionary<int, Location>> Map = new Dictionary<string, Dictionary<int, Location>> {
{"r1h1", new Dictionary<int, Location>{
{ 01, new Location(90f, -18.5f, 177f, 3.141593f, "s", "Center")},
{ 02, new Location(144f, -38.5f, 129f, 0f, "m", "Center")},
{ 03, new Location(79f, -28.5f, 109f, 0f, "s", "Center")},
{ 04, new Location(101f, -28.5f, 78f, -1.570796f, "s", "Center")},
{ 05, new Location(179f, -48.5f, 85f, 3.141593f, "s", "Center")},
{ 06, new Location(184f, -48.5f, 44f, 0f, "s", "Center")},
{ 07, new Location(117f, -28.5f, 42f, -1.570796f, "m", "Center")},
{ 08, new Location(-36f, -18.5f, 86f, -0.9599311f, "m", "Center")},
{ 09, new Location(4f, -18.5f, 59f, 0.6108653f, "s", "Center")},
{ 10, new Location(-23f, -18.5f, 36f, 3.141593f, "s", "Center")},
{ 11, new Location(-71f, -8.5f, 135f, 0f, "s", "Center")},
{ 12, new Location(-114f, 1.5f, 78f, -0.9599311f, "l", "Center")},
{ 13, new Location(-81f, 1.5f, 42f, 0f, "s", "Center")},
{ 14, new Location(-23f, -18.5f, -36f, 0f, "s", "Center")},
{ 15, new Location(-9f, -18.5f, -80f, -0.6108653f, "s", "Center")},
{ 16, new Location(-41f, -8.5f, -94f, -2.181662f, "s", "Center")},
{ 17, new Location(-86f, 1.5f, -43f, -1.570796f, "m", "Center")},
{ 18, new Location(-116f, 11.5f, -85f, -2.181662f, "m", "Center")},
{ 19, new Location(-46f, 1.5f, -146f, -1.570796f, "s", "Center")},
{ 20, new Location(-77f, 1.5f, -147f, 0f, "s", "Center")},
{ 21, new Location(-72f, 11.5f, -190f, 0f, "m", "Center")},
{ 22, new Location(-140f, 21.5f, -136f, -1.570796f, "l", "Center")},
{ 23, new Location(114f, -18.5f, -37f, 0.9599311f, "s", "Center")},
{ 24, new Location(131f, -18.5f, -61f, 2.530727f, "s", "Center")},
{ 25, new Location(88f, -18.5f, -88f, 0f, "s", "Center")},
{ 26, new Location(185f, -38.5f, -54f, 0f, "m", "Center")},
{ 27, new Location(151f, -28.5f, -100f, 0.9599311f, "s", "Center")},
{ 28, new Location(44f, 1.5f, -135f, -1.570796f, "s", "Center")},
{ 29, new Location(52f, 1.5f, -176f, 0f, "s", "Center")},
{ 30, new Location(93f, 1.5f, -178f, 0f, "l", "Center")},
{ 31, new Location(-881f, -18.5f, -614f, -1.570796f, "s", "Center")},
{ 32, new Location(-833f, -38.5f, -560f, 1.570796f, "m", "Center")},
{ 33, new Location(-813f, -28.5f, -625f, 1.570796f, "s", "Center")},
{ 34, new Location(-782f, -28.5f, -603f, 0f, "s", "Center")},
{ 35, new Location(-789f, -48.5f, -525f, -1.570796f, "s", "Center")},
{ 36, new Location(-748f, -48.5f, -520f, 1.570796f, "s", "Center")},
{ 37, new Location(-746f, -28.5f, -587f, 0f, "m", "Center")},
{ 38, new Location(-790f, -18.5f, -740f, 0.6108653f, "m", "Center")},
{ 39, new Location(-763f, -18.5f, -700f, 2.181662f, "s", "Center")},
{ 40, new Location(-740f, -18.5f, -727f, -1.570796f, "s", "Center")},
{ 41, new Location(-839f, -8.5f, -775f, 1.570796f, "s", "Center")},
{ 42, new Location(-782f, 1.5f, -818f, 0.6108653f, "l", "Center")},
{ 43, new Location(-746f, 1.5f, -785f, 1.570796f, "s", "Center")},
{ 44, new Location(-668f, -18.5f, -727f, 1.570796f, "s", "Center")},
{ 45, new Location(-624f, -18.5f, -713f, 0.9599311f, "s", "Center")},
{ 46, new Location(-610f, -8.5f, -745f, -0.6108651f, "s", "Center")},
{ 47, new Location(-661f, 1.5f, -790f, 0f, "m", "Center")},
{ 48, new Location(-619f, 11.5f, -820f, -0.6108651f, "m", "Center")},
{ 49, new Location(-558f, 1.5f, -750f, 0f, "s", "Center")},
{ 50, new Location(-557f, 1.5f, -781f, 1.570796f, "s", "Center")},
{ 51, new Location(-514f, 11.5f, -776f, 1.570796f, "m", "Center")},
{ 52, new Location(-568f, 21.5f, -844f, 0f, "l", "Center")},
{ 53, new Location(-667f, -18.5f, -590f, 2.530728f, "s", "Center")},
{ 54, new Location(-643f, -18.5f, -573f, -2.181662f, "s", "Center")},
{ 55, new Location(-616f, -18.5f, -616f, 1.570796f, "s", "Center")},
{ 56, new Location(-650f, -38.5f, -519f, 1.570796f, "m", "Center")},
{ 57, new Location(-604f, -28.5f, -553f, 2.530728f, "s", "Center")},
{ 58, new Location(-569f, 1.5f, -660f, 0f, "s", "Center")},
{ 59, new Location(-528f, 1.5f, -652f, 1.570796f, "s", "Center")},
{ 60, new Location(-526f, 1.5f, -611f, 1.570796f, "l", "Center")}
} },
{"e1h1", new Dictionary<int, Location>{
{ 01, new Location(-34f, 2.02f, 116f, -1.308997f, "m", "Center")},
{ 02, new Location(-27f, 14.52f, 75f, -1.308997f, "s", "Center")},
{ 03, new Location(5f, 6.02f, 125f, -1.134464f, "s", "Center")},
{ 04, new Location(7f, 20.02f, 79f, -2.617994f, "s", "Center")},
{ 05, new Location(40f, 20.02f, 63f, 0.5235988f, "s", "Center")},
{ 06, new Location(45f, 10.02f, 99f, 0f, "s", "Center")},
{ 07, new Location(50f, 10.02f, 144f, 3.141593f, "l", "Center")},
{ 08, new Location(115f, 11.6f, 130f, 2.356194f, "m", "Center")},
{ 09, new Location(90f, 16.02f, 93f, -2.181662f, "s", "Center")},
{ 10, new Location(118f, 22.02f, 70f, 2.356194f, "s", "Center")},
{ 11, new Location(89f, 22.02f, 47f, 1.047198f, "s", "Center")},
{ 12, new Location(58f, 26.02f, 22f, 0.4363323f, "s", "Center")},
{ 13, new Location(68f, 31.59f, -18f, 0.4886922f, "m", "Center")},
{ 14, new Location(105f, 30.02f, 6f, 0.7853982f, "s", "Center")},
{ 15, new Location(131f, 34.02f, 32f, 2.356194f, "m", "Center")},
{ 16, new Location(136f, 40.02f, -30f, 0.7853982f, "l", "Center")},
{ 17, new Location(-105f, 2.02f, 74f, 2.094395f, "s", "Center")},
{ 18, new Location(-88f, 14.52f, 39f, 2.356194f, "s", "Center")},
{ 19, new Location(-135f, 2.02f, 50f, -0.7853982f, "m", "Center")},
{ 20, new Location(-107f, 10.02f, 12f, 0.9599311f, "s", "Center")},
{ 21, new Location(-164f, 6.02f, 22f, 2.879793f, "s", "Center")},
{ 22, new Location(-167f, 6.02f, -11f, 0f, "s", "Center")},
{ 23, new Location(-151f, 16.02f, -50f, -2.356194f, "s", "Center")},
{ 24, new Location(-123f, 17.51f, -26f, -2.356194f, "m", "Center")},
{ 25, new Location(-80f, 14.02f, -14f, 2.094395f, "s", "Center")},
{ 26, new Location(-97f, 16.02f, -56f, 0.7853982f, "s", "Center")},
{ 27, new Location(-59f, 20.02f, -55f, 0f, "s", "Center")},
{ 28, new Location(-75f, 25.02f, -117f, 0f, "m", "Center")},
{ 29, new Location(-116f, 26.62f, -93f, 1.134464f, "s", "Center")},
{ 30, new Location(-159f, 30.02f, -122f, -0.7853982f, "l", "Center")},
{ 31, new Location(-820f, 2.02f, -738f, 0.2617994f, "m", "Center")},
{ 32, new Location(-779f, 14.52f, -731f, 0.2617994f, "s", "Center")},
{ 33, new Location(-829f, 6.02f, -699f, 0.4363323f, "s", "Center")},
{ 34, new Location(-783f, 20.02f, -697f, -1.047197f, "s", "Center")},
{ 35, new Location(-767f, 20.02f, -664f, 2.094395f, "s", "Center")},
{ 36, new Location(-803f, 10.02f, -659f, 1.570796f, "s", "Center")},
{ 37, new Location(-848f, 10.02f, -654f, -1.570796f, "l", "Center")},
{ 38, new Location(-834f, 11.6f, -589f, -2.356195f, "m", "Center")},
{ 39, new Location(-797f, 16.02f, -614f, -0.6108653f, "s", "Center")},
{ 40, new Location(-774f, 22.02f, -586f, -2.356195f, "s", "Center")},
{ 41, new Location(-751f, 22.02f, -615f, 2.617994f, "s", "Center")},
{ 42, new Location(-726f, 26.02f, -646f, 2.007128f, "s", "Center")},
{ 43, new Location(-686f, 31.59f, -636f, 2.059489f, "m", "Center")},
{ 44, new Location(-710f, 30.02f, -599f, 2.356195f, "s", "Center")},
{ 45, new Location(-736f, 34.02f, -573f, -2.356195f, "m", "Center")},
{ 46, new Location(-674f, 40.02f, -568f, 2.356195f, "l", "Center")},
{ 47, new Location(-778f, 2.02f, -809f, -2.617994f, "s", "Center")},
{ 48, new Location(-743f, 14.52f, -792f, -2.356195f, "s", "Center")},
{ 49, new Location(-754f, 2.02f, -839f, 0.7853982f, "m", "Center")},
{ 50, new Location(-716f, 10.02f, -811f, 2.530728f, "s", "Center")},
{ 51, new Location(-726f, 6.02f, -868f, -1.832596f, "s", "Center")},
{ 52, new Location(-693f, 6.02f, -871f, 1.570796f, "s", "Center")},
{ 53, new Location(-654f, 16.02f, -855f, -0.7853979f, "s", "Center")},
{ 54, new Location(-678f, 17.51f, -827f, -0.7853979f, "m", "Center")},
{ 55, new Location(-690f, 14.02f, -784f, -2.617994f, "s", "Center")},
{ 56, new Location(-648f, 16.02f, -801f, 2.356195f, "s", "Center")},
{ 57, new Location(-649f, 20.02f, -763f, 1.570796f, "s", "Center")},
{ 58, new Location(-587f, 25.02f, -779f, 1.570796f, "m", "Center")},
{ 59, new Location(-611f, 26.62f, -820f, 2.70526f, "s", "Center")},
{ 60, new Location(-582f, 30.02f, -863f, 0.7853982f, "l", "Center")},
} },
{"f1h1", new Dictionary<int, Location>{
{ 01, new Location(144f, 46f, -78.375f, 1.570451f, "m", "Center")},
{ 02, new Location(78.5f, 51f, -100f, 0f, "s", "Center")},
{ 03, new Location(130f, 58.5f, -163f, 2.443461f, "l", "Far Right Side")},
{ 04, new Location(39.5f, 54.5f, -81f, -1.308997f, "s", "Center")},
{ 05, new Location(71.5f, 34.5f, -30.5f, 0f, "m", "Center")},
{ 06, new Location(143f, 23.5f, 2f, 2.007128f, "l", "Far Right Side")},
{ 07, new Location(116.5f, 9.625f, 52f, 0.08726646f, "s", "Center")},
{ 08, new Location(85f, 8.75f, 63.5f, -0.7853982f, "s", "Center")},
{ 09, new Location(125f, 8f, 95f, 1.570451f, "s", "Center")},
{ 10, new Location(74.5f, 7.75f, 93.5f, -1.221731f, "s", "Center")},
{ 11, new Location(52f, 5f, 132f, 0f, "m", "Center")},
{ 12, new Location(64f, 25f, 22.5f, -1.570451f, "s", "Center")},
{ 13, new Location(27.74659f, 34f, 13f, -1.832596f, "s", "Center")},
{ 14, new Location(32f, 19.5f, 43.5f, -1.570451f, "s", "Center")},
{ 15, new Location(12f, 9f, 81.5f, -1.570451f, "s", "Center")},
{ 16, new Location(-92f, 7.75f, 98f, -0.6981317f, "m", "Center")},
{ 17, new Location(-30f, 10f, 78.5f, 0.7853978f, "s", "Center")},
{ 18, new Location(-31f, 21f, 44.5f, -1.570451f, "s", "Center")},
{ 19, new Location(-82f, 21.5f, 45.5f, -1.221731f, "s", "Center")},
{ 20, new Location(-8.5f, 33f, 1f, 1.570451f, "s", "Center")},
{ 21, new Location(-58f, 30.5f, -3f, 0f, "m", "Center")},
{ 22, new Location(-37f, 41.5f, -36.5f, -1.570451f, "s", "Center")},
{ 23, new Location(-65.5f, 32f, -35.5f, 1.570451f, "s", "Center")},
{ 24, new Location(-45.5f, 47f, -65.5f, -1.570451f, "s", "Center")},
{ 25, new Location(-4f, 46.5f, -73f, 1.570451f, "s", "Center")},
{ 26, new Location(-29f, 56.5f, -106f, 2.96706f, "s", "Center")},
{ 27, new Location(-114f, 31.5f, -40f, -1.308997f, "m", "Center")},
{ 28, new Location(-135.75f, 27.5f, -108.5f, -2.6529f, "l", "Left Side")},
{ 29, new Location(-105f, 34.5f, -154.5f, -0.7330383f, "s", "Center")},
{ 30, new Location(-50.5f, 56f, -150f, 0f, "m", "Center")},
{ 31, new Location(-625.625f, 46f, -560f, 3.141247f, "m", "Center")},
{ 32, new Location(-604f, 51f, -625.5f, 1.570796f, "s", "Center")},
{ 33, new Location(-541f, 58.5f, -574f, -2.268928f, "l", "Far Right Side")},
{ 34, new Location(-623f, 54.5f, -664.5f, 0.2617994f, "s", "Center")},
{ 35, new Location(-673.5f, 34.5f, -632.5f, 1.570796f, "m", "Center")},
{ 36, new Location(-706f, 23.5f, -561f, -2.70526f, "l", "Far Right Side")},
{ 37, new Location(-756f, 9.625f, -587.5f, 1.658063f, "s", "Center")},
{ 38, new Location(-767.5f, 8.75f, -619f, 0.7853982f, "s", "Center")},
{ 39, new Location(-799f, 8f, -579f, 3.141247f, "s", "Center")},
{ 40, new Location(-797.5f, 7.75f, -629.5f, 0.3490657f, "s", "Center")},
{ 41, new Location(-836f, 5f, -652f, 1.570796f, "m", "Center")},
{ 42, new Location(-726.5f, 25f, -640f, 0.0003454686f, "s", "Center")},
{ 43, new Location(-717f, 34f, -676.2534f, -0.2617998f, "s", "Center")},
{ 44, new Location(-747.5f, 19.5f, -672f, 0.0003454686f, "s", "Center")},
{ 45, new Location(-785.5f, 9f, -692f, 0.0003454686f, "s", "Center")},
{ 46, new Location(-802f, 7.75f, -796f, 0.8726646f, "m", "Center")},
{ 47, new Location(-782.5f, 10f, -734f, 2.356194f, "s", "Center")},
{ 48, new Location(-748.5f, 21f, -735f, 0.0003454686f, "s", "Center")},
{ 49, new Location(-749.5f, 21.5f, -786f, 0.3490657f, "s", "Center")},
{ 50, new Location(-705f, 33f, -712.5f, 3.141247f, "s", "Center")},
{ 51, new Location(-701f, 30.5f, -762f, 1.570796f, "m", "Center")},
{ 52, new Location(-667.5f, 41.5f, -741f, 0.0003454686f, "s", "Center")},
{ 53, new Location(-668.5f, 32f, -769.5f, 3.141247f, "s", "Center")},
{ 54, new Location(-638.5f, 47f, -749.5f, 0.0003454686f, "s", "Center")},
{ 55, new Location(-631f, 46.5f, -708f, 3.141247f, "s", "Center")},
{ 56, new Location(-598f, 56.5f, -733f, -1.745329f, "s", "Center")},
{ 57, new Location(-664f, 31.5f, -818f, 0.2617989f, "m", "Center")},
{ 58, new Location(-595.5f, 27.5f, -839.75f, -1.082104f, "l", "Left Side")},
{ 59, new Location(-549.5f, 34.5f, -809f, 0.8377581f, "s", "Center")},
{ 60, new Location(-554f, 56f, -754.5f, 1.570796f, "m", "Center")},
} },
{"s1h1", new Dictionary<int, Location>{
{ 01, new Location(-72f, 40f, -108f, -1.570451f, "m", "Center")},
{ 02, new Location(-144f, 36f, -100f, -0.7853982f, "l", "Center")},
{ 03, new Location(-96f, 32f, -60f, 3.141593f, "s", "Center")},
{ 04, new Location(-92f, 18f, -12f, -1.570451f, "m", "Right")},
{ 05, new Location(-148f, 20f, 28f, -2.094395f, "l", "Center")},
{ 06, new Location(-36f, 14f, -4f, 0.7853982f, "m", "Center")},
{ 07, new Location(-40f, 24f, -56f, 0f, "m", "Center")},
{ 08, new Location(-12f, 36f, -92f, 1.570451f, "s", "Right")},
{ 09, new Location(40f, 38f, -100f, 0f, "s", "Right Side")},
{ 10, new Location(64f, 42f, -124f, 0f, "s", "Right")},
{ 11, new Location(4f, 25f, -56f, 0f, "s", "Right Side")},
{ 12, new Location(48f, 26f, -60f, 0f, "s", "Center")},
{ 13, new Location(64f, 18f, -32f, 0f, "s", "Right")},
{ 14, new Location(83.628f, 29f, -63f, -0.7853982f, "m", "Far Right")},
{ 15, new Location(148f, 46f, -104f, 0.7853982f, "l", "Center")},
{ 16, new Location(136f, 28f, -34f, 1.570451f, "s", "Left")},
{ 17, new Location(112f, 22f, -20f, 1.570451f, "s", "Left")},
{ 18, new Location(72f, 17f, 4f, 0f, "s", "Far Right Side")},
{ 19, new Location(42f, 8f, 24f, 0.3490659f, "s", "Center")},
{ 20, new Location(66f, 6f, 36f, 0.5235988f, "s", "Center")},
{ 21, new Location(88f, 5f, 52f, 0.6981317f, "s", "Center")},
{ 22, new Location(120f, 18f, 56f, 0f, "s", "Right Side")},
{ 23, new Location(112f, 18f, 28f, 0f, "s", "Right Side")},
{ 24, new Location(150f, 22f, -6f, 0f, "s", "Center")},
{ 25, new Location(162f, 32f, 24f, 0f, "s", "Right Side")},
{ 26, new Location(168f, 28f, 56f, 1.570451f, "s", "Center")},
{ 27, new Location(186f, 36f, -6f, 0f, "s", "Center")},
{ 28, new Location(196f, 40f, 24f, 0f, "s", "Right")},
{ 29, new Location(196f, 38f, 64f, 1.570451f, "m", "Far Right")},
{ 30, new Location(140f, 20f, 100f, 2.617994f, "m", "Center")},
{ 31, new Location(-596f, 40f, -776f, 0.0003454686f, "m", "Center")},
{ 32, new Location(-604f, 36f, -848f, 0.7853982f, "l", "Center")},
{ 33, new Location(-644f, 32f, -800f, -1.570796f, "s", "Center")},
{ 34, new Location(-692f, 18f, -796f, 0.0003454686f, "m", "Right")},
{ 35, new Location(-732f, 20f, -852f, -0.523599f, "l", "Center")},
{ 36, new Location(-700f, 14f, -740f, 2.356195f, "m", "Center")},
{ 37, new Location(-648f, 24f, -744f, 1.570796f, "m", "Center")},
{ 38, new Location(-612f, 36f, -716f, 3.141247f, "s", "Right")},
{ 39, new Location(-604f, 38f, -664f, 1.570796f, "s", "Right Side")},
{ 40, new Location(-580f, 42f, -640f, 1.570796f, "s", "Right")},
{ 41, new Location(-648f, 25f, -700f, 1.570796f, "s", "Right Side")},
{ 42, new Location(-644f, 26f, -656f, 1.570796f, "s", "Center")},
{ 43, new Location(-672f, 18f, -640f, 1.570796f, "s", "Right")},
{ 44, new Location(-641f, 29f, -620.372f, 0.7853982f, "m", "Far Right")},
{ 45, new Location(-600f, 46f, -556f, 2.356195f, "l", "Center")},
{ 46, new Location(-670f, 28f, -568f, 3.141247f, "s", "Left")},
{ 47, new Location(-684f, 22f, -592f, 3.141247f, "s", "Left")},
{ 48, new Location(-708f, 17f, -632f, 1.570796f, "s", "Far Right Side")},
{ 49, new Location(-728f, 8f, -662f, 1.919862f, "s", "Center")},
{ 50, new Location(-740f, 6f, -638f, 2.094395f, "s", "Center")},
{ 51, new Location(-756f, 5f, -616f, 2.268928f, "s", "Center")},
{ 52, new Location(-760f, 18f, -584f, 1.570796f, "s", "Right Side")},
{ 53, new Location(-732f, 18f, -592f, 1.570796f, "s", "Right Side")},
{ 54, new Location(-698f, 22f, -554f, 1.570796f, "s", "Center")},
{ 55, new Location(-728f, 32f, -542f, 1.570796f, "s", "Right Side")},
{ 56, new Location(-760f, 28f, -536f, 3.141247f, "s", "Center")},
{ 57, new Location(-698f, 36f, -518f, 1.570796f, "s", "Center")},
{ 58, new Location(-728f, 40f, -508f, 1.570796f, "s", "Right")},
{ 59, new Location(-768f, 38f, -508f, 3.141247f, "m", "Far Right")},
{ 60, new Location(-804f, 20f, -564f, -2.094395f, "m", "Center")},
} },
{"w1h1", new Dictionary<int, Location>{
{ 01, new Location(-38.00001f, -4f, -110f, 2.86234f, "s", "Right")},
{ 02, new Location(-64.00002f, 4f, -98.00002f, -0.6108653f, "s", "Right")},
{ 03, new Location(-85.64f, 6f, -80.02f, -0.8834857f, "s", "Right")},
{ 04, new Location(-82.203f, -4f, -121.07f, 2.548181f, "m", "Left")},
{ 05, new Location(-118.665f, 12f, -92.44f, 0.5962044f, "l", "Right Side")},
{ 06, new Location(-39.73f, 0f, -64.44f, 0.7853982f, "m", "Right")},
{ 07, new Location(-72.6f, 8f, -40.315f, 2.059489f, "s", "Right")},
{ 08, new Location(-117.81f, 10f, -23.37f, -1.343904f, "m", "Center")},
{ 09, new Location(-140.246f, 12f, -51.004f, 2.007129f, "s", "Right")},
{ 10, new Location(-147.905f, 15f, -24.543f, 0.1745329f, "s", "Right")},
{ 11, new Location(-158.78f, 15f, 18.875f, -1.833119f, "m", "Right")},
{ 12, new Location(-116f, 15f, 23.99996f, 1.308997f, "m", "Right")},
{ 13, new Location(-72f, 11.618f, 12f, -0.2391101f, "l", "Far Left Side")},
{ 14, new Location(-32.00002f, -20f, 17.99998f, 3.141593f, "s", "Left")},
{ 15, new Location(-14.018f, -16.02f, -36.035f, -0.7853982f, "s", "Right")},
{ 16, new Location(68.24998f, -16f, -39.56697f, -0.5235988f, "s", "Right")},
{ 17, new Location(67f, -20f, 0.2499695f, 3.141593f, "s", "Right")},
{ 18, new Location(73.188f, -24f, 28.851f, 2.094395f, "s", "Right")},
{ 19, new Location(44f, -24f, 53.81f, 2.094395f, "m", "Right")},
{ 20, new Location(0f, -24f, 79.99998f, 3.141593f, "s", "Right")},
{ 21, new Location(93.99998f, -26f, -70.00002f, 2.356194f, "s", "Right")},
{ 22, new Location(106f, -14f, -36.00003f, -0.3490659f, "s", "Left")},
{ 23, new Location(130.465f, -18f, -56.628f, 1.221731f, "s", "Right")},
{ 24, new Location(144.5f, -26f, -32.00003f, 0f, "s", "Right")},
{ 25, new Location(123.75f, -28f, 4f, -1.570796f, "m", "Right")},
{ 26, new Location(120f, -28f, 39.99997f, -1.570796f, "s", "Right")},
{ 27, new Location(130f, -36f, 71.99997f, 3.141593f, "s", "Right")},
{ 28, new Location(95.545f, -33.68f, 59.08f, 0.5235988f, "s", "Right")},
{ 29, new Location(184f, -44f, 23.49998f, 1.570796f, "s", "Right")},
{ 30, new Location(188f, -40f, 60f, 3.141593f, "l", "Far Right Side")},
{ 31, new Location(-594f, -4f, -742f, -1.850049f, "s", "Right")},
{ 32, new Location(-606f, 4f, -768f, 0.9599311f, "s", "Right")},
{ 33, new Location(-623.98f, 6f, -789.64f, 0.6873107f, "s", "Right")},
{ 34, new Location(-582.93f, -4f, -786.203f, -2.164208f, "m", "Left")},
{ 35, new Location(-611.56f, 12f, -822.665f, 2.167001f, "l", "Right Side")},
{ 36, new Location(-639.56f, 0f, -743.73f, 2.356195f, "m", "Right")},
{ 37, new Location(-663.685f, 8f, -776.6f, -2.6529f, "s", "Right")},
{ 38, new Location(-680.63f, 10f, -821.81f, 0.2268923f, "m", "Center")},
{ 39, new Location(-652.996f, 12f, -844.246f, -2.70526f, "s", "Right")},
{ 40, new Location(-679.457f, 15f, -851.905f, 1.745329f, "s", "Right")},
{ 41, new Location(-722.875f, 15f, -862.78f, -0.262323f, "m", "Right")},
{ 42, new Location(-727.9999f, 15f, -820f, 2.879793f, "m", "Right")},
{ 43, new Location(-716f, 11.618f, -776f, 1.331686f, "l", "Far Left Side")},
{ 44, new Location(-722f, -20f, -736f, -1.570796f, "s", "Left")},
{ 45, new Location(-667.965f, -16.02f, -718.018f, 0.7853982f, "s", "Right")},
{ 46, new Location(-664.433f, -16f, -635.75f, 1.047198f, "s", "Right")},
{ 47, new Location(-704.25f, -20f, -637f, -1.570796f, "s", "Right")},
{ 48, new Location(-732.851f, -24f, -630.812f, -2.617994f, "s", "Right")},
{ 49, new Location(-757.81f, -24f, -660f, -2.617994f, "m", "Right")},
{ 50, new Location(-784f, -24f, -704f, -1.570796f, "s", "Right")},
{ 51, new Location(-634f, -26f, -610f, -2.356195f, "s", "Right")},
{ 52, new Location(-668f, -14f, -598f, 1.22173f, "s", "Left")},
{ 53, new Location(-647.372f, -18f, -573.535f, 2.792527f, "s", "Right")},
{ 54, new Location(-672f, -26f, -559.5f, 1.570796f, "s", "Right")},
{ 55, new Location(-708f, -28f, -580.25f, 0f, "m", "Right")},
{ 56, new Location(-744f, -28f, -584f, 0f, "s", "Right")},
{ 57, new Location(-776f, -36f, -574f, -1.570796f, "s", "Right")},
{ 58, new Location(-763.08f, -33.68f, -608.455f, 2.094395f, "s", "Right")},
{ 59, new Location(-727.5f, -44f, -520f, -3.141593f, "s", "Right")},
{ 60, new Location(-764f, -40f, -516f, -1.570796f, "l", "Far Right Side")},
} }
};
}
}

View File

@@ -0,0 +1,145 @@
using Lumina.Excel.Sheets;
using HouseStealerPlugin.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using Dalamud.Bindings.ImGui;
namespace HouseStealerPlugin
{
public static class Utils
{
public static string GetExteriorPartDescriptor(ExteriorPartsType partsType)
{
return partsType switch
{
ExteriorPartsType.Roof => "Roof",
ExteriorPartsType.Walls => "Exterior Wall",
ExteriorPartsType.Windows => "Window",
ExteriorPartsType.Door => "Door",
ExteriorPartsType.RoofOpt => "Roof Decor",
ExteriorPartsType.WallOpt => "Exterior Wall Decor",
ExteriorPartsType.SignOpt => "Placard",
ExteriorPartsType.Fence => "Fence",
_ => "Unknown"
};
}
public static string GetInteriorPartDescriptor(InteriorPartsType partsType)
{
return partsType switch
{
InteriorPartsType.Walls => "Wall",
InteriorPartsType.Windows => "Window",
InteriorPartsType.Door => "Door",
InteriorPartsType.Floor => "Floor",
InteriorPartsType.Light => "Light",
_ => "Unknown"
};
}
public static string GetFloorDescriptor(InteriorFloor floor)
{
return floor switch
{
InteriorFloor.Ground => "Ground Floor",
InteriorFloor.Basement => "Basement",
InteriorFloor.Upstairs => "Upper Floor",
InteriorFloor.External => "Main",
_ => "Unknown"
};
}
public static float DistanceFromPlayer(HousingGameObject obj, Vector3 playerPos)
{
return Distance(new Vector3(playerPos.X, playerPos.Y, playerPos.Z), new Vector3(obj.X, obj.Y, obj.Z));
}
public static double FastDistance(Vector3 v1, Vector3 v2) // for comparison, when actual distance doesn't matter
{
var x1 = Math.Pow(v2.X - v1.X, 2);
var y1 = Math.Pow(v2.Y - v1.Y, 2);
var z1 = Math.Pow(v2.Z - v1.Z, 2);
return x1 + y1 + z1;
}
public static float Distance(Vector3 v1, Vector3 v2)
{
return (float)Math.Sqrt(FastDistance(v1, v2));
}
public static void StainButton(string id, Stain color, Vector2 size)
{
var floatColor = StainToVector4(color.Color);
ImGui.ColorButton($"##{id}", floatColor, ImGuiColorEditFlags.NoTooltip, size);
}
public static Vector4 StainToVector4(uint stainColor)
{
var s = 1.0f / 255.0f;
return new Vector4()
{
X = ((stainColor >> 16) & 0xFF) * s,
Y = ((stainColor >> 8) & 0xFF) * s,
Z = ((stainColor >> 0) & 0xFF) * s,
W = ((stainColor >> 24) & 0xFF) * s
};
}
public static HousingItem GetNearestHousingItem(IEnumerable<HousingItem> items, Vector3 position)
{
return items
.OrderBy(item => FastDistance(position, new Vector3(item.X, item.Y, item.Z)))
.FirstOrDefault();
}
public static void OpenLink(String address)
{
Dalamud.Utility.Util.OpenLink(address);
}
public static class Base64Url
{
public static string Encode(string text)
{
return Convert.ToBase64String(Encoding.UTF8.GetBytes(text)).TrimEnd('=').Replace('+', '-')
.Replace('/', '_');
}
public static string Decode(string text)
{
text = text.Replace('_', '/').Replace('-', '+');
switch (text.Length % 4)
{
case 2:
text += "==";
break;
case 3:
text += "=";
break;
}
return Encoding.UTF8.GetString(Convert.FromBase64String(text));
}
}
public static float radToDeg(float radians)
{
var degrees = Math.Round((radians/ Math.PI)* 180,3);
if (degrees == 0)
{
degrees = 0; // stop -0 from showing up.
}
if (degrees <= -180)
{
degrees = 180; // the other edge case
}
return (float)degrees;
}
}
}

View File

@@ -0,0 +1,41 @@
{
"version": 1,
"dependencies": {
"net9.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[13.0.0, )",
"resolved": "13.0.0",
"contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w=="
},
"DotNet.ReproducibleBuilds": {
"type": "Direct",
"requested": "[1.2.25, )",
"resolved": "1.2.25",
"contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg=="
},
"Lumina.Excel": {
"type": "Direct",
"requested": "[7.3.1, )",
"resolved": "7.3.1",
"contentHash": "NKjlWOi/hnBx6ZHV/VmDknIFQLuBmBoqdufDxawzoAsAwye2dibZdMYyz/gIezSRzAMbU0qP9h51uYo65eiWIQ==",
"dependencies": {
"Lumina": "6.3.0"
}
},
"Lumina": {
"type": "Transitive",
"resolved": "6.3.0",
"contentHash": "FQlZ16KhXtPGCEq1KeRTDZ14uq6qrvW2vYnNNlCpW7Xs1iLhF8AJsbYG8S0y1NnP7BRholPY8KGToVTsvmb8jw==",
"dependencies": {
"Microsoft.Extensions.ObjectPool": "8.0.7"
}
},
"Microsoft.Extensions.ObjectPool": {
"type": "Transitive",
"resolved": "8.0.7",
"contentHash": "2yLweyqmpuuFSRo+I3sLHMxmnAgNcI537kBJiyv49U2ZEqo00jZcG8lrnD8uCiOJp9IklYyTZULtbsXoFVzsjQ=="
}
}
}
}