diff --git a/HuntBuddy/Configuration.cs b/HuntBuddy/Configuration.cs index de27846..6a046c3 100644 --- a/HuntBuddy/Configuration.cs +++ b/HuntBuddy/Configuration.cs @@ -21,7 +21,7 @@ public class Configuration : IPluginConfiguration public void Save() { - Plugin.PluginInterface.SavePluginConfig(this); + Service.PluginInterface.SavePluginConfig(this); } } } \ No newline at end of file diff --git a/HuntBuddy/Interface.cs b/HuntBuddy/Interface.cs deleted file mode 100644 index c6ec4f7..0000000 --- a/HuntBuddy/Interface.cs +++ /dev/null @@ -1,423 +0,0 @@ -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; -using Dalamud.Interface; -using ImGuiNET; - -namespace HuntBuddy -{ - public class Interface - { - private readonly Plugin plugin; - - public bool DrawInterface; - private bool drawConfigurationInterface; - - public Interface(Plugin plugin) - { - this.plugin = plugin; - } - - public unsafe bool Draw() - { - var draw = true; - - ImGui.SetNextWindowSize(new Vector2(400 * ImGui.GetIO().FontGlobalScale, 500), ImGuiCond.Once); - - var windowFlags = ImGuiWindowFlags.NoDocking; - - if (this.plugin.Configuration.LockWindowPositions) - { - windowFlags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; - } - - if (!ImGui.Begin($"{this.plugin.Name}", ref draw, windowFlags)) - { - return draw; - } - - if (!this.plugin.MobHuntEntriesReady) - { - ImGui.Text("Reloading data ..."); - ImGui.End(); - return draw; - } - - if (Interface.IconButton(FontAwesomeIcon.Redo, "Reload")) - { - ImGui.End(); - this.plugin.MobHuntEntriesReady = false; - Task.Run(() => this.plugin.ReloadData()); - return draw; - } - - if (ImGui.IsItemHovered()) - { - ImGui.BeginTooltip(); - ImGui.Text("Click this button to reload daily hunt data"); - ImGui.EndTooltip(); - } - - ImGui.SameLine(); - - if (Interface.IconButton(FontAwesomeIcon.Cog, "Config")) - { - this.drawConfigurationInterface = !this.drawConfigurationInterface; - } - - foreach (var expansionEntry in this.plugin.MobHuntEntries.Where( - expansionEntry => - ImGui.TreeNode(expansionEntry.Key))) - { - foreach (var entry in expansionEntry.Value.Where( - entry => - { - var treeOpen = ImGui.TreeNodeEx(entry.Key.Value, ImGuiTreeNodeFlags.AllowItemOverlap); - ImGui.SameLine(); - var killedCount = entry.Value.Count( - x => - this.plugin.MobHuntStruct->CurrentKills[x.CurrentKillsOffset] == - x.NeededKills); - - if (killedCount != entry.Value.Count) - { - ImGui.Text($"({killedCount}/{entry.Value.Count})"); - } - else - { - ImGui.TextColored( - new Vector4(0f, 1f, 0f, 1f), - $"({killedCount}/{entry.Value.Count})"); - } - - return treeOpen; - })) - { - //ImGui.Indent(); - foreach (var mobHuntEntry in entry.Value) - { - if (Location.Database.ContainsKey(mobHuntEntry.MobHuntId)) - { - if (Interface.IconButton(FontAwesomeIcon.MapMarkerAlt, $"pin##{mobHuntEntry.MobHuntId}")) - { - Location.CreateMapMarker( - mobHuntEntry.TerritoryType, - mobHuntEntry.MapId, - mobHuntEntry.MobHuntId, - mobHuntEntry.Name, - Location.OpenType.None); - } - - if (ImGui.IsItemHovered()) - { - ImGui.BeginTooltip(); - ImGui.Text("Place marker on the map"); - ImGui.EndTooltip(); - } - - ImGui.SameLine(); - - if (Interface.IconButton(FontAwesomeIcon.MapMarkedAlt, $"open##{mobHuntEntry.MobHuntId}")) - { - var includeArea = this.plugin.Configuration.IncludeAreaOnMap; - if (ImGui.IsKeyDown(ImGuiKey.ModShift)) - { - includeArea = !includeArea; - } - Location.CreateMapMarker( - mobHuntEntry.TerritoryType, - mobHuntEntry.MapId, - mobHuntEntry.MobHuntId, - mobHuntEntry.Name, - includeArea ? Location.OpenType.ShowOpen : Location.OpenType.MarkerOpen); - } - - if (ImGui.IsItemHovered()) - { - var color = ImGui.IsKeyDown(ImGuiKey.ModShift) ? new Vector4(0f, 0.7f, 0f, 1f) : new Vector4(0.7f, 0.7f, 0.7f, 1f); - ImGui.BeginTooltip(); - if (this.plugin.Configuration.IncludeAreaOnMap) - { - ImGui.Text("Show hunt area on the map"); - ImGui.TextColored( - color, - "Hold [SHIFT] to show the location only"); - } - else - { - ImGui.Text("Show hunt location on the map"); - ImGui.TextColored( - color, - "Hold [SHIFT] to include the area"); - } - ImGui.EndTooltip(); - } - - ImGui.SameLine(); - - if (Plugin.TeleportConsumer?.IsAvailable == true) - { - if (Interface.IconButton(FontAwesomeIcon.StreetView, $"t##{mobHuntEntry.MobHuntId}")) - { - Location.TeleportToNearestAetheryte( - mobHuntEntry.TerritoryType, - mobHuntEntry.MapId, - mobHuntEntry.MobHuntId); - } - - if (ImGui.IsItemHovered()) - { - ImGui.BeginTooltip(); - ImGui.Text("Teleport to nearest aetheryte"); - ImGui.EndTooltip(); - } - - ImGui.SameLine(); - } - } - - var currentKills = this.plugin.MobHuntStruct->CurrentKills[mobHuntEntry.CurrentKillsOffset]; - ImGui.Text(mobHuntEntry.Name); - if (ImGui.IsItemHovered()) - { - ImGui.PushStyleColor(ImGuiCol.PopupBg, Vector4.Zero); - ImGui.BeginTooltip(); - this.DrawHuntIcon(mobHuntEntry); - ImGui.PopStyleColor(); - ImGui.EndTooltip(); - } - - ImGui.SameLine(); - if (currentKills != mobHuntEntry.NeededKills) - { - ImGui.Text($"({currentKills}/{mobHuntEntry.NeededKills})"); - } - else - { - ImGui.TextColored( - new Vector4(0f, 1f, 0f, 1f), - $"({currentKills}/{mobHuntEntry.NeededKills})"); - } - } - - //ImGui.Unindent(); - ImGui.TreePop(); - } - - ImGui.TreePop(); - } - - ImGui.End(); - - if (this.drawConfigurationInterface) - { - this.DrawConfiguration(); - } - - return draw; - } - - public unsafe void DrawLocalHunts() - { - if (!this.plugin.Configuration.ShowLocalHunts || - this.plugin.CurrentAreaMobHuntEntries.IsEmpty || - this.plugin.CurrentAreaMobHuntEntries.Count( - x => - this.plugin.MobHuntStruct->CurrentKills[x.CurrentKillsOffset] == x.NeededKills) == - this.plugin.CurrentAreaMobHuntEntries.Count) - { - return; - } - - ImGui.SetNextWindowSize(Vector2.Zero, ImGuiCond.Always); - - var windowFlags = ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.NoDocking; - - if (this.plugin.Configuration.HideLocalHuntBackground) - { - windowFlags |= ImGuiWindowFlags.NoBackground; - } - - if (this.plugin.Configuration.LockWindowPositions) - { - windowFlags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; - } - - if (!ImGui.Begin("Hunts in current area", windowFlags)) - { - return; - } - - foreach (var mobHuntEntry in this.plugin.CurrentAreaMobHuntEntries) - { - var currentKills = this.plugin.MobHuntStruct->CurrentKills[mobHuntEntry.CurrentKillsOffset]; - - if (this.plugin.Configuration.HideCompletedHunts && currentKills == mobHuntEntry.NeededKills) - { - continue; - } - - if (Location.Database.ContainsKey(mobHuntEntry.MobHuntId)) - { - if (Interface.IconButton(FontAwesomeIcon.MapMarkerAlt, $"pin##{mobHuntEntry.MobHuntId}")) - { - Location.CreateMapMarker( - mobHuntEntry.TerritoryType, - mobHuntEntry.MapId, - mobHuntEntry.MobHuntId, - mobHuntEntry.Name, - Location.OpenType.None); - } - - if (ImGui.IsItemHovered()) - { - ImGui.BeginTooltip(); - ImGui.Text("Place marker on the map"); - ImGui.EndTooltip(); - } - - ImGui.SameLine(); - - if (Interface.IconButton(FontAwesomeIcon.MapMarkedAlt, $"open##{mobHuntEntry.MobHuntId}")) - { - var includeArea = this.plugin.Configuration.IncludeAreaOnMap; - if (ImGui.IsKeyDown(ImGuiKey.ModShift)) - { - includeArea = !includeArea; - } - Location.CreateMapMarker( - mobHuntEntry.TerritoryType, - mobHuntEntry.MapId, - mobHuntEntry.MobHuntId, - mobHuntEntry.Name, - includeArea ? Location.OpenType.ShowOpen : Location.OpenType.MarkerOpen); - } - - if (ImGui.IsItemHovered()) - { - var color = ImGui.IsKeyDown(ImGuiKey.ModShift) ? new Vector4(0f, 0.7f, 0f, 1f) : new Vector4(0.7f, 0.7f, 0.7f, 1f); - ImGui.BeginTooltip(); - if (this.plugin.Configuration.IncludeAreaOnMap) - { - ImGui.Text("Show hunt area on the map"); - ImGui.TextColored( - color, - "Hold [SHIFT] to show the location only"); - } - else - { - ImGui.Text("Show hunt location on the map"); - ImGui.TextColored( - color, - "Hold [SHIFT] to include the area"); - } - ImGui.EndTooltip(); - } - - ImGui.SameLine(); - } - - ImGui.Text($"{mobHuntEntry.Name} ({currentKills}/{mobHuntEntry.NeededKills})"); - - if (!this.plugin.Configuration.ShowLocalHuntIcons) - { - continue; - } - - this.DrawHuntIcon(mobHuntEntry); - } - - ImGui.End(); - } - - private void DrawConfiguration() - { - ImGui.SetNextWindowSize(Vector2.Zero, ImGuiCond.Always); - - var windowFlags = ImGuiWindowFlags.NoDocking; - - if (this.plugin.Configuration.LockWindowPositions) - { - windowFlags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; - } - - if (!ImGui.Begin($"{this.plugin.Name} settings", windowFlags)) - { - return; - } - - var save = false; - - save |= ImGui.Checkbox("Lock plugin window positions", ref this.plugin.Configuration.LockWindowPositions); - save |= ImGui.Checkbox("Include hunt area on map by default", ref this.plugin.Configuration.IncludeAreaOnMap); - save |= ImGui.Checkbox("Show hunts in local area", ref this.plugin.Configuration.ShowLocalHunts); - save |= ImGui.Checkbox( - "Show icons of hunts in local area", - ref this.plugin.Configuration.ShowLocalHuntIcons); - save |= ImGui.Checkbox( - "Hide background of local hunts window", - ref this.plugin.Configuration.HideLocalHuntBackground); - save |= ImGui.Checkbox( - "Hide completed targets in local hunts window", - ref this.plugin.Configuration.HideCompletedHunts); - save |= ImGui.SliderFloat("Hunt icon scale", ref this.plugin.Configuration.IconScale, 0.2f, 2f, "%.2f"); - if (ImGui.ColorEdit4("Hunt icon background colour", ref this.plugin.Configuration.IconBackgroundColour)) - { - this.plugin.Configuration.IconBackgroundColourU32 = - ImGui.ColorConvertFloat4ToU32(this.plugin.Configuration.IconBackgroundColour); - save = true; - } - - if (save) - { - this.plugin.Configuration.Save(); - } - - ImGui.End(); - } - - private static bool IconButton(FontAwesomeIcon icon, string? id = null) - { - ImGui.PushFont(UiBuilder.IconFont); - - var text = icon.ToIconString(); - if (id != null) - { - text += $"##{id}"; - } - - var result = ImGui.Button(text); - - ImGui.PopFont(); - - return result; - } - - private void DrawHuntIcon(MobHuntEntry mobHuntEntry) - { - var cursorPos = ImGui.GetCursorScreenPos(); - var imageSize = mobHuntEntry.ExpansionId < 3 ? new Vector2(192f, 128f) : new Vector2(210f); - imageSize *= ImGui.GetIO().FontGlobalScale * this.plugin.Configuration.IconScale; - - ImGui.InvisibleButton("canvas", imageSize); - - var drawList = ImGui.GetWindowDrawList(); - if (mobHuntEntry is { ExpansionId: 4, IsEliteMark: false }) // Endwalker uses circle for non elite mobs - { - drawList.AddCircleFilled( - cursorPos + (imageSize / 2f), - imageSize.X / 2f, - this.plugin.Configuration.IconBackgroundColourU32); - } - else - { - drawList.AddRectFilled( - cursorPos, - cursorPos + imageSize, - this.plugin.Configuration.IconBackgroundColourU32); - } - - drawList.AddImage(mobHuntEntry.Icon.ImGuiHandle, cursorPos, cursorPos + imageSize); - } - } -} \ No newline at end of file diff --git a/HuntBuddy/Ipc/TeleportConsumer.cs b/HuntBuddy/Ipc/TeleportConsumer.cs index 782b06c..c346c58 100644 --- a/HuntBuddy/Ipc/TeleportConsumer.cs +++ b/HuntBuddy/Ipc/TeleportConsumer.cs @@ -1,5 +1,4 @@ using System; -using Dalamud.Logging; using Dalamud.Plugin.Ipc; namespace HuntBuddy.Ipc @@ -40,12 +39,12 @@ private void Subscribe() { try { - this.consumerTeleport = Plugin.PluginInterface.GetIpcSubscriber("Teleport"); - this.consumerMessageSetting = Plugin.PluginInterface.GetIpcSubscriber("Teleport.ChatMessage"); + this.consumerTeleport = Service.PluginInterface.GetIpcSubscriber("Teleport"); + this.consumerMessageSetting = Service.PluginInterface.GetIpcSubscriber("Teleport.ChatMessage"); } catch (Exception ex) { - Plugin.PluginLog.Debug($"Failed to subscribe to Teleporter\nReason: {ex}"); + Service.PluginLog.Debug($"Failed to subscribe to Teleporter\nReason: {ex}"); } } @@ -59,7 +58,7 @@ public bool Teleport(uint aetheryteId) } catch { - Plugin.Chat.PrintError("Teleporter plugin is not responding"); + Service.Chat.PrintError("Teleporter plugin is not responding"); return false; } } diff --git a/HuntBuddy/Location.cs b/HuntBuddy/Location.cs index 9d7030d..ed4e190 100644 --- a/HuntBuddy/Location.cs +++ b/HuntBuddy/Location.cs @@ -493,7 +493,7 @@ public static unsafe void CreateMapMarker(uint territoryType, uint mapId, uint m private static (int X, int Y) MapToWorldCoordinates(Vector2 pos, uint mapId) { - var scale = Plugin.DataManager.GetExcelSheet()?.GetRow(mapId)?.SizeFactor ?? 100; + var scale = Service.DataManager.GetExcelSheet()?.GetRow(mapId)?.SizeFactor ?? 100; var num = scale / 100f; var x = (float)(((pos.X - 1.0) * num / 41.0 * 2048.0) - 1024.0) / num * 1000f; var y = (float)(((pos.Y - 1.0) * num / 41.0 * 2048.0) - 1024.0) / num * 1000f; @@ -519,14 +519,14 @@ private static float ConvertRawPositionToMapCoordinate(int pos, float scale) public static void TeleportToNearestAetheryte(uint territoryType, uint mapId, uint mobHuntId) { - var mapRow = Plugin.DataManager.Excel.GetSheet()?.GetRow(mapId); + var mapRow = Service.DataManager.Excel.GetSheet()?.GetRow(mapId); if (mapRow == null) { return; } - var nearestAetheryteId = Plugin.DataManager.Excel.GetSheet() + var nearestAetheryteId = Service.DataManager.Excel.GetSheet() ?.Where(x => x.DataType == 3 && x.RowId == mapRow.MapMarkerRange) .Select( x => new @@ -542,7 +542,7 @@ public static void TeleportToNearestAetheryte(uint territoryType, uint mapId, ui var nearestAetheryte = territoryType == 399 // Support the unique case of aetheryte not being in the same map ? mapRow.TerritoryType?.Value?.Aetheryte.Value - : Plugin.DataManager.Excel.GetSheet()?.FirstOrDefault( + : Service.DataManager.Excel.GetSheet()?.FirstOrDefault( x => x.IsAetheryte && x.Territory.Row == territoryType && x.RowId == nearestAetheryteId); diff --git a/HuntBuddy/Plugin.cs b/HuntBuddy/Plugin.cs index 3ee2b7c..24dd885 100644 --- a/HuntBuddy/Plugin.cs +++ b/HuntBuddy/Plugin.cs @@ -5,10 +5,8 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; -using Dalamud.Game; using Dalamud.Interface.Internal; -using Dalamud.IoC; -using Dalamud.Logging; +using Dalamud.Interface.Windowing; using Dalamud.Plugin; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -16,6 +14,7 @@ using HuntBuddy.Attributes; using HuntBuddy.Ipc; using HuntBuddy.Structs; +using HuntBuddy.Windows; using ImGuiNET; using Lumina.Extensions; @@ -25,26 +24,7 @@ public class Plugin : IDalamudPlugin { public string Name => "Hunt Buddy"; - [PluginService] public static DalamudPluginInterface PluginInterface { get; set; } = null!; - - [PluginService] public static ICommandManager Commands { get; set; } = null!; - - [PluginService] public static IChatGui Chat { get; set; } = null!; - - [PluginService] public static IDataManager DataManager { get; set; } = null!; - - [PluginService] public static ISigScanner SigScanner { get; set; } = null!; - - [PluginService] public static IGameGui GameGui { get; set; } = null!; - - [PluginService] public static IClientState ClientState { get; set; } = null!; - - [PluginService] public static IFramework Framework { get; set; } = null!; - - [PluginService] public static IPluginLog PluginLog { get; set; } = null!; - private readonly PluginCommandManager commandManager; - private readonly Interface pluginInterface; private ObtainedBillEnum lastState; // Dictionary, List>> public readonly Dictionary, List>> MobHuntEntries; @@ -53,30 +33,47 @@ public class Plugin : IDalamudPlugin public readonly unsafe MobHuntStruct* MobHuntStruct; public readonly Configuration Configuration; public static TeleportConsumer? TeleportConsumer; + + private WindowSystem WindowSystem { get; } + + private MainWindow MainWindow { get; } + private ConfigurationWindow ConfigurationWindow { get; } - public Plugin() + public static Plugin Instance { get; internal set; } = null!; + + public Plugin(DalamudPluginInterface pluginInterface) { - this.commandManager = new PluginCommandManager(this, Commands); - this.pluginInterface = new Interface(this); + Instance = this; + + pluginInterface.Create(); + + this.commandManager = new PluginCommandManager(this, Service.Commands); this.MobHuntEntries = new Dictionary, List>>(); this.CurrentAreaMobHuntEntries = new ConcurrentBag(); - this.Configuration = (Configuration)(PluginInterface.GetPluginConfig() ?? new Configuration()); + this.Configuration = (Configuration)(Service.PluginInterface.GetPluginConfig() ?? new Configuration()); this.Configuration.IconBackgroundColourU32 = ImGui.ColorConvertFloat4ToU32(this.Configuration.IconBackgroundColour); unsafe { this.MobHuntStruct = - (MobHuntStruct*)SigScanner.GetStaticAddressFromSig( + (MobHuntStruct*)Service.SigScanner.GetStaticAddressFromSig( "48 8D 0D ?? ?? ?? ?? 8B D8 0F B6 52"); } + + this.MainWindow = new MainWindow(); + this.ConfigurationWindow = new ConfigurationWindow(); + + this.WindowSystem = new WindowSystem("HuntBuddy"); + this.WindowSystem.AddWindow(this.MainWindow); + this.WindowSystem.AddWindow(new LocalHuntsWindow()); + this.WindowSystem.AddWindow(this.ConfigurationWindow); Plugin.TeleportConsumer = new TeleportConsumer(); - Plugin.ClientState.TerritoryChanged += this.ClientStateOnTerritoryChanged; - Plugin.PluginInterface.UiBuilder.Draw += this.DrawInterface; - Plugin.PluginInterface.UiBuilder.Draw += this.pluginInterface.DrawLocalHunts; - Plugin.PluginInterface.UiBuilder.OpenConfigUi += this.OpenConfigUi; - Plugin.Framework.Update += this.FrameworkOnUpdate; + Service.ClientState.TerritoryChanged += this.ClientStateOnTerritoryChanged; + Service.PluginInterface.UiBuilder.Draw += this.WindowSystem.Draw; + Service.PluginInterface.UiBuilder.OpenConfigUi += this.OpenConfigUi; + Service.Framework.Update += this.FrameworkOnUpdate; } private unsafe void FrameworkOnUpdate(IFramework framework) @@ -96,21 +93,21 @@ private void ClientStateOnTerritoryChanged(ushort e) foreach (var mobHuntEntry in this.MobHuntEntries.SelectMany( expansionEntry => expansionEntry.Value - .Where(entry => entry.Key.Key == Plugin.ClientState.TerritoryType) + .Where(entry => entry.Key.Key == Service.ClientState.TerritoryType) .SelectMany(entry => entry.Value))) { this.CurrentAreaMobHuntEntries.Add(mobHuntEntry); } } - private void OpenConfigUi() + private void DrawInterface() { - this.pluginInterface.DrawInterface = !this.pluginInterface.DrawInterface; + this.MainWindow.Toggle(); } - private void DrawInterface() + public void OpenConfigUi() { - this.pluginInterface.DrawInterface = this.pluginInterface.DrawInterface && this.pluginInterface.Draw(); + this.ConfigurationWindow.Toggle(); } private void Dispose(bool disposing) @@ -121,11 +118,12 @@ private void Dispose(bool disposing) } this.MobHuntEntriesReady = false; - Plugin.ClientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; - Plugin.Framework.Update -= this.FrameworkOnUpdate; - Plugin.PluginInterface.UiBuilder.Draw -= this.DrawInterface; - Plugin.PluginInterface.UiBuilder.Draw -= this.pluginInterface.DrawLocalHunts; - Plugin.PluginInterface.UiBuilder.OpenConfigUi -= this.OpenConfigUi; + Service.ClientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; + Service.Framework.Update -= this.FrameworkOnUpdate; + Service.PluginInterface.UiBuilder.Draw -= this.WindowSystem.Draw; + Service.PluginInterface.UiBuilder.OpenConfigUi -= this.OpenConfigUi; + + this.WindowSystem.RemoveAllWindows(); this.commandManager.Dispose(); } @@ -150,8 +148,8 @@ public unsafe void PluginCommand(string command, string args) { var filterPredicate = (MobHuntEntry entry) => entry.IsEliteMark || this.MobHuntStruct->CurrentKills[entry.CurrentKillsOffset] < entry.NeededKills; var openType = Location.OpenType.None; - var playerLocation = Plugin.ClientState.LocalPlayer!.Position; - var map = Plugin.DataManager.GetExcelSheet()!.GetRow(Plugin.ClientState.TerritoryType)!.Map!.Value!; + var playerLocation = Service.ClientState.LocalPlayer!.Position; + var map = Service.DataManager.GetExcelSheet()!.GetRow(Service.ClientState.TerritoryType)!.Map!.Value!; var playerVec2 = MapUtil.WorldToMap(new Vector2(playerLocation.X, playerLocation.Z), map); var chosen = this.CurrentAreaMobHuntEntries .Where(filterPredicate) @@ -159,10 +157,10 @@ public unsafe void PluginCommand(string command, string args) .FirstOrDefault(); if (chosen == null) { - PluginLog.Information("No marks in current zone, looking in current expansion"); + Service.PluginLog.Information("No marks in current zone, looking in current expansion"); openType = this.Configuration.IncludeAreaOnMap ? Location.OpenType.ShowOpen : Location.OpenType.MarkerOpen; - var expansion = Plugin.DataManager.Excel.GetSheet()!.GetRow(Plugin.ClientState.TerritoryType)!.ExVersion.Value!.Name; - PluginLog.Information($"Player is in a zone from {expansion}; known expansions are {string.Join(", ", this.MobHuntEntries.Keys)}"); + var expansion = Service.DataManager.Excel.GetSheet()!.GetRow(Service.ClientState.TerritoryType)!.ExVersion.Value!.Name; + Service.PluginLog.Information($"Player is in a zone from {expansion}; known expansions are {string.Join(", ", this.MobHuntEntries.Keys)}"); var candidates = this.MobHuntEntries.ContainsKey(expansion) ? this.MobHuntEntries[expansion] .Values @@ -173,7 +171,7 @@ public unsafe void PluginCommand(string command, string args) // if we didn't find any candidates, we try a different method to fill it if (candidates.Count == 0) { - PluginLog.Information("Nothing available in current expansion, looking globally"); + Service.PluginLog.Information("Nothing available in current expansion, looking globally"); candidates = this.MobHuntEntries.Values .SelectMany(dict => dict.Values) @@ -186,7 +184,7 @@ public unsafe void PluginCommand(string command, string args) // but this block must ALWAYS run, regardless if (candidates.Count >= 1) { - PluginLog.Information($"Found {candidates.Count}"); + Service.PluginLog.Information($"Found {candidates.Count}"); chosen = candidates[new Random().Next(candidates.Count)]; } } @@ -194,12 +192,12 @@ public unsafe void PluginCommand(string command, string args) { if (chosen.IsEliteMark) { - Chat.Print($"Hunting elite mark {chosen.Name} in {chosen.TerritoryName}"); + Service.Chat.Print($"Hunting elite mark {chosen.Name} in {chosen.TerritoryName}"); } else { var remaining = chosen.NeededKills - this.MobHuntStruct->CurrentKills[chosen.CurrentKillsOffset]; - Chat.Print($"Hunting {remaining}x {chosen.Name} in {chosen.TerritoryName}"); + Service.Chat.Print($"Hunting {remaining}x {chosen.Name} in {chosen.TerritoryName}"); Location.CreateMapMarker( chosen.TerritoryType, chosen.MapId, @@ -210,29 +208,29 @@ public unsafe void PluginCommand(string command, string args) } else { - PluginLog.Information("Unable to find a hunt mark to target"); - Chat.Print("Couldn't find any hunt marks. Either you have no bills, or this is a bug."); + Service.PluginLog.Information("Unable to find a hunt mark to target"); + Service.Chat.Print("Couldn't find any hunt marks. Either you have no bills, or this is a bug."); } } break; case "ls": case "list": if (this.MobHuntEntries.Count < 1) { - Chat.Print("No hunt marks found. If this doesn't sound right, please use `/phb reload` and try again."); + Service.Chat.Print("No hunt marks found. If this doesn't sound right, please use `/phb reload` and try again."); break; } foreach (string expac in this.MobHuntEntries.Keys) { - Chat.Print($"{expac}: {string.Join(", ", this.MobHuntEntries[expac].Values.SelectMany(e => e).OrderBy(s => s.Name).Select(m => m.Name))}"); + Service.Chat.Print($"{expac}: {string.Join(", ", this.MobHuntEntries[expac].Values.SelectMany(e => e).OrderBy(s => s.Name).Select(m => m.Name))}"); } break; default: - this.OpenConfigUi(); + this.DrawInterface(); break; } } catch (Exception e) { - PluginLog.Error("Error in command handler: " + e.ToString()); + Service.PluginLog.Error("Error in command handler: " + e.ToString()); } } @@ -240,7 +238,7 @@ public unsafe void ReloadData() { this.MobHuntEntries.Clear(); var mobHuntList = new List(); - var mobHuntOrderSheet = Plugin.DataManager.Excel.GetSheet()!; + var mobHuntOrderSheet = Service.DataManager.Excel.GetSheet()!; foreach (var billNumber in Enum.GetValues()) { @@ -250,7 +248,7 @@ public unsafe void ReloadData() } var mobHuntOrderTypeRow = - Plugin.DataManager.Excel.GetSheet()!.GetRow((uint)billNumber)!; + Service.DataManager.Excel.GetSheet()!.GetRow((uint)billNumber)!; var rowId = mobHuntOrderTypeRow.OrderStart.Value!.RowId + (uint)(this.MobHuntStruct->BillOffset[mobHuntOrderTypeRow.RowId] - 1); @@ -325,10 +323,10 @@ public unsafe void ReloadData() private static IDalamudTextureWrap LoadIcon(uint id) { - var icon = Plugin.DataManager.GameData.GetHqIcon(id) ?? Plugin.DataManager.GameData.GetIcon(id)!; + var icon = Service.DataManager.GameData.GetHqIcon(id) ?? Service.DataManager.GameData.GetIcon(id)!; var iconData = icon.GetRgbaImageData(); - return Plugin.PluginInterface.UiBuilder.LoadImageRaw(iconData, icon.Header.Width, icon.Header.Height, 4); + return Service.PluginInterface.UiBuilder.LoadImageRaw(iconData, icon.Header.Width, icon.Header.Height, 4); } public void Dispose() diff --git a/HuntBuddy/Service.cs b/HuntBuddy/Service.cs new file mode 100644 index 0000000..7bedf8a --- /dev/null +++ b/HuntBuddy/Service.cs @@ -0,0 +1,27 @@ +using Dalamud.Game; +using Dalamud.IoC; +using Dalamud.Plugin; +using Dalamud.Plugin.Services; + +namespace HuntBuddy; + +public class Service +{ + [PluginService] public static DalamudPluginInterface PluginInterface { get; set; } = null!; + + [PluginService] public static ICommandManager Commands { get; set; } = null!; + + [PluginService] public static IChatGui Chat { get; set; } = null!; + + [PluginService] public static IDataManager DataManager { get; set; } = null!; + + [PluginService] public static ISigScanner SigScanner { get; set; } = null!; + + [PluginService] public static IGameGui GameGui { get; set; } = null!; + + [PluginService] public static IClientState ClientState { get; set; } = null!; + + [PluginService] public static IFramework Framework { get; set; } = null!; + + [PluginService] public static IPluginLog PluginLog { get; set; } = null!; +} \ No newline at end of file diff --git a/HuntBuddy/Utils/InterfaceUtil.cs b/HuntBuddy/Utils/InterfaceUtil.cs new file mode 100644 index 0000000..be86371 --- /dev/null +++ b/HuntBuddy/Utils/InterfaceUtil.cs @@ -0,0 +1,73 @@ +using System.Numerics; +using Dalamud.Interface; +using ImGuiNET; + +namespace HuntBuddy.Utils; + +/// +/// Interface utilities class. +/// +public static class InterfaceUtil +{ + /// + /// Draws hunt icons from game images. + /// + /// containing relevant information. + public static void DrawHuntIcon(MobHuntEntry mobHuntEntry) + { + var cursorPos = ImGui.GetCursorScreenPos(); + var imageSize = mobHuntEntry.ExpansionId < 3 ? new Vector2(192f, 128f) : new Vector2(210f); + imageSize *= ImGui.GetIO().FontGlobalScale * Plugin.Instance.Configuration.IconScale; + + ImGui.InvisibleButton("canvas", imageSize); + + var drawList = ImGui.GetWindowDrawList(); + if (mobHuntEntry is { ExpansionId: 4, IsEliteMark: false }) // Endwalker uses circle for non elite mobs + { + drawList.AddCircleFilled( + cursorPos + (imageSize / 2f), + imageSize.X / 2f, + Plugin.Instance.Configuration.IconBackgroundColourU32); + } + else + { + drawList.AddRectFilled( + cursorPos, + cursorPos + imageSize, + Plugin.Instance.Configuration.IconBackgroundColourU32); + } + + drawList.AddImage(mobHuntEntry.Icon.ImGuiHandle, cursorPos, cursorPos + imageSize); + } + + /// + /// Renders a button with an icon. + /// + /// Desired to be rendered. + /// Button ID. + /// True if pressed. + public static bool IconButton(FontAwesomeIcon icon, string? id) + { + ImGui.PushFont(UiBuilder.IconFont); + + var text = icon.ToIconString(); + + if (id != null) + { + text += $"##{id}"; + } + + var result = ImGui.Button(text); + + ImGui.PopFont(); + + return result; + } + + /// + /// Renders a button with an icon. + /// + /// Desired to be rendered. + /// True if pressed. + public static bool IconButton(FontAwesomeIcon icon) => IconButton(icon, null); +} \ No newline at end of file diff --git a/HuntBuddy/Windows/ConfigurationWindow.cs b/HuntBuddy/Windows/ConfigurationWindow.cs new file mode 100644 index 0000000..1e9fe1e --- /dev/null +++ b/HuntBuddy/Windows/ConfigurationWindow.cs @@ -0,0 +1,58 @@ +using System.Numerics; +using Dalamud.Interface.Windowing; +using ImGuiNET; + +namespace HuntBuddy.Windows; + +/// +/// Configuration window. +/// +public class ConfigurationWindow : Window +{ + public ConfigurationWindow() : base( + $"{Plugin.Instance.Name} configuration", + ImGuiWindowFlags.NoDocking, + true) + { + this.Size = Vector2.Zero; + this.SizeCondition = ImGuiCond.Always; + } + + public override void PreOpenCheck() + { + if (Plugin.Instance.Configuration.LockWindowPositions) + { + this.Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; + } + } + + public override void Draw() + { + var save = false; + + save |= ImGui.Checkbox("Lock plugin window positions", ref Plugin.Instance.Configuration.LockWindowPositions); + save |= ImGui.Checkbox("Include hunt area on map by default", ref Plugin.Instance.Configuration.IncludeAreaOnMap); + save |= ImGui.Checkbox("Show hunts in local area", ref Plugin.Instance.Configuration.ShowLocalHunts); + save |= ImGui.Checkbox( + "Show icons of hunts in local area", + ref Plugin.Instance.Configuration.ShowLocalHuntIcons); + save |= ImGui.Checkbox( + "Hide background of local hunts window", + ref Plugin.Instance.Configuration.HideLocalHuntBackground); + save |= ImGui.Checkbox( + "Hide completed targets in local hunts window", + ref Plugin.Instance.Configuration.HideCompletedHunts); + save |= ImGui.SliderFloat("Hunt icon scale", ref Plugin.Instance.Configuration.IconScale, 0.2f, 2f, "%.2f"); + if (ImGui.ColorEdit4("Hunt icon background colour", ref Plugin.Instance.Configuration.IconBackgroundColour)) + { + Plugin.Instance.Configuration.IconBackgroundColourU32 = + ImGui.ColorConvertFloat4ToU32(Plugin.Instance.Configuration.IconBackgroundColour); + save = true; + } + + if (save) + { + Plugin.Instance.Configuration.Save(); + } + } +} \ No newline at end of file diff --git a/HuntBuddy/Windows/LocalHuntsWindow.cs b/HuntBuddy/Windows/LocalHuntsWindow.cs new file mode 100644 index 0000000..970268d --- /dev/null +++ b/HuntBuddy/Windows/LocalHuntsWindow.cs @@ -0,0 +1,131 @@ +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Windowing; +using HuntBuddy.Utils; +using ImGuiNET; + +namespace HuntBuddy.Windows; + +/// +/// Local hunts window. +/// +public class LocalHuntsWindow : Window +{ + public LocalHuntsWindow() : base( + $"{Plugin.Instance.Name}", + ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.NoDocking, + true) + { + this.Size = Vector2.Zero; + this.SizeCondition = ImGuiCond.Always; + + this.IsOpen = true; + } + + public override void PreOpenCheck() + { + if (Plugin.Instance.Configuration.HideLocalHuntBackground) + { + this.Flags |= ImGuiWindowFlags.NoBackground; + } + + if (Plugin.Instance.Configuration.LockWindowPositions) + { + this.Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; + } + } + + public override unsafe bool DrawConditions() => + Plugin.Instance.Configuration.ShowLocalHunts && + !Plugin.Instance.CurrentAreaMobHuntEntries.IsEmpty && + Plugin.Instance.CurrentAreaMobHuntEntries.Count( + x => Plugin.Instance.MobHuntStruct->CurrentKills[x.CurrentKillsOffset] == x.NeededKills) != + Plugin.Instance.CurrentAreaMobHuntEntries.Count; + + public override unsafe void Draw() + { + foreach (var mobHuntEntry in Plugin.Instance.CurrentAreaMobHuntEntries) + { + var currentKills = Plugin.Instance.MobHuntStruct->CurrentKills[mobHuntEntry.CurrentKillsOffset]; + + if (Plugin.Instance.Configuration.HideCompletedHunts && currentKills == mobHuntEntry.NeededKills) + { + continue; + } + + if (Location.Database.ContainsKey(mobHuntEntry.MobHuntId)) + { + if (InterfaceUtil.IconButton(FontAwesomeIcon.MapMarkerAlt, $"pin##{mobHuntEntry.MobHuntId}")) + { + Location.CreateMapMarker( + mobHuntEntry.TerritoryType, + mobHuntEntry.MapId, + mobHuntEntry.MobHuntId, + mobHuntEntry.Name, + Location.OpenType.None); + } + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.Text("Place marker on the map"); + ImGui.EndTooltip(); + } + + ImGui.SameLine(); + + if (InterfaceUtil.IconButton(FontAwesomeIcon.MapMarkedAlt, $"open##{mobHuntEntry.MobHuntId}")) + { + var includeArea = Plugin.Instance.Configuration.IncludeAreaOnMap; + if (ImGui.IsKeyDown(ImGuiKey.ModShift)) + { + includeArea = !includeArea; + } + + Location.CreateMapMarker( + mobHuntEntry.TerritoryType, + mobHuntEntry.MapId, + mobHuntEntry.MobHuntId, + mobHuntEntry.Name, + includeArea ? Location.OpenType.ShowOpen : Location.OpenType.MarkerOpen); + } + + if (ImGui.IsItemHovered()) + { + var color = ImGui.IsKeyDown(ImGuiKey.ModShift) + ? new Vector4(0f, 0.7f, 0f, 1f) + : new Vector4(0.7f, 0.7f, 0.7f, 1f); + ImGui.BeginTooltip(); + if (Plugin.Instance.Configuration.IncludeAreaOnMap) + { + ImGui.Text("Show hunt area on the map"); + ImGui.TextColored( + color, + "Hold [SHIFT] to show the location only"); + } + else + { + ImGui.Text("Show hunt location on the map"); + ImGui.TextColored( + color, + "Hold [SHIFT] to include the area"); + } + + ImGui.EndTooltip(); + } + + ImGui.SameLine(); + } + + ImGui.Text($"{mobHuntEntry.Name} ({currentKills}/{mobHuntEntry.NeededKills})"); + + if (!Plugin.Instance.Configuration.ShowLocalHuntIcons) + { + continue; + } + + InterfaceUtil.DrawHuntIcon(mobHuntEntry); + } + } +} \ No newline at end of file diff --git a/HuntBuddy/Windows/MainWindow.cs b/HuntBuddy/Windows/MainWindow.cs new file mode 100644 index 0000000..d5a4fa8 --- /dev/null +++ b/HuntBuddy/Windows/MainWindow.cs @@ -0,0 +1,210 @@ +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using Dalamud.Interface; +using Dalamud.Interface.Windowing; +using ImGuiNET; +using HuntBuddy.Utils; + +namespace HuntBuddy.Windows; + +/// +/// Main plugin window. +/// +public class MainWindow : Window +{ + public MainWindow() : base( + $"{Plugin.Instance.Name}", + ImGuiWindowFlags.NoDocking, + true) + { + this.Size = new Vector2(400 * ImGui.GetIO().FontGlobalScale, 500); + this.SizeCondition = ImGuiCond.Once; + } + + public override void PreOpenCheck() + { + if (Plugin.Instance.Configuration.LockWindowPositions) + { + this.Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; + } + } + + public override unsafe void Draw() + { + if (!Plugin.Instance.MobHuntEntriesReady) + { + ImGui.Text("Reloading data ..."); + ImGui.End(); + return; + } + + if (InterfaceUtil.IconButton(FontAwesomeIcon.Redo, "Reload")) + { + ImGui.End(); + Plugin.Instance.MobHuntEntriesReady = false; + Task.Run(Plugin.Instance.ReloadData); + return; + } + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.Text("Click this button to reload daily hunt data"); + ImGui.EndTooltip(); + } + + ImGui.SameLine(); + + if (InterfaceUtil.IconButton(FontAwesomeIcon.Cog, "Config")) + { + Plugin.Instance.OpenConfigUi(); + } + + foreach (var expansionEntry in Plugin.Instance.MobHuntEntries.Where( + expansionEntry => + ImGui.TreeNode(expansionEntry.Key))) + { + foreach (var entry in expansionEntry.Value.Where( + entry => + { + var treeOpen = ImGui.TreeNodeEx(entry.Key.Value, ImGuiTreeNodeFlags.AllowItemOverlap); + ImGui.SameLine(); + var killedCount = entry.Value.Count( + x => + Plugin.Instance.MobHuntStruct->CurrentKills[x.CurrentKillsOffset] == + x.NeededKills); + + if (killedCount != entry.Value.Count) + { + ImGui.Text($"({killedCount}/{entry.Value.Count})"); + } + else + { + ImGui.TextColored( + new Vector4(0f, 1f, 0f, 1f), + $"({killedCount}/{entry.Value.Count})"); + } + + return treeOpen; + })) + { + foreach (var mobHuntEntry in entry.Value) + { + if (Location.Database.ContainsKey(mobHuntEntry.MobHuntId)) + { + if (InterfaceUtil.IconButton(FontAwesomeIcon.MapMarkerAlt, $"pin##{mobHuntEntry.MobHuntId}")) + { + Location.CreateMapMarker( + mobHuntEntry.TerritoryType, + mobHuntEntry.MapId, + mobHuntEntry.MobHuntId, + mobHuntEntry.Name, + Location.OpenType.None); + } + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.Text("Place marker on the map"); + ImGui.EndTooltip(); + } + + ImGui.SameLine(); + + if (InterfaceUtil.IconButton(FontAwesomeIcon.MapMarkedAlt, $"open##{mobHuntEntry.MobHuntId}")) + { + var includeArea = Plugin.Instance.Configuration.IncludeAreaOnMap; + if (ImGui.IsKeyDown(ImGuiKey.ModShift)) + { + includeArea = !includeArea; + } + + Location.CreateMapMarker( + mobHuntEntry.TerritoryType, + mobHuntEntry.MapId, + mobHuntEntry.MobHuntId, + mobHuntEntry.Name, + includeArea ? Location.OpenType.ShowOpen : Location.OpenType.MarkerOpen); + } + + if (ImGui.IsItemHovered()) + { + var color = ImGui.IsKeyDown(ImGuiKey.ModShift) + ? new Vector4(0f, 0.7f, 0f, 1f) + : new Vector4(0.7f, 0.7f, 0.7f, 1f); + ImGui.BeginTooltip(); + if (Plugin.Instance.Configuration.IncludeAreaOnMap) + { + ImGui.Text("Show hunt area on the map"); + ImGui.TextColored( + color, + "Hold [SHIFT] to show the location only"); + } + else + { + ImGui.Text("Show hunt location on the map"); + ImGui.TextColored( + color, + "Hold [SHIFT] to include the area"); + } + + ImGui.EndTooltip(); + } + + ImGui.SameLine(); + + if (Plugin.TeleportConsumer?.IsAvailable == true) + { + if (InterfaceUtil.IconButton(FontAwesomeIcon.StreetView, $"t##{mobHuntEntry.MobHuntId}")) + { + Location.TeleportToNearestAetheryte( + mobHuntEntry.TerritoryType, + mobHuntEntry.MapId, + mobHuntEntry.MobHuntId); + } + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.Text("Teleport to nearest aetheryte"); + ImGui.EndTooltip(); + } + + ImGui.SameLine(); + } + } + + var currentKills = Plugin.Instance.MobHuntStruct->CurrentKills[mobHuntEntry.CurrentKillsOffset]; + ImGui.Text(mobHuntEntry.Name); + if (ImGui.IsItemHovered()) + { + ImGui.PushStyleColor(ImGuiCol.PopupBg, Vector4.Zero); + ImGui.BeginTooltip(); + InterfaceUtil.DrawHuntIcon(mobHuntEntry); + ImGui.PopStyleColor(); + ImGui.EndTooltip(); + } + + ImGui.SameLine(); + if (currentKills != mobHuntEntry.NeededKills) + { + ImGui.Text($"({currentKills}/{mobHuntEntry.NeededKills})"); + } + else + { + ImGui.TextColored( + new Vector4(0f, 1f, 0f, 1f), + $"({currentKills}/{mobHuntEntry.NeededKills})"); + } + } + + ImGui.TreePop(); + } + + ImGui.TreePop(); + } + + ImGui.End(); + } +} \ No newline at end of file