using Dalamud.Game.ClientState.JobGauge.Types; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using HSUI.Config; using HSUI.Config.Attributes; using HSUI.Enums; using HSUI.Helpers; using HSUI.Interface.Bars; using HSUI.Interface.GeneralElements; using FFXIVClientStructs.FFXIV.Client.Game.Gauge; using Newtonsoft.Json; using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Linq; using System.Numerics; using System.Runtime.InteropServices; namespace HSUI.Interface.Jobs { public class BlackMageHud : JobHud { private new BlackMageConfig Config => (BlackMageConfig)_config; private static readonly List ThunderDoTIDs = new() { 161, 162, 163, 1210, 3871, 3872 }; private static readonly List ThunderDoTDurations = new() { 24, 18, 27, 21, 30, 24 }; public BlackMageHud(BlackMageConfig config, string? displayName = null) : base(config, displayName) { } protected override (List, List) ChildrenPositionsAndSizes() { List positions = new List(); List sizes = new List(); if (Config.ManaBar.Enabled) { positions.Add(Config.Position + Config.ManaBar.Position); sizes.Add(Config.ManaBar.Size); } if (Config.StacksBar.Enabled) { positions.Add(Config.Position + Config.StacksBar.Position); sizes.Add(Config.StacksBar.Size); } if (Config.UmbralHeartBar.Enabled) { positions.Add(Config.Position + Config.UmbralHeartBar.Position); sizes.Add(Config.UmbralHeartBar.Size); } if (Config.TriplecastBar.Enabled) { positions.Add(Config.Position + Config.TriplecastBar.Position); sizes.Add(Config.TriplecastBar.Size); } if (Config.EnochianBar.Enabled) { positions.Add(Config.Position + Config.EnochianBar.Position); sizes.Add(Config.EnochianBar.Size); } if (Config.PolyglotBar.Enabled) { positions.Add(Config.Position + Config.PolyglotBar.Position); sizes.Add(Config.PolyglotBar.Size); } if (Config.ParadoxBar.Enabled) { positions.Add(Config.Position + Config.ParadoxBar.Position); sizes.Add(Config.ParadoxBar.Size); } if (Config.ThunderDoTBar.Enabled && !Config.ThunderDoTBar.HideWhenInactive) { positions.Add(Config.Position + Config.ThunderDoTBar.Position); sizes.Add(Config.ThunderDoTBar.Size); } return (positions, sizes); } public override void DrawJobHud(Vector2 origin, IPlayerCharacter player) { Vector2 pos = origin + Config.Position; if (Config.ManaBar.Enabled) { DrawManaBar(pos, player); } if (Config.StacksBar.Enabled) { DrawStacksBar(pos); } if (Config.UmbralHeartBar.Enabled) { DrawUmbralHeartBar(pos); } if (Config.AstralSoulBar.Enabled) { DrawAstralSoulBar(pos); } if (Config.TriplecastBar.Enabled) { DrawTripleCastBar(pos, player); } if (Config.PolyglotBar.Enabled) { DrawPolyglotBar(pos, player); } if (Config.ParadoxBar.Enabled) { DrawParadoxBar(pos, player); } if (Config.EnochianBar.Enabled) { DrawEnochianBar(pos, player); } if (Config.ThunderDoTBar.Enabled) { DrawThunderDoTBar(pos, player); } } protected unsafe void DrawManaBar(Vector2 origin, IPlayerCharacter player) { BlackMageManaBarConfig config = Config.ManaBar; BLMGauge _gauge = Plugin.JobGauges.Get(); BlackMageGaugeTmp* gauge = (BlackMageGaugeTmp*)_gauge.Address; bool inUmbralIce = gauge->ElementStance < 0; bool inAstralFire = gauge->ElementStance > 0; if (config.HideWhenInactive && !inAstralFire && !inUmbralIce && player.CurrentMp == player.MaxMp) { return; } // value config.ValueLabelConfig.SetValue(player.CurrentMp); bool drawTreshold = inAstralFire || !config.ThresholdConfig.ShowOnlyDuringAstralFire; PluginConfigColor fillColor = config.FillColor; PluginConfigColor bgColor = config.BackgroundColor; if (config.UseElementColor) { fillColor = inAstralFire ? config.FireColor : inUmbralIce ? config.IceColor : config.FillColor; bgColor = inAstralFire ? config.FireBackgroundColor : inUmbralIce ? config.IceBackgroundColor : config.BackgroundColor; } BarHud bar = BarUtilities.GetProgressBar( config, drawTreshold ? config.ThresholdConfig : null, [config.ValueLabelConfig], player.CurrentMp, player.MaxMp, 0, player, fillColor: fillColor, backgroundColor: bgColor ); AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel)); } protected unsafe void DrawStacksBar(Vector2 origin) { BLMGauge _gauge = Plugin.JobGauges.Get(); BlackMageGaugeTmp* gauge = (BlackMageGaugeTmp*)_gauge.Address; int umbralIceStacks = gauge->UmbralStacks; int astralFireStacks = gauge->AstralStacks; if (Config.StacksBar.HideWhenInactive && umbralIceStacks == 0 && astralFireStacks == 0) { return; }; PluginConfigColor color = umbralIceStacks > 0 ? Config.StacksBar.IceColor : Config.StacksBar.FireColor; int stacks = umbralIceStacks > 0 ? umbralIceStacks : astralFireStacks; BarHud[] bars = BarUtilities.GetChunkedBars(Config.StacksBar, 3, stacks, 3f, fillColor: color); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.StacksBar.StrataLevel)); } } protected unsafe void DrawUmbralHeartBar(Vector2 origin) { BLMGauge _gauge = Plugin.JobGauges.Get(); BlackMageGaugeTmp* gauge = (BlackMageGaugeTmp*)_gauge.Address; if (Config.UmbralHeartBar.HideWhenInactive && gauge->UmbralHearts == 0) { return; }; BarHud[] bars = BarUtilities.GetChunkedBars(Config.UmbralHeartBar, 3, gauge->UmbralHearts, 3f); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.UmbralHeartBar.StrataLevel)); } } protected unsafe void DrawAstralSoulBar(Vector2 origin) { BLMGauge gauge = Plugin.JobGauges.Get(); BlackMageGaugeTmp* internalGauge = (BlackMageGaugeTmp*)gauge.Address; int stacks = internalGauge->AstralSoulStacks; const int maxStacks = 6; if (Config.AstralSoulBar.HideWhenInactive && stacks == 0) { return; }; bool isFull = stacks == maxStacks; BarGlowConfig? glow = isFull && Config.AstralSoulBar.GlowConfig.Enabled ? Config.AstralSoulBar.GlowConfig : null; PluginConfigColor color = isFull ? Config.AstralSoulBar.FullStacksColor : Config.AstralSoulBar.FillColor; BarHud[] bars = BarUtilities.GetChunkedBars(Config.AstralSoulBar, maxStacks, stacks, maxStacks, fillColor: color, glowConfig: glow); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.AstralSoulBar.StrataLevel)); } } protected void DrawTripleCastBar(Vector2 origin, IPlayerCharacter player) { ushort stackCount = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1211)?.Param ?? 0; int maxCount = 3; if (Config.TriplecastBar.CountSwiftcast) { bool hasSwiftcast = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 167) != null; if (hasSwiftcast) { stackCount++; maxCount = stackCount == 4 ? 4 : 3; } } if (Config.TriplecastBar.HideWhenInactive && stackCount == 0) { return; }; BarHud[] bars = BarUtilities.GetChunkedBars(Config.TriplecastBar, maxCount, stackCount, maxCount); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.TriplecastBar.StrataLevel)); } } protected unsafe void DrawEnochianBar(Vector2 origin, IPlayerCharacter player) { BLMGauge _gauge = Plugin.JobGauges.Get(); BlackMageGaugeTmp* gauge = (BlackMageGaugeTmp*)_gauge.Address; if (Config.EnochianBar.HideWhenInactive && !gauge->EnochianActive) { return; } float timer = gauge->EnochianActive ? (30000f - gauge->EnochianTimer) : 0f; Config.EnochianBar.Label.SetValue(timer / 1000); BarHud bar = BarUtilities.GetProgressBar(Config.EnochianBar, timer / 1000, 30, 0f, player); AddDrawActions(bar.GetDrawActions(origin, Config.EnochianBar.StrataLevel)); } protected unsafe void DrawPolyglotBar(Vector2 origin, IPlayerCharacter player) { BLMGauge _gauge = Plugin.JobGauges.Get(); BlackMageGaugeTmp* gauge = (BlackMageGaugeTmp*)_gauge.Address; if (Config.PolyglotBar.HideWhenInactive && gauge->PolyglotStacks == 0) { return; } // only 1 stack before level 80 if (player.Level < 80) { BarGlowConfig? glow = gauge->PolyglotStacks == 1 && Config.PolyglotBar.GlowConfig.Enabled ? Config.PolyglotBar.GlowConfig : null; BarHud bar = BarUtilities.GetBar(Config.PolyglotBar, gauge->PolyglotStacks, 1, 0, glowConfig: glow); AddDrawActions(bar.GetDrawActions(origin, Config.PolyglotBar.StrataLevel)); } // 2-3 stacks after else { int stacks = player.Level < 98 ? 2 : 3; BarGlowConfig? glow = Config.PolyglotBar.GlowConfig.Enabled ? Config.PolyglotBar.GlowConfig : null; BarHud[] bars = BarUtilities.GetChunkedBars(Config.PolyglotBar, stacks, gauge->PolyglotStacks, stacks, 0, glowConfig: glow); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.PolyglotBar.StrataLevel)); } } } protected unsafe void DrawParadoxBar(Vector2 origin, IPlayerCharacter player) { BLMGauge _gauge = Plugin.JobGauges.Get(); BlackMageGaugeTmp* gauge = (BlackMageGaugeTmp*)_gauge.Address; bool inUmbralIce = gauge->ElementStance < 0; bool inAstralFire = gauge->ElementStance > 0; if (Config.ParadoxBar.HideWhenInactive && !gauge->ParadoxActive) { return; }; PluginConfigColor color = Config.ParadoxBar.FillColor; if (Config.ParadoxBar.UseElementColor) { color = inUmbralIce ? Config.ParadoxBar.IceColor : (inAstralFire ? Config.ParadoxBar.FireColor : color); } BarGlowConfig? glow = gauge->ParadoxActive && Config.ParadoxBar.GlowConfig.Enabled ? Config.ParadoxBar.GlowConfig : null; BarHud bar = BarUtilities.GetBar(Config.ParadoxBar, gauge->ParadoxActive ? 1 : 0, 1, 0, fillColor: color, glowConfig: glow); AddDrawActions(bar.GetDrawActions(origin, Config.ParadoxBar.StrataLevel)); } protected void DrawThunderDoTBar(Vector2 origin, IPlayerCharacter player) { IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target; BarHud? bar = BarUtilities.GetDoTBar(Config.ThunderDoTBar, player, target, ThunderDoTIDs, ThunderDoTDurations); if (bar != null) { AddDrawActions(bar.GetDrawActions(origin, Config.ThunderDoTBar.StrataLevel)); } } } [Section("Job Specific Bars")] [SubSection("Caster", 0)] [SubSection("Black Mage", 1)] public class BlackMageConfig : JobConfig { [JsonIgnore] public override uint JobId => JobIDs.BLM; public new static BlackMageConfig DefaultConfig() { var config = new BlackMageConfig(); config.EnochianBar.Label.FontID = FontsConfig.DefaultMediumFontKey; config.EnochianBar.Label.TextAnchor = DrawAnchor.Left; config.EnochianBar.Label.FrameAnchor = DrawAnchor.Left; config.EnochianBar.Label.Position = new Vector2(2, 0); config.ThunderDoTBar.Label.FontID = FontsConfig.DefaultMediumFontKey; config.ThunderDoTBar.Label.TextAnchor = DrawAnchor.Left; config.ThunderDoTBar.Label.FrameAnchor = DrawAnchor.Left; config.ThunderDoTBar.Label.Position = new Vector2(2, 0); return config; } [NestedConfig("Mana Bar", 30)] public BlackMageManaBarConfig ManaBar = new BlackMageManaBarConfig( new Vector2(0, -10), new Vector2(254, 18), new PluginConfigColor(new Vector4(234f / 255f, 95f / 255f, 155f / 255f, 100f / 100f)) ); [NestedConfig("Umbral Ice / Astral Fire Bar", 31)] public BlackMageStacksBarConfig StacksBar = new BlackMageStacksBarConfig( new(-67, -27), new(120, 10) ); [NestedConfig("Umbral Heart Bar", 32)] public ChunkedBarConfig UmbralHeartBar = new ChunkedBarConfig( new(67, -27), new(120, 10), new PluginConfigColor(new Vector4(125f / 255f, 195f / 255f, 205f / 255f, 100f / 100f)) ); [NestedConfig("Paradox Bar", 33)] public BlackMageParadoxBarConfig ParadoxBar = new BlackMageParadoxBarConfig( new(0, -27), new(10, 10), new PluginConfigColor(new Vector4(123f / 255f, 66f / 255f, 177f / 255f, 100f / 100f)) ); [NestedConfig("Enochian Bar", 40)] public ProgressBarConfig EnochianBar = new ProgressBarConfig( new(-16, -41), new(222, 14), new PluginConfigColor(new Vector4(234f / 255f, 95f / 255f, 155f / 255f, 100f / 100f)) ); [NestedConfig("Polyglot Bar", 45)] public BlackMagePolyglotBarConfig PolyglotBar = new BlackMagePolyglotBarConfig( new(112, -41), new(30, 14), new PluginConfigColor(new Vector4(234f / 255f, 95f / 255f, 155f / 255f, 100f / 100f)) ); [NestedConfig("Astral Soul Bar", 45)] public BlackMageAstralSoulBarConfig AstralSoulBar = new BlackMageAstralSoulBarConfig( new(112, -41), new(30, 14), new PluginConfigColor(new Vector4(220f / 255f, 180f / 255f, 180f / 255f, 100f / 100f)) ); [NestedConfig("Triplecast Bar", 50)] public BlackMageTriplecastBarConfig TriplecastBar = new BlackMageTriplecastBarConfig( new(0, -55), new(254, 10), new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 100f / 100f)) ); [NestedConfig("Thunder DoT Bar", 60)] public ProgressBarConfig ThunderDoTBar = new ProgressBarConfig( new(64, -69), new(126, 14), new PluginConfigColor(new Vector4(67f / 255f, 187 / 255f, 255f / 255f, 100f / 100f)) ); } [Exportable(false)] public class BlackMageManaBarConfig : BarConfig { [Checkbox("Use Element Color" + "##MP", spacing = true)] [Order(50)] public bool UseElementColor = true; [ColorEdit4("Ice Color" + "##MP")] [Order(51, collapseWith = nameof(UseElementColor))] public PluginConfigColor IceColor = new PluginConfigColor(new Vector4(69f / 255f, 115f / 255f, 202f / 255f, 100f / 100f)); [ColorEdit4("Ice Background Color" + "##MP")] [Order(52, collapseWith = nameof(UseElementColor))] public PluginConfigColor IceBackgroundColor = new PluginConfigColor(new Vector4(50f / 255f, 80f / 255f, 130f / 255f, 50f / 100f)); [ColorEdit4("Fire Color" + "##MP")] [Order(53, collapseWith = nameof(UseElementColor))] public PluginConfigColor FireColor = new PluginConfigColor(new Vector4(204f / 255f, 40f / 255f, 40f / 255f, 100f / 100f)); [ColorEdit4("Fire Background Color" + "##MP")] [Order(54, collapseWith = nameof(UseElementColor))] public PluginConfigColor FireBackgroundColor = new PluginConfigColor(new Vector4(120f / 255f, 30f / 255f, 30f / 255f, 50f / 100f)); [NestedConfig("Value Label", 60, separator = false, spacing = true)] public NumericLabelConfig ValueLabelConfig = new NumericLabelConfig(new Vector2(2, 0), "", DrawAnchor.Left, DrawAnchor.Left); [NestedConfig("Threshold", 70, separator = false, spacing = true)] public BlackMakeManaBarThresholdConfig ThresholdConfig = new BlackMakeManaBarThresholdConfig(); public BlackMageManaBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor) { } } [Exportable(false)] public class BlackMakeManaBarThresholdConfig : ThresholdConfig { [Checkbox("Show Only During Astral Fire")] [Order(5)] public bool ShowOnlyDuringAstralFire = true; public BlackMakeManaBarThresholdConfig() { Enabled = true; Value = 2400; Color = new PluginConfigColor(new Vector4(240f / 255f, 120f / 255f, 10f / 255f, 100f / 100f)); ShowMarker = true; MarkerColor = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 100f / 100f)); } } [DisableParentSettings("FillColor", "FillDirection")] [Exportable(false)] public class BlackMageStacksBarConfig : ChunkedBarConfig { [ColorEdit4("Ice Color" + "##MP")] [Order(26)] public PluginConfigColor IceColor = new PluginConfigColor(new Vector4(69f / 255f, 115f / 255f, 202f / 255f, 100f / 100f)); [ColorEdit4("Fire Color" + "##MP")] [Order(27)] public PluginConfigColor FireColor = new PluginConfigColor(new Vector4(204f / 255f, 40f / 255f, 40f / 255f, 100f / 100f)); public BlackMageStacksBarConfig(Vector2 position, Vector2 size) : base(position, size, PluginConfigColor.Empty) { } } [Exportable(false)] public class BlackMagePolyglotBarConfig : ChunkedBarConfig { [NestedConfig("Show Glow", 60, separator = false, spacing = true)] public BarGlowConfig GlowConfig = new BarGlowConfig(); public BlackMagePolyglotBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor) { } } [Exportable(false)] public class BlackMageParadoxBarConfig : BarConfig { [Checkbox("Use Element Color" + "##Paradox", spacing = true)] [Order(50)] public bool UseElementColor = true; [ColorEdit4("Ice Color" + "##Paradox")] [Order(51, collapseWith = nameof(UseElementColor))] public PluginConfigColor IceColor = new PluginConfigColor(new Vector4(69f / 255f, 115f / 255f, 202f / 255f, 100f / 100f)); [ColorEdit4("Fire Color" + "##Paradox")] [Order(52, collapseWith = nameof(UseElementColor))] public PluginConfigColor FireColor = new PluginConfigColor(new Vector4(204f / 255f, 40f / 255f, 40f / 255f, 100f / 100f)); [NestedConfig("Show Glow" + "##Paradox", 60, separator = false, spacing = true)] public BarGlowConfig GlowConfig = new BarGlowConfig(); public BlackMageParadoxBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor) { } } [Exportable(false)] public class BlackMageTriplecastBarConfig : ChunkedBarConfig { [Checkbox("Count Swiftcast" + "##Triplecast", spacing = true)] [Order(50)] public bool CountSwiftcast = false; public BlackMageTriplecastBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor) { } } [DisableParentSettings("FillDirection")] [Exportable(false)] public class BlackMageAstralSoulBarConfig : ChunkedBarConfig { [ColorEdit4("Full Stacks Color")] [Order(26)] public PluginConfigColor FullStacksColor = new PluginConfigColor(new Vector4(255f / 255f, 200f / 255f, 160f / 255f, 100f / 100f)); [NestedConfig("Show Glow" + "##AstralSoul", 60, separator = false, spacing = true)] public BarGlowConfig GlowConfig = new BarGlowConfig(); public BlackMageAstralSoulBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor) { } } } [StructLayout(LayoutKind.Explicit, Size = 0x30)] public struct BlackMageGaugeTmp { [FieldOffset(0x08)] public short EnochianTimer; [FieldOffset(0x0A)] public sbyte ElementStance; [FieldOffset(0x0B)] public byte UmbralHearts; [FieldOffset(0x0C)] public byte PolyglotStacks; [FieldOffset(0x0D)] public EnochianFlags EnochianFlags; public int UmbralStacks => ElementStance >= 0 ? 0 : ElementStance * -1; public int AstralStacks => ElementStance <= 0 ? 0 : ElementStance; public bool EnochianActive => EnochianFlags.HasFlag(EnochianFlags.Enochian); public bool ParadoxActive => EnochianFlags.HasFlag(EnochianFlags.Paradox); public int AstralSoulStacks => ((int)EnochianFlags >> 2) & 7; }