using Dalamud.Game.ClientState.Objects.Types; using HSUI.Config; using HSUI.Enums; using HSUI.Helpers; using HSUI.Interface.GeneralElements; using Dalamud.Bindings.ImGui; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; using FFXIVClientStructs.FFXIV.Client.UI.Agent; namespace HSUI.Interface.Party { public class PartyFramesHud : DraggableHudElement, IHudElementWithMouseOver, IHudElementWithPreview, IHudElementWithVisibilityConfig { private PartyFramesConfig Config => (PartyFramesConfig)_config; public VisibilityConfig VisibilityConfig => Config.VisibilityConfig; private PartyFramesConfigs Configs; private Vector2 _contentMargin = new Vector2(2, 2); private static readonly int MaxMemberCount = 9; // 8 players + chocobo // layout private Vector2 _origin; private LayoutInfo _layoutInfo; private uint _memberCount = 0; private bool _layoutDirty = true; private readonly List bars; private LabelHud _titleLabelHud; private bool Locked => !ConfigurationManager.Instance.IsConfigWindowOpened; public PartyFramesHud(PartyFramesConfig config, string displayName) : base(config, displayName) { Configs = PartyFramesConfigs.GetConfigs(); config.ValueChangeEvent += OnLayoutPropertyChanged; Configs.HealthBar.ValueChangeEvent += OnLayoutPropertyChanged; Configs.HealthBar.ColorsConfig.ValueChangeEvent += OnLayoutPropertyChanged; bars = new List(MaxMemberCount); for (int i = 0; i < bars.Capacity; i++) { PartyFramesBar bar = new PartyFramesBar("DelvUI_partyFramesBar" + i, Configs); bar.OpenContextMenuEvent += OnOpenContextMenu; bars.Add(bar); } _titleLabelHud = new LabelHud(config.ShowPartyTitleConfig); PartyManager.Instance.MembersChangedEvent += OnMembersChanged; UpdateBars(Vector2.Zero); } protected override void InternalDispose() { foreach (var bar in bars) { try { bar.Dispose(); } catch (Exception ex) { Plugin.Logger.Error($"Error disposing PartyFramesBar: {ex.Message}"); } } bars.Clear(); _config.ValueChangeEvent -= OnLayoutPropertyChanged; Configs.HealthBar.ValueChangeEvent -= OnLayoutPropertyChanged; Configs.HealthBar.ColorsConfig.ValueChangeEvent -= OnLayoutPropertyChanged; PartyManager.Instance.MembersChangedEvent -= OnMembersChanged; } private unsafe void OnOpenContextMenu(PartyFramesBar bar) { if (bar.Member == null || Plugin.ObjectTable.LocalPlayer == null) { return; } if (PartyManager.Instance.PartyListAddon == null || PartyManager.Instance.HudAgent == IntPtr.Zero) { return; } int addonId = PartyManager.Instance.PartyListAddon->AtkUnitBase.Id; AgentModule.Instance()->GetAgentHUD()->OpenContextMenuFromPartyAddon(addonId, bar.Member.Index); } private void OnLayoutPropertyChanged(object sender, OnChangeBaseArgs args) { if (args.PropertyName == "Size" || args.PropertyName == "FillRowsFirst" || args.PropertyName == "BarsAnchor" || args.PropertyName == "Padding" || args.PropertyName == "Rows" || args.PropertyName == "Columns") { _layoutDirty = true; } } private void OnMembersChanged(PartyManager sender) { UpdateBars(_origin); } public void UpdateBars(Vector2 origin) { uint memberCount = PartyManager.Instance.MemberCount; uint row = 0; uint col = 0; for (int i = 0; i < bars.Count; i++) { PartyFramesBar bar = bars[i]; if (i >= memberCount) { bar.Visible = false; continue; } // update bar IPartyFramesMember member = PartyManager.Instance.SortedGroupMembers.ElementAt(i); bar.Member = member; bar.Visible = true; // anchor and position CalculateBarPosition(origin, Size, out var x, out var y); bar.Position = new Vector2( x + Configs.HealthBar.Size.X * col + (Configs.HealthBar.Padding.X - 1) * col, y + Configs.HealthBar.Size.Y * row + (Configs.HealthBar.Padding.Y - 1) * row ); // layout if (Config.FillRowsFirst) { col = col + 1; if (col >= _layoutInfo.TotalColCount) { col = 0; row = row + 1; } } else { row = row + 1; if (row >= _layoutInfo.TotalRowCount) { row = 0; col = col + 1; } } } } private void CalculateBarPosition(Vector2 position, Vector2 spaceSize, out float x, out float y) { x = position.X; y = position.Y; if (Config.BarsAnchor == DrawAnchor.Top || Config.BarsAnchor == DrawAnchor.Center || Config.BarsAnchor == DrawAnchor.Bottom) { x += (spaceSize.X - _layoutInfo.ContentSize.X) / 2f; } else if (Config.BarsAnchor == DrawAnchor.TopRight || Config.BarsAnchor == DrawAnchor.Right || Config.BarsAnchor == DrawAnchor.BottomRight) { x += spaceSize.X - _layoutInfo.ContentSize.X; } if (Config.BarsAnchor == DrawAnchor.Left || Config.BarsAnchor == DrawAnchor.Center || Config.BarsAnchor == DrawAnchor.Right) { y += (spaceSize.Y - _layoutInfo.ContentSize.Y) / 2f; } else if (Config.BarsAnchor == DrawAnchor.BottomLeft || Config.BarsAnchor == DrawAnchor.Bottom || Config.BarsAnchor == DrawAnchor.BottomRight) { y += spaceSize.Y - _layoutInfo.ContentSize.Y; } } private void UpdateBarsPosition(Vector2 delta) { foreach (PartyFramesBar bar in bars) { bar.Position = bar.Position + delta; } } public void StopPreview() { Config.Preview = false; PartyManager.Instance?.UpdatePreview(); foreach (PartyFramesBar bar in bars) { bar.StopPreview(); } } protected override (List, List) ChildrenPositionsAndSizes() { return (new List() { Config.Position + Size / 2f }, new List() { Size }); } public void StopMouseover() { foreach (PartyFramesBar bar in bars) { bar.StopMouseover(); } } private Vector2 Size => new Vector2( Config.Columns * Configs.HealthBar.Size.X + (Config.Columns - 1) * Configs.HealthBar.Padding.X, Config.Rows * Configs.HealthBar.Size.Y + (Config.Rows - 1) * Configs.HealthBar.Padding.Y ); private void UpdateLayout(Vector2 origin) { Vector2 contentStartPos = origin + Config.Position; uint count = PartyManager.Instance.MemberCount; if (_layoutDirty || _memberCount != count) { _layoutInfo = LayoutHelper.CalculateLayout( Size, Configs.HealthBar.Size, count, Configs.HealthBar.Padding, Config.FillRowsFirst ); UpdateBars(contentStartPos); } else if (_origin != contentStartPos) { UpdateBarsPosition(contentStartPos - _origin); } _layoutDirty = false; _origin = contentStartPos; _memberCount = count; } public override void DrawChildren(Vector2 origin) { if (!_config.Enabled) { return; } // area bg if (Config.Preview) { AddDrawAction(StrataLevel.LOWEST, () => { ImDrawListPtr drawList = ImGui.GetWindowDrawList(); Vector2 bgPos = origin + Config.Position - _contentMargin; Vector2 bgSize = Size + _contentMargin * 2; drawList.AddRectFilled(bgPos, bgPos + bgSize, 0x66000000); drawList.AddRect(bgPos, bgPos + bgSize, 0x66FFFFFF); }); } uint count = PartyManager.Instance.MemberCount; if (count < 1) { return; } UpdateLayout(origin); // draw bars // check borders to determine the order in which the bars are drawn // which is necessary for grid-like party frames IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target; int targetIndex = -1; int enmityLeaderIndex = -1; int enmitySecondIndex = -1; List raisedIndexes = new List(); List cleanseIndexes = new List(); List whosTalkingIndexes = new List(); for (int i = 0; i < count; i++) { IPartyFramesMember? member = bars[i].Member; if (member != null) { // whos talking if (Configs.Icons.WhosTalking.ChangeBorders && member.WhosTalkingState != WhosTalkingState.None) { whosTalkingIndexes.Add(i); continue; } // target if (target != null && member.ObjectId == target.GameObjectId) { targetIndex = i; continue; } // cleanse bool cleanseCheck = true; if (Configs.Trackers.Cleanse.CleanseJobsOnly) { cleanseCheck = Utils.IsOnCleanseJob(); } if (Configs.Trackers.Cleanse.Enabled && Configs.Trackers.Cleanse.ChangeBorderCleanseColor && member.HasDispellableDebuff && cleanseCheck) { cleanseIndexes.Add(i); continue; } // raise if (Configs.Trackers.Raise.Enabled && Configs.Trackers.Raise.ChangeBorderColorWhenRaised && member.RaiseTime.HasValue) { raisedIndexes.Add(i); continue; } // enmity if (Configs.HealthBar.ColorsConfig.ShowEnmityBorderColors) { if (member.EnmityLevel == EnmityLevel.Leader) { enmityLeaderIndex = i; continue; } else if (Configs.HealthBar.ColorsConfig.ShowSecondEnmity && member.EnmityLevel == EnmityLevel.Second && (count > 4 || !Configs.HealthBar.ColorsConfig.HideSecondEnmityInLightParties)) { enmitySecondIndex = i; continue; } } } // no special border AddDrawActions(bars[i].GetBarDrawActions(origin)); } // special colors for borders // 2nd enmity if (enmitySecondIndex >= 0) { AddDrawActions(bars[enmitySecondIndex].GetBarDrawActions(origin, Configs.HealthBar.ColorsConfig.EnmitySecondBordercolor)); } // 1st enmity if (enmityLeaderIndex >= 0) { AddDrawActions(bars[enmityLeaderIndex].GetBarDrawActions(origin, Configs.HealthBar.ColorsConfig.EnmityLeaderBordercolor)); } // raise foreach (int index in raisedIndexes) { AddDrawActions(bars[index].GetBarDrawActions(origin, Configs.Trackers.Raise.BorderColor)); } // target if (targetIndex >= 0) { AddDrawActions(bars[targetIndex].GetBarDrawActions(origin, Configs.HealthBar.ColorsConfig.TargetBordercolor)); } // cleanseable debuff foreach (int index in cleanseIndexes) { AddDrawActions(bars[index].GetBarDrawActions(origin, Configs.Trackers.Cleanse.BorderColor)); } // whos talking foreach (int index in whosTalkingIndexes) { IPartyFramesMember? member = bars[index].Member; if (member != null) { AddDrawActions(bars[index].GetBarDrawActions(origin, Configs.Icons.WhosTalking.ColorForState(member.WhosTalkingState))); } else { AddDrawActions(bars[index].GetBarDrawActions(origin)); } } // extra elements foreach (PartyFramesBar bar in bars) { AddDrawActions(bar.GetElementsDrawActions(origin)); } AddDrawAction(Config.ShowPartyTitleConfig.StrataLevel, () => { Config.ShowPartyTitleConfig.SetText(PartyManager.Instance.PartyTitle); _titleLabelHud.Draw(origin + Config.Position); }); } } #region utils public struct PartyFramesConfigs { public PartyFramesHealthBarsConfig HealthBar; public PartyFramesManaBarConfig ManaBar; public PartyFramesCastbarConfig CastBar; public PartyFramesIconsConfig Icons; public PartyFramesBuffsConfig Buffs; public PartyFramesDebuffsConfig Debuffs; public PartyFramesTrackersConfig Trackers; public PartyFramesCooldownListConfig CooldownList; public PartyFramesConfigs( PartyFramesHealthBarsConfig healthBar, PartyFramesManaBarConfig manaBar, PartyFramesCastbarConfig castBar, PartyFramesIconsConfig icons, PartyFramesBuffsConfig buffs, PartyFramesDebuffsConfig debuffs, PartyFramesTrackersConfig trackers, PartyFramesCooldownListConfig cooldownList) { HealthBar = healthBar; ManaBar = manaBar; CastBar = castBar; Icons = icons; Buffs = buffs; Debuffs = debuffs; Trackers = trackers; CooldownList = cooldownList; } public static PartyFramesConfigs GetConfigs() { return new PartyFramesConfigs( ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject() ); } } #endregion }