diff --git a/Visibility.sln b/Visibility.sln index 0d41bf0..0c24892 100644 --- a/Visibility.sln +++ b/Visibility.sln @@ -1,20 +1,20 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30413.136 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36301.6 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Visibility", "Visibility\Visibility.csproj", "{4D2166B1-F610-4BCE-B74E-B69FBE2CA766}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4D2166B1-F610-4BCE-B74E-B69FBE2CA766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D2166B1-F610-4BCE-B74E-B69FBE2CA766}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D2166B1-F610-4BCE-B74E-B69FBE2CA766}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D2166B1-F610-4BCE-B74E-B69FBE2CA766}.Release|Any CPU.Build.0 = Release|Any CPU + {4D2166B1-F610-4BCE-B74E-B69FBE2CA766}.Debug|x64.ActiveCfg = Debug|x64 + {4D2166B1-F610-4BCE-B74E-B69FBE2CA766}.Debug|x64.Build.0 = Debug|x64 + {4D2166B1-F610-4BCE-B74E-B69FBE2CA766}.Release|x64.ActiveCfg = Release|x64 + {4D2166B1-F610-4BCE-B74E-B69FBE2CA766}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Visibility/Api/VisibilityApi.cs b/Visibility/Api/VisibilityApi.cs index a87d8d8..6720e10 100644 --- a/Visibility/Api/VisibilityApi.cs +++ b/Visibility/Api/VisibilityApi.cs @@ -47,7 +47,7 @@ public void AddToVoidList(string name, uint worldId, string reason) throw new Exception($"Invalid worldId ({worldId})."); } - VisibilityPlugin.Instance.VoidPlayer("", $"{name} {world.Value.Name.ToString()} {reason}"); + VisibilityPlugin.Instance.CommandManagerHandler.VoidPlayer("", $"{name} {world.Value.Name.ToString()} {reason}"); } public void RemoveFromVoidList(string name, uint worldId) @@ -99,7 +99,7 @@ public void AddToWhitelist(string name, uint worldId, string reason) throw new Exception($"Invalid worldId ({worldId})."); } - VisibilityPlugin.Instance.WhitelistPlayer("", $"{name} {world.Value.Name.ToString()} {reason}"); + VisibilityPlugin.Instance.CommandManagerHandler.WhitelistPlayer("", $"{name} {world.Value.Name.ToString()} {reason}"); } public void RemoveFromWhitelist(string name, uint worldId) @@ -119,8 +119,8 @@ public void RemoveFromWhitelist(string name, uint worldId) } public void Enable(bool state) => - VisibilityPlugin.Instance.Configuration - .SettingDictionary[nameof(VisibilityPlugin.Instance.Configuration.Enabled)].Invoke(state, false, false); + VisibilityPlugin.Instance.Configuration.SettingsHandler + .Invoke(nameof(VisibilityPlugin.Instance.Configuration.Enabled), state, false, false); public void Dispose() => this.initialised = false; } diff --git a/Visibility/Configuration/TerritoryConfig.cs b/Visibility/Configuration/TerritoryConfig.cs index 4ab05f0..88f979f 100644 --- a/Visibility/Configuration/TerritoryConfig.cs +++ b/Visibility/Configuration/TerritoryConfig.cs @@ -9,6 +9,10 @@ public class TerritoryConfig public bool HidePlayer; public bool HideMinion; public bool HideChocobo; + public bool HidePetInCombat; + public bool HidePlayerInCombat; + public bool HideMinionInCombat; + public bool HideChocoboInCombat; public bool ShowCompanyPet; public bool ShowCompanyPlayer; public bool ShowCompanyMinion; diff --git a/Visibility/Configuration/VisibilityConfiguration.cs b/Visibility/Configuration/VisibilityConfiguration.cs index 5c57f54..06be7f1 100644 --- a/Visibility/Configuration/VisibilityConfiguration.cs +++ b/Visibility/Configuration/VisibilityConfiguration.cs @@ -7,12 +7,13 @@ using Lumina.Excel.Sheets; using Lumina.Text.ReadOnly; +using Visibility.Handlers; using Visibility.Utils; using Visibility.Void; namespace Visibility.Configuration; -public class VisibilityConfiguration: IPluginConfiguration +public partial class VisibilityConfiguration: IPluginConfiguration { public int Version { get; set; } @@ -31,8 +32,7 @@ public class VisibilityConfiguration: IPluginConfiguration [NonSerialized] public Dictionary VoidDictionary = null!; [NonSerialized] public Dictionary WhitelistDictionary = null!; - [NonSerialized] public readonly Dictionary> SettingDictionary = - new(StringComparer.InvariantCultureIgnoreCase); + [NonSerialized] public SettingsHandler SettingsHandler = null!; [NonSerialized] public readonly HashSet TerritoryTypeWhitelist = []; @@ -50,288 +50,46 @@ public void Init(ushort territoryType) { this.VoidDictionary = this.VoidList.Where(x => x.Id != 0).DistinctBy(x => x.Id).ToDictionary(x => x.Id, x => x); this.WhitelistDictionary = this.Whitelist.Where(x => x.Id != 0).DistinctBy(x => x.Id).ToDictionary(x => x.Id, x => x); + this.SettingsHandler = new SettingsHandler(this); - this.SettingDictionary[nameof(this.Enabled)] = (val, toggle, _) => - { - this.Enabled.ToggleBool(val, toggle); - - if (!VisibilityPlugin.Instance.Disable) // Make sure the disable event is finished before enabling again - { - VisibilityPlugin.Instance.Disable = !this.Enabled; - } - }; - - this.SettingDictionary[nameof(this.HideStar)] = (val, toggle, _) => this.HideStar.ToggleBool(val, toggle); - this.SettingDictionary[nameof(this.AdvancedEnabled)] = - (val, toggle, _) => this.AdvancedEnabled.ToggleBool(val, toggle); - - this.SettingDictionary[nameof(this.EnableContextMenu)] = (val, toggle, _) => - { - this.EnableContextMenu.ToggleBool(val, toggle); - - // TODO: Switch to dalamud service - // VisibilityPlugin.Instance.ContextMenu.Toggle(val, toggle); - }; - - this.SettingDictionary[nameof(this.ShowTargetOfTarget)] = (val, toggle, _) => this.ShowTargetOfTarget.ToggleBool(val, toggle); - - this.SettingDictionary[nameof(TerritoryConfig.HidePet)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.HidePet.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.HidePet.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowPets(ContainerType.All); - }; - - this.SettingDictionary[nameof(TerritoryConfig.HidePlayer)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.HidePlayer.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.HidePlayer.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowPlayers(ContainerType.All); - }; - - this.SettingDictionary[nameof(TerritoryConfig.HideChocobo)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.HideChocobo.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.HideChocobo.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowChocobos(ContainerType.All); - }; - - this.SettingDictionary[nameof(TerritoryConfig.HideMinion)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.HideMinion.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.HideMinion.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowMinions(ContainerType.All); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowCompanyPet)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowCompanyPet.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowCompanyPet.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowPets(ContainerType.Company); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowCompanyPlayer)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowCompanyPlayer.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowCompanyPlayer.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowPlayers(ContainerType.Company); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowCompanyChocobo)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowCompanyChocobo.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowCompanyChocobo.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowChocobos(ContainerType.Company); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowCompanyMinion)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowCompanyMinion.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowCompanyMinion.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowMinions(ContainerType.Company); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowPartyPet)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowPartyPet.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowPartyPet.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowPets(ContainerType.Party); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowPartyPlayer)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowPartyPlayer.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowPartyPlayer.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowPlayers(ContainerType.Party); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowPartyChocobo)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowPartyChocobo.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowPartyChocobo.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowChocobos(ContainerType.Party); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowPartyMinion)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowPartyMinion.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowPartyMinion.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowMinions(ContainerType.Party); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowFriendPet)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowFriendPet.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowFriendPet.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowPets(ContainerType.Friend); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowFriendPlayer)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowFriendPlayer.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowFriendPlayer.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowPlayers(ContainerType.Friend); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowFriendChocobo)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowFriendChocobo.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowFriendChocobo.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowChocobos(ContainerType.Friend); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowFriendMinion)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowFriendMinion.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowFriendMinion.ToggleBool(val, toggle); - } - - VisibilityPlugin.Instance.ShowMinions(ContainerType.Friend); - }; - - this.SettingDictionary[nameof(TerritoryConfig.ShowDeadPlayer)] = (val, toggle, edit) => - { - if (edit) - { - this.CurrentEditedConfig.ShowDeadPlayer.ToggleBool(val, toggle); - } - else - { - this.CurrentConfig.ShowDeadPlayer.ToggleBool(val, toggle); - } - }; - - IEnumerable<(ushort, ReadOnlySeString)>? valueTuples = Service.DataManager.GetExcelSheet()? - .Where( - x => (x.TerritoryIntendedUse.RowId is 0 or 1 or 13 or 19 or 21 or 23 or 44 or 46 or 47 || - this.TerritoryTypeWhitelist.Contains((ushort)x.RowId)) && !x.Name.IsEmpty && - x.RowId != 136) + IEnumerable<(ushort, ReadOnlySeString)> valueTuples = Service.DataManager.GetExcelSheet() + .Where(this.IsAllowedTerritory) .Select(x => ((ushort)x.RowId, x.PlaceName.ValueNullable?.Name ?? "Unknown Place")); - if (valueTuples != null) + foreach ((ushort rowId, ReadOnlySeString placeName) in valueTuples) { - foreach ((ushort rowId, ReadOnlySeString placeName) in valueTuples) - { - this.allowedTerritory.Add(rowId); - this.TerritoryTypeWhitelist.Add(rowId); - this.TerritoryPlaceNameDictionary[rowId] = placeName.ToString(); - } + this.allowedTerritory.Add(rowId); + this.TerritoryTypeWhitelist.Add(rowId); + this.TerritoryPlaceNameDictionary[rowId] = ItalicRegex().Replace(placeName.ToString(), ""); } this.UpdateCurrentConfig(territoryType); this.HandleVersionChanges(); } + // Allowed territory intended use IDs + private static readonly HashSet allowedTerritoryIntendedUses = [ + 0, // Hub Cities + 1, // Overworld + 13, // Residential Area + 19, + 21, // The Firmament + 23, // Gold Saucer + 44, // Leap of Faith + 46, // Ocean Fishing + 47, // The Diadem + 60, // Stellar Exploration + ]; + + // Helper method to determine if a territory is allowed + private bool IsAllowedTerritory(TerritoryType territory) + { + return (allowedTerritoryIntendedUses.Contains(territory.TerritoryIntendedUse.RowId) || + this.TerritoryTypeWhitelist.Contains((ushort)territory.RowId)) && + !territory.Name.IsEmpty && + territory.RowId != 136; // Exclude test map + } + public void UpdateCurrentConfig(ushort territoryType, bool edit = false) { if (this.AdvancedEnabled == false || @@ -382,4 +140,6 @@ private void HandleVersionChanges() } public void Save() => Service.PluginInterface.SavePluginConfig(this); + [System.Text.RegularExpressions.GeneratedRegex("")] + private static partial System.Text.RegularExpressions.Regex ItalicRegex(); } diff --git a/Visibility/Handlers/ChatHandler.cs b/Visibility/Handlers/ChatHandler.cs new file mode 100644 index 0000000..1ee4773 --- /dev/null +++ b/Visibility/Handlers/ChatHandler.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; + +using Dalamud.Game.Text; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; + +using Visibility.Configuration; + + +namespace Visibility.Handlers; + +public class ChatHandler: IDisposable +{ + private readonly VisibilityConfiguration configuration; + + public ChatHandler(VisibilityConfiguration config) + { + this.configuration = config; + + Service.ChatGui.ChatMessage += this.OnChatMessage; + } + + public void Dispose() + { + Service.ChatGui.ChatMessage -= this.OnChatMessage; + } + + private void OnChatMessage(XivChatType type, int timestamp, ref SeString sender, ref SeString message, + ref bool isHandled) + { + if (!this.configuration.Enabled) + { + return; + } + + if (isHandled) + { + return; + } + + PlayerPayload? playerPayload = sender.Payloads.SingleOrDefault(x => x is PlayerPayload) as PlayerPayload; + PlayerPayload? emotePlayerPayload = + message.Payloads.FirstOrDefault(x => x is PlayerPayload) as PlayerPayload; + bool isEmoteType = type is XivChatType.CustomEmote or XivChatType.StandardEmote; + + if (playerPayload == null && + (!isEmoteType || emotePlayerPayload == null)) + { + return; + } + + if (this.configuration.VoidList.Any(x => + x.HomeworldId == + (isEmoteType ? emotePlayerPayload?.World.RowId : playerPayload?.World.RowId) + && x.Name == (isEmoteType ? emotePlayerPayload?.PlayerName : playerPayload?.PlayerName))) + { + isHandled = true; + } + } +} diff --git a/Visibility/Handlers/CommandManagerHandler.cs b/Visibility/Handlers/CommandManagerHandler.cs new file mode 100644 index 0000000..b2cb5cc --- /dev/null +++ b/Visibility/Handlers/CommandManagerHandler.cs @@ -0,0 +1,464 @@ +using System; +using System.Linq; + +using Dalamud.Game.Command; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; + +using Visibility.Configuration; +using Visibility.Utils; +using Visibility.Void; + +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +using Lumina.Excel.Sheets; + +using Visibility.Managers; + +namespace Visibility.Handlers; + +public class CommandManagerHandler: IDisposable +{ + private readonly VisibilityConfiguration configuration; + private readonly Localization pluginLocalization; + private readonly FrameworkHandler frameworkHandler; + private readonly UiManager uiManager; + private readonly FrameworkUpdateHandler frameworkUpdateHandler; + + private static string PluginCommandName => "/pvis"; + private static string VoidCommandName => "/void"; + private static string VoidTargetCommandName => "/voidtarget"; + private static string WhitelistCommandName => "/whitelist"; + private static string WhitelistTargetCommandName => "/whitelisttarget"; + + public CommandManagerHandler( + VisibilityConfiguration config, + Localization localization, + FrameworkHandler framework, + UiManager uiManager, + FrameworkUpdateHandler frameworkUpdateHandler) + { + this.configuration = config; + this.pluginLocalization = localization; + this.frameworkHandler = framework; + this.uiManager = uiManager; + this.frameworkUpdateHandler = frameworkUpdateHandler; + + this.RegisterCommands(); + } + + private void RegisterCommands() + { + Service.CommandManager.AddHandler( + PluginCommandName, + new CommandInfo(this.PluginCommand) + { + HelpMessage = this.pluginLocalization.PluginCommandHelpMessage, ShowInHelp = true + }); + + Service.CommandManager.AddHandler( + VoidCommandName, + new CommandInfo(this.VoidPlayer) + { + HelpMessage = this.pluginLocalization.VoidPlayerHelpMessage, ShowInHelp = true + }); + + Service.CommandManager.AddHandler( + VoidTargetCommandName, + new CommandInfo(this.VoidTargetPlayer) + { + HelpMessage = this.pluginLocalization.VoidTargetPlayerHelpMessage, ShowInHelp = true + }); + + Service.CommandManager.AddHandler( + WhitelistCommandName, + new CommandInfo(this.WhitelistPlayer) + { + HelpMessage = this.pluginLocalization.WhitelistPlayerHelpMessage, ShowInHelp = true + }); + + Service.CommandManager.AddHandler( + WhitelistTargetCommandName, + new CommandInfo(this.WhitelistTargetPlayer) + { + HelpMessage = this.pluginLocalization.WhitelistTargetPlayerHelpMessage, ShowInHelp = true + }); + } + + public void Dispose() + { + Service.CommandManager.RemoveHandler(PluginCommandName); + Service.CommandManager.RemoveHandler(VoidCommandName); + Service.CommandManager.RemoveHandler(VoidTargetCommandName); + Service.CommandManager.RemoveHandler(WhitelistCommandName); + Service.CommandManager.RemoveHandler(WhitelistTargetCommandName); + } + + private void PluginCommand(string command, string arguments) + { + if (this.frameworkUpdateHandler.Disable) + { + return; + } + + if (string.IsNullOrEmpty(arguments)) + { + this.uiManager.OpenConfigUi(); + } + else + { + string[] args = arguments.Split([' '], 2); + + if (args[0].Equals("help", StringComparison.InvariantCultureIgnoreCase)) + { + Service.ChatGui.Print(this.pluginLocalization.PluginCommandHelpMenu1); + Service.ChatGui.Print(this.pluginLocalization.PluginCommandHelpMenu2); + Service.ChatGui.Print(this.pluginLocalization.PluginCommandHelpMenu3); + Service.ChatGui.Print(this.pluginLocalization.PluginCommandHelpMenu4); + + foreach (string? key in this.configuration.SettingsHandler.GetKeys()) + { + Service.ChatGui.Print($"{key}"); + } + + return; + } + + if (args[0].Equals("refresh", StringComparison.InvariantCulture)) + { + this.frameworkUpdateHandler.RequestRefresh(); + return; + } + + if (args.Length != 2) + { + Service.ChatGui.Print(this.pluginLocalization.PluginCommandHelpMenuError); + return; + } + + if (!this.configuration.SettingsHandler.ContainsKey(args[0])) + { + Service.ChatGui.Print(this.pluginLocalization.PluginCommandHelpMenuInvalidValueError(args[0])); + return; + } + + bool value = false; + bool toggle = false; + + switch (args[1].ToLowerInvariant()) + { + case "0": + case "off": + case "false": + value = false; + break; + + case "1": + case "on": + case "true": + value = true; + break; + + case "toggle": + toggle = true; + break; + default: + Service.ChatGui.Print(this.pluginLocalization.PluginCommandHelpMenuInvalidValueError(args[1])); + return; + } + + this.configuration.SettingsHandler.Invoke(args[0], value, toggle, false); + this.configuration.Save(); + } + } + + public void VoidPlayer(string command, string arguments) + { + if (string.IsNullOrEmpty(arguments)) + { + Service.ChatGui.Print(this.pluginLocalization.NoArgumentsError(this.pluginLocalization.VoidListName)); + return; + } + + string[] args = arguments.Split([' '], 4); + + if (args.Length < 3) + { + Service.ChatGui.Print( + this.pluginLocalization.NotEnoughArgumentsError(this.pluginLocalization.VoidListName)); + return; + } + + World? world = Service.DataManager.GetExcelSheet().SingleOrDefault(x => + x.DataCenter.ValueNullable?.Region != 0 && + x.Name.ToString().Equals(args[2], StringComparison.InvariantCultureIgnoreCase)); + + if (world is null) + { + Service.ChatGui.Print( + this.pluginLocalization.InvalidWorldNameError(this.pluginLocalization.VoidListName, args[2])); + return; + } + + string playerName = $"{args[0].ToUppercase()} {args[1].ToUppercase()}"; + + VoidItem voidItem; + IGameObject? playerCharacter = Service.ObjectTable.SingleOrDefault( + x => x is IPlayerCharacter character && character.HomeWorld.Value.RowId == world.Value.RowId && + character.Name.TextValue.Equals(playerName, StringComparison.InvariantCultureIgnoreCase)) as + IPlayerCharacter; + + if (playerCharacter != null) + { + unsafe + { + Character* character = (Character*)playerCharacter.Address; + voidItem = new VoidItem + { + Id = character->ContentId, + Name = character->NameString, + HomeworldId = world.Value.RowId, + HomeworldName = world.Value.Name.ToString(), + Reason = args.Length == 3 ? string.Empty : args[3], + Manual = command == "VoidUIManual" // Or handle UI source differently + }; + } + } + else + { + voidItem = new VoidItem + { + Name = playerName, + HomeworldId = world.Value.RowId, + HomeworldName = world.Value.Name.ToString(), + Reason = args.Length == 3 ? string.Empty : args[3], + Manual = command == "VoidUIManual" // Or handle UI source differently + }; + } + + SeString playerString = new( + new PlayerPayload(playerName, world.Value.RowId), + new IconPayload(BitmapFontIcon.CrossWorld), + new TextPayload(world.Value.Name.ToString())); + + if (!this.configuration.VoidList.Any( + x => + x.Name == voidItem.Name && x.HomeworldId == voidItem.HomeworldId)) + { + this.configuration.VoidList.Add(voidItem); + this.configuration.Save(); + + if (playerCharacter != null) + { + this.frameworkHandler.RemoveChecked(playerCharacter.EntityId); + } + + Service.ChatGui.Print( + this.pluginLocalization.EntryAdded(this.pluginLocalization.VoidListName, playerString)); + } + else + { + Service.ChatGui.Print( + this.pluginLocalization.EntryExistsError(this.pluginLocalization.VoidListName, playerString)); + } + } + + private void VoidTargetPlayer(string command, string arguments) + { + if (this.GetTargetPlayer() is { } playerCharacter) + { + VoidItem voidItem; + + unsafe + { + Character* character = (Character*)playerCharacter.Address; + voidItem = new VoidItem + { + Id = character->ContentId, + Name = character->NameString, + HomeworldId = character->HomeWorld, + HomeworldName = playerCharacter.HomeWorld.Value.Name.ToString(), + Reason = arguments, + Manual = false + }; + } + + SeString playerString = new( + new PlayerPayload(playerCharacter.Name.TextValue, playerCharacter.HomeWorld.Value.RowId), + new IconPayload(BitmapFontIcon.CrossWorld), + new TextPayload(playerCharacter.HomeWorld.Value.Name.ToString())); + + if (!this.configuration.VoidList.Any( + x => + x.Name == voidItem.Name && x.HomeworldId == voidItem.HomeworldId)) + { + this.configuration.VoidList.Add(voidItem); + this.configuration.Save(); + this.frameworkHandler.RemoveChecked(playerCharacter.EntityId); + Service.ChatGui.Print( + this.pluginLocalization.EntryAdded(this.pluginLocalization.VoidListName, playerString)); + } + else + { + Service.ChatGui.Print( + this.pluginLocalization.EntryExistsError(this.pluginLocalization.VoidListName, playerString)); + } + } + else + { + Service.ChatGui.Print(this.pluginLocalization.InvalidTargetError(this.pluginLocalization.VoidListName)); + } + } + + public void WhitelistPlayer(string command, string arguments) + { + if (string.IsNullOrEmpty(arguments)) + { + Service.ChatGui.Print(this.pluginLocalization.NoArgumentsError(this.pluginLocalization.WhitelistName)); + return; + } + + string[] args = arguments.Split([' '], 4); + + if (args.Length < 3) + { + Service.ChatGui.Print( + this.pluginLocalization.NotEnoughArgumentsError(this.pluginLocalization.WhitelistName)); + return; + } + + World? world = Service.DataManager.GetExcelSheet().SingleOrDefault(x => + x.DataCenter.ValueNullable?.Region != 0 && + x.Name.ToString().Equals(args[2], StringComparison.InvariantCultureIgnoreCase)); + + if (world is null) + { + Service.ChatGui.Print( + this.pluginLocalization.InvalidWorldNameError(this.pluginLocalization.WhitelistName, args[2])); + return; + } + + string playerName = $"{args[0].ToUppercase()} {args[1].ToUppercase()}"; + + IPlayerCharacter? playerCharacter = Service.ObjectTable.SingleOrDefault( + x => + x is IPlayerCharacter character && character.HomeWorld.Value.RowId == world.Value.RowId && + character.Name.TextValue.Equals(playerName, StringComparison.Ordinal)) as IPlayerCharacter; + + VoidItem item; + + if (playerCharacter != null) + { + unsafe + { + Character* character = (Character*)playerCharacter.Address; + item = new VoidItem + { + Id = character->ContentId, + Name = character->NameString, + HomeworldId = world.Value.RowId, + HomeworldName = world.Value.Name.ToString(), + Reason = args.Length == 3 ? string.Empty : args[3], + Manual = command == "WhitelistUIManual" // Or handle UI source differently + }; + } + } + else + { + item = new VoidItem + { + Name = playerName, + HomeworldId = world.Value.RowId, + HomeworldName = world.Value.Name.ToString(), + Reason = args.Length == 3 ? string.Empty : args[3], + Manual = command == "WhitelistUIManual" // Or handle UI source differently + }; + } + + SeString playerString = new( + new PlayerPayload(playerName, world.Value.RowId), + new IconPayload(BitmapFontIcon.CrossWorld), + new TextPayload(world.Value.Name.ToString())); + + if (!this.configuration.Whitelist.Any( + x => + x.Name == item.Name && x.HomeworldId == item.HomeworldId)) + { + this.configuration.Whitelist.Add(item); + this.configuration.Save(); + + if (playerCharacter != null) + { + this.frameworkHandler.RemoveChecked(playerCharacter.EntityId); + this.frameworkHandler.ShowPlayer(playerCharacter.EntityId); + } + + Service.ChatGui.Print( + this.pluginLocalization.EntryAdded(this.pluginLocalization.WhitelistName, playerString)); + } + else + { + Service.ChatGui.Print( + this.pluginLocalization.EntryExistsError(this.pluginLocalization.WhitelistName, playerString)); + } + } + + private void WhitelistTargetPlayer(string command, string arguments) + { + if (this.GetTargetPlayer() is IPlayerCharacter playerCharacter) + { + VoidItem item; + + unsafe + { + Character* character = (Character*)playerCharacter.Address; + item = new VoidItem + { + Id = character->ContentId, + Name = character->NameString, + HomeworldId = character->HomeWorld, + HomeworldName = playerCharacter.HomeWorld.Value.Name.ToString(), + Reason = arguments, + Manual = false + }; + } + + SeString playerString = new( + new PlayerPayload(playerCharacter.Name.TextValue, playerCharacter.HomeWorld.Value.RowId), + new IconPayload(BitmapFontIcon.CrossWorld), + new TextPayload(playerCharacter.HomeWorld.Value.Name.ToString())); + + if (!this.configuration.Whitelist.Any( + x => + x.Name == item.Name && x.HomeworldId == item.HomeworldId)) + { + this.configuration.Whitelist.Add(item); + this.configuration.Save(); + this.frameworkHandler.RemoveChecked(playerCharacter.EntityId); + this.frameworkHandler.ShowPlayer(playerCharacter.EntityId); + Service.ChatGui.Print( + this.pluginLocalization.EntryAdded(this.pluginLocalization.WhitelistName, playerString)); + } + else + { + Service.ChatGui.Print( + this.pluginLocalization.EntryExistsError(this.pluginLocalization.WhitelistName, playerString)); + } + } + else + { + Service.ChatGui.Print(this.pluginLocalization.InvalidTargetError(this.pluginLocalization.WhitelistName)); + } + } + + private IPlayerCharacter? GetTargetPlayer() + { + return Service.ObjectTable.SingleOrDefault( + x => x is IPlayerCharacter + && x.EntityId != 0 + && x.EntityId != Service.ClientState.LocalPlayer?.EntityId + && x.EntityId == Service.ClientState.LocalPlayer?.TargetObjectId) as IPlayerCharacter; + } +} diff --git a/Visibility/Handlers/FrameworkUpdateHandler.cs b/Visibility/Handlers/FrameworkUpdateHandler.cs new file mode 100644 index 0000000..a7a4bcf --- /dev/null +++ b/Visibility/Handlers/FrameworkUpdateHandler.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading.Tasks; + +using Dalamud.Plugin.Services; + +using Visibility.Configuration; +using Visibility.Utils; + +namespace Visibility.Handlers; + +public class FrameworkUpdateHandler: IDisposable +{ + private readonly FrameworkHandler frameworkHandler; + private readonly VisibilityConfiguration configuration; + private readonly Localization pluginLocalization; + + private bool refresh; + public bool Disable { get; set; } + + public FrameworkUpdateHandler(FrameworkHandler framework, VisibilityConfiguration config, Localization localization) + { + this.frameworkHandler = framework; + this.configuration = config; + this.pluginLocalization = localization; + + Service.Framework.Update += this.FrameworkOnOnUpdateEvent; + } + + public void Dispose() + { + Service.Framework.Update -= this.FrameworkOnOnUpdateEvent; + } + + public void RequestRefresh() + { + if (!this.refresh) + { + this.refresh = true; + } + } + + private void FrameworkOnOnUpdateEvent(IFramework framework) + { + if (this.Disable) + { + this.frameworkHandler.ShowAll(); + this.Disable = false; + + if (this.refresh) + { + Task.Run( + async () => + { + await Task.Delay(250); + this.configuration.Enabled = true; + Service.ChatGui.Print(this.pluginLocalization.RefreshComplete); + }); + } + + this.refresh = false; + } + else if (this.refresh) + { + this.Disable = true; + this.configuration.Enabled = false; + } + else + { + this.frameworkHandler.Update(); + } + } +} diff --git a/Visibility/Handlers/SettingsHandler.cs b/Visibility/Handlers/SettingsHandler.cs new file mode 100644 index 0000000..8335b65 --- /dev/null +++ b/Visibility/Handlers/SettingsHandler.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; + +using Visibility.Configuration; +using Visibility.Utils; + +namespace Visibility.Handlers; + +public class SettingsHandler +{ + private readonly Dictionary> settingActions = + new(StringComparer.InvariantCultureIgnoreCase); + + private readonly VisibilityConfiguration configurationInstance; + + public SettingsHandler(VisibilityConfiguration configurationInstance) + { + this.configurationInstance = configurationInstance; + this.InitializeSettingActions(); + } + + /// + /// Checks if a setting action exists for the given key (property name). + /// + /// The property name. + /// True if an action exists, false otherwise. + public bool ContainsKey(string key) => this.settingActions.ContainsKey(key); + + /// + /// Gets a read-only collection of all setting keys. + /// + /// A read-only collection of all setting keys. + public IEnumerable GetKeys() => this.settingActions.Keys; + + /// + /// Gets a setting action associated with the given key. + /// + /// The property name. + /// The action associated with the given key, or null if not found. + public Action? GetAction(string key) => this.settingActions.GetValueOrDefault(key); + + /// + /// Invokes the setting action associated with the given key. + /// + /// The property name. + /// The value parameter for the action. + /// The toggle parameter for the action. + /// The edit parameter for the action (used for territory configs). + public void Invoke(string key, bool val, bool toggle, bool edit) + { + if (!this.settingActions.TryGetValue(key, out Action? action)) + return; + + action.Invoke(val, toggle, edit); + } + + /// + /// Creates a generic action for handling territory-specific boolean settings. + /// + private Action CreateToggleAction( + Action propertyToggler, + Action? afterToggleAction = null) + { + return (val, toggle, edit) => + { + TerritoryConfig configToModify = + edit ? this.configurationInstance.CurrentEditedConfig : this.configurationInstance.CurrentConfig; + propertyToggler(configToModify, val, toggle); + afterToggleAction?.Invoke(); + }; + } + + /// + /// Creates a generic action for handling direct VisibilityConfiguration boolean settings. + /// + private Action CreateDirectToggleAction( + Action directPropertyToggler, + Action? afterToggleAction = null) + { + return (val, toggle, _) => // Ignores edit flag + { + directPropertyToggler(val, toggle); + afterToggleAction?.Invoke(); + }; + } + + /// + /// Populates the _settingActions dictionary. + /// + private void InitializeSettingActions() + { + // --- Direct Settings --- + this.settingActions[nameof(this.configurationInstance.Enabled)] = this.CreateDirectToggleAction( + (v, t) => this.configurationInstance.Enabled.ToggleBool(v, t), + () => + { + if (!VisibilityPlugin.Instance.Disable) + VisibilityPlugin.Instance.Disable = !this.configurationInstance.Enabled; + } + ); + + this.settingActions[nameof(this.configurationInstance.HideStar)] = + this.CreateDirectToggleAction((v, t) => this.configurationInstance.HideStar.ToggleBool(v, t) + ); + + this.settingActions[nameof(this.configurationInstance.AdvancedEnabled)] = + this.CreateDirectToggleAction((v, t) => + this.configurationInstance.AdvancedEnabled.ToggleBool(v, t), + () => + { + this.configurationInstance.UpdateCurrentConfig(Service.ClientState.TerritoryType); + } + ); + + this.settingActions[nameof(this.configurationInstance.EnableContextMenu)] = + this.CreateDirectToggleAction((v, t) => + this.configurationInstance.EnableContextMenu.ToggleBool(v, t) + // afterToggleAction: () => { /* TODO: Switch to dalamud service */ } + ); + + this.settingActions[nameof(this.configurationInstance.ShowTargetOfTarget)] = + this.CreateDirectToggleAction((v, t) => + this.configurationInstance.ShowTargetOfTarget.ToggleBool(v, t) + ); + + // --- Territory Config Settings (using nameof(TerritoryConfig.Property)) --- + + // Hide Section + this.settingActions[nameof(TerritoryConfig.HidePet)] = this.CreateToggleAction( + (config, v, t) => config.HidePet.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPets(ContainerType.All) + ); + + this.settingActions[nameof(TerritoryConfig.HidePlayer)] = this.CreateToggleAction( + (config, v, t) => config.HidePlayer.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPlayers(ContainerType.All) + ); + + this.settingActions[nameof(TerritoryConfig.HideChocobo)] = this.CreateToggleAction( + (config, v, t) => config.HideChocobo.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowChocobos(ContainerType.All) + ); + + this.settingActions[nameof(TerritoryConfig.HideMinion)] = this.CreateToggleAction( + (config, v, t) => config.HideMinion.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowMinions(ContainerType.All) + ); + + this.settingActions[nameof(TerritoryConfig.HidePetInCombat)] = this.CreateToggleAction( + (config, v, t) => config.HidePetInCombat.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPets(ContainerType.All) + ); + + this.settingActions[nameof(TerritoryConfig.HidePlayerInCombat)] = this.CreateToggleAction( + (config, v, t) => config.HidePlayerInCombat.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPlayers(ContainerType.All) + ); + + this.settingActions[nameof(TerritoryConfig.HideChocoboInCombat)] = this.CreateToggleAction( + (config, v, t) => config.HideChocoboInCombat.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowChocobos(ContainerType.All) + ); + + this.settingActions[nameof(TerritoryConfig.HideMinionInCombat)] = this.CreateToggleAction( + (config, v, t) => config.HideMinionInCombat.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowMinions(ContainerType.All) + ); + + // Show Company Section + this.settingActions[nameof(TerritoryConfig.ShowCompanyPet)] = this.CreateToggleAction( + (config, v, t) => config.ShowCompanyPet.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPets(ContainerType.Company) + ); + + this.settingActions[nameof(TerritoryConfig.ShowCompanyPlayer)] = this.CreateToggleAction( + (config, v, t) => config.ShowCompanyPlayer.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPlayers(ContainerType.Company) + ); + + this.settingActions[nameof(TerritoryConfig.ShowCompanyChocobo)] = this.CreateToggleAction( + (config, v, t) => config.ShowCompanyChocobo.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowChocobos(ContainerType.Company) + ); + + this.settingActions[nameof(TerritoryConfig.ShowCompanyMinion)] = this.CreateToggleAction( + (config, v, t) => config.ShowCompanyMinion.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowMinions(ContainerType.Company) + ); + + // Show Party Section + this.settingActions[nameof(TerritoryConfig.ShowPartyPet)] = this.CreateToggleAction( + (config, v, t) => config.ShowPartyPet.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPets(ContainerType.Party) + ); + + this.settingActions[nameof(TerritoryConfig.ShowPartyPlayer)] = this.CreateToggleAction( + (config, v, t) => config.ShowPartyPlayer.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPlayers(ContainerType.Party) + ); + + this.settingActions[nameof(TerritoryConfig.ShowPartyChocobo)] = this.CreateToggleAction( + (config, v, t) => config.ShowPartyChocobo.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowChocobos(ContainerType.Party) + ); + + this.settingActions[nameof(TerritoryConfig.ShowPartyMinion)] = this.CreateToggleAction( + (config, v, t) => config.ShowPartyMinion.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowMinions(ContainerType.Party) + ); + + // Show Friend Section + this.settingActions[nameof(TerritoryConfig.ShowFriendPet)] = this.CreateToggleAction( + (config, v, t) => config.ShowFriendPet.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPets(ContainerType.Friend) + ); + + this.settingActions[nameof(TerritoryConfig.ShowFriendPlayer)] = this.CreateToggleAction( + (config, v, t) => config.ShowFriendPlayer.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowPlayers(ContainerType.Friend) + ); + + this.settingActions[nameof(TerritoryConfig.ShowFriendChocobo)] = this.CreateToggleAction( + (config, v, t) => config.ShowFriendChocobo.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowChocobos(ContainerType.Friend) + ); + + this.settingActions[nameof(TerritoryConfig.ShowFriendMinion)] = this.CreateToggleAction( + (config, v, t) => config.ShowFriendMinion.ToggleBool(v, t), + () => VisibilityPlugin.Instance.ShowMinions(ContainerType.Friend) + ); + + // Special Case: ShowDeadPlayer (no afterToggleAction) + this.settingActions[nameof(TerritoryConfig.ShowDeadPlayer)] = this.CreateToggleAction((config, v, t) => + config.ShowDeadPlayer.ToggleBool(v, t) + // No specific afterToggleAction needed + ); + } +} diff --git a/Visibility/Handlers/TerritoryChangeHandler.cs b/Visibility/Handlers/TerritoryChangeHandler.cs new file mode 100644 index 0000000..03a27e3 --- /dev/null +++ b/Visibility/Handlers/TerritoryChangeHandler.cs @@ -0,0 +1,39 @@ +using System; + +using Visibility.Configuration; +using Visibility.Utils; + +namespace Visibility.Handlers; + +public class TerritoryChangeHandler: IDisposable +{ + private readonly FrameworkHandler frameworkHandler; + private readonly VisibilityConfiguration configuration; + + public TerritoryChangeHandler(FrameworkHandler framework, VisibilityConfiguration config) + { + this.frameworkHandler = framework; + this.configuration = config; + + Service.ClientState.TerritoryChanged += this.ClientStateOnTerritoryChanged; + } + + public void Dispose() + { + Service.ClientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; + } + + private void ClientStateOnTerritoryChanged(ushort territoryType) + { + this.frameworkHandler.OnTerritoryChanged(); + + if (!this.configuration.AdvancedEnabled) + { + return; + } + + this.configuration.Enabled = false; // Temporarily disable while updating + this.configuration.UpdateCurrentConfig(territoryType); + this.configuration.Enabled = true; + } +} diff --git a/Visibility/Localization.cs b/Visibility/Localization.cs index 993ab66..12d1dea 100644 --- a/Visibility/Localization.cs +++ b/Visibility/Localization.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text.RegularExpressions; diff --git a/Visibility/Managers/UIManager.cs b/Visibility/Managers/UIManager.cs new file mode 100644 index 0000000..8672aab --- /dev/null +++ b/Visibility/Managers/UIManager.cs @@ -0,0 +1,43 @@ +using System; +using Dalamud.Interface.Windowing; +using Dalamud.Plugin; + +namespace Visibility.Managers; + +public class UiManager : IDisposable +{ + private readonly WindowSystem windowSystem; + private readonly Windows.Configuration configurationWindow; + + public UiManager(IDalamudPluginInterface pluginInterface) + { + this.windowSystem = new WindowSystem("VisibilityPlugin"); + this.configurationWindow = new Windows.Configuration(this.windowSystem); + this.windowSystem.AddWindow(this.configurationWindow); + + Service.PluginInterface.UiBuilder.Draw += this.BuildUi; + Service.PluginInterface.UiBuilder.OpenConfigUi += this.OpenConfigUi; + } + + public void Dispose() + { + Service.PluginInterface.UiBuilder.Draw -= this.BuildUi; + Service.PluginInterface.UiBuilder.OpenConfigUi -= this.OpenConfigUi; + this.windowSystem.RemoveAllWindows(); + } + + private void BuildUi() + { + this.windowSystem.Draw(); + } + + public void ToggleConfigWindow() + { + this.configurationWindow.Toggle(); + } + + public void OpenConfigUi() + { + this.ToggleConfigWindow(); + } +} diff --git a/Visibility/Utils/ContainerManager.cs b/Visibility/Utils/ContainerManager.cs new file mode 100644 index 0000000..8b04223 --- /dev/null +++ b/Visibility/Utils/ContainerManager.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Visibility.Utils; + +/// +/// Manages containers for different unit types and their relationships +/// +public class ContainerManager +{ + private readonly Dictionary>> containers; + private readonly HashSet idsToDelete = new(capacity: 200); + + public ContainerManager() + { + this.containers = new Dictionary>> + { + { + UnitType.Players, new Dictionary> + { + { ContainerType.All, new Dictionary() }, + { ContainerType.Friend, new Dictionary() }, + { ContainerType.Party, new Dictionary() }, + { ContainerType.Company, new Dictionary() }, + } + }, + { + UnitType.Pets, new Dictionary> + { + { ContainerType.All, new Dictionary() }, + { ContainerType.Friend, new Dictionary() }, + { ContainerType.Party, new Dictionary() }, + { ContainerType.Company, new Dictionary() }, + } + }, + { + UnitType.Chocobos, new Dictionary> + { + { ContainerType.All, new Dictionary() }, + { ContainerType.Friend, new Dictionary() }, + { ContainerType.Party, new Dictionary() }, + { ContainerType.Company, new Dictionary() }, + } + }, + { + UnitType.Minions, new Dictionary> + { + { ContainerType.All, new Dictionary() }, + { ContainerType.Friend, new Dictionary() }, + { ContainerType.Party, new Dictionary() }, + { ContainerType.Company, new Dictionary() }, + } + }, + }; + } + + /// + /// Add an entity to a specific container + /// + public void AddToContainer(UnitType unitType, ContainerType containerType, uint entityId) + { + this.containers[unitType][containerType][entityId] = Environment.TickCount64; + } + + /// + /// Remove an entity from a specific container + /// + public void RemoveFromContainer(UnitType unitType, ContainerType containerType, uint entityId) + { + this.containers[unitType][containerType].Remove(entityId); + } + + /// + /// Check if an entity exists in a specific container + /// + public bool IsInContainer(UnitType unitType, ContainerType containerType, uint entityId) + { + return this.containers[unitType][containerType].ContainsKey(entityId); + } + + /// + /// Cleanup expired entries from all containers + /// + public void CleanupContainers() + { + foreach ((UnitType _, Dictionary>? unitContainer) in this.containers) + { + foreach ((ContainerType _, Dictionary? container) in unitContainer) + { + foreach ((uint id, long ticks) in container) + if (ticks > Environment.TickCount64 + 5000) + this.idsToDelete.Add(id); + + foreach (uint id in this.idsToDelete) container.Remove(id); + + this.idsToDelete.Clear(); + } + } + } + + /// + /// Get all entities in a specific container + /// + public IEnumerable> GetContainerEntities(UnitType unitType, ContainerType containerType) + { + return this.containers[unitType][containerType].ToList(); + } + + /// + /// Clear a specific container + /// + public void ClearContainer(UnitType unitType, ContainerType containerType) + { + this.containers[unitType][containerType].Clear(); + } + + /// + /// Clear all containers + /// + public void ClearAllContainers() + { + foreach ((UnitType _, Dictionary>? unitContainer) in this.containers) + foreach ((ContainerType _, Dictionary? container) in unitContainer) + container.Clear(); + } +} diff --git a/Visibility/Utils/EntityHandlers/ChocoboHandler.cs b/Visibility/Utils/EntityHandlers/ChocoboHandler.cs new file mode 100644 index 0000000..0b88fb1 --- /dev/null +++ b/Visibility/Utils/EntityHandlers/ChocoboHandler.cs @@ -0,0 +1,126 @@ +using Dalamud.Game.ClientState.Conditions; + +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +using Visibility.Configuration; + +namespace Visibility.Utils.EntityHandlers; + +/// +/// Handles visibility logic for chocobo entities +/// +public class ChocoboHandler +{ + private readonly ContainerManager containerManager; + private readonly VoidListManager voidListManager; + private readonly ObjectVisibilityManager visibilityManager; + + private static VisibilityConfiguration Configuration => VisibilityPlugin.Instance.Configuration; + private static TerritoryConfig CurrentConfig => VisibilityPlugin.Instance.Configuration.CurrentConfig; + + public ChocoboHandler( + ContainerManager containerManager, + VoidListManager voidListManager, + ObjectVisibilityManager visibilityManager) + { + this.containerManager = containerManager; + this.voidListManager = voidListManager; + this.visibilityManager = visibilityManager; + } + + /// + /// Process a chocobo entity and determine its visibility + /// + public unsafe void ProcessChocobo(Character* characterPtr, Character* localPlayer) + { + // Ignore own chocobo + if (characterPtr->GameObject.OwnerId == localPlayer->GameObject.EntityId || + this.visibilityManager.ShowGameObject(characterPtr)) return; + + // Add to containers + this.UpdateContainers(characterPtr); + + // Hide chocobo if it belongs to a voided player + if (this.voidListManager.IsObjectVoided(characterPtr->GameObject.OwnerId)) + { + this.visibilityManager.HideGameObject(characterPtr); + return; + } + + // Check visibility conditions + if (this.ShouldShowChocobo(characterPtr)) + { + this.visibilityManager.MarkObjectToShow(characterPtr->GameObject.EntityId); + return; + } + + this.visibilityManager.HideGameObject(characterPtr); + } + + /// + /// Update container memberships for the chocobo + /// + private unsafe void UpdateContainers(Character* characterPtr) + { + // All chocobos container + this.containerManager.AddToContainer(UnitType.Chocobos, ContainerType.All, characterPtr->GameObject.EntityId); + + // Friend's chocobo container + if (this.containerManager.IsInContainer(UnitType.Players, ContainerType.Friend, + characterPtr->GameObject.OwnerId)) + { + this.containerManager.AddToContainer(UnitType.Chocobos, ContainerType.Friend, + characterPtr->GameObject.EntityId); + } + + // Party member's chocobo container + if (this.containerManager.IsInContainer(UnitType.Players, ContainerType.Party, + characterPtr->GameObject.OwnerId)) + { + this.containerManager.AddToContainer(UnitType.Chocobos, ContainerType.Party, + characterPtr->GameObject.EntityId); + } + + // Company member's chocobo container + if (this.containerManager.IsInContainer(UnitType.Players, ContainerType.Company, + characterPtr->GameObject.OwnerId)) + { + this.containerManager.AddToContainer(UnitType.Chocobos, ContainerType.Company, + characterPtr->GameObject.EntityId); + } + } + + /// + /// Determine if a chocobo should be shown based on configuration settings + /// + private unsafe bool ShouldShowChocobo(Character* characterPtr) + { + // Check if plugin is disabled or chocobo hiding is disabled + if (!Configuration.Enabled || + !CurrentConfig.HideChocobo) + return true; + + // Check if chocobo's owner is a friend and show friends' chocobos is enabled + if (CurrentConfig.ShowFriendChocobo && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Friend, + characterPtr->GameObject.OwnerId)) return true; + + // Check if chocobo's owner is in the same company and show company members' chocobos is enabled + if (CurrentConfig.ShowCompanyChocobo && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Company, + characterPtr->GameObject.OwnerId)) return true; + + // Check if chocobo's owner is in the party and show party members' chocobos is enabled + if (CurrentConfig.ShowPartyChocobo && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Party, + characterPtr->GameObject.OwnerId)) return true; + + // Check if chocobo's owner is whitelisted + if (this.voidListManager.IsObjectWhitelisted(characterPtr->GameObject.OwnerId)) + return true; + + // Check if local player is in combat and hide chocobos in combat is enabled + return CurrentConfig is { HideChocoboInCombat: true, HideChocobo: false } && + !Service.Condition[ConditionFlag.InCombat]; + } +} diff --git a/Visibility/Utils/EntityHandlers/MinionHandler.cs b/Visibility/Utils/EntityHandlers/MinionHandler.cs new file mode 100644 index 0000000..2f675de --- /dev/null +++ b/Visibility/Utils/EntityHandlers/MinionHandler.cs @@ -0,0 +1,108 @@ +using Dalamud.Game.ClientState.Conditions; + +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +using Visibility.Configuration; + +namespace Visibility.Utils.EntityHandlers; + +/// +/// Handles visibility logic for minion entities +/// +public class MinionHandler +{ + private readonly ContainerManager containerManager; + private readonly ObjectVisibilityManager visibilityManager; + + private static VisibilityConfiguration Configuration => VisibilityPlugin.Instance.Configuration; + private static TerritoryConfig CurrentConfig => VisibilityPlugin.Instance.Configuration.CurrentConfig; + + public MinionHandler( + ContainerManager containerManager, + ObjectVisibilityManager visibilityManager) + { + this.containerManager = containerManager; + this.visibilityManager = visibilityManager; + } + + /// + /// Process a minion entity and determine its visibility + /// + public unsafe void ProcessMinion(Character* characterPtr, Character* localPlayer) + { + if (localPlayer == null || + characterPtr->CompanionOwnerId == localPlayer->GameObject.EntityId || + this.visibilityManager.ShowGameObject(characterPtr, ObjectVisibilityManager.ObjectType.Companion)) + return; + + if (!Configuration.Enabled || + !CurrentConfig.HideMinion) + return; + + // Add to containers + this.UpdateContainers(characterPtr); + + // Check visibility conditions + if (this.ShouldShowMinion(characterPtr)) + { + this.visibilityManager.MarkObjectToShow(characterPtr->GameObject.EntityId); + return; + } + + this.visibilityManager.HideGameObject(characterPtr, ObjectVisibilityManager.ObjectType.Companion); + } + + /// + /// Update container memberships for the minion + /// + private unsafe void UpdateContainers(Character* characterPtr) + { + // All minions container + this.containerManager.AddToContainer(UnitType.Minions, ContainerType.All, characterPtr->CompanionOwnerId); + + // Friend's minion container + if (this.containerManager.IsInContainer(UnitType.Players, ContainerType.Friend, characterPtr->CompanionOwnerId)) + { + this.containerManager.AddToContainer(UnitType.Minions, ContainerType.Friend, + characterPtr->CompanionOwnerId); + } + + // Party member's minion container + if (this.containerManager.IsInContainer(UnitType.Players, ContainerType.Party, characterPtr->CompanionOwnerId)) + this.containerManager.AddToContainer(UnitType.Minions, ContainerType.Party, characterPtr->CompanionOwnerId); + + // Company member's minion container + if (this.containerManager.IsInContainer(UnitType.Players, ContainerType.Company, + characterPtr->CompanionOwnerId)) + { + this.containerManager.AddToContainer(UnitType.Minions, ContainerType.Company, + characterPtr->CompanionOwnerId); + } + } + + /// + /// Determine if a minion should be shown based on configuration settings + /// + private unsafe bool ShouldShowMinion(Character* characterPtr) + { + // Check if minion's owner is a friend and show friends' minions is enabled + if (CurrentConfig.ShowFriendMinion && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Friend, characterPtr->CompanionOwnerId)) + return true; + + // Check if minion's owner is in the same company and show company members' minions is enabled + if (CurrentConfig.ShowCompanyMinion && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Company, + characterPtr->CompanionOwnerId)) return true; + + // Check if minion's owner is in the party and show party members' minions is enabled + if (CurrentConfig.ShowPartyMinion && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Party, + characterPtr->CompanionOwnerId)) + return true; + + // Check if local player is in combat and hide minions in combat is enabled + return CurrentConfig is { HideMinionInCombat: true, HideMinion: false } && + !Service.Condition[ConditionFlag.InCombat]; + } +} diff --git a/Visibility/Utils/EntityHandlers/PetHandler.cs b/Visibility/Utils/EntityHandlers/PetHandler.cs new file mode 100644 index 0000000..406c9eb --- /dev/null +++ b/Visibility/Utils/EntityHandlers/PetHandler.cs @@ -0,0 +1,139 @@ +using Dalamud.Game.ClientState.Conditions; + +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +using Visibility.Configuration; + +namespace Visibility.Utils.EntityHandlers; + +/// +/// Handles visibility logic for pet entities +/// +public class PetHandler +{ + private readonly ContainerManager containerManager; + private readonly VoidListManager voidListManager; + private readonly ObjectVisibilityManager visibilityManager; + + private static VisibilityConfiguration Configuration => VisibilityPlugin.Instance.Configuration; + private static TerritoryConfig CurrentConfig => VisibilityPlugin.Instance.Configuration.CurrentConfig; + + public PetHandler( + ContainerManager containerManager, + VoidListManager voidListManager, + ObjectVisibilityManager visibilityManager) + { + this.containerManager = containerManager; + this.voidListManager = voidListManager; + this.visibilityManager = visibilityManager; + } + + /// + /// Process a pet entity and determine its visibility + /// + public unsafe void ProcessPet(Character* characterPtr, Character* localPlayer, bool isBound) + { + // Ignore own pet + if (characterPtr->GameObject.OwnerId == localPlayer->GameObject.EntityId || + this.visibilityManager.ShowGameObject(characterPtr)) return; + + // Add to containers + this.UpdateContainers(characterPtr); + + // Do not hide pets in duties + if (isBound) return; + + // Hide pet if it belongs to a voided player + if (this.voidListManager.IsObjectVoided(characterPtr->GameObject.OwnerId)) + { + this.visibilityManager.HideGameObject(characterPtr); + return; + } + + // Check visibility conditions + if (this.ShouldShowPet(characterPtr)) + { + this.visibilityManager.MarkObjectToShow(characterPtr->GameObject.EntityId); + return; + } + + this.visibilityManager.HideGameObject(characterPtr); + } + + /// + /// Update container memberships for the pet + /// + private unsafe void UpdateContainers(Character* characterPtr) + { + // All pets container + this.containerManager.AddToContainer(UnitType.Pets, ContainerType.All, characterPtr->GameObject.EntityId); + + // Friend's pet container + if (this.containerManager.IsInContainer(UnitType.Players, ContainerType.Friend, + characterPtr->GameObject.OwnerId)) + { + this.containerManager.AddToContainer(UnitType.Pets, ContainerType.Friend, + characterPtr->GameObject.EntityId); + } + + // Party member's pet container + if (this.containerManager.IsInContainer(UnitType.Players, ContainerType.Party, + characterPtr->GameObject.OwnerId)) + this.containerManager.AddToContainer(UnitType.Pets, ContainerType.Party, characterPtr->GameObject.EntityId); + + // Company member's pet container + if (this.containerManager.IsInContainer(UnitType.Players, ContainerType.Company, + characterPtr->GameObject.OwnerId)) + { + this.containerManager.AddToContainer(UnitType.Pets, ContainerType.Company, + characterPtr->GameObject.EntityId); + } + } + + /// + /// Determine if a pet should be shown based on configuration settings + /// + private unsafe bool ShouldShowPet(Character* characterPtr) + { + // Check if plugin is disabled or pet hiding is disabled + if (!Configuration.Enabled || + !CurrentConfig.HidePet) + return true; + + // Check if pet's owner is a friend and show friends' pets is enabled + if (CurrentConfig.ShowFriendPet && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Friend, + characterPtr->GameObject.OwnerId)) return true; + + // Check if pet's owner is in the same company and show company members' pets is enabled + if (CurrentConfig.ShowCompanyPet && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Company, + characterPtr->GameObject.OwnerId)) return true; + + // Check if pet's owner is in the party and show party members' pets is enabled + if (CurrentConfig.ShowPartyPet && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Party, + characterPtr->GameObject.OwnerId)) return true; + + // Check if pet's owner is whitelisted + if (this.voidListManager.IsObjectWhitelisted(characterPtr->GameObject.OwnerId)) + return true; + + // Check if local player is in combat and hide pets in combat is enabled + return CurrentConfig is { HidePetInCombat: true, HidePet: false } && + !Service.Condition[ConditionFlag.InCombat]; + } + + /// + /// Process an Earthly Star (special pet type) + /// + public unsafe void ProcessEarthlyStar(Character* characterPtr, Character* localPlayer) + { + if (Configuration is { Enabled: true, HideStar: true } + && Service.Condition[ConditionFlag.InCombat] + && characterPtr->GameObject.OwnerId != localPlayer->GameObject.EntityId + && !this.containerManager.IsInContainer(UnitType.Players, ContainerType.Party, + characterPtr->GameObject.OwnerId)) + this.visibilityManager.HideGameObject(characterPtr); + } +} diff --git a/Visibility/Utils/EntityHandlers/PlayerHandler.cs b/Visibility/Utils/EntityHandlers/PlayerHandler.cs new file mode 100644 index 0000000..a4325c6 --- /dev/null +++ b/Visibility/Utils/EntityHandlers/PlayerHandler.cs @@ -0,0 +1,155 @@ +using System; + +using Dalamud.Game.ClientState.Conditions; + +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +using Visibility.Configuration; + +namespace Visibility.Utils.EntityHandlers; + +/// +/// Handles visibility logic for player entities +/// +public class PlayerHandler +{ + private readonly ContainerManager containerManager; + private readonly VoidListManager voidListManager; + private readonly ObjectVisibilityManager visibilityManager; + + private static VisibilityConfiguration Configuration => VisibilityPlugin.Instance.Configuration; + private static TerritoryConfig CurrentConfig => VisibilityPlugin.Instance.Configuration.CurrentConfig; + + public PlayerHandler( + ContainerManager containerManager, + VoidListManager voidListManager, + ObjectVisibilityManager visibilityManager) + { + this.containerManager = containerManager; + this.voidListManager = voidListManager; + this.visibilityManager = visibilityManager; + } + + /// + /// Process a player entity and determine its visibility + /// + public unsafe void ProcessPlayer(Character* characterPtr, Character* localPlayer, bool isBound) + { + if (characterPtr->GameObject.EntityId == 0xE0000000 || + this.visibilityManager.ShowGameObject(characterPtr)) return; + + // Add to containers + this.UpdateContainers(characterPtr, localPlayer); + + // Check territory whitelist + if (isBound && !Configuration.TerritoryTypeWhitelist.Contains( + Service.ClientState.TerritoryType)) + return; + + // Check void list + if (this.voidListManager.CheckAndProcessVoidList(characterPtr)) + { + this.visibilityManager.HideGameObject(characterPtr); + return; + } + + // Check whitelist + if (this.voidListManager.CheckAndProcessWhitelist(characterPtr)) return; + + // Check visibility conditions + if (this.ShouldShowPlayer(characterPtr)) + { + this.visibilityManager.MarkObjectToShow(characterPtr->GameObject.EntityId); + return; + } + + this.visibilityManager.HideGameObject(characterPtr); + } + + /// + /// Update container memberships for the player + /// + private unsafe void UpdateContainers(Character* characterPtr, Character* localPlayer) + { + // All players container + this.containerManager.AddToContainer(UnitType.Players, ContainerType.All, characterPtr->GameObject.EntityId); + + // Friend container + if (characterPtr->IsFriend) + { + this.containerManager.AddToContainer(UnitType.Players, ContainerType.Friend, + characterPtr->GameObject.EntityId); + } + else + { + this.containerManager.RemoveFromContainer(UnitType.Players, ContainerType.Friend, + characterPtr->GameObject.EntityId); + } + + // Party container + bool isObjectIdInParty = FrameworkHandler.IsObjectIdInParty(characterPtr->GameObject.EntityId); + if (isObjectIdInParty) + { + this.containerManager.AddToContainer(UnitType.Players, ContainerType.Party, + characterPtr->GameObject.EntityId); + } + else + { + this.containerManager.RemoveFromContainer(UnitType.Players, ContainerType.Party, + characterPtr->GameObject.EntityId); + } + + // Company container + if (localPlayer->FreeCompanyTag[0] != 0 + && localPlayer->CurrentWorld == localPlayer->HomeWorld + && characterPtr->FreeCompanyTag.SequenceEqual(localPlayer->FreeCompanyTag)) + { + this.containerManager.AddToContainer(UnitType.Players, ContainerType.Company, + characterPtr->GameObject.EntityId); + } + else + { + this.containerManager.RemoveFromContainer(UnitType.Players, ContainerType.Company, + characterPtr->GameObject.EntityId); + } + } + + /// + /// Determine if a player should be shown based on configuration settings + /// + private unsafe bool ShouldShowPlayer(Character* characterPtr) + { + // Check if plugin is disabled or player hiding is disabled + if (!Configuration.Enabled || + !CurrentConfig.HidePlayer) + return true; + + // Check if player is dead and show dead players is enabled + if (CurrentConfig.ShowDeadPlayer && + characterPtr->GameObject.IsDead()) + return true; + + // Check if player is a friend and show friends is enabled + if (CurrentConfig.ShowFriendPlayer && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Friend, + characterPtr->GameObject.EntityId)) return true; + + // Check if player is in the same company and show company members is enabled + if (CurrentConfig.ShowCompanyPlayer && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Company, + characterPtr->GameObject.EntityId)) return true; + + // Check if player is in the party and show party members is enabled + if (CurrentConfig.ShowPartyPlayer && + this.containerManager.IsInContainer(UnitType.Players, ContainerType.Party, + characterPtr->GameObject.EntityId)) return true; + + // Check if player is the target of the target + if (FrameworkHandler.CheckTargetOfTarget(characterPtr)) + return true; + + // Check if local player is in combat and hide players in combat is enabled + return CurrentConfig.HidePlayerInCombat && + !Service.Condition[ConditionFlag.InCombat]; + } +} diff --git a/Visibility/Utils/FrameworkHandler.cs b/Visibility/Utils/FrameworkHandler.cs index 4aa99d0..f77615a 100644 --- a/Visibility/Utils/FrameworkHandler.cs +++ b/Visibility/Utils/FrameworkHandler.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.Enums; @@ -13,7 +12,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Info; using FFXIVClientStructs.FFXIV.Component.GUI; -using Visibility.Void; +using Visibility.Utils.EntityHandlers; using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; @@ -35,80 +34,58 @@ public enum UnitType Minions } +/// +/// Main handler for managing visibility of game objects +/// public class FrameworkHandler: IDisposable { - private enum ObjectType - { - Character, - Companion - } + // Component managers + private readonly ContainerManager containerManager; + private readonly VoidListManager voidListManager; + private readonly ObjectVisibilityManager visibilityManager; - private readonly Dictionary hiddenObjectIds = new(capacity: 200); - private readonly Dictionary objectIdsToShow = new(capacity: 200); - private readonly Dictionary checkedVoidedObjectIds = new(capacity: 1000); - private readonly Dictionary checkedWhitelistedObjectIds = new(capacity: 1000); - private readonly Dictionary voidedObjectIds = new(capacity: 1000); - private readonly Dictionary whitelistedObjectIds = new(capacity: 1000); - - private readonly Dictionary>> containers = new() - { - { - UnitType.Players, new Dictionary> - { - { ContainerType.All, new Dictionary() }, - { ContainerType.Friend, new Dictionary() }, - { ContainerType.Party, new Dictionary() }, - { ContainerType.Company, new Dictionary() }, - } - }, - { - UnitType.Pets, new Dictionary> - { - { ContainerType.All, new Dictionary() }, - { ContainerType.Friend, new Dictionary() }, - { ContainerType.Party, new Dictionary() }, - { ContainerType.Company, new Dictionary() }, - } - }, - { - UnitType.Chocobos, new Dictionary> - { - { ContainerType.All, new Dictionary() }, - { ContainerType.Friend, new Dictionary() }, - { ContainerType.Party, new Dictionary() }, - { ContainerType.Company, new Dictionary() }, - } - }, - { - UnitType.Minions, new Dictionary> - { - { ContainerType.All, new Dictionary() }, - { ContainerType.Friend, new Dictionary() }, - { ContainerType.Party, new Dictionary() }, - { ContainerType.Company, new Dictionary() }, - } - }, - }; + // Entity handlers + private readonly PlayerHandler playerHandler; + private readonly PetHandler petHandler; + private readonly ChocoboHandler chocoboHandler; + private readonly MinionHandler minionHandler; - private readonly Dictionary hiddenMinionObjectIds = new(capacity: 200); - private readonly Dictionary minionObjectIdsToShow = new(capacity: 200); private bool isChangingTerritory; - private readonly HashSet idToDelete = new(capacity: 200); + /// + /// Constructor for FrameworkHandler + /// + public FrameworkHandler() + { + // Initialize managers + this.containerManager = new ContainerManager(); + this.voidListManager = new VoidListManager(); + this.visibilityManager = new ObjectVisibilityManager(); + + // Initialize entity handlers + this.playerHandler = new PlayerHandler(this.containerManager, this.voidListManager, this.visibilityManager); + this.petHandler = new PetHandler(this.containerManager, this.voidListManager, this.visibilityManager); + this.chocoboHandler = new ChocoboHandler(this.containerManager, this.voidListManager, this.visibilityManager); + this.minionHandler = new MinionHandler(this.containerManager, this.visibilityManager); + } + /// + /// Main update method called by the framework + /// public unsafe void Update() { + // Get local player and check if we should process visibility GameObject* localPlayerGameObject = GameObjectManager.Instance()->Objects.IndexSorted[0]; IntPtr namePlateWidget = Service.GameGui.GetAddonByName("NamePlate"); + // Early exit conditions if (namePlateWidget == nint.Zero || (!((AtkUnitBase*)namePlateWidget)->IsVisible && !Service.Condition[ConditionFlag.Performing]) || localPlayerGameObject == null || localPlayerGameObject->EntityId == 0xE0000000 || VisibilityPlugin.Instance.Disable || this.isChangingTerritory) - { return; - } + // Check if player is in a duty or other special area bool isBound = (Service.Condition[ConditionFlag.BoundByDuty] && localPlayerGameObject->EventId.ContentId != EventHandlerContent.TreasureHuntDirector) || Service.Condition[ConditionFlag.BetweenAreas] @@ -117,592 +94,201 @@ public unsafe void Update() Character* localPlayer = (Character*)localPlayerGameObject; + // Process all game objects for (int i = 1; i != 200; ++i) { GameObject* gameObject = GameObjectManager.Instance()->Objects.IndexSorted[i]; Character* characterPtr = (Character*)gameObject; - if (gameObject == null || gameObject == localPlayerGameObject || !gameObject->IsCharacter()) - { - continue; - } + if (gameObject == null || gameObject == localPlayerGameObject || !gameObject->IsCharacter()) continue; + // Process different types of entities switch ((ObjectKind)characterPtr->GameObject.ObjectKind) { case ObjectKind.Player: - this.PlayerHandler(characterPtr, localPlayer, isBound); + this.playerHandler.ProcessPlayer(characterPtr, localPlayer, isBound); break; case ObjectKind.BattleNpc when characterPtr->GameObject.SubKind == (byte)BattleNpcSubKind.Pet && characterPtr->NameId != 6565: - this.PetHandler(characterPtr, localPlayer, isBound); + this.petHandler.ProcessPet(characterPtr, localPlayer, isBound); break; case ObjectKind.BattleNpc - when characterPtr->GameObject.SubKind == (byte)BattleNpcSubKind.Pet && characterPtr->NameId == 6565 - : // Earthly Star - { - if (VisibilityPlugin.Instance.Configuration is { Enabled: true, HideStar: true } - && Service.Condition[ConditionFlag.InCombat] - && characterPtr->GameObject.OwnerId != localPlayer->GameObject.EntityId - && !this.containers[UnitType.Players][ContainerType.Party] - .ContainsKey(characterPtr->GameObject.OwnerId)) - { - this.HideGameObject(characterPtr); - } - - break; - } + when characterPtr->GameObject.SubKind == (byte)BattleNpcSubKind.Pet && characterPtr->NameId == 6565: + // Earthly Star + this.petHandler.ProcessEarthlyStar(characterPtr, localPlayer); + break; case ObjectKind.BattleNpc when characterPtr->GameObject.SubKind == (byte)BattleNpcSubKind.Chocobo: - this.ChocoboHandler(characterPtr, localPlayer); + this.chocoboHandler.ProcessChocobo(characterPtr, localPlayer); break; case ObjectKind.Companion: - this.MinionHandler(characterPtr, localPlayer); + this.minionHandler.ProcessMinion(characterPtr, localPlayer); break; } } - foreach ((UnitType _, Dictionary>? unitContainer) in this.containers) - { - foreach ((ContainerType _, Dictionary? container) in unitContainer) - { - foreach ((uint id, long ticks) in container) - { - if (ticks > Environment.TickCount64 + 5000) - { - this.idToDelete.Add(id); - } - } - - foreach (uint id in this.idToDelete) - { - container.Remove(id); - } - - this.idToDelete.Clear(); - } - } + // Clean up expired entries in containers + this.containerManager.CleanupContainers(); } - private static unsafe bool CheckTargetOfTarget(Character* ptr) + /// + /// Check if a character is the target of the current target + /// + public static unsafe bool CheckTargetOfTarget(Character* ptr) { - if (!VisibilityPlugin.Instance.Configuration.ShowTargetOfTarget) - { - return false; - } + if (!VisibilityPlugin.Instance.Configuration.ShowTargetOfTarget) return false; Character* target = (Character*)TargetSystem.Instance()->Target; - if (target == null || !target->IsCharacter()) - { - return false; - } + if (target == null || !target->IsCharacter()) return false; return CharacterManager.Instance()->LookupBattleCharaByEntityId(target->TargetId.ObjectId) == ptr; } - private unsafe void PlayerHandler(Character* characterPtr, Character* localPlayer, bool isBound) - { - if (characterPtr->GameObject.EntityId == 0xE0000000 || - this.ShowGameObject(characterPtr)) - { - return; - } - - this.containers[UnitType.Players][ContainerType.All][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - - if (characterPtr->IsFriend) - { - this.containers[UnitType.Players][ContainerType.Friend][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - } - else - { - this.containers[UnitType.Players][ContainerType.Friend] - .Remove(characterPtr->GameObject.EntityId); - } - - bool isObjectIdInParty = IsObjectIdInParty(characterPtr->GameObject.EntityId); - - if (isObjectIdInParty) - { - this.containers[UnitType.Players][ContainerType.Party][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - } - else - { - this.containers[UnitType.Players][ContainerType.Party].Remove(characterPtr->GameObject.EntityId); - } - - if (isBound && !VisibilityPlugin.Instance.Configuration.TerritoryTypeWhitelist.Contains( - Service.ClientState.TerritoryType)) - { - return; - } - - if (localPlayer->FreeCompanyTag[0] != 0 - && localPlayer->CurrentWorld == localPlayer->HomeWorld - && characterPtr->FreeCompanyTag.SequenceEqual(localPlayer->FreeCompanyTag)) - { - this.containers[UnitType.Players][ContainerType.Company][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - } - else - { - this.containers[UnitType.Players][ContainerType.Company] - .Remove(characterPtr->GameObject.EntityId); - } - - if (!this.checkedVoidedObjectIds.ContainsKey(characterPtr->GameObject.EntityId)) - { - if (!VisibilityPlugin.Instance.Configuration.VoidDictionary.TryGetValue(characterPtr->ContentId, - out VoidItem? voidedPlayer)) - { - voidedPlayer = VisibilityPlugin.Instance.Configuration.VoidList.Find( - x => characterPtr->GameObject.Name.StartsWith(x.NameBytes) && - x.HomeworldId == characterPtr->HomeWorld); - } - - if (voidedPlayer != null) - { - if (voidedPlayer.Id == 0) - { - voidedPlayer.Id = characterPtr->ContentId; - VisibilityPlugin.Instance.Configuration.Save(); - VisibilityPlugin.Instance.Configuration.VoidDictionary[characterPtr->ContentId] = voidedPlayer; - } - - voidedPlayer.ObjectId = characterPtr->GameObject.EntityId; - this.voidedObjectIds[characterPtr->GameObject.EntityId] = Environment.TickCount64; - } - - this.checkedVoidedObjectIds[characterPtr->GameObject.EntityId] = Environment.TickCount64; - } - - if (this.voidedObjectIds.ContainsKey(characterPtr->GameObject.EntityId)) - { - this.HideGameObject(characterPtr); - return; - } - - if (((VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowDeadPlayer && - characterPtr->GameObject.IsDead()) || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowPartyPlayer && isObjectIdInParty) || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowFriendPlayer && characterPtr->IsFriend) || - CheckTargetOfTarget(characterPtr)) && - this.hiddenObjectIds.ContainsKey(characterPtr->GameObject.EntityId)) - { - characterPtr->GameObject.RenderFlags &= ~(int)VisibilityFlags.Invisible; - this.hiddenObjectIds.Remove(characterPtr->GameObject.EntityId); - return; - } - - if (!VisibilityPlugin.Instance.Configuration.Enabled || - !VisibilityPlugin.Instance.Configuration.CurrentConfig.HidePlayer || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowDeadPlayer && - characterPtr->GameObject.IsDead()) || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowFriendPlayer && - this.containers[UnitType.Players][ContainerType.Friend] - .ContainsKey(characterPtr->GameObject.EntityId)) || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowCompanyPlayer && - this.containers[UnitType.Players][ContainerType.Company] - .ContainsKey(characterPtr->GameObject.EntityId)) || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowPartyPlayer && - this.containers[UnitType.Players][ContainerType.Party] - .ContainsKey(characterPtr->GameObject.EntityId)) || - CheckTargetOfTarget(characterPtr)) - { - return; - } - - if (!this.checkedWhitelistedObjectIds.ContainsKey(characterPtr->GameObject.EntityId)) - { - if (!VisibilityPlugin.Instance.Configuration.WhitelistDictionary.TryGetValue(characterPtr->ContentId, - out VoidItem? whitelistedPlayer)) - { - whitelistedPlayer = VisibilityPlugin.Instance.Configuration.Whitelist.Find( - x => characterPtr->GameObject.Name.StartsWith(x.NameBytes) && - x.HomeworldId == characterPtr->HomeWorld); - } - - if (whitelistedPlayer != null) - { - if (whitelistedPlayer.Id == 0) - { - whitelistedPlayer.Id = characterPtr->ContentId; - VisibilityPlugin.Instance.Configuration.Save(); - VisibilityPlugin.Instance.Configuration.WhitelistDictionary[characterPtr->ContentId] = whitelistedPlayer; - } - - whitelistedPlayer.ObjectId = characterPtr->GameObject.EntityId; - this.whitelistedObjectIds[characterPtr->GameObject.EntityId] = Environment.TickCount64; - } - - this.checkedWhitelistedObjectIds[characterPtr->GameObject.EntityId] = Environment.TickCount64; - } - - if (this.whitelistedObjectIds.ContainsKey(characterPtr->GameObject.EntityId)) - { - return; - } - - this.HideGameObject(characterPtr); - } - - private unsafe void PetHandler(Character* characterPtr, Character* localPlayer, bool isBound) - { - // Ignore own pet - if (characterPtr->GameObject.OwnerId == localPlayer->GameObject.EntityId || - this.ShowGameObject(characterPtr)) - { - return; - } - - this.containers[UnitType.Pets][ContainerType.All][characterPtr->GameObject.EntityId] = Environment.TickCount64; - - if (this.containers[UnitType.Players][ContainerType.Friend] - .ContainsKey(characterPtr->GameObject.OwnerId)) - { - this.containers[UnitType.Pets][ContainerType.Friend][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - } - - if (this.containers[UnitType.Players][ContainerType.Party] - .ContainsKey(characterPtr->GameObject.OwnerId)) - { - this.containers[UnitType.Pets][ContainerType.Party][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - } - - if (this.containers[UnitType.Players][ContainerType.Company] - .ContainsKey(characterPtr->GameObject.OwnerId)) - { - this.containers[UnitType.Pets][ContainerType.Company][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - } - - // Do not hide pets in duties - if (isBound) - { - return; - } - - // Hide pet if it belongs to a voided player - if (this.voidedObjectIds.ContainsKey(characterPtr->GameObject.OwnerId)) - { - this.HideGameObject(characterPtr); - return; - } - - if (!VisibilityPlugin.Instance.Configuration.Enabled || - !VisibilityPlugin.Instance.Configuration.CurrentConfig.HidePet || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowFriendPet && - this.containers[UnitType.Players][ContainerType.Friend] - .ContainsKey(characterPtr->GameObject.OwnerId)) || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowCompanyPet && - this.containers[UnitType.Players][ContainerType.Company] - .ContainsKey(characterPtr->GameObject.OwnerId)) || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowPartyPet && - this.containers[UnitType.Players][ContainerType.Party] - .ContainsKey(characterPtr->GameObject.OwnerId)) || - this.whitelistedObjectIds.ContainsKey(characterPtr->GameObject.OwnerId)) - { - return; - } - - this.HideGameObject(characterPtr); - } - - private unsafe void ChocoboHandler(Character* characterPtr, Character* localPlayer) - { - // Ignore own chocobo - if (characterPtr->GameObject.OwnerId == localPlayer->GameObject.EntityId || - this.ShowGameObject(characterPtr)) - { - return; - } - - this.containers[UnitType.Chocobos][ContainerType.All][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - - if (this.containers[UnitType.Players][ContainerType.Friend] - .ContainsKey(characterPtr->GameObject.OwnerId)) - { - this.containers[UnitType.Chocobos][ContainerType.Friend][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - } - - if (this.containers[UnitType.Players][ContainerType.Party] - .ContainsKey(characterPtr->GameObject.OwnerId)) - { - this.containers[UnitType.Chocobos][ContainerType.Party][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - } - - if (this.containers[UnitType.Players][ContainerType.Company] - .ContainsKey(characterPtr->GameObject.OwnerId)) - { - this.containers[UnitType.Chocobos][ContainerType.Company][characterPtr->GameObject.EntityId] = - Environment.TickCount64; - } - - // Hide chocobo if it belongs to a voided player - if (this.voidedObjectIds.ContainsKey(characterPtr->GameObject.OwnerId)) - { - this.HideGameObject(characterPtr); - return; - } - - if (!VisibilityPlugin.Instance.Configuration.Enabled || - !VisibilityPlugin.Instance.Configuration.CurrentConfig.HideChocobo || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowFriendChocobo && - this.containers[UnitType.Players][ContainerType.Friend] - .ContainsKey(characterPtr->GameObject.OwnerId)) || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowCompanyChocobo && - this.containers[UnitType.Players][ContainerType.Company] - .ContainsKey(characterPtr->GameObject.OwnerId)) || - (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowPartyChocobo && - this.containers[UnitType.Players][ContainerType.Party] - .ContainsKey(characterPtr->GameObject.OwnerId)) || - this.whitelistedObjectIds.ContainsKey(characterPtr->GameObject.OwnerId)) - { - return; - } - - this.HideGameObject(characterPtr); - } - - private unsafe void MinionHandler(Character* characterPtr, Character* localPlayer) - { - if (localPlayer == null || - characterPtr->CompanionOwnerId == localPlayer->GameObject.EntityId || - this.ShowGameObject(characterPtr, ObjectType.Companion)) - { - return; - } - - if (!VisibilityPlugin.Instance.Configuration.Enabled || - !VisibilityPlugin.Instance.Configuration.CurrentConfig.HideMinion) - { - return; - } - - this.containers[UnitType.Minions][ContainerType.All][characterPtr->CompanionOwnerId] = Environment.TickCount64; - - if (this.containers[UnitType.Players][ContainerType.Friend] - .ContainsKey(characterPtr->CompanionOwnerId)) - { - this.containers[UnitType.Minions][ContainerType.Friend][characterPtr->CompanionOwnerId] = - Environment.TickCount64; - } - - if (this.containers[UnitType.Players][ContainerType.Party] - .ContainsKey(characterPtr->CompanionOwnerId)) - { - this.containers[UnitType.Minions][ContainerType.Party][characterPtr->CompanionOwnerId] = - Environment.TickCount64; - } - - if (this.containers[UnitType.Players][ContainerType.Company] - .ContainsKey(characterPtr->CompanionOwnerId)) - { - this.containers[UnitType.Minions][ContainerType.Company][characterPtr->CompanionOwnerId] = - Environment.TickCount64; - } - - if ((VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowFriendMinion && - this.containers[UnitType.Players][ContainerType.Friend] - .ContainsKey(characterPtr->CompanionOwnerId)) - || (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowCompanyMinion && - this.containers[UnitType.Players][ContainerType.Company] - .ContainsKey(characterPtr->CompanionOwnerId)) - || (VisibilityPlugin.Instance.Configuration.CurrentConfig.ShowPartyMinion && - this.containers[UnitType.Players][ContainerType.Party] - .ContainsKey(characterPtr->CompanionOwnerId))) - { - return; - } - - this.HideGameObject(characterPtr, ObjectType.Companion); - } - - private unsafe void HideGameObject(Character* thisPtr, ObjectType objectType = ObjectType.Character) - { - switch (objectType) - { - case ObjectType.Character when !thisPtr->GameObject.RenderFlags.TestFlag(VisibilityFlags.Invisible): - this.hiddenObjectIds[thisPtr->GameObject.EntityId] = Environment.TickCount64; - thisPtr->GameObject.RenderFlags |= (int)VisibilityFlags.Invisible; - break; - case ObjectType.Companion when !thisPtr->GameObject.RenderFlags.TestFlag(VisibilityFlags.Invisible): - this.hiddenMinionObjectIds[thisPtr->CompanionOwnerId] = Environment.TickCount64; - thisPtr->GameObject.RenderFlags |= (int)VisibilityFlags.Invisible; - break; - } - } - - private unsafe bool ShowGameObject(Character* thisPtr, ObjectType objectType = ObjectType.Character) - { - switch (objectType) - { - case ObjectType.Character when this.objectIdsToShow.ContainsKey(thisPtr->GameObject.EntityId) && - thisPtr->GameObject.RenderFlags.TestFlag(VisibilityFlags.Invisible): - this.hiddenObjectIds.Remove(thisPtr->GameObject.EntityId); - this.objectIdsToShow.Remove(thisPtr->GameObject.EntityId); - thisPtr->GameObject.RenderFlags &= ~(int)VisibilityFlags.Invisible; - return true; - case ObjectType.Companion when this.minionObjectIdsToShow.ContainsKey(thisPtr->CompanionOwnerId) && - thisPtr->GameObject.RenderFlags.TestFlag(VisibilityFlags.Invisible): - this.hiddenMinionObjectIds.Remove(thisPtr->CompanionOwnerId); - this.minionObjectIdsToShow.Remove(thisPtr->CompanionOwnerId); - thisPtr->GameObject.RenderFlags &= ~(int)VisibilityFlags.Invisible; - return true; - } - - return false; - } - - private static unsafe bool IsObjectIdInParty(uint objectId) + /// + /// Check if an object ID is in the player's party + /// + public static unsafe bool IsObjectIdInParty(uint objectId) { GroupManager* groupManager = GroupManager.Instance(); InfoProxyCrossRealm* infoProxyCrossRealm = InfoProxyCrossRealm.Instance(); - if (groupManager->MainGroup.MemberCount > 0 && groupManager->MainGroup.IsEntityIdInParty(objectId)) - { - return true; - } + // Check regular party + if (groupManager->MainGroup.MemberCount > 0 && groupManager->MainGroup.IsEntityIdInParty(objectId)) return true; - if (infoProxyCrossRealm->IsInCrossRealmParty == 0) - { - return false; - } + // Check cross-realm party + if (!infoProxyCrossRealm->IsInCrossRealmParty) return false; foreach (CrossRealmGroup group in infoProxyCrossRealm->CrossRealmGroups) { - if (group.GroupMembers.Length == 0) - { - continue; - } + if (group.GroupMembers.Length == 0) continue; for (int i = 0; i < group.GroupMembers.Length; ++i) { if (group.GroupMembers[i].EntityId == objectId) - { return true; - } } } return false; } + /// + /// Show entities of a specific type and container + /// public void Show(UnitType unitType, ContainerType containerType) { - if (unitType == UnitType.Minions) - { - this.containers[unitType][containerType].ToList().ForEach( - x => - { - this.minionObjectIdsToShow[x.Key] = x.Value; - this.hiddenMinionObjectIds.Remove(x.Key); - }); - } - else + // Get all entities in the container + IEnumerable> entities = this.containerManager.GetContainerEntities(unitType, containerType); + + // Mark each entity to be shown + foreach (KeyValuePair entity in entities) { - this.containers[unitType][containerType].ToList().ForEach( - x => - { - this.objectIdsToShow[x.Key] = x.Value; - this.hiddenObjectIds.Remove(x.Key); - }); + this.visibilityManager.MarkObjectToShow(entity.Key, + unitType == UnitType.Minions + ? ObjectVisibilityManager.ObjectType.Companion + : ObjectVisibilityManager.ObjectType.Character); } - this.containers[unitType][containerType].Clear(); + // Clear the container after processing + this.containerManager.ClearContainer(unitType, containerType); } + /// + /// Handle territory change events + /// public void OnTerritoryChanged() { this.isChangingTerritory = true; - this.hiddenObjectIds.Clear(); - this.objectIdsToShow.Clear(); - this.hiddenMinionObjectIds.Clear(); - this.minionObjectIdsToShow.Clear(); - this.checkedVoidedObjectIds.Clear(); - this.checkedWhitelistedObjectIds.Clear(); - this.voidedObjectIds.Clear(); - this.whitelistedObjectIds.Clear(); - - foreach ((UnitType _, Dictionary>? unitContainer) in this.containers) - { - foreach ((ContainerType _, Dictionary? container) in unitContainer) - { - container.Clear(); - } - } + + // Clear visibility states + this.visibilityManager.ClearAll(); + + // Clear void and whitelist states + this.voidListManager.ClearAll(); + + // Clear all containers + this.containerManager.ClearAllContainers(); this.isChangingTerritory = false; } + /// + /// Show all players in a specific container + /// public void ShowPlayers(ContainerType type) => this.Show(UnitType.Players, type); + /// + /// Show all pets in a specific container + /// public void ShowPets(ContainerType type) => this.Show(UnitType.Pets, type); + /// + /// Show all chocobos in a specific container + /// public void ShowChocobos(ContainerType type) => this.Show(UnitType.Chocobos, type); + /// + /// Show all minions in a specific container + /// public void ShowMinions(ContainerType type) => this.Show(UnitType.Minions, type); + /// + /// Show all hidden entities + /// public unsafe void ShowAll() { - if (Service.ClientState.LocalPlayer == null) - { - return; - } + if (Service.ClientState.LocalPlayer == null) return; - foreach (Dalamud.Game.ClientState.Objects.Types.IGameObject? actor in Service.ObjectTable) + // Process all game objects in the object table + foreach (Dalamud.Game.ClientState.Objects.Types.IGameObject gameObject in Service.ObjectTable) { - Character* thisPtr = (Character*)actor.Address; + Character* thisPtr = (Character*)gameObject.Address; + // Handle companions (minions) if ((byte)thisPtr->GameObject.ObjectKind == (byte)ObjectKind.Companion) { - if (!this.hiddenMinionObjectIds.ContainsKey(thisPtr->CompanionOwnerId)) - { - continue; - } + // Skip if not hidden + if (!this.visibilityManager.IsObjectHidden(thisPtr->CompanionOwnerId, + ObjectVisibilityManager.ObjectType.Companion)) continue; - this.minionObjectIdsToShow[thisPtr->CompanionOwnerId] = Environment.TickCount64; - this.hiddenMinionObjectIds.Remove(thisPtr->CompanionOwnerId); + // Mark to show + this.visibilityManager.MarkObjectToShow(thisPtr->CompanionOwnerId, + ObjectVisibilityManager.ObjectType.Companion); } - else + else // Handle characters (players, pets, chocobos) { - if (!this.hiddenObjectIds.ContainsKey(thisPtr->GameObject.EntityId)) - { - continue; - } - - this.RemoveChecked(thisPtr->GameObject.EntityId); - this.objectIdsToShow[thisPtr->GameObject.EntityId] = Environment.TickCount64; - this.hiddenObjectIds.Remove(thisPtr->GameObject.EntityId); + // Skip if not hidden + if (!this.visibilityManager.IsObjectHidden(thisPtr->GameObject.EntityId)) continue; + + // Remove from void and whitelist checks + this.voidListManager.RemoveChecked(thisPtr->GameObject.EntityId); + + // Mark to show + this.visibilityManager.MarkObjectToShow(thisPtr->GameObject.EntityId); } } } + /// + /// Remove an entity from the checked lists + /// public void RemoveChecked(uint id) { - this.voidedObjectIds.Remove(id); - this.whitelistedObjectIds.Remove(id); - this.checkedVoidedObjectIds.Remove(id); - this.checkedWhitelistedObjectIds.Remove(id); + this.voidListManager.RemoveChecked(id); } + /// + /// Show a specific player by ID + /// public void ShowPlayer(uint id) { - if (!this.hiddenObjectIds.ContainsKey(id)) - { - return; - } + if (!this.visibilityManager.IsObjectHidden(id)) return; - this.objectIdsToShow[id] = Environment.TickCount64; - this.hiddenObjectIds.Remove(id); + this.visibilityManager.MarkObjectToShow(id); } + /// + /// Dispose the framework handler and show all hidden entities + /// public void Dispose() => this.ShowAll(); } diff --git a/Visibility/Utils/ImGuiElements.cs b/Visibility/Utils/ImGuiElements.cs index 70fb92b..63e7137 100644 --- a/Visibility/Utils/ImGuiElements.cs +++ b/Visibility/Utils/ImGuiElements.cs @@ -3,37 +3,40 @@ using System.Numerics; using System.Text; -using ImGuiNET; +using Dalamud.Bindings.ImGui; namespace Visibility.Utils; public static class ImGuiElements { - public static void Checkbox(bool value, string name) + public static bool Checkbox(bool value, string name) { if (!ImGui.Checkbox($"###{name}", ref value)) { - return; + return false; } - if (!VisibilityPlugin.Instance.Configuration.SettingDictionary.TryGetValue(name, - out Action? onValueChanged)) + Action? onValueChanged = + VisibilityPlugin.Instance.Configuration.SettingsHandler.GetAction(name); + + if (onValueChanged == null) { - return; + return false; } onValueChanged(value, false, true); VisibilityPlugin.Instance.Configuration.Save(); + return true; } - public static void CenteredCheckbox(bool value, string name) + public static bool CenteredCheckbox(bool value, string name) { ImGui.SetCursorPosX( ImGui.GetCursorPosX() + ((ImGui.GetColumnWidth() + (2 * ImGui.GetStyle().FramePadding.X)) / 2) - (2 * ImGui.GetStyle().ItemSpacing.X) - (2 * ImGui.GetStyle().CellPadding.X)); - Checkbox(value, name); + return Checkbox(value, name); } /// @@ -95,7 +98,7 @@ public static bool ComboWithFilter( comboNewOpen = true; } - ImGui.InputText("##ComboWithFilter_inputText", textBuffer, (uint)textBuffer.Length); + ImGui.InputText("##ComboWithFilter_inputText", textBuffer); if (fontPtr.HasValue) { diff --git a/Visibility/Utils/ObjectVisibilityManager.cs b/Visibility/Utils/ObjectVisibilityManager.cs new file mode 100644 index 0000000..a7eeaa3 --- /dev/null +++ b/Visibility/Utils/ObjectVisibilityManager.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; + +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +namespace Visibility.Utils; + +/// +/// Manages the visibility state of game objects +/// +public class ObjectVisibilityManager +{ + private readonly Dictionary hiddenObjectIds = new(capacity: 200); + private readonly Dictionary objectIdsToShow = new(capacity: 200); + private readonly Dictionary hiddenMinionObjectIds = new(capacity: 200); + private readonly Dictionary minionObjectIdsToShow = new(capacity: 200); + + /// + /// Enum to distinguish between different object types + /// + public enum ObjectType + { + Character, + Companion + } + + /// + /// Hide a game object by setting its render flags + /// + public unsafe void HideGameObject(Character* thisPtr, ObjectType objectType = ObjectType.Character) + { + switch (objectType) + { + case ObjectType.Character when !thisPtr->GameObject.RenderFlags.TestFlag(VisibilityFlags.Invisible): + this.hiddenObjectIds[thisPtr->GameObject.EntityId] = Environment.TickCount64; + thisPtr->GameObject.RenderFlags |= (int)VisibilityFlags.Invisible; + break; + case ObjectType.Companion when !thisPtr->GameObject.RenderFlags.TestFlag(VisibilityFlags.Invisible): + this.hiddenMinionObjectIds[thisPtr->CompanionOwnerId] = Environment.TickCount64; + thisPtr->GameObject.RenderFlags |= (int)VisibilityFlags.Invisible; + break; + } + } + + /// + /// Show a previously hidden game object + /// + public unsafe bool ShowGameObject(Character* thisPtr, ObjectType objectType = ObjectType.Character) + { + switch (objectType) + { + case ObjectType.Character when this.objectIdsToShow.ContainsKey(thisPtr->GameObject.EntityId) && + thisPtr->GameObject.RenderFlags.TestFlag(VisibilityFlags.Invisible): + this.hiddenObjectIds.Remove(thisPtr->GameObject.EntityId); + this.objectIdsToShow.Remove(thisPtr->GameObject.EntityId); + thisPtr->GameObject.RenderFlags &= ~(int)VisibilityFlags.Invisible; + return true; + case ObjectType.Companion when this.minionObjectIdsToShow.ContainsKey(thisPtr->CompanionOwnerId) && + thisPtr->GameObject.RenderFlags.TestFlag(VisibilityFlags.Invisible): + this.hiddenMinionObjectIds.Remove(thisPtr->CompanionOwnerId); + this.minionObjectIdsToShow.Remove(thisPtr->CompanionOwnerId); + thisPtr->GameObject.RenderFlags &= ~(int)VisibilityFlags.Invisible; + return true; + } + + return false; + } + + /// + /// Check if an object is currently hidden + /// + public bool IsObjectHidden(uint entityId, ObjectType objectType = ObjectType.Character) + { + return objectType == ObjectType.Character + ? this.hiddenObjectIds.ContainsKey(entityId) + : this.hiddenMinionObjectIds.ContainsKey(entityId); + } + + /// + /// Mark an object to be shown + /// + public void MarkObjectToShow(uint entityId, ObjectType objectType = ObjectType.Character) + { + if (objectType == ObjectType.Character) + { + if (!this.hiddenObjectIds.ContainsKey(entityId)) return; + + this.objectIdsToShow[entityId] = Environment.TickCount64; + this.hiddenObjectIds.Remove(entityId); + } + else + { + if (!this.hiddenMinionObjectIds.ContainsKey(entityId)) return; + + this.minionObjectIdsToShow[entityId] = Environment.TickCount64; + this.hiddenMinionObjectIds.Remove(entityId); + } + } + + /// + /// Clear all visibility states + /// + public void ClearAll() + { + this.hiddenObjectIds.Clear(); + this.objectIdsToShow.Clear(); + this.hiddenMinionObjectIds.Clear(); + this.minionObjectIdsToShow.Clear(); + } +} diff --git a/Visibility/Utils/VoidListManager.cs b/Visibility/Utils/VoidListManager.cs new file mode 100644 index 0000000..1539a30 --- /dev/null +++ b/Visibility/Utils/VoidListManager.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; + +using FFXIVClientStructs.FFXIV.Client.Game.Character; + +using Visibility.Void; + +namespace Visibility.Utils; + +/// +/// Manages void and whitelist functionality for game objects +/// +public class VoidListManager +{ + private readonly Dictionary checkedVoidedObjectIds = new(capacity: 1000); + private readonly Dictionary checkedWhitelistedObjectIds = new(capacity: 1000); + private readonly Dictionary voidedObjectIds = new(capacity: 1000); + private readonly Dictionary whitelistedObjectIds = new(capacity: 1000); + + /// + /// Check if an object is in the void list + /// + public unsafe bool CheckAndProcessVoidList(Character* characterPtr) + { + if (this.checkedVoidedObjectIds.ContainsKey(characterPtr->GameObject.EntityId)) + return this.voidedObjectIds.ContainsKey(characterPtr->GameObject.EntityId); + + if (!VisibilityPlugin.Instance.Configuration.VoidDictionary.TryGetValue(characterPtr->ContentId, + out VoidItem? voidedPlayer)) + { + voidedPlayer = VisibilityPlugin.Instance.Configuration.VoidList.Find(x => + characterPtr->GameObject.Name.StartsWith(x.NameBytes) && + x.HomeworldId == characterPtr->HomeWorld); + } + + if (voidedPlayer != null) + { + if (voidedPlayer.Id == 0) + { + voidedPlayer.Id = characterPtr->ContentId; + VisibilityPlugin.Instance.Configuration.Save(); + VisibilityPlugin.Instance.Configuration.VoidDictionary[characterPtr->ContentId] = voidedPlayer; + } + + voidedPlayer.ObjectId = characterPtr->GameObject.EntityId; + this.voidedObjectIds[characterPtr->GameObject.EntityId] = Environment.TickCount64; + } + + this.checkedVoidedObjectIds[characterPtr->GameObject.EntityId] = Environment.TickCount64; + + return this.voidedObjectIds.ContainsKey(characterPtr->GameObject.EntityId); + } + + /// + /// Check if an object is in the whitelist + /// + public unsafe bool CheckAndProcessWhitelist(Character* characterPtr) + { + if (this.checkedWhitelistedObjectIds.ContainsKey(characterPtr->GameObject.EntityId)) + return this.whitelistedObjectIds.ContainsKey(characterPtr->GameObject.EntityId); + + if (!VisibilityPlugin.Instance.Configuration.WhitelistDictionary.TryGetValue(characterPtr->ContentId, + out VoidItem? whitelistedPlayer)) + { + whitelistedPlayer = VisibilityPlugin.Instance.Configuration.Whitelist.Find(x => + characterPtr->GameObject.Name.StartsWith(x.NameBytes) && + x.HomeworldId == characterPtr->HomeWorld); + } + + if (whitelistedPlayer != null) + { + if (whitelistedPlayer.Id == 0) + { + whitelistedPlayer.Id = characterPtr->ContentId; + VisibilityPlugin.Instance.Configuration.Save(); + VisibilityPlugin.Instance.Configuration.WhitelistDictionary[characterPtr->ContentId] = + whitelistedPlayer; + } + + whitelistedPlayer.ObjectId = characterPtr->GameObject.EntityId; + this.whitelistedObjectIds[characterPtr->GameObject.EntityId] = Environment.TickCount64; + } + + this.checkedWhitelistedObjectIds[characterPtr->GameObject.EntityId] = Environment.TickCount64; + + return this.whitelistedObjectIds.ContainsKey(characterPtr->GameObject.EntityId); + } + + /// + /// Check if an object ID is in the void list + /// + public bool IsObjectVoided(uint objectId) + { + return this.voidedObjectIds.ContainsKey(objectId); + } + + /// + /// Check if an object ID is in the whitelist + /// + public bool IsObjectWhitelisted(uint objectId) + { + return this.whitelistedObjectIds.ContainsKey(objectId); + } + + /// + /// Remove an object from the checked lists + /// + public void RemoveChecked(uint id) + { + this.voidedObjectIds.Remove(id); + this.whitelistedObjectIds.Remove(id); + this.checkedVoidedObjectIds.Remove(id); + this.checkedWhitelistedObjectIds.Remove(id); + } + + /// + /// Clear all lists + /// + public void ClearAll() + { + this.checkedVoidedObjectIds.Clear(); + this.checkedWhitelistedObjectIds.Clear(); + this.voidedObjectIds.Clear(); + this.whitelistedObjectIds.Clear(); + } +} diff --git a/Visibility/Visibility.csproj b/Visibility/Visibility.csproj index 9cabfda..1c9216f 100644 --- a/Visibility/Visibility.csproj +++ b/Visibility/Visibility.csproj @@ -1,6 +1,6 @@ - + - 1.1.8.3 + 1.1.9.0 $(AssemblyVersion) false SheepGoMeh diff --git a/Visibility/VisibilityPlugin.cs b/Visibility/VisibilityPlugin.cs index e60e2f4..062f9c4 100644 --- a/Visibility/VisibilityPlugin.cs +++ b/Visibility/VisibilityPlugin.cs @@ -1,25 +1,16 @@ using System; using System.Linq; -using System.Threading.Tasks; -using Dalamud.Game.Text; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.Command; -using Dalamud.Interface.Windowing; using Dalamud.Plugin; -using Dalamud.Plugin.Services; using Visibility.Api; using Visibility.Configuration; using Visibility.Ipc; using Visibility.Utils; -using Visibility.Void; -using FFXIVClientStructs.FFXIV.Client.Game.Character; - -using Lumina.Excel.Sheets; +using Visibility.Handlers; +using Visibility.Managers; namespace Visibility; @@ -27,35 +18,26 @@ public class VisibilityPlugin: IDalamudPlugin { public string Name => "Visibility"; - private static string PluginCommandName => "/pvis"; - - private static string VoidCommandName => "/void"; - - private static string VoidTargetCommandName => "/voidtarget"; - - private static string WhitelistCommandName => "/whitelist"; - - private static string WhitelistTargetCommandName => "/whitelisttarget"; - public readonly Localization PluginLocalization; public readonly VisibilityConfiguration Configuration; - - // TODO: Switch to dalamud service - // public readonly ContextMenu ContextMenu; + public readonly FrameworkHandler FrameworkHandler; public static VisibilityPlugin Instance { get; private set; } = null!; - private bool refresh; - public bool Disable; - private readonly FrameworkHandler frameworkHandler; + public bool Disable + { + get => this.frameworkUpdateHandler.Disable; + set => this.frameworkUpdateHandler.Disable = value; + } public VisibilityApi Api { get; } - public VisibilityProvider IpcProvider { get; } - public WindowSystem WindowSystem { get; } - - public Windows.Configuration ConfigurationWindow { get; } + public readonly CommandManagerHandler CommandManagerHandler; + private readonly ChatHandler chatHandler; + private readonly FrameworkUpdateHandler frameworkUpdateHandler; + private readonly TerritoryChangeHandler territoryChangeHandler; + private readonly UiManager uiManager; public VisibilityPlugin(IDalamudPluginInterface pluginInterface) { @@ -67,112 +49,20 @@ public VisibilityPlugin(IDalamudPluginInterface pluginInterface) this.Configuration.Init(Service.ClientState.TerritoryType); this.PluginLocalization = new Localization(this.Configuration.Language); - // TODO: Switch to dalamud service - // this.ContextMenu = new ContextMenu(); - // - // if (this.Configuration.EnableContextMenu) - // { - // this.ContextMenu.Toggle(); - // } + this.FrameworkHandler = new FrameworkHandler(); - Service.CommandManager.AddHandler( - PluginCommandName, - new CommandInfo(this.PluginCommand) - { - HelpMessage = this.PluginLocalization.PluginCommandHelpMessage, ShowInHelp = true - }); - - Service.CommandManager.AddHandler( - VoidCommandName, - new CommandInfo(this.VoidPlayer) - { - HelpMessage = this.PluginLocalization.VoidPlayerHelpMessage, ShowInHelp = true - }); - - Service.CommandManager.AddHandler( - VoidTargetCommandName, - new CommandInfo(this.VoidTargetPlayer) - { - HelpMessage = this.PluginLocalization.VoidTargetPlayerHelpMessage, ShowInHelp = true - }); - - Service.CommandManager.AddHandler( - WhitelistCommandName, - new CommandInfo(this.WhitelistPlayer) - { - HelpMessage = this.PluginLocalization.WhitelistPlayerHelpMessage, ShowInHelp = true - }); - - Service.CommandManager.AddHandler( - WhitelistTargetCommandName, - new CommandInfo(this.WhitelistTargetPlayer) - { - HelpMessage = this.PluginLocalization.WhitelistTargetPlayerHelpMessage, ShowInHelp = true - }); - - this.frameworkHandler = new FrameworkHandler(); - - Service.Framework.Update += this.FrameworkOnOnUpdateEvent; - - this.WindowSystem = new WindowSystem("VisibilityPlugin"); - this.ConfigurationWindow = new Windows.Configuration(); - this.WindowSystem.AddWindow(this.ConfigurationWindow); - - Service.PluginInterface.UiBuilder.Draw += this.BuildUi; - Service.PluginInterface.UiBuilder.OpenConfigUi += this.OpenConfigUi; - Service.ChatGui.ChatMessage += this.OnChatMessage; - Service.ClientState.TerritoryChanged += this.ClientStateOnTerritoryChanged; + this.uiManager = new UiManager(pluginInterface); + this.frameworkUpdateHandler = + new FrameworkUpdateHandler(this.FrameworkHandler, this.Configuration, this.PluginLocalization); + this.CommandManagerHandler = new CommandManagerHandler(this.Configuration, this.PluginLocalization, + this.FrameworkHandler, this.uiManager, this.frameworkUpdateHandler); + this.chatHandler = new ChatHandler(this.Configuration); + this.territoryChangeHandler = new TerritoryChangeHandler(this.FrameworkHandler, this.Configuration); this.Api = new VisibilityApi(); this.IpcProvider = new VisibilityProvider(this.Api); } - private void ClientStateOnTerritoryChanged(ushort e) - { - this.frameworkHandler.OnTerritoryChanged(); - - if (this.Configuration.AdvancedEnabled == false) - { - return; - } - - this.Configuration.Enabled = false; - this.Configuration.UpdateCurrentConfig(Service.ClientState.TerritoryType); - this.Configuration.Enabled = true; - } - - private void FrameworkOnOnUpdateEvent(IFramework framework) - { - if (this.Disable) - { - this.frameworkHandler.ShowAll(); - - this.Disable = false; - - if (this.refresh) - { - Task.Run( - async () => - { - await Task.Delay(250); - this.Configuration.Enabled = true; - Service.ChatGui.Print(this.PluginLocalization.RefreshComplete); - }); - } - - this.refresh = false; - } - else if (this.refresh) - { - this.Disable = true; - this.Configuration.Enabled = false; - } - else - { - this.frameworkHandler.Update(); - } - } - protected virtual void Dispose(bool disposing) { if (!disposing) @@ -180,25 +70,14 @@ protected virtual void Dispose(bool disposing) return; } - this.WindowSystem.RemoveAllWindows(); this.IpcProvider.Dispose(); this.Api.Dispose(); - - // TODO: Switch to dalamud service - // this.ContextMenu.Dispose(); - - Service.ClientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; - Service.Framework.Update -= this.FrameworkOnOnUpdateEvent; - Service.PluginInterface.UiBuilder.Draw -= this.BuildUi; - Service.PluginInterface.UiBuilder.OpenConfigUi -= this.OpenConfigUi; - Service.ChatGui.ChatMessage -= this.OnChatMessage; - Service.CommandManager.RemoveHandler(PluginCommandName); - Service.CommandManager.RemoveHandler(VoidCommandName); - Service.CommandManager.RemoveHandler(VoidTargetCommandName); - Service.CommandManager.RemoveHandler(WhitelistCommandName); - Service.CommandManager.RemoveHandler(WhitelistTargetCommandName); - - this.frameworkHandler.Dispose(); + this.territoryChangeHandler.Dispose(); + this.chatHandler.Dispose(); + this.CommandManagerHandler.Dispose(); + this.frameworkUpdateHandler.Dispose(); + this.uiManager.Dispose(); + this.FrameworkHandler.Dispose(); } public void Dispose() @@ -208,19 +87,19 @@ public void Dispose() } public void Show(UnitType unitType, ContainerType containerType) => - this.frameworkHandler.Show(unitType, containerType); + this.FrameworkHandler.Show(unitType, containerType); - public void ShowPlayers(ContainerType type) => this.frameworkHandler.ShowPlayers(type); + public void ShowPlayers(ContainerType type) => this.FrameworkHandler.ShowPlayers(type); - public void ShowPets(ContainerType type) => this.frameworkHandler.ShowPets(type); + public void ShowPets(ContainerType type) => this.FrameworkHandler.ShowPets(type); - public void ShowMinions(ContainerType type) => this.frameworkHandler.ShowMinions(type); + public void ShowMinions(ContainerType type) => this.FrameworkHandler.ShowMinions(type); - public void ShowChocobos(ContainerType type) => this.frameworkHandler.ShowChocobos(type); + public void ShowChocobos(ContainerType type) => this.FrameworkHandler.ShowChocobos(type); - public void ShowPlayer(uint id) => this.frameworkHandler.ShowPlayer(id); + public void ShowPlayer(uint id) => this.FrameworkHandler.ShowPlayer(id); - public void RemoveChecked(uint id) => this.frameworkHandler.RemoveChecked(id); + public void RemoveChecked(uint id) => this.FrameworkHandler.RemoveChecked(id); public void RemoveChecked(string name) { @@ -231,7 +110,7 @@ public void RemoveChecked(string name) if (gameObject != null) { - this.frameworkHandler.RemoveChecked(gameObject.EntityId); + this.FrameworkHandler.RemoveChecked(gameObject.EntityId); } } @@ -244,425 +123,9 @@ public void ShowPlayer(string name) if (gameObject != null) { - this.frameworkHandler.ShowPlayer(gameObject.EntityId); - } - } - - private void PluginCommand(string command, string arguments) - { - if (this.refresh) - { - return; - } - - if (string.IsNullOrEmpty(arguments)) - { - this.ConfigurationWindow.Toggle(); - } - else - { - string[] args = arguments.Split(new[] { ' ' }, 2); - - if (args[0].Equals("help", StringComparison.InvariantCultureIgnoreCase)) - { - Service.ChatGui.Print(this.PluginLocalization.PluginCommandHelpMenu1); - Service.ChatGui.Print(this.PluginLocalization.PluginCommandHelpMenu2); - Service.ChatGui.Print(this.PluginLocalization.PluginCommandHelpMenu3); - Service.ChatGui.Print(this.PluginLocalization.PluginCommandHelpMenu4); - - foreach (string? key in this.Configuration.SettingDictionary.Keys) - { - Service.ChatGui.Print($"{key}"); - } - - return; - } - - if (args[0].Equals("refresh", StringComparison.InvariantCulture)) - { - this.RefreshActors(); - return; - } - - if (args.Length != 2) - { - Service.ChatGui.Print(this.PluginLocalization.PluginCommandHelpMenuError); - return; - } - - if (!this.Configuration.SettingDictionary.Keys.Any( - x => x.Equals(args[0], StringComparison.InvariantCultureIgnoreCase))) - { - Service.ChatGui.Print(this.PluginLocalization.PluginCommandHelpMenuInvalidValueError(args[0])); - return; - } - - bool value = false; - bool toggle = false; - - switch (args[1].ToLowerInvariant()) - { - case "0": - case "off": - case "false": - value = false; - break; - - case "1": - case "on": - case "true": - value = true; - break; - - case "toggle": - toggle = true; - break; - default: - Service.ChatGui.Print(this.PluginLocalization.PluginCommandHelpMenuInvalidValueError(args[1])); - return; - } - - this.Configuration.SettingDictionary[args[0]].Invoke(value, toggle, false); - this.Configuration.Save(); - } - } - - public void VoidPlayer(string command, string arguments) - { - if (string.IsNullOrEmpty(arguments)) - { - Service.ChatGui.Print(this.PluginLocalization.NoArgumentsError(this.PluginLocalization.VoidListName)); - return; - } - - string[] args = arguments.Split(new[] { ' ' }, 4); - - if (args.Length < 3) - { - Service.ChatGui.Print( - this.PluginLocalization.NotEnoughArgumentsError(this.PluginLocalization.VoidListName)); - return; - } - - World? world = Service.DataManager.GetExcelSheet().SingleOrDefault(x => - x.DataCenter.ValueNullable?.Region != 0 && - x.Name.ToString().Equals(args[2], StringComparison.InvariantCultureIgnoreCase)); - - if (world is null) - { - Service.ChatGui.Print( - this.PluginLocalization.InvalidWorldNameError(this.PluginLocalization.VoidListName, args[2])); - return; - } - - string playerName = $"{args[0].ToUppercase()} {args[1].ToUppercase()}"; - - VoidItem voidItem; - IGameObject? playerCharacter = Service.ObjectTable.SingleOrDefault( - x => x is IPlayerCharacter character && character.HomeWorld.Value.RowId == world.Value.RowId && - character.Name.TextValue.Equals(playerName, StringComparison.InvariantCultureIgnoreCase)) as IPlayerCharacter; - - if (playerCharacter != null) - { - unsafe - { - Character* character = (Character*)playerCharacter.Address; - voidItem = new VoidItem - { - Id = character->ContentId, - Name = character->NameString, - HomeworldId = world.Value.RowId, - HomeworldName = world.Value.Name.ToString(), - Reason = args.Length == 3 ? string.Empty : args[3], - Manual = command == "VoidUIManual" - }; - } - } - else - { - voidItem = new VoidItem - { - Name = playerName, - HomeworldId = world.Value.RowId, - HomeworldName = world.Value.Name.ToString(), - Reason = args.Length == 3 ? string.Empty : args[3], - Manual = command == "VoidUIManual" - }; - } - - SeString playerString = new( - new PlayerPayload(playerName, world.Value.RowId), - new IconPayload(BitmapFontIcon.CrossWorld), - new TextPayload(world.Value.Name.ToString())); - - if (!this.Configuration.VoidList.Any( - x => - x.Name == voidItem.Name && x.HomeworldId == voidItem.HomeworldId)) - { - this.Configuration.VoidList.Add(voidItem); - this.Configuration.Save(); - - if (playerCharacter != null) - { - this.RemoveChecked(playerCharacter.EntityId); - } - - Service.ChatGui.Print( - this.PluginLocalization.EntryAdded(this.PluginLocalization.VoidListName, playerString)); - } - else - { - Service.ChatGui.Print( - this.PluginLocalization.EntryExistsError(this.PluginLocalization.VoidListName, playerString)); - } - } - - public void VoidTargetPlayer(string command, string arguments) - { - if (Service.ObjectTable.SingleOrDefault( - x => x is IPlayerCharacter - && x.EntityId != 0 - && x.EntityId != Service.ClientState.LocalPlayer?.EntityId - && x.EntityId == Service.ClientState.LocalPlayer?.TargetObjectId) is IPlayerCharacter - playerCharacter) - { - VoidItem voidItem; - - unsafe - { - Character* character = (Character*)playerCharacter.Address; - voidItem = new VoidItem - { - Id = character->ContentId, - Name = character->NameString, - HomeworldId = character->HomeWorld, - HomeworldName = playerCharacter.HomeWorld.Value.Name.ToString(), - Reason = arguments, - Manual = false - }; - } - - SeString playerString = new( - new PlayerPayload(playerCharacter.Name.TextValue, playerCharacter.HomeWorld.Value.RowId), - new IconPayload(BitmapFontIcon.CrossWorld), - new TextPayload(playerCharacter.HomeWorld.Value.Name.ToString())); - - if (!this.Configuration.VoidList.Any( - x => - x.Name == voidItem.Name && x.HomeworldId == voidItem.HomeworldId)) - { - this.Configuration.VoidList.Add(voidItem); - this.Configuration.Save(); - this.RemoveChecked(playerCharacter.EntityId); - Service.ChatGui.Print( - this.PluginLocalization.EntryAdded(this.PluginLocalization.VoidListName, playerString)); - } - else - { - Service.ChatGui.Print( - this.PluginLocalization.EntryExistsError(this.PluginLocalization.VoidListName, playerString)); - } - } - else - { - Service.ChatGui.Print(this.PluginLocalization.InvalidTargetError(this.PluginLocalization.VoidListName)); - } - } - - public void WhitelistPlayer(string command, string arguments) - { - if (string.IsNullOrEmpty(arguments)) - { - Service.ChatGui.Print(this.PluginLocalization.NoArgumentsError(this.PluginLocalization.WhitelistName)); - return; - } - - string[] args = arguments.Split(new[] { ' ' }, 4); - - if (args.Length < 3) - { - Service.ChatGui.Print( - this.PluginLocalization.NotEnoughArgumentsError(this.PluginLocalization.WhitelistName)); - return; - } - - World? world = Service.DataManager.GetExcelSheet().SingleOrDefault(x => - x.DataCenter.ValueNullable?.Region != 0 && - x.Name.ToString().Equals(args[2], StringComparison.InvariantCultureIgnoreCase)); - - if (world is null) - { - Service.ChatGui.Print( - this.PluginLocalization.InvalidWorldNameError(this.PluginLocalization.WhitelistName, args[2])); - return; - } - - string playerName = $"{args[0].ToUppercase()} {args[1].ToUppercase()}"; - - IPlayerCharacter? playerCharacter = Service.ObjectTable.SingleOrDefault( - x => - x is IPlayerCharacter character && character.HomeWorld.Value.RowId == world.Value.RowId && - character.Name.TextValue.Equals(playerName, StringComparison.Ordinal)) as IPlayerCharacter; - - VoidItem item; - - if (playerCharacter != null) - { - unsafe - { - Character* character = (Character*)playerCharacter.Address; - item = new VoidItem - { - Id = character->ContentId, - Name = character->NameString, - HomeworldId = world.Value.RowId, - HomeworldName = world.Value.Name.ToString(), - Reason = args.Length == 3 ? string.Empty : args[3], - Manual = command == "WhitelistUIManual" - }; - } - } - else - { - item = new VoidItem - { - Name = playerName, - HomeworldId = world.Value.RowId, - HomeworldName = world.Value.Name.ToString(), - Reason = args.Length == 3 ? string.Empty : args[3], - Manual = command == "WhitelistUIManual" - }; - } - - SeString playerString = new( - new PlayerPayload(playerName, world.Value.RowId), - new IconPayload(BitmapFontIcon.CrossWorld), - new TextPayload(world.Value.Name.ToString())); - - if (!this.Configuration.Whitelist.Any( - x => - x.Name == item.Name && x.HomeworldId == item.HomeworldId)) - { - this.Configuration.Whitelist.Add(item); - this.Configuration.Save(); - - if (playerCharacter != null) - { - this.RemoveChecked(playerCharacter.EntityId); - this.ShowPlayer(playerCharacter.EntityId); - } - - Service.ChatGui.Print( - this.PluginLocalization.EntryAdded(this.PluginLocalization.WhitelistName, playerString)); - } - else - { - Service.ChatGui.Print( - this.PluginLocalization.EntryExistsError(this.PluginLocalization.WhitelistName, playerString)); - } - } - - public void WhitelistTargetPlayer(string command, string arguments) - { - if (Service.ObjectTable.SingleOrDefault( - x => x is IPlayerCharacter - && x.EntityId != 0 - && x.EntityId != Service.ClientState.LocalPlayer?.EntityId - && x.EntityId == Service.ClientState.LocalPlayer?.TargetObjectId) is IPlayerCharacter - playerCharacter) - { - VoidItem item; - - unsafe - { - Character* character = (Character*)playerCharacter.Address; - item = new VoidItem - { - Id = character->ContentId, - Name = character->NameString, - HomeworldId = character->HomeWorld, - HomeworldName = playerCharacter.HomeWorld.Value.Name.ToString(), - Reason = arguments, - Manual = false - }; - } - - SeString playerString = new( - new PlayerPayload(playerCharacter.Name.TextValue, playerCharacter.HomeWorld.Value.RowId), - new IconPayload(BitmapFontIcon.CrossWorld), - new TextPayload(playerCharacter.HomeWorld.Value.Name.ToString())); - - if (!this.Configuration.Whitelist.Any( - x => - x.Name == item.Name && x.HomeworldId == item.HomeworldId)) - { - this.Configuration.Whitelist.Add(item); - this.Configuration.Save(); - this.RemoveChecked(playerCharacter.EntityId); - this.ShowPlayer(playerCharacter.EntityId); - Service.ChatGui.Print( - this.PluginLocalization.EntryAdded(this.PluginLocalization.WhitelistName, playerString)); - } - else - { - Service.ChatGui.Print( - this.PluginLocalization.EntryExistsError(this.PluginLocalization.WhitelistName, playerString)); - } - } - else - { - Service.ChatGui.Print(this.PluginLocalization.InvalidTargetError(this.PluginLocalization.WhitelistName)); - } - } - - private void OpenConfigUi() => this.ConfigurationWindow.Toggle(); - - private void BuildUi() => this.WindowSystem.Draw(); - - private void OnChatMessage( - XivChatType type, - int _, - ref SeString sender, - ref SeString message, - ref bool isHandled) - { - if (!this.Configuration.Enabled) - { - return; - } - - try - { - if (isHandled) - { - return; - } - - PlayerPayload? playerPayload = sender.Payloads.SingleOrDefault(x => x is PlayerPayload) as PlayerPayload; - PlayerPayload? emotePlayerPayload = - message.Payloads.FirstOrDefault(x => x is PlayerPayload) as PlayerPayload; - bool isEmoteType = type is XivChatType.CustomEmote or XivChatType.StandardEmote; - - if (playerPayload == default(PlayerPayload) && - (!isEmoteType || emotePlayerPayload == default(PlayerPayload))) - { - return; - } - - if (this.Configuration.VoidList.Any( - x => - x.HomeworldId == - (isEmoteType ? emotePlayerPayload?.World.RowId : playerPayload?.World.RowId) - && x.Name == (isEmoteType ? emotePlayerPayload?.PlayerName : playerPayload?.PlayerName))) - { - isHandled = true; - } - } - catch (Exception) - { - // Ignore exception + this.FrameworkHandler.ShowPlayer(gameObject.EntityId); } } - public void RefreshActors() => this.refresh = this.Configuration.Enabled; + public void RefreshActors() => this.frameworkUpdateHandler.RequestRefresh(); } diff --git a/Visibility/Windows/Configuration.cs b/Visibility/Windows/Configuration.cs index 0de02b8..c1db6df 100644 --- a/Visibility/Windows/Configuration.cs +++ b/Visibility/Windows/Configuration.cs @@ -4,7 +4,7 @@ using Dalamud.Interface; using Dalamud.Interface.Windowing; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Visibility.Configuration; using Visibility.Utils; @@ -13,13 +13,13 @@ namespace Visibility.Windows; public class Configuration: Window { - public Configuration(): base($"{VisibilityPlugin.Instance.Name} Config", ImGuiWindowFlags.NoResize, true) + public Configuration(WindowSystem windowSystem): base($"{VisibilityPlugin.Instance.Name} Config", ImGuiWindowFlags.NoResize, true) { this.whitelistWindow = new VoidItemList(isWhitelist: true); this.voidItemListWindow = new VoidItemList(isWhitelist: false); - VisibilityPlugin.Instance.WindowSystem.AddWindow(this.whitelistWindow); - VisibilityPlugin.Instance.WindowSystem.AddWindow(this.voidItemListWindow); + windowSystem.AddWindow(this.whitelistWindow); + windowSystem.AddWindow(this.voidItemListWindow); this.Size = new Vector2(700 * ImGui.GetIO().FontGlobalScale, 0); this.SizeCondition = ImGuiCond.Always; @@ -82,7 +82,7 @@ public override void Draw() ImGui.TextColored(versionColor, versionString); ImGui.SetCursorPosY(cursorY); - if (ImGui.BeginTable("###cols", 6, ImGuiTableFlags.BordersOuterH)) + if (ImGui.BeginTable("###cols", 7, ImGuiTableFlags.BordersOuterH)) { ImGui.TableSetupColumn( string.Empty, @@ -90,6 +90,9 @@ public override void Draw() ImGui.TableSetupColumn( VisibilityPlugin.Instance.PluginLocalization.OptionHideAll, ImGuiTableColumnFlags.NoSort | ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupColumn( + "Hide in combat", + ImGuiTableColumnFlags.NoSort | ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn( VisibilityPlugin.Instance.PluginLocalization.OptionShowParty, ImGuiTableColumnFlags.NoSort | ImGuiTableColumnFlags.WidthStretch); @@ -111,6 +114,10 @@ public override void Draw() configuration.CurrentEditedConfig.HidePlayer, nameof(configuration.CurrentEditedConfig.HidePlayer)); ImGui.TableNextColumn(); + ImGuiElements.CenteredCheckbox( + configuration.CurrentEditedConfig.HidePlayerInCombat, + nameof(configuration.CurrentEditedConfig.HidePlayerInCombat)); + ImGui.TableNextColumn(); ImGuiElements.CenteredCheckbox( configuration.CurrentEditedConfig.ShowPartyPlayer, nameof(configuration.CurrentEditedConfig.ShowPartyPlayer)); @@ -135,6 +142,10 @@ public override void Draw() configuration.CurrentEditedConfig.HidePet, nameof(configuration.CurrentEditedConfig.HidePet)); ImGui.TableNextColumn(); + ImGuiElements.CenteredCheckbox( + configuration.CurrentEditedConfig.HidePetInCombat, + nameof(configuration.CurrentEditedConfig.HidePetInCombat)); + ImGui.TableNextColumn(); ImGuiElements.CenteredCheckbox( configuration.CurrentEditedConfig.ShowPartyPet, nameof(configuration.CurrentEditedConfig.ShowPartyPet)); @@ -155,6 +166,10 @@ public override void Draw() configuration.CurrentEditedConfig.HideChocobo, nameof(configuration.CurrentEditedConfig.HideChocobo)); ImGui.TableNextColumn(); + ImGuiElements.CenteredCheckbox( + configuration.CurrentEditedConfig.HideChocoboInCombat, + nameof(configuration.CurrentEditedConfig.HideChocoboInCombat)); + ImGui.TableNextColumn(); ImGuiElements.CenteredCheckbox( configuration.CurrentEditedConfig.ShowPartyChocobo, nameof(configuration.CurrentEditedConfig.ShowPartyChocobo)); @@ -175,6 +190,10 @@ public override void Draw() configuration.CurrentEditedConfig.HideMinion, nameof(configuration.CurrentEditedConfig.HideMinion)); ImGui.TableNextColumn(); + ImGuiElements.CenteredCheckbox( + configuration.CurrentEditedConfig.HideMinionInCombat, + nameof(configuration.CurrentEditedConfig.HideMinionInCombat)); + ImGui.TableNextColumn(); ImGuiElements.CenteredCheckbox( configuration.CurrentEditedConfig.ShowPartyMinion, nameof(configuration.CurrentEditedConfig.ShowPartyMinion)); diff --git a/Visibility/Windows/VoidItemList.cs b/Visibility/Windows/VoidItemList.cs index e684ad8..591aaf6 100644 --- a/Visibility/Windows/VoidItemList.cs +++ b/Visibility/Windows/VoidItemList.cs @@ -9,7 +9,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Interface.Windowing; -using ImGuiNET; +using Dalamud.Bindings.ImGui; using Visibility.Configuration; using Visibility.Void; @@ -178,36 +178,33 @@ public override void Draw() ImGui.InputText( "###playerFirstName", this.buffer[0], - (uint)this.buffer[0].Length, ImGuiInputTextFlags.CharsNoBlank); ImGui.TableNextColumn(); ImGui.InputText( "###playerLastName", this.buffer[1], - (uint)this.buffer[1].Length, ImGuiInputTextFlags.CharsNoBlank); ImGui.TableNextColumn(); ImGui.InputText( "###homeworldName", this.buffer[2], - (uint)this.buffer[2].Length, ImGuiInputTextFlags.CharsNoBlank); ImGui.TableNextColumn(); ImGui.TableNextColumn(); - ImGui.InputText("###reason", this.buffer[3], (uint)this.buffer[3].Length); + ImGui.InputText("###reason", this.buffer[3]); ImGui.TableNextColumn(); if (ImGui.Button(VisibilityPlugin.Instance.PluginLocalization.OptionAddPlayer)) { if (this.isWhitelist) { - VisibilityPlugin.Instance.WhitelistPlayer( + VisibilityPlugin.Instance.CommandManagerHandler.WhitelistPlayer( manual ? "WhitelistUIManual" : string.Empty, $"{this.buffer[0].ByteToString()} {this.buffer[1].ByteToString()} {this.buffer[2].ByteToString()} {this.buffer[3].ByteToString()}"); } else { - VisibilityPlugin.Instance.VoidPlayer( + VisibilityPlugin.Instance.CommandManagerHandler.VoidPlayer( manual ? "VoidUIManual" : string.Empty, $"{this.buffer[0].ByteToString()} {this.buffer[1].ByteToString()} {this.buffer[2].ByteToString()} {this.buffer[3].ByteToString()}"); } diff --git a/Visibility/packages.lock.json b/Visibility/packages.lock.json index 2babe9d..a790ca8 100644 --- a/Visibility/packages.lock.json +++ b/Visibility/packages.lock.json @@ -4,9 +4,9 @@ "net9.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[12.0.0, )", - "resolved": "12.0.0", - "contentHash": "J5TJLV3f16T/E2H2P17ClWjtfEBPpq3yxvqW46eN36JCm6wR+EaoaYkqG9Rm5sHqs3/nK/vKjWWyvEs/jhKoXw==" + "requested": "[13.0.0, )", + "resolved": "13.0.0", + "contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w==" }, "DotNet.ReproducibleBuilds": { "type": "Direct",