using Dalamud.Game.ClientState.JobGauge.Enums; using Dalamud.Game.ClientState.JobGauge.Types; using Dalamud.Game.ClientState.Objects.SubKinds; using HSUI.Config; using HSUI.Config.Attributes; using HSUI.Enums; using HSUI.Helpers; using HSUI.Interface.Bars; using HSUI.Interface.GeneralElements; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; namespace HSUI.Interface.Jobs { public class BardHud : JobHud { private readonly SpellHelper _spellHelper = new(); private new BardConfig Config => (BardConfig)_config; private PluginConfigColor EmptyColor => GlobalColors.Instance.EmptyColor; public BardHud(BardConfig config, string? displayName = null) : base(config, displayName) { } protected override (List, List) ChildrenPositionsAndSizes() { List positions = new List(); List sizes = new List(); if (Config.SongGaugeBar.Enabled) { positions.Add(Config.Position + Config.SongGaugeBar.Position); sizes.Add(Config.SongGaugeBar.Size); } if (Config.SoulVoiceBar.Enabled) { positions.Add(Config.Position + Config.SoulVoiceBar.Position); sizes.Add(Config.SoulVoiceBar.Size); } if (Config.StacksBar.Enabled) { positions.Add(Config.Position + Config.StacksBar.Position); sizes.Add(Config.StacksBar.Size); } if (Config.CausticBiteDoTBar.Enabled) { positions.Add(Config.Position + Config.CausticBiteDoTBar.Position); sizes.Add(Config.CausticBiteDoTBar.Size); } if (Config.StormbiteDoTBar.Enabled) { positions.Add(Config.Position + Config.StormbiteDoTBar.Position); sizes.Add(Config.StormbiteDoTBar.Size); } if (Config.CodaBar.Enabled) { positions.Add(Config.Position + Config.CodaBar.Position); sizes.Add(Config.CodaBar.Size); } return (positions, sizes); } public override void DrawJobHud(Vector2 origin, IPlayerCharacter player) { Vector2 pos = origin + Config.Position; if (Config.CausticBiteDoTBar.Enabled) { DrawCausticBiteDoTBar(pos, player); } if (Config.StormbiteDoTBar.Enabled) { DrawStormbiteDoTBar(pos, player); } HandleCurrentSong(pos, player); if (Config.SoulVoiceBar.Enabled) { DrawSoulVoiceBar(pos, player); } if (Config.CodaBar.Enabled) { DrawCodaBar(pos, player); } } private void DrawCodaBar(Vector2 origin, IPlayerCharacter player) { BRDGauge gauge = Plugin.JobGauges.Get(); var containsCoda = new[] { gauge.Coda.Contains(Song.Wanderer) ? 1 : 0, gauge.Coda.Contains(Song.Mage) ? 1 : 0, gauge.Coda.Contains(Song.Army) ? 1 : 0 }; bool hasCoda = containsCoda.Any(o => o == 1); if (!Config.CodaBar.HideWhenInactive || hasCoda) { var order = Config.CodaBar.CodaOrder; var colors = new[] { Config.CodaBar.WMColor, Config.CodaBar.MBColor, Config.CodaBar.APColor }; var coda = new Tuple[3]; for (int i = 0; i < 3; i++) { coda[i] = new Tuple(colors[order[i]], containsCoda[order[i]], null); } BarHud[] bars = BarUtilities.GetChunkedBars(Config.CodaBar, coda, player); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.CodaBar.StrataLevel)); } } } private static List CausticBiteDoTIDs = new List { 124, 1200 }; private static List CausticBiteDoTDurations = new List { 45, 45 }; protected void DrawCausticBiteDoTBar(Vector2 origin, IPlayerCharacter player) { var target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target; BarHud? bar = BarUtilities.GetDoTBar(Config.CausticBiteDoTBar, player, target, CausticBiteDoTIDs, CausticBiteDoTDurations); if (bar != null) { AddDrawActions(bar.GetDrawActions(origin, Config.CausticBiteDoTBar.StrataLevel)); } } private static List StormbiteDoTIDs = new List { 129, 1201 }; private static List StormbiteDoTDurations = new List { 45, 45 }; protected void DrawStormbiteDoTBar(Vector2 origin, IPlayerCharacter player) { var target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target; BarHud? bar = BarUtilities.GetDoTBar(Config.StormbiteDoTBar, player, target, StormbiteDoTIDs, StormbiteDoTDurations); if (bar != null) { AddDrawActions(bar.GetDrawActions(origin, Config.StormbiteDoTBar.StrataLevel)); } } private void HandleCurrentSong(Vector2 origin, IPlayerCharacter player) { BRDGauge gauge = Plugin.JobGauges.Get(); byte songStacks = gauge.Repertoire; Song song = gauge.Song; ushort songTimer = gauge.SongTimer; switch (song) { case Song.Wanderer: if (Config.StacksBar.Enabled && Config.StacksBar.ShowWMStacks) { DrawStacksBar( origin, player, songStacks, 3, Config.StacksBar.WMStackColor, Config.StacksBar.WMGlowConfig.Enabled && songStacks == 3 ? Config.StacksBar.WMGlowConfig : null ); } DrawSongTimerBar(origin, songTimer, Config.SongGaugeBar.WMColor, Config.SongGaugeBar.WMThreshold, player); break; case Song.Mage: if (Config.StacksBar.Enabled && Config.StacksBar.ShowMBProc) { DrawBloodletterReady(origin, player); } DrawSongTimerBar(origin, songTimer, Config.SongGaugeBar.MBColor, Config.SongGaugeBar.MBThreshold, player); break; case Song.Army: if (Config.StacksBar.Enabled && Config.StacksBar.ShowAPStacks) { DrawStacksBar(origin, player, songStacks, 4, Config.StacksBar.APStackColor); } DrawSongTimerBar(origin, songTimer, Config.SongGaugeBar.APColor, Config.SongGaugeBar.APThreshold, player); break; case Song.None: if (Config.StacksBar.Enabled && !Config.StacksBar.HideWhenInactive) { DrawStacksBar(origin, player, 0, 3, Config.StacksBar.WMStackColor); } DrawSongTimerBar(origin, 0, EmptyColor, Config.SongGaugeBar.ThresholdConfig, player); break; default: if (Config.StacksBar.Enabled && !Config.StacksBar.HideWhenInactive) { DrawStacksBar(origin, player, 0, 3, Config.StacksBar.WMStackColor); } DrawSongTimerBar(origin, 0, EmptyColor, Config.SongGaugeBar.ThresholdConfig, player); break; } } private void DrawBloodletterReady(Vector2 origin, IPlayerCharacter player) { int maxStacks = player.Level < 84 ? 2 : 3; int maxCooldown = maxStacks * 15; int cooldown = _spellHelper.GetSpellCooldownInt(110); cooldown = player.Level < 84 ? Math.Max(0, cooldown - 15) : cooldown; int stacks = (maxCooldown - cooldown) / 15; DrawStacksBar(origin, player, stacks, maxStacks, Config.StacksBar.MBProcColor, Config.StacksBar.MBGlowConfig.Enabled ? Config.StacksBar.MBGlowConfig : null); } protected void DrawSongTimerBar(Vector2 origin, ushort songTimer, PluginConfigColor songColor, ThresholdConfig songThreshold, IPlayerCharacter player) { if (Config.SongGaugeBar.HideWhenInactive && songTimer == 0 || !Config.SongGaugeBar.Enabled) { return; } float duration = Math.Abs(songTimer / 1000f); Config.SongGaugeBar.Label.SetValue(duration); Config.SongGaugeBar.ThresholdConfig = songThreshold; BarHud bar = BarUtilities.GetProgressBar(Config.SongGaugeBar, duration, 45f, 0f, player, songColor); AddDrawActions(bar.GetDrawActions(origin, Config.SongGaugeBar.StrataLevel)); } protected void DrawSoulVoiceBar(Vector2 origin, IPlayerCharacter player) { BardSoulVoiceBarConfig config = Config.SoulVoiceBar; byte soulVoice = Plugin.JobGauges.Get().SoulVoice; if (config.HideWhenInactive && soulVoice == 0) { return; } config.Label.SetValue(soulVoice); BarHud bar = BarUtilities.GetProgressBar( config, config.ThresholdConfig, new LabelConfig[] { config.Label }, soulVoice, 100f, 0f, player, config.FillColor, soulVoice == 100f && config.GlowConfig.Enabled ? config.GlowConfig : null ); AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel)); } private void DrawStacksBar(Vector2 origin, IPlayerCharacter player, int amount, int max, PluginConfigColor stackColor, BarGlowConfig? glowConfig = null) { BardStacksBarConfig config = Config.StacksBar; config.FillColor = stackColor; BarHud[] bars = BarUtilities.GetChunkedBars(Config.StacksBar, max, amount, max, 0f, player, glowConfig: glowConfig); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.CodaBar.StrataLevel)); } } } [Section("Job Specific Bars")] [SubSection("Ranged", 0)] [SubSection("Bard", 1)] public class BardConfig : JobConfig { [JsonIgnore] public override uint JobId => JobIDs.BRD; public new static BardConfig DefaultConfig() { var config = new BardConfig(); config.SoulVoiceBar.Label.FontID = FontsConfig.DefaultMediumFontKey; config.StormbiteDoTBar.Label.FontID = FontsConfig.DefaultMediumFontKey; config.StormbiteDoTBar.Label.TextAnchor = DrawAnchor.Left; config.StormbiteDoTBar.Label.FrameAnchor = DrawAnchor.Left; config.StormbiteDoTBar.Label.Position = new Vector2(2, 0); config.CausticBiteDoTBar.Label.FontID = FontsConfig.DefaultMediumFontKey; config.CausticBiteDoTBar.Label.TextAnchor = DrawAnchor.Right; config.CausticBiteDoTBar.Label.FrameAnchor = DrawAnchor.Right; config.CausticBiteDoTBar.Label.Position = new Vector2(-2, 0); config.CausticBiteDoTBar.FillDirection = BarDirection.Left; config.SoulVoiceBar.ThresholdConfig.Enabled = true; config.SoulVoiceBar.ThresholdConfig.Value = 80; config.SoulVoiceBar.ThresholdConfig.ThresholdType = ThresholdType.Above; config.SoulVoiceBar.ThresholdConfig.ChangeColor = true; config.SoulVoiceBar.ThresholdConfig.Color = new PluginConfigColor(new Vector4(150f / 255f, 0f / 255f, 255f / 255f, 100f / 100f)); return config; } [NestedConfig("Song Gauge Bar", 30)] public BardSongBarConfig SongGaugeBar = new BardSongBarConfig( new(0, -22), new(254, 20), new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 0f / 100f)) ); [NestedConfig("Soul Voice Bar", 35)] public BardSoulVoiceBarConfig SoulVoiceBar = new BardSoulVoiceBarConfig( new(0, -5), new(254, 10), new PluginConfigColor(new Vector4(248f / 255f, 227f / 255f, 0f / 255f, 100f / 100f)) ); [NestedConfig("Stacks Bar", 40)] public BardStacksBarConfig StacksBar = new BardStacksBarConfig( new(0, -39), new(254, 10), new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 0f / 100f)) ); [NestedConfig("Caustic Bite Bar", 60)] public ProgressBarConfig CausticBiteDoTBar = new ProgressBarConfig( new(-64, -51), new(126, 10), new PluginConfigColor(new Vector4(182f / 255f, 68f / 255f, 235f / 255f, 100f / 100f)) ); [NestedConfig("Stormbite Bar", 65)] public ProgressBarConfig StormbiteDoTBar = new ProgressBarConfig( new(64, -51), new(126, 10), new PluginConfigColor(new Vector4(72f / 255f, 117f / 255f, 202f / 255f, 100f / 100f)) ); [NestedConfig("Coda Bar", 40)] public BardCodaBarConfig CodaBar = new BardCodaBarConfig( new(0, -63), new(254, 10), new PluginConfigColor(new Vector4(0, 0, 0, 0)) ); } [DisableParentSettings("FillColor", "ThresholdConfig")] [Exportable(false)] public class BardSongBarConfig : ProgressBarConfig { [ColorEdit4("Wanderer's Minuet" + "##Song")] [Order(31)] public PluginConfigColor WMColor = new(new Vector4(158f / 255f, 157f / 255f, 36f / 255f, 100f / 100f)); [ColorEdit4("Mage's Ballad" + "##Song")] [Order(32)] public PluginConfigColor MBColor = new(new Vector4(143f / 255f, 90f / 255f, 143f / 255f, 100f / 100f)); [ColorEdit4("Army's Paeon" + "##Song")] [Order(33)] public PluginConfigColor APColor = new(new Vector4(207f / 255f, 205f / 255f, 52f / 255f, 100f / 100f)); [NestedConfig("Wanderer's Minuet Threshold", 36, separator = false, spacing = true)] public ThresholdConfig WMThreshold = new ThresholdConfig() { ChangeColor = true, Enabled = true, ThresholdType = ThresholdType.Below, Value = 3 }; [NestedConfig("Mage's Ballad Threshold", 37, separator = false, spacing = true)] public ThresholdConfig MBThreshold = new ThresholdConfig() { ChangeColor = true, Enabled = true, ThresholdType = ThresholdType.Below, Value = 14 }; [NestedConfig("Army's Paeon Threshold", 38, separator = false, spacing = true)] public ThresholdConfig APThreshold = new ThresholdConfig() { ChangeColor = true, Enabled = true, ThresholdType = ThresholdType.Below, Value = 3 }; public BardSongBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor) { } } [Exportable(false)] public class BardSoulVoiceBarConfig : ProgressBarConfig { [NestedConfig("Show Glow", 39, separator = false, spacing = true)] public BarGlowConfig GlowConfig = new BarGlowConfig(); public BardSoulVoiceBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor) { } } [DisableParentSettings("FillColor")] [Exportable(false)] public class BardStacksBarConfig : ChunkedBarConfig { [Checkbox("Wanderer's Minuet Stacks", separator = false, spacing = true)] [Order(51)] public bool ShowWMStacks = true; [NestedConfig("Wanderer's Minuet Stacks Glow", 52, separator = false, spacing = true)] public BarGlowConfig WMGlowConfig = new BarGlowConfig(); [Checkbox("Mage's Ballad Proc" + "##Stacks")] [Order(53)] public bool ShowMBProc = true; [NestedConfig("Mage's Ballad Proc Glow", 54, separator = false, spacing = true)] public BarGlowConfig MBGlowConfig = new BarGlowConfig(); [Checkbox("Army's Paeon Stacks" + "##Stacks")] [Order(56)] public bool ShowAPStacks = true; [ColorEdit4("Wanderer's Minuet Stack" + "##Stacks")] [Order(57)] public PluginConfigColor WMStackColor = new(new Vector4(150f / 255f, 215f / 255f, 232f / 255f, 100f / 100f)); [ColorEdit4("Mage's Ballad Proc" + "##Stacks")] [Order(58)] public PluginConfigColor MBProcColor = new(new Vector4(199f / 255f, 46f / 255f, 46f / 255f, 100f / 100f)); [ColorEdit4("Army's Paeon Stack" + "##Stacks")] [Order(59)] public PluginConfigColor APStackColor = new(new Vector4(0f / 255f, 222f / 255f, 177f / 255f, 100f / 100f)); public BardStacksBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor) { } } [DisableParentSettings("FillColor")] [Exportable(false)] public class BardCodaBarConfig : ChunkedBarConfig { [ColorEdit4("Wanderer's Minuet" + "##Coda", spacing = true)] [Order(71)] public PluginConfigColor WMColor = new(new Vector4(145f / 255f, 186f / 255f, 94f / 255f, 100f / 100f)); [ColorEdit4("Mage's Ballad" + "##Coda")] [Order(72)] public PluginConfigColor MBColor = new(new Vector4(143f / 255f, 90f / 255f, 143f / 255f, 100f / 100f)); [ColorEdit4("Army's Paeon" + "##Coda")] [Order(73)] public PluginConfigColor APColor = new(new Vector4(207f / 255f, 205f / 255f, 52f / 255f, 100f / 100f)); [DragDropHorizontal("Order", "Wanderer's Minuet", "Mage's Ballad", "Army's Paeon")] [Order(74)] public int[] CodaOrder = new int[] { 0, 1, 2 }; public BardCodaBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor, 2) { } } }