commit dfb8cd7215526160812920f302025f8d2047cdda Author: Administrator Date: Sat Aug 16 20:46:04 2025 +0300 first commit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..29ca9d4 --- /dev/null +++ b/.github/workflows/release.yml @@ -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" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5e0cfc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs/ +obj/ +bin/ +*.user +.idea/ \ No newline at end of file diff --git a/HouseStealerPlugin.sln b/HouseStealerPlugin.sln new file mode 100644 index 0000000..4feaacf --- /dev/null +++ b/HouseStealerPlugin.sln @@ -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 diff --git a/HouseStealerPlugin/Configuration.cs b/HouseStealerPlugin/Configuration.cs new file mode 100644 index 0000000..5e70521 --- /dev/null +++ b/HouseStealerPlugin/Configuration.cs @@ -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 HiddenScreenItemHistory = new List(); + public List GroupingList = new List(); + + 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(); + } + + } +} diff --git a/HouseStealerPlugin/DalamudApi.cs b/HouseStealerPlugin/DalamudApi.cs new file mode 100644 index 0000000..3bfb01c --- /dev/null +++ b/HouseStealerPlugin/DalamudApi.cs @@ -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(); + + // [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; +} \ No newline at end of file diff --git a/HouseStealerPlugin/Gui/ConfigurationWindow.cs b/HouseStealerPlugin/Gui/ConfigurationWindow.cs new file mode 100644 index 0000000..3411859 --- /dev/null +++ b/HouseStealerPlugin/Gui/ConfigurationWindow.cs @@ -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 + { + + public Configuration Config => Plugin.Config; + + private string CustomTag = string.Empty; + private readonly Dictionary 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().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().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 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().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 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().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 + + + + + + } +} \ No newline at end of file diff --git a/HouseStealerPlugin/Gui/Window.cs b/HouseStealerPlugin/Gui/Window.cs new file mode 100644 index 0000000..2a24bc7 --- /dev/null +++ b/HouseStealerPlugin/Gui/Window.cs @@ -0,0 +1,44 @@ +using Dalamud.Plugin; + +namespace HouseStealerPlugin.Gui +{ + public abstract class Window 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(); + } +} \ No newline at end of file diff --git a/HouseStealerPlugin/HouseStealerPlugin.cs b/HouseStealerPlugin/HouseStealerPlugin.cs new file mode 100644 index 0000000..f48c2ab --- /dev/null +++ b/HouseStealerPlugin/HouseStealerPlugin.cs @@ -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 ItemsToPlace = new List(); + + private delegate bool UpdateLayoutDelegate(IntPtr a1); + private HookWrapper 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 SelectItemHook; + + public static SaveLayoutManager LayoutManager; + + public static bool logHousingDetour = false; + + internal static Location PlotLocation = new Location(); + + public Layout Layout = new Layout(); + public List InteriorItemList = new List(); + + + 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("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("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("48 89 74 24 18 57 48 83 ec 20 b8 dc 02 00 00 0f b7 f2 ??", UpdateYardObj); + + GetGameObjectHook = HookManager.Hook("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("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("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 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 GetGameObjectHook; + internal static HookWrapper 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 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().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 dObjects; + Memory.Instance.TryGetNameSortedHousingGameObjectList(out dObjects); + + InteriorItemList.Clear(); + + foreach (var gameObject in dObjects) + { + uint furnitureKey = gameObject.housingRowId; + + if (!DalamudApi.DataManager.GetExcelSheet().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(); + 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); + } + + } + +} diff --git a/HouseStealerPlugin/HouseStealerPlugin.csproj b/HouseStealerPlugin/HouseStealerPlugin.csproj new file mode 100644 index 0000000..2e954ad --- /dev/null +++ b/HouseStealerPlugin/HouseStealerPlugin.csproj @@ -0,0 +1,66 @@ + + + + 1.0.0 + net9.0-windows + latest + HouseStealerPlugin + HouseStealerPlugin + $(PluginVersion) + $(PluginVersion) + $(PluginVersion) + ..\bin\ + true + false + false + true + + + + full + DEBUG;TRACE + + + + full + DEBUG;TRACE + + + + pdbonly + + + + pdbonly + + + + + + + + + + + $(DalamudLibPath)Newtonsoft.Json.dll + False + + + $(DalamudLibPath)Dalamud.dll + False + + + $(DalamudLibPath)FFXIVClientStructs.dll + False + + + $(DalamudLibPath)Lumina.dll + false + + + $(DalamudLibPath)Lumina.Excel.dll + false + + + + \ No newline at end of file diff --git a/HouseStealerPlugin/HouseStealerPlugin.json b/HouseStealerPlugin/HouseStealerPlugin.json new file mode 100644 index 0000000..60a9490 --- /dev/null +++ b/HouseStealerPlugin/HouseStealerPlugin.json @@ -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" +} diff --git a/HouseStealerPlugin/HousingData.cs b/HouseStealerPlugin/HousingData.cs new file mode 100644 index 0000000..efd841b --- /dev/null +++ b/HouseStealerPlugin/HousingData.cs @@ -0,0 +1,133 @@ +using Lumina.Excel.Sheets; +using System.Collections.Generic; +using System.Linq; + + +namespace HouseStealerPlugin +{ + public class HousingData + { + private readonly Dictionary _furnitureDict; + private readonly Dictionary _itemDict; + private readonly Dictionary _stainDict; + + private readonly Dictionary _unitedDict; + + private readonly Dictionary _wallpaper; + private readonly Dictionary _smallFishprint; + private readonly Dictionary _mediumFishprint; + private readonly Dictionary _largeFishprint; + private readonly Dictionary _extraLargeFishprint; + private readonly Dictionary _painting; + + + private static HouseStealerPlugin Plugin; + + private HousingData() + { + var sheet = DalamudApi.DataManager.GetExcelSheet(); + uint[] terriKeys = { 339, 340, 341, 641 }; + + var unitedExteriorSheet = DalamudApi.DataManager.GetExcelSheet(); + + _unitedDict = new Dictionary(); + foreach (var row in unitedExteriorSheet) + foreach (var type in unitedExteriorSheet.Columns) + _unitedDict[type.Offset] = row.RowId; + + + _itemDict = DalamudApi.DataManager.GetExcelSheet() + .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().ToDictionary(row => row.RowId, row => row); + _furnitureDict = DalamudApi.DataManager.GetExcelSheet().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(); + _smallFishprint = new Dictionary(); + _mediumFishprint = new Dictionary(); + _largeFishprint = new Dictionary(); + _extraLargeFishprint = new Dictionary(); + _painting = new Dictionary(); + + var materialSheet = DalamudApi.DataManager.GetExcelSheet(); + + 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); + + } + } +} \ No newline at end of file diff --git a/HouseStealerPlugin/Memory.cs b/HouseStealerPlugin/Memory.cs new file mode 100644 index 0000000..298f63d --- /dev/null +++ b/HouseStealerPlugin/Memory.cs @@ -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().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 objects) + { + objects = null; + if (HousingModule == null || + HousingModule->GetCurrentManager() == null || + HousingModule->GetCurrentManager()->Objects == null) + return false; + + objects = new List(); + + for (var i = 0; i < 400; i++) + { + var oPtr = HousingModule->GetCurrentManager()->Objects[i]; + if (oPtr == 0) + continue; + + var o = *(HousingGameObject*)oPtr; + + objects.Add(o); + } + + 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().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; + } + + } +} \ No newline at end of file diff --git a/HouseStealerPlugin/Objects/HousingItem.cs b/HouseStealerPlugin/Objects/HousingItem.cs new file mode 100644 index 0000000..fba44c3 --- /dev/null +++ b/HouseStealerPlugin/Objects/HousingItem.cs @@ -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); + } + } +} diff --git a/HouseStealerPlugin/Objects/Structs.cs b/HouseStealerPlugin/Objects/Structs.cs new file mode 100644 index 0000000..7a34b47 --- /dev/null +++ b/HouseStealerPlugin/Objects/Structs.cs @@ -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.ToPointer()); + // return Marshal.PtrToStructure(_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]; + } + +} \ No newline at end of file diff --git a/HouseStealerPlugin/PluginUi.cs b/HouseStealerPlugin/PluginUi.cs new file mode 100644 index 0000000..d934ab6 --- /dev/null +++ b/HouseStealerPlugin/PluginUi.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/HouseStealerPlugin/SaveLayoutManager.cs b/HouseStealerPlugin/SaveLayoutManager.cs new file mode 100644 index 0000000..4f8149b --- /dev/null +++ b/HouseStealerPlugin/SaveLayoutManager.cs @@ -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 location { get; set; } = new List { 0, 0, 0 }; + public List rotation { get; set; } = new List { 0, 0, 0, 1 }; + public List scale { get; set; } = new List { 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 properties { get; set; } = new Dictionary(); + + public List attachments { get; set; } = new List(); + + 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(); + } + + } + + 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 interiorFixture { get; set; } = new List(); + + public List interiorFurniture { get; set; } = new List(); + + } + + public class ObjectToInferredTypesConverter : JsonConverter + { + 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 RotationToQuat(float rotation) + { + Quaternion q = Quaternion.CreateFromYawPitchRoll(0, 0, rotation); + + return new List { 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().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 furnitureList, List 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 { 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().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"); + } + + } +} diff --git a/HouseStealerPlugin/Util/HookManager.cs b/HouseStealerPlugin/Util/HookManager.cs new file mode 100644 index 0000000..2f05fea --- /dev/null +++ b/HouseStealerPlugin/Util/HookManager.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace HouseStealerPlugin +{ + public class HookManager + { + public static List 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 Hook(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 HookAddress(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(h); + if (enable) wh.Enable(); + HookList.Add(wh); + return wh; + } + + } +} diff --git a/HouseStealerPlugin/Util/HookWrapper.cs b/HouseStealerPlugin/Util/HookWrapper.cs new file mode 100644 index 0000000..acdfe39 --- /dev/null +++ b/HouseStealerPlugin/Util/HookWrapper.cs @@ -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 : IHookWrapper where T : Delegate + { + private bool disposed; + + private readonly Hook wrappedHook; + + public HookWrapper(Hook 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; + + + } +} \ No newline at end of file diff --git a/HouseStealerPlugin/Util/MoveUtil.cs b/HouseStealerPlugin/Util/MoveUtil.cs new file mode 100644 index 0000000..cc3f056 --- /dev/null +++ b/HouseStealerPlugin/Util/MoveUtil.cs @@ -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; + + } + } +} \ No newline at end of file diff --git a/HouseStealerPlugin/Util/Plots.cs b/HouseStealerPlugin/Util/Plots.cs new file mode 100644 index 0000000..fdb2a6f --- /dev/null +++ b/HouseStealerPlugin/Util/Plots.cs @@ -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> Map = new Dictionary> { + {"r1h1", new Dictionary{ + { 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{ + { 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{ + { 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{ + { 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{ + { 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")}, + } } + }; + + } +} diff --git a/HouseStealerPlugin/Util/Utils.cs b/HouseStealerPlugin/Util/Utils.cs new file mode 100644 index 0000000..fb1f44e --- /dev/null +++ b/HouseStealerPlugin/Util/Utils.cs @@ -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 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; + } + } +} \ No newline at end of file diff --git a/HouseStealerPlugin/packages.lock.json b/HouseStealerPlugin/packages.lock.json new file mode 100644 index 0000000..7db725e --- /dev/null +++ b/HouseStealerPlugin/packages.lock.json @@ -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==" + } + } + } +} \ No newline at end of file