diff --git a/CHANGELOG.md b/CHANGELOG.md index de33be6..8cd46b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [1.0.4] - 2025-02-20 + +### Added + +- **Auto-accept party invites** – Option to automatically accept party invites when the inviter is on your friend list or is a member of your Free Company. Configurable toggles for friends and FC members. + ## [1.0.3] - 2025-02-03 ### Added diff --git a/HSRTools/Configuration/HSRToolsConfiguration.cs b/HSRTools/Configuration/HSRToolsConfiguration.cs index 9163db7..b3da551 100644 --- a/HSRTools/Configuration/HSRToolsConfiguration.cs +++ b/HSRTools/Configuration/HSRToolsConfiguration.cs @@ -45,4 +45,22 @@ public class HSRToolsConfiguration /// Use this to diagnose cross-world invite issues. /// public bool Debug { get; set; } = false; + + // --- Auto-accept party invites --- + + /// + /// When true, automatically accept party invites from friends and/or Free Company members + /// (based on the options below). + /// + public bool AutoAcceptEnabled { get; set; } = false; + + /// + /// When true and AutoAcceptEnabled, accept party invites from players on your friend list. + /// + public bool AutoAcceptFromFriends { get; set; } = true; + + /// + /// When true and AutoAcceptEnabled, accept party invites from members of your Free Company. + /// + public bool AutoAcceptFromFreeCompany { get; set; } = true; } diff --git a/HSRTools/HSRTools.csproj b/HSRTools/HSRTools.csproj index 792d55c..3f15c14 100644 --- a/HSRTools/HSRTools.csproj +++ b/HSRTools/HSRTools.csproj @@ -1,7 +1,7 @@ true - 1.0.3.0 + 1.0.4.0 Knack117 HSRTools HSRTools diff --git a/HSRTools/HSRTools.json b/HSRTools/HSRTools.json index 3f3fba9..c6d1401 100644 --- a/HSRTools/HSRTools.json +++ b/HSRTools/HSRTools.json @@ -1,8 +1,8 @@ { "Author": "Knack117", "Name": "HSRTools", - "Punchline": "Auto-invite to party when a trigger word is said in FC, LS, CWLS, or tells.", - "Description": "Detects a user-defined trigger word in Free Company, Link Shell, Cross-World Link Shell, and tell chat, then invites the sender to your party. Works for same-world and cross-world (CWLS).", + "Punchline": "Auto-invite and auto-accept party invites from friends/FC.", + "Description": "Detects a trigger word in FC, LS, CWLS, and tells to auto-invite; optionally auto-accepts party invites from friends and FC members.", "RepoUrl": "https://github.com/Knack117/HSRTools", "Tags": [ "chat", "party", "invite", "automation" ], "AcceptsFeedback": true diff --git a/HSRTools/Plugin.cs b/HSRTools/Plugin.cs index d38f515..a6cc6e2 100644 --- a/HSRTools/Plugin.cs +++ b/HSRTools/Plugin.cs @@ -15,6 +15,7 @@ public sealed class HSRToolsPlugin : IDalamudPlugin private readonly string _configDir; private readonly ChatMonitorService _chatMonitorService; + private readonly AutoAcceptPartyService _autoAcceptPartyService; private readonly ConfigWindow _configWindow; private readonly WindowSystem _windowSystem; private HSRToolsConfiguration _config; @@ -32,6 +33,11 @@ public sealed class HSRToolsPlugin : IDalamudPlugin PluginServices.PluginLog, _config); + _autoAcceptPartyService = new AutoAcceptPartyService( + PluginServices.AddonLifecycle, + PluginServices.PluginLog, + _config); + _configWindow = new ConfigWindow(_config); _windowSystem = new WindowSystem("HSRTools"); _windowSystem.AddWindow(_configWindow); @@ -44,12 +50,15 @@ public sealed class HSRToolsPlugin : IDalamudPlugin }); _chatMonitorService.SetConfiguration(_config); _chatMonitorService.Start(); + _autoAcceptPartyService.SetConfiguration(_config); + _autoAcceptPartyService.Start(); } public void Dispose() { PluginServices.CommandManager.RemoveHandler("/hsr"); _chatMonitorService.Stop(); + _autoAcceptPartyService.Stop(); _windowSystem.RemoveAllWindows(); SaveConfig(_configDir, _config); } diff --git a/HSRTools/Services/AutoAcceptPartyService.cs b/HSRTools/Services/AutoAcceptPartyService.cs new file mode 100644 index 0000000..9fc82d9 --- /dev/null +++ b/HSRTools/Services/AutoAcceptPartyService.cs @@ -0,0 +1,134 @@ +using System; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Client.UI.Info; +using HSRTools.Configuration; + +namespace HSRTools.Services; + +/// +/// Automatically accepts party invites when the inviter is a friend or Free Company member. +/// Uses IAddonLifecycle to detect when the party invite addon opens, then checks the +/// inviter against the friend list and FC roster before accepting. +/// +public sealed class AutoAcceptPartyService +{ + private const string PartyInviteAddonName = "PartyInvite"; + + private readonly IAddonLifecycle _addonLifecycle; + private readonly IPluginLog _log; + private HSRToolsConfiguration _config; + + public AutoAcceptPartyService(IAddonLifecycle addonLifecycle, IPluginLog log, HSRToolsConfiguration config) + { + _addonLifecycle = addonLifecycle; + _log = log; + _config = config; + } + + public void SetConfiguration(HSRToolsConfiguration config) + { + _config = config; + } + + public void Start() + { + _addonLifecycle.RegisterListener(AddonEvent.PreSetup, PartyInviteAddonName, OnPartyInviteAddon); + } + + public void Stop() + { + _addonLifecycle.UnregisterListener(AddonEvent.PreSetup, PartyInviteAddonName, OnPartyInviteAddon); + } + + private void OnPartyInviteAddon(AddonEvent eventType, AddonArgs args) + { + if (!_config.AutoAcceptEnabled) + return; + + if (!_config.AutoAcceptFromFriends && !_config.AutoAcceptFromFreeCompany) + return; + + unsafe + { + var agent = AgentPartyInvite.Instance(); + if (agent == null) + return; + + var proxy = agent->InfoProxyPartyInvite; + if (proxy == null) + return; + + var inviterName = proxy->InviterName.ToString(); + if (string.IsNullOrWhiteSpace(inviterName)) + return; + + var inviterWorldId = proxy->InviterWorldId; + + var isFriend = _config.AutoAcceptFromFriends && IsFriend(inviterName, inviterWorldId); + var isFcMember = _config.AutoAcceptFromFreeCompany && IsFreeCompanyMember(inviterName, inviterWorldId); + + if (!isFriend && !isFcMember) + return; + + var reason = isFriend && isFcMember ? "friend and FC member" : isFriend ? "friend" : "FC member"; + _log.Info($"Auto-accepting party invite from {inviterName} ({reason})."); + + try + { + // RespondToInvitation is on InfoProxyInvitedList (parent of InfoProxyPartyInvite) + var accepted = ((InfoProxyInvitedList*)proxy)->RespondToInvitation(inviterName, true); + if (!accepted) + _log.Warning($"Failed to auto-accept party invite from {inviterName}."); + } + catch (Exception ex) + { + _log.Error(ex, $"Error auto-accepting party invite from {inviterName}"); + } + } + } + + private static unsafe bool IsFriend(string characterName, ushort worldId) + { + try + { + var infoModule = InfoModule.Instance(); + if (infoModule == null) + return false; + + var proxy = (InfoProxyCommonList*)infoModule->GetInfoProxyById(InfoProxyId.FriendList); + if (proxy == null) + return false; + + var entry = proxy->GetEntryByName(characterName, worldId); + return entry != null; + } + catch + { + return false; + } + } + + private static unsafe bool IsFreeCompanyMember(string characterName, ushort worldId) + { + try + { + var infoModule = InfoModule.Instance(); + if (infoModule == null) + return false; + + var proxy = (InfoProxyCommonList*)infoModule->GetInfoProxyById(InfoProxyId.FreeCompanyMember); + if (proxy == null) + return false; + + var entry = proxy->GetEntryByName(characterName, worldId); + return entry != null; + } + catch + { + return false; + } + } +} diff --git a/HSRTools/Services/PluginServices.cs b/HSRTools/Services/PluginServices.cs index 679604b..dc4bd83 100644 --- a/HSRTools/Services/PluginServices.cs +++ b/HSRTools/Services/PluginServices.cs @@ -13,4 +13,5 @@ public sealed class PluginServices [PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!; [PluginService] public static IToastGui ToastGui { get; private set; } = null!; [PluginService] public static ICommandManager CommandManager { get; private set; } = null!; + [PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!; } diff --git a/HSRTools/UI/ConfigWindow.cs b/HSRTools/UI/ConfigWindow.cs index 10fb357..527302d 100644 --- a/HSRTools/UI/ConfigWindow.cs +++ b/HSRTools/UI/ConfigWindow.cs @@ -15,6 +15,9 @@ public sealed class ConfigWindow : Window private bool _monitorTell; private bool _enabled; private bool _debug; + private bool _autoAcceptEnabled; + private bool _autoAcceptFromFriends; + private bool _autoAcceptFromFreeCompany; public ConfigWindow(HSRToolsConfiguration config) : base("HSRTools Configuration", ImGuiWindowFlags.AlwaysAutoResize) @@ -33,6 +36,9 @@ public sealed class ConfigWindow : Window _monitorTell = _config.MonitorTell; _enabled = _config.Enabled; _debug = _config.Debug; + _autoAcceptEnabled = _config.AutoAcceptEnabled; + _autoAcceptFromFriends = _config.AutoAcceptFromFriends; + _autoAcceptFromFreeCompany = _config.AutoAcceptFromFreeCompany; } public override void Draw() @@ -47,6 +53,9 @@ public sealed class ConfigWindow : Window _config.MonitorTell = true; _config.Enabled = true; _config.Debug = false; + _config.AutoAcceptEnabled = false; + _config.AutoAcceptFromFriends = true; + _config.AutoAcceptFromFreeCompany = true; SyncFromConfig(); } @@ -78,5 +87,19 @@ public sealed class ConfigWindow : Window ImGui.Separator(); if (ImGui.Checkbox("Debug logging (diagnose cross-world invite)", ref _debug)) _config.Debug = _debug; + + ImGui.Separator(); + ImGui.Text("Auto-accept party invites:"); + ImGui.Indent(); + if (ImGui.Checkbox("Auto-accept from friends and FC members", ref _autoAcceptEnabled)) + _config.AutoAcceptEnabled = _autoAcceptEnabled; + if (_autoAcceptEnabled) + { + if (ImGui.Checkbox(" From friends", ref _autoAcceptFromFriends)) + _config.AutoAcceptFromFriends = _autoAcceptFromFriends; + if (ImGui.Checkbox(" From Free Company members", ref _autoAcceptFromFreeCompany)) + _config.AutoAcceptFromFreeCompany = _autoAcceptFromFreeCompany; + } + ImGui.Unindent(); } } diff --git a/README.md b/README.md index cd6649a..7216497 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ A [Dalamud](https://github.com/goatcorp/Dalamud) plugin for FFXIV (XIVLauncher) ## Features - **Configurable trigger text** – Set any word or phrase (e.g. `inv`, `invite`, `party`). When someone types it in a monitored channel, they are invited to your party. +- **Auto-accept party invites** – Optionally automatically accept party invites when the inviter is on your friend list or is a member of your Free Company. - **Channel selection** – Choose which channels to monitor: - Free Company - Link Shell (1–8) @@ -21,6 +22,10 @@ A [Dalamud](https://github.com/goatcorp/Dalamud) plugin for FFXIV (XIVLauncher) For **tells**, the game provides the sender’s content ID and world, so invites work reliably. For **FC, LS, and CWLS**, the plugin uses the sender’s content ID when available (e.g. from the last message in CWLS), so cross-world invites work for CWLS and same-world for FC/LS. +### Auto-accept party invites + +When enabled, the plugin detects incoming party invites and automatically accepts them if the inviter is on your friend list or is a member of your Free Company. You can toggle friends and FC members independently. The friend list and FC roster must have been opened in-game at least once for the plugin to recognize members. + ## Building 1. Open `HSRTools.sln` in Visual Studio or use the command line. @@ -35,7 +40,7 @@ For **tells**, the game provides the sender’s content ID and world, so invites ## Configuration - Open the plugin config from the Dalamud plugin list (right-click HSRTools → **Settings**). -- Set the trigger text, enable/disable the plugin, choose channels, and set case sensitivity. +- Set the trigger text, enable/disable the plugin, choose channels, set case sensitivity, and configure auto-accept (friends/FC). - Settings are saved when you close the game or disable the plugin. ## Requirements