using Dalamud.Game.ClientState.JobGauge.Types; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Statuses; using HSUI.Config; using HSUI.Config.Attributes; 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.Linq; using System.Numerics; namespace HSUI.Interface.Jobs { public class NinjaHud : JobHud { private new NinjaConfig Config => (NinjaConfig)_config; public NinjaHud(NinjaConfig config, string? displayName = null) : base(config, displayName) { } protected override (List, List) ChildrenPositionsAndSizes() { List positions = new List(); List sizes = new List(); if (Config.KazematoiBar.Enabled) { positions.Add(Config.Position + Config.KazematoiBar.Position); sizes.Add(Config.KazematoiBar.Size); } if (Config.NinkiBar.Enabled) { positions.Add(Config.Position + Config.NinkiBar.Position); sizes.Add(Config.NinkiBar.Size); } if (Config.TrickAttackBar.Enabled) { positions.Add(Config.Position + Config.TrickAttackBar.Position); sizes.Add(Config.TrickAttackBar.Size); } if (Config.SuitonBar.Enabled) { positions.Add(Config.Position + Config.SuitonBar.Position); sizes.Add(Config.SuitonBar.Size); } if (Config.MudraBar.Enabled) { positions.Add(Config.Position + Config.MudraBar.Position); sizes.Add(Config.MudraBar.Size); } return (positions, sizes); } public override void DrawJobHud(Vector2 origin, IPlayerCharacter player) { var pos = origin + Config.Position; if (Config.MudraBar.Enabled) { DrawMudraBars(pos, player); } if (Config.KazematoiBar.Enabled) { DrawKazematoiBar(pos, player); } if (Config.NinkiBar.Enabled) { DrawNinkiGauge(pos, player); } if (Config.TrickAttackBar.Enabled) { DrawTrickAttackBar(pos, player); } if (Config.SuitonBar.Enabled) { DrawSuitonBar(pos, player); } } public (bool, bool, bool) GetMudraBuffs(IPlayerCharacter? player, out IStatus? ninjutsuBuff, out IStatus? kassatsuBuff, out IStatus? tcjBuff) { ninjutsuBuff = null; kassatsuBuff = null; tcjBuff = null; if (player is not null) { var statusList = Utils.StatusListForBattleChara(player); foreach (IStatus status in statusList) { if (status.StatusId == 496) { ninjutsuBuff = status; } if (status.StatusId == 497) { kassatsuBuff = status; } if (status.StatusId == 1186) { tcjBuff = status; } } } return (ninjutsuBuff is not null, kassatsuBuff is not null, tcjBuff is not null); } private void DrawMudraBars(Vector2 origin, IPlayerCharacter player) { var (hasNinjutsuBuff, hasKassatsuBuff, hasTCJBuff) = GetMudraBuffs(player, out IStatus? ninjutsuBuff, out IStatus? kassatsuBuff, out IStatus? tcjBuff); int mudraStacks = SpellHelper.Instance.GetStackCount(2, 2259); float mudraCooldown = SpellHelper.Instance.GetSpellCooldown(2259); float current = 0f; float max = 0f; // For some reason, the mudras may be on cooldown before the "Mudra" buff is applied. // Mudra stack count is set to -2 when a mudra is in the middle of its re-cast timer, so we can check for that instead. bool inNinjutsu = mudraStacks == -2 || hasNinjutsuBuff; if (hasTCJBuff || hasKassatsuBuff || inNinjutsu) { if (hasTCJBuff) { max = 6f; current = tcjBuff is null || tcjBuff.RemainingTime < 0 ? max : tcjBuff.RemainingTime; Config.MudraBar.Label.SetText(GenerateNinjutsuText(tcjBuff?.Param ?? 0, hasKassatsuBuff, hasTCJBuff)); } else if (hasKassatsuBuff) { max = 15f; current = kassatsuBuff is null || kassatsuBuff.RemainingTime < 0 ? max : kassatsuBuff.RemainingTime; Config.MudraBar.Label.SetText("KASSATSU"); } if (inNinjutsu) { max = 6f; current = ninjutsuBuff is null || ninjutsuBuff.RemainingTime < 0 ? max : ninjutsuBuff.RemainingTime; Config.MudraBar.Label.SetText(GenerateNinjutsuText(ninjutsuBuff?.Param ?? 0, hasKassatsuBuff, hasTCJBuff)); } PluginConfigColor fillColor = hasTCJBuff ? Config.MudraBar.TCJBarColor : hasKassatsuBuff ? Config.MudraBar.KassatsuBarColor : Config.MudraBar.FillColor; Rect foreground = BarUtilities.GetFillRect(Config.MudraBar.Position, Config.MudraBar.Size, Config.MudraBar.FillDirection, fillColor, current, max); BarHud bar = new BarHud(Config.MudraBar, player); bar.AddForegrounds(foreground); bar.AddLabels(Config.MudraBar.Label); AddDrawActions(bar.GetDrawActions(origin, Config.MudraBar.StrataLevel)); } else { max = 40f; current = max - mudraCooldown; if (!Config.MudraBar.HideWhenInactive || current < max) { Config.MudraBar.Label.SetValue((max - current) % 20); BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.MudraBar, 2, current, max, 0f, player); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.MudraBar.StrataLevel)); } } } } private unsafe void DrawKazematoiBar(Vector2 origin, IPlayerCharacter player) { NinjaGauge* gauge = (NinjaGauge*)Plugin.JobGauges.Get().Address; int stacks = gauge->Kazematoi; if (Config.KazematoiBar.HideWhenInactive && stacks == 0) { return; } BarHud[] bars = BarUtilities.GetChunkedBars(Config.KazematoiBar, 5, stacks, 5, 0, player); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.KazematoiBar.StrataLevel)); } } private unsafe void DrawNinkiGauge(Vector2 origin, IPlayerCharacter player) { NinjaGauge* gauge = (NinjaGauge*)Plugin.JobGauges.Get().Address; int ninki = gauge->Ninki; if (Config.NinkiBar.HideWhenInactive && ninki == 0) { return; } Config.NinkiBar.Label.SetValue(ninki); BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.NinkiBar, 2, ninki, 100, 0, player); foreach (BarHud bar in bars) { AddDrawActions(bar.GetDrawActions(origin, Config.NinkiBar.StrataLevel)); } } private void DrawTrickAttackBar(Vector2 origin, IPlayerCharacter player) { IGameObject? actor = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target; float trickDuration = Utils.StatusListForActor(actor).FirstOrDefault( o => o.StatusId is 3254 or 3906 && o.SourceId == player.GameObjectId && o.RemainingTime > 0 )?.RemainingTime ?? 0f; if (Config.TrickAttackBar.HideWhenInactive && trickDuration == 0) { return; } Config.TrickAttackBar.Label.SetValue(trickDuration); BarHud bar = BarUtilities.GetProgressBar(Config.TrickAttackBar, trickDuration, 15f, 0f, player); AddDrawActions(bar.GetDrawActions(origin, Config.TrickAttackBar.StrataLevel)); } private void DrawSuitonBar(Vector2 origin, IPlayerCharacter player) { float suitonDuration = Utils.StatusListForBattleChara(player).FirstOrDefault( o => o.StatusId == 3848 && o.RemainingTime > 0 )?.RemainingTime ?? 0f; if (Config.SuitonBar.HideWhenInactive && suitonDuration == 0) { return; } Config.SuitonBar.Label.SetValue(suitonDuration); BarHud bar = BarUtilities.GetProgressBar(Config.SuitonBar, suitonDuration, 20f, 0f, player); AddDrawActions(bar.GetDrawActions(origin, Config.SuitonBar.StrataLevel)); } private string GenerateNinjutsuText(ushort param, bool haveKassatsuBuff, bool haveTCJBuff) { return param switch { 1 or 2 or 3 => "FUMA SHURIKEN", 6 or 7 => haveKassatsuBuff ? "GOKA MEKKYAKU" : "KATON", 9 or 11 => "RAITON", 13 or 14 => haveKassatsuBuff ? "HYOSHO RANRYU" : "HYOTON", 27 or 30 => "HUTON", 39 or 45 => "DOTON", 54 or 57 => "SUITON", _ => haveTCJBuff ? "TEN CHI JIN" : "", }; } } [Section("Job Specific Bars")] [SubSection("Melee", 0)] [SubSection("Ninja", 1)] public class NinjaConfig : JobConfig { [JsonIgnore] public override uint JobId => JobIDs.NIN; public new static NinjaConfig DefaultConfig() { var config = new NinjaConfig(); config.MudraBar.Label.FontID = FontsConfig.DefaultMediumFontKey; config.MudraBar.LabelMode = LabelMode.ActiveChunk; config.TrickAttackBar.Label.FontID = FontsConfig.DefaultMediumFontKey; config.TrickAttackBar.Enabled = false; config.SuitonBar.Label.FontID = FontsConfig.DefaultMediumFontKey; config.SuitonBar.Enabled = false; config.NinkiBar.UsePartialFillColor = true; return config; } [NestedConfig("Mudra Bar", 30)] public NinjaMudraBarConfig MudraBar = new NinjaMudraBarConfig( new(0, -50), new(254, 10), new PluginConfigColor(new Vector4(211f / 255f, 166f / 255f, 75f / 242f, 100f / 100f)) ); [NestedConfig("Kazematoi Bar", 35)] public ChunkedBarConfig KazematoiBar = new ChunkedBarConfig( new(0, -10), new(254, 20), PluginConfigColor.FromHex(0xFFABB6BD) ); [NestedConfig("Ninki Bar", 40)] public ChunkedProgressBarConfig NinkiBar = new ChunkedProgressBarConfig( new(0, -32), new(254, 20), new PluginConfigColor(new Vector4(137f / 255f, 82f / 255f, 236f / 255f, 100f / 100f)) ); [NestedConfig("Trick Attack Bar", 45)] public ProgressBarConfig TrickAttackBar = new ProgressBarConfig( new(0, -63), new(254, 10), new PluginConfigColor(new Vector4(191f / 255f, 40f / 255f, 0f / 255f, 100f / 100f)) ); [NestedConfig("Shadow Walker Bar", 50)] public ProgressBarConfig SuitonBar = new ProgressBarConfig( new(0, -75), new(254, 10), new PluginConfigColor(new Vector4(202f / 255f, 228f / 255f, 246f / 242f, 100f / 100f)) ); } [Exportable(false)] public class NinjaMudraBarConfig : ChunkedProgressBarConfig { [ColorEdit4("Kassatsu Color", spacing = true)] [Order(60)] public PluginConfigColor KassatsuBarColor = new(new Vector4(239 / 255f, 123 / 255f, 222 / 242f, 100f / 100f)); [ColorEdit4("Ten Chi Jin Color")] [Order(65)] public PluginConfigColor TCJBarColor = new(new Vector4(181 / 255f, 33 / 255f, 41 / 242f, 100f / 100f)); public NinjaMudraBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor, 2) { Label.Enabled = true; UsePartialFillColor = true; } } }