using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Textures.TextureWraps; using HSUI.Config; using HSUI.Enums; using HSUI.Helpers; using HSUI.Interface.Bars; using HSUI.Interface.GeneralElements; using HSUI.Interface.StatusEffects; using Dalamud.Bindings.ImGui; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; namespace HSUI.Interface.EnemyList { public class EnemyListHud : DraggableHudElement, IHudElementWithMouseOver, IHudElementWithPreview { private EnemyListConfig Config => (EnemyListConfig)_config; private EnemyListConfigs Configs; private EnemyListHelper _helper = new EnemyListHelper(); private List _smoothHPHelpers = new List(); private const int MaxEnemyCount = 8; private List _previewValues = new List(MaxEnemyCount); private bool _wasHovering = false; private LabelHud _nameLabelHud; private LabelHud _healthLabelHud; private LabelHud _orderLabelHud; private List _castbarHud; private StatusEffectsListHud _buffsListHud; private StatusEffectsListHud _debuffsListHud; private IDalamudTextureWrap? _iconsTexture => TexturesHelper.GetTextureFromPath("ui/uld/enemylist_hr1.tex"); public EnemyListHud(EnemyListConfig config, string displayName) : base(config, displayName) { Configs = EnemyListConfigs.GetConfigs(); config.ValueChangeEvent += OnConfigPropertyChanged; _nameLabelHud = new LabelHud(Configs.HealthBar.NameLabel); _healthLabelHud = new LabelHud(Configs.HealthBar.HealthLabel); _orderLabelHud = new LabelHud(Configs.HealthBar.OrderLabel); _castbarHud = new List(); _buffsListHud = new StatusEffectsListHud(Configs.Buffs); _debuffsListHud = new StatusEffectsListHud(Configs.Debuffs); for (int i = 0; i < MaxEnemyCount; i++) { _smoothHPHelpers.Add(new SmoothHPHelper()); _castbarHud.Add(new EnemyListCastbarHud(Configs.CastBar)); } UpdatePreview(); } protected override void InternalDispose() { _config.ValueChangeEvent -= OnConfigPropertyChanged; } private void OnConfigPropertyChanged(object sender, OnChangeBaseArgs args) { if (args.PropertyName == "Preview") { UpdatePreview(); } } private void UpdatePreview() { _previewValues.Clear(); if (!Config.Preview) { return; } Random RNG = new Random((int)ImGui.GetTime()); for (int i = 0; i < MaxEnemyCount; i++) { _previewValues.Add(RNG.Next(0, 101) / 100f); } } protected override (List, List) ChildrenPositionsAndSizes() { Vector2 size = new Vector2(Configs.HealthBar.Size.X, MaxEnemyCount * Configs.HealthBar.Size.Y + (MaxEnemyCount - 1) * Config.VerticalPadding); Vector2 pos = Config.GrowthDirection == EnemyListGrowthDirection.Down ? Config.Position : Config.Position - new Vector2(0, size.Y); return (new List() { pos + size / 2f }, new List() { size }); } public void StopPreview() { Config.Preview = false; foreach (EnemyListCastbarHud castbar in _castbarHud) { castbar.StopPreview(); } _buffsListHud.StopPreview(); _debuffsListHud.StopPreview(); Configs.HealthBar.MouseoverAreaConfig.Preview = false; Configs.SignIcon.Preview = false; } public void StopMouseover() { if (_wasHovering) { InputsHelper.Instance.ClearTarget(); _wasHovering = false; } } public override void DrawChildren(Vector2 origin) { if (!Config.Enabled) { return; } _helper.Update(); int count = Math.Min(MaxEnemyCount, Config.Preview ? MaxEnemyCount : _helper.EnemyCount); uint fakeMaxHp = 100000; ICharacter? mouseoverTarget = null; bool hovered = false; for (int i = 0; i < count; i++) { // hp bar ICharacter? character = Config.Preview ? null : Plugin.ObjectTable.SearchById((uint)_helper.EnemiesData.ElementAt(i).EntityId) as ICharacter; uint currentHp = Config.Preview ? (uint)(_previewValues[i] * fakeMaxHp) : character?.CurrentHp ?? fakeMaxHp; uint maxHp = Config.Preview ? fakeMaxHp : character?.MaxHp ?? fakeMaxHp; int enmityLevel = Config.Preview ? Math.Max(4, i + 1) : _helper.EnemiesData.ElementAt(i).EnmityLevel; if (Configs.HealthBar.SmoothHealthConfig.Enabled) { currentHp = _smoothHPHelpers[i].GetNextHp((int)currentHp, (int)maxHp, Configs.HealthBar.SmoothHealthConfig.Velocity); } int direction = Config.GrowthDirection == EnemyListGrowthDirection.Down ? 1 : -1; float y = Config.Position.Y + i * direction * Configs.HealthBar.Size.Y + i * direction * Config.VerticalPadding; Vector2 pos = new Vector2(Config.Position.X, y); PluginConfigColor fillColor = GetColor(character, currentHp, maxHp); PluginConfigColor bgColor = Configs.HealthBar.BackgroundColor; if (Configs.HealthBar.RangeConfig.Enabled) { fillColor = GetDistanceColor(character, fillColor); bgColor = GetDistanceColor(character, bgColor); } Rect background = new Rect(pos, Configs.HealthBar.Size, bgColor); PluginConfigColor borderColor = GetBorderColor(character, enmityLevel); Rect healthFill = BarUtilities.GetFillRect(pos, Configs.HealthBar.Size, Configs.HealthBar.FillDirection, fillColor, currentHp, maxHp); BarHud bar = new BarHud( Configs.HealthBar.ID + $"_{i}", Configs.HealthBar.DrawBorder, borderColor, GetBorderThickness(character), DrawAnchor.TopLeft, current: currentHp, max: maxHp, shadowConfig: Configs.HealthBar.ShadowConfig, barTextureName: Configs.HealthBar.BarTextureName, barTextureDrawMode: Configs.HealthBar.BarTextureDrawMode ); bar.NeedsInputs = true; bar.SetBackground(background); bar.AddForegrounds(healthFill); if (Configs.HealthBar.Colors.UseMissingHealthBar) { Vector2 healthMissingSize = Configs.HealthBar.Size - BarUtilities.GetFillDirectionOffset(healthFill.Size, Configs.HealthBar.FillDirection); Vector2 healthMissingPos = Configs.HealthBar.FillDirection.IsInverted() ? pos : pos + BarUtilities.GetFillDirectionOffset(healthFill.Size, Configs.HealthBar.FillDirection); PluginConfigColor? color = Configs.HealthBar.RangeConfig.Enabled ? GetDistanceColor(character, Configs.HealthBar.Colors.HealthMissingColor) : Configs.HealthBar.Colors.HealthMissingColor; bar.AddForegrounds(new Rect(healthMissingPos, healthMissingSize, color)); } // highlight var (areaStart, areaEnd) = Configs.HealthBar.MouseoverAreaConfig.GetArea(origin + pos, Configs.HealthBar.Size); bool isHovering = character != null && ImGui.IsMouseHoveringRect(areaStart, areaEnd); bool isSoftTarget = character != null && character.EntityId == Plugin.TargetManager.SoftTarget?.EntityId; if (isHovering || isSoftTarget) { if (Configs.HealthBar.Colors.ShowHighlight) { Rect highlight = new Rect(pos, Configs.HealthBar.Size, Configs.HealthBar.Colors.HighlightColor); bar.AddForegrounds(highlight); } mouseoverTarget = character; hovered = isHovering; } AddDrawActions(bar.GetDrawActions(origin, Configs.HealthBar.StrataLevel)); // mouseover area BarHud? mouseoverAreaBar = Configs.HealthBar.MouseoverAreaConfig.GetBar( pos, Configs.HealthBar.Size, Configs.HealthBar.ID + "_mouseoverArea" ); if (mouseoverAreaBar != null) { AddDrawActions(mouseoverAreaBar.GetDrawActions(origin, StrataLevel.HIGHEST)); } // enmity icon if (_iconsTexture != null && Configs.EnmityIcon.Enabled) { var parentPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.EnmityIcon.HealthBarAnchor); var iconPos = Utils.GetAnchoredPosition(parentPos + Configs.EnmityIcon.Position, Configs.EnmityIcon.Size, Configs.EnmityIcon.Anchor); int enmityIndex = Config.Preview ? Math.Min(3, i) : _helper.EnemiesData.ElementAt(i).EnmityLevel - 1; AddDrawAction(Configs.EnmityIcon.StrataLevel, () => { DrawHelper.DrawInWindow(ID + "_enmityIcon", iconPos, Configs.EnmityIcon.Size, false, (drawList) => { float w = 48f / _iconsTexture.Width; float h = 48f / _iconsTexture.Height; Vector2 uv0 = new Vector2(w * enmityIndex, 0.48f); Vector2 uv1 = new Vector2(w * (enmityIndex + 1), 0.48f + h); drawList.AddImage(_iconsTexture.Handle, iconPos, iconPos + Configs.EnmityIcon.Size, uv0, uv1); }); }); } // sign icon uint? signIconId = null; if (Configs.SignIcon.Enabled) { signIconId = Configs.SignIcon.IconID(character); if (signIconId.HasValue) { var parentPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.SignIcon.HealthBarAnchor); var iconPos = Utils.GetAnchoredPosition(parentPos + Configs.SignIcon.Position, Configs.SignIcon.Size, Configs.SignIcon.Anchor); AddDrawAction(Configs.SignIcon.StrataLevel, () => { DrawHelper.DrawInWindow(ID + "_signIcon", iconPos, Configs.SignIcon.Size, false, (drawList) => { DrawHelper.DrawIcon(signIconId.Value, iconPos, Configs.SignIcon.Size, false, drawList); }); }); } } // labels string? name = Config.Preview ? "Fake Name" : null; AddDrawAction(Configs.HealthBar.NameLabel.StrataLevel, () => { _nameLabelHud.Draw(origin + pos, Configs.HealthBar.Size, character, name, currentHp, maxHp); }); AddDrawAction(Configs.HealthBar.HealthLabel.StrataLevel, () => { _healthLabelHud.Draw(origin + pos, Configs.HealthBar.Size, character, name, currentHp, maxHp); }); if (!signIconId.HasValue || !Configs.SignIcon.ReplaceOrderLabel) { int letter = i; if (!Config.Preview && _helper.EnemiesData.ElementAt(i).LetterIndex.HasValue) { letter = _helper.EnemiesData.ElementAt(i).LetterIndex!.Value; } string str = char.ConvertFromUtf32(0xE071 + letter).ToString(); AddDrawAction(Configs.HealthBar.OrderLabel.StrataLevel, () => { Configs.HealthBar.OrderLabel.SetText(str); _orderLabelHud.Draw(origin + pos, Configs.HealthBar.Size); }); } // buffs / debuffs var buffsPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.Buffs.HealthBarAnchor); AddDrawAction(Configs.Buffs.StrataLevel, () => { _buffsListHud.Actor = character; _buffsListHud.PrepareForDraw(buffsPos); _buffsListHud.Draw(buffsPos); }); var debuffsPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.Debuffs.HealthBarAnchor); AddDrawAction(Configs.Debuffs.StrataLevel, () => { _debuffsListHud.Actor = character; _debuffsListHud.PrepareForDraw(debuffsPos); _debuffsListHud.Draw(debuffsPos); }); // castbar EnemyListCastbarHud castbar = _castbarHud[i]; castbar.EnemyListIndex = i; var castbarPos = Utils.GetAnchoredPosition(origin + pos, -Configs.HealthBar.Size, Configs.CastBar.HealthBarAnchor); AddDrawAction(Configs.CastBar.StrataLevel, () => { castbar.Actor = character; castbar.PrepareForDraw(castbarPos); castbar.Draw(castbarPos); }); } // mouseover bool ignoreMouseover = Configs.HealthBar.MouseoverAreaConfig.Enabled && Configs.HealthBar.MouseoverAreaConfig.Ignore; if (hovered && mouseoverTarget != null) { _wasHovering = true; InputsHelper.Instance.SetTarget(mouseoverTarget, ignoreMouseover); // left click if (InputsHelper.Instance.LeftButtonClicked) { Plugin.TargetManager.Target = mouseoverTarget; } } else if (_wasHovering) { InputsHelper.Instance.ClearTarget(); _wasHovering = false; } } private PluginConfigColor GetColor(ICharacter? character, uint currentHp = 0, uint maxHp = 0) { if (Configs.HealthBar.Colors.ColorByHealth.Enabled && (character != null || Config.Preview)) { var scale = (float)currentHp / Math.Max(1, maxHp); return ColorUtils.GetColorByScale(scale, Configs.HealthBar.Colors.ColorByHealth); } return Configs.HealthBar.FillColor; } private PluginConfigColor GetBorderColor(ICharacter? character, int enmityLevel) { IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target; if (character != null && target != null && character.EntityId == target.EntityId) { return Configs.HealthBar.Colors.TargetBordercolor; } if (!Configs.HealthBar.Colors.ShowEnmityBorderColors) { return Configs.HealthBar.BorderColor; } return enmityLevel switch { >= 3 => Configs.HealthBar.Colors.EnmityLeaderBorderColor, >= 1 => Configs.HealthBar.Colors.EnmitySecondBorderColor, _ => Configs.HealthBar.BorderColor }; } private int GetBorderThickness(ICharacter? character) { IGameObject? target = Plugin.TargetManager.Target; if (character != null && character == target) { return Configs.HealthBar.Colors.TargetBorderThickness; } return Configs.HealthBar.BorderThickness; } private PluginConfigColor GetDistanceColor(ICharacter? character, PluginConfigColor color) { byte distance = character != null ? character.YalmDistanceX : byte.MaxValue; float currentAlpha = color.Vector.W * 100f; float alpha = Configs.HealthBar.RangeConfig.AlphaForDistance(distance, currentAlpha) / 100f; return color.WithAlpha(alpha); } } #region utils public struct EnemyListConfigs { public EnemyListHealthBarConfig HealthBar; public EnemyListEnmityIconConfig EnmityIcon; public EnemyListSignIconConfig SignIcon; public EnemyListCastbarConfig CastBar; public EnemyListBuffsConfig Buffs; public EnemyListDebuffsConfig Debuffs; public EnemyListConfigs( EnemyListHealthBarConfig healthBar, EnemyListEnmityIconConfig enmityIcon, EnemyListSignIconConfig signIcon, EnemyListCastbarConfig castBar, EnemyListBuffsConfig buffs, EnemyListDebuffsConfig debuffs) { HealthBar = healthBar; EnmityIcon = enmityIcon; SignIcon = signIcon; CastBar = castBar; Buffs = buffs; Debuffs = debuffs; } public static EnemyListConfigs GetConfigs() { return new EnemyListConfigs( ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject(), ConfigurationManager.Instance.GetConfigObject() ); } } #endregion }