diff --git a/Automaton/Features/ARCeruleum.cs b/Automaton/Features/ARCeruleum.cs index c09900e..4e16ce8 100644 --- a/Automaton/Features/ARCeruleum.cs +++ b/Automaton/Features/ARCeruleum.cs @@ -73,7 +73,7 @@ public override void DrawConfig() private unsafe void CheckCharacter() { - if (!P.UsingARPostProcess && InventoryManager.Instance()->GetInventoryItemCount(CeruleumTankId) <= 200 && CompanyWorkshopTerritories.Contains(Player.Territory)) + if (!P.UsingARPostProcess && P.AutoRetainerAPI.GetOfflineCharacterData(Player.CID).EnabledSubs.Count > 0 && InventoryManager.Instance()->GetInventoryItemCount(CeruleumTankId) <= 200 && CompanyWorkshopTerritories.Contains(Player.Territory)) { P.UsingARPostProcess = true; AutoRetainer.RequestCharacterPostprocess(); diff --git a/Automaton/Features/DebugTools.cs b/Automaton/Features/DebugTools.cs index f967825..0ef648b 100644 --- a/Automaton/Features/DebugTools.cs +++ b/Automaton/Features/DebugTools.cs @@ -1,11 +1,15 @@ -using Dalamud.Game.ClientState.Keys; +using Automaton.IPC; +using Automaton.UI; +using Dalamud.Game.ClientState.Keys; using Dalamud.Game.Gui.Toast; using ECommons; using ECommons.Interop; +using ECommons.SimpleGui; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.System.Framework; using ImGuiNET; +using Lumina.Excel.GeneratedSheets2; namespace Automaton.Features; @@ -57,6 +61,10 @@ private unsafe void OnSetup(AddonEvent type, AddonArgs args) private void OnTeleportClick(string command, string arguments) { tpActive ^= true; + if (tpActive) + EzConfigGui.WindowSystem.AddWindow(new MousePositionOverlay()); + else + EzConfigGui.RemoveWindow(); Svc.Toasts.ShowNormal($"TPClick {(tpActive ? "Enabled" : "Disabled")}", new ToastOptions() { Speed = ToastSpeed.Fast }); } @@ -88,16 +96,19 @@ private void OnNoClip(string command, string arguments) [CommandHandler(["/move", "/speed"], "Modify your movement speed", nameof(Config.EnableMoveSpeed))] private void OnMoveSpeed(string command, string arguments) => Player.Speed = float.TryParse(arguments, out var speed) ? speed : 1.0f; + public static bool ShowMouseOverlay; private bool IsLButtonPressed; private bool tpActive; private bool ncActive; private unsafe void OnUpdate(IFramework framework) { if (!Player.Available || Player.Occupied) return; + ShowMouseOverlay = false; if (Config.EnableTPClick && tpActive) { if (!Framework.Instance()->WindowInactive && IsKeyPressed([LimitedKeys.LeftControlKey, LimitedKeys.RightControlKey]) && Utils.IsClickingInGameWorld()) { + ShowMouseOverlay = true; var pos = ImGui.GetMousePos(); if (Svc.GameGui.ScreenToWorld(pos, out var res)) { diff --git a/Automaton/UI/DebugWindow.cs b/Automaton/UI/DebugWindow.cs index 0d79e89..7ca927c 100644 --- a/Automaton/UI/DebugWindow.cs +++ b/Automaton/UI/DebugWindow.cs @@ -1,13 +1,8 @@ -using Dalamud; using Dalamud.Interface.Windowing; -using ECommons; using FFXIVClientStructs.Attributes; -using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions; -using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; -using System.Diagnostics.Metrics; using System.Reflection; using System.Runtime.InteropServices; @@ -26,21 +21,27 @@ public DebugWindow() : base($"{Name} - Debug {P.GetType().Assembly.GetName().Ver public static void Dispose() { } - public unsafe override void Draw() + public override unsafe void Draw() { - for (var i = 0; i < RaptureAtkUnitManager.Instance()->FocusedUnitsList.Count; i++) + for (var i = 0; i < RaptureAtkUnitManager.Instance()->AllLoadedUnitsList.Count; i++) { - var atk = RaptureAtkUnitManager.Instance()->FocusedUnitsList.Entries[i].Value; - ImGui.TextUnformatted($"{i} {atk == null} {(atk != null ? atk->NameString : "")}"); - } - ImGui.TextUnformatted($"{RaptureAtkUnitManager.Instance()->FocusedUnitsList.Entries[^1].Value == null}"); - if (GenericHelpers.TryGetAddonByName("LookingForGroup", out var lfg)) - { - ImGui.TextUnformatted($"{RaptureAtkUnitManager.Instance()->FocusedUnitsList.Entries[^1].Value == lfg}"); + var atk = RaptureAtkUnitManager.Instance()->AllLoadedUnitsList.Entries[i].Value; + if (atk == null || (atk->Flags198 & 0b1100_0000) != 0 || atk->HostId != 0) continue; + ImGui.TextUnformatted($"special addon: {atk->NameString}"); } //for (var i = 0; i < RaptureAtkUnitManager.Instance()->FocusedUnitsList.Count; i++) //{ // var atk = RaptureAtkUnitManager.Instance()->FocusedUnitsList.Entries[i].Value; + // ImGui.TextUnformatted($"{i} {atk == null} {(atk != null ? atk->NameString : "")}"); + //} + //ImGui.TextUnformatted($"{RaptureAtkUnitManager.Instance()->FocusedUnitsList.Entries[^1].Value == null}"); + //if (TryGetAddonByName("LookingForGroup", out var lfg)) + //{ + // ImGui.TextUnformatted($"{RaptureAtkUnitManager.Instance()->FocusedUnitsList.Entries[^1].Value == lfg}"); + //} + //for (var i = 0; i < RaptureAtkUnitManager.Instance()->FocusedUnitsList.Count; i++) + //{ + // var atk = RaptureAtkUnitManager.Instance()->FocusedUnitsList.Entries[i].Value; // if (atk == null) continue; // var str = GetAddonStruct(atk); // if (str == null) continue; diff --git a/Automaton/UI/MousePositionOverlay.cs b/Automaton/UI/MousePositionOverlay.cs new file mode 100644 index 0000000..c4c48a3 --- /dev/null +++ b/Automaton/UI/MousePositionOverlay.cs @@ -0,0 +1,78 @@ +using Automaton.Features; +using Dalamud.Interface.Windowing; +using ECommons.ImGuiMethods; +using ECommons.Interop; +using ECommons.SimpleGui; +using ImGuiNET; + +namespace Automaton.UI; +public class MousePositionOverlay : Window +{ + public MousePositionOverlay() : base("Hyperborea Overlay", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.AlwaysUseWindowPadding, true) + { + Position = Vector2.Zero; + PositionCondition = ImGuiCond.Always; + Size = ImGuiHelpers.MainViewport.Size; + SizeCondition = ImGuiCond.Always; + EzConfigGui.WindowSystem.AddWindow(this); + IsOpen = true; + RespectCloseHotkey = false; + } + + public override void PreDraw() => ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); + public override void PostDraw() => ImGui.PopStyleVar(); + public override bool DrawConditions() => DebugTools.ShowMouseOverlay; + + public override void Draw() + { + var pos = ImGui.GetMousePos(); + if (Svc.GameGui.ScreenToWorld(pos, out var res)) + { + var col = GradientColor.Get(EColor.RedBright, EColor.YellowBright); + DrawRingWorld(res, 0.5f, col.ToUint(), 2f); + var l = MathF.Sqrt(2f) / 2f * 0.5f; + DrawLineWorld(res + new Vector3(-l, 0, -l), res + new Vector3(l, 0, l), col.ToUint(), 2f); + DrawLineWorld(res + new Vector3(l, 0, -l), res + new Vector3(-l, 0, l), col.ToUint(), 2f); + } + } + + void DrawLineWorld(Vector3 a, Vector3 b, uint color, float thickness) + { + var result = GetAdjustedLine(a, b); + if (result.posA == null) return; + ImGui.GetWindowDrawList().PathLineTo(new Vector2(result.posA.Value.X, result.posA.Value.Y)); + ImGui.GetWindowDrawList().PathLineTo(new Vector2(result.posB.Value.X, result.posB.Value.Y)); + ImGui.GetWindowDrawList().PathStroke(color, ImDrawFlags.None, thickness); + } + + (Vector2? posA, Vector2? posB) GetAdjustedLine(Vector3 pointA, Vector3 pointB) + { + var resultA = Svc.GameGui.WorldToScreen(pointA, out Vector2 posA); + var resultB = Svc.GameGui.WorldToScreen(pointB, out Vector2 posB); + //if (!resultA || !resultB) return default; + return (posA, posB); + } + + public void DrawRingWorld(Vector3 position, float radius, uint color, float thickness) + { + var segments = 50; + int seg = segments / 2; + Vector2?[] elements = new Vector2?[segments]; + for (int i = 0; i < segments; i++) + { + Svc.GameGui.WorldToScreen( + new Vector3(position.X + radius * (float)Math.Sin(Math.PI / seg * i), + position.Y, + position.Z + radius * (float)Math.Cos(Math.PI / seg * i) + ), + out Vector2 pos); + elements[i] = new Vector2(pos.X, pos.Y); + } + foreach (var pos in elements) + { + if (pos == null) continue; + ImGui.GetWindowDrawList().PathLineTo(pos.Value); + } + ImGui.GetWindowDrawList().PathStroke(color, ImDrawFlags.Closed, thickness); + } +} diff --git a/Automaton/Utilities/FocusWatcher.cs b/Automaton/Utilities/FocusWatcher.cs new file mode 100644 index 0000000..89e55a1 --- /dev/null +++ b/Automaton/Utilities/FocusWatcher.cs @@ -0,0 +1,41 @@ +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; +using FFXIVClientStructs.Interop; + +namespace Automaton.Utilities; +public unsafe class FocusWatcher : IDisposable +{ + public FocusWatcher() => Svc.Framework.Update += CheckAddonFocus; + + public static event Action> AddonFocusChanged; + public static void OnAddonFocusChange(Pointer atk) + { + LastFocusedAddon = atk != null ? atk.Value->NameString : string.Empty; + AddonFocusChanged?.Invoke(atk); + } + + private static string LastFocusedAddon = string.Empty; + private void CheckAddonFocus(IFramework framework) + { + var focus = AtkStage.Instance()->GetFocus(); + if (focus == null && LastFocusedAddon != string.Empty) + { + LastFocusedAddon = string.Empty; + Svc.Log.Info($"{nameof(LastFocusedAddon)} is now null"); + AddonFocusChanged?.Invoke(null); + return; + } + for (var i = 0; i < RaptureAtkUnitManager.Instance()->FocusedUnitsList.Count; i++) + { + var atk = RaptureAtkUnitManager.Instance()->FocusedUnitsList.Entries[i].Value; + if (atk != null && atk->RootNode == GetRootNode(focus) && atk->NameString != LastFocusedAddon) + { + LastFocusedAddon = atk->NameString; + Svc.Log.Info($"New addon focused {LastFocusedAddon}"); + AddonFocusChanged?.Invoke(atk); + } + } + } + + public void Dispose() => Svc.Framework.Update -= CheckAddonFocus; +} diff --git a/Automaton/Utilities/Inventory.cs b/Automaton/Utilities/Inventory.cs index de71961..3917107 100644 --- a/Automaton/Utilities/Inventory.cs +++ b/Automaton/Utilities/Inventory.cs @@ -99,4 +99,22 @@ public static unsafe List> GetDesynthableItems(IEnumerabl } return items; } + + public static unsafe uint GetEmptySlots(IEnumerable inventories = null) + { + if (inventories == null) + return InventoryManager.Instance()->GetEmptySlotsInBag(); + else + { + uint count = 0; + foreach (var inv in inventories) + { + var cont = InventoryManager.Instance()->GetInventoryContainer(inv); + for (var i = 0; i < cont->Size; ++i) + if (cont->GetInventorySlot(i)->ItemId == 0) + count++; + } + return count; + } + } } diff --git a/Automaton/Utilities/Memory.cs b/Automaton/Utilities/Memory.cs index e9a36a9..4edcbb4 100644 --- a/Automaton/Utilities/Memory.cs +++ b/Automaton/Utilities/Memory.cs @@ -1,5 +1,6 @@ using Dalamud.Game.Network.Structures; using Dalamud.Hooking; +using ECommons.Automation; using ECommons.EzHookManager; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Character; @@ -280,7 +281,7 @@ private unsafe nint IsFlightProhibitedDetour(nint a1) private byte ReturnDetour(AgentInterface* agent) { - if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 6) != 0) + if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 6) != 0 || GameMain.IsInPvPInstance()) return ReturnHook.Original(agent); ExecuteCommand(214); diff --git a/Automaton/Utilities/Player.cs b/Automaton/Utilities/Player.cs index 15a6d53..4b66e4d 100644 --- a/Automaton/Utilities/Player.cs +++ b/Automaton/Utilities/Player.cs @@ -17,7 +17,7 @@ namespace Automaton.Utilities; -public unsafe static class Player +public static unsafe class Player { public static IPlayerCharacter Object => Svc.ClientState.LocalPlayer; public static bool Available => Svc.ClientState.LocalPlayer != null; diff --git a/ECommons b/ECommons index b39bbc6..4011a66 160000 --- a/ECommons +++ b/ECommons @@ -1 +1 @@ -Subproject commit b39bbc68ff7ee632d3121388e6f4e4aad6753dcd +Subproject commit 4011a66cf53752ef7db0186fbe5ab2a1364fdd06 diff --git a/TODO.txt b/TODO.txt index 8518862..a8d73cb 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,9 +1,7 @@ Better targeting that works in pvp - ability to preview which target is next/prev auto hide helmet during cutscene -autoretainer companion tweaks - - auto turn in when inventory is at a certain level - - auto buy tanks at a certain level +relay helper: minion support Some sort of go to npc command that incorporates itemvendorlocation/lifestream/navmesh /tplast command to teleport to the last linked loc Enhanced login/logout, ability to do commands when those are triggered