Initial release: HSUI v1.0.0.0 - HUD replacement with configurable hotbars

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-01-30 23:52:46 -05:00
commit f37369cdda
202 changed files with 40137 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using System.Numerics;
using HSUI.Interface.GeneralElements;
using HSUI.Enums;
namespace HSUI.Interface.Bars
{
[Exportable(false)]
public class BarConfig : AnchorablePluginConfigObject
{
[ColorEdit4("Background Color")]
[Order(16)]
public PluginConfigColor BackgroundColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 50f / 100f));
[ColorEdit4("Fill Color")]
[Order(25)]
public PluginConfigColor FillColor;
[Combo("Fill Direction", new string[] { "Left", "Right", "Up", "Down" })]
[Order(30)]
public BarDirection FillDirection;
[BarTexture("Bar Texture", spacing = true, help = "Default means the bar will be drawn using the global gradient configuration for bars found in Colors > Misc.")]
[Order(31)]
public string BarTextureName = "";
[BarTextureDrawMode("Draw Mode")]
[Order(32)]
public BarTextureDrawMode BarTextureDrawMode = BarTextureDrawMode.Stretch;
[Checkbox("Show Border", spacing = true)]
[Order(35)]
public bool DrawBorder = true;
[ColorEdit4("Border Color")]
[Order(36, collapseWith = nameof(DrawBorder))]
public PluginConfigColor BorderColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[DragInt("Border Thickness", min = 1, max = 10)]
[Order(37, collapseWith = nameof(DrawBorder))]
public int BorderThickness = 1;
[NestedConfig("Shadow", 40, spacing = true)]
public ShadowConfig ShadowConfig = new ShadowConfig() { Enabled = false };
[Checkbox("Hide When Inactive", spacing = true)]
[Order(41)]
public bool HideWhenInactive = false;
public BarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
{
Position = position;
Size = size;
FillColor = fillColor;
FillDirection = fillDirection;
}
}
[Exportable(false)]
public class BarGlowConfig : PluginConfigObject
{
[ColorEdit4("Color")]
[Order(5)]
public PluginConfigColor Color = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 50f / 100f));
[DragInt("Size", min = 1, max = 100)]
[Order(25)]
public int Size = 1;
}
public enum BarDirection
{
Left,
Right,
Up,
Down
}
}
+210
View File
@@ -0,0 +1,210 @@
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.Numerics;
namespace HSUI.Interface.Bars
{
public class BarHud
{
private string ID { get; set; }
private Rect BackgroundRect { get; set; } = new Rect();
private List<Rect> ForegroundRects { get; set; } = new List<Rect>();
private List<LabelHud> LabelHuds { get; set; } = new List<LabelHud>();
private bool DrawBorder { get; set; }
private PluginConfigColor? BorderColor { get; set; }
private int BorderThickness { get; set; }
private DrawAnchor Anchor { get; set; }
private IGameObject? Actor { get; set; }
private PluginConfigColor? GlowColor { get; set; }
private int GlowSize { get; set; }
private float? Current;
private float? Max;
private ShadowConfig? ShadowConfig { get; set; }
private string? BarTextureName { get; set; }
private BarTextureDrawMode BarTextureDrawMode { get; set; }
public bool NeedsInputs = false;
public BarHud(
string id,
bool drawBorder = true,
PluginConfigColor? borderColor = null,
int borderThickness = 1,
DrawAnchor anchor = DrawAnchor.TopLeft,
IGameObject? actor = null,
PluginConfigColor? glowColor = null,
int? glowSize = 1,
float? current = null,
float? max = null,
ShadowConfig? shadowConfig = null,
string? barTextureName = null,
BarTextureDrawMode barTextureDrawMode = BarTextureDrawMode.Stretch)
{
ID = id;
DrawBorder = drawBorder;
BorderColor = borderColor;
BorderThickness = borderThickness;
Anchor = anchor;
Actor = actor;
GlowColor = glowColor;
GlowSize = glowSize ?? 1;
Current = current;
Max = max;
ShadowConfig = shadowConfig;
BarTextureName = barTextureName;
BarTextureDrawMode = barTextureDrawMode;
}
public BarHud(BarConfig config, IGameObject? actor = null, BarGlowConfig? glowConfig = null, float? current = null, float? max = null)
: this(config.ID,
config.DrawBorder,
config.BorderColor,
config.BorderThickness,
config.Anchor,
actor,
glowConfig?.Color,
glowConfig?.Size,
current,
max,
null,
config.BarTextureName,
config.BarTextureDrawMode)
{
BackgroundRect = new Rect(config.Position, config.Size, config.BackgroundColor);
ShadowConfig = config.ShadowConfig;
}
public BarHud SetBackground(Rect rect)
{
BackgroundRect = rect;
return this;
}
public BarHud AddForegrounds(params Rect[] rects)
{
ForegroundRects.AddRange(rects);
return this;
}
public BarHud AddLabels(params LabelConfig[]? labels)
{
if (labels != null)
{
foreach (LabelConfig config in labels)
{
var labelHud = new LabelHud(config);
LabelHuds.Add(labelHud);
}
}
return this;
}
public BarHud SetGlow(PluginConfigColor color, int size = 1)
{
GlowColor = color;
GlowSize = size;
return this;
}
public void Draw(Vector2 origin)
{
var barPos = Utils.GetAnchoredPosition(origin, BackgroundRect.Size, Anchor);
var backgroundPos = barPos + BackgroundRect.Position;
DrawRects(barPos, backgroundPos);
// labels
foreach (LabelHud label in LabelHuds)
{
label.Draw(backgroundPos, BackgroundRect.Size, Actor, null, (uint?)Current, (uint?)Max);
}
}
public List<(StrataLevel, Action)> GetDrawActions(Vector2 origin, StrataLevel strataLevel)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
var barPos = Utils.GetAnchoredPosition(origin, BackgroundRect.Size, Anchor);
var backgroundPos = barPos + BackgroundRect.Position;
drawActions.Add((strataLevel, () =>
{
DrawRects(barPos, backgroundPos);
}
));
// labels
foreach (LabelHud label in LabelHuds)
{
drawActions.Add((label.GetConfig().StrataLevel, () =>
{
label.Draw(backgroundPos, BackgroundRect.Size, Actor, null, (uint?)Current, (uint?)Max);
}
));
}
return drawActions;
}
private void DrawRects(Vector2 barPos, Vector2 backgroundPos)
{
DrawHelper.DrawInWindow(ID, backgroundPos, BackgroundRect.Size, NeedsInputs, (drawList) =>
{
// Draw background
drawList.AddRectFilled(backgroundPos, backgroundPos + BackgroundRect.Size, BackgroundRect.Color.Base);
// Draw Shadow
if (ShadowConfig != null && ShadowConfig.Enabled)
{
// Right Side
drawList.AddRectFilled(backgroundPos + new Vector2(BackgroundRect.Size.X, ShadowConfig.Offset), backgroundPos + BackgroundRect.Size + new Vector2(ShadowConfig.Offset, ShadowConfig.Offset) + new Vector2(ShadowConfig.Thickness - 1, ShadowConfig.Thickness - 1), ShadowConfig.Color.Base);
// Bottom Size
drawList.AddRectFilled(backgroundPos + new Vector2(ShadowConfig.Offset, BackgroundRect.Size.Y), backgroundPos + BackgroundRect.Size + new Vector2(ShadowConfig.Offset, ShadowConfig.Offset) + new Vector2(ShadowConfig.Thickness - 1, ShadowConfig.Thickness - 1), ShadowConfig.Color.Base);
}
// Draw foregrounds
foreach (Rect rect in ForegroundRects)
{
DrawHelper.DrawBarTexture(barPos + rect.Position, rect.Size, rect.Color, BarTextureName, BarTextureDrawMode, drawList);
}
// Draw Border
if (DrawBorder)
{
drawList.AddRect(backgroundPos, backgroundPos + BackgroundRect.Size, BorderColor?.Base ?? 0xFF000000, 0, ImDrawFlags.None, BorderThickness);
}
// Draw Glow
if (GlowColor != null)
{
var glowPosition = new Vector2(backgroundPos.X - 1, backgroundPos.Y - 1);
var glowSize = new Vector2(BackgroundRect.Size.X + 2, BackgroundRect.Size.Y + 2);
drawList.AddRect(glowPosition, glowPosition + glowSize, GlowColor.Base, 0, ImDrawFlags.None, GlowSize);
}
});
}
}
}
+441
View File
@@ -0,0 +1,441 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Statuses;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.GeneralElements;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Bars
{
public class BarUtilities
{
public static BarHud GetProgressBar(ProgressBarConfig config, float current, float max, float min = 0f, IGameObject? actor = null, PluginConfigColor? fillColor = null, BarGlowConfig? barGlowConfig = null)
{
return GetProgressBar(config, config.ThresholdConfig, new LabelConfig[] { config.Label }, current, max, min, actor, fillColor, barGlowConfig);
}
public static BarHud GetProgressBar(
BarConfig config,
ThresholdConfig? thresholdConfig,
LabelConfig[]? labelConfigs,
float current,
float max,
float min = 0f,
IGameObject? actor = null,
PluginConfigColor? fillColor = null,
BarGlowConfig? glowConfig = null,
PluginConfigColor? backgroundColor = null
)
{
BarHud bar = new(config, actor, glowConfig, current, max);
PluginConfigColor color = fillColor ?? config.FillColor;
if (thresholdConfig != null)
{
color = thresholdConfig.ChangeColor && thresholdConfig.IsActive(current) ? thresholdConfig.Color : color;
}
Rect foreground = GetFillRect(config.Position, config.Size, config.FillDirection, color, current, max, min);
bar.AddForegrounds(foreground);
bar.AddLabels(labelConfigs);
if (backgroundColor != null)
{
Rect bg = new Rect(config.Position, config.Size, backgroundColor);
bar.SetBackground(bg);
}
AddThresholdMarker(bar, config, thresholdConfig, max, min);
return bar;
}
public static BarHud? GetProcBar(
ProgressBarConfig config,
IPlayerCharacter player,
uint statusId,
float maxDuration,
bool trackDuration = true)
{
return GetProcBar(config, player, new List<uint> { statusId }, new List<float> { maxDuration }, trackDuration);
}
public static BarHud? GetProcBar(
ProgressBarConfig config,
IPlayerCharacter player,
List<uint> statusIDs,
List<float> maxDurations,
bool trackDuration = true)
{
if (statusIDs.Count == 0 || maxDurations.Count == 0) { return null; }
IStatus? status = Utils.StatusListForBattleChara(player).FirstOrDefault(o => statusIDs.Contains(o.StatusId));
if (status == null && config.HideWhenInactive)
{
return null;
}
float duration = Math.Abs(status?.RemainingTime ?? 0);
if (trackDuration)
{
int index = status != null ? statusIDs.IndexOf(status.StatusId) : 0;
config.Label.SetValue(duration);
return GetProgressBar(config, duration, maxDurations[index], 0, player);
}
config.Label.SetText("");
return GetBar(config, duration <= 0 ? 0 : 1, 1, 0);
}
public static BarHud? GetDoTBar(
ProgressBarConfig config,
IPlayerCharacter player,
IGameObject? target,
uint statusId,
float maxDuration)
{
return GetDoTBar(config, player, target, new List<uint> { statusId }, new List<float> { maxDuration });
}
public static BarHud? GetDoTBar(
ProgressBarConfig config,
IPlayerCharacter player,
IGameObject? target,
List<uint> statusIDs,
List<float> maxDurations)
{
if (statusIDs.Count == 0 || maxDurations.Count == 0) { return null; }
IStatus? status = null;
if (target != null && target is IBattleChara targetChara)
{
status = Utils.StatusListForBattleChara(targetChara).FirstOrDefault(o => o.SourceId == player.GameObjectId && statusIDs.Contains(o.StatusId));
}
if (status == null && config.HideWhenInactive)
{
return null;
}
int index = status != null ? statusIDs.IndexOf(status.StatusId) : 0;
float duration = Math.Abs(status?.RemainingTime ?? 0);
float maxDuration = maxDurations[index];
config.Label.SetValue(duration);
return GetProgressBar(config, duration, maxDuration, 0, player);
}
private static void AddThresholdMarker(BarHud bar, BarConfig config, ThresholdConfig? thresholdConfig, float max, float min)
{
if (thresholdConfig == null || !thresholdConfig.Enabled || !thresholdConfig.ShowMarker)
{
return;
}
float thresholdPercent = Math.Clamp(thresholdConfig.Value / (max - min), 0f, 1f);
Vector2 offset = GetFillDirectionOffset(
new Vector2(config.Size.X * thresholdPercent, config.Size.Y * thresholdPercent),
config.FillDirection
);
Vector2 markerSize = config.FillDirection.IsHorizontal() ?
new Vector2(thresholdConfig.MarkerSize, config.Size.Y) :
new Vector2(config.Size.X, thresholdConfig.MarkerSize);
Vector2 markerPos = config.FillDirection.IsInverted() ?
config.Position + GetFillDirectionOffset(config.Size, config.FillDirection) - offset :
config.Position + offset;
Vector2 anchoredPos = Utils.GetAnchoredPosition(markerPos, markerSize, config.FillDirection.IsHorizontal() ? DrawAnchor.Top : DrawAnchor.Left);
Rect marker = new(anchoredPos, markerSize, thresholdConfig.MarkerColor);
bar.AddForegrounds(marker);
}
// Tuple is <foregroundColor, percent fill, labels>
public static BarHud[] GetChunkedBars(
ChunkedBarConfig config,
Tuple<PluginConfigColor, float, LabelConfig?>[] chunks,
IGameObject? actor,
BarGlowConfig glowConfig)
{
List<bool> chunksToGlowList = new();
for (int i = 0; i < chunks.Length; i++)
{
chunksToGlowList.Add(chunks[i].Item2 >= 1f);
}
return GetChunkedBars(config, chunks, actor, glowConfig, chunksToGlowList.ToArray());
}
public static BarHud[] GetChunkedBars(
ChunkedBarConfig config,
Tuple<PluginConfigColor, float, LabelConfig?>[] chunks,
IGameObject? actor,
BarGlowConfig? glowConfig = null,
bool[]? chunksToGlow = null)
{
BarHud[] bars = new BarHud[chunks.Length];
Vector2 pos = Utils.GetAnchoredPosition(config.Position, config.Size, config.Anchor);
for (int i = 0; i < chunks.Length; i++)
{
Vector2 chunkPos, chunkSize;
if (config.FillDirection.IsHorizontal())
{
chunkSize = new Vector2((config.Size.X - config.Padding * (chunks.Length - 1)) / chunks.Length, config.Size.Y);
chunkPos = pos + new Vector2((chunkSize.X + config.Padding) * i, 0);
}
else
{
chunkSize = new Vector2(config.Size.X, (config.Size.Y - config.Padding * (chunks.Length - 1)) / chunks.Length);
chunkPos = pos + new Vector2(0, (chunkSize.Y + config.Padding) * i);
}
Rect background = new(chunkPos, chunkSize, config.BackgroundColor);
Rect foreground = GetFillRect(chunkPos, chunkSize, config.FillDirection, chunks[i].Item1, chunks[i].Item2, 1f, 0f);
BarGlowConfig? glow = (glowConfig?.Enabled == true && chunksToGlow?[i] == true) ? glowConfig : null;
bars[i] = new BarHud(config.ID + i,
config.DrawBorder,
config.BorderColor,
config.BorderThickness,
actor: actor,
glowColor: glow?.Color,
glowSize: glow?.Size,
barTextureName: config.BarTextureName,
barTextureDrawMode: config.BarTextureDrawMode,
shadowConfig: config.ShadowConfig
);
bars[i].SetBackground(background);
bars[i].AddForegrounds(foreground);
LabelConfig? label = chunks[i].Item3;
if (label is not null)
{
bars[i].AddLabels(label);
}
}
return bars;
}
public static BarHud[] GetChunkedBars(
ChunkedBarConfig config,
int chunks,
float current,
float max,
float min = 0f,
IGameObject? actor = null,
LabelConfig?[]? labels = null,
PluginConfigColor? fillColor = null,
PluginConfigColor? partialFillColor = null,
BarGlowConfig? glowConfig = null,
bool[]? chunksToGlow = null)
{
float chunkRange = (max - min) / chunks;
var barChunks = new Tuple<PluginConfigColor, float, LabelConfig?>[chunks];
for (int i = 0; i < chunks; i++)
{
int barIndex = config.FillDirection.IsInverted() ? chunks - i - 1 : i;
float chunkMin = min + chunkRange * i;
float chunkMax = min + chunkRange * (i + 1);
float chunkPercent = Math.Clamp((current - chunkMin) / (chunkMax - chunkMin), 0f, 1f);
PluginConfigColor chunkColor = partialFillColor != null && current < chunkMax ? partialFillColor : fillColor ?? config.FillColor;
barChunks[barIndex] = new Tuple<PluginConfigColor, float, LabelConfig?>(chunkColor, chunkPercent, labels?[i]);
}
if (glowConfig != null && chunksToGlow == null)
{
return GetChunkedBars(config, barChunks, actor, glowConfig);
}
return GetChunkedBars(config, barChunks, actor, glowConfig, chunksToGlow);
}
public static BarHud[] GetChunkedProgressBars(
ChunkedProgressBarConfig config,
int chunks,
float current,
float max,
float min = 0f,
IGameObject? actor = null,
BarGlowConfig? glowConfig = null,
PluginConfigColor? fillColor = null,
int thresholdChunk = 1,
bool[]? chunksToGlow = null,
int forceLabelIndex = -1)
{
var color = fillColor ?? config.FillColor;
if (config.UseChunks)
{
NumericLabelConfig?[] labels = new NumericLabelConfig?[chunks];
for (int i = 0; i < chunks; i++)
{
float chunkRange = (max - min) / chunks;
float chunkMin = min + chunkRange * i;
float chunkMax = min + chunkRange * (i + 1);
float chunkPercent = Math.Clamp((current - chunkMin) / (chunkMax - chunkMin), 0f, 1f);
NumericLabelConfig? label = config.Label;
if (forceLabelIndex == -1)
{
switch (config.LabelMode)
{
case LabelMode.AllChunks:
label = config.Label.Clone(i);
label.SetValue(Math.Clamp(current - chunkMin, 0, chunkRange));
break;
case LabelMode.ActiveChunk:
label = chunkPercent < 1f && chunkPercent > 0f ? config.Label.Clone(i) : null;
break;
};
}
else
{
label = forceLabelIndex == i ? config.Label : null;
}
labels[i] = label;
}
var partialColor = config.UsePartialFillColor ? config.PartialFillColor : null;
return GetChunkedBars(config, chunks, current, max, min, actor, labels, color, partialColor, glowConfig, chunksToGlow);
}
var threshold = GetThresholdConfigForChunk(config, thresholdChunk, chunks, min, max);
BarHud bar = GetProgressBar(config, threshold, new LabelConfig[] { config.Label }, current, max, min, actor, color, glowConfig);
return new BarHud[] { bar };
}
public static Rect[] GetShieldForeground(
ShieldConfig shieldConfig,
Vector2 pos,
Vector2 size,
Vector2 healthFillSize,
BarDirection fillDirection,
float shieldPercent,
float currentHp,
float maxHp,
PluginConfigColor? color = null)
{
float shieldValue = shieldPercent * maxHp;
float overshield = shieldConfig.FillHealthFirst ? Math.Max(shieldValue + currentHp - maxHp, 0f) : shieldValue;
float shieldSize = shieldConfig.Height;
PluginConfigColor c = color ?? shieldConfig.Color;
if (!shieldConfig.HeightInPixels)
{
shieldSize = (fillDirection.IsHorizontal() ? size.Y : size.X) * shieldConfig.Height / 100f;
}
var overshieldSize = fillDirection.IsHorizontal()
? new Vector2(size.X, Math.Min(shieldSize, size.Y))
: new Vector2(Math.Min(shieldSize, size.X), size.Y);
Rect overshieldFill = GetFillRect(pos, overshieldSize, fillDirection, c, overshield, maxHp);
if (shieldConfig.FillHealthFirst && currentHp < maxHp)
{
var shieldPos = fillDirection.IsInverted() ? pos : pos + GetFillDirectionOffset(healthFillSize, fillDirection);
var shieldFillSize = size - GetFillDirectionOffset(healthFillSize, fillDirection);
var healthFillShieldSize = fillDirection.IsHorizontal()
? new Vector2(shieldFillSize.X, Math.Min(shieldSize, size.Y))
: new Vector2(Math.Min(shieldSize, size.X), shieldFillSize.Y);
Rect shieldFill = GetFillRect(shieldPos, healthFillShieldSize, fillDirection, c, shieldValue - overshield, maxHp - currentHp, 0f);
return new[] { overshieldFill, shieldFill };
}
return new[] { overshieldFill };
}
public static BarHud GetBar(
BarConfig Config,
float current,
float max,
float min = 0f,
IGameObject? actor = null,
PluginConfigColor? fillColor = null,
BarGlowConfig? glowConfig = null,
LabelConfig[]? labels = null)
{
Rect foreground = GetFillRect(Config.Position, Config.Size, Config.FillDirection, fillColor ?? Config.FillColor, current, max, min);
BarHud bar = new BarHud(Config, actor, glowConfig);
bar.AddForegrounds(foreground);
bar.AddLabels(labels);
return bar;
}
/// <summary>
/// Gets the horizonal or vertical offset depending on the fill direction.
/// </summary>
public static Vector2 GetFillDirectionOffset(Vector2 size, BarDirection fillDirection)
{
return fillDirection.IsHorizontal() ? new(size.X, 0) : new(0, size.Y);
}
public static Rect GetFillRect(Vector2 pos, Vector2 size, BarDirection fillDirection, PluginConfigColor color, float current, float max, float min = 0f)
{
float fillPercent = max == 0 ? 1f : Math.Clamp((current - min) / (max - min), 0f, 1f);
Vector2 fillPos = Vector2.Zero;
Vector2 fillSize = fillDirection.IsHorizontal() ? new(size.X * fillPercent, size.Y) : new(size.X, size.Y * fillPercent);
if (fillDirection == BarDirection.Left)
{
fillPos = Utils.GetAnchoredPosition(new(size.X, 0), fillSize, DrawAnchor.TopRight);
}
else if (fillDirection == BarDirection.Up)
{
fillPos = Utils.GetAnchoredPosition(new(0, size.Y), fillSize, DrawAnchor.BottomLeft);
}
return new Rect(pos + fillPos, fillSize, color);
}
public static ThresholdConfig GetThresholdConfigForChunk(ChunkedProgressBarConfig config, int chunk, int chunks, float min, float max) =>
new ThresholdConfig
{
ThresholdType = ThresholdType.Below,
Color = config.PartialFillColor,
Enabled = config.UsePartialFillColor,
Value = (max - min) / chunks * chunk,
ChangeColor = true,
ShowMarker = false
};
public static void AddShield(BarHud bar, BarConfig config, ShieldConfig shieldConfig, ICharacter character, Vector2 fillSize, PluginConfigColor? color = null)
{
if (shieldConfig.Enabled)
{
float shield = Utils.ActorShieldValue(character);
if (shield > 0f)
{
bar.AddForegrounds(
GetShieldForeground(
shieldConfig,
config.Position,
config.Size,
fillSize,
config.FillDirection,
shield,
character.CurrentHp,
character.MaxHp,
color)
);
}
}
}
}
}
+83
View File
@@ -0,0 +1,83 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Interface.GeneralElements;
using System.Numerics;
namespace HSUI.Interface.Bars
{
[Exportable(false)]
public class ChunkedBarConfig : BarConfig
{
[DragInt("Padding", min = -4000, max = 4000)]
[Order(45)]
public int Padding = 2;
public ChunkedBarConfig(
Vector2 position,
Vector2 size,
PluginConfigColor fillColor,
int padding = 2) : base(position, size, fillColor)
{
Padding = padding;
}
}
[Exportable(false)]
public class ChunkedProgressBarConfig : ChunkedBarConfig
{
[Checkbox("Show In Chunks", spacing = true)]
[Order(46)]
public bool UseChunks = true;
[RadioSelector("Show Text on All Chunks", "Show Text on Active Chunk")]
[Order(47, collapseWith = nameof(UseChunks))]
public LabelMode LabelMode;
[Checkbox("Use Partial Fill Color", spacing = true)]
[Order(50)]
public bool UsePartialFillColor = false;
[ColorEdit4("Partial Fill Color")]
[Order(55, collapseWith = nameof(UsePartialFillColor))]
public PluginConfigColor PartialFillColor;
[NestedConfig("Bar Text", 1000, separator = false, spacing = true)]
public NumericLabelConfig Label;
public ChunkedProgressBarConfig(
Vector2 position,
Vector2 size,
PluginConfigColor fillColor,
int padding = 2,
PluginConfigColor? partialFillColor = null) : base(position, size, fillColor, padding)
{
Label = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
Label.Enabled = false;
PartialFillColor = partialFillColor ?? new PluginConfigColor(new(180f / 255f, 180f / 255f, 180f / 255f, 100f / 100f));
}
}
[DisableParentSettings("LabelMode", "UsePartialFillColor", "PartialFillColor")]
[Exportable(false)]
public class StacksWithDurationBarConfig : ChunkedProgressBarConfig
{
public StacksWithDurationBarConfig(
Vector2 position,
Vector2 size,
PluginConfigColor fillColor,
int padding = 2,
PluginConfigColor? partialFillColor = null) : base(position, size, fillColor, padding)
{
UseChunks = true;
UsePartialFillColor = false;
}
}
public enum LabelMode
{
AllChunks,
ActiveChunk
}
}
+80
View File
@@ -0,0 +1,80 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Interface.GeneralElements;
using System.Numerics;
namespace HSUI.Interface.Bars
{
[Exportable(false)]
public class ProgressBarConfig : BarConfig
{
[NestedConfig("Threshold", 45)]
public ThresholdConfig ThresholdConfig = new ThresholdConfig();
[NestedConfig("Bar Text", 1000)]
public NumericLabelConfig Label;
public ProgressBarConfig(
Vector2 position,
Vector2 size,
PluginConfigColor fillColor,
BarDirection fillDirection = BarDirection.Right,
PluginConfigColor? threshHoldColor = null,
float threshold = 0f) : base(position, size, fillColor, fillDirection)
{
Label = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
ThresholdConfig.Color = threshHoldColor ?? ThresholdConfig.Color;
ThresholdConfig.Value = threshold;
}
}
[Exportable(false)]
public class ThresholdConfig : PluginConfigObject
{
[DragFloat("Threshold Value", min = 0f, max = 10000f)]
[Order(10)]
public float Value = 0f;
[Checkbox("Change Color")]
[Order(15)]
public bool ChangeColor = true;
[Combo("Activate Above/Below Threshold", "Above", "Below")]
[Order(20, collapseWith = nameof(ChangeColor))]
public ThresholdType ThresholdType = ThresholdType.Below;
[ColorEdit4("Color")]
[Order(25, collapseWith = nameof(ChangeColor))]
public PluginConfigColor Color = new PluginConfigColor(new(230f / 255f, 33f / 255f, 33f / 255f, 100f / 100f));
[Checkbox("Show Threshold Marker")]
[Order(30)]
public bool ShowMarker = false;
[DragInt("Threshold Marker Size", min = 0, max = 10000)]
[Order(35, collapseWith = nameof(ShowMarker))]
public int MarkerSize = 2;
[ColorEdit4("Threshold Marker Color")]
[Order(40, collapseWith = nameof(ShowMarker))]
public PluginConfigColor MarkerColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
public bool IsActive(float current)
{
return Enabled && (ThresholdType == ThresholdType.Below && current < Value ||
ThresholdType == ThresholdType.Above && current > Value);
}
public ThresholdConfig()
{
Enabled = false;
}
}
public enum ThresholdType
{
Above,
Below
}
}
+23
View File
@@ -0,0 +1,23 @@
using HSUI.Config;
using System.Numerics;
namespace HSUI.Interface.Bars
{
public class Rect
{
public Vector2 Position { get; set; }
public Vector2 Size { get; set; }
public PluginConfigColor Color { get; set; }
public Rect(Vector2 pos, Vector2 size, PluginConfigColor? color = null)
{
Position = pos;
Size = size;
Color = color ?? new PluginConfigColor(new(0, 0, 0, 0));
}
public Rect() : this(new(0, 0), new(0, 0), new PluginConfigColor(new(0, 0, 0, 0))) { }
}
}
+302
View File
@@ -0,0 +1,302 @@
using Dalamud.Logging;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace HSUI.Interface
{
public delegate void DraggableHudElementSelectHandler(DraggableHudElement element);
public class DraggableHudElement : HudElement
{
public DraggableHudElement(MovablePluginConfigObject config, string? displayName = null) : base(config)
{
_displayName = displayName ?? ID;
}
public event DraggableHudElementSelectHandler? SelectEvent;
public bool Selected = false;
private string _displayName;
protected bool _windowPositionSet = false;
private Vector2 _lastWindowPos = Vector2.Zero;
private Vector2 _positionOffset;
private Vector2 _contentMargin = new Vector2(4, 0);
private bool _draggingEnabled = false;
public bool DraggingEnabled
{
get => _draggingEnabled;
set
{
_draggingEnabled = value;
if (_draggingEnabled)
{
_windowPositionSet = false;
_minPos = null;
_maxPos = null;
}
}
}
public bool CanTakeInputForDrag = false;
public bool NeedsInputForDrag { get; private set; } = false;
public virtual Vector2 ParentPos() { return Vector2.Zero; } // override
protected sealed override void CreateDrawActions(Vector2 origin)
{
if (_draggingEnabled)
{
AddDrawAction(_config.StrataLevel, () =>
{
DrawDraggableArea(origin);
});
return;
}
DrawChildren(origin);
}
public virtual void DrawChildren(Vector2 origin) { }
private bool CalculateNeedsInput(Vector2 pos, Vector2 size, bool selected)
{
Vector2 mousePos = ImGui.GetMousePos();
if (ImGui.IsMouseHoveringRect(pos, pos + size))
{
return true;
}
if (!selected)
{
return false;
}
var arrowsPos = DraggablesHelper.GetArrowPositions(pos, size);
foreach (Vector2 arrowPos in arrowsPos)
{
if (ImGui.IsMouseHoveringRect(arrowPos, arrowPos + DraggablesHelper.ArrowSize))
{
return true;
}
}
return false;
}
protected virtual void DrawDraggableArea(Vector2 origin)
{
var windowFlags = ImGuiWindowFlags.NoScrollbar
| ImGuiWindowFlags.NoTitleBar
| ImGuiWindowFlags.NoResize
| ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoSavedSettings;
// always update size
var size = MaxPos - MinPos + _contentMargin * 2;
ImGui.SetNextWindowSize(size, ImGuiCond.Always);
// needs input?
NeedsInputForDrag = CanTakeInputForDrag && CalculateNeedsInput(_lastWindowPos, size, Selected);
if (!NeedsInputForDrag)
{
windowFlags |= ImGuiWindowFlags.NoMove;
}
// set initial position
if (!_windowPositionSet)
{
ImGui.SetNextWindowPos(origin + MinPos - _contentMargin);
_windowPositionSet = true;
_positionOffset = _config.Position - MinPos + _contentMargin;
}
// update config object position
ImGui.Begin(ID + "_dragArea", windowFlags);
var windowPos = ImGui.GetWindowPos();
_lastWindowPos = windowPos;
_config.Position = windowPos + _positionOffset - origin;
// check selection
var tooltipText = "x: " + _config.Position.X.ToString() + " y: " + _config.Position.Y.ToString();
if (NeedsInputForDrag && ImGui.IsMouseHoveringRect(windowPos, windowPos + size))
{
bool cliked = ImGui.IsMouseClicked(ImGuiMouseButton.Left) || ImGui.IsMouseDown(ImGuiMouseButton.Left);
if (cliked && !Selected)
{
SelectEvent?.Invoke(this);
}
// tooltip
TooltipsHelper.Instance.ShowTooltipOnCursor(tooltipText);
}
// draw window
var drawList = ImGui.GetWindowDrawList();
var contentPos = windowPos + _contentMargin;
var contentSize = size - _contentMargin * 2;
// draw draggable indicators
drawList.AddRectFilled(contentPos, contentPos + contentSize, 0x88444444, 3);
var lineColor = Selected ? 0xEEFFFFFF : 0x66FFFFFF;
drawList.AddRect(contentPos, contentPos + contentSize, lineColor, 3, ImDrawFlags.None, 2);
drawList.AddLine(contentPos + new Vector2(contentSize.X / 2f, 0), contentPos + new Vector2(contentSize.X / 2, contentSize.Y), lineColor);
drawList.AddLine(contentPos + new Vector2(0, contentSize.Y / 2f), contentPos + new Vector2(contentSize.X, contentSize.Y / 2), lineColor);
ImGui.End();
// arrows
if (Selected)
{
if (DraggablesHelper.DrawArrows(windowPos, size, tooltipText, out var movement))
{
_minPos = null;
_maxPos = null;
_config.Position += movement;
_windowPositionSet = false;
}
}
// element name
var textSize = ImGui.CalcTextSize(_displayName);
var textColor = Selected ? 0xFFFFFFFF : 0xEEFFFFFF;
var textOutlineColor = Selected ? 0xFF000000 : 0xEE000000;
DrawHelper.DrawOutlinedText(_displayName, contentPos + contentSize / 2f - textSize / 2f, textColor, textOutlineColor, drawList);
}
#region draggable area
protected Vector2? _minPos = null;
public Vector2 MinPos
{
get
{
if (_minPos != null)
{
return (Vector2)_minPos;
}
var (positions, sizes) = ChildrenPositionsAndSizes();
if (positions.Count == 0 || sizes.Count == 0)
{
return Vector2.Zero;
}
float minX = float.MaxValue;
float minY = float.MaxValue;
var anchorConfig = _config as AnchorablePluginConfigObject;
for (int i = 0; i < positions.Count; i++)
{
var pos = GetAnchoredPosition(positions[i], sizes[i], anchorConfig?.Anchor ?? DrawAnchor.Center);
minX = Math.Min(minX, pos.X);
minY = Math.Min(minY, pos.Y);
}
_minPos = new Vector2(minX, minY);
return (Vector2)_minPos;
}
}
protected Vector2? _maxPos = null;
public Vector2 MaxPos
{
get
{
if (_maxPos != null)
{
return (Vector2)_maxPos;
}
var (positions, sizes) = ChildrenPositionsAndSizes();
if (positions.Count == 0 || sizes.Count == 0)
{
return Vector2.Zero;
}
float maxX = float.MinValue;
float maxY = float.MinValue;
var anchorConfig = _config as AnchorablePluginConfigObject;
for (int i = 0; i < positions.Count; i++)
{
var pos = GetAnchoredPosition(positions[i], sizes[i], anchorConfig?.Anchor ?? DrawAnchor.Center) + sizes[i];
maxX = Math.Max(maxX, pos.X);
maxY = Math.Max(maxY, pos.Y);
}
_maxPos = new Vector2(maxX, maxY);
return (Vector2)_maxPos;
}
}
public void FlagDraggableAreaDirty()
{
_minPos = null;
_maxPos = null;
}
protected virtual Vector2 GetAnchoredPosition(Vector2 position, Vector2 size, DrawAnchor anchor)
{
return Utils.GetAnchoredPosition(ParentPos() + position, size, anchor);
}
protected virtual (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>(), new List<Vector2>());
}
#endregion
}
public abstract class ParentAnchoredDraggableHudElement : DraggableHudElement
{
public ParentAnchoredDraggableHudElement(MovablePluginConfigObject config, string? displayName = null)
: base(config, displayName)
{
}
protected virtual bool AnchorToParent { get; }
protected virtual DrawAnchor ParentAnchor { get; }
public AnchorablePluginConfigObject? ParentConfig { get; set; }
private Vector2? _lastParentPosition = null;
private bool IsAnchored => AnchorToParent && ParentConfig != null;
public override Vector2 ParentPos()
{
if (!IsAnchored)
{
return Vector2.Zero;
}
Vector2 parentAnchoredPos = Utils.GetAnchoredPosition(ParentConfig!.Position, ParentConfig!.Size, ParentConfig!.Anchor);
return Utils.GetAnchoredPosition(parentAnchoredPos, -ParentConfig!.Size, ParentAnchor);
}
protected override void DrawDraggableArea(Vector2 origin)
{
// if the parent moved, update own draggable area
if (IsAnchored && (_lastParentPosition == null || _lastParentPosition != ParentConfig!.Position))
{
_windowPositionSet = false;
_minPos = null;
_maxPos = null;
_lastParentPosition = ParentConfig!.Position;
}
base.DrawDraggableArea(origin);
}
}
}
+321
View File
@@ -0,0 +1,321 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.StatusEffects;
using Dalamud.Bindings.ImGui;
using System.Numerics;
namespace HSUI.Interface.EnemyList
{
public enum EnemyListGrowthDirection
{
Down = 0,
Up
}
[Exportable(false)]
[Section("Enemy List", true)]
[SubSection("General", 0)]
public class EnemyListConfig : MovablePluginConfigObject
{
public new static EnemyListConfig DefaultConfig()
{
var config = new EnemyListConfig();
Vector2 screenSize = ImGui.GetMainViewport().Size;
config.Position = new Vector2(screenSize.X * 0.2f, -screenSize.Y * 0.2f);
return config;
}
[Checkbox("Preview", isMonitored = true)]
[Order(4)]
public bool Preview = false;
[Combo("Growth Direction", "Down", "Up", spacing = true)]
[Order(20)]
public EnemyListGrowthDirection GrowthDirection = EnemyListGrowthDirection.Down;
[DragInt("Vertical Padding", min = 0, max = 500)]
[Order(25)]
public int VerticalPadding = 10;
}
[Exportable(false)]
[DisableParentSettings("Position", "Anchor", "HideWhenInactive")]
[Section("Enemy List", true)]
[SubSection("Health Bar", 0)]
public class EnemyListHealthBarConfig : BarConfig
{
[NestedConfig("Name Label", 70)]
public EditableLabelConfig NameLabel = new EditableLabelConfig(new Vector2(-5, 12), "[name]", DrawAnchor.TopRight, DrawAnchor.BottomRight);
[NestedConfig("Health Label", 80)]
public EditableLabelConfig HealthLabel = new EditableLabelConfig(new Vector2(30, 0), "[health:percent]%", DrawAnchor.Left, DrawAnchor.Left);
[NestedConfig("Order Label", 90)]
public DefaultFontLabelConfig OrderLabel = new DefaultFontLabelConfig(new Vector2(5, 0), "", DrawAnchor.Left, DrawAnchor.Left);
[NestedConfig("Colors", 100)]
public EnemyListHealthBarColorsConfig Colors = new EnemyListHealthBarColorsConfig();
[NestedConfig("Change Alpha Based on Range", 110)]
public EnemyListRangeConfig RangeConfig = new EnemyListRangeConfig();
[NestedConfig("Use Smooth Transitions", 120)]
public SmoothHealthConfig SmoothHealthConfig = new SmoothHealthConfig();
[NestedConfig("Custom Mouseover Area", 130)]
public MouseoverAreaConfig MouseoverAreaConfig = new MouseoverAreaConfig();
public new static EnemyListHealthBarConfig DefaultConfig()
{
Vector2 size = new Vector2(180, 40);
var config = new EnemyListHealthBarConfig(Vector2.Zero, size, new PluginConfigColor(new(233f / 255f, 4f / 255f, 4f / 255f, 100f / 100f)));
config.Colors.ColorByHealth.Enabled = false;
config.NameLabel.FontID = FontsConfig.DefaultMediumFontKey;
config.HealthLabel.FontID = FontsConfig.DefaultMediumFontKey;
config.MouseoverAreaConfig.Enabled = false;
return config;
}
public EnemyListHealthBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
: base(position, size, fillColor, fillDirection)
{
}
}
[Disableable(false)]
[Exportable(false)]
public class EnemyListHealthBarColorsConfig : PluginConfigObject
{
[NestedConfig("Color Based On Health Value", 30, collapsingHeader = false)]
public ColorByHealthValueConfig ColorByHealth = new ColorByHealthValueConfig();
[Checkbox("Highlight When Hovering With Cursor Or Soft Targeting", spacing = true)]
[Order(40)]
public bool ShowHighlight = true;
[ColorEdit4("Highlight Color")]
[Order(41, collapseWith = nameof(ShowHighlight))]
public PluginConfigColor HighlightColor = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 5f / 100f));
[Checkbox("Missing Health Color", spacing = true)]
[Order(45)]
public bool UseMissingHealthBar = false;
[ColorEdit4("Color" + "##MissingHealth")]
[Order(46, collapseWith = nameof(UseMissingHealthBar))]
public PluginConfigColor HealthMissingColor = new PluginConfigColor(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Target Border Color", spacing = true)]
[Order(50)]
public PluginConfigColor TargetBordercolor = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
[DragInt("Target Border Thickness", min = 1, max = 10)]
[Order(51)]
public int TargetBorderThickness = 1;
[Checkbox("Show Enmity Border Colors", spacing = true)]
[Order(60)]
public bool ShowEnmityBorderColors = true;
[ColorEdit4("Enmity Leader Color")]
[Order(61, collapseWith = nameof(ShowEnmityBorderColors))]
public PluginConfigColor EnmityLeaderBorderColor = new PluginConfigColor(new Vector4(255f / 255f, 40f / 255f, 40f / 255f, 100f / 100f));
[ColorEdit4("Enmity Close To Leader Color")]
[Order(62, collapseWith = nameof(ShowEnmityBorderColors))]
public PluginConfigColor EnmitySecondBorderColor = new PluginConfigColor(new Vector4(255f / 255f, 175f / 255f, 40f / 255f, 100f / 100f));
}
[Exportable(false)]
public class EnemyListRangeConfig : PluginConfigObject
{
[DragInt("Range (yalms)", min = 1, max = 500)]
[Order(5)]
public int Range = 30;
[DragFloat("Alpha", min = 1, max = 100)]
[Order(10)]
public float Alpha = 25;
public float AlphaForDistance(int distance, float alpha = 100f)
{
if (!Enabled)
{
return 100f;
}
return distance > Range ? Alpha : alpha;
}
}
[DisableParentSettings("FrameAnchor")]
[Exportable(false)]
[Section("Enemy List", true)]
[SubSection("Enmity Icon", 0)]
public class EnemyListEnmityIconConfig : IconConfig
{
[Anchor("Health Bar Anchor")]
[Order(16)]
public DrawAnchor HealthBarAnchor = DrawAnchor.TopLeft;
public new static EnemyListEnmityIconConfig DefaultConfig() =>
new EnemyListEnmityIconConfig(new Vector2(5), new Vector2(24), DrawAnchor.Center, DrawAnchor.TopLeft);
public EnemyListEnmityIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
HealthBarAnchor = frameAnchor;
}
}
[DisableParentSettings("FrameAnchor")]
[Exportable(false)]
[Section("Enemy List", true)]
[SubSection("Sign Icon", 0)]
public class EnemyListSignIconConfig : SignIconConfig
{
[Anchor("Health Bar Anchor")]
[Order(16)]
public DrawAnchor HealthBarAnchor = DrawAnchor.TopLeft;
[Checkbox("Replace Order Label", help = "When enabled and if the enemy has a sign assigned, the sign icon will be drawn instead of the order label.")]
[Order(30)]
public bool ReplaceOrderLabel = true;
public new static EnemyListSignIconConfig DefaultConfig() =>
new EnemyListSignIconConfig(new Vector2(0), new Vector2(30), DrawAnchor.Center, DrawAnchor.Left);
public EnemyListSignIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
HealthBarAnchor = frameAnchor;
}
}
[DisableParentSettings("AnchorToUnitFrame", "UnitFrameAnchor", "HideWhenInactive", "FillDirection")]
[Exportable(false)]
[Section("Enemy List", true)]
[SubSection("Castbar", 0)]
public class EnemyListCastbarConfig : TargetCastbarConfig
{
public new static EnemyListCastbarConfig DefaultConfig()
{
var size = new Vector2(180, 10);
var castNameConfig = new LabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
castNameConfig.FontID = FontsConfig.DefaultMediumFontKey;
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
castTimeConfig.Enabled = false;
castTimeConfig.FontID = FontsConfig.DefaultMediumFontKey;
castTimeConfig.NumberFormat = 1;
var config = new EnemyListCastbarConfig(Vector2.Zero, size, castNameConfig, castTimeConfig);
config.HealthBarAnchor = DrawAnchor.Bottom;
config.Anchor = DrawAnchor.Bottom;
config.ShowIcon = false;
return config;
}
[Anchor("Health Bar Anchor")]
[Order(16)]
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
public EnemyListCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
: base(position, size, castNameConfig, castTimeConfig)
{
}
}
[Exportable(false)]
[Section("Enemy List", true)]
[SubSection("Buffs", 0)]
public class EnemyListBuffsConfig : EnemyListStatusEffectsListConfig
{
public new static EnemyListBuffsConfig DefaultConfig()
{
var durationConfig = new LabelConfig(new Vector2(0, -4), "", DrawAnchor.Bottom, DrawAnchor.Center);
var stacksConfig = new LabelConfig(new Vector2(-3, 4), "", DrawAnchor.TopRight, DrawAnchor.Center);
stacksConfig.Color = new(Vector4.UnitW);
stacksConfig.OutlineColor = new(Vector4.One);
var iconConfig = new StatusEffectIconConfig(durationConfig, stacksConfig);
iconConfig.DispellableBorderConfig.Enabled = false;
iconConfig.Size = new Vector2(24, 24);
var pos = new Vector2(5, 8);
var size = new Vector2(iconConfig.Size.X * 4 + 6, iconConfig.Size.Y);
var config = new EnemyListBuffsConfig(DrawAnchor.TopRight, pos, size, true, false, false, GrowthDirections.Right | GrowthDirections.Down, iconConfig);
config.Limit = 4;
config.ShowPermanentEffects = true;
config.IconConfig.DispellableBorderConfig.Enabled = false;
return config;
}
public EnemyListBuffsConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(anchor, position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
[Exportable(false)]
[Section("Enemy List", true)]
[SubSection("Debuffs", 0)]
public class EnemyListDebuffsConfig : EnemyListStatusEffectsListConfig
{
public new static EnemyListDebuffsConfig DefaultConfig()
{
var durationConfig = new LabelConfig(new Vector2(0, -4), "", DrawAnchor.Bottom, DrawAnchor.Center);
var stacksConfig = new LabelConfig(new Vector2(-3, 4), "", DrawAnchor.TopRight, DrawAnchor.Center);
stacksConfig.Color = new(Vector4.UnitW);
stacksConfig.OutlineColor = new(Vector4.One);
var iconConfig = new StatusEffectIconConfig(durationConfig, stacksConfig);
iconConfig.Size = new Vector2(24, 24);
var pos = new Vector2(-5, 8);
var size = new Vector2(iconConfig.Size.X * 4 + 6, iconConfig.Size.Y);
var config = new EnemyListDebuffsConfig(DrawAnchor.TopLeft, pos, size, false, true, false, GrowthDirections.Left | GrowthDirections.Down, iconConfig);
config.Limit = 4;
config.ShowPermanentEffects = true;
config.IconConfig.DispellableBorderConfig.Enabled = false;
return config;
}
public EnemyListDebuffsConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(anchor, position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
public class EnemyListStatusEffectsListConfig : StatusEffectsListConfig
{
[Anchor("Health Bar Anchor")]
[Order(4)]
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
public EnemyListStatusEffectsListConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
HealthBarAnchor = anchor;
}
}
}
+88
View File
@@ -0,0 +1,88 @@
using Dalamud.Memory;
using HSUI.Helpers;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using System;
using System.Collections.Generic;
using FFXIVClientStructs.FFXIV.Client.UI.Arrays;
using StructsFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
namespace HSUI.Interface.EnemyList
{
public unsafe class EnemyListHelper
{
private List<EnemyListData> _enemiesData = new List<EnemyListData>();
public IReadOnlyCollection<EnemyListData> EnemiesData => _enemiesData.AsReadOnly();
public int EnemyCount => _enemiesData.Count;
public void Update()
{
_enemiesData.Clear();
var enemyListNumberInstance = EnemyListNumberArray.Instance();
var enemyNumberArrayEnemies = enemyListNumberInstance->Enemies;
int enemyCount = *(int*)((byte*)enemyListNumberInstance + 0x04);
//TODO: Change it to the correct property when it lands in CS
//int enemyCount = enemyListNumberInstance->Unk1;
if(enemyCount == 0)
{
return;
}
for (int i = 0; i < enemyCount; i++)
{
int entityId = enemyNumberArrayEnemies[i].EntityId;
int? letter = GetEnemyLetter(entityId, i);
int enmityLevel = GetEnmityLevelForIndex(i);
_enemiesData.Add(new EnemyListData(entityId, letter, enmityLevel));
}
}
private int? GetEnemyLetter(int objectId, int index)
{
var enemyStringArrayMembers = EnemyListStringArray.Instance()->Members;
if (enemyStringArrayMembers.IsEmpty || enemyStringArrayMembers.Length <= index)
{
return null;
}
string name = enemyStringArrayMembers[index].EnemyName;
bool isMarked = Utils.SignIconIDForObjectID((uint)objectId) != null;
char letterSymbol = isMarked && name.Length > 1 ? name[2] : name[0];
return letterSymbol - 57457;
}
private int GetEnmityLevelForIndex(int index)
{
// gets enmity level by checking texture in enemy list addon
AtkUnitBase* enemyList = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_EnemyList", 1).Address;
if (enemyList == null || enemyList->RootNode == null) { return 0; }
int id = index == 0 ? 2 : 20000 + index; // makes no sense but it is what it is (blame SE)
AtkResNode* node = enemyList->GetNodeById((uint)id);
if (node == null || node->GetComponent() == null) { return 0; }
AtkImageNode* imageNode = (AtkImageNode*)node->GetComponent()->UldManager.SearchNodeById(13);
if (imageNode == null) { return 0; }
return Math.Min(4, imageNode->PartId + 1);
}
}
public struct EnemyListData
{
public int EntityId;
public int? LetterIndex;
public int EnmityLevel;
public EnemyListData(int entityId, int? letterIndex, int enmityLevel)
{
EntityId = entityId;
LetterIndex = letterIndex;
EnmityLevel = enmityLevel;
}
}
}
+428
View File
@@ -0,0 +1,428 @@
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<SmoothHPHelper> _smoothHPHelpers = new List<SmoothHPHelper>();
private const int MaxEnemyCount = 8;
private List<float> _previewValues = new List<float>(MaxEnemyCount);
private bool _wasHovering = false;
private LabelHud _nameLabelHud;
private LabelHud _healthLabelHud;
private LabelHud _orderLabelHud;
private List<EnemyListCastbarHud> _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<EnemyListCastbarHud>();
_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<Vector2>, List<Vector2>) 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<Vector2>() { pos + size / 2f }, new List<Vector2>() { 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<EnemyListHealthBarConfig>(),
ConfigurationManager.Instance.GetConfigObject<EnemyListEnmityIconConfig>(),
ConfigurationManager.Instance.GetConfigObject<EnemyListSignIconConfig>(),
ConfigurationManager.Instance.GetConfigObject<EnemyListCastbarConfig>(),
ConfigurationManager.Instance.GetConfigObject<EnemyListBuffsConfig>(),
ConfigurationManager.Instance.GetConfigObject<EnemyListDebuffsConfig>()
);
}
}
#endregion
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,159 @@
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Memory.Exceptions;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using Dalamud.Bindings.ImGui;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[Disableable(false)]
[Section("Customization")]
[SubSection("Bar Textures", 0)]
public class BarTexturesConfig : PluginConfigObject
{
public new static BarTexturesConfig DefaultConfig() { return new BarTexturesConfig(); }
public string BarTexturesPath = "C:\\";
[JsonIgnore] public string ValidatedBarTexturesPath => ValidatePath(BarTexturesPath);
[JsonIgnore] private int _inputBarTexture = 0;
[JsonIgnore] private int _drawModeIndex = 0;
[JsonIgnore] private Vector4 _color = new Vector4(229 / 255f, 57 / 255f, 57 / 255f, 1);
[JsonIgnore] private PluginConfigColor _pluginConfigColor = PluginConfigColor.FromHex(0xFFE53939);
[JsonIgnore] private FileDialogManager _fileDialogManager = new FileDialogManager();
[JsonIgnore] private bool _applying = false;
private string ValidatePath(string path)
{
if (path.EndsWith("\\") || path.EndsWith("/"))
{
return path;
}
return path + "\\";
}
private void SelectFolder()
{
Action<bool, string> callback = (finished, path) =>
{
if (finished && path.Length > 0)
{
BarTexturesPath = path;
BarTexturesManager.Instance?.ReloadTextures();
}
};
_fileDialogManager.OpenFolderDialog("Select Bar Textures Folder", callback);
}
[ManualDraw]
public bool Draw(ref bool changed)
{
if (BarTexturesManager.Instance == null) { return false; }
string[] textureNames = BarTexturesManager.Instance.BarTextureNames.ToArray();
string[] drawModes = new string[] { "Stretch", "Repeat Horizontal", "Repeat Vertical", "Repeat" };
if (ImGui.BeginChild("Bar Textures", new Vector2(800, 400), false, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
{
ImGuiHelper.NewLineAndTab();
ImGui.Text("Custom Bar Textures path");
ImGuiHelper.Tab();
if (ImGui.InputText("", ref BarTexturesPath, 200, ImGuiInputTextFlags.EnterReturnsTrue))
{
changed = true;
BarTexturesManager.Instance?.ReloadTextures();
}
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Folder.ToIconString(), new Vector2(0, 0)))
{
SelectFolder();
}
ImGui.PopFont();
ImGuiHelper.NewLineAndTab();
ImGui.Text("Preview");
ImGuiHelper.Tab();
ImGui.Combo("Bar Texture ##bar texture", ref _inputBarTexture, textureNames);
ImGuiHelper.Tab();
ImGui.Combo("Draw Mode", ref _drawModeIndex, drawModes);
ImGuiHelper.Tab();
if (ImGui.ColorEdit4("Color", ref _color))
{
_pluginConfigColor = new PluginConfigColor(_color);
}
if (textureNames.Length > _inputBarTexture)
{
// draw preview
ImGui.NewLine();
ImGuiHelper.NewLineAndTab();
Vector2 pos = ImGui.GetWindowPos() + ImGui.GetCursorPos();
Vector2 size = new Vector2(512, 64);
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
DrawHelper.DrawBarTexture(
pos,
size,
_pluginConfigColor,
textureNames[_inputBarTexture],
(BarTextureDrawMode)_drawModeIndex,
drawList
);
ImGuiHelper.DrawSpacing(3);
ImGuiHelper.NewLineAndTab();
if (ImGui.Button("Apply to all bars", new Vector2(200, 30)))
{
_applying = true;
}
}
}
ImGui.EndChild();
_fileDialogManager.Draw();
if (_applying)
{
string[] lines = new string[] { "This will replace the Bar Texture", "and Draw Mode for ALL bars!", "THIS CAN'T BE UNDONE!", "Are you sure?" };
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Apply to ALL bars?", lines);
if (didConfirm)
{
List<BarConfig> barConfigs = ConfigurationManager.Instance.GetObjects<BarConfig>();
foreach (BarConfig barConfig in barConfigs)
{
barConfig.BarTextureName = textureNames[_inputBarTexture];
barConfig.BarTextureDrawMode = (BarTextureDrawMode)_drawModeIndex;
}
changed = true;
}
if (didConfirm || didClose)
{
_applying = false;
}
}
return false;
}
}
}
+258
View File
@@ -0,0 +1,258 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Interface.Bars;
using System;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[Section("Castbars")]
[SubSection("Player", 0)]
public class PlayerCastbarConfig : UnitFrameCastbarConfig
{
[Checkbox("Use Job Color", spacing = true)]
[Order(19)]
public bool UseJobColor = false;
[Checkbox("Slide Cast", spacing = true)]
[Order(60)]
public bool ShowSlideCast = true;
[DragInt("Time (milliseconds)", min = 0, max = 10000)]
[Order(61, collapseWith = nameof(ShowSlideCast))]
public int SlideCastTime = 500;
[ColorEdit4("Color ##SlidecastColor")]
[Order(62, collapseWith = nameof(ShowSlideCast))]
public PluginConfigColor SlideCastColor = new PluginConfigColor(new(190f / 255f, 28f / 255f, 57f / 255f, 100f / 100f));
public PlayerCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
: base(position, size, castNameConfig, castTimeConfig)
{
}
public new static PlayerCastbarConfig DefaultConfig()
{
var size = new Vector2(254, 24);
var pos = new Vector2(0, HUDConstants.PlayerCastbarY);
var castNameConfig = new LabelConfig(new Vector2(5, 0), "", DrawAnchor.Left, DrawAnchor.Left);
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
castTimeConfig.NumberFormat = 1;
return new PlayerCastbarConfig(pos, size, castNameConfig, castTimeConfig);
}
}
[Section("Castbars")]
[SubSection("Target", 0)]
public class TargetCastbarConfig : UnitFrameCastbarConfig
{
[Checkbox("Interruptable Color", spacing = true)]
[Order(50)]
public bool ShowInterruptableColor = true;
[ColorEdit4("Interruptable")]
[Order(51, collapseWith = nameof(ShowInterruptableColor))]
public PluginConfigColor InterruptableColor = new PluginConfigColor(new(255f / 255f, 87f / 255f, 113f / 255f, 100f / 100f));
[Checkbox("Damage Type Colors", spacing = true)]
[Order(60)]
public bool UseColorForDamageTypes = true;
[ColorEdit4("Physical")]
[Order(61, collapseWith = nameof(UseColorForDamageTypes))]
public PluginConfigColor PhysicalDamageColor = new PluginConfigColor(new(190f / 255f, 28f / 255f, 57f / 255f, 100f / 100f));
[ColorEdit4("Magical")]
[Order(62, collapseWith = nameof(UseColorForDamageTypes))]
public PluginConfigColor MagicalDamageColor = new PluginConfigColor(new(0f / 255f, 72f / 255f, 179f / 255f, 100f / 100f));
[ColorEdit4("Darkness")]
[Order(63, collapseWith = nameof(UseColorForDamageTypes))]
public PluginConfigColor DarknessDamageColor = new PluginConfigColor(new(188f / 255f, 19f / 255f, 254f / 255f, 100f / 100f));
public TargetCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
: base(position, size, castNameConfig, castTimeConfig)
{
}
public new static TargetCastbarConfig DefaultConfig()
{
var size = new Vector2(254, 24);
var pos = new Vector2(0, HUDConstants.BaseHUDOffsetY / 2f - size.Y / 2);
var castNameConfig = new LabelConfig(new Vector2(5, 0), "", DrawAnchor.Left, DrawAnchor.Left);
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
castTimeConfig.NumberFormat = 1;
return new TargetCastbarConfig(pos, size, castNameConfig, castTimeConfig);
}
}
[Section("Castbars")]
[SubSection("Target of Target", 0)]
public class TargetOfTargetCastbarConfig : TargetCastbarConfig
{
public TargetOfTargetCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
: base(position, size, castNameConfig, castTimeConfig)
{
}
public new static TargetOfTargetCastbarConfig DefaultConfig()
{
var size = new Vector2(120, 24);
var pos = new Vector2(0, -1);
var castNameConfig = new LabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
castTimeConfig.Enabled = false;
castTimeConfig.NumberFormat = 1;
var config = new TargetOfTargetCastbarConfig(pos, size, castNameConfig, castTimeConfig);
config.Anchor = DrawAnchor.Top;
config.AnchorToUnitFrame = true;
config.ShowIcon = false;
return config;
}
}
[Section("Castbars")]
[SubSection("Focus Target", 0)]
public class FocusTargetCastbarConfig : TargetCastbarConfig
{
public FocusTargetCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
: base(position, size, castNameConfig, castTimeConfig)
{
}
public new static FocusTargetCastbarConfig DefaultConfig()
{
var size = new Vector2(120, 24);
var pos = new Vector2(0, -1);
var castNameConfig = new LabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
castTimeConfig.Enabled = false;
castTimeConfig.NumberFormat = 1;
var config = new FocusTargetCastbarConfig(pos, size, castNameConfig, castTimeConfig);
config.Anchor = DrawAnchor.Top;
config.AnchorToUnitFrame = true;
config.ShowIcon = false;
return config;
}
}
public abstract class UnitFrameCastbarConfig : CastbarConfig
{
[Checkbox("Anchor to Unit Frame")]
[Order(16)]
public bool AnchorToUnitFrame = false;
[Anchor("Unit Frame Anchor")]
[Order(17, collapseWith = nameof(AnchorToUnitFrame))]
public DrawAnchor UnitFrameAnchor = DrawAnchor.Bottom;
public UnitFrameCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
: base(position, size, castNameConfig, castTimeConfig)
{
}
}
[DisableParentSettings("HideWhenInactive")]
public abstract class CastbarConfig : BarConfig
{
[Checkbox("Preview")]
[Order(3)]
public bool Preview = false;
[Checkbox("Show Ability Icon")]
[Order(4)]
public bool ShowIcon = true;
[Checkbox("Reverse Fill Background Color")]
[Order(5)]
public bool UseReverseFill = false;
[Checkbox("Show Current Cast Time + Max Cast Time")]
[Order(6)]
public bool ShowMaxCastTime = false;
[Checkbox("Truncate Cast Name", help = "This will automatically truncate the cast name if it's too long and won't fit inside the bar.")]
[Order(7)]
public bool TruncateCastName = false;
[Checkbox("Separate Icon", spacing = true)]
[Order(100)]
public bool SeparateIcon = false;
[DragInt2("Custom Icon Position", min = -500, max = 500)]
[Order(101, collapseWith = nameof(SeparateIcon))]
public Vector2 CustomIconPosition = Vector2.Zero;
[DragInt2("Custom Icon Size", min = 1, max = 500)]
[Order(101, collapseWith = nameof(SeparateIcon))]
public Vector2 CustomIconSize = new Vector2(40);
[NestedConfig("Cast Name", 500)]
public LabelConfig CastNameLabel;
[NestedConfig("Cast Time", 505)]
public NumericLabelConfig CastTimeLabel;
[ColorEdit4("Color" + "##ReverseFill")]
[Order(515, collapseWith = nameof(UseReverseFill))]
public PluginConfigColor ReverseFillColor = new(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
public CastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
: base(position, size, new PluginConfigColor(new(0f / 255f, 162f / 255f, 252f / 255f, 100f / 100f)), BarDirection.Right)
{
CastNameLabel = castNameConfig;
CastTimeLabel = castTimeConfig;
Strata = StrataLevel.MID;
}
}
public class CastbarConfigConverter : PluginConfigObjectConverter
{
public CastbarConfigConverter()
{
SameClassFieldConverter<LabelConfig> name = new SameClassFieldConverter<LabelConfig>(
"CastNameLabel",
new LabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center)
);
NewClassFieldConverter<LabelConfig, NumericLabelConfig> time = new NewClassFieldConverter<LabelConfig, NumericLabelConfig>(
"CastTimeLabel",
new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right),
(oldValue) =>
{
NumericLabelConfig label = new NumericLabelConfig(oldValue.Position, "", oldValue.FrameAnchor, oldValue.TextAnchor);
label.Enabled = oldValue.Enabled;
label.FontID = oldValue.FontID;
label.NumberFormat = 1;
label.Color = oldValue.Color;
label.OutlineColor = oldValue.OutlineColor;
label.ShadowConfig = oldValue.ShadowConfig;
label.UseJobColor = oldValue.UseJobColor;
return label;
});
FieldConvertersMap.Add("CastNameConfig", name);
FieldConvertersMap.Add("CastTimeConfig", time);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(CastbarConfig);
}
}
}
+509
View File
@@ -0,0 +1,509 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Utility;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.EnemyList;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using System;
using System.Collections.Generic;
using System.Numerics;
using LuminaAction = Lumina.Excel.Sheets.Action;
using StructsBattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;
namespace HSUI.Interface.GeneralElements
{
public class CastbarHud : ParentAnchoredDraggableHudElement, IHudElementWithActor, IHudElementWithAnchorableParent, IHudElementWithPreview
{
private CastbarConfig Config => (CastbarConfig)_config;
private readonly LabelHud _castNameLabel;
private readonly LabelHud _castTimeLabel;
protected LastUsedCast? LastUsedCast;
public IGameObject? Actor { get; set; }
protected override bool AnchorToParent => Config is UnitFrameCastbarConfig { AnchorToUnitFrame: true };
protected override DrawAnchor ParentAnchor => Config is UnitFrameCastbarConfig config ? config.UnitFrameAnchor : DrawAnchor.Center;
public CastbarHud(CastbarConfig config, string? displayName = null) : base(config, displayName)
{
_castNameLabel = new LabelHud(config.CastNameLabel);
_castTimeLabel = new LabelHud(config.CastTimeLabel);
}
public void StopPreview()
{
Config.Preview = false;
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes() => (new List<Vector2> { Config.Position }, new List<Vector2> { Config.Size });
public override void DrawChildren(Vector2 origin)
{
if (!Config.Enabled)
{
return;
}
if (!Config.Preview &&
(Actor == null || Actor is not ICharacter || Actor.ObjectKind != ObjectKind.Player && Actor.ObjectKind != ObjectKind.BattleNpc))
{
return;
}
UpdateCurrentCast(out float currentCastTime, out float totalCastTime);
if (totalCastTime == 0 || currentCastTime >= totalCastTime)
{
return;
}
if (!ShouldShow() && !Config.Preview)
{
return;
}
Vector2 size = GetSize();
IDalamudTextureWrap? iconTexture = LastUsedCast?.GetIconTexture();
bool validIcon = Config.Preview ? true : iconTexture is not null;
Vector2 iconSize = Config.ShowIcon && validIcon && !Config.SeparateIcon ? new Vector2(size.Y, size.Y) : Vector2.Zero;
PluginConfigColor fillColor = GetColor();
Rect background = new(Config.Position, size, Config.BackgroundColor);
Rect progress = BarUtilities.GetFillRect(Config.Position, size, Config.FillDirection, fillColor, currentCastTime, totalCastTime);
BarHud bar = new(Config, Actor);/**/
bar.SetBackground(background);
if (Config.UseReverseFill)
{
Vector2 reverseFillSize = size - BarUtilities.GetFillDirectionOffset(progress.Size, Config.FillDirection);
Vector2 reverseFillPos = Config.FillDirection.IsInverted()
? Config.Position
: Config.Position + BarUtilities.GetFillDirectionOffset(progress.Size, Config.FillDirection);
PluginConfigColor reverseFillColor = Config.ReverseFillColor;
bar.AddForegrounds(new Rect(reverseFillPos, reverseFillSize, reverseFillColor));
}
AddExtras(bar, totalCastTime, iconTexture);
bar.AddForegrounds(progress);
Vector2 pos = origin + ParentPos();
AddDrawActions(bar.GetDrawActions(pos, Config.StrataLevel));
// icon
Vector2 startPos = Config.Position + Utils.GetAnchoredPosition(pos, size, Config.Anchor);
if (Config.ShowIcon && validIcon)
{
Vector2 finalIconPos = Config.SeparateIcon ? startPos + Config.CustomIconPosition : startPos;
Vector2 finalIconSize = Config.SeparateIcon ? Config.CustomIconSize : iconSize;
AddDrawAction(Config.StrataLevel, () =>
{
DrawHelper.DrawInWindow(ID + "_icon", finalIconPos, finalIconSize, false, (drawList) =>
{
ImGui.SetCursorPos(finalIconPos);
IDalamudTextureWrap? texture = Config.Preview ? TexturesHelper.GetTexture<LuminaAction>(3577) : iconTexture;
if (texture != null)
{
ImGui.Image(texture.Handle, finalIconSize);
}
if (Config.DrawBorder)
{
drawList.AddRect(finalIconPos, finalIconPos + finalIconSize, Config.BorderColor.Base, 0, ImDrawFlags.None, Config.BorderThickness);
}
});
});
}
// cast time
bool isTimeLeftAnchored = Config.CastTimeLabel.TextAnchor is DrawAnchor.Left or DrawAnchor.TopLeft or DrawAnchor.BottomLeft;
Vector2 timePos = Config.ShowIcon && isTimeLeftAnchored ? startPos + new Vector2(iconSize.X, 0) : startPos;
float value = Config.Preview ? 0.5f : totalCastTime - currentCastTime;
if (Config.ShowMaxCastTime)
{
string format = Config.CastTimeLabel.NumberFormat.ToString();
Config.CastTimeLabel.SetText(
value.ToString("N" + format, ConfigurationManager.Instance.ActiveCultreInfo) +
" / " +
totalCastTime.ToString("N" + format, ConfigurationManager.Instance.ActiveCultreInfo)
);
}
else
{
Config.CastTimeLabel.SetValue(value);
}
AddDrawAction(Config.CastTimeLabel.StrataLevel, () =>
{
_castTimeLabel.Draw(timePos, size, Actor);
});
// cast name
bool isNameLeftAnchored = Config.CastNameLabel.TextAnchor is DrawAnchor.Left or DrawAnchor.TopLeft or DrawAnchor.BottomLeft;
Vector2 namePos = Config.ShowIcon && isNameLeftAnchored ? startPos + new Vector2(iconSize.X, 0) : startPos;
string original = CustomCastName() ?? (LastUsedCast?.ActionText ?? "");
string castName = EncryptedStringsHelper.GetString(original).CheckForUpperCase() ?? original;
if (Config.TruncateCastName)
{
castName = TruncatedCastName(castName) ?? castName;
}
Config.CastNameLabel.SetText(Config.Preview ? "Cast Name" : castName);
AddDrawAction(Config.CastNameLabel.StrataLevel, () =>
{
_castNameLabel.Draw(namePos, size, Actor);
});
}
private unsafe void UpdateCurrentCast(out float currentCastTime, out float totalCastTime)
{
if (Config.Preview || Actor is not IBattleChara battleChara)
{
currentCastTime = Config.Preview ? 0.5f : 0f;
totalCastTime = 1f;
return;
}
float current = 0;
float total = 0;
try
{
current = battleChara.CurrentCastTime;
StructsBattleChara* chara = (StructsBattleChara*)battleChara.Address;
CastInfo* castInfo = chara->GetCastInfo();
if (castInfo != null)
{
total = castInfo->TotalCastTime;
}
}
catch
{
currentCastTime = 0;
totalCastTime = 0;
return;
}
if (!Utils.IsActorCasting(battleChara) && current <= 0)
{
currentCastTime = 0;
totalCastTime = 0;
return;
}
currentCastTime = current;
totalCastTime = total;
uint currentCastId = battleChara.CastActionId;
ActionType currentCastType = (ActionType)battleChara.CastActionType;
if (LastUsedCast == null || LastUsedCast.CastId != currentCastId || LastUsedCast.ActionType != currentCastType)
{
LastUsedCast = new LastUsedCast(currentCastId, currentCastType, battleChara.IsCastInterruptible);
}
}
private string? TruncatedCastName(string text)
{
if (text.Length <= 5)
{
return null;
}
LabelConfig castNamelabel = Config.CastNameLabel;
LabelConfig castTimeLabel = Config.CastTimeLabel;
Vector2 size;
using (FontsManager.Instance.PushFont(castNamelabel.FontID))
{
size = ImGui.CalcTextSize(text) * castNamelabel.GetFontScale();
}
float maxWidth = Config.Size.X;
if (!Config.SeparateIcon)
{
maxWidth -= Config.Size.Y;
}
if (Config.CastTimeLabel.Enabled)
{
using (FontsManager.Instance.PushFont(Config.CastTimeLabel.FontID))
{
maxWidth -= (ImGui.CalcTextSize("XX.X") * castTimeLabel.GetFontScale()).X;
}
}
if (size.X > maxWidth)
{
return TruncatedCastName(text.Substring(0, text.Length - 5) + "...");
}
return text;
}
public virtual void AddExtras(BarHud bar, float totalCastTime, IDalamudTextureWrap? iconTexture)
{
// override
}
public virtual string? CustomCastName()
{
// override
return null;
}
public virtual PluginConfigColor GetColor() => Config.FillColor;
public virtual Vector2 GetSize() => Config.Size;
public virtual bool ShouldShow() => true;
}
public class PlayerCastbarHud : CastbarHud
{
private PlayerCastbarConfig Config => (PlayerCastbarConfig)_config;
public PlayerCastbarHud(PlayerCastbarConfig config, string displayName) : base(config, displayName)
{
}
public override unsafe string? CustomCastName()
{
AddonCastBar* castBar = (AddonCastBar*)Plugin.GameGui.GetAddonByName("_CastBar", 1).Address;
if (castBar == null) { return null; }
AtkTextNode* node = castBar->GetTextNodeById(4);
if (node == null) { return null; }
return node->GetText().ExtractText();
}
public override void AddExtras(BarHud bar, float totalCastTime, IDalamudTextureWrap? iconTexture)
{
if (!Config.ShowSlideCast || Config.SlideCastTime <= 0 || Config.Preview)
{
return;
}
Rect slideCast;
if (Config.FillDirection.IsHorizontal())
{
float slideCastWidth = Math.Min(Config.Size.X, Config.SlideCastTime / 1000f * Config.Size.X / totalCastTime);
Vector2 size = new(slideCastWidth, Config.Size.Y);
slideCast = new(Config.Position + Config.Size - size, size, Config.SlideCastColor);
if (Config.FillDirection is BarDirection.Left)
{
bool validIcon = iconTexture is not null;
Vector2 iconSize = Config.ShowIcon && validIcon ? new Vector2(Config.Size.Y, Config.Size.Y) : Vector2.Zero;
slideCast = Config.ShowIcon ? new Rect(Config.Position, size + new Vector2(iconSize.X, 0), Config.SlideCastColor) : new Rect(Config.Position, size, Config.SlideCastColor);
}
}
else
{
float slideCastHeight = Math.Min(Config.Size.Y, Config.SlideCastTime / 1000f * Config.Size.Y / totalCastTime);
Vector2 size = new(Config.Size.X, slideCastHeight);
slideCast = new(Config.Position + Config.Size - size, size, Config.SlideCastColor);
if (Config.FillDirection is BarDirection.Up)
{
slideCast = new(Config.Position, size, Config.SlideCastColor);
}
}
bar.AddForegrounds(slideCast);
}
public override PluginConfigColor GetColor()
{
if (!Config.UseJobColor || Actor is not ICharacter)
{
return Config.FillColor;
}
ICharacter? chara = (ICharacter)Actor;
PluginConfigColor? color = GlobalColors.Instance.ColorForJobId(chara.ClassJob.RowId);
return color ?? Config.FillColor;
}
}
public class TargetCastbarHud : CastbarHud
{
private TargetCastbarConfig Config => (TargetCastbarConfig)_config;
public TargetCastbarHud(TargetCastbarConfig config, string? displayName = null) : base(config, displayName)
{
}
public override PluginConfigColor GetColor()
{
if (Config.ShowInterruptableColor && LastUsedCast?.Interruptible == true)
{
return Config.InterruptableColor;
}
if (!Config.UseColorForDamageTypes)
{
return Config.FillColor;
}
if (LastUsedCast != null)
{
switch (LastUsedCast.DamageType)
{
case DamageType.Physical:
case DamageType.Blunt:
case DamageType.Slashing:
case DamageType.Piercing:
return Config.PhysicalDamageColor;
case DamageType.Magic:
return Config.MagicalDamageColor;
case DamageType.Darkness:
return Config.DarknessDamageColor;
}
}
return Config.FillColor;
}
public override unsafe bool ShouldShow()
{
bool? targetCasting = Utils.IsTargetCasting();
if (targetCasting.HasValue)
{
return targetCasting.Value;
}
return true;
}
}
public class FocusTargetCastbarHud : TargetCastbarHud
{
private TargetCastbarConfig Config => (TargetCastbarConfig)_config;
public FocusTargetCastbarHud(TargetCastbarConfig config, string? displayName = null) : base(config, displayName)
{
}
public override unsafe bool ShouldShow()
{
bool? focusTargetCasting = Utils.IsFocusTargetCasting();
if (focusTargetCasting.HasValue)
{
return focusTargetCasting.Value;
}
return true;
}
}
public class TargetOfTargetCastbarHud : TargetCastbarHud
{
private TargetCastbarConfig Config => (TargetCastbarConfig)_config;
public TargetOfTargetCastbarHud(TargetCastbarConfig config, string? displayName = null) : base(config, displayName)
{
}
public override unsafe bool ShouldShow()
{
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
if (Actor == target)
{
bool? targetCasting = Utils.IsTargetCasting();
if (targetCasting.HasValue)
{
return targetCasting.Value;
}
}
IGameObject? focusTarget = Plugin.TargetManager.FocusTarget;
if (Actor == focusTarget)
{
bool? focusTargetCasting = Utils.IsFocusTargetCasting();
if (focusTargetCasting.HasValue)
{
return focusTargetCasting.Value;
}
}
return true;
}
}
public class EnemyListCastbarHud : TargetCastbarHud
{
private EnemyListCastbarConfig Config => (EnemyListCastbarConfig)_config;
public int EnemyListIndex = 0;
public EnemyListCastbarHud(EnemyListCastbarConfig config, string? displayName = null) : base(config, displayName)
{
}
public override unsafe bool ShouldShow()
{
bool? casting = Utils.IsEnemyInListCasting(EnemyListIndex);
if (casting.HasValue)
{
return casting.Value;
}
return true;
}
}
public class NameplateCastbarHud : TargetOfTargetCastbarHud
{
private NameplateCastbarConfig Config => (NameplateCastbarConfig)_config;
private Vector2 _customSize = new Vector2(0);
public Vector2 ParentSize { get; set; } = new Vector2(0);
public NameplateCastbarHud(NameplateCastbarConfig config, string? displayName = null) : base(config, displayName)
{
_customSize = Config.Size;
}
public override void DrawChildren(Vector2 origin)
{
// calculate size
float x = Config.MatchWidth ? ParentSize.X : Config.Size.X;
float y = Config.MatchHeight ? ParentSize.Y : Config.Size.Y;
_customSize = new Vector2(x, y);
// draw
base.DrawChildren(origin);
}
public override Vector2 GetSize() => _customSize;
}
}
@@ -0,0 +1,57 @@
using Dalamud.Interface;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Interface.Bars;
using Dalamud.Bindings.ImGui;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[Section("Other Elements")]
[SubSection("Experience Bar", 0)]
public class ExperienceBarConfig : BarConfig
{
[Checkbox("Hide When Downsynced")]
[Order(44, collapseWith = nameof(HideWhenInactive))]
public bool HideWhenDownsynced = false;
[Checkbox("Use Job Color")]
[Order(45)]
public bool UseJobColor = false;
[Checkbox("Show Rested Exp")]
[Order(50)]
public bool ShowRestedExp = true;
[ColorEdit4("Rested Exp Color")]
[Order(55, collapseWith = nameof(ShowRestedExp))]
public PluginConfigColor RestedExpColor = new PluginConfigColor(new Vector4(110f / 255f, 197f / 255f, 207f / 255f, 50f / 100f));
[NestedConfig("Left Text", 60)]
public EditableLabelConfig LeftLabel;
[NestedConfig("Right Text", 61)]
public EditableLabelConfig RightLabel;
[NestedConfig("Sanctuary Icon", 62)]
public IconLabelConfig SanctuaryLabel = new IconLabelConfig(new Vector2(5, 0), FontAwesomeIcon.Moon, DrawAnchor.Right, DrawAnchor.Left);
[NestedConfig("Visibility", 70)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public ExperienceBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor)
{
LeftLabel = new EditableLabelConfig(new Vector2(5, 0), "[job] Lv[level] EXP [exp:current-short]/[exp:required-short]", DrawAnchor.BottomLeft, DrawAnchor.TopLeft);
RightLabel = new EditableLabelConfig(new Vector2(-5, 0), "([exp:percent]%)", DrawAnchor.BottomRight, DrawAnchor.TopRight);
}
public new static ExperienceBarConfig DefaultConfig()
{
return new ExperienceBarConfig(
new Vector2(0, -ImGui.GetMainViewport().Size.Y * 0.45f),
new Vector2(860, 10),
new PluginConfigColor(new Vector4(211f / 255f, 166f / 255f, 79f / 255f, 100f / 100f)));
}
}
}
@@ -0,0 +1,84 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface;
using HSUI.Config;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using FFXIVClientStructs.FFXIV.Client.UI;
using System.Collections.Generic;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace HSUI.Interface.GeneralElements
{
public class ExperienceBarHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
{
private ExperienceBarConfig Config => (ExperienceBarConfig)_config;
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
public IGameObject? Actor { get; set; } = null;
private ExperienceHelper _helper = new ExperienceHelper();
private IconLabelHud _sanctuaryLabel;
public ExperienceBarHud(ExperienceBarConfig config, string displayName) : base(config, displayName)
{
Config.SanctuaryLabel.IconId = FontAwesomeIcon.Moon;
_sanctuaryLabel = new IconLabelHud(Config.SanctuaryLabel);
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
}
public override void DrawChildren(Vector2 origin)
{
if (!Config.Enabled ||
Actor is null ||
Config.HideWhenInactive && (Plugin.ObjectTable.LocalPlayer?.Level ?? 0) >= 100 ||
(Config.HideWhenInactive && Config.HideWhenDownsynced && _helper.IsMaxLevel()))
{
return;
}
uint current = ExperienceHelper.Instance.CurrentExp;
uint required = ExperienceHelper.Instance.RequiredExp;
uint rested = Config.ShowRestedExp ? ExperienceHelper.Instance.RestedExp : 0;
// Exp progress bar
PluginConfigColor expFillColor = Config.UseJobColor ? ColorUtils.ColorForActor(Actor) : Config.FillColor;
Rect expBar = BarUtilities.GetFillRect(Config.Position, Config.Size, Config.FillDirection, expFillColor, current, required);
// Rested exp bar
var restedPos = Config.FillDirection.IsInverted() ? Config.Position : Config.Position + BarUtilities.GetFillDirectionOffset(expBar.Size, Config.FillDirection);
var restedSize = Config.Size - BarUtilities.GetFillDirectionOffset(expBar.Size, Config.FillDirection);
Rect restedBar = BarUtilities.GetFillRect(restedPos, restedSize, Config.FillDirection, Config.RestedExpColor, rested, required, 0f);
BarHud bar = new BarHud(Config, Actor);
bar.AddForegrounds(expBar, restedBar);
bar.AddLabels(Config.LeftLabel, Config.RightLabel);
AddDrawActions(bar.GetDrawActions(origin, Config.StrataLevel));
// sanctuary icon
if (IsInSanctuary())
{
AddDrawAction(Config.SanctuaryLabel.StrataLevel, () =>
{
var pos = Utils.GetAnchoredPosition(origin, Config.Size, Config.Anchor);
_sanctuaryLabel.Draw(pos + Config.Position, Config.Size, Actor);
});
}
}
private unsafe bool IsInSanctuary()
{
AddonExp* addon = ExperienceHelper.Instance.GetExpAddon();
if (addon == null) { return false; }
AtkImageNode* sanctuaryNode = addon->GetImageNodeById(3);
if (sanctuaryNode == null) { return false; }
return sanctuaryNode->IsVisible();
}
}
}
+401
View File
@@ -0,0 +1,401 @@
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using Dalamud.Interface.GameFonts;
using Dalamud.Logging;
using HSUI.Enums;
using HSUI.Interface.Bars;
namespace HSUI.Interface.GeneralElements
{
public struct FontData
{
public string Name;
public int Size;
public FontData(string name, int size)
{
Name = name;
Size = size;
}
}
[Disableable(false)]
[Section("Customization")]
[SubSection("Fonts", 0)]
public class FontsConfig : PluginConfigObject
{
public new static FontsConfig DefaultConfig() { return new FontsConfig(); }
public string FontsPath = "C:\\";
[JsonIgnore] public string ValidatedFontsPath => ValidatePath(FontsPath);
public SortedList<string, FontData> Fonts = new SortedList<string, FontData>();
public bool SupportChineseCharacters = false;
public bool SupportKoreanCharacters = false;
public bool SupportCyrillicCharacters = false;
[JsonIgnore] public readonly Dictionary<string, string> GameFontMap = new Dictionary<string, string>()
{
{"Axis", "axis-ffxiv"},
{"Jupiter", "jupiter-ffxiv"},
{"JupiterNumeric", "jupiter-numeric-ffxiv"},
{"MiedingerMid", "meidinger-ffxiv"},
{"Meidinger", "meidinger-numberic-ffxiv"},
{"TrumpGothic", "trumpgothic-ffxiv"},
};
[JsonIgnore] public static readonly List<string> DefaultFontsKeys = new List<string>() { "Expressway_18", "Expressway_14", "Expressway_12" };
[JsonIgnore] public static string DefaultBigFontKey => DefaultFontsKeys[0];
[JsonIgnore] public static string DefaultMediumFontKey => DefaultFontsKeys[1];
[JsonIgnore] public static string DefaultSmallFontKey => DefaultFontsKeys[2];
[JsonIgnore] private int _inputFont = 0;
[JsonIgnore] private int _inputSize = 23;
[JsonIgnore] private string[] _fonts = null!;
[JsonIgnore] private string[] _sizes = null!;
[JsonIgnore] private int _removingIndex = -1;
[JsonIgnore] private int _applyingIndex = -1!;
[JsonIgnore] private FileDialogManager _fileDialogManager = new FileDialogManager();
public FontsConfig()
{
ReloadFonts();
// default fonts
foreach (string key in DefaultFontsKeys)
{
if (!Fonts.ContainsKey(key))
{
string[] str = key.Split("_", StringSplitOptions.RemoveEmptyEntries);
var defaultFont = new FontData(str[0], int.Parse(str[1]));
Fonts.Add(key, defaultFont);
}
}
// sizes
_sizes = new string[100];
for (int i = 0; i < _sizes.Length; i++)
{
_sizes[i] = (i + 1).ToString();
}
}
private bool IsDefaultFont(string key)
{
return DefaultFontsKeys.Contains(key);
}
private string ValidatePath(string path)
{
if (path.EndsWith("\\") || path.EndsWith("/"))
{
return path;
}
return path + "\\";
}
private string[] FontsFromPath(string path)
{
string[] fonts;
try
{
fonts = Directory.GetFiles(path, "*.ttf");
}
catch
{
fonts = new string[0];
}
for (int i = 0; i < fonts.Length; i++)
{
fonts[i] = fonts[i]
.Replace(path, "")
.Replace(".ttf", "")
.Replace(".TTF", "");
}
return fonts;
}
private string[] FontsFromGame()
{
string[] gameFontArray = Enum.GetNames(typeof(GameFontFamily)).Skip(1).ToArray();
string[] fonts = new string[gameFontArray.Length];
for (int i = 0; i < gameFontArray.Length; i++)
{
fonts[i] = GameFontMap[gameFontArray[i]];
}
return fonts;
}
private void ReloadFonts()
{
string defaultFontsPath = ValidatePath(FontsManager.Instance.DefaultFontsPath);
string[] defaultFonts = FontsFromPath(defaultFontsPath);
string[] gameFonts = FontsFromGame();
string[] userFonts = FontsFromPath(ValidatedFontsPath);
_fonts = new string[defaultFonts.Length + gameFonts.Length + userFonts.Length];
defaultFonts.CopyTo(_fonts, 0);
gameFonts.CopyTo(_fonts, defaultFonts.Length);
userFonts.CopyTo(_fonts, defaultFonts.Length + gameFonts.Length);
}
private bool AddNewEntry(int font, int size)
{
if (font < 0 || font > _fonts.Length)
{
return false;
}
if (size <= 0 || size > _sizes.Length)
{
return false;
}
string fontName = _fonts[font];
string key = fontName + "_" + size.ToString();
if (Fonts.ContainsKey(key))
{
return false;
}
FontData fontData = new FontData(fontName, size);
Fonts.Add(key, fontData);
FontsManager.Instance.BuildFonts();
return true;
}
private void SelectFolder()
{
Action<bool, string> callback = (finished, path) =>
{
if (finished && path.Length > 0)
{
FontsPath = path;
ReloadFonts();
}
};
_fileDialogManager.OpenFolderDialog("Select Fonts Folder", callback);
}
[ManualDraw]
public bool Draw(ref bool changed)
{
ImGuiTableFlags flags =
ImGuiTableFlags.RowBg |
ImGuiTableFlags.Borders |
ImGuiTableFlags.BordersOuter |
ImGuiTableFlags.BordersInner |
ImGuiTableFlags.ScrollY |
ImGuiTableFlags.SizingFixedSame;
if (ImGui.BeginChild("Fonts", new Vector2(800, 500), false, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
{
if (_fonts.Length == 0)
{
ImGuiHelper.Tab();
ImGui.Text("Default font not found in \"%appdata%/Roaming/XIVLauncher/InstalledPlugins/HSUI/Media/Fonts/Expressway.ttf\"");
return false;
}
ImGuiHelper.NewLineAndTab();
if (ImGui.InputText("Path", ref FontsPath, 200, ImGuiInputTextFlags.EnterReturnsTrue))
{
changed = true;
ReloadFonts();
}
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Folder.ToIconString(), new Vector2(0, 0)))
{
SelectFolder();
}
ImGui.PopFont();
ImGuiHelper.Tab();
ImGui.Combo("Font ##font", ref _inputFont, _fonts, 10);
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button("\uf2f9", new Vector2(0, 0)))
{
ReloadFonts();
}
ImGui.PopFont();
ImGuiHelper.Tab();
ImGui.Combo("Size ##size", ref _inputSize, _sizes, 10);
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(0, 0)))
{
changed |= AddNewEntry(_inputFont, _inputSize + 1);
}
ImGui.PopFont();
ImGuiHelper.NewLineAndTab();
if (ImGui.BeginTable("table", 3, flags, new Vector2(326, 300)))
{
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 0, 0);
ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, 0, 1);
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 0, 2);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
for (int i = 0; i < Fonts.Count; i++)
{
var key = Fonts.Keys[i];
var fontData = Fonts.Values[i];
ImGui.PushID(i.ToString());
ImGui.TableNextRow(ImGuiTableRowFlags.None);
// icon
if (ImGui.TableSetColumnIndex(0))
{
ImGui.Text(fontData.Name);
}
// id
if (ImGui.TableSetColumnIndex(1))
{
ImGui.Text(fontData.Size.ToString());
}
// remove
if (ImGui.TableSetColumnIndex(2))
{
ImGui.PushFont(UiBuilder.IconFont);
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero);
if (ImGui.Button(FontAwesomeIcon.ArrowAltCircleUp.ToIconString()))
{
_applyingIndex = i;
}
if (!IsDefaultFont(key))
{
ImGui.SameLine();
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString()))
{
_removingIndex = i;
}
}
ImGui.PopFont();
ImGui.PopStyleColor(3);
}
ImGui.PopID();
}
ImGui.EndTable();
}
ImGuiHelper.NewLineAndTab();
if (ImGui.Checkbox("Support Chinese", ref SupportChineseCharacters))
{
changed = true;
FontsManager.Instance.BuildFonts();
}
ImGui.SameLine();
if (ImGui.Checkbox("Support Korean", ref SupportKoreanCharacters))
{
changed = true;
FontsManager.Instance.BuildFonts();
}
ImGui.SameLine();
if (ImGui.Checkbox("Support Cyrillic", ref SupportCyrillicCharacters))
{
changed = true;
FontsManager.Instance.BuildFonts();
}
}
// apply confirmation
if (_applyingIndex >= 0)
{
string[] lines = new string[] { "Are you sure you want to apply this font", "to all labels using a font with the same size?" };
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Apply to all labels?", lines);
if (didConfirm)
{
var (key, font) = Fonts.ElementAt(_applyingIndex);
List<LabelConfig> labelConfigs = ConfigurationManager.Instance.GetObjects<LabelConfig>();
foreach (LabelConfig label in labelConfigs)
{
if (label.FontID != null && Fonts.TryGetValue(label.FontID, out FontData value))
{
if (font.Size == value.Size)
{
label.FontID = key;
}
}
}
changed = true;
ConfigurationManager.Instance.SaveConfigurations(forced: true);
}
if (didConfirm || didClose)
{
_applyingIndex = -1;
}
}
// delete confirmation
if (_removingIndex >= 0)
{
string[] lines = new string[] { "Are you sure you want to remove this font?" };
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Remove custom font?", lines);
if (didConfirm)
{
Fonts.RemoveAt(_removingIndex);
FontsManager.Instance.BuildFonts();
changed = true;
}
if (didConfirm || didClose)
{
_removingIndex = -1;
}
}
ImGui.EndChild();
_fileDialogManager.Draw();
return false;
}
}
}
@@ -0,0 +1,96 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Interface.Bars;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[DisableParentSettings("Size")]
[Section("Other Elements")]
[SubSection("GCD Indicator", 0)]
public class GCDIndicatorConfig : AnchorablePluginConfigObject
{
[Checkbox("Always Show")]
[Order(3)]
public bool AlwaysShow = false;
[Checkbox("Anchor To Mouse")]
[Order(4)]
public bool AnchorToMouse = false;
[ColorEdit4("Background Color")]
[Order(16)]
public PluginConfigColor BackgroundColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 50f / 100f));
[ColorEdit4("Color")]
[Order(17)]
public PluginConfigColor FillColor = new PluginConfigColor(new(220f / 255f, 220f / 255f, 220f / 255f, 100f / 100f));
[Checkbox("Show Border")]
[Order(18)]
public bool ShowBorder = true;
[Checkbox("Instant GCDs only", spacing = true)]
[Order(19)]
public bool InstantGCDsOnly = false;
[Checkbox("Only show when under GCD Threshold", spacing = true)]
[Order(20)]
public bool LimitGCDThreshold = false;
[DragFloat("GCD Threshold", velocity = 0.01f)]
[Order(21, collapseWith = nameof(LimitGCDThreshold))]
public float GCDThreshold = 1.50f;
[Checkbox("Show GCD Queue Indicator", spacing = true)]
[Order(24)]
public bool ShowGCDQueueIndicator = true;
[ColorEdit4("GCD Queue Color")]
[Order(25, collapseWith = nameof(ShowGCDQueueIndicator))]
public PluginConfigColor QueueColor = new PluginConfigColor(new(13f / 255f, 207f / 255f, 31f / 255f, 100f / 100f));
[Checkbox("Circular Mode", spacing = true)]
[Order(30)]
public bool CircularMode = false;
[DragInt("Radius")]
[Order(35, collapseWith = nameof(CircularMode))]
public int CircleRadius = 40;
[DragInt("Thickness")]
[Order(40, collapseWith = nameof(CircularMode))]
public int CircleThickness = 10;
[DragInt("Start Angle", min = 0, max = 359)]
[Order(45, collapseWith = nameof(CircularMode))]
public int CircleStartAngle = 0;
[Checkbox("Rotate CCW")]
[Order(50, collapseWith = nameof(CircularMode))]
public bool RotateCCW = false;
[NestedConfig("Bar Mode", 45, collapsingHeader = false)]
public GCDBarConfig Bar = new GCDBarConfig(
new Vector2(0, HUDConstants.BaseHUDOffsetY + 21),
new Vector2(254, 8),
PluginConfigColor.Empty
);
[NestedConfig("Visibility", 70)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public new static GCDIndicatorConfig DefaultConfig() { return new GCDIndicatorConfig() { Enabled = false, Strata = StrataLevel.MID_HIGH }; }
}
[DisableParentSettings("Position", "Anchor", "HideWhenInactive", "FillColor", "BackgroundColor", "DrawBorder")]
[Exportable(false)]
public class GCDBarConfig : BarConfig
{
public GCDBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
: base(position, size, fillColor, fillDirection)
{
}
}
}
@@ -0,0 +1,247 @@
using System;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using Dalamud.Bindings.ImGui;
using System.Collections.Generic;
using System.Numerics;
using HSUI.Config;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
namespace HSUI.Interface.GeneralElements
{
public class GCDIndicatorHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
{
private GCDIndicatorConfig Config => (GCDIndicatorConfig)_config;
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
public IGameObject? Actor { get; set; } = null;
private bool _wasBarEnabled = true;
private bool _wasCircularModeEnabled = false;
private float _lastTotalCastTime = 0;
public GCDIndicatorHud(GCDIndicatorConfig config, string displayName) : base(config, displayName) { }
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
var (pos, size) = GetPositionAndSize(Vector2.Zero);
if (Config.CircularMode)
{
pos -= size / 2f;
}
return (new List<Vector2>() { pos }, new List<Vector2>() { size });
}
private (Vector2, Vector2) GetPositionAndSize(Vector2 origin)
{
Vector2 pos = Config.AnchorToMouse ? ImGui.GetMousePos() + Config.Position : origin + Config.Position;
Vector2 size = Config.Bar.Size;
if (Config.CircularMode)
{
size = new Vector2(Config.CircleRadius * 2, Config.CircleRadius * 2);
pos += size / 2f;
}
return (pos, size);
}
protected override void DrawDraggableArea(Vector2 origin)
{
if (Config.AnchorToMouse)
{
return;
}
base.DrawDraggableArea(origin);
}
public override void DrawChildren(Vector2 origin)
{
CheckToggles();
if (!Config.Enabled || Actor == null || Actor is not IPlayerCharacter)
{
return;
}
GCDHelper.GetGCDInfo((IPlayerCharacter)Actor, out var elapsed, out var total);
if (!Config.AlwaysShow && total == 0)
{
_lastTotalCastTime = 0;
return;
}
if (_lastTotalCastTime == 0 && Utils.IsActorCasting(Actor))
{
_lastTotalCastTime = ((IBattleChara)Actor).TotalCastTime;
}
var scale = elapsed / total;
if (scale <= 0)
{
_lastTotalCastTime = 0;
return;
}
bool instantGCDsOnly = Config.InstantGCDsOnly && _lastTotalCastTime != 0;
bool thresholdGCDs = Config.LimitGCDThreshold && _lastTotalCastTime > Config.GCDThreshold;
if (instantGCDsOnly || thresholdGCDs)
{
if (Config.AlwaysShow)
{
elapsed = 0;
total = 0;
}
else
{
return;
}
}
Config.Bar.Position = Config.Position;
Config.Bar.Anchor = Config.Anchor;
Config.Bar.BackgroundColor = Config.BackgroundColor;
Config.Bar.FillColor = Config.FillColor;
Config.Bar.DrawBorder = Config.ShowBorder;
if (Config.Bar.Enabled)
{
DrawNormalBar(origin, elapsed, total);
}
else
{
var (pos, size) = GetPositionAndSize(origin);
pos = Utils.GetAnchoredPosition(pos, size, Config.Anchor);
AddDrawAction(_config.StrataLevel, () =>
{
DrawCircularIndicator(pos, Config.CircleRadius, elapsed, total);
});
}
}
private void CheckToggles()
{
bool barEnabledChanged = _wasBarEnabled != Config.Bar.Enabled;
if (barEnabledChanged)
{
Config.CircularMode = !Config.Bar.Enabled;
}
else
{
bool circularModeChanged = _wasCircularModeEnabled != Config.CircularMode;
if (circularModeChanged)
{
Config.Bar.Enabled = !Config.CircularMode;
}
}
_wasBarEnabled = Config.Bar.Enabled;
_wasCircularModeEnabled = Config.CircularMode;
}
private void DrawCircularIndicator(Vector2 position, float radius, float current, float total)
{
total = Config.AlwaysShow && total == 0 ? 1 : total;
current = Config.AlwaysShow && current == 0 ? total : current;
var size = new Vector2(radius * 2);
DrawHelper.DrawInWindow(ID, position - size / 2, size, false, (drawList) =>
{
current = Math.Min(current, total);
// controls how smooth the arc looks
const int segments = 100;
const float queueTime = 0.5f;
float startAngle = 0f;
float endAngle = 2f * (float)Math.PI;
float offset = (float)(-Math.PI / 2f + (Config.CircleStartAngle * (Math.PI / 180f)));
if (Config.RotateCCW)
{
startAngle *= -1;
endAngle *= -1;
}
if (Config.AlwaysShow && current == total)
{
drawList.PathArcTo(position, radius, startAngle + offset, endAngle + offset, segments);
drawList.PathStroke(Config.FillColor.Base, ImDrawFlags.None, Config.CircleThickness);
}
else
{
// always draw until the queue threshold
float progressAngle = Math.Min(current, total - (Config.ShowGCDQueueIndicator ? queueTime : 0f)) / total * endAngle;
// drawing an arc with thickness to make it look like an annular sector
drawList.PathArcTo(position, radius, startAngle + offset, progressAngle + offset, segments);
drawList.PathStroke(Config.FillColor.Base, ImDrawFlags.None, Config.CircleThickness);
// draw the queue indicator
if (Config.ShowGCDQueueIndicator && current > total - queueTime)
{
float oldAngle = progressAngle - 0.0003f * total * endAngle;
progressAngle = current / total * endAngle;
drawList.PathArcTo(position, radius, oldAngle + offset, progressAngle + offset, segments);
drawList.PathStroke(Config.QueueColor.Base, ImDrawFlags.None, Config.CircleThickness);
}
// anything that remains is background
drawList.PathArcTo(position, radius, progressAngle + offset, endAngle + offset, segments);
drawList.PathStroke(Config.BackgroundColor.Base, ImDrawFlags.None, Config.CircleThickness);
}
if (Config.ShowBorder)
{
drawList.PathArcTo(position, radius - Config.CircleThickness / 2f, 0, endAngle, segments);
drawList.PathStroke(0xFF000000, ImDrawFlags.None, 1);
drawList.PathArcTo(position, radius + Config.CircleThickness / 2f, 0, endAngle, segments);
drawList.PathStroke(0xFF000000, ImDrawFlags.None, 1);
}
});
}
private void DrawNormalBar(Vector2 origin, float current, float total)
{
GCDBarConfig config = Config.Bar;
Rect mainRect = BarUtilities.GetFillRect(config.Position, config.Size, config.FillDirection, config.FillColor, current, total, 0);
BarHud bar = new BarHud(config, null, null);
bar.AddForegrounds(mainRect);
float currentPercent = current / total;
float percentNonQueue = total != 0 ? 1F - (500f / 1000f) / total : 0;
if (percentNonQueue > 0 && currentPercent >= percentNonQueue && Config.ShowGCDQueueIndicator)
{
float scale = 1 - percentNonQueue;
Vector2 size = config.FillDirection.IsHorizontal() ?
new Vector2(config.Size.X * scale, config.Size.Y) :
new Vector2(config.Size.X, config.Size.Y * scale);
Vector2 pos = config.Position;
if (config.FillDirection == BarDirection.Right)
{
pos.X += config.Size.X * percentNonQueue;
}
else if (config.FillDirection == BarDirection.Down)
{
pos.Y += config.Size.Y * percentNonQueue;
}
Rect foreground = BarUtilities.GetFillRect(pos, size, config.FillDirection, Config.QueueColor, currentPercent - percentNonQueue, scale, 0);
bar.AddForegrounds(foreground);
}
AddDrawActions(bar.GetDrawActions(origin, _config.StrataLevel));
}
}
}
+478
View File
@@ -0,0 +1,478 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Party;
using System;
using System.Collections.Generic;
namespace HSUI.Interface.GeneralElements
{
public class GlobalColors : IDisposable
{
#region Singleton
private MiscColorConfig _miscColorConfig = null!;
private RolesColorConfig _rolesColorConfig = null!;
private Dictionary<uint, PluginConfigColor> ColorMap = null!;
private GlobalColors()
{
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
OnConfigReset(ConfigurationManager.Instance);
}
private void OnConfigReset(ConfigurationManager sender)
{
_miscColorConfig = sender.GetConfigObject<MiscColorConfig>();
_rolesColorConfig = sender.GetConfigObject<RolesColorConfig>();
var tanksColorConfig = sender.GetConfigObject<TanksColorConfig>();
var healersColorConfig = sender.GetConfigObject<HealersColorConfig>();
var meleeColorConfig = sender.GetConfigObject<MeleeColorConfig>();
var rangedColorConfig = sender.GetConfigObject<RangedColorConfig>();
var castersColorConfig = sender.GetConfigObject<CastersColorConfig>();
ColorMap = new Dictionary<uint, PluginConfigColor>()
{
// tanks
[JobIDs.GLA] = tanksColorConfig.GLAColor,
[JobIDs.MRD] = tanksColorConfig.MRDColor,
[JobIDs.PLD] = tanksColorConfig.PLDColor,
[JobIDs.WAR] = tanksColorConfig.WARColor,
[JobIDs.DRK] = tanksColorConfig.DRKColor,
[JobIDs.GNB] = tanksColorConfig.GNBColor,
// healers
[JobIDs.CNJ] = healersColorConfig.CNJColor,
[JobIDs.WHM] = healersColorConfig.WHMColor,
[JobIDs.SCH] = healersColorConfig.SCHColor,
[JobIDs.AST] = healersColorConfig.ASTColor,
[JobIDs.SGE] = healersColorConfig.SGEColor,
// melee
[JobIDs.PGL] = meleeColorConfig.PGLColor,
[JobIDs.LNC] = meleeColorConfig.LNCColor,
[JobIDs.ROG] = meleeColorConfig.ROGColor,
[JobIDs.MNK] = meleeColorConfig.MNKColor,
[JobIDs.DRG] = meleeColorConfig.DRGColor,
[JobIDs.NIN] = meleeColorConfig.NINColor,
[JobIDs.SAM] = meleeColorConfig.SAMColor,
[JobIDs.RPR] = meleeColorConfig.RPRColor,
[JobIDs.VPR] = meleeColorConfig.VPRColor,
// ranged
[JobIDs.ARC] = rangedColorConfig.ARCColor,
[JobIDs.BRD] = rangedColorConfig.BRDColor,
[JobIDs.MCH] = rangedColorConfig.MCHColor,
[JobIDs.DNC] = rangedColorConfig.DNCColor,
// casters
[JobIDs.THM] = castersColorConfig.THMColor,
[JobIDs.ACN] = castersColorConfig.ACNColor,
[JobIDs.BLM] = castersColorConfig.BLMColor,
[JobIDs.SMN] = castersColorConfig.SMNColor,
[JobIDs.RDM] = castersColorConfig.RDMColor,
[JobIDs.PCT] = castersColorConfig.PCTColor,
[JobIDs.BLU] = castersColorConfig.BLUColor,
// crafters
[JobIDs.CRP] = _rolesColorConfig.HANDColor,
[JobIDs.BSM] = _rolesColorConfig.HANDColor,
[JobIDs.ARM] = _rolesColorConfig.HANDColor,
[JobIDs.GSM] = _rolesColorConfig.HANDColor,
[JobIDs.LTW] = _rolesColorConfig.HANDColor,
[JobIDs.WVR] = _rolesColorConfig.HANDColor,
[JobIDs.ALC] = _rolesColorConfig.HANDColor,
[JobIDs.CUL] = _rolesColorConfig.HANDColor,
// gatherers
[JobIDs.MIN] = _rolesColorConfig.LANDColor,
[JobIDs.BOT] = _rolesColorConfig.LANDColor,
[JobIDs.FSH] = _rolesColorConfig.LANDColor
};
}
public static void Initialize()
{
Instance = new GlobalColors();
}
public static GlobalColors Instance { get; private set; } = null!;
~GlobalColors()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
Instance = null!;
}
#endregion
public PluginConfigColor? ColorForJobId(uint jobId) => ColorMap.TryGetValue(jobId, out PluginConfigColor? color) ? color : null;
public PluginConfigColor SafeColorForJobId(uint jobId) => ColorForJobId(jobId) ?? _miscColorConfig.NPCNeutralColor;
public PluginConfigColor? RoleColorForJobId(uint jobId)
{
JobRoles role = JobsHelper.RoleForJob(jobId);
return role switch
{
JobRoles.Tank => _rolesColorConfig.TankRoleColor,
JobRoles.Healer => _rolesColorConfig.HealerRoleColor,
JobRoles.DPSMelee => _rolesColorConfig.UseSpecificDPSColors ? _rolesColorConfig.MeleeDPSRoleColor : _rolesColorConfig.DPSRoleColor,
JobRoles.DPSRanged => _rolesColorConfig.UseSpecificDPSColors ? _rolesColorConfig.RangedDPSRoleColor : _rolesColorConfig.DPSRoleColor,
JobRoles.DPSCaster => _rolesColorConfig.UseSpecificDPSColors ? _rolesColorConfig.CasterDPSRoleColor : _rolesColorConfig.DPSRoleColor,
JobRoles.Gatherer => _rolesColorConfig.LANDColor,
JobRoles.Crafter => _rolesColorConfig.HANDColor,
_ => null
};
}
public PluginConfigColor SafeRoleColorForJobId(uint jobId) => RoleColorForJobId(jobId) ?? _miscColorConfig.NPCNeutralColor;
public PluginConfigColor EmptyUnitFrameColor => _miscColorConfig.EmptyUnitFrameColor;
public PluginConfigColor EmptyColor => _miscColorConfig.EmptyColor;
public PluginConfigColor PartialFillColor => _miscColorConfig.PartialFillColor;
public PluginConfigColor NPCFriendlyColor => _miscColorConfig.NPCFriendlyColor;
public PluginConfigColor NPCHostileColor => _miscColorConfig.NPCHostileColor;
public PluginConfigColor NPCNeutralColor => _miscColorConfig.NPCNeutralColor;
}
[Disableable(false)]
[Section("Colors")]
[SubSection("Tanks", 0)]
public class TanksColorConfig : PluginConfigObject
{
public new static TanksColorConfig DefaultConfig() { return new TanksColorConfig(); }
[ColorEdit4("Paladin", spacing = true)]
[Order(5)]
public PluginConfigColor PLDColor = new PluginConfigColor(new(168f / 255f, 210f / 255f, 230f / 255f, 100f / 100f));
[ColorEdit4("Dark Knight")]
[Order(10)]
public PluginConfigColor DRKColor = new PluginConfigColor(new(209f / 255f, 38f / 255f, 204f / 255f, 100f / 100f));
[ColorEdit4("Warrior")]
[Order(15)]
public PluginConfigColor WARColor = new PluginConfigColor(new(207f / 255f, 38f / 255f, 33f / 255f, 100f / 100f));
[ColorEdit4("Gunbreaker")]
[Order(20)]
public PluginConfigColor GNBColor = new PluginConfigColor(new(121f / 255f, 109f / 255f, 48f / 255f, 100f / 100f));
[ColorEdit4("Gladiator", spacing = true)]
[Order(25)]
public PluginConfigColor GLAColor = new PluginConfigColor(new(168f / 255f, 210f / 255f, 230f / 255f, 100f / 100f));
[ColorEdit4("Marauder")]
[Order(30)]
public PluginConfigColor MRDColor = new PluginConfigColor(new(207f / 255f, 38f / 255f, 33f / 255f, 100f / 100f));
}
[Disableable(false)]
[Section("Colors")]
[SubSection("Healers", 0)]
public class HealersColorConfig : PluginConfigObject
{
public new static HealersColorConfig DefaultConfig() { return new HealersColorConfig(); }
[ColorEdit4("Scholar", spacing = true)]
[Order(5)]
public PluginConfigColor SCHColor = new PluginConfigColor(new(134f / 255f, 87f / 255f, 255f / 255f, 100f / 100f));
[ColorEdit4("White Mage")]
[Order(10)]
public PluginConfigColor WHMColor = new PluginConfigColor(new(255f / 255f, 240f / 255f, 220f / 255f, 100f / 100f));
[ColorEdit4("Astrologian")]
[Order(15)]
public PluginConfigColor ASTColor = new PluginConfigColor(new(255f / 255f, 231f / 255f, 74f / 255f, 100f / 100f));
[ColorEdit4("Sage")]
[Order(20)]
public PluginConfigColor SGEColor = new PluginConfigColor(new(144f / 255f, 176f / 255f, 255f / 255f, 100f / 100f));
[ColorEdit4("Conjurer", spacing = true)]
[Order(25)]
public PluginConfigColor CNJColor = new PluginConfigColor(new(255f / 255f, 240f / 255f, 220f / 255f, 100f / 100f));
}
[Disableable(false)]
[Section("Colors")]
[SubSection("Melee", 0)]
public class MeleeColorConfig : PluginConfigObject
{
public new static MeleeColorConfig DefaultConfig() { return new MeleeColorConfig(); }
[ColorEdit4("Monk", spacing = true)]
[Order(5)]
public PluginConfigColor MNKColor = new PluginConfigColor(new(214f / 255f, 156f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Ninja")]
[Order(10)]
public PluginConfigColor NINColor = new PluginConfigColor(new(175f / 255f, 25f / 255f, 100f / 255f, 100f / 100f));
[ColorEdit4("Dragoon")]
[Order(15)]
public PluginConfigColor DRGColor = new PluginConfigColor(new(65f / 255f, 100f / 255f, 205f / 255f, 100f / 100f));
[ColorEdit4("Samurai")]
[Order(20)]
public PluginConfigColor SAMColor = new PluginConfigColor(new(228f / 255f, 109f / 255f, 4f / 255f, 100f / 100f));
[ColorEdit4("Reaper")]
[Order(25)]
public PluginConfigColor RPRColor = new PluginConfigColor(new(150f / 255f, 90f / 255f, 144f / 255f, 100f / 100f));
[ColorEdit4("Viper")]
[Order(25)]
public PluginConfigColor VPRColor = new PluginConfigColor(new(16f / 255f, 130f / 255f, 16f / 255f, 100f / 100f));
[ColorEdit4("Pugilist", spacing = true)]
[Order(30)]
public PluginConfigColor PGLColor = new PluginConfigColor(new(214f / 255f, 156f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Rogue")]
[Order(35)]
public PluginConfigColor ROGColor = new PluginConfigColor(new(175f / 255f, 25f / 255f, 100f / 255f, 100f / 100f));
[ColorEdit4("Lancer")]
[Order(40)]
public PluginConfigColor LNCColor = new PluginConfigColor(new(65f / 255f, 100f / 255f, 205f / 255f, 100f / 100f));
}
[Disableable(false)]
[Section("Colors")]
[SubSection("Ranged", 0)]
public class RangedColorConfig : PluginConfigObject
{
public new static RangedColorConfig DefaultConfig() { return new RangedColorConfig(); }
[ColorEdit4("Bard", spacing = true)]
[Order(5)]
public PluginConfigColor BRDColor = new PluginConfigColor(new(145f / 255f, 186f / 255f, 94f / 255f, 100f / 100f));
[ColorEdit4("Machinist")]
[Order(10)]
public PluginConfigColor MCHColor = new PluginConfigColor(new(110f / 255f, 225f / 255f, 214f / 255f, 100f / 100f));
[ColorEdit4("Dancer")]
[Order(15)]
public PluginConfigColor DNCColor = new PluginConfigColor(new(226f / 255f, 176f / 255f, 175f / 255f, 100f / 100f));
[ColorEdit4("Archer", separator = true)]
[Order(20)]
public PluginConfigColor ARCColor = new PluginConfigColor(new(145f / 255f, 186f / 255f, 94f / 255f, 100f / 100f));
}
[Disableable(false)]
[Section("Colors")]
[SubSection("Caster", 0)]
public class CastersColorConfig : PluginConfigObject
{
public new static CastersColorConfig DefaultConfig() { return new CastersColorConfig(); }
[ColorEdit4("Black Mage", spacing = true)]
[Order(5)]
public PluginConfigColor BLMColor = new PluginConfigColor(new(165f / 255f, 121f / 255f, 214f / 255f, 100f / 100f));
[ColorEdit4("Summoner")]
[Order(10)]
public PluginConfigColor SMNColor = new PluginConfigColor(new(45f / 255f, 155f / 255f, 120f / 255f, 100f / 100f));
[ColorEdit4("Red Mage")]
[Order(15)]
public PluginConfigColor RDMColor = new PluginConfigColor(new(232f / 255f, 123f / 255f, 123f / 255f, 100f / 100f));
[ColorEdit4("Pictomancer")]
[Order(15)]
public PluginConfigColor PCTColor = new PluginConfigColor(new(252f / 255f, 146f / 255f, 225f / 255f, 100f / 100f));
[ColorEdit4("Blue Mage", spacing = true)]
[Order(20)]
public PluginConfigColor BLUColor = new PluginConfigColor(new(0f / 255f, 185f / 255f, 247f / 255f, 100f / 100f));
[ColorEdit4("Thaumaturge")]
[Order(25)]
public PluginConfigColor THMColor = new PluginConfigColor(new(165f / 255f, 121f / 255f, 214f / 255f, 100f / 100f));
[ColorEdit4("Arcanist")]
[Order(30)]
public PluginConfigColor ACNColor = new PluginConfigColor(new(45f / 255f, 155f / 255f, 120f / 255f, 100f / 100f));
}
[Disableable(false)]
[Section("Colors")]
[SubSection("Roles", 0)]
public class RolesColorConfig : PluginConfigObject
{
public new static RolesColorConfig DefaultConfig() { return new RolesColorConfig(); }
[ColorEdit4("Tank")]
[Order(10)]
public PluginConfigColor TankRoleColor = new PluginConfigColor(new(21f / 255f, 28f / 255f, 100f / 255f, 100f / 100f));
[ColorEdit4("DPS")]
[Order(15)]
public PluginConfigColor DPSRoleColor = new PluginConfigColor(new(153f / 255f, 23f / 255f, 23f / 255f, 100f / 100f));
[ColorEdit4("Healer")]
[Order(20)]
public PluginConfigColor HealerRoleColor = new PluginConfigColor(new(46f / 255f, 125f / 255f, 50f / 255f, 100f / 100f));
[ColorEdit4("Disciple of the Land", spacing = true)]
[Order(25)]
public PluginConfigColor LANDColor = new PluginConfigColor(new(99f / 255f, 172f / 255f, 14f / 255f, 100f / 100f));
[ColorEdit4("Disciple of the Hand")]
[Order(30)]
public PluginConfigColor HANDColor = new PluginConfigColor(new(99f / 255f, 172f / 255f, 14f / 255f, 100f / 100f));
[Checkbox("Use Specific DPS Colors", spacing = true)]
[Order(35)]
public bool UseSpecificDPSColors = false;
[ColorEdit4("Melee DPS")]
[Order(40, collapseWith = nameof(UseSpecificDPSColors))]
public PluginConfigColor MeleeDPSRoleColor = new PluginConfigColor(new(151f / 255f, 56f / 255f, 56f / 255f, 100f / 100f));
[ColorEdit4("Ranged DPS")]
[Order(40, collapseWith = nameof(UseSpecificDPSColors))]
public PluginConfigColor RangedDPSRoleColor = new PluginConfigColor(new(250f / 255f, 185f / 255f, 67f / 255f, 100f / 100f));
[ColorEdit4("Caster DPS")]
[Order(40, collapseWith = nameof(UseSpecificDPSColors))]
public PluginConfigColor CasterDPSRoleColor = new PluginConfigColor(new(154f / 255f, 82f / 255f, 193f / 255f, 100f / 100f));
}
[Disableable(false)]
[Section("Colors")]
[SubSection("Misc", 0)]
public class MiscColorConfig : PluginConfigObject
{
public new static MiscColorConfig DefaultConfig() { return new MiscColorConfig(); }
[Combo("Gradient Type For Bars", "Flat Color", "Right", "Left", "Up", "Down", "Centered Horizontal", spacing = true)]
[Order(4)]
public GradientDirection GradientDirection = GradientDirection.Down;
[ColorEdit4("Empty Unit Frame", separator = true)]
[Order(5)]
public PluginConfigColor EmptyUnitFrameColor = new PluginConfigColor(new(0f / 255f, 0f / 255f, 0f / 255f, 95f / 100f));
[ColorEdit4("Empty Bar")]
[Order(10)]
public PluginConfigColor EmptyColor = new PluginConfigColor(new(0f / 255f, 0f / 255f, 0f / 255f, 50f / 100f));
[ColorEdit4("Partially Filled Bar")]
[Order(15)]
public PluginConfigColor PartialFillColor = new PluginConfigColor(new(180f / 255f, 180f / 255f, 180f / 255f, 100f / 100f));
[ColorEdit4("NPC Friendly", separator = true)]
[Order(20)]
public PluginConfigColor NPCFriendlyColor = new PluginConfigColor(new(99f / 255f, 172f / 255f, 14f / 255f, 100f / 100f));
[ColorEdit4("NPC Hostile")]
[Order(25)]
public PluginConfigColor NPCHostileColor = new PluginConfigColor(new(233f / 255f, 4f / 255f, 4f / 255f, 100f / 100f));
[ColorEdit4("NPC Neutral")]
[Order(30)]
public PluginConfigColor NPCNeutralColor = new PluginConfigColor(new(218f / 255f, 157f / 255f, 46f / 255f, 100f / 100f));
}
[Exportable(false)]
public class ColorByHealthValueConfig : PluginConfigObject
{
[Checkbox("Use Max Health Color")]
[Order(5)]
public bool UseMaxHealthColor = false;
[ColorEdit4("Max Health Color")]
[Order(10, collapseWith = nameof(UseMaxHealthColor))]
public PluginConfigColor MaxHealthColor = new PluginConfigColor(new(18f / 255f, 18f / 255f, 18f / 255f, 100f / 100f));
[Checkbox("Job Color as Max Health Color")]
[Order(15, collapseWith = nameof(UseMaxHealthColor))]
public bool UseJobColorAsMaxHealth = false;
[Checkbox("Job Role as Max Health Color")]
[Order(20, collapseWith = nameof(UseMaxHealthColor))]
public bool UseRoleColorAsMaxHealth = false;
[ColorEdit4("High Health Color")]
[Order(25)]
public PluginConfigColor FullHealthColor = new PluginConfigColor(new(0f / 255f, 255f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Low Health Color")]
[Order(30)]
public PluginConfigColor LowHealthColor = new PluginConfigColor(new(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[DragFloat("Max Health Color Above Health %", min = 50f, max = 100f, velocity = 1f)]
[Order(35)]
public float FullHealthColorThreshold = 75f;
[DragFloat("Low Health Color Below Health %", min = 0f, max = 50f, velocity = 1f)]
[Order(40)]
public float LowHealthColorThreshold = 25f;
[Combo("Blend Mode", "LAB", "LChab", "XYZ", "RGB", "LChuv", "Luv", "Jzazbz", "JzCzhz")]
[Order(45)]
public BlendMode BlendMode = BlendMode.LAB;
}
public class ColorByHealthFieldsConverter : PluginConfigObjectConverter
{
public ColorByHealthFieldsConverter()
{
SameTypeFieldConverter<bool> enabled =
new SameTypeFieldConverter<bool>("ColorByHealth.Enabled", false);
FieldConvertersMap.Add("UseColorBasedOnHealthValue", enabled);
SameClassFieldConverter<PluginConfigColor> fullHealth =
new SameClassFieldConverter<PluginConfigColor>("ColorByHealth.FullHealthColor", new PluginConfigColor(new(0f / 255f, 255f / 255f, 0f / 255f, 100f / 100f)));
FieldConvertersMap.Add("FullHealthColor", fullHealth);
SameClassFieldConverter<PluginConfigColor> lowHealth =
new SameClassFieldConverter<PluginConfigColor>("ColorByHealth.LowHealthColor", new PluginConfigColor(new(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f)));
FieldConvertersMap.Add("LowHealthColor", lowHealth);
SameTypeFieldConverter<float> fullThreshold = new SameTypeFieldConverter<float>("ColorByHealth.FullHealthColorThreshold", 75f);
FieldConvertersMap.Add("FullHealthColorThreshold", fullThreshold);
SameTypeFieldConverter<float> lowThreshold = new SameTypeFieldConverter<float>("ColorByHealth.LowHealthColorThreshold", 25f);
FieldConvertersMap.Add("LowHealthColorThreshold", lowThreshold);
SameTypeFieldConverter<BlendMode> blendMode = new SameTypeFieldConverter<BlendMode>("ColorByHealth.BlendMode", BlendMode.LAB);
FieldConvertersMap.Add("blendMode", blendMode);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(PartyFramesColorsConfig) ||
objectType == typeof(UnitFrameConfig) ||
objectType == typeof(PlayerUnitFrameConfig) ||
objectType == typeof(TargetUnitFrameConfig) ||
objectType == typeof(TargetOfTargetUnitFrameConfig) ||
objectType == typeof(FocusTargetUnitFrameConfig);
}
}
}
@@ -0,0 +1,54 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;
using Newtonsoft.Json;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[Disableable(false)]
[Exportable(false)]
[Section("Visibility")]
[SubSection("Global", 0)]
public class GlobalVisibilityConfig : PluginConfigObject
{
public new static GlobalVisibilityConfig DefaultConfig() { return new GlobalVisibilityConfig(); }
[NestedConfig("Visibility", 50, collapsingHeader = false)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
[JsonIgnore]
private bool _applying = false;
[ManualDraw]
public bool Draw(ref bool changed)
{
ImGui.NewLine();
if (ImGui.Button("Apply to all elements", new Vector2(200, 30)))
{
_applying = true;
}
if (_applying)
{
string[] lines = new string[] { "This will replace the visibility settings", "for ALL HSUI elements!", "Are you sure?" };
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Apply?", lines);
if (didConfirm)
{
ConfigurationManager.Instance.OnGlobalVisibilityChanged(VisibilityConfig);
changed = true;
}
if (didConfirm || didClose)
{
_applying = false;
}
}
return false;
}
}
}
+42
View File
@@ -0,0 +1,42 @@
using HSUI.Config;
using HSUI.Config.Attributes;
namespace HSUI.Interface.GeneralElements
{
[Exportable(false)]
[Section("Misc")]
[SubSection("Grid", 0)]
public class GridConfig : PluginConfigObject
{
public new static GridConfig DefaultConfig()
{
var config = new GridConfig();
config.Enabled = false;
return config;
}
[DragFloat("Background Alpha", min = 0, max = 1, velocity = .05f)]
[Order(10)]
public float BackgroundAlpha = 0.3f;
[Checkbox("Show Center Lines")]
[Order(15)]
public bool ShowCenterLines = true;
[Checkbox("Show Anchor Points")]
[Order(20)]
public bool ShowAnchorPoints = true;
[Checkbox("Grid Divisions", spacing = true)]
[Order(25)]
public bool ShowGrid = true;
[DragInt("Divisions Distance", min = 50, max = 500)]
[Order(30, collapseWith = nameof(ShowGrid))]
public int GridDivisionsDistance = 50;
[DragInt("Subdivision Count", min = 1, max = 10)]
[Order(35, collapseWith = nameof(ShowGrid))]
public int GridSubdivisionCount = 4;
}
}
@@ -0,0 +1,132 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[Disableable(false)]
[Section("Misc")]
[SubSection("HUD Options", 0)]
public class HUDOptionsConfig : PluginConfigObject
{
[Checkbox("Global HUD Position")]
[Order(5)]
public bool UseGlobalHudShift = false;
[DragInt2("Position", min = -4000, max = 4000)]
[Order(6, collapseWith = nameof(UseGlobalHudShift))]
public Vector2 HudOffset = new(0, 0);
[Checkbox("Dim HSUI's settings window when not focused")]
[Order(10)]
public bool DimConfigWindow = false;
[Checkbox("Automatically disable HUD elements preview", help = "If enabled, all HUD elements preview modes are disabled when HSUI's setting window is closed.")]
[Order(11)]
public bool AutomaticPreviewDisabling = true;
[Checkbox("Use HSUI style", help = "If enabled, HSUI will use its own style for the setting window instead of the general Dalamud style.")]
[Order(12)]
public bool OverrideDalamudStyle = true;
[Checkbox("Mouseover", separator = true)]
[Order(15)]
public bool MouseoverEnabled = true;
[Checkbox("Automatic Mode", help =
"When enabled: All your actions will automatically assume mouseover when your cursor is on top of a unit frame.\n" +
"Mouseover macros or other mouseover plugins are not necessary and WON'T WORK in this mode!\n\n" +
"When disabled: HSUI unit frames will behave like the game's ones.\n" +
"You'll need to use mouseover macros or other mouseover related plugins in this mode.")]
[Order(16, collapseWith = nameof(MouseoverEnabled))]
public bool MouseoverAutomaticMode = true;
//[Checkbox("Support Special Mouse Clicks", isMonitored = true, spacing = true, help =
// "When enabled HSUI will attempt to support special mouse binds (mousewheel, M4, M5, etc) when the cursor\n" +
// "is hovering on top of HSUI's unit frames.\n\n" +
// "If you don't have actions bound to these mouse buttons, it is adviced that you leave this feature disabled.\n\n" +
// "This feature can cause some issues such as click inputs not working in HSUI, or through out the game.\n" +
// "If you run into these kinds of issues, you can try reloading HSUI, restarting the game, or disabling this feature.")]
//[Order(17)]
public bool InputsProxyEnabled = false;
[Checkbox("Hide Default HUD When Replaced", isMonitored = true, separator = true, help =
"When enabled, HSUI automatically hides the default game HUD elements that HSUI replaces.\n" +
"For example: when HSUI hotbars are on, game hotbars are hidden; when HSUI unit frames are on, game parameter/target bars are hidden; etc.")]
[Order(38)]
public bool HideDefaultHudWhenReplaced = true;
[Checkbox("Hide Default Job Gauges", isMonitored = true)]
[Order(40)]
public bool HideDefaultJobGauges = false;
[Checkbox("Hide Default Castbar", isMonitored = true)]
[Order(45)]
public bool HideDefaultCastbar = false;
[Checkbox("Hide Default Pulltimer", isMonitored = true)]
[Order(50)]
public bool HideDefaultPulltimer = false;
[Checkbox("Use Regional Number Format", help = "When enabled, HSUI will use your system's regional format settings when showing numbers.\nWhen disabled, HSUI will use English number formatting instead.", separator = true)]
[Order(60)]
public bool UseRegionalNumberFormats = true;
public new static HUDOptionsConfig DefaultConfig() => new();
}
public class HUDOptionsConfigConverter : PluginConfigObjectConverter
{
public HUDOptionsConfigConverter()
{
Func<Vector2, Vector2[]> func = (value) =>
{
Vector2[] array = new Vector2[4];
for (int i = 0; i < 4; i++)
{
array[i] = value;
}
return array;
};
TypeToClassFieldConverter<Vector2, Vector2[]> castBar = new TypeToClassFieldConverter<Vector2, Vector2[]>(
"CastBarOriginalPositions",
new Vector2[] { Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero },
func
);
TypeToClassFieldConverter<Vector2, Vector2[]> pullTimer = new TypeToClassFieldConverter<Vector2, Vector2[]>(
"PulltimerOriginalPositions",
new Vector2[] { Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero },
func
);
NewClassFieldConverter<Dictionary<string, Vector2>, Dictionary<string, Vector2>[]> jobGauge =
new NewClassFieldConverter<Dictionary<string, Vector2>, Dictionary<string, Vector2>[]>(
"JobGaugeOriginalPositions",
new Dictionary<string, Vector2>[] { new(), new(), new(), new() },
(oldValue) =>
{
Dictionary<string, Vector2>[] array = new Dictionary<string, Vector2>[4];
for (int i = 0; i < 4; i++)
{
array[i] = oldValue;
}
return array;
});
FieldConvertersMap.Add("CastBarOriginalPosition", castBar);
FieldConvertersMap.Add("PulltimerOriginalPosition", pullTimer);
FieldConvertersMap.Add("JobGaugeOriginalPosition", jobGauge);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HUDOptionsConfig);
}
}
}
+215
View File
@@ -0,0 +1,215 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Interface;
using System.Numerics;
using Dalamud.Bindings.ImGui;
namespace HSUI.Interface.GeneralElements
{
/// <summary>
/// Config for a single hotbar (1-10). HotbarIndex is set by the containing config.
/// </summary>
public class HotbarBarConfig : AnchorablePluginConfigObject
{
internal int HotbarIndex { get; set; } = 1;
[RadioSelector("12×1", "6×2", "4×3", "3×4", "2×6", "1×12", Label = "Bar Layout")]
[Order(20)]
public int BarLayout = 0; // 0=12x1, 1=6x2, 2=4x3, 3=3x4, 4=2x6, 5=1x12
/// <summary>Get (columns, rows) for the current BarLayout.</summary>
public (int Cols, int Rows) GetLayoutGrid() => BarLayout switch
{
0 => (12, 1),
1 => (6, 2),
2 => (4, 3),
3 => (3, 4),
4 => (2, 6),
5 => (1, 12),
_ => (12, 1)
};
[DragInt("Slot Count", min = 1, max = 12)]
[Order(21)]
public int SlotCount = 12;
[DragInt2("Slot Size", min = 24, max = 96)]
[Order(22)]
public Vector2 SlotSize = new Vector2(40, 40);
[DragInt("Slot Padding", min = 0, max = 16)]
[Order(23)]
public int SlotPadding = 2;
[Checkbox("Show Cooldown Overlay")]
[Order(24)]
public bool ShowCooldownOverlay = true;
[Checkbox("Show Cooldown Numbers")]
[Order(25)]
public bool ShowCooldownNumbers = true;
[Checkbox("Show Border")]
[Order(26)]
public bool ShowBorder = true;
[Checkbox("Show Tooltips")]
[Order(27)]
public bool ShowTooltips = true;
[Checkbox("Show Keybinds")]
[Order(28)]
public bool ShowSlotNumbers = true;
[Checkbox("Show Combo Highlight")]
[Order(29)]
public bool ShowComboHighlight = true;
[Checkbox("Debug Drag & Drop")]
[Order(30)]
public bool DebugDragDrop = false;
[NestedConfig("Visibility", 70)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public static HotbarBarConfig DefaultConfig(int hotbarIndex)
{
var config = new HotbarBarConfig { HotbarIndex = hotbarIndex };
ApplyDefaults(config, hotbarIndex);
return config;
}
protected static void ApplyDefaults(HotbarBarConfig config, int hotbarIndex)
{
var viewport = ImGui.GetMainViewport().Size;
float yOffset = 60 + (hotbarIndex - 1) * 50;
config.Position = new Vector2(0, -viewport.Y * 0.5f + yOffset);
config.Anchor = DrawAnchor.Top;
config.Size = new Vector2(12 * 40 + 11 * 2, 40);
config.HotbarIndex = hotbarIndex;
}
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("General", 0)]
public class HotbarsConfig : PluginConfigObject
{
[Checkbox("Use HSUI Hotbars", help = "When enabled, HSUI hotbars replace the default game hotbars.")]
[Order(1, collapseWith = null)]
public new bool Enabled = true;
[NestedConfig("Drag & Drop Options", 5, separator = true, collapseWith = null)]
public HotbarsGeneralOptionsConfig GeneralOptions = new();
public new static HotbarsConfig DefaultConfig() => new HotbarsConfig();
}
public class HotbarsGeneralOptionsConfig : PluginConfigObject
{
[Checkbox("Enable drag and drop from game UI", help = "When enabled, you can drag actions, macros, and items from the Actions menu, Macro menu, and Inventory onto HSUI hotbars.")]
[Order(1)]
public bool EnableDragDropFromGame = true;
[Checkbox("Enable Shift+drag to rearrange", help = "When enabled, holding Shift and dragging a hotbar slot lets you swap it with another slot or rearrange your hotbar.")]
[Order(2)]
public bool EnableShiftDragToRearrange = true;
[Checkbox("Enable release outside to clear slot", help = "When enabled, releasing a picked-up icon outside of HSUI hotbars clears that slot.")]
[Order(3)]
public bool EnableReleaseOutsideToClear = true;
public new static HotbarsGeneralOptionsConfig DefaultConfig() => new();
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 1", 0)]
public class Hotbar1BarConfig : HotbarBarConfig
{
public Hotbar1BarConfig() => HotbarIndex = 1;
public new static Hotbar1BarConfig DefaultConfig() { var c = new Hotbar1BarConfig(); ApplyDefaults(c, 1); return c; }
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 2", 0)]
public class Hotbar2BarConfig : HotbarBarConfig
{
public Hotbar2BarConfig() => HotbarIndex = 2;
public new static Hotbar2BarConfig DefaultConfig() { var c = new Hotbar2BarConfig(); ApplyDefaults(c, 2); return c; }
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 3", 0)]
public class Hotbar3BarConfig : HotbarBarConfig
{
public Hotbar3BarConfig() => HotbarIndex = 3;
public new static Hotbar3BarConfig DefaultConfig() { var c = new Hotbar3BarConfig(); ApplyDefaults(c, 3); return c; }
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 4", 0)]
public class Hotbar4BarConfig : HotbarBarConfig
{
public Hotbar4BarConfig() => HotbarIndex = 4;
public new static Hotbar4BarConfig DefaultConfig() { var c = new Hotbar4BarConfig(); ApplyDefaults(c, 4); return c; }
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 5", 0)]
public class Hotbar5BarConfig : HotbarBarConfig
{
public Hotbar5BarConfig() => HotbarIndex = 5;
public new static Hotbar5BarConfig DefaultConfig() { var c = new Hotbar5BarConfig(); ApplyDefaults(c, 5); return c; }
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 6", 0)]
public class Hotbar6BarConfig : HotbarBarConfig
{
public Hotbar6BarConfig() => HotbarIndex = 6;
public new static Hotbar6BarConfig DefaultConfig() { var c = new Hotbar6BarConfig(); ApplyDefaults(c, 6); return c; }
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 7", 0)]
public class Hotbar7BarConfig : HotbarBarConfig
{
public Hotbar7BarConfig() => HotbarIndex = 7;
public new static Hotbar7BarConfig DefaultConfig() { var c = new Hotbar7BarConfig(); ApplyDefaults(c, 7); return c; }
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 8", 0)]
public class Hotbar8BarConfig : HotbarBarConfig
{
public Hotbar8BarConfig() => HotbarIndex = 8;
public new static Hotbar8BarConfig DefaultConfig() { var c = new Hotbar8BarConfig(); ApplyDefaults(c, 8); return c; }
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 9", 0)]
public class Hotbar9BarConfig : HotbarBarConfig
{
public Hotbar9BarConfig() => HotbarIndex = 9;
public new static Hotbar9BarConfig DefaultConfig() { var c = new Hotbar9BarConfig(); ApplyDefaults(c, 9); return c; }
}
[Exportable(false)]
[Section("Hotbars", true)]
[SubSection("Hotbar 10", 0)]
public class Hotbar10BarConfig : HotbarBarConfig
{
public Hotbar10BarConfig() => HotbarIndex = 10;
public new static Hotbar10BarConfig DefaultConfig() { var c = new Hotbar10BarConfig(); ApplyDefaults(c, 10); return c; }
}
}
@@ -0,0 +1,71 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HSUI.Interface.GeneralElements
{
[Disableable(false)]
[Exportable(false)]
[Section("Visibility")]
[SubSection("Hotbars", 0)]
public class HotbarsVisibilityConfig : PluginConfigObject
{
public new static HotbarsVisibilityConfig DefaultConfig() { return new HotbarsVisibilityConfig(); }
[NestedConfig("Hotbar 1", 50)]
public VisibilityConfig HotbarConfig1 = new VisibilityConfig();
[NestedConfig("Hotbar 2", 51)]
public VisibilityConfig HotbarConfig2 = new VisibilityConfig();
[NestedConfig("Hotbar 3", 52)]
public VisibilityConfig HotbarConfig3 = new VisibilityConfig();
[NestedConfig("Hotbar 4", 53)]
public VisibilityConfig HotbarConfig4 = new VisibilityConfig();
[NestedConfig("Hotbar 5", 54)]
public VisibilityConfig HotbarConfig5 = new VisibilityConfig();
[NestedConfig("Hotbar 6", 55)]
public VisibilityConfig HotbarConfig6 = new VisibilityConfig();
[NestedConfig("Hotbar 7", 56)]
public VisibilityConfig HotbarConfig7 = new VisibilityConfig();
[NestedConfig("Hotbar 8", 57)]
public VisibilityConfig HotbarConfig8 = new VisibilityConfig();
[NestedConfig("Hotbar 9", 58)]
public VisibilityConfig HotbarConfig9 = new VisibilityConfig();
[NestedConfig("Hotbar 10", 59)]
public VisibilityConfig HotbarConfig10 = new VisibilityConfig();
[NestedConfig("Cross Hotbar", 60)]
public VisibilityConfig HotbarConfigCross = new VisibilityConfig();
private List<VisibilityConfig> _configs;
public List<VisibilityConfig> GetHotbarConfigs() => _configs;
public HotbarsVisibilityConfig()
{
_configs = new List<VisibilityConfig>() {
HotbarConfig1,
HotbarConfig2,
HotbarConfig3,
HotbarConfig4,
HotbarConfig5,
HotbarConfig6,
HotbarConfig7,
HotbarConfig8,
HotbarConfig9,
HotbarConfig10
};
}
}
}
+172
View File
@@ -0,0 +1,172 @@
using Dalamud.Game.ClientState.Objects.Types;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Party;
using System;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
public class IconConfig : AnchorablePluginConfigObject
{
[Anchor("Frame Anchor")]
[Order(16)]
public DrawAnchor FrameAnchor = DrawAnchor.Center;
// don't remove (used by json converter)
public IconConfig()
{
Strata = StrataLevel.MID_HIGH;
}
public IconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
{
Position = position;
Size = size;
Anchor = anchor;
FrameAnchor = frameAnchor;
Strata = StrataLevel.MID_HIGH;
}
}
public class IconWithLabelConfig : IconConfig
{
[NestedConfig("Label", 20)]
public NumericLabelConfig NumericLabel = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
public IconWithLabelConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
}
}
public class RoleJobIconConfig : IconConfig
{
public RoleJobIconConfig() : base() { }
public RoleJobIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
}
[Combo("Style", "Style 1", "Style 2", "Style 3", spacing = true)]
[Order(25)]
public int Style = 0;
[Checkbox("Use Role Icons", spacing = true)]
[Order(30)]
public bool UseRoleIcons = false;
[Checkbox("Use Specific DPS Role Icons")]
[Order(35, collapseWith = nameof(UseRoleIcons))]
public bool UseSpecificDPSRoleIcons = false;
}
public class SignIconConfig : IconConfig
{
public SignIconConfig() : base() { }
public SignIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
}
[Checkbox("Preview")]
[Order(35)]
public bool Preview = false;
public uint? IconID(IGameObject? actor)
{
if (Preview)
{
return 61231;
}
return Utils.SignIconIDForActor(actor);
}
}
public class NameplateIconConfig : IconConfig
{
public NameplateIconConfig() : base() { }
public NameplateIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
}
[Combo("Nameplate Label Anchor", new string[] { "Name", "Title", "Highest", "Lowest" }, spacing = true)]
[Order(17)]
public NameplateLabelAnchor NameplateLabelAnchor = NameplateLabelAnchor.Name;
[Checkbox("Prioritize Health Bar as Anchor when visible", help = "When enabled, the icon will anchor to the Health Bar if it's visible.\nIf the Health Bar disappears, it will anchor back to the desired label.")]
[Order(18)]
public bool PrioritizeHealthBarAnchor = false;
}
public class NameplatePlayerIconConfig : NameplateIconConfig
{
public NameplatePlayerIconConfig() : base() { }
public NameplatePlayerIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
}
[Checkbox("Only show disconnected icon", spacing = true)]
[Order(19)]
public bool OnlyShowDisconnected = false;
public bool ShouldDrawIcon(int iconId)
{
if (!OnlyShowDisconnected) { return true; }
return (iconId >= 61503 && iconId <= 61505) ||
(iconId >= 61553 && iconId <= 61555);
}
}
public class NameplateRoleJobIconConfig : RoleJobIconConfig
{
public NameplateRoleJobIconConfig() : base() { }
public NameplateRoleJobIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
}
[Combo("Nameplate Label Anchor", new string[] { "Name", "Title", "Highest", "Lowest" }, spacing = true)]
[Order(17)]
public NameplateLabelAnchor NameplateLabelAnchor = NameplateLabelAnchor.Name;
[Checkbox("Prioritize Health Bar as Anchor when visible", help = "When enabled, the icon will anchor to the Health Bar if it's visible.\nIf the Health Bar disappears, it will anchor back to the desired label.")]
[Order(18)]
public bool PrioritizeHealthBarAnchor = false;
}
public class PartyFramesIconsConverter : PluginConfigObjectConverter
{
public PartyFramesIconsConverter()
{
SameTypeFieldConverter<DrawAnchor> converter = new SameTypeFieldConverter<DrawAnchor>("FrameAnchor", DrawAnchor.Center);
FieldConvertersMap.Add("HealthBarAnchor", converter);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(PartyFramesRoleIconConfig) ||
objectType == typeof(PartyFramesLeaderIconConfig);
}
}
public enum NameplateLabelAnchor
{
Name = 0,
Title = 1,
Highest = 2,
Lowest = 3
}
}
+227
View File
@@ -0,0 +1,227 @@
using Dalamud.Interface;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using Newtonsoft.Json;
using System;
using System.Globalization;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[Exportable(false)]
public class EditableLabelConfig : LabelConfig
{
[InputText("Text")]
[Order(10)]
public string Text;
public EditableLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
: base(position, text, frameAnchor, textAnchor)
{
Text = text;
}
public override string GetText() => Text;
public override void SetText(string text)
{
Text = text;
}
}
[Exportable(false)]
public class EditableNonFormattableLabelConfig : LabelConfig
{
[InputText("Text", formattable = false)]
[Order(10)]
public string Text;
public EditableNonFormattableLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
: base(position, text, frameAnchor, textAnchor)
{
Text = text;
}
public override string GetText() => Text;
public override void SetText(string text)
{
Text = text;
}
}
[Exportable(false)]
public class NumericLabelConfig : LabelConfig
{
[Combo("Number Format", "No Decimals (i.e. \"12\")", "One Decimal (i.e. \"12.3\")", "Two Decimals (i.e. \"12.34\")")]
[Order(10)]
public int NumberFormat;
[Combo("Rounding Mode", "Truncate", "Floor", "Ceil", "Round")]
[Order(15)]
public int NumberFunction;
[Checkbox("Hide Text When Zero")]
[Order(65)]
public bool HideIfZero = false;
public NumericLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
: base(position, text, frameAnchor, textAnchor)
{
}
public void SetValue(float value)
{
if (value == 0)
{
_text = HideIfZero ? string.Empty : "0";
return;
}
int aux = (int)Math.Pow(10, NumberFormat);
double textValue = value * aux;
textValue = NumberFunction switch
{
0 => Math.Truncate(textValue),
1 => Math.Floor(textValue),
2 => Math.Ceiling(textValue),
3 => Math.Round(textValue),
var _ => Math.Truncate(textValue)
};
double v = textValue / aux;
_text = v.ToString($"F{NumberFormat}", ConfigurationManager.Instance.ActiveCultreInfo);
}
public override NumericLabelConfig Clone(int index) =>
new NumericLabelConfig(Position, _text, FrameAnchor, TextAnchor)
{
Color = Color,
OutlineColor = OutlineColor,
ShadowConfig = ShadowConfig,
ShowOutline = ShowOutline,
FontID = FontID,
UseJobColor = UseJobColor,
Enabled = Enabled,
HideIfZero = HideIfZero,
ID = ID + "_{index}"
};
}
[DisableParentSettings("FontID")]
[Exportable(false)]
public class IconLabelConfig : LabelConfig
{
[DragFloat("Scale", min = 1, max = 5, velocity = 0.05f)]
[Order(11)]
public float FontScale = 1;
public FontAwesomeIcon IconId;
public IconLabelConfig(Vector2 position, FontAwesomeIcon iconId, DrawAnchor frameAnchor, DrawAnchor textAnchor) : base(position, "", frameAnchor, textAnchor)
{
IconId = iconId;
}
public override string GetText() => IconId.ToIconString();
public override float GetFontScale() => FontScale;
}
[DisableParentSettings("FontID")]
[Exportable(false)]
public class DefaultFontLabelConfig : LabelConfig
{
[DragFloat("Scale", min = 1, max = 5, velocity = 0.05f)]
[Order(11)]
public float FontScale = 1;
public DefaultFontLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
: base(position, text, frameAnchor, textAnchor)
{
}
public override bool UseSystemFont() => true;
public override float GetFontScale() => FontScale;
}
[Exportable(false)]
public class LabelConfig : MovablePluginConfigObject
{
[JsonIgnore] protected string _text;
[Font]
[Order(15)]
public string? FontID = null;
[Anchor("Frame Anchor")]
[Order(20)]
public DrawAnchor FrameAnchor = DrawAnchor.Center;
[Anchor("Text Anchor")]
[Order(25)]
public DrawAnchor TextAnchor = DrawAnchor.TopLeft;
[ColorEdit4("Color ##Text")]
[Order(30)]
public PluginConfigColor Color = new PluginConfigColor(Vector4.One);
[Checkbox("Outline")]
[Order(35)]
public bool ShowOutline = true;
[ColorEdit4("Color ##Outline")]
[Order(40, collapseWith = nameof(ShowOutline))]
public PluginConfigColor OutlineColor = new PluginConfigColor(Vector4.UnitW);
[NestedConfig("Shadow", 45)]
public ShadowConfig ShadowConfig = new ShadowConfig() { Enabled = false };
[Checkbox("Use Job Color", spacing = true)]
[Order(60)]
public bool UseJobColor = false;
[Checkbox("Use Role Color")]
[Order(65)]
public bool UseRoleColor = false;
public LabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
{
Position = position;
_text = text;
FrameAnchor = frameAnchor;
TextAnchor = textAnchor;
Position = position;
Strata = StrataLevel.HIGHEST;
}
public virtual string GetText() => _text;
public virtual void SetText(string text)
{
_text = text;
}
public virtual PluginConfigColor GetColor() => Color;
public virtual PluginConfigColor GetOutlineColor() => OutlineColor;
public virtual bool UseSystemFont() => false;
public virtual float GetFontScale() => 1;
public virtual LabelConfig Clone(int index) =>
new LabelConfig(Position, _text, FrameAnchor, TextAnchor)
{
Color = Color,
OutlineColor = OutlineColor,
ShadowConfig = ShadowConfig,
ShowOutline = ShowOutline,
FontID = FontID,
UseJobColor = UseJobColor,
Enabled = Enabled,
ID = ID + "_{index}"
};
}
}
+258
View File
@@ -0,0 +1,258 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface;
using HSUI.Config;
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;
using Lumina.Excel.Sheets;
using System;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
public class LabelHud : HudElement
{
private LabelConfig Config => (LabelConfig)_config;
public LabelHud(LabelConfig config) : base(config)
{
}
protected override void CreateDrawActions(Vector2 origin)
{
// unused
}
public override void Draw(Vector2 origin)
{
Draw(origin);
}
public virtual void Draw(
Vector2 origin,
Vector2? parentSize = null,
IGameObject? actor = null,
string? actorName = null,
uint? actorCurrentHp = null,
uint? actorMaxHp = null,
bool? isPlayerName = null,
string? title = null)
{
if (!Config.Enabled || Config.GetText() == null)
{
return;
}
string? text = actor == null && actorName == null && actorCurrentHp == null && actorMaxHp == null && title == null ?
Config.GetText() :
TextTagsHelper.FormattedText(Config.GetText(), actor, actorName, actorCurrentHp, actorMaxHp, isPlayerName, title);
DrawLabel(text, origin, parentSize ?? Vector2.Zero, actor);
}
protected virtual void DrawLabel(string text, Vector2 parentPos, Vector2 parentSize, IGameObject? actor = null)
{
Vector2 size;
Vector2 pos;
if (Config.UseSystemFont())
{
ImGui.PushFont(UiBuilder.DefaultFont);
size = ImGui.CalcTextSize(text) * Config.GetFontScale();
pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(parentPos + Config.Position, -parentSize, Config.FrameAnchor), size, Config.TextAnchor);
ImGui.PopFont();
}
else
{
using (FontsManager.Instance.PushFont(Config.FontID))
{
size = ImGui.CalcTextSize(text) * Config.GetFontScale();
pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(parentPos + Config.Position, -parentSize, Config.FrameAnchor), size, Config.TextAnchor);
}
}
DrawLabel(text, pos, size, Color(actor));
}
public void DrawLabel(string text, Vector2 pos, Vector2 size, PluginConfigColor color, float? alpha = null)
{
if (!Config.Enabled) { return; }
PluginConfigColor fillColor = color;
PluginConfigColor shadowColor = Config.ShadowConfig.Color;
PluginConfigColor outlineColor = Config.GetOutlineColor();
if (alpha.HasValue)
{
fillColor = fillColor.WithAlpha(alpha.Value);
shadowColor = shadowColor.WithAlpha(alpha.Value);
outlineColor = outlineColor.WithAlpha(alpha.Value);
}
Action<ImDrawListPtr> action = (ImDrawListPtr drawList) =>
{
if (Config.ShadowConfig.Enabled)
{
DrawHelper.DrawShadowText(text, pos, fillColor.Base, shadowColor.Base, drawList, Config.ShadowConfig.Offset, Config.ShadowConfig.Thickness);
}
if (Config.ShowOutline)
{
DrawHelper.DrawOutlinedText(text, pos, fillColor.Base, outlineColor.Base, drawList);
}
if (!Config.ShowOutline && !Config.ShadowConfig.Enabled)
{
drawList.AddText(pos, fillColor.Base, text);
}
};
DrawHelper.DrawInWindow(ID, pos, size, false, (drawList) =>
{
if (Config.UseSystemFont())
{
ImGui.SetWindowFontScale(Config.GetFontScale());
ImGui.PushFont(UiBuilder.DefaultFont);
action(drawList);
ImGui.PopFont();
ImGui.SetWindowFontScale(1);
}
else
{
using (FontsManager.Instance.PushFont(Config.FontID))
{
action(drawList);
}
}
});
}
public virtual PluginConfigColor Color(IGameObject? actor = null)
{
switch (Config.UseJobColor)
{
case true when (actor is ICharacter || actor is IBattleNpc battleNpc && battleNpc.ClassJob.RowId > 0):
return ColorUtils.ColorForActor(actor);
case true when actor is not ICharacter:
return GlobalColors.Instance.NPCFriendlyColor;
}
switch (Config.UseRoleColor)
{
case true when (actor is ICharacter || actor is IBattleNpc battleNpc && battleNpc.ClassJob.RowId > 0):
{
ICharacter? character = actor as ICharacter;
return character != null && character.ClassJob.RowId > 0 ?
GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId) :
ColorUtils.ColorForActor(character);
}
case true when actor is not ICharacter:
return GlobalColors.Instance.NPCFriendlyColor;
default:
return Config.GetColor();
}
}
public virtual (string, Vector2, Vector2, PluginConfigColor) PreCalculate(
Vector2 origin,
Vector2? parentSize = null,
IGameObject? actor = null,
string? actorName = null,
uint? actorCurrentHp = null,
uint? actorMaxHp = null,
bool? isPlayerName = null,
string? title = null)
{
if (!Config.Enabled || Config.GetText() == null)
{
return ("", Vector2.Zero, Vector2.Zero, Color(null));
}
string? text = actor == null && actorName == null && actorCurrentHp == null && actorMaxHp == null && title == null ?
Config.GetText() :
TextTagsHelper.FormattedText(Config.GetText(), actor, actorName, actorCurrentHp, actorMaxHp, isPlayerName, title);
Vector2 pSize = parentSize ?? Vector2.Zero;
Vector2 size;
Vector2 pos;
if (Config.UseSystemFont())
{
ImGui.PushFont(UiBuilder.DefaultFont);
size = ImGui.CalcTextSize(text) * Config.GetFontScale();
pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(origin + Config.Position, -pSize, Config.FrameAnchor), size, Config.TextAnchor);
ImGui.PopFont();
}
else
{
using (FontsManager.Instance.PushFont(Config.FontID))
{
size = ImGui.CalcTextSize(text) * Config.GetFontScale();
pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(origin + Config.Position, -pSize, Config.FrameAnchor), size, Config.TextAnchor);
}
}
return (text, pos, size, Color(actor));
}
}
public class IconLabelHud : LabelHud
{
private IconLabelConfig Config => (IconLabelConfig)_config;
public IconLabelHud(IconLabelConfig config) : base(config)
{
}
public override void Draw(Vector2 origin,
Vector2? parentSize = null,
IGameObject? actor = null,
string? actorName = null,
uint? actorCurrentHp = null,
uint? actorMaxHp = null,
bool? isPlayerName = null,
string? title = null)
{
string? text = Config.GetText();
if (!Config.Enabled || text == null)
{
return;
}
DrawLabel(text, origin, parentSize ?? Vector2.Zero, actor);
}
protected override void DrawLabel(string text, Vector2 parentPos, Vector2 parentSize, IGameObject? actor = null)
{
ImGui.PushFont(UiBuilder.IconFont);
Vector2 size = ImGui.CalcTextSize(text) * Config.GetFontScale();
Vector2 pos = Utils.GetAnchoredPosition(Utils.GetAnchoredPosition(parentPos + Config.Position, -parentSize, Config.FrameAnchor), size, Config.TextAnchor);
ImGui.PopFont();
DrawHelper.DrawInWindow(ID, pos, size, false, (drawList) =>
{
ImGui.SetWindowFontScale(Config.GetFontScale());
ImGui.PushFont(UiBuilder.IconFont);
PluginConfigColor? color = Color(actor);
if (Config.ShadowConfig.Enabled)
{
DrawHelper.DrawShadowText(text, pos, color.Base, Config.ShadowConfig.Color.Base, drawList, Config.ShadowConfig.Offset, Config.ShadowConfig.Thickness);
}
if (Config.ShowOutline)
{
DrawHelper.DrawOutlinedText(text, pos, color.Base, Config.OutlineColor.Base, drawList);
}
if (!Config.ShowOutline && !Config.ShadowConfig.Enabled)
{
drawList.AddText(pos, color.Base, text);
}
ImGui.PopFont();
ImGui.SetWindowFontScale(1);
});
}
}
}
@@ -0,0 +1,35 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Interface.Bars;
using Dalamud.Bindings.ImGui;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[Section("Other Elements")]
[SubSection("Limit Break", 0)]
public class LimitBreakConfig : ChunkedProgressBarConfig
{
[NestedConfig("Visibility", 70)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public LimitBreakConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor)
{
}
public new static LimitBreakConfig DefaultConfig()
{
var config = new LimitBreakConfig(
new Vector2(0, -ImGui.GetMainViewport().Size.Y * 0.4f),
new Vector2(500, 10),
new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 0f / 255f, 100f / 100f)));
config.HideWhenInactive = true;
config.UsePartialFillColor = true;
config.PartialFillColor = new PluginConfigColor(new Vector4(0f / 255f, 181f / 255f, 255f / 255f, 100f / 100f));
return config;
}
}
}
@@ -0,0 +1,67 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI;
namespace HSUI.Interface.GeneralElements
{
public class LimitBreakHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
{
private LimitBreakConfig Config => (LimitBreakConfig)_config;
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
public IGameObject? Actor { get; set; } = null;
public LimitBreakHud(LimitBreakConfig config, string displayName) : base(config, displayName) { }
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
}
public override unsafe void DrawChildren(Vector2 origin)
{
Config.Label.SetText("");
if (!Config.Enabled)
{
return;
}
LimitBreakController* lbController = LimitBreakController.Instance();
AddonHWDAetherGauge* caGauge = (AddonHWDAetherGauge*) Plugin.GameGui.GetAddonByName("HWDAetherGauge", 1).Address;
int currentLimitBreak = lbController->CurrentUnits;
int maxLimitBreak = lbController->BarUnits * lbController->BarCount;
int limitBreakChunks = lbController->BarCount;
if (caGauge != null)
{
currentLimitBreak = caGauge->MaxGaugeValue;
maxLimitBreak = 1000;
limitBreakChunks = 5;
}
int valuePerChunk = limitBreakChunks == 0 ? 0 : maxLimitBreak / limitBreakChunks;
int currentChunksFilled = valuePerChunk == 0 ? 0 : currentLimitBreak / valuePerChunk;
if (Config.HideWhenInactive && limitBreakChunks == 0)
{
return;
}
Config.Label.SetValue(currentChunksFilled);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config, limitBreakChunks, currentLimitBreak, maxLimitBreak);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.StrataLevel));
}
}
}
}
@@ -0,0 +1,75 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Interface.Bars;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[DisableParentSettings("Position")]
[Section("Other Elements")]
[SubSection("MP Ticker", 0)]
public class MPTickerConfig : MovablePluginConfigObject
{
[Checkbox("Hide on Full MP", spacing = false)]
[Order(15)]
public bool HideOnFullMP = true;
[Checkbox("Enable Only for BLM")]
[Order(20)]
public bool EnableOnlyForBLM = false;
[Checkbox("Show Only During Umbral Ice")]
[Order(25, collapseWith = nameof(EnableOnlyForBLM))]
public bool ShowOnlyDuringUmbralIce = true;
[NestedConfig("MP Ticker Bar", 30)]
public MPTickerBarConfig Bar = new MPTickerBarConfig(
Vector2.Zero,
new Vector2(254, 8),
new PluginConfigColor(new(240f / 255f, 92f / 255f, 232f / 255f, 100f / 100f))
);
[NestedConfig("Visibility", 70)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public new static MPTickerConfig DefaultConfig()
{
var config = new MPTickerConfig();
config.Enabled = false;
config.Bar.Position = new Vector2(0, HUDConstants.BaseHUDOffsetY + 27);
return config;
}
}
[Disableable(false)]
[DisableParentSettings("HideWhenInactive")]
[Exportable(false)]
public class MPTickerBarConfig : BarConfig
{
[NestedConfig("Fire III Threshold (BLM only)", 50, separator = false, spacing = true)]
public MPTickerFire3ThresholdConfig Fire3Threshold = new MPTickerFire3ThresholdConfig();
public MPTickerBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
[Exportable(false)]
[DisableParentSettings("Value")]
public class MPTickerFire3ThresholdConfig : ThresholdConfig
{
[DragFloat("Estimated Fire III Cast Time", min = 0f, max = 10)]
[Order(11)]
public float Fire3CastTime = 1.5f;
public MPTickerFire3ThresholdConfig()
{
Enabled = false;
ThresholdType = ThresholdType.Above;
ShowMarker = true;
MarkerColor = new(new Vector4(255f / 255f, 136f / 255f, 0 / 255f, 90f / 100f));
}
}
}
+110
View File
@@ -0,0 +1,110 @@
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Types;
using HSUI.Interface.Bars;
using Dalamud.Game.ClientState.Objects.SubKinds;
using System.Linq;
using Dalamud.Game.ClientState.JobGauge.Types;
namespace HSUI.Interface.GeneralElements
{
public class MPTickerHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
{
private MPTickerConfig Config => (MPTickerConfig)_config;
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
private MPTickHelper _mpTickHelper = null!;
public IGameObject? Actor { get; set; } = null;
public MPTickerHud(MPTickerConfig config, string displayName) : base(config, displayName) { }
protected override void InternalDispose()
{
_mpTickHelper?.Dispose();
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>() { Config.Position + Config.Bar.Position },
new List<Vector2>() { Config.Bar.Size });
}
public override void DrawChildren(Vector2 origin)
{
if (!Config.Enabled || Actor == null || Actor is not IPlayerCharacter player)
{
return;
}
// full mp
if (Config.HideOnFullMP && player.CurrentMp >= player.MaxMp)
{
return;
}
// BLM specific settings
if (Config.EnableOnlyForBLM)
{
if (player.ClassJob.RowId != JobIDs.BLM)
{
return;
}
else
{
var gauge = Plugin.JobGauges.Get<BLMGauge>();
if (Config.ShowOnlyDuringUmbralIce && !gauge.InUmbralIce)
{
return;
}
}
}
_mpTickHelper ??= new MPTickHelper();
var now = ImGui.GetTime();
var scale = (float)((now - _mpTickHelper.LastTick) / MPTickHelper.ServerTickRate);
if (scale <= 0)
{
return;
}
if (scale > 1)
{
scale = 1;
}
MPTickerFire3ThresholdConfig? thresholdConfig = GetFire3ThresholdConfig();
BarHud bar = BarUtilities.GetProgressBar(Config.Bar, thresholdConfig, null, scale, 1, 0, fillColor: Config.Bar.FillColor);
AddDrawActions(bar.GetDrawActions(origin + Config.Position, _config.StrataLevel));
}
private MPTickerFire3ThresholdConfig? GetFire3ThresholdConfig()
{
if (Actor is not IPlayerCharacter player || player.ClassJob.RowId != JobIDs.BLM)
{
return null;
}
MPTickerFire3ThresholdConfig config = Config.Bar.Fire3Threshold;
if (!config.Enabled)
{
return null;
}
bool leyLinesActive = Utils.StatusListForBattleChara(player).Any(e => e.StatusId == 738);
float castTime = config.Fire3CastTime * (leyLinesActive ? 0.85f : 1f);
// tick rate is 3s
// adding 0.3f as "safety net"
config.Value = (3 - castTime + 0.3f) / 3;
return config;
}
}
}
@@ -0,0 +1,139 @@
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Interface.Bars;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[DisableParentSettings("HideWhenInactive", "Label")]
[Section("Mana Bars")]
[SubSection("Player", 0)]
public class PlayerPrimaryResourceConfig : UnitFramePrimaryResourceConfig
{
public PlayerPrimaryResourceConfig(Vector2 position, Vector2 size)
: base(position, size)
{
}
public new static PlayerPrimaryResourceConfig DefaultConfig()
{
var size = new Vector2(HUDConstants.DefaultBigUnitFrameSize.X, 10);
var pos = new Vector2(0, 0);
var config = new PlayerPrimaryResourceConfig(pos, size);
config.Anchor = DrawAnchor.Bottom;
return config;
}
}
[DisableParentSettings("HideWhenInactive", "Label")]
[Section("Mana Bars")]
[SubSection("Target", 0)]
public class TargetPrimaryResourceConfig : UnitFramePrimaryResourceConfig
{
public TargetPrimaryResourceConfig(Vector2 position, Vector2 size)
: base(position, size)
{
}
public new static TargetPrimaryResourceConfig DefaultConfig()
{
var size = new Vector2(HUDConstants.DefaultBigUnitFrameSize.X, 10);
var pos = new Vector2(0, 0);
var config = new TargetPrimaryResourceConfig(pos, size);
config.Anchor = DrawAnchor.Bottom;
return config;
}
}
[DisableParentSettings("HideWhenInactive", "Label")]
[Section("Mana Bars")]
[SubSection("Target of Target", 0)]
public class TargetOfTargetPrimaryResourceConfig : UnitFramePrimaryResourceConfig
{
public TargetOfTargetPrimaryResourceConfig(Vector2 position, Vector2 size)
: base(position, size)
{
}
public new static TargetOfTargetPrimaryResourceConfig DefaultConfig()
{
var size = new Vector2(HUDConstants.DefaultSmallUnitFrameSize.X, 10);
var pos = new Vector2(0, 0);
var config = new TargetOfTargetPrimaryResourceConfig(pos, size);
config.Anchor = DrawAnchor.Bottom;
return config;
}
}
[DisableParentSettings("HideWhenInactive", "Label")]
[Section("Mana Bars")]
[SubSection("Focus Target", 0)]
public class FocusTargetPrimaryResourceConfig : UnitFramePrimaryResourceConfig
{
public FocusTargetPrimaryResourceConfig(Vector2 position, Vector2 size)
: base(position, size)
{
}
public new static FocusTargetPrimaryResourceConfig DefaultConfig()
{
var size = new Vector2(HUDConstants.DefaultSmallUnitFrameSize.X, 10);
var pos = new Vector2(0, 0);
var config = new FocusTargetPrimaryResourceConfig(pos, size);
config.Anchor = DrawAnchor.Bottom;
return config;
}
}
public abstract class UnitFramePrimaryResourceConfig : PrimaryResourceConfig
{
[Checkbox("Anchor to Unit Frame")]
[Order(16)]
public bool AnchorToUnitFrame = true;
[Anchor("Unit Frame Anchor")]
[Order(17, collapseWith = nameof(AnchorToUnitFrame))]
public DrawAnchor UnitFrameAnchor = DrawAnchor.Bottom;
[NestedConfig("Visibility", 1200)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public UnitFramePrimaryResourceConfig(Vector2 position, Vector2 size)
: base(position, size)
{
}
}
public abstract class PrimaryResourceConfig : ProgressBarConfig
{
[Checkbox("Use Job Color", spacing = true)]
[Order(19)]
public bool UseJobColor = false;
[Checkbox("Hide When Full", spacing = true)]
[Order(41)]
public bool HidePrimaryResourceWhenFull = false;
[NestedConfig("Label", 1000, separator = false, spacing = true)]
public EditableLabelConfig ValueLabel = new EditableLabelConfig(Vector2.Zero, "[mana:current]", DrawAnchor.Center, DrawAnchor.Center);
public PrimaryResourceConfig(Vector2 position, Vector2 size)
: base(position, size, new(new(0 / 255f, 162f / 255f, 252f / 255f, 100f / 100f)))
{
Strata = StrataLevel.LOW;
}
}
}
@@ -0,0 +1,134 @@
using HSUI.Config;
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Types;
using System;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Enums;
using HSUI.Interface.Bars;
using HSUI.Interface.Party;
namespace HSUI.Interface.GeneralElements
{
public class PrimaryResourceHud : ParentAnchoredDraggableHudElement, IHudElementWithActor, IHudElementWithAnchorableParent, IHudElementWithVisibilityConfig
{
private PrimaryResourceConfig Config => (PrimaryResourceConfig)_config;
public VisibilityConfig? VisibilityConfig => Config is UnitFramePrimaryResourceConfig config ? config.VisibilityConfig : null;
public PrimaryResourceTypes ResourceType = PrimaryResourceTypes.MP;
private IGameObject? _actor;
public IGameObject? Actor
{
get => _actor;
set
{
if (value is IPlayerCharacter chara)
{
_actor = value;
JobRoles role = JobsHelper.RoleForJob(chara.ClassJob.RowId);
ResourceType = JobsHelper.PrimaryResourceTypesByRole[role];
}
else
{
_actor = null;
ResourceType = PrimaryResourceTypes.None;
}
}
}
public IPartyFramesMember? PartyMember;
protected override bool AnchorToParent => Config is UnitFramePrimaryResourceConfig config ? config.AnchorToUnitFrame : false;
protected override DrawAnchor ParentAnchor => Config is UnitFramePrimaryResourceConfig config ? config.UnitFrameAnchor : DrawAnchor.Center;
public PrimaryResourceHud(PrimaryResourceConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
}
public override void DrawChildren(Vector2 origin)
{
if (!Config.Enabled)
{
return;
}
if (PartyMember == null && (ResourceType == PrimaryResourceTypes.None || Actor == null || Actor is not IPlayerCharacter))
{
return;
}
ICharacter? chara = Actor != null ? (ICharacter)Actor : null;
uint current = chara == null ? PartyMember?.MP ?? 0 : 0;
uint max = chara == null ? PartyMember?.MaxMP ?? 0 : 0;
if (chara != null)
{
GetResources(ref current, ref max, chara);
}
if (Config.HidePrimaryResourceWhenFull && current == max)
{
return;
}
BarHud bar = BarUtilities.GetProgressBar(
Config,
Config.ThresholdConfig,
new LabelConfig[] { Config.ValueLabel },
current,
max,
0,
chara,
GetColor()
);
Vector2 pos = origin + ParentPos();
AddDrawActions(bar.GetDrawActions(pos, Config.StrataLevel));
}
private void GetResources(ref uint current, ref uint max, ICharacter actor)
{
switch (ResourceType)
{
case PrimaryResourceTypes.MP:
current = actor.CurrentMp;
max = actor.MaxMp;
break;
case PrimaryResourceTypes.CP:
current = actor.CurrentCp;
max = actor.MaxCp;
break;
case PrimaryResourceTypes.GP:
current = actor.CurrentGp;
max = actor.MaxGp;
break;
}
}
public virtual PluginConfigColor GetColor()
{
if (!Config.UseJobColor)
{
return Config.FillColor;
}
if (PartyMember != null)
{
return GlobalColors.Instance.SafeColorForJobId(PartyMember.JobId);
}
return ColorUtils.ColorForActor(Actor);
}
}
}
@@ -0,0 +1,33 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Interface.Bars;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[Section("Other Elements")]
[SubSection("Pull Timer", 0)]
public class PullTimerConfig : ProgressBarConfig
{
[Checkbox("Use Job Color")]
[Order(45)]
public bool UseJobColor = false;
public PullTimerConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor)
{
}
public new static PullTimerConfig DefaultConfig()
{
var config = new PullTimerConfig(
new Vector2(0, HUDConstants.BaseHUDOffsetY - 35),
new Vector2(254, 20),
new PluginConfigColor(new Vector4(233f / 255f, 4f / 255f, 4f / 255f, 100f / 100f)));
config.HideWhenInactive = true;
return config;
}
}
}
+53
View File
@@ -0,0 +1,53 @@
using Dalamud.Game.ClientState.Objects.Types;
using HSUI.Config;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using System.Collections.Generic;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
public class PullTimerHud : DraggableHudElement, IHudElementWithActor
{
private PullTimerConfig Config => (PullTimerConfig)_config;
public IGameObject? Actor { get; set; } = null;
public PullTimerHud(PullTimerConfig config, string displayName) : base(config, displayName) { }
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
}
public override void DrawChildren(Vector2 origin)
{
PullTimerState helper = PullTimerHelper.Instance.PullTimerState;
Config.Label.SetValue(helper.CountDownValue);
if (!helper.CountingDown)
{
Config.Label.SetText("");
}
if (!Config.Enabled || Actor is null)
{
return;
}
if (Config.HideWhenInactive && !helper.CountingDown)
{
return;
}
PluginConfigColor? fillColor = Config.UseJobColor ? ColorUtils.ColorForActor(Actor) : null;
BarHud bar = BarUtilities.GetProgressBar(Config,
helper.CountDownValue,
helper.CountDownMax, 0F, Actor, fillColor);
AddDrawActions(bar.GetDrawActions(origin, Config.StrataLevel));
}
}
}
+22
View File
@@ -0,0 +1,22 @@
using System.Numerics;
using HSUI.Config;
using HSUI.Config.Attributes;
namespace HSUI.Interface.GeneralElements
{
[Exportable(false)]
public class ShadowConfig : PluginConfigObject
{
[ColorEdit4("Color")]
[Order(5)]
public PluginConfigColor Color = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[DragInt("Thickness", min = 1, max = 20)]
[Order(10)]
public int Thickness = 1;
[DragInt("Offset", min = 0, max = 20)]
[Order(15)]
public int Offset = 2;
}
}
@@ -0,0 +1,401 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
[DisableParentSettings("HideWhenInactive", "HideHealthIfPossible", "RangeConfig", "EnemyRangeConfig")]
[Section("Unit Frames")]
[SubSection("Player", 0)]
public class PlayerUnitFrameConfig : UnitFrameConfig
{
[NestedConfig("Tank Stance Indicator", 122, spacing = true)]
public TankStanceIndicatorConfig TankStanceIndicatorConfig = new TankStanceIndicatorConfig();
public PlayerUnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
{
}
public new static PlayerUnitFrameConfig DefaultConfig()
{
var size = HUDConstants.DefaultBigUnitFrameSize;
var pos = new Vector2(-HUDConstants.UnitFramesOffsetX - size.X / 2f, HUDConstants.BaseHUDOffsetY);
var leftLabelConfig = new EditableLabelConfig(new Vector2(5, 0), "[name]", DrawAnchor.TopLeft, DrawAnchor.BottomLeft);
var rightLabelConfig = new EditableLabelConfig(new Vector2(-5, 0), "[health:current-short] | [health:percent]", DrawAnchor.TopRight, DrawAnchor.BottomRight);
var optionalLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
var config = new PlayerUnitFrameConfig(pos, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig);
return config;
}
}
public enum TankStanceCorner
{
TopLeft = 0,
TopRight,
BottomLeft,
BottomRight
}
[Exportable(false)]
public class TankStanceIndicatorConfig : PluginConfigObject
{
[Combo("Corner", "Top Left", "Top Right", "Bottom Left", "Bottom Right")]
[Order(5)]
public TankStanceCorner Corner = TankStanceCorner.BottomLeft;
[DragFloat2("Size", min = 1, max = 500)]
[Order(10)]
public Vector2 Size = new Vector2(HUDConstants.DefaultBigUnitFrameSize.Y - 20, HUDConstants.DefaultBigUnitFrameSize.Y - 20);
[DragInt("Thickness", min = 2, max = 20)]
[Order(15)]
public int Thickess = 4;
[ColorEdit4("Active Color")]
[Order(20)]
public PluginConfigColor ActiveColor = new PluginConfigColor(new Vector4(0f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
[ColorEdit4("Inactive Color")]
[Order(25)]
public PluginConfigColor InactiveColor = new PluginConfigColor(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
}
[DisableParentSettings("HideWhenInactive")]
[Section("Unit Frames")]
[SubSection("Target", 0)]
public class TargetUnitFrameConfig : UnitFrameConfig
{
public TargetUnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
{
}
public new static TargetUnitFrameConfig DefaultConfig()
{
var size = HUDConstants.DefaultBigUnitFrameSize;
var pos = new Vector2(HUDConstants.UnitFramesOffsetX + size.X / 2f, HUDConstants.BaseHUDOffsetY);
var leftLabelConfig = new EditableLabelConfig(new Vector2(5, 0), "[health:current-short] | [health:percent]", DrawAnchor.TopLeft, DrawAnchor.BottomLeft);
var rightLabelConfig = new EditableLabelConfig(new Vector2(-5, 0), "[name]", DrawAnchor.TopRight, DrawAnchor.BottomRight);
var optionalLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
return new TargetUnitFrameConfig(pos, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig);
}
}
[DisableParentSettings("HideWhenInactive")]
[Section("Unit Frames")]
[SubSection("Target of Target", 0)]
public class TargetOfTargetUnitFrameConfig : UnitFrameConfig
{
public TargetOfTargetUnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
{
}
public new static TargetOfTargetUnitFrameConfig DefaultConfig()
{
var size = HUDConstants.DefaultSmallUnitFrameSize;
var pos = new Vector2(
HUDConstants.UnitFramesOffsetX + HUDConstants.DefaultBigUnitFrameSize.X + 6 + size.X / 2f,
HUDConstants.BaseHUDOffsetY - 15
);
var leftLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "[name]", DrawAnchor.Top, DrawAnchor.Bottom);
var rightLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.TopLeft);
var optionalLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.BottomLeft);
return new TargetOfTargetUnitFrameConfig(pos, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig);
}
}
[DisableParentSettings("HideWhenInactive")]
[Section("Unit Frames")]
[SubSection("Focus Target", 0)]
public class FocusTargetUnitFrameConfig : UnitFrameConfig
{
public FocusTargetUnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
{
}
public new static FocusTargetUnitFrameConfig DefaultConfig()
{
var size = HUDConstants.DefaultSmallUnitFrameSize;
var pos = new Vector2(
-HUDConstants.UnitFramesOffsetX - HUDConstants.DefaultBigUnitFrameSize.X - 6 - size.X / 2f,
HUDConstants.BaseHUDOffsetY - 15
);
var leftLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "[name]", DrawAnchor.Top, DrawAnchor.Bottom);
var rightLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center);
var optionalLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Bottom, DrawAnchor.Bottom);
return new FocusTargetUnitFrameConfig(pos, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig);
}
}
[DisableParentSettings("HideWhenInactive")]
public class UnitFrameConfig : BarConfig
{
[Checkbox("Use Job Color", spacing = true)]
[Order(45)]
public bool UseJobColor = true;
[Checkbox("Use Role Color")]
[Order(46)]
public bool UseRoleColor = false;
[NestedConfig("Color Based On Health Value", 50, collapsingHeader = false)]
public ColorByHealthValueConfig ColorByHealth = new ColorByHealthValueConfig();
[Checkbox("Job Color As Background Color", spacing = true)]
[Order(50)]
public bool UseJobColorAsBackgroundColor = false;
[Checkbox("Role Color As Background Color")]
[Order(51)]
public bool UseRoleColorAsBackgroundColor = false;
[Checkbox("Missing Health Color")]
[Order(55)]
public bool UseMissingHealthBar = false;
[Checkbox("Job Color As Missing Health Color")]
[Order(56, collapseWith = nameof(UseMissingHealthBar))]
public bool UseJobColorAsMissingHealthColor = false;
[Checkbox("Role Color As Missing Health Color")]
[Order(57, collapseWith = nameof(UseMissingHealthBar))]
public bool UseRoleColorAsMissingHealthColor = false;
[ColorEdit4("Color" + "##MissingHealth")]
[Order(60, collapseWith = nameof(UseMissingHealthBar))]
public PluginConfigColor HealthMissingColor = new PluginConfigColor(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[Checkbox("Death Indicator Background Color", spacing = true)]
[Order(61)]
public bool UseDeathIndicatorBackgroundColor = false;
[ColorEdit4("Color" + "##DeathIndicator")]
[Order(62, collapseWith = nameof(UseDeathIndicatorBackgroundColor))]
public PluginConfigColor DeathIndicatorBackgroundColor = new PluginConfigColor(new Vector4(204f / 255f, 3f / 255f, 3f / 255f, 50f / 100f));
[Checkbox("Tank Invulnerability", spacing = true)]
[Order(95)]
public bool ShowTankInvulnerability = true;
[Checkbox("Tank Invulnerability Custom Color")]
[Order(100, collapseWith = nameof(ShowTankInvulnerability))]
public bool UseCustomInvulnerabilityColor = true;
[ColorEdit4("Tank Invulnerability Color ##TankInvulnerabilityCustom")]
[Order(105, collapseWith = nameof(UseCustomInvulnerabilityColor))]
public PluginConfigColor CustomInvulnerabilityColor = new PluginConfigColor(new Vector4(211f / 255f, 235f / 255f, 215f / 245f, 50f / 100f));
[Checkbox("Walking Dead Custom Color")]
[Order(110, collapseWith = nameof(ShowTankInvulnerability))]
public bool UseCustomWalkingDeadColor = true;
[ColorEdit4("Walking Dead Color ##TankWalkingDeadCustom")]
[Order(115, collapseWith = nameof(UseCustomWalkingDeadColor))]
public PluginConfigColor CustomWalkingDeadColor = new PluginConfigColor(new Vector4(158f / 255f, 158f / 255f, 158f / 255f, 50f / 100f));
[NestedConfig("Use Smooth Transitions", 120, collapsingHeader = false)]
public SmoothHealthConfig SmoothHealthConfig = new SmoothHealthConfig();
[Checkbox("Hide Health if Possible", spacing = true, help = "This will hide any label that has a health tag if the character doesn't have health (ie minions, friendly npcs, etc)")]
[Order(121)]
public bool HideHealthIfPossible = true;
[NestedConfig("Left Text", 125)]
public EditableLabelConfig LeftLabelConfig = null!;
[NestedConfig("Right Text", 130)]
public EditableLabelConfig RightLabelConfig = null!;
[NestedConfig("Optional Text", 131)]
public EditableLabelConfig OptionalLabelConfig = null!;
[NestedConfig("Role/Job Icon", 135)]
public RoleJobIconConfig RoleIconConfig = new RoleJobIconConfig(
new Vector2(5, 0),
new Vector2(30, 30),
DrawAnchor.Left,
DrawAnchor.Left
);
[NestedConfig("Sign Icon", 136)]
public SignIconConfig SignIconConfig = new SignIconConfig(
new Vector2(0, 0),
new Vector2(30, 30),
DrawAnchor.Center,
DrawAnchor.Top
);
[NestedConfig("Shields", 140)]
public ShieldConfig ShieldConfig = new ShieldConfig();
[NestedConfig("Change Friendly Alpha Based on Range", 145)]
public UnitFramesRangeConfig RangeConfig = new();
[NestedConfig("Change Enemy Alpha Based on Range", 146)]
public UnitFramesRangeConfig EnemyRangeConfig = new();
[NestedConfig("Custom Mouseover Area", 150)]
public MouseoverAreaConfig MouseoverAreaConfig = new MouseoverAreaConfig();
[NestedConfig("Visibility", 200)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public UnitFrameConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
: base(position, size, new PluginConfigColor(new(40f / 255f, 40f / 255f, 40f / 255f, 100f / 100f)))
{
Position = position;
Size = size;
LeftLabelConfig = leftLabelConfig;
RightLabelConfig = rightLabelConfig;
OptionalLabelConfig = optionalLabelConfig;
BackgroundColor = new PluginConfigColor(new(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
RoleIconConfig.Enabled = false;
SignIconConfig.Enabled = false;
ColorByHealth.Enabled = false;
MouseoverAreaConfig.Enabled = false;
}
public UnitFrameConfig() : base(Vector2.Zero, Vector2.Zero, PluginConfigColor.Empty) { } // don't remove
}
[Exportable(false)]
public class ShieldConfig : PluginConfigObject
{
[DragInt("Thickness")]
[Order(5)]
public int Height = 26; // Should be 'Size' instead of 'Height' but leaving as is to avoid breaking configs
[Checkbox("Thickness in Pixels")]
[Order(10)]
public bool HeightInPixels = false;
[Checkbox("Fill Health First")]
[Order(15)]
public bool FillHealthFirst = true;
[ColorEdit4("Color ##Shields")]
[Order(20)]
public PluginConfigColor Color = new PluginConfigColor(new Vector4(198f / 255f, 210f / 255f, 255f / 255f, 70f / 100f));
}
[Exportable(false)]
public class SmoothHealthConfig : PluginConfigObject
{
[DragFloat("Velocity", min = 1f, max = 100f)]
[Order(5)]
public float Velocity = 25f;
}
[Exportable(false)]
public class MouseoverAreaConfig : PluginConfigObject
{
[Checkbox("Preview")]
[Order(5)]
public bool Preview = false;
[Checkbox("Ignore Mouseover", help = "Enabling this will make it so this element is ignored by mouseover completely.\nThe area can still be defined for left and right clicks.")]
[Order(6)]
public bool Ignore = false;
[DragInt2("Top Left Offset", min = -500, max = 500)]
[Order(10)]
public Vector2 TopLeftOffset = Vector2.Zero;
[DragInt2("Bottom Right Offset", min = -500, max = 500)]
[Order(11)]
public Vector2 BottomRightOffset = Vector2.Zero;
public MouseoverAreaConfig()
{
Enabled = false;
}
public (Vector2, Vector2) GetArea(Vector2 pos, Vector2 size)
{
if (!Enabled) { return (pos, pos + size); }
Vector2 start = pos + TopLeftOffset;
Vector2 end = pos + size + BottomRightOffset;
return (start, end);
}
public BarHud? GetBar(Vector2 pos, Vector2 size, string id, DrawAnchor anchor = DrawAnchor.TopLeft)
{
if (!Enabled || !Preview) { return null; }
BarHud bar = new BarHud(
id,
true,
new(Vector4.One),
2
);
var barPos = Utils.GetAnchoredPosition(Vector2.Zero, size, anchor);
var (start, end) = GetArea(barPos + pos, size);
Rect background = new Rect(start, end - start, new(new(1, 1, 1, 0.5f)));
bar.SetBackground(background);
return bar;
}
}
[Exportable(false)]
public class UnitFramesRangeConfig : PluginConfigObject
{
[DragInt("Range (yalms)", min = 1, max = 500)]
[Order(5)]
public int Range = 30;
[DragFloat("Alpha", min = 1, max = 100)]
[Order(10)]
public float Alpha = 24;
[Checkbox("Use Additional Range Check")]
[Order(15)]
public bool UseAdditionalRangeCheck = false;
[DragInt("Additional Range (yalms)", min = 1, max = 500)]
[Order(20, collapseWith = nameof(UseAdditionalRangeCheck))]
public int AdditionalRange = 15;
[DragFloat("Additional Alpha", min = 1, max = 100)]
[Order(25, collapseWith = nameof(UseAdditionalRangeCheck))]
public float AdditionalAlpha = 60;
public float AlphaForDistance(int distance, float alpha = 100f)
{
if (!Enabled)
{
return 100f;
}
if (!UseAdditionalRangeCheck)
{
return distance > Range ? Alpha : alpha;
}
if (Range > AdditionalRange)
{
return distance > Range ? Alpha : (distance > AdditionalRange ? AdditionalAlpha : alpha);
}
return distance > AdditionalRange ? AdditionalAlpha : (distance > Range ? Alpha : alpha);
}
}
}
+528
View File
@@ -0,0 +1,528 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Statuses;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using BattleChara = Dalamud.Game.ClientState.Objects.Types.IBattleChara;
using BattleNpcSubKind = Dalamud.Game.ClientState.Objects.Enums.BattleNpcSubKind;
using Character = Dalamud.Game.ClientState.Objects.Types.ICharacter;
namespace HSUI.Interface.GeneralElements
{
public unsafe class UnitFrameHud(UnitFrameConfig config, string displayName)
: DraggableHudElement(config, displayName), IHudElementWithActor, IHudElementWithMouseOver, IHudElementWithPreview, IHudElementWithVisibilityConfig
{
public UnitFrameConfig Config => (UnitFrameConfig)_config;
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
private SmoothHPHelper _smoothHPHelper = new SmoothHPHelper();
public IGameObject? Actor { get; set; }
private bool _wasHovering = false;
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>() { Config.Position }, new List<Vector2>() { Config.Size });
}
public void StopPreview()
{
Config.MouseoverAreaConfig.Preview = false;
Config.SignIconConfig.Preview = false;
}
public void StopMouseover()
{
if (_wasHovering)
{
InputsHelper.Instance.ClearTarget();
_wasHovering = false;
}
}
public override void DrawChildren(Vector2 origin)
{
if (!Config.Enabled || Actor == null)
{
StopMouseover();
return;
}
DrawExtras(origin, Actor);
if (Actor is Character character)
{
DrawCharacter(origin, character);
}
else
{
DrawFriendlyNPC(origin, Actor);
}
// Check if mouse is hovering over the box properly
var startPos = Utils.GetAnchoredPosition(origin + Config.Position, Config.Size, Config.Anchor);
var (areaStart, areaEnd) = Config.MouseoverAreaConfig.GetArea(startPos, Config.Size);
bool isHovering = ImGui.IsMouseHoveringRect(areaStart, areaEnd);
bool ignoreMouseover = Config.MouseoverAreaConfig.Enabled && Config.MouseoverAreaConfig.Ignore;
if (isHovering && !DraggingEnabled)
{
_wasHovering = true;
InputsHelper.Instance.SetTarget(Actor, ignoreMouseover);
if (InputsHelper.Instance.LeftButtonClicked)
{
Plugin.TargetManager.Target = Actor;
}
else if (InputsHelper.Instance.RightButtonClicked)
{
AgentModule.Instance()->GetAgentHUD()->OpenContextMenuFromTarget((GameObject*)Actor.Address);
}
}
else if (_wasHovering)
{
InputsHelper.Instance.ClearTarget();
_wasHovering = false;
}
}
protected virtual void DrawExtras(Vector2 origin, IGameObject? actor)
{
// override
}
private void DrawCharacter(Vector2 pos, Character character)
{
uint currentHp = character.CurrentHp;
uint maxHp = character.MaxHp;
// fixes weird bug with npcs
if (maxHp == 1)
{
currentHp = 1;
}
else if (Config.SmoothHealthConfig.Enabled)
{
currentHp = _smoothHPHelper.GetNextHp((int)currentHp, (int)maxHp, Config.SmoothHealthConfig.Velocity);
}
PluginConfigColor fillColor = ColorUtils.ColorForCharacter(
character,
currentHp,
maxHp,
Config.UseJobColor,
Config.UseRoleColor,
Config.ColorByHealth
) ?? Config.FillColor;
Rect background = new Rect(Config.Position, Config.Size, BackgroundColor(character));
if (Config.RangeConfig.Enabled || Config.EnemyRangeConfig.Enabled)
{
fillColor = GetDistanceColor(character, fillColor);
background.Color = GetDistanceColor(character, background.Color);
}
Rect healthFill = BarUtilities.GetFillRect(Config.Position, Config.Size, Config.FillDirection, fillColor, currentHp, maxHp);
BarHud bar = new BarHud(Config, character);
bar.NeedsInputs = true;
bar.SetBackground(background);
bar.AddForegrounds(healthFill);
bar.AddLabels(GetLabels(maxHp));
if (Config.UseMissingHealthBar)
{
Vector2 healthMissingSize = Config.Size - BarUtilities.GetFillDirectionOffset(healthFill.Size, Config.FillDirection);
Vector2 healthMissingPos = Config.FillDirection.IsInverted()
? Config.Position
: Config.Position + BarUtilities.GetFillDirectionOffset(healthFill.Size, Config.FillDirection);
PluginConfigColor missingHealthColor = Config.UseJobColorAsMissingHealthColor && character is BattleChara
? GlobalColors.Instance.SafeColorForJobId(character!.ClassJob.RowId)
: Config.UseRoleColorAsMissingHealthColor && character is BattleChara
? GlobalColors.Instance.SafeRoleColorForJobId(character!.ClassJob.RowId)
: Config.HealthMissingColor;
if (Config.UseDeathIndicatorBackgroundColor && character is BattleChara { CurrentHp: <= 0 })
{
missingHealthColor = Config.DeathIndicatorBackgroundColor;
}
if (Config.UseCustomInvulnerabilityColor && character is BattleChara battleChara)
{
IStatus? tankInvuln = Utils.GetTankInvulnerabilityID(battleChara);
if (tankInvuln is not null)
{
missingHealthColor = Config.CustomInvulnerabilityColor;
}
}
if (Config.RangeConfig.Enabled || Config.EnemyRangeConfig.Enabled)
{
missingHealthColor = GetDistanceColor(character, missingHealthColor);
}
bar.AddForegrounds(new Rect(healthMissingPos, healthMissingSize, missingHealthColor));
}
// shield
BarUtilities.AddShield(bar, Config, Config.ShieldConfig, character, healthFill.Size);
// draw action
AddDrawActions(bar.GetDrawActions(pos, Config.StrataLevel));
// mouseover area
BarHud? mouseoverAreaBar = Config.MouseoverAreaConfig.GetBar(
Config.Position,
Config.Size,
Config.ID + "_mouseoverArea",
Config.Anchor
);
if (mouseoverAreaBar != null)
{
AddDrawActions(mouseoverAreaBar.GetDrawActions(pos, StrataLevel.HIGHEST));
}
// role/job icon
if (Config.RoleIconConfig.Enabled && character is IPlayerCharacter)
{
uint jobId = character.ClassJob.RowId;
uint iconId = Config.RoleIconConfig.UseRoleIcons ?
JobsHelper.RoleIconIDForJob(jobId, Config.RoleIconConfig.UseSpecificDPSRoleIcons) :
JobsHelper.IconIDForJob(jobId, (uint)Config.RoleIconConfig.Style);
if (iconId > 0)
{
var barPos = Utils.GetAnchoredPosition(pos, Config.Size, Config.Anchor);
var parentPos = Utils.GetAnchoredPosition(barPos + Config.Position, -Config.Size, Config.RoleIconConfig.FrameAnchor);
var iconPos = Utils.GetAnchoredPosition(parentPos + Config.RoleIconConfig.Position, Config.RoleIconConfig.Size, Config.RoleIconConfig.Anchor);
AddDrawAction(Config.RoleIconConfig.StrataLevel, () =>
{
DrawHelper.DrawInWindow(ID + "_jobIcon", iconPos, Config.RoleIconConfig.Size, false, (drawList) =>
{
DrawHelper.DrawIcon(iconId, iconPos, Config.RoleIconConfig.Size, false, drawList);
});
});
}
}
// sign icon
if (Config.SignIconConfig.Enabled)
{
uint? iconId = Config.SignIconConfig.IconID(character);
if (iconId.HasValue)
{
var barPos = Utils.GetAnchoredPosition(pos, Config.Size, Config.Anchor);
var parentPos = Utils.GetAnchoredPosition(barPos + Config.Position, -Config.Size, Config.SignIconConfig.FrameAnchor);
var iconPos = Utils.GetAnchoredPosition(parentPos + Config.SignIconConfig.Position, Config.SignIconConfig.Size, Config.SignIconConfig.Anchor);
AddDrawAction(Config.SignIconConfig.StrataLevel, () =>
{
DrawHelper.DrawInWindow(ID + "_signIcon", iconPos, Config.SignIconConfig.Size, false, (drawList) =>
{
DrawHelper.DrawIcon(iconId.Value, iconPos, Config.SignIconConfig.Size, false, drawList);
});
});
}
}
}
private LabelConfig[] GetLabels(uint maxHp)
{
List<LabelConfig> labels = new List<LabelConfig>();
if (Config.HideHealthIfPossible && maxHp <= 1)
{
if (!Utils.IsHealthLabel(Config.LeftLabelConfig))
{
labels.Add(Config.LeftLabelConfig);
}
if (!Utils.IsHealthLabel(Config.RightLabelConfig))
{
labels.Add(Config.RightLabelConfig);
}
if (!Utils.IsHealthLabel(Config.OptionalLabelConfig))
{
labels.Add(Config.OptionalLabelConfig);
}
}
else
{
labels.Add(Config.LeftLabelConfig);
labels.Add(Config.RightLabelConfig);
labels.Add(Config.OptionalLabelConfig);
}
return labels.ToArray();
}
private PluginConfigColor GetDistanceColor(Character? character, PluginConfigColor color)
{
byte distance = character != null ? character.YalmDistanceX : byte.MaxValue;
float currentAlpha = color.Vector.W * 100f;
float alpha = Config.RangeConfig.AlphaForDistance(distance, currentAlpha) / 100f;
if (character is IBattleNpc { BattleNpcKind: BattleNpcSubKind.Enemy or BattleNpcSubKind.BattleNpcPart } && Config.EnemyRangeConfig.Enabled)
{
alpha = Config.EnemyRangeConfig.AlphaForDistance(distance, currentAlpha) / 100f;
}
return color.WithAlpha(alpha);
}
private unsafe void GetNPCHpValues(IGameObject? actor, out uint currentHp, out uint maxHp)
{
currentHp = 0;
maxHp = 0;
var player = Plugin.ObjectTable.LocalPlayer;
if (player == null || actor == null || player.TargetObject == null || actor.GameObjectId != player.TargetObject.GameObjectId)
{
return;
}
AtkUnitBase* TargetWidget = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_TargetInfoMainTarget", 1).Address;
if (TargetWidget != null)
{
AtkTextNode* textNode = TargetWidget->GetTextNodeById(11);
string integrityText = textNode->NodeText.ToString();
// not a gathering node or node at 100%, nothing to do
if (!integrityText.Contains("%"))
{
return;
}
try
{
currentHp = Convert.ToUInt32((integrityText.Replace("%", "")));
maxHp = 100;
}
catch { }
}
}
private void DrawFriendlyNPC(Vector2 pos, IGameObject? actor)
{
GetNPCHpValues(actor, out uint currentHp, out uint maxHp);
BarHud bar = new BarHud(Config, actor);
bar.AddLabels(GetLabels(0));
if (maxHp == 0)
{
bar.AddForegrounds(new Rect(Config.Position, Config.Size, ColorUtils.ColorForActor(actor)));
}
else
{
if (Config.SmoothHealthConfig.Enabled)
{
currentHp = _smoothHPHelper.GetNextHp((int)currentHp, (int)maxHp, Config.SmoothHealthConfig.Velocity);
}
PluginConfigColor fillColor = ColorUtils.ColorForCharacter(
actor,
currentHp,
maxHp,
colorByHealthConfig: Config.ColorByHealth
) ?? Config.FillColor;
Rect background = new Rect(Config.Position, Config.Size, Config.BackgroundColor);
Rect healthFill = BarUtilities.GetFillRect(Config.Position, Config.Size, Config.FillDirection, fillColor, currentHp, maxHp);
bar.NeedsInputs = true;
bar.SetBackground(background);
bar.AddForegrounds(healthFill);
}
AddDrawActions(bar.GetDrawActions(pos, Config.StrataLevel));
}
private PluginConfigColor BackgroundColor(Character? chara)
{
if (Config.ShowTankInvulnerability &&
!Config.UseMissingHealthBar &&
chara is BattleChara battleChara)
{
IStatus? tankInvuln = Utils.GetTankInvulnerabilityID(battleChara);
if (tankInvuln != null)
{
PluginConfigColor color;
if (Config.UseCustomInvulnerabilityColor)
{
color = Config.CustomInvulnerabilityColor;
}
else if (tankInvuln.StatusId == 811 && Config.UseCustomWalkingDeadColor)
{
color = Config.CustomWalkingDeadColor;
}
else
{
color = new PluginConfigColor(GlobalColors.Instance.SafeColorForJobId(chara.ClassJob.RowId).Vector.AdjustColor(-.8f));
}
return color;
}
}
if (chara is BattleChara)
{
if (Config.UseJobColorAsBackgroundColor)
{
return GlobalColors.Instance.SafeColorForJobId(chara.ClassJob.RowId);
}
else if (Config.UseRoleColorAsBackgroundColor)
{
return GlobalColors.Instance.SafeRoleColorForJobId(chara.ClassJob.RowId);
}
else if (Config.UseDeathIndicatorBackgroundColor && chara.CurrentHp <= 0)
{
return Config.DeathIndicatorBackgroundColor;
}
else
{
return Config.BackgroundColor;
}
}
return GlobalColors.Instance.EmptyUnitFrameColor;
}
}
public class PlayerUnitFrameHud : UnitFrameHud
{
public new PlayerUnitFrameConfig Config => (PlayerUnitFrameConfig)_config;
public PlayerUnitFrameHud(PlayerUnitFrameConfig config, string displayName) : base(config, displayName)
{
}
protected override void DrawExtras(Vector2 origin, IGameObject? actor)
{
TankStanceIndicatorConfig config = Config.TankStanceIndicatorConfig;
if (!config.Enabled || actor is not IPlayerCharacter chara) { return; }
uint jobId = chara.ClassJob.RowId;
if (JobsHelper.RoleForJob(jobId) != JobRoles.Tank) { return; }
var tankStanceBuff = Utils.StatusListForBattleChara(chara).Where(o =>
o.StatusId == 79 || // IRON WILL
o.StatusId == 91 || // DEFIANCE
o.StatusId == 392 || // ROYAL GUARD
o.StatusId == 393 || // IRON WILL
o.StatusId == 743 || // GRIT
o.StatusId == 1396 || // DEFIANCE
o.StatusId == 1397 || // GRIT
o.StatusId == 1833 // ROYAL GUARD
);
PluginConfigColor color = tankStanceBuff.Any() ? config.ActiveColor : config.InactiveColor;
Vector2 pos = GetTankStanceCornerOrigin(origin);
var (verticalDir, horizontalDir) = GetTankStanceLinesDirections();
pos = new Vector2(pos.X + config.Thickess * -horizontalDir, pos.Y + config.Thickess * -verticalDir);
Vector2 vSize = new Vector2(config.Thickess * horizontalDir, (config.Size.Y + config.Thickess) * verticalDir);
Vector2 vEndPos = pos + vSize;
Vector2 hSize = new Vector2((config.Size.X + config.Thickess) * horizontalDir, config.Thickess * verticalDir);
Vector2 hEndPos = pos + hSize;
Vector2 startPos = new Vector2(Math.Min(pos.X, hEndPos.X), Math.Min(pos.Y, hEndPos.Y));
Vector2 endPos = new Vector2(Math.Max(pos.X, hEndPos.X), Math.Max(pos.Y, hEndPos.Y)); ;
AddDrawAction(StrataLevel.LOWEST, () =>
{
DrawHelper.DrawInWindow(ID + "_TankStance", startPos, endPos - startPos, false, (drawList) =>
{
// TODO: clean up hacky math
// there's some 1px errors prob due to negative sizes
// couldn't figure it out so I did the hacky fixes
// vertical
drawList.AddRectFilled(pos, vEndPos, color.Base);
if (config.Corner == TankStanceCorner.TopRight)
{
drawList.AddLine(pos, pos + new Vector2(0, vSize.Y + 1), 0xFF000000);
}
else
{
drawList.AddLine(pos, pos + new Vector2(0, vSize.Y), 0xFF000000);
}
drawList.AddLine(pos + vSize, pos + vSize + new Vector2(-vSize.X, 0), 0xFF000000);
// horizontal
drawList.AddRectFilled(pos, hEndPos, color.Base);
if (config.Corner == TankStanceCorner.BottomLeft)
{
drawList.AddLine(pos, pos + new Vector2(hSize.X + 1, 0), 0xFF000000);
}
else
{
drawList.AddLine(pos, pos + new Vector2(hSize.X, 0), 0xFF000000);
}
if (config.Corner == TankStanceCorner.BottomRight)
{
drawList.AddLine(pos + new Vector2(0, 1), pos + new Vector2(0, hSize.Y), 0xFF000000);
}
else
{
drawList.AddLine(pos, pos + new Vector2(0, hSize.Y), 0xFF000000);
}
drawList.AddLine(pos + hSize, pos + hSize + new Vector2(0, -hSize.Y), 0xFF000000);
});
});
}
private Vector2 GetTankStanceCornerOrigin(Vector2 origin)
{
var topLeft = Utils.GetAnchoredPosition(origin + Config.Position, Config.Size, Config.Anchor);
return Config.TankStanceIndicatorConfig.Corner switch
{
TankStanceCorner.TopRight => topLeft + new Vector2(Config.Size.X - 1, 0),
TankStanceCorner.BottomLeft => topLeft + new Vector2(0, Config.Size.Y - 1),
TankStanceCorner.BottomRight => topLeft + Config.Size - Vector2.One,
_ => topLeft
};
}
private (int, int) GetTankStanceLinesDirections()
{
return Config.TankStanceIndicatorConfig.Corner switch
{
TankStanceCorner.TopLeft => (1, 1),
TankStanceCorner.TopRight => (1, -1),
TankStanceCorner.BottomLeft => (-1, 1),
_ => (-1, -1)
};
}
}
}
@@ -0,0 +1,168 @@
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.Party;
using System.Linq;
namespace HSUI.Interface
{
[Exportable(false)]
public class VisibilityConfig : PluginConfigObject
{
[Checkbox("Hide outside of combat")]
[Order(5)]
public bool HideOutsideOfCombat = false;
[Checkbox("Hide in combat")]
[Order(6)]
public bool HideInCombat = false;
[Checkbox("Hide in Gold Saucer")]
[Order(7)]
public bool HideInGoldSaucer = false;
[Checkbox("Hide while at full HP")]
[Order(8)]
public bool HideOnFullHP = false;
[Checkbox("Hide when in duty")]
[Order(9)]
public bool HideInDuty = false;
[Checkbox("Hide in Island Sanctuary")]
[Order(10)]
public bool HideInIslandSanctuary = false;
[Checkbox("Hide in PvP")]
[Order(11)]
public bool HideInPvP = false;
[Checkbox("Always show when in duty")]
[Order(20)]
public bool ShowInDuty = false;
[Checkbox("Always show when weapon is drawn")]
[Order(21)]
public bool ShowOnWeaponDrawn = false;
[Checkbox("Always show when crafting")]
[Order(22)]
public bool ShowWhileCrafting = false;
[Checkbox("Always show when gathering")]
[Order(23)]
public bool ShowWhileGathering = false;
[Checkbox("Always show while in a party")]
[Order(24)]
public bool ShowInParty = false;
[Checkbox("Always show while in Island Sanctuary")]
[Order(25)]
public bool ShowInIslandSanctuary = false;
[Checkbox("Always show while in PvP")]
[Order(26)]
public bool ShowInPvP = false;
[Checkbox("Always show while target exists")]
[Order(27)]
public bool ShowWhileTargetExists = false;
private bool IsInCombat() => Plugin.Condition[ConditionFlag.InCombat];
private bool IsInDuty() => Plugin.Condition[ConditionFlag.BoundByDuty];
private bool IsCrafting() => Plugin.Condition[ConditionFlag.Crafting] || Plugin.Condition[ConditionFlag.ExecutingCraftingAction];
private bool IsGathering() => Plugin.Condition[ConditionFlag.Gathering] || Plugin.Condition[ConditionFlag.ExecutingGatheringAction];
private bool HasWeaponDrawn() => (Plugin.ObjectTable.LocalPlayer != null && Plugin.ObjectTable.LocalPlayer.StatusFlags.HasFlag(StatusFlags.WeaponOut));
private bool IsInGoldSaucer() => _goldSaucerIDs.Any(id => id == Plugin.ClientState.TerritoryType);
private bool IsInIslandSanctuary() => Plugin.ClientState.TerritoryType == 1055;
private readonly uint[] _goldSaucerIDs = { 144, 388, 389, 390, 391, 579, 792, 899, 941 };
public bool IsElementVisible(HudElement? element = null)
{
if (!Enabled) { return true; }
if (!ConfigurationManager.Instance.LockHUD) { return true; }
if (element != null && element.GetType() == typeof(PlayerCastbarHud)) { return true; }
if (element != null && !element.GetConfig().Enabled) { return false; }
bool isInIslandSanctuary = IsInIslandSanctuary();
bool isInDuty = IsInDuty() && !isInIslandSanctuary;
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
// show
if (ShowInDuty && isInDuty) { return true; }
if (ShowOnWeaponDrawn && HasWeaponDrawn()) { return true; }
if (ShowWhileCrafting && IsCrafting()) { return true; }
if (ShowWhileGathering && IsGathering()) { return true; }
if (ShowInParty && PartyManager.Instance.MemberCount > 1) { return true; }
if (ShowInIslandSanctuary && isInIslandSanctuary) { return true; }
if (ShowInPvP && Plugin.ClientState.IsPvP) { return true; }
if (ShowWhileTargetExists && player != null && player.TargetObject != null) { return true; }
// hide
if (HideOutsideOfCombat && !IsInCombat()) { return false; }
if (HideInCombat && IsInCombat()) { return false; }
if (HideInGoldSaucer && IsInGoldSaucer()) { return false; }
if (HideOnFullHP && player != null && player.CurrentHp == player.MaxHp) { return false; }
if (HideInDuty && isInDuty) { return false; }
if (HideInIslandSanctuary && isInIslandSanctuary) { return false; }
if (HideInPvP && Plugin.ClientState.IsPvP) { return false; }
return true;
}
public void CopyFrom(VisibilityConfig config)
{
Enabled = config.Enabled;
HideOutsideOfCombat = config.HideOutsideOfCombat;
HideInCombat = config.HideInCombat;
HideInGoldSaucer = config.HideInGoldSaucer;
HideOnFullHP = config.HideOnFullHP;
HideInDuty = config.HideInDuty;
HideInIslandSanctuary = config.HideInIslandSanctuary;
HideInPvP = config.HideInPvP;
ShowInDuty = config.ShowInDuty;
ShowOnWeaponDrawn = config.ShowOnWeaponDrawn;
ShowWhileCrafting = config.ShowWhileCrafting;
ShowWhileGathering = config.ShowWhileGathering;
ShowInParty = config.ShowInParty;
ShowInIslandSanctuary = config.ShowInIslandSanctuary;
ShowInPvP = config.ShowInPvP;
ShowWhileTargetExists = config.ShowWhileTargetExists;
}
public VisibilityConfig()
{
Enabled = false;
}
}
}
@@ -0,0 +1,151 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;
namespace HSUI.Interface.GeneralElements
{
public enum WindowClippingMode
{
Full,
Hide,
Performance
}
[Exportable(false)]
[Disableable(false)]
[Section("Misc")]
[SubSection("Window Clipping", 0)]
public class WindowClippingConfig : PluginConfigObject
{
public new static WindowClippingConfig DefaultConfig() => new WindowClippingConfig();
public WindowClippingMode Mode = WindowClippingMode.Full;
public bool NameplatesClipRectsEnabled = true;
public bool TargetCastbarClipRectEnabled = false;
public bool HotbarsClipRectsEnabled = false;
public bool ChatBubblesPlayersClipRectsEnabled = true;
public bool ChatBubblesNPCClipRectsEnabled = true;
public bool ThirdPartyClipRectsEnabled = true;
private bool _showConfirmationDialog = false;
[ManualDraw]
public bool Draw(ref bool changed)
{
ImGuiHelper.NewLineAndTab();
if (ImGui.Checkbox("Enabled", ref Enabled))
{
if (Enabled)
{
Enabled = false;
_showConfirmationDialog = true;
}
else
{
changed = true;
}
}
// confirmation dialog
if (_showConfirmationDialog)
{
string[] lines = new string[] { "THIS FEATURE IS KNOWN TO CAUSE RANDOM", "CRASHES TO A SMALL PORTION OF USERS!!!", "Are you sure you want to enable it?" };
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("WARNING!", lines);
if (didConfirm)
{
Enabled = true;
changed = true;
}
if (didConfirm || didClose)
{
_showConfirmationDialog = false;
}
}
if (!Enabled) { return changed; }
// mode
ImGuiHelper.NewLineAndTab();
ImGui.SameLine();
ImGui.Text("Mode: ");
ImGui.SameLine();
if (ImGui.RadioButton("Full", Mode == WindowClippingMode.Full))
{
Mode = WindowClippingMode.Full;
}
ImGui.SameLine();
if (ImGui.RadioButton("Hide", Mode == WindowClippingMode.Hide))
{
Mode = WindowClippingMode.Hide;
}
ImGui.SameLine();
if (ImGui.RadioButton("Performance", Mode == WindowClippingMode.Performance))
{
Mode = WindowClippingMode.Performance;
}
// nameplates
ImGui.NewLine();
ImGuiHelper.NewLineAndTab();
changed |= ImGui.Checkbox("Enable special clipping for Nameplates", ref NameplatesClipRectsEnabled);
ImGuiHelper.SetTooltip("When enabled, Nameplates will get covered by game UI elements that wouldn't normally cover HSUI elements.");
if (NameplatesClipRectsEnabled)
{
ImGuiHelper.Tab(); ImGuiHelper.Tab();
changed |= ImGui.Checkbox("Default Target Castbar", ref TargetCastbarClipRectEnabled);
ImGuiHelper.SetTooltip("When enabled, the game's target castbar will not be covered by HSUI Nameplates.\nFor players that prefer to use the default target cast bar over HSUI's.");
ImGuiHelper.Tab(); ImGuiHelper.Tab();
changed |= ImGui.Checkbox("Hotbars", ref HotbarsClipRectsEnabled);
ImGuiHelper.SetTooltip("When enabled, active hotbar will not be covered by HSUI Nameplates.\nNote that the way this is calculated is not perfect and it might not work well for hotbars that have empty slots.");
ImGuiHelper.Tab(); ImGuiHelper.Tab();
changed |= ImGui.Checkbox("NPC Chat Bubbles", ref ChatBubblesNPCClipRectsEnabled);
ImGuiHelper.Tab(); ImGuiHelper.Tab();
changed |= ImGui.Checkbox("Player Chat Bubbles", ref ChatBubblesPlayersClipRectsEnabled);
}
// third party
ImGui.NewLine();
ImGuiHelper.NewLineAndTab();
changed |= ImGui.Checkbox("Enable clipping for other plugins", ref ThirdPartyClipRectsEnabled);
ImGuiHelper.SetTooltip("When enabled, other plugins' windows can also be clipped so HSUI elements don't cover them.\nPlease note that this requires the developer of each third party plugin to implement the feature.");
// text
ImGui.NewLine();
ImGuiHelper.NewLineAndTab();
ImGui.SameLine();
switch (Mode)
{
case WindowClippingMode.Full:
ImGui.Text("HSUI will attempt to not cover game windows in this mode by clipping around them.");
break;
case WindowClippingMode.Hide:
ImGui.Text("HSUI will attempt to not cover game windows in this mode by not drawing an element if its touching a game window.");
break;
case WindowClippingMode.Performance:
ImGui.Text("Window Clipping functionality will be reduced in favor of performance.\nOnly one game window will be clipped at a time. This might yield unexpected / ugly results.\n\nNote: This mode won't work well with Nameplates.");
break;
}
ImGuiHelper.NewLineAndTab();
ImGui.Text("If you're experiencing random crashes or bad performance, we recommend you try a different mode\nor disable Window Clipping altogether");
return false;
}
}
}
+120
View File
@@ -0,0 +1,120 @@
using HSUI.Config;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Types;
using System;
using System.Collections.Generic;
using HSUI.Enums;
namespace HSUI.Interface
{
public abstract class HudElement : IDisposable
{
protected MovablePluginConfigObject _config;
public MovablePluginConfigObject GetConfig() { return _config; }
public string ID => _config.ID;
private Dictionary<StrataLevel, List<Action>> _drawActions = new Dictionary<StrataLevel, List<Action>>();
public HudElement(MovablePluginConfigObject config)
{
_config = config;
}
public void PrepareForDraw(Vector2 origin)
{
_drawActions.Clear();
CreateDrawActions(origin);
}
public virtual void Draw(Vector2 origin)
{
// iterate like this so it goes in order
StrataLevel[] levels = (StrataLevel[])Enum.GetValues(typeof(StrataLevel));
foreach (StrataLevel key in levels)
{
_drawActions.TryGetValue(key, out List<Action>? drawActions);
if (drawActions == null) { continue; }
foreach (Action drawAction in _drawActions[key])
{
drawAction();
}
}
}
protected void AddDrawAction(StrataLevel strataLevel, Action drawAction)
{
_drawActions.TryGetValue(strataLevel, out List<Action>? drawActions);
if (drawActions == null)
{
drawActions = new List<Action>();
_drawActions.Add(strataLevel, drawActions);
}
drawActions.Add(drawAction);
}
protected void AddDrawActions(List<(StrataLevel, Action)> drawActions)
{
foreach ((StrataLevel strataLevel, Action drawAction) in drawActions)
{
AddDrawAction(strataLevel, drawAction);
}
}
protected abstract void CreateDrawActions(Vector2 origin);
~HudElement()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
InternalDispose();
}
protected virtual void InternalDispose()
{
// override
}
}
public interface IHudElementWithActor
{
public IGameObject? Actor { get; set; }
}
public interface IHudElementWithAnchorableParent
{
public AnchorablePluginConfigObject? ParentConfig { get; set; }
}
public interface IHudElementWithMouseOver
{
public void StopMouseover();
}
public interface IHudElementWithPreview
{
public void StopPreview();
}
public interface IHudElementWithVisibilityConfig
{
public VisibilityConfig? VisibilityConfig { get; }
}
}
+506
View File
@@ -0,0 +1,506 @@
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Config;
using HSUI.Helpers;
using HSUI.Interface.EnemyList;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.Party;
using HSUI.Interface.Jobs;
using HSUI.Interface.Nameplates;
using HSUI.Interface.StatusEffects;
using FFXIVClientStructs.FFXIV.Component.GUI;
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
namespace HSUI.Interface
{
public class HudHelper : IDisposable
{
private HUDOptionsConfig Config => ConfigurationManager.Instance.GetConfigObject<HUDOptionsConfig>();
private bool _firstUpdate = true;
private Vector2 _castBarPos = Vector2.Zero;
private bool _hidingCastBar = false;
private Vector2 _pullTimerPos = Vector2.Zero;
private bool _hidingPullTimer = false;
public HudHelper()
{
Config.ValueChangeEvent += ConfigValueChanged;
}
~HudHelper()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
Config.ValueChangeEvent -= ConfigValueChanged;
// Only restore defaults when already on framework thread. Skip RunOnFrameworkThread
// during unload—it can deadlock. Restore is best-effort; game state may be torn down.
if (Plugin.Framework.IsInFrameworkUpdateThread && Plugin.ObjectTable.LocalPlayer != null)
{
try
{
SetGameHudElementsHidden(Array.Empty<uint>(), true);
UpdateDefaultCastBar(true);
UpdateDefaultPulltimer(true);
UpdateDefaultNameplates(true);
UpdateJobGauges(true);
}
catch (Exception ex)
{
Plugin.Logger.Error($"Exception during HudHelper.Dispose restore: {ex.Message}");
}
}
}
public void Update()
{
try
{
if (_firstUpdate)
{
_firstUpdate = false;
UpdateDefaultCastBar();
UpdateDefaultPulltimer();
}
UpdateJobGauges();
UpdateDefaultHudElementsHidden();
UpdateDefaultNameplates();
}
catch (Exception ex)
{
var now = DateTime.UtcNow;
if ((now - _lastHudElementsErrorLog).TotalSeconds >= HudElementsErrorLogIntervalSeconds)
{
_lastHudElementsErrorLog = now;
Plugin.Logger.Warning($"[HSUI] HudHelper.Update: {ex.Message}");
}
}
}
public bool IsElementHidden(HudElement element)
{
IHudElementWithVisibilityConfig? e = element as IHudElementWithVisibilityConfig;
if (e == null || e.VisibilityConfig == null) { return false; }
return !e.VisibilityConfig.IsElementVisible(element);
}
private void ConfigValueChanged(object sender, OnChangeBaseArgs e)
{
switch (e.PropertyName)
{
case "HideDefaultHudWhenReplaced":
UpdateDefaultHudElementsHidden();
break;
case "HideDefaultCastbar":
UpdateDefaultCastBar();
break;
case "HideDefaultPulltimer":
UpdateDefaultPulltimer();
break;
}
}
private static DateTime _lastHudElementsErrorLog = DateTime.MinValue;
private const double HudElementsErrorLogIntervalSeconds = 10.0;
private void UpdateDefaultHudElementsHidden()
{
try
{
UpdateDefaultHudElementsHiddenCore();
}
catch (Exception ex)
{
var now = DateTime.UtcNow;
if ((now - _lastHudElementsErrorLog).TotalSeconds >= HudElementsErrorLogIntervalSeconds)
{
_lastHudElementsErrorLog = now;
Plugin.Logger.Warning($"[HSUI] HudHelper: skipped HUD hide update (resolver not ready): {ex.Message}");
}
}
}
private void UpdateDefaultHudElementsHiddenCore()
{
if (Plugin.Condition.Any(
ConditionFlag.OccupiedInEvent,
ConditionFlag.OccupiedInQuestEvent,
ConditionFlag.OccupiedInCutSceneEvent,
ConditionFlag.OccupiedSummoningBell,
ConditionFlag.Occupied,
ConditionFlag.Occupied30,
ConditionFlag.Occupied33,
ConditionFlag.Occupied38,
ConditionFlag.Occupied39,
ConditionFlag.WatchingCutscene,
ConditionFlag.WatchingCutscene78,
ConditionFlag.CreatingCharacter,
ConditionFlag.BetweenAreas,
ConditionFlag.BetweenAreas51,
ConditionFlag.BoundByDuty95,
ConditionFlag.ChocoboRacing,
ConditionFlag.PlayingLordOfVerminion)
|| Plugin.ClientState.IsPvP)
{
return;
}
if (!Config.HideDefaultHudWhenReplaced)
{
SetGameHudElementsHidden(Array.Empty<uint>(), false);
return;
}
var hashesToHide = new List<uint>();
void AddHashes(params string[] addonNames)
{
foreach (var name in addonNames)
{
var h = HudLayoutHashHelper.GetHash(name);
if (h != 0)
hashesToHide.Add(h);
}
}
var hotbarsConfig = ConfigurationManager.Instance?.GetConfigObject<HotbarsConfig>();
if (hotbarsConfig?.Enabled == true)
{
AddHashes("_ActionBar", "_ActionBar01", "_ActionBar02", "_ActionBar03", "_ActionBar04",
"_ActionBar05", "_ActionBar06", "_ActionBar07", "_ActionBar08", "_ActionBar09", "_ActionCross");
}
var playerUnitFrame = ConfigurationManager.Instance?.GetConfigObject<PlayerUnitFrameConfig>();
if (playerUnitFrame?.Enabled == true)
AddHashes("_ParameterWidget");
var targetUnitFrame = ConfigurationManager.Instance?.GetConfigObject<TargetUnitFrameConfig>();
if (targetUnitFrame?.Enabled == true)
AddHashes("_TargetInfo", "_TargetInfoMainTarget", "_TargetInfoCastBar", "_TargetInfoBuffDebuff");
var focusUnitFrame = ConfigurationManager.Instance?.GetConfigObject<FocusTargetUnitFrameConfig>();
if (focusUnitFrame?.Enabled == true)
AddHashes("_FocusTargetInfo");
var playerCastbar = ConfigurationManager.Instance?.GetConfigObject<PlayerCastbarConfig>();
if (playerCastbar?.Enabled == true)
AddHashes("_CastBar");
var expBar = ConfigurationManager.Instance?.GetConfigObject<ExperienceBarConfig>();
if (expBar?.Enabled == true)
AddHashes("_Exp");
var limitBreak = ConfigurationManager.Instance?.GetConfigObject<LimitBreakConfig>();
if (limitBreak?.Enabled == true)
AddHashes("_LimitBreak");
var playerBuffs = ConfigurationManager.Instance?.GetConfigObject<PlayerBuffsListConfig>();
var playerDebuffs = ConfigurationManager.Instance?.GetConfigObject<PlayerDebuffsListConfig>();
if (playerBuffs?.Enabled == true || playerDebuffs?.Enabled == true)
AddHashes("_Status", "_StatusCustom0", "_StatusCustom1", "_StatusCustom2", "_StatusCustom3");
var partyFrames = ConfigurationManager.Instance?.GetConfigObject<PartyFramesConfig>();
if (partyFrames?.Enabled == true)
AddHashes("_PartyList");
var enemyList = ConfigurationManager.Instance?.GetConfigObject<EnemyListConfig>();
if (enemyList?.Enabled == true)
AddHashes("_EnemyList");
var nameplatesConfig = ConfigurationManager.Instance?.GetConfigObject<NameplatesGeneralConfig>();
if (nameplatesConfig?.Enabled == true)
AddHashes("NamePlate");
if (IsCurrentJobHudEnabled())
{
foreach (var name in GetCurrentJobGaugeAddonNames())
AddHashes(name);
}
SetGameHudElementsHidden(hashesToHide.ToArray(), false);
}
/// <summary>Visibility (ByteValue2) of game HUD elements before we hid them. Restored on disable/unload.</summary>
private readonly Dictionary<uint, byte> _gameHudVisibilityBeforeHide = new();
private unsafe void SetGameHudElementsHidden(uint[] hashesToHide, bool forceRestore)
{
AddonConfig* config = AddonConfig.Instance();
if (config == null || !config->IsLoaded || config->ActiveDataSet == null)
return;
var hashSet = new HashSet<uint>(hashesToHide);
Span<AddonConfigEntry> entries = config->ActiveDataSet->HudLayoutConfigEntries;
bool hasChanges = false;
if (forceRestore || hashesToHide.Length == 0)
{
foreach (ref var entry in entries)
{
if (!_gameHudVisibilityBeforeHide.TryGetValue(entry.AddonNameHash, out byte saved))
continue;
if (entry.ByteValue2 == saved)
continue;
entry.ByteValue2 = saved;
hasChanges = true;
}
_gameHudVisibilityBeforeHide.Clear();
}
else
{
foreach (ref var entry in entries)
{
if (!hashSet.Contains(entry.AddonNameHash))
continue;
if (!_gameHudVisibilityBeforeHide.ContainsKey(entry.AddonNameHash))
_gameHudVisibilityBeforeHide[entry.AddonNameHash] = entry.ByteValue2;
if (entry.ByteValue2 == 0x0)
continue;
entry.ByteValue2 = 0x0;
hasChanges = true;
}
}
if (hasChanges)
{
config->SaveFile(true);
config->ApplyHudLayout();
}
}
private unsafe void UpdateDefaultCastBar(bool forceVisible = false)
{
if (Config.HideDefaultCastbar && !_hidingCastBar)
{
Plugin.AddonLifecycle.RegisterListener(AddonEvent.PreDraw, "_CastBar", (addonEvent, args) =>
{
AtkUnitBase* addon = (AtkUnitBase*)args.Addon.Address;
if (!_hidingCastBar)
{
_castBarPos = new Vector2(addon->RootNode->GetXFloat(), addon->RootNode->GetYFloat());
}
addon->RootNode->SetPositionFloat(-9999.0f, -9999.0f);
});
_hidingCastBar = true;
}
else if ((forceVisible || !Config.HideDefaultCastbar) && _hidingCastBar)
{
Plugin.AddonLifecycle.UnregisterListener(AddonEvent.PreDraw, "_CastBar");
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_CastBar", 1).Address;
if (addon != null)
{
addon->RootNode->SetPositionFloat(_castBarPos.X, _castBarPos.Y);
}
_hidingCastBar = false;
}
return;
}
private unsafe void UpdateDefaultPulltimer(bool forceVisible = false)
{
if (Config.HideDefaultPulltimer && !_hidingPullTimer)
{
Plugin.AddonLifecycle.RegisterListener(AddonEvent.PreDraw, "ScreenInfo_CountDown", (addonEvent, args) =>
{
AtkUnitBase* addon = (AtkUnitBase*)args.Addon.Address;
if (!_hidingPullTimer)
{
_pullTimerPos = new Vector2(addon->RootNode->GetXFloat(), addon->RootNode->GetYFloat());
}
addon->RootNode->SetPositionFloat(-9999.0f, -9999.0f);
});
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("ScreenInfo_CountDown", 1).Address;
if (addon != null)
{
_pullTimerPos = new Vector2(addon->RootNode->GetXFloat(), addon->RootNode->GetYFloat());
addon->RootNode->SetPositionFloat(-9999, -9999);
}
_hidingPullTimer = true;
}
else if ((forceVisible || !Config.HideDefaultPulltimer) && _hidingPullTimer)
{
Plugin.AddonLifecycle.UnregisterListener(AddonEvent.PreDraw, "ScreenInfo_CountDown");
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("ScreenInfo_CountDown", 1).Address;
if (addon != null)
{
addon->RootNode->SetPositionFloat(_pullTimerPos.X, _pullTimerPos.Y);
}
_hidingPullTimer = false;
}
return;
}
private static readonly Dictionary<uint, Type> _jobIdToConfigType = new()
{
[JobIDs.PLD] = typeof(PaladinConfig), [JobIDs.WAR] = typeof(WarriorConfig),
[JobIDs.DRK] = typeof(DarkKnightConfig), [JobIDs.GNB] = typeof(GunbreakerConfig),
[JobIDs.WHM] = typeof(WhiteMageConfig), [JobIDs.SCH] = typeof(ScholarConfig),
[JobIDs.AST] = typeof(AstrologianConfig), [JobIDs.SGE] = typeof(SageConfig),
[JobIDs.MNK] = typeof(MonkConfig), [JobIDs.DRG] = typeof(DragoonConfig),
[JobIDs.NIN] = typeof(NinjaConfig), [JobIDs.SAM] = typeof(SamuraiConfig),
[JobIDs.RPR] = typeof(ReaperConfig), [JobIDs.VPR] = typeof(ViperConfig),
[JobIDs.BRD] = typeof(BardConfig), [JobIDs.MCH] = typeof(MachinistConfig),
[JobIDs.DNC] = typeof(DancerConfig), [JobIDs.BLM] = typeof(BlackMageConfig),
[JobIDs.SMN] = typeof(SummonerConfig), [JobIDs.RDM] = typeof(RedMageConfig),
[JobIDs.BLU] = typeof(BlueMageConfig), [JobIDs.PCT] = typeof(PictomancerConfig),
};
private static bool IsCurrentJobHudEnabled()
{
var player = Plugin.ObjectTable.LocalPlayer;
if (player == null) return false;
if (!_jobIdToConfigType.TryGetValue(player.ClassJob.RowId, out var configType))
return false;
var config = ConfigurationManager.Instance?.GetConfigObjectForType(configType) as JobConfig;
return config?.Enabled ?? false;
}
private static IEnumerable<string> GetCurrentJobGaugeAddonNames()
{
var player = Plugin.ObjectTable.LocalPlayer;
if (player == null) yield break;
if (!JobsHelper.JobNames.TryGetValue(player.ClassJob.RowId, out var jobName))
yield break;
for (int i = 0; i < 4; i++)
{
string addonName = $"JobHud{jobName}{i}";
if (_specialCases.TryGetValue(addonName, out var name) && name != null)
addonName = name;
yield return addonName;
}
}
private bool _hidingNameplates = false;
private bool _nameplateVisibilityBeforeHide = true;
private unsafe void UpdateDefaultNameplates(bool forceVisible = false)
{
var nameplatesConfig = ConfigurationManager.Instance?.GetConfigObject<NameplatesGeneralConfig>();
bool shouldHide = !forceVisible && Config.HideDefaultHudWhenReplaced && (nameplatesConfig?.Enabled ?? false);
var addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("NamePlate", 1).Address;
if (addon == null) return;
if (shouldHide)
{
if (!_hidingNameplates)
{
_nameplateVisibilityBeforeHide = addon->IsVisible;
_hidingNameplates = true;
}
addon->IsVisible = false;
}
else
{
if (_hidingNameplates)
{
addon->IsVisible = _nameplateVisibilityBeforeHide;
_hidingNameplates = false;
}
}
}
private static Dictionary<string, string> _specialCases = new()
{
["JobHudPCT0"] = "JobHudRPM0",
["JobHudPCT1"] = "JobHudRPM1",
["JobHudNIN1"] = "JobHudNIN1v70",
["JobHudVPR0"] = "JobHudRDB0",
["JobHudVPR1"] = "JobHudRDB1"
};
private unsafe void UpdateJobGauges(bool forceVisible = false)
{
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
if (player == null) { return; }
string jobName = JobsHelper.JobNames[player.ClassJob.RowId];
int i = 0;
bool stop = false;
do
{
string addonName = $"JobHud{jobName}{i}";
if (_specialCases.TryGetValue(addonName, out string? name) && name != null)
{
addonName = name;
}
AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName(addonName, 1).Address;
if (addon == null)
{
stop = true;
}
else
{
addon->IsVisible = forceVisible || !Config.HideDefaultJobGauges;
}
i++;
} while (!stop);
}
}
internal class StrataLevelComparer<TKey> : IComparer<TKey> where TKey : PluginConfigObject
{
public int Compare(TKey? a, TKey? b)
{
MovablePluginConfigObject? configA = a is MovablePluginConfigObject ? a as MovablePluginConfigObject : null;
MovablePluginConfigObject? configB = b is MovablePluginConfigObject ? b as MovablePluginConfigObject : null;
if (configA == null && configB == null) { return 0; }
if (configA == null && configB != null) { return -1; }
if (configA != null && configB == null) { return 1; }
if (configA!.StrataLevel == configB!.StrataLevel)
{
return configA.ID.CompareTo(configB.ID);
}
if (configA.StrataLevel < configB.StrataLevel)
{
return -1;
}
return 1;
}
}
}
+786
View File
@@ -0,0 +1,786 @@
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using HSUI.Config;
using HSUI.Helpers;
using HSUI.Interface.EnemyList;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.Jobs;
using HSUI.Interface.Nameplates;
using HSUI.Interface.Party;
using HSUI.Interface.PartyCooldowns;
using HSUI.Interface.StatusEffects;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace HSUI.Interface
{
public class HudManager : IDisposable
{
private GridConfig? _gridConfig;
private HUDOptionsConfig? _hudOptions;
private DraggableHudElement? _selectedElement = null;
private SortedList<PluginConfigObject, DraggableHudElement> _hudElements = null!;
private List<IHudElementWithActor> _hudElementsUsingPlayer = null!;
private List<IHudElementWithActor> _hudElementsUsingTarget = null!;
private List<IHudElementWithActor> _hudElementsUsingTargetOfTarget = null!;
private List<IHudElementWithActor> _hudElementsUsingFocusTarget = null!;
private List<IHudElementWithPreview> _hudElementsWithPreview = null!;
private UnitFrameHud _playerUnitFrameHud = null!;
private UnitFrameHud _targetUnitFrameHud = null!;
private UnitFrameHud _totUnitFrameHud = null!;
private UnitFrameHud _focusTargetUnitFrameHud = null!;
private PlayerCastbarHud _playerCastbarHud = null!;
private CustomEffectsListHud _customEffectsHud = null!;
private PrimaryResourceHud _playerManaBarHud = null!;
private JobHud? _jobHud = null;
private Dictionary<uint, JobHudTypes> _jobsMap = null!;
private Dictionary<uint, Type> _unsupportedJobsMap = null!;
private List<Type> _jobTypes = null!;
private NameplatesHud _nameplatesHud = null!;
private double _occupiedInQuestStartTime = -1;
private HudHelper _hudHelper = new HudHelper();
public HudManager()
{
CreateJobsMap();
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
ConfigurationManager.Instance.LockEvent += OnHUDLockChanged;
ConfigurationManager.Instance.ConfigClosedEvent += OnConfingWindowClosed;
ConfigurationManager.Instance.StrataLevelsChangedEvent += OnStrataLevelsChanged;
ConfigurationManager.Instance.GlobalVisibilityEvent += OnGlobalVisibilityChanged;
CreateHudElements();
}
~HudManager()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
_hudHelper.Dispose();
foreach (var element in _hudElements.Values)
{
try { element.Dispose(); }
catch (Exception ex) { Plugin.Logger.Error($"Error disposing HUD element {element.ID}: {ex.Message}"); }
}
try { _jobHud?.Dispose(); }
catch (Exception ex) { Plugin.Logger.Error($"Error disposing JobHud: {ex.Message}"); }
_hudElements.Clear();
_hudElementsUsingPlayer.Clear();
_hudElementsUsingTarget.Clear();
_hudElementsUsingTargetOfTarget.Clear();
_hudElementsUsingFocusTarget.Clear();
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
ConfigurationManager.Instance.LockEvent -= OnHUDLockChanged;
ConfigurationManager.Instance.ConfigClosedEvent -= OnConfingWindowClosed;
ConfigurationManager.Instance.StrataLevelsChangedEvent -= OnStrataLevelsChanged;
ConfigurationManager.Instance.GlobalVisibilityEvent -= OnGlobalVisibilityChanged;
}
private void OnConfigReset(ConfigurationManager sender)
{
CreateHudElements();
_jobHud = null;
}
private void OnHUDLockChanged(ConfigurationManager sender)
{
var draggingEnabled = !sender.LockHUD;
foreach (var element in _hudElements.Values)
{
element.DraggingEnabled = draggingEnabled;
element.Selected = false;
}
if (_jobHud != null)
{
_jobHud.DraggingEnabled = draggingEnabled;
}
_selectedElement = null;
}
private void OnConfingWindowClosed(ConfigurationManager sender)
{
if (_hudOptions == null || !_hudOptions.AutomaticPreviewDisabling)
{
return;
}
foreach (IHudElementWithPreview element in _hudElementsWithPreview)
{
element.StopPreview();
}
_nameplatesHud.StopPreview();
}
private void OnStrataLevelsChanged(ConfigurationManager sender, PluginConfigObject config)
{
SortedList<PluginConfigObject, DraggableHudElement> tmp = new SortedList<PluginConfigObject, DraggableHudElement>(new StrataLevelComparer<PluginConfigObject>());
foreach (DraggableHudElement element in _hudElements.Values)
{
tmp.Add(element.GetConfig(), element);
}
_hudElements = tmp;
}
private void OnGlobalVisibilityChanged(ConfigurationManager sender, VisibilityConfig config)
{
foreach (DraggableHudElement element in _hudElements.Values)
{
if (element is IHudElementWithVisibilityConfig e)
{
e.VisibilityConfig?.CopyFrom(config);
}
}
foreach (Type jobType in _jobTypes)
{
JobConfig jobConfig = (JobConfig)ConfigurationManager.Instance.GetConfigObjectForType(jobType);
jobConfig.VisibilityConfig.CopyFrom(config);
}
}
private void OnDraggableElementSelected(DraggableHudElement sender)
{
foreach (var element in _hudElements.Values)
{
element.Selected = element == sender;
}
if (_jobHud != null)
{
_jobHud.Selected = _jobHud == sender;
}
_selectedElement = sender;
}
private void CreateHudElements()
{
_gridConfig = ConfigurationManager.Instance.GetConfigObject<GridConfig>();
_hudOptions = ConfigurationManager.Instance.GetConfigObject<HUDOptionsConfig>();
_hudElements = new SortedList<PluginConfigObject, DraggableHudElement>(new StrataLevelComparer<PluginConfigObject>());
_hudElementsUsingPlayer = new List<IHudElementWithActor>();
_hudElementsUsingTarget = new List<IHudElementWithActor>();
_hudElementsUsingTargetOfTarget = new List<IHudElementWithActor>();
_hudElementsUsingFocusTarget = new List<IHudElementWithActor>();
_hudElementsWithPreview = new List<IHudElementWithPreview>();
_nameplatesHud = new NameplatesHud(ConfigurationManager.Instance.GetConfigObject<NameplatesGeneralConfig>());
CreateUnitFrames();
CreateManaBars();
CreateCastbars();
CreateStatusEffectsLists();
CreateMiscElements();
foreach (var element in _hudElements.Values)
{
element.SelectEvent += OnDraggableElementSelected;
}
}
private void CreateUnitFrames()
{
var playerUnitFrameConfig = ConfigurationManager.Instance.GetConfigObject<PlayerUnitFrameConfig>();
_playerUnitFrameHud = new PlayerUnitFrameHud(playerUnitFrameConfig, "Player");
_hudElements.Add(playerUnitFrameConfig, _playerUnitFrameHud);
_hudElementsUsingPlayer.Add(_playerUnitFrameHud);
_hudElementsWithPreview.Add(_playerUnitFrameHud);
var targetUnitFrameConfig = ConfigurationManager.Instance.GetConfigObject<TargetUnitFrameConfig>();
_targetUnitFrameHud = new UnitFrameHud(targetUnitFrameConfig, "Target");
_hudElements.Add(targetUnitFrameConfig, _targetUnitFrameHud);
_hudElementsUsingTarget.Add(_targetUnitFrameHud);
_hudElementsWithPreview.Add(_targetUnitFrameHud);
var targetOfTargetUnitFrameConfig = ConfigurationManager.Instance.GetConfigObject<TargetOfTargetUnitFrameConfig>();
_totUnitFrameHud = new UnitFrameHud(targetOfTargetUnitFrameConfig, "Target of Target");
_hudElements.Add(targetOfTargetUnitFrameConfig, _totUnitFrameHud);
_hudElementsUsingTargetOfTarget.Add(_totUnitFrameHud);
_hudElementsWithPreview.Add(_totUnitFrameHud);
var focusTargetUnitFrameConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetUnitFrameConfig>();
_focusTargetUnitFrameHud = new UnitFrameHud(focusTargetUnitFrameConfig, "Focus Target");
_hudElements.Add(focusTargetUnitFrameConfig, _focusTargetUnitFrameHud);
_hudElementsUsingFocusTarget.Add(_focusTargetUnitFrameHud);
_hudElementsWithPreview.Add(_focusTargetUnitFrameHud);
var partyFramesConfig = ConfigurationManager.Instance.GetConfigObject<PartyFramesConfig>();
var partyFramesHud = new PartyFramesHud(partyFramesConfig, "Party Frames");
_hudElements.Add(partyFramesConfig, partyFramesHud);
_hudElementsWithPreview.Add(partyFramesHud);
var enemyListConfig = ConfigurationManager.Instance.GetConfigObject<EnemyListConfig>();
var enemyListHud = new EnemyListHud(enemyListConfig, "Enemy List");
_hudElements.Add(enemyListConfig, enemyListHud);
_hudElementsWithPreview.Add(enemyListHud);
}
private void CreateManaBars()
{
var playerManaBarConfig = ConfigurationManager.Instance.GetConfigObject<PlayerPrimaryResourceConfig>();
_playerManaBarHud = new PrimaryResourceHud(playerManaBarConfig, "Player Mana Bar");
_playerManaBarHud.ParentConfig = _playerUnitFrameHud.Config;
_hudElements.Add(playerManaBarConfig, _playerManaBarHud);
_hudElementsUsingPlayer.Add(_playerManaBarHud);
var targetManaBarConfig = ConfigurationManager.Instance.GetConfigObject<TargetPrimaryResourceConfig>();
var targetManaBarHud = new PrimaryResourceHud(targetManaBarConfig, "Target Mana Bar");
targetManaBarHud.ParentConfig = _targetUnitFrameHud.Config;
_hudElements.Add(targetManaBarConfig, targetManaBarHud);
_hudElementsUsingTarget.Add(targetManaBarHud);
var totManaBarConfig = ConfigurationManager.Instance.GetConfigObject<TargetOfTargetPrimaryResourceConfig>();
var totManaBarHud = new PrimaryResourceHud(totManaBarConfig, "ToT Mana Bar");
totManaBarHud.ParentConfig = _totUnitFrameHud.Config;
_hudElements.Add(totManaBarConfig, totManaBarHud);
_hudElementsUsingTargetOfTarget.Add(totManaBarHud);
var focusManaBarConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetPrimaryResourceConfig>();
var focusManaBarHud = new PrimaryResourceHud(focusManaBarConfig, "Focus Mana Bar");
focusManaBarHud.ParentConfig = _focusTargetUnitFrameHud.Config;
_hudElements.Add(focusManaBarConfig, focusManaBarHud);
_hudElementsUsingFocusTarget.Add(focusManaBarHud);
}
private void CreateCastbars()
{
var playerCastbarConfig = ConfigurationManager.Instance.GetConfigObject<PlayerCastbarConfig>();
_playerCastbarHud = new PlayerCastbarHud(playerCastbarConfig, "Player Castbar");
_playerCastbarHud.ParentConfig = _playerUnitFrameHud.Config;
_hudElements.Add(playerCastbarConfig, _playerCastbarHud);
_hudElementsUsingPlayer.Add(_playerCastbarHud);
_hudElementsWithPreview.Add(_playerCastbarHud);
var targetCastbarConfig = ConfigurationManager.Instance.GetConfigObject<TargetCastbarConfig>();
var targetCastbar = new TargetCastbarHud(targetCastbarConfig, "Target Castbar");
targetCastbar.ParentConfig = _targetUnitFrameHud.Config;
_hudElements.Add(targetCastbarConfig, targetCastbar);
_hudElementsUsingTarget.Add(targetCastbar);
_hudElementsWithPreview.Add(targetCastbar);
var targetOfTargetCastbarConfig = ConfigurationManager.Instance.GetConfigObject<TargetOfTargetCastbarConfig>();
var targetOfTargetCastbar = new TargetOfTargetCastbarHud(targetOfTargetCastbarConfig, "ToT Castbar");
targetOfTargetCastbar.ParentConfig = _totUnitFrameHud.Config;
_hudElements.Add(targetOfTargetCastbarConfig, targetOfTargetCastbar);
_hudElementsUsingTargetOfTarget.Add(targetOfTargetCastbar);
_hudElementsWithPreview.Add(targetOfTargetCastbar);
var focusTargetCastbarConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetCastbarConfig>();
var focusTargetCastbar = new FocusTargetCastbarHud(focusTargetCastbarConfig, "Focus Castbar");
focusTargetCastbar.ParentConfig = _focusTargetUnitFrameHud.Config;
_hudElements.Add(focusTargetCastbarConfig, focusTargetCastbar);
_hudElementsUsingFocusTarget.Add(focusTargetCastbar);
_hudElementsWithPreview.Add(focusTargetCastbar);
}
private void CreateStatusEffectsLists()
{
var playerBuffsConfig = ConfigurationManager.Instance.GetConfigObject<PlayerBuffsListConfig>();
var playerBuffs = new StatusEffectsListHud(playerBuffsConfig, "Buffs");
playerBuffs.ParentConfig = _playerUnitFrameHud.Config;
_hudElements.Add(playerBuffsConfig, playerBuffs);
_hudElementsUsingPlayer.Add(playerBuffs);
_hudElementsWithPreview.Add(playerBuffs);
var playerDebuffsConfig = ConfigurationManager.Instance.GetConfigObject<PlayerDebuffsListConfig>();
var playerDebuffs = new StatusEffectsListHud(playerDebuffsConfig, "Debuffs");
playerDebuffs.ParentConfig = _playerUnitFrameHud.Config;
_hudElements.Add(playerDebuffsConfig, playerDebuffs);
_hudElementsUsingPlayer.Add(playerDebuffs);
_hudElementsWithPreview.Add(playerDebuffs);
var targetBuffsConfig = ConfigurationManager.Instance.GetConfigObject<TargetBuffsListConfig>();
var targetBuffs = new StatusEffectsListHud(targetBuffsConfig, "Target Buffs");
targetBuffs.ParentConfig = _targetUnitFrameHud.Config;
_hudElements.Add(targetBuffsConfig, targetBuffs);
_hudElementsUsingTarget.Add(targetBuffs);
_hudElementsWithPreview.Add(targetBuffs);
var targetDebuffsConfig = ConfigurationManager.Instance.GetConfigObject<TargetDebuffsListConfig>();
var targetDebuffs = new StatusEffectsListHud(targetDebuffsConfig, "Target Debuffs");
targetDebuffs.ParentConfig = _targetUnitFrameHud.Config;
_hudElements.Add(targetDebuffsConfig, targetDebuffs);
_hudElementsUsingTarget.Add(targetDebuffs);
_hudElementsWithPreview.Add(targetDebuffs);
var focusTargetBuffsConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetBuffsListConfig>();
var focusTargetBuffs = new StatusEffectsListHud(focusTargetBuffsConfig, "focusTarget Buffs");
focusTargetBuffs.ParentConfig = _focusTargetUnitFrameHud.Config;
_hudElements.Add(focusTargetBuffsConfig, focusTargetBuffs);
_hudElementsUsingFocusTarget.Add(focusTargetBuffs);
_hudElementsWithPreview.Add(focusTargetBuffs);
var focusTargetDebuffsConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetDebuffsListConfig>();
var focusTargetDebuffs = new StatusEffectsListHud(focusTargetDebuffsConfig, "focusTarget Debuffs");
focusTargetDebuffs.ParentConfig = _focusTargetUnitFrameHud.Config;
_hudElements.Add(focusTargetDebuffsConfig, focusTargetDebuffs);
_hudElementsUsingFocusTarget.Add(focusTargetDebuffs);
_hudElementsWithPreview.Add(focusTargetDebuffs);
var custonEffectsConfig = ConfigurationManager.Instance.GetConfigObject<CustomEffectsListConfig>();
_customEffectsHud = new CustomEffectsListHud(custonEffectsConfig, "Custom Effects");
_hudElements.Add(custonEffectsConfig, _customEffectsHud);
_hudElementsUsingPlayer.Add(_customEffectsHud);
_hudElementsWithPreview.Add(_customEffectsHud);
}
private void CreateMiscElements()
{
var gcdIndicatorConfig = ConfigurationManager.Instance.GetConfigObject<GCDIndicatorConfig>();
var gcdIndicator = new GCDIndicatorHud(gcdIndicatorConfig, "GCD Indicator");
_hudElements.Add(gcdIndicatorConfig, gcdIndicator);
_hudElementsUsingPlayer.Add(gcdIndicator);
var mpTickerConfig = ConfigurationManager.Instance.GetConfigObject<MPTickerConfig>();
var mpTicker = new MPTickerHud(mpTickerConfig, "MP Ticker");
_hudElements.Add(mpTickerConfig, mpTicker);
_hudElementsUsingPlayer.Add(mpTicker);
var expBarConfig = ConfigurationManager.Instance.GetConfigObject<ExperienceBarConfig>();
var expBarHud = new ExperienceBarHud(expBarConfig, "Experience Bar");
_hudElements.Add(expBarConfig, expBarHud);
_hudElementsUsingPlayer.Add(expBarHud);
var pullTimerConfig = ConfigurationManager.Instance.GetConfigObject<PullTimerConfig>();
var pullTimerHud = new PullTimerHud(pullTimerConfig, "Pull Timer");
_hudElements.Add(pullTimerConfig, pullTimerHud);
_hudElementsUsingPlayer.Add(pullTimerHud);
var limitBreakConfig = ConfigurationManager.Instance.GetConfigObject<LimitBreakConfig>();
var limitBreakHud = new LimitBreakHud(limitBreakConfig, "Limit Break");
_hudElements.Add(limitBreakConfig, limitBreakHud);
var hotbarsConfig = ConfigurationManager.Instance.GetConfigObject<HotbarsConfig>();
HotbarBarConfig[] hotbarConfigs = new HotbarBarConfig[]
{
ConfigurationManager.Instance.GetConfigObject<Hotbar1BarConfig>(),
ConfigurationManager.Instance.GetConfigObject<Hotbar2BarConfig>(),
ConfigurationManager.Instance.GetConfigObject<Hotbar3BarConfig>(),
ConfigurationManager.Instance.GetConfigObject<Hotbar4BarConfig>(),
ConfigurationManager.Instance.GetConfigObject<Hotbar5BarConfig>(),
ConfigurationManager.Instance.GetConfigObject<Hotbar6BarConfig>(),
ConfigurationManager.Instance.GetConfigObject<Hotbar7BarConfig>(),
ConfigurationManager.Instance.GetConfigObject<Hotbar8BarConfig>(),
ConfigurationManager.Instance.GetConfigObject<Hotbar9BarConfig>(),
ConfigurationManager.Instance.GetConfigObject<Hotbar10BarConfig>()
};
for (int i = 0; i < hotbarConfigs.Length; i++)
{
var barConfig = hotbarConfigs[i];
var barHud = new ActionBarsHud(barConfig, $"Hotbar {i + 1}");
_hudElements.Add(barConfig, barHud);
_hudElementsUsingPlayer.Add(barHud);
}
var partyCooldownsConfig = ConfigurationManager.Instance.GetConfigObject<PartyCooldownsConfig>();
var partyCooldownsHud = new PartyCooldownsHud(partyCooldownsConfig, "Party Cooldowns");
_hudElements.Add(partyCooldownsConfig, partyCooldownsHud);
_hudElementsWithPreview.Add(partyCooldownsHud);
}
public void Draw(uint jobId)
{
if (!FontsManager.Instance.DefaultFontBuilt)
{
Plugin.UiBuilder.FontAtlas.BuildFontsAsync();
}
try
{
PullTimerHelper.Instance.Update();
}
catch { }
TooltipsHelper.Instance.RemoveTooltip(); // remove tooltip from previous frame
_hudHelper.Update();
if (!ShouldBeVisible())
{
return;
}
WhosTalkingHelper.Instance?.Update();
ClipRectsHelper.Instance.Update();
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0);
ImGuiHelpers.ForceNextWindowMainViewport();
ImGui.SetNextWindowPos(Vector2.Zero);
ImGui.SetNextWindowSize(ImGui.GetMainViewport().Size);
var begin = ImGui.Begin(
"HSUI_HUD",
ImGuiWindowFlags.NoTitleBar
| ImGuiWindowFlags.NoScrollbar
| ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoInputs
| ImGuiWindowFlags.NoBringToFrontOnFocus
| ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoSavedSettings
);
ImGui.PopStyleVar(3);
if (!begin)
{
ImGui.End();
return;
}
UpdateJob(jobId);
AssignActors();
var origin = ImGui.GetMainViewport().Size / 2f;
if (_hudOptions != null && _hudOptions.UseGlobalHudShift)
{
origin += _hudOptions.HudOffset;
}
// show only castbar during quest events
if (ShouldOnlyShowCastbar())
{
_playerCastbarHud?.PrepareForDraw(origin);
_playerCastbarHud?.Draw(origin);
ImGui.End();
return;
}
// grid
if (_gridConfig is not null && _gridConfig.Enabled)
{
DraggablesHelper.DrawGrid(_gridConfig, _hudOptions, _selectedElement);
}
// nameplates
if (_nameplatesHud.GetConfig().Enabled)
{
ClipRectsHelper.Instance?.AddNameplatesClipRects();
_nameplatesHud.PrepareForDraw(origin);
_nameplatesHud.Draw(origin);
ClipRectsHelper.Instance?.RemoveNameplatesClipRects();
}
// draw elements
lock (_hudElements)
{
DraggablesHelper.DrawElements(origin, _hudHelper, _hudElements.Values, _jobHud, _selectedElement);
}
// tooltip
TooltipsHelper.Instance.Draw();
ImGui.End();
}
protected unsafe bool ShouldBeVisible()
{
if (!ConfigurationManager.Instance.ShowHUD || Plugin.ObjectTable.LocalPlayer == null)
{
return false;
}
bool hudHidden =
Plugin.Condition[ConditionFlag.WatchingCutscene] ||
Plugin.Condition[ConditionFlag.WatchingCutscene78] ||
Plugin.Condition[ConditionFlag.OccupiedInCutSceneEvent] ||
Plugin.Condition[ConditionFlag.CreatingCharacter] ||
Plugin.Condition[ConditionFlag.BetweenAreas] ||
Plugin.Condition[ConditionFlag.BetweenAreas51] ||
Plugin.Condition[ConditionFlag.OccupiedSummoningBell];
if (hudHidden)
{
return false;
}
AtkUnitBase* parameterWidget = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_ParameterWidget", 1).Address;
AtkUnitBase* fadeMiddleWidget = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("FadeMiddle", 1).Address;
bool paramenterVisible = parameterWidget != null && parameterWidget->IsVisible;
bool fadeMiddleVisible = fadeMiddleWidget != null && fadeMiddleWidget->IsVisible;
return paramenterVisible && !fadeMiddleVisible;
}
protected bool ShouldOnlyShowCastbar()
{
// when in quest dialogs and events, hide everything except castbars
// this includes talking to npcs or interacting with quest related stuff
if (Plugin.Condition[ConditionFlag.OccupiedInQuestEvent] ||
Plugin.Condition[ConditionFlag.OccupiedInEvent])
{
// we have to wait a bit to avoid weird flickering when clicking shiny stuff
// we hide HSUI after half a second passed in this state
// interestingly enough, default hotbars seem to do something similar
var time = ImGui.GetTime();
if (_occupiedInQuestStartTime > 0)
{
if (time - _occupiedInQuestStartTime > 0.5)
{
return true;
}
}
else
{
_occupiedInQuestStartTime = time;
}
}
else
{
_occupiedInQuestStartTime = -1;
}
return false;
}
private void UpdateJob(uint newJobId)
{
if (_jobHud != null && _jobHud.Config.JobId == newJobId)
{
return;
}
JobConfig? config = null;
// unsupported jobs
if (_unsupportedJobsMap.ContainsKey(newJobId) && _unsupportedJobsMap.TryGetValue(newJobId, out var type))
{
config = (JobConfig)Activator.CreateInstance(type)!;
_jobHud = new JobHud(config);
}
// supported jobs
if (_jobsMap.TryGetValue(newJobId, out var types))
{
config = (JobConfig)ConfigurationManager.Instance.GetConfigObjectForType(types.ConfigType);
_jobHud = (JobHud)Activator.CreateInstance(types.HudType, config, types.DisplayName)!;
_jobHud.SelectEvent += OnDraggableElementSelected;
}
}
private void AssignActors()
{
// player
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
foreach (var element in _hudElementsUsingPlayer)
{
element.Actor = player;
if (_jobHud != null)
{
_jobHud.Actor = player;
}
}
// target
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
foreach (var element in _hudElementsUsingTarget)
{
element.Actor = target;
if (_customEffectsHud != null)
{
_customEffectsHud.TargetActor = target;
}
}
// target of target
IGameObject? targetOfTarget = Utils.FindTargetOfTarget(target, player, Plugin.ObjectTable);
foreach (var element in _hudElementsUsingTargetOfTarget)
{
element.Actor = targetOfTarget;
}
// focus
IGameObject? focusTarget = Plugin.TargetManager.FocusTarget;
foreach (var element in _hudElementsUsingFocusTarget)
{
element.Actor = focusTarget;
}
// player mana bar
if (_jobHud != null && _playerManaBarHud != null && !_jobHud.Config.UseDefaultPrimaryResourceBar)
{
_playerManaBarHud.ResourceType = PrimaryResourceTypes.None;
}
}
protected void CreateJobsMap()
{
_jobsMap = new Dictionary<uint, JobHudTypes>()
{
// tanks
[JobIDs.PLD] = new JobHudTypes(typeof(PaladinHud), typeof(PaladinConfig), "Paladin HUD"),
[JobIDs.WAR] = new JobHudTypes(typeof(WarriorHud), typeof(WarriorConfig), "Warrior HUD"),
[JobIDs.DRK] = new JobHudTypes(typeof(DarkKnightHud), typeof(DarkKnightConfig), "Dark Knight HUD"),
[JobIDs.GNB] = new JobHudTypes(typeof(GunbreakerHud), typeof(GunbreakerConfig), "Gunbreaker HUD"),
// healers
[JobIDs.WHM] = new JobHudTypes(typeof(WhiteMageHud), typeof(WhiteMageConfig), "White Mage HUD"),
[JobIDs.SCH] = new JobHudTypes(typeof(ScholarHud), typeof(ScholarConfig), "Scholar HUD"),
[JobIDs.AST] = new JobHudTypes(typeof(AstrologianHud), typeof(AstrologianConfig), "Astrologian HUD"),
[JobIDs.SGE] = new JobHudTypes(typeof(SageHud), typeof(SageConfig), "Sage HUD"),
// melee
[JobIDs.MNK] = new JobHudTypes(typeof(MonkHud), typeof(MonkConfig), "Monk HUD"),
[JobIDs.DRG] = new JobHudTypes(typeof(DragoonHud), typeof(DragoonConfig), "Dragoon HUD"),
[JobIDs.NIN] = new JobHudTypes(typeof(NinjaHud), typeof(NinjaConfig), "Ninja HUD"),
[JobIDs.SAM] = new JobHudTypes(typeof(SamuraiHud), typeof(SamuraiConfig), "Samurai HUD"),
[JobIDs.RPR] = new JobHudTypes(typeof(ReaperHud), typeof(ReaperConfig), "Reaper HUD"),
[JobIDs.VPR] = new JobHudTypes(typeof(ViperHud), typeof(ViperConfig), "Viper HUD"),
// ranged
[JobIDs.BRD] = new JobHudTypes(typeof(BardHud), typeof(BardConfig), "Bard HUD"),
[JobIDs.MCH] = new JobHudTypes(typeof(MachinistHud), typeof(MachinistConfig), "Mechanic HUD"),
[JobIDs.DNC] = new JobHudTypes(typeof(DancerHud), typeof(DancerConfig), "Dancer HUD"),
// casters
[JobIDs.BLM] = new JobHudTypes(typeof(BlackMageHud), typeof(BlackMageConfig), "Black Mage HUD"),
[JobIDs.SMN] = new JobHudTypes(typeof(SummonerHud), typeof(SummonerConfig), "Summoner HUD"),
[JobIDs.RDM] = new JobHudTypes(typeof(RedMageHud), typeof(RedMageConfig), "Red Mage HUD"),
[JobIDs.BLU] = new JobHudTypes(typeof(BlueMageHud), typeof(BlueMageConfig), "Blue Mage HUD"),
[JobIDs.PCT] = new JobHudTypes(typeof(PictomancerHud), typeof(PictomancerConfig), "Pictomancer HUD")
};
_unsupportedJobsMap = new Dictionary<uint, Type>()
{
// base jobs
[JobIDs.GLA] = typeof(GladiatorConfig),
[JobIDs.MRD] = typeof(MarauderConfig),
[JobIDs.PGL] = typeof(PugilistConfig),
[JobIDs.LNC] = typeof(LancerConfig),
[JobIDs.ROG] = typeof(RogueConfig),
[JobIDs.ARC] = typeof(ArcherConfig),
[JobIDs.THM] = typeof(ThaumaturgeConfig),
[JobIDs.ACN] = typeof(ArcanistConfig),
[JobIDs.CNJ] = typeof(ConjurerConfig),
// crafters
[JobIDs.CRP] = typeof(CarpenterConfig),
[JobIDs.BSM] = typeof(BlacksmithConfig),
[JobIDs.ARM] = typeof(ArmorerConfig),
[JobIDs.GSM] = typeof(GoldsmithConfig),
[JobIDs.LTW] = typeof(LeatherworkerConfig),
[JobIDs.WVR] = typeof(WeaverConfig),
[JobIDs.ALC] = typeof(AlchemistConfig),
[JobIDs.CUL] = typeof(CulinarianConfig),
// gatherers
[JobIDs.MIN] = typeof(MinerConfig),
[JobIDs.BOT] = typeof(BotanistConfig),
[JobIDs.FSH] = typeof(FisherConfig)
};
_jobTypes = new List<Type>()
{
typeof(PaladinConfig),
typeof(WarriorConfig),
typeof(DarkKnightConfig),
typeof(GunbreakerConfig),
typeof(WhiteMageConfig),
typeof(ScholarConfig),
typeof(AstrologianConfig),
typeof(SageConfig),
typeof(MonkConfig),
typeof(DragoonConfig),
typeof(NinjaConfig),
typeof(SamuraiConfig),
typeof(ReaperConfig),
typeof(ViperConfig),
typeof(BardConfig),
typeof(MachinistConfig),
typeof(DancerConfig),
typeof(BlackMageConfig),
typeof(SummonerConfig),
typeof(RedMageConfig),
typeof(BlueMageConfig),
typeof(PictomancerConfig)
};
}
}
internal struct JobHudTypes
{
public Type HudType;
public Type ConfigType;
public string DisplayName;
public JobHudTypes(Type hudType, Type configType, string displayName)
{
HudType = hudType;
ConfigType = configType;
DisplayName = displayName;
}
}
public static class ForcedJob
{
internal static bool Enabled;
internal static uint ForcedJobId;
}
internal static class HUDConstants
{
internal static int BaseHUDOffsetY = (int)(ImGui.GetMainViewport().Size.Y * 0.3f);
internal static int UnitFramesOffsetX = 160;
internal static int PlayerCastbarY = BaseHUDOffsetY - 13;
internal static int JobHudsBaseY = PlayerCastbarY - 14;
internal static Vector2 DefaultBigUnitFrameSize = new Vector2(270, 50);
internal static Vector2 DefaultSmallUnitFrameSize = new Vector2(120, 20);
internal static Vector2 DefaultStatusEffectsListSize = new Vector2(292, 82);
internal static Vector2 DefaultPlayerNameplateBarSize = new Vector2(120, 10);
internal static Vector2 DefaultEnemyNameplateBarSize = new Vector2(220, 26);
}
}
+273
View File
@@ -0,0 +1,273 @@
using Dalamud.Game.ClientState.JobGauge.Enums;
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;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class AstrologianHud : JobHud
{
private readonly SpellHelper _spellHelper = new();
private new AstrologianConfig Config => (AstrologianConfig)_config;
private static PluginConfigColor EmptyColor => GlobalColors.Instance.EmptyColor;
private static readonly List<uint> DotIDs = new() { 1881, 843, 838 };
private static readonly List<float> DotDuration = new() { 30f, 30f, 18f };
private const float STAR_MAX_DURATION = 10f;
private const float LIGHTSPEED_MAX_DURATION = 15f;
public AstrologianHud(JobConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new();
List<Vector2> sizes = new();
if (Config.CardsBar.Enabled)
{
positions.Add(Config.Position + Config.CardsBar.Position);
sizes.Add(Config.CardsBar.Size);
}
if (Config.DotBar.Enabled)
{
positions.Add(Config.Position + Config.DotBar.Position);
sizes.Add(Config.DotBar.Size);
}
if (Config.StarBar.Enabled)
{
positions.Add(Config.Position + Config.StarBar.Position);
sizes.Add(Config.StarBar.Size);
}
if (Config.LightspeedBar.Enabled)
{
positions.Add(Config.Position + Config.LightspeedBar.Position);
sizes.Add(Config.LightspeedBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.CardsBar.Enabled)
{
DrawCardsBar(pos, player);
}
if (Config.DotBar.Enabled)
{
DrawDot(pos, player);
}
if (Config.LightspeedBar.Enabled)
{
DrawLightspeed(pos, player);
}
if (Config.StarBar.Enabled)
{
DrawStar(pos, player);
}
}
private unsafe void DrawCardsBar(Vector2 origin, IPlayerCharacter player)
{
AstrologianCardsBarConfig config = Config.CardsBar;
PluginConfigColor emptyColor = PluginConfigColor.Empty;
List<Tuple<PluginConfigColor, float, LabelConfig?>> chunks = new();
uint play1 = ActionManager.Instance()->GetAdjustedActionId(37019);
PluginConfigColor play1Color = play1 == 37023 ? config.TheBalanceColor : (play1 == 37026 ? config.TheSpearColor : emptyColor);
chunks.Add(new(play1Color, 1, null));
uint play2 = ActionManager.Instance()->GetAdjustedActionId(37020);
PluginConfigColor play2Color = play2 == 37024 ? config.TheArrowColor : (play2 == 37027 ? config.TheBoleColor : emptyColor);
chunks.Add(new(play2Color, 1, null));
uint play3 = ActionManager.Instance()->GetAdjustedActionId(37021);
PluginConfigColor play3Color = play3 == 37025 ? config.TheSpireColor : (play3 == 37028 ? config.TheEwerColor : emptyColor);
chunks.Add(new(play3Color, 1, null));
if (player.Level >= 70)
{
uint minorArcana = ActionManager.Instance()->GetAdjustedActionId(37022);
PluginConfigColor minorArcanaColor = minorArcana == 7444 ? config.TheLordOfCrownsColor : (minorArcana == 7445 ? config.TheLadyOfCrownsColor : emptyColor);
chunks.Add(new(minorArcanaColor, 1, null));
}
BarHud[] bars = BarUtilities.GetChunkedBars(config, chunks.ToArray(), player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
private void DrawDot(Vector2 origin, IPlayerCharacter player)
{
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
BarHud? bar = BarUtilities.GetDoTBar(Config.DotBar, player, target, DotIDs, DotDuration);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.DotBar.StrataLevel));
}
}
private void DrawLightspeed(Vector2 origin, IPlayerCharacter player)
{
float lightspeedDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 841 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (Config.LightspeedBar.HideWhenInactive && lightspeedDuration <= 0)
{
return;
}
Config.LightspeedBar.Label.SetValue(lightspeedDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.LightspeedBar, lightspeedDuration, LIGHTSPEED_MAX_DURATION, 0, player);
AddDrawActions(bar.GetDrawActions(origin, Config.LightspeedBar.StrataLevel));
}
private void DrawStar(Vector2 origin, IPlayerCharacter player)
{
float starPreCookingBuff = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1224 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
float starPostCookingBuff = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1248 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (Config.StarBar.HideWhenInactive && starPostCookingBuff <= 0f && starPreCookingBuff <= 0f)
{
return;
}
float currentStarDuration = starPreCookingBuff > 0 ? STAR_MAX_DURATION - Math.Abs(starPreCookingBuff) : Math.Abs(starPostCookingBuff);
PluginConfigColor currentStarColor = starPreCookingBuff > 0 ? Config.StarBar.StarEarthlyColor : Config.StarBar.StarGiantColor;
Config.StarBar.Label.SetValue(currentStarDuration);
// Star Countdown after Star is ready
BarHud bar = BarUtilities.GetProgressBar(Config.StarBar, currentStarDuration, STAR_MAX_DURATION, 0f, player, currentStarColor, Config.StarBar.StarGlowConfig.Enabled && starPostCookingBuff > 0 ? Config.StarBar.StarGlowConfig : null);
AddDrawActions(bar.GetDrawActions(origin, Config.StarBar.StrataLevel));
}
}
[Section("Job Specific Bars")]
[SubSection("Healer", 0)]
[SubSection("Astrologian", 1)]
public class AstrologianConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.AST;
public new static AstrologianConfig DefaultConfig()
{
var config = new AstrologianConfig();
config.UseDefaultPrimaryResourceBar = true;
return config;
}
[NestedConfig("Cards Bar", 40)]
public AstrologianCardsBarConfig CardsBar = new(
new Vector2(0, 0),
new Vector2(254, 20)
);
[NestedConfig("Dot Bar", 40)]
public ProgressBarConfig DotBar = new(
new Vector2(-85, -29),
new Vector2(84, 14),
new PluginConfigColor(new Vector4(20f / 255f, 80f / 255f, 168f / 255f, 255f / 100f))
);
[NestedConfig("Star Bar", 45)]
public AstrologianStarBarConfig StarBar = new(
new Vector2(0, -29),
new Vector2(84, 14)
);
[NestedConfig("Lightspeed Bar", 50)]
public ProgressBarConfig LightspeedBar = new(
new Vector2(85, -29),
new Vector2(84, 14),
new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 173f / 255f, 100f / 100f))
);
}
[Exportable(false)]
[DisableParentSettings("FillColor", "UsePartialFillColor", "UseChunks", "PartialFillColor", "LabelMode", "HideWhenInactive")]
public class AstrologianCardsBarConfig : ChunkedBarConfig
{
[ColorEdit4("The Balance Color", spacing = true)]
[Order(201)]
public PluginConfigColor TheBalanceColor = PluginConfigColor.FromHex(0xFFBE423F);
[ColorEdit4("The Arrow Color")]
[Order(201)]
public PluginConfigColor TheArrowColor = PluginConfigColor.FromHex(0xFF628AA7);
[ColorEdit4("The Spire Color")]
[Order(201)]
public PluginConfigColor TheSpireColor = PluginConfigColor.FromHex(0xFFC8A348);
[ColorEdit4("The Spear Color")]
[Order(201)]
public PluginConfigColor TheSpearColor = PluginConfigColor.FromHex(0xFF5673DF);
[ColorEdit4("The Bole Color")]
[Order(201)]
public PluginConfigColor TheBoleColor = PluginConfigColor.FromHex(0xFF9ACB77);
[ColorEdit4("The Ewer Color")]
[Order(201)]
public PluginConfigColor TheEwerColor = PluginConfigColor.FromHex(0xFF7FBDFF);
[ColorEdit4("The Lord of Crowns Color")]
[Order(201)]
public PluginConfigColor TheLordOfCrownsColor = PluginConfigColor.FromHex(0xFFCA3640);
[ColorEdit4("The Lady of Crowns Color")]
[Order(201)]
public PluginConfigColor TheLadyOfCrownsColor = PluginConfigColor.FromHex(0xFF974A97);
public AstrologianCardsBarConfig(Vector2 position, Vector2 size)
: base(position, size, PluginConfigColor.Empty)
{
}
}
[Exportable(false)]
[DisableParentSettings("FillColor")]
public class AstrologianStarBarConfig : ProgressBarConfig
{
[ColorEdit4("Earthly" + "##Star")]
[Order(402)]
public PluginConfigColor StarEarthlyColor = new(new Vector4(37f / 255f, 181f / 255f, 177f / 255f, 100f / 100f));
[ColorEdit4("Giant" + "##Star")]
[Order(403)]
public PluginConfigColor StarGiantColor = new(new Vector4(198f / 255f, 154f / 255f, 199f / 255f, 100f / 100f));
[NestedConfig("Giant Dominance Glow" + "##Star", 404, separator = false, spacing = true)]
public BarGlowConfig StarGlowConfig = new();
public AstrologianStarBarConfig(Vector2 position, Vector2 size)
: base(position, size, PluginConfigColor.Empty)
{
}
}
}
+489
View File
@@ -0,0 +1,489 @@
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<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
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<BRDGauge>();
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<PluginConfigColor, float, LabelConfig?>[3];
for (int i = 0; i < 3; i++)
{
coda[i] = new Tuple<PluginConfigColor, float, LabelConfig?>(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<uint> CausticBiteDoTIDs = new List<uint> { 124, 1200 };
private static List<float> CausticBiteDoTDurations = new List<float> { 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<uint> StormbiteDoTIDs = new List<uint> { 129, 1201 };
private static List<float> StormbiteDoTDurations = new List<float> { 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<BRDGauge>();
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<BRDGauge>().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) { }
}
}
+60
View File
@@ -0,0 +1,60 @@
using HSUI.Helpers;
using Newtonsoft.Json;
namespace HSUI.Interface.Jobs
{
public class BaseJobsConfig : JobConfig
{
public override uint JobId => 0;
public BaseJobsConfig()
{
UseDefaultPrimaryResourceBar = true;
}
}
public class GladiatorConfig : BaseJobsConfig
{
[JsonIgnore] public override uint JobId => JobIDs.GLA;
}
public class MarauderConfig : BaseJobsConfig
{
[JsonIgnore] public override uint JobId => JobIDs.MRD;
}
public class PugilistConfig : BaseJobsConfig
{
[JsonIgnore] public override uint JobId => JobIDs.PGL;
}
public class LancerConfig : BaseJobsConfig
{
[JsonIgnore] public override uint JobId => JobIDs.LNC;
}
public class RogueConfig : BaseJobsConfig
{
[JsonIgnore] public override uint JobId => JobIDs.ROG;
}
public class ArcherConfig : BaseJobsConfig
{
[JsonIgnore] public override uint JobId => JobIDs.ARC;
}
public class ThaumaturgeConfig : BaseJobsConfig
{
[JsonIgnore] public override uint JobId => JobIDs.THM;
}
public class ArcanistConfig : BaseJobsConfig
{
[JsonIgnore] public override uint JobId => JobIDs.ACN;
}
public class ConjurerConfig : BaseJobsConfig
{
[JsonIgnore] public override uint JobId => JobIDs.CNJ;
}
}
+588
View File
@@ -0,0 +1,588 @@
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<uint> ThunderDoTIDs = new() { 161, 162, 163, 1210, 3871, 3872 };
private static readonly List<float> ThunderDoTDurations = new() { 24, 18, 27, 21, 30, 24 };
public BlackMageHud(BlackMageConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
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<BLMGauge>();
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<BLMGauge>();
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<BLMGauge>();
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<BLMGauge>();
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<BLMGauge>();
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<BLMGauge>();
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<BLMGauge>();
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;
}
+313
View File
@@ -0,0 +1,313 @@
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 Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class BlueMageHud : JobHud
{
private new BlueMageConfig Config => (BlueMageConfig)_config;
public BlueMageHud(BlueMageConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.BleedBar.Enabled)
{
positions.Add(Config.Position + Config.BleedBar.Position);
sizes.Add(Config.BleedBar.Size);
}
if (Config.WindburnBar.Enabled)
{
positions.Add(Config.Position + Config.WindburnBar.Position);
sizes.Add(Config.WindburnBar.Size);
}
if (Config.SurpanakhaBar.Enabled)
{
positions.Add(Config.Position + Config.SurpanakhaBar.Position);
sizes.Add(Config.SurpanakhaBar.Size);
}
if (Config.OffGuardBar.Enabled)
{
positions.Add(Config.Position + Config.OffGuardBar.Position);
sizes.Add(Config.OffGuardBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.BleedBar.Enabled)
{
DrawBleedBar(pos, player);
}
if (Config.WindburnBar.Enabled)
{
DrawWindburnBar(pos, player);
}
if (Config.SurpanakhaBar.Enabled)
{
DrawSurpanakhaBar(pos, player);
}
if (Config.OffGuardBar.Enabled)
{
DrawOffGuardBar(pos, player);
}
if (Config.MoonFluteBar.Enabled)
{
DrawMoonFluteBar(pos, player);
}
if (Config.SpellAmpBar.Enabled)
{
DrawSpellAmpBar(pos, player);
}
if (Config.TingleBar.Enabled)
{
DrawTingleBar(pos, player);
}
}
private static List<uint> BleedID = new List<uint> { 1714 };
private static List<float> BleedDurations = new List<float> { 30, 60 };
protected void DrawBleedBar(Vector2 origin, IPlayerCharacter player)
{
var target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
BarHud? bar = BarUtilities.GetDoTBar(Config.BleedBar, player, target, BleedID, BleedDurations);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.BleedBar.StrataLevel));
}
}
protected void DrawWindburnBar(Vector2 origin, IPlayerCharacter player)
{
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
bool dotExists = false;
if (target != null && target is IBattleChara targetChara)
{
dotExists = Utils.StatusListForBattleChara(targetChara).FirstOrDefault(o => o.SourceId == player.GameObjectId && o.StatusId == 1723) != null;
}
if (dotExists)
{
BarHud? bar = BarUtilities.GetDoTBar(Config.WindburnBar, player, target, 1723, 6f);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.WindburnBar.StrataLevel));
}
}
else
{
float featherRainCD = SpellHelper.Instance.GetSpellCooldown(11426);
float max = 30f;
float current = max - featherRainCD;
if (!Config.WindburnBar.HideWhenInactive || current < max)
{
Config.WindburnBar.Label.SetValue(max - current);
if (current == max)
{
Config.WindburnBar.Label.SetText("Ready");
}
BarHud bar = BarUtilities.GetProgressBar(Config.WindburnBar, current, max, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.WindburnBar.StrataLevel));
}
}
}
protected void DrawSurpanakhaBar(Vector2 origin, IPlayerCharacter player)
{
float surpanakhaCD = SpellHelper.Instance.GetSpellCooldown(18323);
float max = 120f;
float current = max - surpanakhaCD;
if (!Config.SurpanakhaBar.HideWhenInactive || current < max)
{
Config.SurpanakhaBar.Label.SetValue((max - current) % 30);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.SurpanakhaBar, 4, current, max, 0f, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.SurpanakhaBar.StrataLevel));
}
}
}
protected void DrawOffGuardBar(Vector2 origin, IPlayerCharacter player)
{
var target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
BarHud? bar = BarUtilities.GetDoTBar(Config.OffGuardBar, player, target, 1717, 15f);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.OffGuardBar.StrataLevel));
}
}
protected void DrawMoonFluteBar(Vector2 origin, IPlayerCharacter player)
{
IStatus? buff = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1718 or 1727 && o.RemainingTime > 0f);
if (!Config.MoonFluteBar.HideWhenInactive || buff is not null)
{
var buffColor = buff is not null ? buff.StatusId switch
{
1718 => Config.MoonFluteBar.WaxingCrescentColor,
1727 => Config.MoonFluteBar.WaningCrescentColor,
_ => Config.MoonFluteBar.WaxingCrescentColor
} : Config.MoonFluteBar.WaxingCrescentColor;
float buffDuration = buff?.RemainingTime ?? 0f;
Config.MoonFluteBar.Label.SetValue(buffDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.MoonFluteBar, buffDuration, 15f, 0, player, fillColor: buffColor);
AddDrawActions(bar.GetDrawActions(origin, Config.MoonFluteBar.StrataLevel));
}
}
protected void DrawSpellAmpBar(Vector2 origin, IPlayerCharacter player)
{
IStatus? buff = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 2118 or 1716 && o.RemainingTime > 0f);
if (!Config.SpellAmpBar.HideWhenInactive || buff is not null)
{
var buffColor = buff is not null ? buff.StatusId switch
{
2118 => Config.SpellAmpBar.BristleColor,
1716 => Config.SpellAmpBar.WhistleColor,
_ => Config.SpellAmpBar.BristleColor
} : Config.SpellAmpBar.BristleColor;
float buffDuration = buff?.RemainingTime ?? 0f;
Config.SpellAmpBar.Label.SetValue(buffDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.SpellAmpBar, buffDuration, 30f, 0, player, fillColor: buffColor);
AddDrawActions(bar.GetDrawActions(origin, Config.SpellAmpBar.StrataLevel));
}
}
protected void DrawTingleBar(Vector2 origin, IPlayerCharacter player)
{
BarHud? bar = BarUtilities.GetProcBar(Config.TingleBar, player, 2492, 15f);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.TingleBar.StrataLevel));
}
}
}
[Section("Job Specific Bars")]
[SubSection("Caster", 0)]
[SubSection("Blue Mage", 1)]
public class BlueMageConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.BLU;
public new static BlueMageConfig DefaultConfig()
{
var config = new BlueMageConfig();
config.UseDefaultPrimaryResourceBar = true;
return config;
}
[NestedConfig("Bleed Bar", 40)]
public ProgressBarConfig BleedBar = new ProgressBarConfig(
new(-64, -55),
new(126, 14),
new PluginConfigColor(new Vector4(106f / 255f, 237f / 255f, 241f / 255f, 100f / 100f)),
BarDirection.Left
);
[NestedConfig("Windburn Bar", 45)]
public ProgressBarConfig WindburnBar = new ProgressBarConfig(
new(64, -55),
new(126, 14),
new PluginConfigColor(new Vector4(50f / 255f, 93f / 255f, 37f / 255f, 100f / 100f))
);
[NestedConfig("Surpanakha Bar", 50)]
public ChunkedProgressBarConfig SurpanakhaBar = new ChunkedProgressBarConfig(
new(0, -39),
new(254, 14),
new PluginConfigColor(new Vector4(202f / 255f, 228f / 255f, 246f / 242f, 100f / 100f))
);
[NestedConfig("Off-Guard Bar", 55)]
public ProgressBarConfig OffGuardBar = new ProgressBarConfig(
new(0, -23),
new(254, 14),
new PluginConfigColor(new Vector4(202f / 255f, 228f / 255f, 246f / 242f, 100f / 100f))
);
[NestedConfig("Moon Flute Bar", 60)]
public MoonFluteBarConfig MoonFluteBar = new MoonFluteBarConfig(
new(0, -7),
new(84, 14),
new(new Vector4(128f / 255f, 255f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Spell Amp Bar", 65)]
public SpellAmpBarConfig SpellAmpBar = new SpellAmpBarConfig(
new(-86, -7),
new(82, 14),
new(new Vector4(128f / 255f, 255f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Tingle Bar", 70)]
public ProgressBarConfig TingleBar = new ProgressBarConfig(
new(86, -7),
new(82, 14),
new(new Vector4(128f / 255f, 255f / 255f, 255f / 255f, 100f / 100f))
);
}
[Exportable(false)]
public class MoonFluteBarConfig : ProgressBarConfig
{
[ColorEdit4("Waning Crescent Color")]
[Order(26)]
public PluginConfigColor WaxingCrescentColor = new(new Vector4(0f / 255f, 255f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Waxing Crescent Color")]
[Order(27)]
public PluginConfigColor WaningCrescentColor = new(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
public MoonFluteBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
[Exportable(false)]
public class SpellAmpBarConfig : ProgressBarConfig
{
[ColorEdit4("Waning Crescent Color")]
[Order(26)]
public PluginConfigColor BristleColor = new(new Vector4(0f / 255f, 255f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Waxing Crescent Color")]
[Order(27)]
public PluginConfigColor WhistleColor = new(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
public SpellAmpBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
}
+56
View File
@@ -0,0 +1,56 @@
using HSUI.Helpers;
using Newtonsoft.Json;
namespace HSUI.Interface.Jobs
{
public class CraftersConfig : JobConfig
{
public override uint JobId => 0;
public CraftersConfig()
{
UseDefaultPrimaryResourceBar = true;
PrimaryResourceType = PrimaryResourceTypes.CP;
}
}
public class CarpenterConfig : CraftersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.CRP;
}
public class BlacksmithConfig : CraftersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.BSM;
}
public class ArmorerConfig : CraftersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.ARM;
}
public class GoldsmithConfig : CraftersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.GSM;
}
public class LeatherworkerConfig : CraftersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.LTW;
}
public class WeaverConfig : CraftersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.WVR;
}
public class AlchemistConfig : CraftersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.ALC;
}
public class CulinarianConfig : CraftersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.CUL;
}
}
+430
View File
@@ -0,0 +1,430 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Statuses;
using HSUI.Config;
using HSUI.Config.Attributes;
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 DancerHud : JobHud
{
private new DancerConfig Config => (DancerConfig)_config;
public DancerHud(DancerConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.StandardFinishBar.Enabled)
{
positions.Add(Config.Position + Config.StandardFinishBar.Position);
sizes.Add(Config.StandardFinishBar.Size);
}
if (Config.TechnicalFinishBar.Enabled)
{
positions.Add(Config.Position + Config.TechnicalFinishBar.Position);
sizes.Add(Config.TechnicalFinishBar.Position);
}
if (Config.DevilmentBar.Enabled)
{
positions.Add(Config.Position + Config.DevilmentBar.Position);
sizes.Add(Config.DevilmentBar.Position);
}
if (Config.EspritGauge.Enabled)
{
positions.Add(Config.Position + Config.EspritGauge.Position);
sizes.Add(Config.EspritGauge.Size);
}
if (Config.FeatherGauge.Enabled)
{
positions.Add(Config.Position + Config.FeatherGauge.Position);
sizes.Add(Config.FeatherGauge.Size);
}
if (Config.CascadeBar.Enabled)
{
positions.Add(Config.Position + Config.CascadeBar.Position);
sizes.Add(Config.CascadeBar.Position);
}
if (Config.FountainBar.Enabled)
{
positions.Add(Config.Position + Config.FountainBar.Position);
sizes.Add(Config.FountainBar.Position);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.EspritGauge.Enabled)
{
DrawEspritBar(pos, player);
}
if (Config.FeatherGauge.Enabled)
{
DrawFeathersBar(pos, player);
}
if (Config.StandardFinishBar.Enabled)
{
DrawStandardBar(pos, player);
}
if (Config.TechnicalFinishBar.Enabled)
{
DrawTechnicalBar(pos, player);
}
if (Config.DevilmentBar.Enabled)
{
DrawDevilmentBar(pos, player);
}
bool showingStepBar = false;
if (Config.StepsBar.Enabled)
{
showingStepBar = DrawStepBar(pos, player);
}
if (!showingStepBar || !Config.StepsBar.HideProcs)
{
if (Config.CascadeBar.Enabled) { DrawProcBar(pos, player, Config.CascadeBar, 2693, 3017); }
if (Config.FountainBar.Enabled) { DrawProcBar(pos, player, Config.FountainBar, 2694, 3018); }
}
}
private void DrawProcBar(Vector2 origin, IPlayerCharacter player, DancerProcBarConfig config, params uint[] statusIDs)
{
List<float> durations = new List<float>();
for (int i = 0; i < statusIDs.Length; i++)
{
durations.Add(30f);
}
BarHud? bar = BarUtilities.GetProcBar(config, player, statusIDs.ToList(), durations, !config.IgnoreBuffDuration);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
private unsafe bool DrawStepBar(Vector2 origin, IPlayerCharacter player)
{
DNCGauge gauge = Plugin.JobGauges.Get<DNCGauge>();
if (!gauge.IsDancing)
{
return false;
}
List<Tuple<PluginConfigColor, float, LabelConfig?>> chunks = new List<Tuple<PluginConfigColor, float, LabelConfig?>>();
List<bool> glows = new List<bool>();
bool danceReady = true;
for (var i = 0; i < 4; i++)
{
DNCStep step = (DNCStep)gauge.Steps[i];
if (step == DNCStep.None)
{
break;
}
if (gauge.CompletedSteps == i)
{
glows.Add(true);
danceReady = false;
}
else
{
glows.Add(false);
}
PluginConfigColor color = PluginConfigColor.Empty;
switch (step)
{
case DNCStep.Emboite:
color = Config.StepsBar.EmboiteColor;
break;
case DNCStep.Entrechat:
color = Config.StepsBar.EntrechatColor;
break;
case DNCStep.Jete:
color = Config.StepsBar.JeteColor;
break;
case DNCStep.Pirouette:
color = Config.StepsBar.PirouetteColor;
break;
}
var tuple = new Tuple<PluginConfigColor, float, LabelConfig?>(color, 1, null);
chunks.Add(tuple);
}
if (danceReady)
{
for (int i = 0; i < glows.Count; i++)
{
glows[i] = true;
}
}
BarHud[] bars = BarUtilities.GetChunkedBars(Config.StepsBar, chunks.ToArray(), player, Config.StepsBar.GlowConfig, glows.ToArray());
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.StepsBar.StrataLevel));
}
return true;
}
private void DrawEspritBar(Vector2 origin, IPlayerCharacter player)
{
DNCGauge gauge = Plugin.JobGauges.Get<DNCGauge>();
if (Config.EspritGauge.HideWhenInactive && gauge.Esprit is 0) { return; }
Config.EspritGauge.Label.SetValue(gauge.Esprit);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.EspritGauge, 2, gauge.Esprit, 100, 0f, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.EspritGauge.StrataLevel));
}
}
private void DrawFeathersBar(Vector2 origin, IPlayerCharacter player)
{
DNCGauge gauge = Plugin.JobGauges.Get<DNCGauge>();
bool hasFlourishingBuff = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1820 or 2021) != null;
bool[]? glows = null;
if (Config.FeatherGauge.HideWhenInactive && gauge.Feathers is 0 && !hasFlourishingBuff)
{
return;
}
if (Config.FeatherGauge.GlowConfig.Enabled)
{
glows = new bool[] { hasFlourishingBuff, hasFlourishingBuff, hasFlourishingBuff, hasFlourishingBuff };
}
BarGlowConfig? config = hasFlourishingBuff ? Config.FeatherGauge.GlowConfig : null;
BarHud[] bars = BarUtilities.GetChunkedBars(Config.FeatherGauge, 4, gauge.Feathers, 4, 0, player, glowConfig: config, chunksToGlow: glows);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.FeatherGauge.StrataLevel));
}
}
private void DrawTechnicalBar(Vector2 origin, IPlayerCharacter player)
{
IEnumerable<IStatus> devilmentBuff = Utils.StatusListForBattleChara(player).Where(o => o.StatusId is 1825 && o.SourceId == player.GameObjectId);
float technicalFinishDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1822 or 2050 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.TechnicalFinishBar.HideWhenInactive || technicalFinishDuration > 0)
{
Config.TechnicalFinishBar.Label.SetValue(Math.Abs(technicalFinishDuration));
BarHud bar = BarUtilities.GetProgressBar(Config.TechnicalFinishBar, technicalFinishDuration, 20f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.TechnicalFinishBar.StrataLevel));
}
}
private void DrawDevilmentBar(Vector2 origin, IPlayerCharacter player)
{
float devilmentDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1825 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.DevilmentBar.HideWhenInactive || devilmentDuration > 0)
{
Config.DevilmentBar.Label.SetValue(Math.Abs(devilmentDuration));
BarHud bar = BarUtilities.GetProgressBar(Config.DevilmentBar, devilmentDuration, 20f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.DevilmentBar.StrataLevel));
}
}
private void DrawStandardBar(Vector2 origin, IPlayerCharacter player)
{
float standardFinishDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1821 or 2024 or 2105 or 2113 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.StandardFinishBar.HideWhenInactive || standardFinishDuration > 0)
{
Config.StandardFinishBar.Label.SetValue(Math.Abs(standardFinishDuration));
BarHud bar = BarUtilities.GetProgressBar(Config.StandardFinishBar, standardFinishDuration, 60f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.StandardFinishBar.StrataLevel));
}
}
}
public enum DNCStep : uint
{
None = 15998,
Emboite = 15999,
Entrechat = 16000,
Jete = 16001,
Pirouette = 16002
}
[Section("Job Specific Bars")]
[SubSection("Ranged", 0)]
[SubSection("Dancer", 1)]
public class DancerConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.DNC;
public new static DancerConfig DefaultConfig()
{
var config = new DancerConfig();
config.EspritGauge.UseChunks = false;
config.EspritGauge.Label.Enabled = true;
config.CascadeBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.FountainBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
return config;
}
[NestedConfig("Standard Finish Bar", 30)]
public ProgressBarConfig StandardFinishBar = new ProgressBarConfig(
new(0, -10),
new(254, 20),
new PluginConfigColor(new Vector4(0f / 255f, 193f / 255f, 95f / 255f, 100f / 100f))
);
[NestedConfig("Technical Finish Bar", 35)]
public ProgressBarConfig TechnicalFinishBar = new ProgressBarConfig(
new(-64, -32),
new(126, 20),
new PluginConfigColor(new Vector4(255f / 255f, 9f / 255f, 102f / 255f, 100f / 100f))
);
[NestedConfig("Devilment Bar", 40)]
public ProgressBarConfig DevilmentBar = new ProgressBarConfig(
new(64, -32),
new(126, 20),
new PluginConfigColor(new Vector4(52f / 255f, 78f / 255f, 29f / 255f, 100f / 100f))
);
[NestedConfig("Esprit Gauge", 45)]
public ChunkedProgressBarConfig EspritGauge = new ChunkedProgressBarConfig(
new(0, -54),
new(254, 20),
new PluginConfigColor(new Vector4(72f / 255f, 20f / 255f, 99f / 255f, 100f / 100f))
);
[NestedConfig("Feathers Gauge", 50)]
public DancerFeatherGaugeConfig FeatherGauge = new DancerFeatherGaugeConfig(
new(0, -71),
new(254, 10),
new PluginConfigColor(new Vector4(175f / 255f, 229f / 255f, 29f / 255f, 100f / 100f))
);
[NestedConfig("Flourishing Symmetry Bar", 60)]
public DancerProcBarConfig CascadeBar = new DancerProcBarConfig(
new(-96, -83),
new(62, 10),
new(new Vector4(0f / 255f, 255f / 255f, 0f / 255f, 100f / 100f))
);
[NestedConfig("Flourishing Flow Bar", 65)]
public DancerProcBarConfig FountainBar = new DancerProcBarConfig(
new(-32, -83),
new(62, 10),
new(new Vector4(255f / 255f, 215f / 255f, 0f / 255f, 100f / 100f))
);
[NestedConfig("Steps Bar", 80)]
public DancerStepsBarConfig StepsBar = new DancerStepsBarConfig(
new(0, -83),
new(254, 10)
);
}
[Exportable(false)]
public class DancerFeatherGaugeConfig : ChunkedBarConfig
{
[NestedConfig("Glow on Flourishing Fan Dance", 1000, separator = false, spacing = true)]
public BarGlowConfig GlowConfig = new BarGlowConfig();
public DancerFeatherGaugeConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
GlowConfig.Color = new PluginConfigColor(new Vector4(255f / 255f, 215f / 255f, 0f / 255f, 100f / 100f));
}
}
[Exportable(false)]
public class DancerProcBarConfig : ProgressBarConfig
{
[Checkbox("Ignore Buff Duration")]
[Order(4)]
public bool IgnoreBuffDuration = true;
public DancerProcBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
[DisableParentSettings("FillColor", "HideWhenInactive")]
[Exportable(false)]
public class DancerStepsBarConfig : ChunkedBarConfig
{
[Checkbox("Hide Procs When Active")]
[Order(50)]
public bool HideProcs = true;
[ColorEdit4("Emboite", spacing = true)]
[Order(55)]
public PluginConfigColor EmboiteColor = new(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Entrechat")]
[Order(60)]
public PluginConfigColor EntrechatColor = new(new Vector4(0f / 255f, 0f / 255f, 255f / 255f, 100f / 100f));
[ColorEdit4("Jete")]
[Order(65)]
public PluginConfigColor JeteColor = new(new Vector4(0f / 255f, 255f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Pirouette")]
[Order(70)]
public PluginConfigColor PirouetteColor = new(new Vector4(255f / 255f, 215f / 255f, 0f / 255f, 100f / 100f));
[NestedConfig("Glow", 75, separator = false, spacing = true)]
public BarGlowConfig GlowConfig = new BarGlowConfig();
public DancerStepsBarConfig(Vector2 position, Vector2 size)
: base(position, size, PluginConfigColor.Empty)
{
}
}
}
+344
View File
@@ -0,0 +1,344 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Config;
using HSUI.Config.Attributes;
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;
using Dalamud.Game.ClientState.Statuses;
namespace HSUI.Interface.Jobs
{
public class DarkKnightHud : JobHud
{
private new DarkKnightConfig Config => (DarkKnightConfig)_config;
public DarkKnightHud(DarkKnightConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new();
List<Vector2> sizes = new();
if (Config.ManaBar.Enabled)
{
positions.Add(Config.Position + Config.ManaBar.Position);
sizes.Add(Config.ManaBar.Size);
}
if (Config.BloodGauge.Enabled)
{
positions.Add(Config.Position + Config.BloodGauge.Position);
sizes.Add(Config.BloodGauge.Size);
}
if (Config.DarksideBar.Enabled)
{
positions.Add(Config.Position + Config.DarksideBar.Position);
sizes.Add(Config.DarksideBar.Size);
}
if (Config.BloodWeaponBar.Enabled)
{
positions.Add(Config.Position + Config.BloodWeaponBar.Position);
sizes.Add(Config.BloodWeaponBar.Size);
}
if (Config.DeliriumBar.Enabled)
{
positions.Add(Config.Position + Config.DeliriumBar.Position);
sizes.Add(Config.DeliriumBar.Size);
}
if (Config.LivingShadowBar.Enabled)
{
positions.Add(Config.Position + Config.LivingShadowBar.Position);
sizes.Add(Config.LivingShadowBar.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.BloodGauge.Enabled)
{
DrawBloodGauge(pos, player);
}
if (Config.DarksideBar.Enabled)
{
DrawDarkside(pos, player);
}
if (Config.BloodWeaponBar.Enabled)
{
DrawBloodWeaponBar(pos, player);
}
if (Config.DeliriumBar.Enabled)
{
DrawDeliriumBar(pos, player);
}
if (Config.LivingShadowBar.Enabled)
{
DrawLivingShadowBar(pos, player);
}
}
private unsafe void DrawManaBar(Vector2 origin, IPlayerCharacter player)
{
DRKGauge gauge = Plugin.JobGauges.Get<DRKGauge>();
if (Config.ManaBar.HideWhenInactive && !gauge.HasDarkArts && player.CurrentMp == player.MaxMp)
{
return;
}
Config.ManaBar.UsePartialFillColor = !gauge.HasDarkArts;
Config.ManaBar.Label.SetValue(player.CurrentMp);
// hardcoded 9k as maxMP so the chunks are each 3k since that's what a DRK wants to see
BarHud[] bars = BarUtilities.GetChunkedProgressBars(
Config.ManaBar,
gauge.HasDarkArts ? 1 : 3,
player.CurrentMp,
Config.ManaBar.ShowFullMana ? player.MaxMp : 9000,
0f,
player,
null,
gauge.HasDarkArts ? Config.ManaBar.DarkArtsColor : Config.ManaBar.FillColor
);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.ManaBar.StrataLevel));
}
}
private void DrawDarkside(Vector2 origin, IPlayerCharacter player)
{
DRKGauge gauge = Plugin.JobGauges.Get<DRKGauge>();
if (Config.DarksideBar.HideWhenInactive && gauge.DarksideTimeRemaining == 0)
{
return;
};
float timer = Math.Abs(gauge.DarksideTimeRemaining) / 1000;
Config.DarksideBar.Label.SetValue(timer);
BarHud bar = BarUtilities.GetProgressBar(Config.DarksideBar, timer, 60, 0, player);
AddDrawActions(bar.GetDrawActions(origin, Config.DarksideBar.StrataLevel));
}
private void DrawBloodGauge(Vector2 origin, IPlayerCharacter player)
{
DRKGauge gauge = Plugin.JobGauges.Get<DRKGauge>();
if (Config.BloodGauge.HideWhenInactive && gauge.Blood <= 0)
{
return;
}
Config.BloodGauge.Label.SetValue(gauge.Blood);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.BloodGauge, 2, gauge.Blood, 100, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.BloodGauge.StrataLevel));
}
}
private void DrawBloodWeaponBar(Vector2 origin, IPlayerCharacter player)
{
IStatus? bloodWeaponBuff = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 742);
float duration = bloodWeaponBuff?.RemainingTime ?? 0f;
int stacks = bloodWeaponBuff?.Param ?? 0;
if (Config.BloodWeaponBar.HideWhenInactive && duration <= 0)
{
return;
}
var chunks = new Tuple<PluginConfigColor, float, LabelConfig?>[3];
for (int i = 0; i < 3; i++)
{
chunks[i] = new(Config.BloodWeaponBar.FillColor, i < stacks ? 1 : 0, i == 1 ? Config.BloodWeaponBar.Label : null);
}
if(Config.BloodWeaponBar.FillDirection is BarDirection.Left or BarDirection.Up)
{
chunks = chunks.Reverse().ToArray();
}
Config.BloodWeaponBar.Label.SetValue(duration);
BarHud[] bars = BarUtilities.GetChunkedBars(Config.BloodWeaponBar, chunks, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.BloodWeaponBar.StrataLevel));
}
}
private void DrawDeliriumBar(Vector2 origin, IPlayerCharacter player)
{
IStatus? deliriumBuff = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1972);
float deliriumDuration = Math.Max(0f, deliriumBuff?.RemainingTime ?? 0f);
int stacks = deliriumBuff?.Param ?? 0;
if (Config.DeliriumBar.HideWhenInactive && deliriumDuration <= 0)
{
return;
}
var chunks = new Tuple<PluginConfigColor, float, LabelConfig?>[3];
for (int i = 0; i < 3; i++)
{
chunks[i] = new(Config.DeliriumBar.FillColor, i < stacks ? 1 : 0, i == 1 ? Config.DeliriumBar.Label : null);
}
if(Config.DeliriumBar.FillDirection is BarDirection.Left or BarDirection.Up)
{
chunks = chunks.Reverse().ToArray();
}
Config.DeliriumBar.Label.SetValue(deliriumDuration);
BarHud[] bars = BarUtilities.GetChunkedBars(Config.DeliriumBar, chunks, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.DeliriumBar.StrataLevel));
}
}
private unsafe void DrawLivingShadowBar(Vector2 origin, IPlayerCharacter player)
{
DRKGauge gauge = Plugin.JobGauges.Get<DRKGauge>();
if (Config.LivingShadowBar.HideWhenInactive && gauge.ShadowTimeRemaining <= 0)
{
return;
}
float timer = Math.Abs(gauge.ShadowTimeRemaining) / 1000;
Config.LivingShadowBar.Label.SetValue(timer);
BarHud bar = BarUtilities.GetProgressBar(Config.LivingShadowBar, timer, 20, 0, player);
AddDrawActions(bar.GetDrawActions(origin, Config.LivingShadowBar.StrataLevel));
}
}
[Section("Job Specific Bars")]
[SubSection("Tank", 0)]
[SubSection("Dark Knight", 1)]
public class DarkKnightConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.DRK;
public new static DarkKnightConfig DefaultConfig()
{
var config = new DarkKnightConfig();
config.ManaBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.ManaBar.UsePartialFillColor = true;
config.DarksideBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.DarksideBar.ThresholdConfig.Enabled = true;
config.BloodGauge.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.BloodGauge.UsePartialFillColor = true;
return config;
}
[NestedConfig("Mana Bar", 30)]
public DarkKnightManaBarConfig ManaBar = new DarkKnightManaBarConfig(
new Vector2(0, -61),
new Vector2(254, 10),
new PluginConfigColor(new Vector4(0f / 255f, 162f / 255f, 252f / 255f, 100f / 100f))
);
[NestedConfig("Blood Gauge", 35)]
public ChunkedProgressBarConfig BloodGauge = new ChunkedProgressBarConfig(
new Vector2(0, -49),
new Vector2(254, 10),
new PluginConfigColor(new Vector4(216f / 255f, 0f / 255f, 73f / 255f, 100f / 100f)),
2,
new PluginConfigColor(new Vector4(180f / 255f, 180f / 255f, 180f / 255f, 100f / 100f))
);
[NestedConfig("Darkside Bar", 40)]
public ProgressBarConfig DarksideBar = new ProgressBarConfig(
new Vector2(0, -73),
new Vector2(254, 10),
new PluginConfigColor(new Vector4(209f / 255f, 38f / 255f, 73f / 204f, 100f / 100f)),
BarDirection.Right,
new PluginConfigColor(new Vector4(160f / 255f, 0f / 255f, 0f / 255f, 100f / 100f)),
5
);
[NestedConfig("Blood Weapon Bar", 45)]
public DarkKnightChunkedBarConfig BloodWeaponBar = new DarkKnightChunkedBarConfig(
new Vector2(-64, -32),
new Vector2(126, 20),
new PluginConfigColor(new Vector4(160f / 255f, 0f / 255f, 0f / 255f, 100f / 100f))
);
[NestedConfig("Delirium Bar", 50)]
public DarkKnightChunkedBarConfig DeliriumBar = new DarkKnightChunkedBarConfig(
new Vector2(64, -32),
new Vector2(126, 20),
new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Living Shadow Bar", 55)]
public ProgressBarConfig LivingShadowBar = new ProgressBarConfig(
new Vector2(0, -10),
new Vector2(254, 20),
new PluginConfigColor(new Vector4(255f / 255f, 105f / 255f, 205f / 255f, 100f / 100f))
);
}
[Exportable(false)]
public class DarkKnightManaBarConfig : ChunkedProgressBarConfig
{
[ColorEdit4("Dark Arts Color" + "##MP")]
[Order(26)]
public PluginConfigColor DarkArtsColor = new PluginConfigColor(new Vector4(210f / 255f, 33f / 255f, 33f / 255f, 100f / 100f));
[Checkbox("Show mana values up to 10k (will break thresholds)", spacing = true)]
[Order(27)]
public bool ShowFullMana = false;
public DarkKnightManaBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
[Exportable(false)]
[DisableParentSettings("UseChunks", "LabelMode")]
public class DarkKnightChunkedBarConfig : ChunkedProgressBarConfig
{
public DarkKnightChunkedBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
}
+172
View File
@@ -0,0 +1,172 @@
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.Helpers;
using HSUI.Interface.Bars;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class DragoonHud : JobHud
{
private new DragoonConfig Config => (DragoonConfig)_config;
private static readonly List<uint> ChaosThrustIDs = new() { 118, 1312, 2719 };
private static readonly List<float> ChaosThrustDurations = new() { 24, 24, 24 };
public DragoonHud(DragoonConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.ChaosThrustBar.Enabled)
{
positions.Add(Config.Position + Config.ChaosThrustBar.Position);
sizes.Add(Config.ChaosThrustBar.Size);
}
if (Config.PowerSurgeBar.Enabled)
{
positions.Add(Config.Position + Config.PowerSurgeBar.Position);
sizes.Add(Config.PowerSurgeBar.Size);
}
if (Config.FirstmindsFocusBar.Enabled)
{
positions.Add(Config.Position + Config.FirstmindsFocusBar.Position);
sizes.Add(Config.FirstmindsFocusBar.Size);
}
if (Config.LifeOfTheDragonBar.Enabled)
{
positions.Add(Config.Position + Config.LifeOfTheDragonBar.Position);
sizes.Add(Config.LifeOfTheDragonBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
var position = origin + Config.Position;
if (Config.ChaosThrustBar.Enabled)
{
DrawChaosThrustBar(position, player);
}
if (Config.PowerSurgeBar.Enabled)
{
DrawPowerSurgeBar(position, player);
}
if (Config.FirstmindsFocusBar.Enabled)
{
DrawFirstmindsFocusBars(position, player);
}
if (Config.LifeOfTheDragonBar.Enabled)
{
DrawBloodOfTheDragonBar(position, player);
}
}
private void DrawChaosThrustBar(Vector2 origin, IPlayerCharacter player)
{
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
BarHud? bar = BarUtilities.GetDoTBar(Config.ChaosThrustBar, player, target, ChaosThrustIDs, ChaosThrustDurations);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.ChaosThrustBar.StrataLevel));
}
}
private void DrawFirstmindsFocusBars(Vector2 origin, IPlayerCharacter player)
{
DRGGauge gauge = Plugin.JobGauges.Get<DRGGauge>();
if (!Config.FirstmindsFocusBar.HideWhenInactive || gauge.FirstmindsFocusCount > 0)
{
BarHud[] bars = BarUtilities.GetChunkedBars(Config.FirstmindsFocusBar, 2, gauge.FirstmindsFocusCount, 2, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.FirstmindsFocusBar.StrataLevel));
}
}
}
private void DrawBloodOfTheDragonBar(Vector2 origin, IPlayerCharacter player)
{
DRGGauge gauge = Plugin.JobGauges.Get<DRGGauge>();
float duration = gauge.LOTDTimer / 1000f;
if (!Config.LifeOfTheDragonBar.HideWhenInactive || duration > 0f)
{
Config.LifeOfTheDragonBar.Label.SetValue(duration);
BarHud bar = BarUtilities.GetProgressBar(Config.LifeOfTheDragonBar, duration, 20, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.LifeOfTheDragonBar.StrataLevel));
}
}
private void DrawPowerSurgeBar(Vector2 origin, IPlayerCharacter player)
{
var duration = Math.Abs(Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 2720)?.RemainingTime ?? 0f);
if (!Config.PowerSurgeBar.HideWhenInactive || duration > 0f)
{
Config.PowerSurgeBar.Label.SetValue(duration);
BarHud bar = BarUtilities.GetProgressBar(Config.PowerSurgeBar, duration, 30f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.PowerSurgeBar.StrataLevel));
}
}
}
[Section("Job Specific Bars")]
[SubSection("Melee", 0)]
[SubSection("Dragoon", 1)]
public class DragoonConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.DRG;
public new static DragoonConfig DefaultConfig() { return new DragoonConfig(); }
[NestedConfig("Chaos Thrust", 30)]
public ProgressBarConfig ChaosThrustBar = new ProgressBarConfig(
new(0, -76),
new(254, 20),
new(new Vector4(217f / 255f, 145f / 255f, 232f / 255f, 100f / 100f))
);
[NestedConfig("Power Surge", 35)]
public ProgressBarConfig PowerSurgeBar = new ProgressBarConfig(
new(0, -54),
new(254, 20),
new(new Vector4(244f / 255f, 206f / 255f, 191f / 255f, 100f / 100f))
);
[NestedConfig("Firstminds' Focus", 40)]
public ChunkedBarConfig FirstmindsFocusBar = new ChunkedBarConfig(
new(64, -32),
new(126, 20),
new PluginConfigColor(new(134f / 255f, 120f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Life of the Dragon", 45)]
public ProgressBarConfig LifeOfTheDragonBar = new ProgressBarConfig(
new(0, -10),
new(254, 20),
new(new Vector4(185f / 255f, 0f / 255f, 25f / 255f, 100f / 100f))
);
}
}
+31
View File
@@ -0,0 +1,31 @@
using HSUI.Helpers;
using Newtonsoft.Json;
namespace HSUI.Interface.Jobs
{
public class GatherersConfig : JobConfig
{
public override uint JobId => 0;
public GatherersConfig()
{
UseDefaultPrimaryResourceBar = true;
PrimaryResourceType = PrimaryResourceTypes.GP;
}
}
public class MinerConfig : GatherersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.MIN;
}
public class BotanistConfig : GatherersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.BOT;
}
public class FisherConfig : GatherersConfig
{
[JsonIgnore] public override uint JobId => JobIDs.FSH;
}
}
+129
View File
@@ -0,0 +1,129 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Config;
using HSUI.Config.Attributes;
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 GunbreakerHud : JobHud
{
private new GunbreakerConfig Config => (GunbreakerConfig)_config;
public GunbreakerHud(GunbreakerConfig config, string? displayName = null) : base(config, displayName) { }
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.PowderGauge.Enabled)
{
positions.Add(Config.Position + Config.PowderGauge.Position);
sizes.Add(Config.PowderGauge.Size);
}
if (Config.NoMercy.Enabled)
{
positions.Add(Config.Position + Config.NoMercy.Position);
sizes.Add(Config.NoMercy.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
if (Config.PowderGauge.Enabled)
{
DrawPowderGauge(origin + Config.Position, player);
}
if (Config.NoMercy.Enabled)
{
DrawNoMercyBar(origin + Config.Position, player);
}
}
private void DrawPowderGauge(Vector2 origin, IPlayerCharacter player)
{
GNBGauge gauge = Plugin.JobGauges.Get<GNBGauge>();
if (Config.PowderGauge.HideWhenInactive && gauge.Ammo == 0)
{
return;
}
PluginConfigColor mainColor = Config.PowderGauge.FillColor;
PluginConfigColor extraColor = Config.PowderGauge.BloodfestExtraCartridgesColor;
int maxCartridges = player.Level >= 88 ? 3 : 2;
List<Tuple<PluginConfigColor, float, LabelConfig?>> chunks = new();
for (int i = 1; i < maxCartridges + 1; i++)
{
PluginConfigColor color = (gauge.Ammo < i || gauge.Ammo - maxCartridges < i) ? mainColor : extraColor;
chunks.Add(new(color, i <= gauge.Ammo ? 1 : 0, null));
}
BarHud[] bars = BarUtilities.GetChunkedBars(Config.PowderGauge, chunks.ToArray(), player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.PowderGauge.StrataLevel));
}
}
private void DrawNoMercyBar(Vector2 origin, IPlayerCharacter player)
{
float noMercyDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId == 1831 && o.RemainingTime > 0f)?.RemainingTime ?? 0f;
if (Config.NoMercy.HideWhenInactive && noMercyDuration <= 0)
{
return;
}
Config.NoMercy.Label.SetValue(noMercyDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.NoMercy, noMercyDuration, 20f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.NoMercy.StrataLevel));
}
}
[Section("Job Specific Bars")]
[SubSection("Tank", 0)]
[SubSection("Gunbreaker", 1)]
public class GunbreakerConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.GNB;
public new static GunbreakerConfig DefaultConfig() { return new GunbreakerConfig(); }
[NestedConfig("Powder Gauge", 30)]
public PowderGauge PowderGauge = new PowderGauge(
new(0, -32),
new(254, 20),
new(new Vector4(0f / 255f, 162f / 255f, 252f / 255f, 1f))
);
[NestedConfig("No Mercy", 35)]
public ProgressBarConfig NoMercy = new ProgressBarConfig(
new(0, -10),
new(254, 20),
new(new Vector4(252f / 255f, 204f / 255f, 255f / 255f, 1f))
);
}
public class PowderGauge : ChunkedBarConfig
{
[ColorEdit4("Bloodfest Extra Cartridges Color")]
[Order(26)]
public PluginConfigColor BloodfestExtraCartridgesColor = new(new Vector4(240f / 255f, 200f / 255f, 0, 1));
public PowderGauge(Vector2 position, Vector2 size, PluginConfigColor fillColor, int padding = 2) : base(position, size, fillColor, padding)
{
}
}
}
+41
View File
@@ -0,0 +1,41 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Helpers;
using Newtonsoft.Json;
using System;
using System.Reflection;
namespace HSUI.Interface.Jobs
{
public abstract class JobConfig : MovablePluginConfigObject
{
[JsonIgnore]
public abstract uint JobId { get; }
[Checkbox("Show Generic Mana Bar")]
[Order(20)]
public bool UseDefaultPrimaryResourceBar = false;
[NestedConfig("Visibility", 2000)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
[JsonIgnore]
public PrimaryResourceTypes PrimaryResourceType = PrimaryResourceTypes.MP;
public new static JobConfig? DefaultConfig()
{
var type = MethodBase.GetCurrentMethod()?.DeclaringType;
if (type is null)
{
return null;
}
return (JobConfig?)Activator.CreateInstance(type);
}
public JobConfig()
{
Position.Y = HUDConstants.JobHudsBaseY;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class JobHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
{
protected IDalamudPluginInterface PluginInterface => Plugin.PluginInterface;
public JobConfig Config => (JobConfig)_config;
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
public IGameObject? Actor { get; set; } = null;
protected IPlayerCharacter? Player => Actor is IPlayerCharacter ? (IPlayerCharacter)Actor : null;
public JobHud(JobConfig config, string? displayName = null) : base(config, displayName)
{
}
public override void DrawChildren(Vector2 origin)
{
if (Player == null || !_config.Enabled)
{
return;
}
DrawJobHud(origin, Player);
}
public virtual void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
// override
}
}
}
+257
View File
@@ -0,0 +1,257 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Logging;
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.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class MachinistHud : JobHud
{
private bool _robotMaxDurationSet;
private float _robotMaxDuration;
private new MachinistConfig Config => (MachinistConfig)_config;
public MachinistHud(MachinistConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.OverheatChunkedGauge.Enabled)
{
positions.Add(Config.Position + Config.OverheatChunkedGauge.Position);
sizes.Add(Config.OverheatChunkedGauge.Size);
}
if (Config.HeatGauge.Enabled)
{
positions.Add(Config.Position + Config.HeatGauge.Position);
sizes.Add(Config.HeatGauge.Size);
}
if (Config.BatteryGauge.Enabled)
{
positions.Add(Config.Position + Config.BatteryGauge.Position);
sizes.Add(Config.BatteryGauge.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.OverheatChunkedGauge.Enabled)
{
DrawOverheatBar(pos, player);
}
if (Config.HeatGauge.Enabled)
{
DrawHeatGauge(pos, player);
}
if (Config.BatteryGauge.Enabled)
{
DrawBatteryGauge(pos, player);
}
if (Config.AutomatonBar.Enabled)
{
DrawAutomatonBar(pos, player);
}
if (Config.WildfireBar.Enabled)
{
DrawWildfireBar(pos, player);
}
}
private void DrawHeatGauge(Vector2 origin, IPlayerCharacter player)
{
MCHGauge gauge = Plugin.JobGauges.Get<MCHGauge>();
if (!Config.HeatGauge.HideWhenInactive || gauge.Heat > 0)
{
Config.HeatGauge.Label.SetValue(gauge.Heat);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.HeatGauge, 2, gauge.Heat, 100, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.HeatGauge.StrataLevel));
}
}
}
private void DrawBatteryGauge(Vector2 origin, IPlayerCharacter player)
{
MCHGauge gauge = Plugin.JobGauges.Get<MCHGauge>();
if (!Config.BatteryGauge.HideWhenInactive || gauge.Battery > 0)
{
Config.BatteryGauge.Label.SetValue(gauge.Battery);
BarHud bar = BarUtilities.GetProgressBar(Config.BatteryGauge, gauge.Battery, 100f, 0f, player, Config.BatteryGauge.BatteryColor);
AddDrawActions(bar.GetDrawActions(origin, Config.BatteryGauge.StrataLevel));
}
}
private void DrawAutomatonBar(Vector2 origin, IPlayerCharacter player)
{
MCHGauge gauge = Plugin.JobGauges.Get<MCHGauge>();
if (!gauge.IsRobotActive && _robotMaxDurationSet)
{
_robotMaxDurationSet = false;
}
if (!Config.AutomatonBar.HideWhenInactive || gauge.IsRobotActive)
{
float remaining = gauge.SummonTimeRemaining / 1000f;
if (!_robotMaxDurationSet && gauge.IsRobotActive)
{
_robotMaxDuration = remaining;
_robotMaxDurationSet = true;
}
Config.AutomatonBar.Label.SetValue(remaining);
BarHud bar = BarUtilities.GetProgressBar(Config.AutomatonBar, remaining, _robotMaxDuration > 0 ? _robotMaxDuration : 1f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.AutomatonBar.StrataLevel));
}
}
private void DrawOverheatBar(Vector2 origin, IPlayerCharacter player)
{
MCHGauge gauge = Plugin.JobGauges.Get<MCHGauge>();
ushort stackCount = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 2688)?.Param ?? 0;
if (!Config.OverheatChunkedGauge.HideWhenInactive || stackCount > 0)
{
LabelConfig[]? labels = null;
if (Config.OverheatChunkedGauge.DurationLabel.Enabled)
{
Config.OverheatChunkedGauge.DurationLabel.SetValue(gauge.OverheatTimeRemaining / 1000f);
labels = new LabelConfig[5];
labels[2] = Config.OverheatChunkedGauge.DurationLabel;
}
BarHud[] bars = BarUtilities.GetChunkedBars(Config.OverheatChunkedGauge, 5, stackCount, 5, labels: labels );
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.OverheatChunkedGauge.StrataLevel));
}
}
}
private void DrawWildfireBar(Vector2 origin, IPlayerCharacter player)
{
float wildfireDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1946)?.RemainingTime ?? 0f;
if (!Config.WildfireBar.HideWhenInactive || wildfireDuration > 0)
{
Config.WildfireBar.Label.SetValue(wildfireDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.WildfireBar, wildfireDuration, 10, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.WildfireBar.StrataLevel));
}
}
}
[Section("Job Specific Bars")]
[SubSection("Ranged", 0)]
[SubSection("Machinist", 1)]
public class MachinistConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.MCH;
public new static MachinistConfig DefaultConfig()
{
var config = new MachinistConfig();
config.HeatGauge.UsePartialFillColor = true;
return config;
}
[NestedConfig("Overheat Gauge", 30)]
public MachinistOverheatBarConfig OverheatChunkedGauge = new MachinistOverheatBarConfig(
new Vector2(0, -54),
new Vector2(254, 20),
new PluginConfigColor(new Vector4(255f / 255f, 239f / 255f, 14f / 255f, 100f / 100f))
);
[NestedConfig("Heat Gauge", 35)]
public ChunkedProgressBarConfig HeatGauge = new ChunkedProgressBarConfig(
new Vector2(0, -32),
new Vector2(254, 20),
new PluginConfigColor(new Vector4(201f / 255f, 13f / 255f, 13f / 255f, 100f / 100f)),
2,
new PluginConfigColor(new Vector4(180f / 255f, 180f / 255f, 180f / 255f, 100f / 100f))
);
[NestedConfig("Battery Gauge", 40)]
public MachinistBatteryGaugeConfig BatteryGauge = new MachinistBatteryGaugeConfig(
new Vector2(-31, -10),
new Vector2(192, 20),
new PluginConfigColor(new Vector4(0, 0, 0, 0))
);
[NestedConfig("Automaton Queen Bar", 45)]
public ProgressBarConfig AutomatonBar = new ProgressBarConfig(
new Vector2(97, -10),
new Vector2(60, 20),
new PluginConfigColor(new Vector4(153f / 255f, 0f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Wildfire Bar", 50)]
public ProgressBarConfig WildfireBar = new ProgressBarConfig(
new Vector2(0, -76),
new Vector2(254, 20),
new PluginConfigColor(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f)),
BarDirection.Right,
new PluginConfigColor(new Vector4(180f / 255f, 180f / 255f, 180f / 255f, 100f / 100f)),
50
);
}
[DisableParentSettings("FillColor")]
[Exportable(false)]
public class MachinistBatteryGaugeConfig : ProgressBarConfig
{
[ColorEdit4("Battery Color", spacing = true)]
[Order(55)]
public PluginConfigColor BatteryColor = new(new Vector4(106f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
public MachinistBatteryGaugeConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor)
{
}
}
[Exportable(false)]
public class MachinistOverheatBarConfig : ChunkedBarConfig
{
[NestedConfig("Duration Text", 1000)]
public NumericLabelConfig DurationLabel;
public MachinistOverheatBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
DurationLabel = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
DurationLabel.HideIfZero = true;
}
}
}
+464
View File
@@ -0,0 +1,464 @@
using Dalamud.Game.ClientState.JobGauge.Enums;
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Statuses;
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;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class MonkHud : JobHud
{
private new MonkConfig Config => (MonkConfig)_config;
private string[] _chunkTexts = new string[] { "I", "II", "III" };
public MonkHud(MonkConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.ChakraBar.Enabled)
{
positions.Add(Config.Position + Config.ChakraBar.Position);
sizes.Add(Config.ChakraBar.Size);
}
if (Config.BeastChakraStacksBar.Enabled)
{
positions.Add(Config.Position + Config.BeastChakraStacksBar.Position);
sizes.Add(Config.BeastChakraStacksBar.Size);
}
if (Config.MastersGauge.Enabled)
{
positions.Add(Config.Position + Config.MastersGauge.Position);
sizes.Add(Config.MastersGauge.Size);
}
if (Config.StancesBar.Enabled)
{
positions.Add((Config.Position + Config.StancesBar.Position));
sizes.Add(Config.StancesBar.Size);
}
if (Config.PerfectBalanceBar.Enabled)
{
positions.Add(Config.Position + Config.PerfectBalanceBar.Position);
sizes.Add(Config.PerfectBalanceBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
var position = origin + Config.Position;
if (Config.ChakraBar.Enabled)
{
DrawChakraGauge(position, player);
}
if (Config.BeastChakraStacksBar.Enabled)
{
DrawBeastChakraStacksBar(position, player);
}
if (Config.MastersGauge.Enabled)
{
DrawMastersGauge(position, player);
}
if (Config.StancesBar.Enabled)
{
DrawFormsBar(position, player);
}
if (Config.PerfectBalanceBar.Enabled)
{
DrawPerfectBalanceBar(position, player);
}
}
private void DrawChakraGauge(Vector2 origin, IPlayerCharacter player)
{
MNKGauge gauge = Plugin.JobGauges.Get<MNKGauge>();
if (Config.ChakraBar.HideWhenInactive && gauge.Chakra == 0)
{
return;
}
PluginConfigColor mainColor = Config.ChakraBar.FillColor;
PluginConfigColor extraColor = Config.ChakraBar.BrotherhoodExtraCharkaColor;
List<Tuple<PluginConfigColor, float, LabelConfig?>> chunks = new();
for (int i = 1; i < 6; i++)
{
PluginConfigColor color = (gauge.Chakra < i || gauge.Chakra - 5 < i) ? mainColor : extraColor;
chunks.Add(new(color, i <= gauge.Chakra ? 1 : 0, null));
}
BarHud[] bars = BarUtilities.GetChunkedBars(Config.ChakraBar, chunks.ToArray(), player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.ChakraBar.StrataLevel));
}
}
private unsafe void DrawBeastChakraStacksBar(Vector2 origin, IPlayerCharacter player)
{
MonkBeastChakraStacksBar config = Config.BeastChakraStacksBar;
MNKGauge gauge = Plugin.JobGauges.Get<MNKGauge>();
int stacks = gauge.OpoOpoFury + gauge.RaptorFury + gauge.CoeurlFury;
if (config.HideWhenInactive && stacks == 0)
{
return;
}
PluginConfigColor empty = PluginConfigColor.Empty;
Tuple<PluginConfigColor, float, LabelConfig?>[] chunks =
[
new(gauge.OpoOpoFury > 0 ? config.OpoopoColor : empty, 1, null),
new(gauge.RaptorFury > 0 ? config.RaptorColor : empty, 1, null),
new(gauge.CoeurlFury > 0 ? config.CoeurlColor : empty, 1, null),
new(gauge.CoeurlFury > 1 ? config.CoeurlColor : empty, 1, null),
];
BarHud[] bars = BarUtilities.GetChunkedBars(config, chunks, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
private unsafe void DrawMastersGauge(Vector2 origin, IPlayerCharacter player)
{
MNKGauge gauge = Plugin.JobGauges.Get<MNKGauge>();
if (Config.MastersGauge.HideWhenInactive &&
gauge.Nadi == Nadi.None &&
gauge.BeastChakra[0] == BeastChakra.None &&
gauge.BeastChakra[1] == BeastChakra.None &&
gauge.BeastChakra[2] == BeastChakra.None)
{
return;
}
int[] order = Config.MastersGauge.ChakraOrder;
int[] hasChakra =
[
gauge.Nadi.HasFlag(Nadi.Lunar) ? 1 : 0,
gauge.BeastChakra[0] != BeastChakra.None ? 1 : 0,
gauge.BeastChakra[0] != BeastChakra.None ? 1 : 0,
gauge.BeastChakra[0] != BeastChakra.None ? 1 : 0,
gauge.Nadi.HasFlag(Nadi.Solar) ? 1 : 0,
];
PluginConfigColor[] colors = new[]
{
Config.MastersGauge.LunarNadiColor,
GetChakraColor(gauge.BeastChakra[0]),
GetChakraColor(gauge.BeastChakra[1]),
GetChakraColor(gauge.BeastChakra[2]),
Config.MastersGauge.SolarNadiColor
};
var chunks = new Tuple<PluginConfigColor, float, LabelConfig?>[5];
for (int i = 0; i < chunks.Length; i++)
{
chunks[i] = new(colors[order[i]], hasChakra[order[i]], i == 2 ? Config.MastersGauge.BlitzTimerLabel : null);
}
Config.MastersGauge.BlitzTimerLabel.SetValue(gauge.BlitzTimeRemaining / 1000);
BarHud[] bars = BarUtilities.GetChunkedBars(Config.MastersGauge, chunks, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.MastersGauge.StrataLevel));
}
}
private void DrawFormsBar(Vector2 origin, IPlayerCharacter player)
{
// formless fist
IStatus? formlessFist = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId == 2513);
if (formlessFist != null)
{
float remaining = Math.Abs(formlessFist.RemainingTime);
BarHud bar = BarUtilities.GetProgressBar(
Config.StancesBar,
null,
new LabelConfig[] { Config.StancesBar.FormlessFistLabel },
remaining,
30f,
0,
player,
Config.StancesBar.FormlessFistColor
);
Config.StancesBar.FormlessFistLabel.SetValue(remaining);
AddDrawActions(bar.GetDrawActions(origin, Config.StancesBar.StrataLevel));
return;
}
// forms
IStatus? form = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 107 or 108 or 109);
if (Config.StancesBar.HideWhenInactive && form is null)
{
return;
}
int activeFormIndex = form != null ? (int)form.StatusId - 107 : -1;
PluginConfigColor[] chunkColors = new PluginConfigColor[]
{
Config.StancesBar.OpoOpoColor,
Config.StancesBar.RaptorColor,
Config.StancesBar.CoeurlColor
};
LabelConfig[] chunkLabels = new LabelConfig[]
{
Config.StancesBar.FormLabel.Clone(0),
Config.StancesBar.FormLabel.Clone(1),
Config.StancesBar.FormLabel.Clone(2)
};
var chunks = new Tuple<PluginConfigColor, float, LabelConfig?>[3];
for (int i = 0; i < chunks.Length; i++)
{
LabelConfig label = chunkLabels[i];
label.SetText(_chunkTexts[i]);
chunks[i] = new(chunkColors[i], activeFormIndex == i ? 1 : 0, label);
}
BarHud[] bars = BarUtilities.GetChunkedBars(Config.StancesBar, chunks, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.StancesBar.StrataLevel));
}
}
private void DrawPerfectBalanceBar(Vector2 origin, IPlayerCharacter player)
{
IStatus? perfectBalance = Utils.StatusListForBattleChara(player).Where(o => o.StatusId is 110 && o.RemainingTime > 0f).FirstOrDefault();
float duration = perfectBalance?.RemainingTime ?? 0f;
float stacks = perfectBalance?.Param ?? 0f;
if (Config.PerfectBalanceBar.HideWhenInactive && duration <= 0)
{
return;
}
Tuple<PluginConfigColor, float, LabelConfig?>[] chunks = new Tuple<PluginConfigColor, float, LabelConfig?>[3];
for (int i = 0; i < chunks.Length; i++)
{
chunks[i] = new(
Config.PerfectBalanceBar.FillColor,
i < stacks ? 1f : 0,
i == 1 ? Config.PerfectBalanceBar.PerfectBalanceLabel : null
);
}
Config.PerfectBalanceBar.PerfectBalanceLabel.SetValue(duration);
BarHud[] bars = BarUtilities.GetChunkedBars(Config.PerfectBalanceBar, chunks, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.PerfectBalanceBar.StrataLevel));
}
}
private PluginConfigColor GetChakraColor(BeastChakra chakra) => chakra switch
{
BeastChakra.OpoOpo => Config.MastersGauge.OpoopoChakraColor,
BeastChakra.Raptor => Config.MastersGauge.RaptorChakraColor,
BeastChakra.Coeurl => Config.MastersGauge.CoeurlChakraColor,
_ => new PluginConfigColor(new(0, 0, 0, 0))
};
}
[Section("Job Specific Bars")]
[SubSection("Melee", 0)]
[SubSection("Monk", 1)]
public class MonkConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.MNK;
public new static MonkConfig DefaultConfig()
{
var config = new MonkConfig();
config.StancesBar.Enabled = false;
config.MastersGauge.BlitzTimerLabel.HideIfZero = true;
config.PerfectBalanceBar.PerfectBalanceLabel.HideIfZero = true;
return config;
}
[NestedConfig("Chakra Bar", 35)]
public ChakraBar ChakraBar = new ChakraBar(
new(0, -32),
new(254, 20),
new(new Vector4(204f / 255f, 115f / 255f, 0f, 100f / 100f))
);
[NestedConfig("Fury Stacks Bar", 40)]
public MonkBeastChakraStacksBar BeastChakraStacksBar = new MonkBeastChakraStacksBar(
new(0, -32),
new(254, 20)
);
[NestedConfig("Masterful Blitz Bar", 45)]
public MastersGauge MastersGauge = new MastersGauge(
new(0, -54),
new(254, 20)
);
[NestedConfig("Forms Bar", 50)]
public MonkStancesBarConfig StancesBar = new MonkStancesBarConfig(
new(0, -98),
new(254, 20)
);
[NestedConfig("Perfect Balance Bar", 55)]
public PerfectBalanceBar PerfectBalanceBar = new PerfectBalanceBar(
new(0, -76),
new(254, 20),
new(new Vector4(150f / 255f, 255f / 255f, 255f / 255f, 100f / 100f))
);
}
public class ChakraBar: ChunkedBarConfig
{
[ColorEdit4("Brotherhood Extra Chakra Color")]
[Order(26)]
public PluginConfigColor BrotherhoodExtraCharkaColor = new(new Vector4(204f / 255f, 0, 0, 1));
public ChakraBar(Vector2 position, Vector2 size, PluginConfigColor fillColor, int padding = 2) : base(position, size, fillColor, padding)
{
}
}
public class PerfectBalanceBar : ChunkedBarConfig
{
[NestedConfig("Perfect Balance Duration Text", 50, spacing = true)]
public NumericLabelConfig PerfectBalanceLabel;
public PerfectBalanceBar(Vector2 position, Vector2 size, PluginConfigColor fillColor, int padding = 2) : base(position, size, fillColor, padding)
{
PerfectBalanceLabel = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
}
}
[DisableParentSettings("FillColor", "FillDirection")]
public class MonkBeastChakraStacksBar : ChunkedBarConfig
{
[ColorEdit4("Opo-opo Color")]
[Order(19)]
public PluginConfigColor OpoopoColor = PluginConfigColor.FromHex(0xFFFFB3D3);
[ColorEdit4("Raptor Color")]
[Order(20)]
public PluginConfigColor RaptorColor = PluginConfigColor.FromHex(0xFFBF89E5);
[ColorEdit4("Coeurl Color")]
[Order(21)]
public PluginConfigColor CoeurlColor = PluginConfigColor.FromHex(0xFF9AE7C0);
public MonkBeastChakraStacksBar(Vector2 position, Vector2 size)
: base(position, size, PluginConfigColor.Empty)
{
}
}
[DisableParentSettings("FillColor", "FillDirection")]
public class MastersGauge : ChunkedBarConfig
{
[ColorEdit4("Lunar Nadi Color")]
[Order(19)]
public PluginConfigColor LunarNadiColor = PluginConfigColor.FromHex(0xFFDA87FF);
[ColorEdit4("Solar Nadi Color")]
[Order(20)]
public PluginConfigColor SolarNadiColor = PluginConfigColor.FromHex(0xFFFFFFCA);
[ColorEdit4("Opo-opo Color")]
[Order(21)]
public PluginConfigColor OpoopoChakraColor = PluginConfigColor.FromHex(0xFFC1527E);
[ColorEdit4("Raptor Color")]
[Order(22)]
public PluginConfigColor RaptorChakraColor = PluginConfigColor.FromHex(0xFF8C67BA);
[ColorEdit4("Coeurl Color")]
[Order(23)]
public PluginConfigColor CoeurlChakraColor = PluginConfigColor.FromHex(0xFF326D5A);
[DragDropHorizontal("Chakra Order", "Lunar Nadi", "Chakra 1", "Chakra 2", "Chakra 3", "Solar Nadi")]
[Order(24)]
public int[] ChakraOrder = new int[] { 0, 1, 2, 3, 4 };
[NestedConfig("Blitz Timer Text", 50, spacing = true)]
public NumericLabelConfig BlitzTimerLabel;
public MastersGauge(Vector2 position, Vector2 size)
: base(position, size, PluginConfigColor.Empty)
{
BlitzTimerLabel = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
}
}
[DisableParentSettings("FillColor")]
public class MonkStancesBarConfig : ChunkedBarConfig
{
[ColorEdit4("Opo-opo Color")]
[Order(19)]
public PluginConfigColor OpoOpoColor = PluginConfigColor.FromHex(0xFFFFB3D3);
[ColorEdit4("Raptor Color")]
[Order(20)]
public PluginConfigColor RaptorColor = PluginConfigColor.FromHex(0xFFBF89E5);
[ColorEdit4("Coeurl Color")]
[Order(21)]
public PluginConfigColor CoeurlColor = PluginConfigColor.FromHex(0xFF9AE7C0);
[ColorEdit4("Formless Fist Color")]
[Order(22)]
public PluginConfigColor FormlessFistColor = PluginConfigColor.FromHex(0xFF514793);
[NestedConfig("Form Number Text", 500, spacing = true)]
public LabelConfig FormLabel;
[NestedConfig("Formless Fist Duration Text", 1000, separator = false, spacing = true)]
public NumericLabelConfig FormlessFistLabel;
public MonkStancesBarConfig(Vector2 position, Vector2 size)
: base(position, size, PluginConfigColor.Empty)
{
FormLabel = new LabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
FormlessFistLabel = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
FormlessFistLabel.Enabled = false;
}
}
}
+341
View File
@@ -0,0 +1,341 @@
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<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
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<NINGauge>().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<NINGauge>().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;
}
}
}
+164
View File
@@ -0,0 +1,164 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Statuses;
using HSUI.Config;
using HSUI.Config.Attributes;
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;
using System.Security.Principal;
namespace HSUI.Interface.Jobs
{
public class PaladinHud : JobHud
{
private new PaladinConfig Config => (PaladinConfig)_config;
public PaladinHud(PaladinConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new();
List<Vector2> sizes = new();
if (Config.OathGauge.Enabled)
{
positions.Add(Config.Position + Config.OathGauge.Position);
sizes.Add(Config.OathGauge.Size);
}
if (Config.FightOrFlightBar.Enabled)
{
positions.Add(Config.Position + Config.FightOrFlightBar.Position);
sizes.Add(Config.FightOrFlightBar.Size);
}
if (Config.RequiescatStacksBar.Enabled)
{
positions.Add(Config.Position + Config.RequiescatStacksBar.Position);
sizes.Add(Config.RequiescatStacksBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.OathGauge.Enabled)
{
DrawOathGauge(pos, player);
}
if (Config.FightOrFlightBar.Enabled)
{
DrawFightOrFlightBar(pos, player);
}
if (Config.RequiescatStacksBar.Enabled)
{
DrawRequiescatBar(pos, player);
}
}
private void DrawOathGauge(Vector2 origin, IPlayerCharacter player)
{
PLDGauge gauge = Plugin.JobGauges.Get<PLDGauge>();
if (!Config.OathGauge.HideWhenInactive || gauge.OathGauge > 0)
{
Config.OathGauge.Label.SetValue(gauge.OathGauge);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.OathGauge, 2, gauge.OathGauge, 100, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.OathGauge.StrataLevel));
}
}
}
private void DrawFightOrFlightBar(Vector2 origin, IPlayerCharacter player)
{
float fightOrFlightDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 76)?.RemainingTime ?? 0f;
if (!Config.FightOrFlightBar.HideWhenInactive || fightOrFlightDuration > 0)
{
Config.FightOrFlightBar.Label.SetValue(fightOrFlightDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.FightOrFlightBar, fightOrFlightDuration, 20f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.FightOrFlightBar.StrataLevel));
}
}
private void DrawRequiescatBar(Vector2 origin, IPlayerCharacter player)
{
IStatus? requiescatBuff = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1368);
float requiescatDuration = Math.Max(0f, requiescatBuff?.RemainingTime ?? 0f);
int stacks = requiescatBuff?.Param ?? 0;
if (!Config.RequiescatStacksBar.HideWhenInactive || requiescatDuration > 0)
{
Config.RequiescatStacksBar.Label.SetValue(requiescatDuration);
LabelConfig[] labels = new LabelConfig[4];
labels[2] = Config.RequiescatStacksBar.Label;
BarHud[] bars = BarUtilities.GetChunkedBars(Config.RequiescatStacksBar, 4, stacks, 4, labels: labels);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.RequiescatStacksBar.StrataLevel));
}
}
}
}
[Section("Job Specific Bars")]
[SubSection("Tank", 0)]
[SubSection("Paladin", 1)]
public class PaladinConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.PLD;
public new static PaladinConfig DefaultConfig()
{
var config = new PaladinConfig();
config.UseDefaultPrimaryResourceBar = true;
config.OathGauge.UsePartialFillColor = true;
config.RequiescatStacksBar.Label.Enabled = true;
return config;
}
[NestedConfig("Oath Gauge", 35)]
public ChunkedProgressBarConfig OathGauge = new ChunkedProgressBarConfig(
new Vector2(0, -54),
new Vector2(254, 20),
new PluginConfigColor(new Vector4(24f / 255f, 80f / 255f, 175f / 255f, 100f / 100f)),
2,
new PluginConfigColor(new Vector4(180f / 255f, 180f / 255f, 180f / 255f, 100f / 100f))
);
[NestedConfig("Fight or Flight Bar", 40)]
public ProgressBarConfig FightOrFlightBar = new ProgressBarConfig(
new Vector2(-64, -32),
new Vector2(126, 20),
new PluginConfigColor(new Vector4(240f / 255f, 50f / 255f, 0f / 255f, 100f / 100f))
);
[NestedConfig("Requiescat Bar", 45)]
public ChunkedProgressBarConfig RequiescatStacksBar = new ChunkedProgressBarConfig(
new Vector2(64, -32),
new Vector2(126, 20),
new PluginConfigColor(new Vector4(61f / 255f, 61f / 255f, 255f / 255f, 100f / 100f))
);
}
}
+537
View File
@@ -0,0 +1,537 @@
using Dalamud.Game.ClientState.JobGauge.Enums;
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Statuses;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.GeneralElements;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text.Json.Serialization;
namespace HSUI.Interface.Jobs
{
public class PictomancerHud : JobHud
{
private new PictomancerConfig Config => (PictomancerConfig)_config;
private static PluginConfigColor EmptyColor => GlobalColors.Instance.EmptyColor;
public PictomancerHud(PictomancerConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.PaletteBar.Enabled)
{
positions.Add(Config.Position + Config.PaletteBar.Position);
sizes.Add(Config.PaletteBar.Size);
}
if (Config.PaintBar.Enabled)
{
positions.Add(Config.Position + Config.PaintBar.Position);
sizes.Add(Config.PaintBar.Size);
}
if (Config.CreatureCanvasBar.Enabled)
{
positions.Add(Config.Position + Config.CreatureCanvasBar.Position);
sizes.Add(Config.CreatureCanvasBar.Size);
}
if (Config.WeaponCanvasBar.Enabled)
{
positions.Add(Config.Position + Config.WeaponCanvasBar.Position);
sizes.Add(Config.WeaponCanvasBar.Size);
}
if (Config.LandscapeCanvasBar.Enabled)
{
positions.Add(Config.Position + Config.LandscapeCanvasBar.Position);
sizes.Add(Config.LandscapeCanvasBar.Size);
}
if (Config.HammerTimeBar.Enabled)
{
positions.Add(Config.Position + Config.HammerTimeBar.Position);
sizes.Add(Config.HammerTimeBar.Size);
}
if (Config.HyperphantasiaBar.Enabled)
{
positions.Add(Config.Position + Config.HyperphantasiaBar.Position);
sizes.Add(Config.HyperphantasiaBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.PaletteBar.Enabled)
{
DrawPaletteBar(pos, player);
}
if (Config.PaintBar.Enabled)
{
DrawPaintBar(pos, player);
}
if (Config.CreatureCanvasBar.Enabled)
{
DrawCreatureCanvasBar(pos, player);
}
if (Config.WeaponCanvasBar.Enabled)
{
DrawWeaponCanvasBar(pos, player);
}
if (Config.LandscapeCanvasBar.Enabled)
{
DrawLandscapeCanvasBar(pos, player);
}
if (Config.HammerTimeBar.Enabled)
{
DrawHammerTimeBar(pos, player);
}
if (Config.HyperphantasiaBar.Enabled)
{
DrawHyperphantasiaBar(pos, player);
}
}
protected unsafe void DrawPaletteBar(Vector2 origin, IPlayerCharacter player)
{
PictomancerPaletteBarConfig config = Config.PaletteBar;
PCTGauge gauge = Plugin.JobGauges.Get<PCTGauge>();
if (config.HideWhenInactive && gauge.PalleteGauge == 0)
{
return;
}
bool isSubstractive = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 3674) != null;
config.Label = config.PaletteLabel;
config.PaletteLabel.SubtractiveMode = isSubstractive;
config.Label.SetValue(gauge.PalleteGauge);
PluginConfigColor fillColor = isSubstractive ? config.SubtractiveColor : config.FillColor;
BarHud[] bars = BarUtilities.GetChunkedProgressBars(
config,
2,
gauge.PalleteGauge,
100,
0,
player,
fillColor: fillColor
);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
private unsafe void DrawPaintBar(Vector2 origin, IPlayerCharacter player)
{
PictomancerPaintBarConfig config = Config.PaintBar;
PCTGauge gauge = Plugin.JobGauges.Get<PCTGauge>();
if (config.HideWhenInactive && gauge.Paint == 0)
{
return;
}
bool hasBlackPaint = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 3691) != null;
Tuple<PluginConfigColor, float, LabelConfig?>[] chunks = new Tuple<PluginConfigColor, float, LabelConfig?>[5];
for (int i = 0; i < 5; i++)
{
PluginConfigColor color = PluginConfigColor.Empty;
if (i < gauge.Paint)
{
color = i == gauge.Paint - 1 && hasBlackPaint ? config.BlackPaintColor : config.WhitePaintColor;
}
chunks[i] = new(color, 1, null);
}
BarHud[] bars = BarUtilities.GetChunkedBars(config, chunks, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
private unsafe void DrawCreatureCanvasBar(Vector2 origin, IPlayerCharacter player)
{
PictomancerCreatureCanvasBarConfig config = Config.CreatureCanvasBar;
PCTGauge gauge = Plugin.JobGauges.Get<PCTGauge>();
if (config.HideWhenInactive && !gauge.CreatureMotifDrawn && gauge.CreatureFlags != 0)
{
return;
}
// canvas
PluginConfigColor? canvasColor = null;
if (gauge.CanvasFlags.HasFlag(CanvasFlags.Maw))
{
canvasColor = config.FangsColor;
}
else if (gauge.CanvasFlags.HasFlag(CanvasFlags.Claw))
{
canvasColor = config.ClawColor;
}
else if (gauge.CanvasFlags.HasFlag(CanvasFlags.Wing))
{
canvasColor = config.WingsColor;
}
else if(gauge.CanvasFlags.HasFlag(CanvasFlags.Pom))
{
canvasColor = config.PomColor;
}
Tuple<PluginConfigColor, float, LabelConfig?> canvas = new(
canvasColor ?? PluginConfigColor.Empty,
canvasColor != null ? 1 : 0,
null
);
// part drawing
PluginConfigColor? drawingColor = null;
if (gauge.CreatureFlags.HasFlag(CreatureFlags.Claw))
{
drawingColor = config.ClawColor;
}
else if (config.ShowEmptyPomWings &&
gauge.CreatureFlags.HasFlag(CreatureFlags.Pom) &&
gauge.CreatureFlags.HasFlag(CreatureFlags.Wings))
{
drawingColor = PluginConfigColor.Empty;
}
else if (gauge.CreatureFlags.HasFlag(CreatureFlags.Wings))
{
drawingColor = config.WingsColor;
}
else if(gauge.CreatureFlags.HasFlag(CreatureFlags.Pom))
{
drawingColor = config.PomColor;
}
Tuple<PluginConfigColor, float, LabelConfig?> drawing = new(
drawingColor ?? PluginConfigColor.Empty,
drawingColor != null ? 1 : 0,
null
);
// portrait
PluginConfigColor? portraitColor = null;
if (gauge.CreatureFlags.HasFlag(CreatureFlags.MooglePortait))
{
portraitColor = config.MoogleColor;
}
else if (gauge.CreatureFlags.HasFlag(CreatureFlags.MadeenPortrait))
{
portraitColor = config.MadeenColor;
}
Tuple<PluginConfigColor, float, LabelConfig?> portrait = new(
portraitColor ?? PluginConfigColor.Empty,
portraitColor != null ? 1 : 0,
null
);
var chunks = new List<Tuple<PluginConfigColor, float, LabelConfig?>>();
chunks.Add(canvas);
if (!config.DontShowDrawing)
{
chunks.Add(drawing);
}
chunks.Add(portrait);
BarHud[] bars = BarUtilities.GetChunkedBars(config, chunks.ToArray(), player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
private unsafe void DrawWeaponCanvasBar(Vector2 origin, IPlayerCharacter player)
{
ChunkedBarConfig config = Config.WeaponCanvasBar;
PCTGauge gauge = Plugin.JobGauges.Get<PCTGauge>();
bool active = gauge.CanvasFlags.HasFlag(CanvasFlags.Weapon);
if (config.HideWhenInactive && !active)
{
return;
}
BarHud[] bars = BarUtilities.GetChunkedBars(config, 1, active ? 1 : 0, 1, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
private unsafe void DrawLandscapeCanvasBar(Vector2 origin, IPlayerCharacter player)
{
ChunkedBarConfig config = Config.LandscapeCanvasBar;
PCTGauge gauge = Plugin.JobGauges.Get<PCTGauge>();
bool active = gauge.CanvasFlags.HasFlag(CanvasFlags.Landscape);
if (config.HideWhenInactive && !active)
{
return;
}
BarHud[] bars = BarUtilities.GetChunkedBars(config, 1, active ? 1 : 0, 1, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
private unsafe void DrawHammerTimeBar(Vector2 origin, IPlayerCharacter player)
{
StacksWithDurationBarConfig config = Config.HammerTimeBar;
IStatus? status = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 3680);
int stacks = status?.Param ?? 0;
float time = Math.Abs(status?.RemainingTime ?? 0f);
if (config.HideWhenInactive && stacks == 0)
{
return;
}
config.Label.SetValue(time);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(config, 3, stacks, 3, 0, player, forceLabelIndex: 1);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
private unsafe void DrawHyperphantasiaBar(Vector2 origin, IPlayerCharacter player)
{
StacksWithDurationBarConfig config = Config.HyperphantasiaBar;
IStatus? status = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 3688);
int stacks = status?.Param ?? 0;
float time = Math.Abs(status?.RemainingTime ?? 0f);
if (config.HideWhenInactive && stacks == 0)
{
return;
}
config.Label.SetValue(time);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(config, 5, stacks, 5, 0, player, forceLabelIndex: 2);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, config.StrataLevel));
}
}
}
[Section("Job Specific Bars")]
[SubSection("Caster", 0)]
[SubSection("Pictomancer", 1)]
public class PictomancerConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.PCT;
public new static PictomancerConfig DefaultConfig()
{
var config = new PictomancerConfig();
config.PaletteBar.UseChunks = false;
config.PaletteBar.Label.Enabled = true;
return config;
}
[NestedConfig("Palette Bar", 30)]
public PictomancerPaletteBarConfig PaletteBar = new PictomancerPaletteBarConfig(
new Vector2(0, -10),
new Vector2(152, 18),
PluginConfigColor.FromHex(0xFFC8C89F)
);
[NestedConfig("Paint Bar", 35)]
public PictomancerPaintBarConfig PaintBar = new PictomancerPaintBarConfig(
new(0, -26),
new(254, 10)
);
[NestedConfig("Creature Canvas Bar", 40)]
public PictomancerCreatureCanvasBarConfig CreatureCanvasBar = new PictomancerCreatureCanvasBarConfig(
new(0, -38),
new(254, 10)
);
[NestedConfig("Weapon Canvas Bar", 40)]
public ChunkedBarConfig WeaponCanvasBar = new ChunkedBarConfig(
new(-102.5f, -10),
new(49, 18),
PluginConfigColor.FromHex(0xFFC5616C)
);
[NestedConfig("Landscape Canvas Bar", 40)]
public ChunkedBarConfig LandscapeCanvasBar = new ChunkedBarConfig(
new(102.5f, -10),
new(49, 18),
PluginConfigColor.FromHex(0xFF8690E5)
);
[NestedConfig("Hammer Time Bar", 40)]
public StacksWithDurationBarConfig HammerTimeBar = new StacksWithDurationBarConfig(
new(0, -50),
new(254, 10),
PluginConfigColor.FromHex(0xFFFFFFFF)
);
[NestedConfig("Hyperphantasia Bar", 45)]
public StacksWithDurationBarConfig HyperphantasiaBar = new StacksWithDurationBarConfig(
new(0, -50),
new(254, 10),
PluginConfigColor.FromHex(0xFFFFFFFF)
);
}
[DisableParentSettings("Label")]
[Exportable(false)]
public class PictomancerPaletteBarConfig : ChunkedProgressBarConfig
{
[ColorEdit4("Subtractive Fill Color")]
[Order(27)]
public PluginConfigColor SubtractiveColor = PluginConfigColor.FromHex(0xFFAF6BAE);
[NestedConfig("Bar Text", 1001, separator = false, spacing = true)]
public PictomancerPaletteLabelConfig PaletteLabel = new PictomancerPaletteLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
public PictomancerPaletteBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
Label = PaletteLabel;
}
}
[Exportable(false)]
public class PictomancerPaletteLabelConfig : NumericLabelConfig
{
[ColorEdit4("Subtractive Color")]
[Order(31)]
public PluginConfigColor SubtractiveColor = new PluginConfigColor(Vector4.UnitW);
[ColorEdit4("Subtractive Color ##Outline")]
[Order(41, collapseWith = nameof(ShowOutline))]
public PluginConfigColor SubtractiveOutlineColor = new PluginConfigColor(Vector4.One);
public PictomancerPaletteLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
: base(position, text, frameAnchor, textAnchor)
{
}
[JsonIgnore] public bool SubtractiveMode = false;
public override PluginConfigColor GetColor() => SubtractiveMode ? SubtractiveColor : Color;
public override PluginConfigColor GetOutlineColor() => SubtractiveMode ? SubtractiveOutlineColor : OutlineColor;
public override PictomancerPaletteLabelConfig Clone(int index) =>
new PictomancerPaletteLabelConfig(Position, _text, FrameAnchor, TextAnchor)
{
Color = Color,
SubtractiveColor = SubtractiveColor,
OutlineColor = OutlineColor,
SubtractiveOutlineColor = SubtractiveOutlineColor,
ShadowConfig = ShadowConfig,
ShowOutline = ShowOutline,
FontID = FontID,
UseJobColor = UseJobColor,
Enabled = Enabled,
HideIfZero = HideIfZero,
ID = ID + "_{index}"
};
}
[DisableParentSettings("FillColor")]
[Exportable(false)]
public class PictomancerPaintBarConfig : ChunkedBarConfig
{
[ColorEdit4("White Paint Color")]
[Order(26)]
public PluginConfigColor WhitePaintColor = PluginConfigColor.FromHex(0xFF00FFFF);
[ColorEdit4("Black Paint Color")]
[Order(27)]
public PluginConfigColor BlackPaintColor = PluginConfigColor.FromHex(0xFFDB57DB);
public PictomancerPaintBarConfig(Vector2 position, Vector2 size)
: base(position, size, PluginConfigColor.Empty)
{
}
}
[DisableParentSettings("FillColor")]
[Exportable(false)]
public class PictomancerCreatureCanvasBarConfig : ChunkedBarConfig
{
[ColorEdit4("Pom Color")]
[Order(17)]
public PluginConfigColor PomColor = PluginConfigColor.FromHex(0xFFE69378);
[ColorEdit4("Wings Color")]
[Order(18)]
public PluginConfigColor WingsColor = PluginConfigColor.FromHex(0xFFD38BE4);
[ColorEdit4("Claw Color")]
[Order(19)]
public PluginConfigColor ClawColor = PluginConfigColor.FromHex(0xFFA16854);
[ColorEdit4("Fangs Color")]
[Order(20)]
public PluginConfigColor FangsColor = PluginConfigColor.FromHex(0xFF80BFBD);
[ColorEdit4("Moogle Color")]
[Order(21)]
public PluginConfigColor MoogleColor = PluginConfigColor.FromHex(0xFFA745C7);
[ColorEdit4("Madeen Color")]
[Order(22)]
public PluginConfigColor MadeenColor = PluginConfigColor.FromHex(0xFF93Cf7D);
[Checkbox("Don't Show Drawing", spacing = true, help = "When enabled, this hides the middle chunk that shows which creature parts were already drawn.")]
[Order(50)]
public bool DontShowDrawing = false;
[Checkbox("Show Empty Drawing for Pom + Wings")]
[Order(51)]
public bool ShowEmptyPomWings = false;
public PictomancerCreatureCanvasBarConfig(Vector2 position, Vector2 size)
: base(position, size, PluginConfigColor.Empty)
{
}
}
}
+246
View File
@@ -0,0 +1,246 @@
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 Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class ReaperHud : JobHud
{
private new ReaperConfig Config => (ReaperConfig)_config;
public ReaperHud(JobConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new();
List<Vector2> sizes = new();
if (Config.DeathsDesignBar.Enabled)
{
positions.Add(Config.Position + Config.DeathsDesignBar.Position);
sizes.Add(Config.DeathsDesignBar.Size);
}
if (Config.SoulBar.Enabled)
{
positions.Add(Config.Position + Config.SoulBar.Position);
sizes.Add(Config.SoulBar.Size);
}
if (Config.ShroudBar.Enabled)
{
positions.Add(Config.Position + Config.ShroudBar.Position);
sizes.Add(Config.ShroudBar.Size);
}
if (Config.DeathGauge.Enabled)
{
positions.Add(Config.Position + Config.DeathGauge.Position);
sizes.Add(Config.DeathGauge.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
RPRGauge gauge = Plugin.JobGauges.Get<RPRGauge>();
if (Config.DeathsDesignBar.Enabled)
{
DrawDeathsDesignBar(pos, player);
}
if (Config.SoulBar.Enabled)
{
DrawSoulGauge(pos, gauge, player);
}
if (Config.ShroudBar.Enabled)
{
DrawShroudGauge(pos, gauge, player);
}
if (Config.DeathGauge.Enabled)
{
DrawDeathGauge(pos, gauge, player);
}
}
private void DrawDeathsDesignBar(Vector2 origin, IPlayerCharacter player)
{
IGameObject? actor = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
float duration = 0f;
if (actor is IBattleChara target)
{
duration = Utils.StatusListForBattleChara(target).FirstOrDefault(o => o.StatusId is 2586 && o.SourceId == player.GameObjectId && o.RemainingTime > 0)?.RemainingTime ?? 0f;
}
if (!Config.DeathsDesignBar.HideWhenInactive || duration > 0)
{
Config.DeathsDesignBar.Label.SetValue(duration);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.DeathsDesignBar, 2, duration, 60f, 0f, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.DeathsDesignBar.StrataLevel));
}
}
}
private void DrawSoulGauge(Vector2 origin, RPRGauge gauge, IPlayerCharacter player)
{
float soul = gauge.Soul;
if (!Config.SoulBar.HideWhenInactive || soul > 0)
{
Config.SoulBar.Label.SetValue(soul);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.SoulBar, 2, soul, 100f, 0f, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.SoulBar.StrataLevel));
}
}
}
private void DrawShroudGauge(Vector2 origin, RPRGauge gauge, IPlayerCharacter player)
{
float shroud = gauge.Shroud;
if (!Config.ShroudBar.HideWhenInactive || shroud > 0)
{
Config.ShroudBar.Label.SetValue(shroud);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.ShroudBar, 2, shroud, 100f, 0f, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.ShroudBar.StrataLevel));
}
}
}
private void DrawDeathGauge(Vector2 origin, RPRGauge gauge, IPlayerCharacter player)
{
var lemureShroud = gauge.LemureShroud;
var voidShroud = gauge.VoidShroud;
if (!Config.DeathGauge.HideWhenInactive || gauge.EnshroudedTimeRemaining > 0)
{
var deathChunks = new Tuple<PluginConfigColor, float, LabelConfig?>[5];
int i = 0;
for (; i < lemureShroud && i < deathChunks.Length; i++)
{
deathChunks[i] = new(Config.DeathGauge.LemureShroudColor, 1f, i == 2 ? Config.DeathGauge.EnshroudTimerLabel : null);
}
for (; i < lemureShroud + voidShroud && i < deathChunks.Length; i++)
{
deathChunks[i] = new(Config.DeathGauge.VoidShroudColor, 1f, i == 2 ? Config.DeathGauge.EnshroudTimerLabel : null);
}
for (; i < deathChunks.Length; i++)
{
deathChunks[i] = new(Config.DeathGauge.VoidShroudColor, 0f, i == 2 ? Config.DeathGauge.EnshroudTimerLabel : null);
}
Config.DeathGauge.EnshroudTimerLabel.SetValue(gauge.EnshroudedTimeRemaining / 1000);
BarHud[] bars = BarUtilities.GetChunkedBars(Config.DeathGauge, deathChunks, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.DeathGauge.StrataLevel));
}
}
}
}
[Section("Job Specific Bars")]
[SubSection("Melee", 0)]
[SubSection("Reaper", 1)]
public class ReaperConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.RPR;
public new static ReaperConfig DefaultConfig()
{
var config = new ReaperConfig();
config.DeathsDesignBar.UseChunks = false;
config.DeathsDesignBar.Label.Enabled = true;
config.SoulBar.UseChunks = false;
config.SoulBar.Label.Enabled = true;
config.ShroudBar.UseChunks = false;
config.ShroudBar.Label.Enabled = true;
config.DeathGauge.EnshroudTimerLabel.HideIfZero = true;
return config;
}
[NestedConfig("Death's Design Bar", 35)]
public ChunkedProgressBarConfig DeathsDesignBar = new ChunkedProgressBarConfig(
new(0, -10),
new(254, 20),
new PluginConfigColor(new Vector4(145f / 255f, 0f / 255f, 25f / 255f, 100f / 100f))
);
[NestedConfig("Soul Bar", 40)]
public ChunkedProgressBarConfig SoulBar = new ChunkedProgressBarConfig(
new(0, -32),
new(254, 20),
new PluginConfigColor(new Vector4(254f / 255f, 21f / 255f, 94f / 255f, 100f / 100f))
);
[NestedConfig("Shroud Bar", 45)]
public ChunkedProgressBarConfig ShroudBar = new ChunkedProgressBarConfig(
new(0, -54),
new(254, 20),
new PluginConfigColor(new Vector4(0f / 255f, 176f / 255f, 196f / 255f, 100f / 100f))
);
[NestedConfig("Death Gauge", 50)]
public DeathGauge DeathGauge = new DeathGauge(
new(0, -76),
new(254, 20),
new PluginConfigColor(new(0, 0, 0, 0))
);
}
[DisableParentSettings("FillColor")]
public class DeathGauge : ChunkedBarConfig
{
[ColorEdit4("Lemure Shroud Color")]
[Order(21)]
public PluginConfigColor LemureShroudColor = new PluginConfigColor(new Vector4(0f / 255f, 176f / 255f, 196f / 255f, 100f / 100f));
[ColorEdit4("Void Shroud Color")]
[Order(22)]
public PluginConfigColor VoidShroudColor = new PluginConfigColor(new Vector4(150f / 255f, 90f / 255f, 144f / 255f, 100f / 100f));
[NestedConfig("Enshroud Duration Text", 50, spacing = true)]
public NumericLabelConfig EnshroudTimerLabel;
public DeathGauge(Vector2 position, Vector2 size, PluginConfigColor fillColor, int padding = 2) : base(position, size, fillColor, padding)
{
EnshroudTimerLabel = new NumericLabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
}
}
}
+319
View File
@@ -0,0 +1,319 @@
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.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class RedMageHud : JobHud
{
private new RedMageConfig Config => (RedMageConfig)_config;
public RedMageHud(RedMageConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.BalanceBar.Enabled)
{
positions.Add(Config.Position + Config.BalanceBar.Position);
sizes.Add(Config.BalanceBar.Size);
}
if (Config.WhiteManaBar.Enabled)
{
positions.Add(Config.Position + Config.WhiteManaBar.Position);
sizes.Add(Config.WhiteManaBar.Size);
}
if (Config.BlackManaBar.Enabled)
{
positions.Add(Config.Position + Config.BlackManaBar.Position);
sizes.Add(Config.BlackManaBar.Size);
}
if (Config.ManaStacksBar.Enabled)
{
positions.Add(Config.Position + Config.ManaStacksBar.Position);
sizes.Add(Config.ManaStacksBar.Size);
}
if (Config.DualcastBar.Enabled)
{
positions.Add(Config.Position + Config.DualcastBar.Position);
sizes.Add(Config.DualcastBar.Size);
}
if (Config.VerstoneBar.Enabled)
{
positions.Add(Config.Position + Config.VerstoneBar.Position);
sizes.Add(Config.VerstoneBar.Size);
}
if (Config.VerfireBar.Enabled)
{
positions.Add(Config.Position + Config.VerfireBar.Position);
sizes.Add(Config.VerfireBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.BalanceBar.Enabled)
{
DrawBalanceBar(pos, player);
}
if (Config.WhiteManaBar.Enabled)
{
DrawWhiteManaBar(pos, player);
}
if (Config.BlackManaBar.Enabled)
{
DrawBlackManaBar(pos, player);
}
if (Config.ManaStacksBar.Enabled)
{
DrawManaStacksBarBar(pos, player);
}
if (Config.DualcastBar.Enabled)
{
DrawDualCastBar(pos, player);
}
if (Config.VerstoneBar.Enabled)
{
DrawVerstoneBar(pos, player);
}
if (Config.VerfireBar.Enabled)
{
DrawVerfireBar(pos, player);
}
}
private void DrawBalanceBar(Vector2 origin, IPlayerCharacter player)
{
RDMGauge gauge = Plugin.JobGauges.Get<RDMGauge>();
float whiteGauge = (float)Plugin.JobGauges.Get<RDMGauge>().WhiteMana;
float blackGauge = (float)Plugin.JobGauges.Get<RDMGauge>().BlackMana;
int scale = gauge.WhiteMana - gauge.BlackMana;
PluginConfigColor color = Config.BalanceBar.FillColor;
int value = 0;
if (whiteGauge >= 50 && blackGauge >= 50)
{
value = 1;
}
else if (scale >= 30)
{
color = Config.WhiteManaBar.FillColor;
value = 1;
}
else if (scale <= -30)
{
color = Config.BlackManaBar.FillColor;
value = 1;
}
if (Config.BalanceBar.HideWhenInactive && value == 0)
{
return;
}
BarHud bar = BarUtilities.GetBar(Config.BalanceBar, value, 1, 0, player, color);
AddDrawActions(bar.GetDrawActions(origin, Config.BalanceBar.StrataLevel));
}
private void DrawWhiteManaBar(Vector2 origin, IPlayerCharacter player)
{
byte mana = Plugin.JobGauges.Get<RDMGauge>().WhiteMana;
if (Config.WhiteManaBar.HideWhenInactive && mana == 0)
{
return;
}
Config.WhiteManaBar.Label.SetValue(mana);
BarHud bar = BarUtilities.GetProgressBar(Config.WhiteManaBar, mana, 100, 0, player);
AddDrawActions(bar.GetDrawActions(origin, Config.WhiteManaBar.StrataLevel));
}
private void DrawBlackManaBar(Vector2 origin, IPlayerCharacter player)
{
byte mana = Plugin.JobGauges.Get<RDMGauge>().BlackMana;
if (Config.BlackManaBar.HideWhenInactive && mana == 0)
{
return;
}
Config.BlackManaBar.Label.SetValue(mana);
BarHud bar = BarUtilities.GetProgressBar(Config.BlackManaBar, mana, 100, 0, player);
AddDrawActions(bar.GetDrawActions(origin, Config.BlackManaBar.StrataLevel));
}
private void DrawManaStacksBarBar(Vector2 origin, IPlayerCharacter player)
{
byte manaStacks = Plugin.JobGauges.Get<RDMGauge>().ManaStacks;
if (Config.ManaStacksBar.HideWhenInactive && manaStacks == 0)
{
return;
}
BarHud[] bars = BarUtilities.GetChunkedBars(Config.ManaStacksBar, 3, manaStacks, 3f, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.ManaStacksBar.StrataLevel));
}
}
private void DrawDualCastBar(Vector2 origin, IPlayerCharacter player)
{
float duration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1249)?.RemainingTime ?? 0f;
if (Config.DualcastBar.HideWhenInactive && duration == 0)
{
return;
};
Config.DualcastBar.Label.SetValue(duration);
BarHud bar = BarUtilities.GetProgressBar(Config.DualcastBar, duration, 15f, 0, player);
AddDrawActions(bar.GetDrawActions(origin, Config.DualcastBar.StrataLevel));
}
private void DrawVerstoneBar(Vector2 origin, IPlayerCharacter player)
{
BarHud? bar = BarUtilities.GetProcBar(Config.VerstoneBar, player, 1235, 30);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.VerstoneBar.StrataLevel));
}
}
private void DrawVerfireBar(Vector2 origin, IPlayerCharacter player)
{
BarHud? bar = BarUtilities.GetProcBar(Config.VerfireBar, player, 1234, 30);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.VerfireBar.StrataLevel));
}
}
}
[Section("Job Specific Bars")]
[SubSection("Caster", 0)]
[SubSection("Red Mage", 1)]
public class RedMageConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.RDM;
public new static RedMageConfig DefaultConfig()
{
var config = new RedMageConfig();
config.UseDefaultPrimaryResourceBar = true;
config.WhiteManaBar.ThresholdConfig.Enabled = true;
config.WhiteManaBar.ThresholdConfig.ChangeColor = false;
config.WhiteManaBar.ThresholdConfig.ShowMarker = true;
config.WhiteManaBar.Label.TextAnchor = DrawAnchor.Right;
config.WhiteManaBar.Label.FrameAnchor = DrawAnchor.Right;
config.WhiteManaBar.Label.Position = new Vector2(-2, 0);
config.BlackManaBar.ThresholdConfig.Enabled = true;
config.BlackManaBar.ThresholdConfig.ChangeColor = false;
config.BlackManaBar.ThresholdConfig.ShowMarker = true;
config.BlackManaBar.Label.TextAnchor = DrawAnchor.Left;
config.BlackManaBar.Label.FrameAnchor = DrawAnchor.Left;
config.BlackManaBar.Label.Position = new Vector2(2, 0);
config.DualcastBar.Label.Enabled = false;
config.VerstoneBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.VerstoneBar.Label.TextAnchor = DrawAnchor.Right;
config.VerstoneBar.Label.FrameAnchor = DrawAnchor.Right;
config.VerstoneBar.Label.Position = new Vector2(-2, 0);
config.VerfireBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.VerfireBar.Label.TextAnchor = DrawAnchor.Left;
config.VerfireBar.Label.FrameAnchor = DrawAnchor.Left;
config.VerfireBar.Label.Position = new Vector2(2, 0);
return config;
}
[NestedConfig("Balance Bar", 30)]
public BarConfig BalanceBar = new BarConfig(
new(0, -10),
new(20, 20),
new PluginConfigColor(new(195f / 255f, 35f / 255f, 35f / 255f, 100f / 100f))
);
[NestedConfig("White Mana Bar", 35)]
public ProgressBarConfig WhiteManaBar = new ProgressBarConfig(
new(-69.5f, -10),
new(115, 20),
new PluginConfigColor(new(221f / 255f, 212f / 255f, 212f / 255f, 100f / 100f)),
BarDirection.Left,
null,
80
);
[NestedConfig("Black Mana Bar", 40)]
public ProgressBarConfig BlackManaBar = new ProgressBarConfig(
new(69.5f, -10),
new(115, 20),
new PluginConfigColor(new(60f / 255f, 81f / 255f, 197f / 255f, 100f / 100f)),
threshold: 80
);
[NestedConfig("Mana Stacks Bar", 45)]
public ChunkedBarConfig ManaStacksBar = new ChunkedBarConfig(
new(0, -27),
new(254, 10),
new PluginConfigColor(new(200f / 255f, 45f / 255f, 40f / 255f, 100f / 100f))
);
[NestedConfig("Dualcast Bar", 50)]
public ProgressBarConfig DualcastBar = new ProgressBarConfig(
new(0, -41),
new(16, 14),
new PluginConfigColor(new(204f / 255f, 17f / 255f, 255f / 95f, 100f / 100f))
);
[NestedConfig("Verstone Ready Bar", 55)]
public ProgressBarConfig VerstoneBar = new ProgressBarConfig(
new(-68.5f, -41),
new(117, 14),
new PluginConfigColor(new(228f / 255f, 188f / 255f, 145 / 255f, 90f / 100f)),
BarDirection.Left
);
[NestedConfig("Verfire Ready Bar", 60)]
public ProgressBarConfig VerfireBar = new ProgressBarConfig(
new(68.5f, -41),
new(117, 14),
new PluginConfigColor(new(238f / 255f, 119f / 255f, 17 / 255f, 90f / 100f))
);
}
}
+246
View File
@@ -0,0 +1,246 @@
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.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.GeneralElements;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Logging;
namespace HSUI.Interface.Jobs
{
public class SageHud : JobHud
{
private new SageConfig Config => (SageConfig)_config;
private static readonly List<uint> DotIDs = new() { 2614, 2615, 2616, 3897 };
private static readonly List<float> DotDurations = new() { 30, 30, 30, 30 };
public SageHud(JobConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new();
List<Vector2> sizes = new();
if (Config.AddersgallBar.Enabled)
{
positions.Add(Config.Position + Config.AddersgallBar.Position);
sizes.Add(Config.AddersgallBar.Size);
}
if (Config.DotBar.Enabled)
{
positions.Add(Config.Position + Config.DotBar.Position);
sizes.Add(Config.DotBar.Size);
}
if (Config.KeracholeBar.Enabled)
{
positions.Add(Config.Position + Config.KeracholeBar.Position);
sizes.Add(Config.KeracholeBar.Size);
}
if (Config.PhysisBar.Enabled)
{
positions.Add(Config.Position + Config.PhysisBar.Position);
sizes.Add(Config.PhysisBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.AddersgallBar.Enabled)
{
DrawAddersgallBar(pos, player);
}
if (Config.DotBar.Enabled)
{
DrawDotBar(pos, player);
}
if (Config.KeracholeBar.Enabled)
{
DrawKeracholeBar(pos, player);
}
if (Config.PhysisBar.Enabled)
{
DrawPhysisBar(pos, player);
}
}
private void DrawDotBar(Vector2 origin, IPlayerCharacter player)
{
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
BarHud? bar = BarUtilities.GetDoTBar(Config.DotBar, player, target, DotIDs, DotDurations);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.DotBar.StrataLevel));
}
}
private void DrawAddersgallBar(Vector2 origin, IPlayerCharacter player)
{
SGEGauge gauge = Plugin.JobGauges.Get<SGEGauge>();
const float addersgallCooldown = 20000f;
float GetScale(int num, float timer) => num + (timer / addersgallCooldown);
float adderScale = GetScale(gauge.Addersgall, gauge.AddersgallTimer);
BarGlowConfig? glow = gauge.Eukrasia && Config.EukrasiaGlow ? Config.AddersgallBar.GlowConfig : null;
if (!Config.AddersgallBar.HideWhenInactive || adderScale > 0)
{
BarHud[] bars = BarUtilities.GetChunkedBars(Config.AddersgallBar, 3, adderScale, 3, 0, player, partialFillColor: Config.AddersgallBar.PartialFillColor, glowConfig: glow, chunksToGlow: new[] { true, true, true });
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.AddersgallBar.StrataLevel));
}
}
if (!Config.AdderstingBar.HideWhenInactive && Config.AdderstingBar.Enabled || gauge.Addersting > 0)
{
int adderstingStacks = player.Level > 65 ? gauge.Addersting : 0;
BarHud[] bars = BarUtilities.GetChunkedBars(Config.AdderstingBar, 3, adderstingStacks, 3, 0, player, glowConfig: glow, chunksToGlow: new[] { true, true, true });
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.AdderstingBar.StrataLevel));
}
}
}
private void DrawPhysisBar(Vector2 origin, IPlayerCharacter player)
{
float physisDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 2617 or 2620 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.PhysisBar.HideWhenInactive || physisDuration > 0)
{
Config.PhysisBar.Label.SetValue(physisDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.PhysisBar, physisDuration, 15f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.PhysisBar.StrataLevel));
}
}
private void DrawKeracholeBar(Vector2 origin, IPlayerCharacter player)
{
float keracholeDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 2618 or 2938 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
float holosDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 3003 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.KeracholeBar.HideWhenInactive || keracholeDuration > 0 || holosDuration > 0)
{
float duration = holosDuration > 0 ? holosDuration : keracholeDuration;
float maxDuration = holosDuration > 0 ? 20f : 15f;
Config.KeracholeBar.Label.SetValue(duration);
BarHud bar = BarUtilities.GetProgressBar(Config.KeracholeBar, duration, maxDuration, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.KeracholeBar.StrataLevel));
}
}
}
[Section("Job Specific Bars")]
[SubSection("Healer", 0)]
[SubSection("Sage", 1)]
public class SageConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.SGE;
public new static SageConfig DefaultConfig()
{
var config = new SageConfig();
config.UseDefaultPrimaryResourceBar = true;
config.DotBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
return config;
}
[Checkbox("Enable Eukrasia Glow", spacing = true)]
[Order(30)]
public bool EukrasiaGlow = true;
[NestedConfig("Addersgall Bar", 35)]
public AddersgallBarConfig AddersgallBar = new AddersgallBarConfig(
new(-64, -32),
new(126, 20),
new PluginConfigColor(new(197f / 255f, 247f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Addersting Bar", 40)]
public AdderstingBarConfig AdderstingBar = new AdderstingBarConfig(
new(64, -32),
new(126, 20),
new PluginConfigColor(new(255f / 255f, 232f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Eukrasian Dosis Bar", 45)]
public ProgressBarConfig DotBar = new ProgressBarConfig(
new(0, -10),
new(254, 20),
new PluginConfigColor(new(41f / 255f, 142f / 255f, 144f / 255f, 100f / 100f))
);
[NestedConfig("Kerachole / Holos Bar", 50)]
public ProgressBarConfig KeracholeBar = new ProgressBarConfig(
new(64, -52),
new(126, 15),
new PluginConfigColor(new(100f / 255f, 207f / 255f, 211f / 255f, 100f / 100f))
);
[NestedConfig("Physis Bar", 55)]
public ProgressBarConfig PhysisBar = new ProgressBarConfig(
new(-64, -52),
new(126, 15),
new PluginConfigColor(new(26f / 255f, 167f / 255f, 109f / 255f, 100f / 100f))
);
}
[Exportable(false)]
public class AddersgallBarConfig : ChunkedBarConfig
{
[NestedConfig("Glow Color (when Eukrasia active)", 60, separator = false, spacing = true)]
public BarGlowConfig GlowConfig = new();
[Checkbox("Use Partial Fill Color", spacing = true)]
[Order(65)]
public bool UsePartialFillColor = false;
[ColorEdit4("Partial Fill Color")]
[Order(66, collapseWith = nameof(UsePartialFillColor))]
public PluginConfigColor PartialFillColor;
public AddersgallBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
GlowConfig.Color = new PluginConfigColor(new(247f / 255f, 177f / 255f, 67f / 255f, 100f / 100f));
PartialFillColor = new PluginConfigColor(new(197 / 255f, 247f / 255f, 255f / 255f, 50f / 100f));
}
}
[Exportable(false)]
public class AdderstingBarConfig : ChunkedBarConfig
{
[NestedConfig("Glow Color (when Eukrasia active)", 60, separator = false, spacing = true)]
public BarGlowConfig GlowConfig = new();
public AdderstingBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
GlowConfig.Color = new PluginConfigColor(new(247f / 255f, 177f / 255f, 67f / 255f, 100f / 100f));
}
}
}
+274
View File
@@ -0,0 +1,274 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Config;
using HSUI.Config.Attributes;
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 SamuraiHud : JobHud
{
private new SamuraiConfig Config => (SamuraiConfig)_config;
private static readonly List<uint> HiganbanaIDs = new() { 1228, 1319 };
private static readonly List<float> HiganabaDurations = new() { 60f, 60f };
public SamuraiHud(SamuraiConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.KenkiBar.Enabled)
{
positions.Add(Config.Position + Config.KenkiBar.Position);
sizes.Add(Config.KenkiBar.Size);
}
if (Config.ShifuBar.Enabled)
{
positions.Add(Config.Position + Config.ShifuBar.Position);
sizes.Add(Config.ShifuBar.Size);
}
if (Config.JinpuBar.Enabled)
{
positions.Add(Config.Position + Config.JinpuBar.Position);
sizes.Add(Config.JinpuBar.Size);
}
if (Config.HiganbanaBar.Enabled)
{
positions.Add(Config.Position + Config.HiganbanaBar.Position);
sizes.Add(Config.HiganbanaBar.Size);
}
if (Config.SenBar.Enabled)
{
positions.Add(Config.Position + Config.SenBar.Position);
sizes.Add(Config.SenBar.Size);
}
if (Config.MeditationBar.Enabled)
{
positions.Add(Config.Position + Config.MeditationBar.Position);
sizes.Add(Config.MeditationBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
if (Config.KenkiBar.Enabled)
{
DrawKenkiBar(origin + Config.Position, player);
}
if (Config.ShifuBar.Enabled)
{
DrawShifuBar(origin + Config.Position, player);
}
if (Config.JinpuBar.Enabled)
{
DrawJinpuBar(origin + Config.Position, player);
}
if (Config.SenBar.Enabled)
{
DrawSenBar(origin + Config.Position, player);
}
if (Config.MeditationBar.Enabled)
{
DrawMeditationBar(origin + Config.Position, player);
}
if (Config.HiganbanaBar.Enabled)
{
DrawHiganbanaBar(origin + Config.Position, player);
}
}
private void DrawKenkiBar(Vector2 origin, IPlayerCharacter player)
{
SAMGauge gauge = Plugin.JobGauges.Get<SAMGauge>();
if (!Config.KenkiBar.HideWhenInactive || gauge.Kenki > 0)
{
Config.KenkiBar.Label.SetValue(gauge.Kenki);
BarHud bar = BarUtilities.GetProgressBar(Config.KenkiBar, gauge.Kenki, 100f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.KenkiBar.StrataLevel));
}
}
private void DrawShifuBar(Vector2 origin, IPlayerCharacter player)
{
float shifuDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1299)?.RemainingTime ?? 0f;
if (!Config.ShifuBar.HideWhenInactive || shifuDuration > 0)
{
Config.ShifuBar.Label.SetValue(shifuDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.ShifuBar, shifuDuration, 40f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.ShifuBar.StrataLevel));
}
}
private void DrawJinpuBar(Vector2 origin, IPlayerCharacter player)
{
float jinpuDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1298)?.RemainingTime ?? 0f;
if (!Config.JinpuBar.HideWhenInactive || jinpuDuration > 0)
{
Config.JinpuBar.Label.SetValue(jinpuDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.JinpuBar, jinpuDuration, 40f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.JinpuBar.StrataLevel));
}
}
private void DrawHiganbanaBar(Vector2 origin, IPlayerCharacter player)
{
var target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
BarHud? bar = BarUtilities.GetDoTBar(Config.HiganbanaBar, player, target, HiganbanaIDs, HiganabaDurations);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.HiganbanaBar.StrataLevel));
}
}
private void DrawSenBar(Vector2 origin, IPlayerCharacter player)
{
SAMGauge gauge = Plugin.JobGauges.Get<SAMGauge>();
if (!Config.SenBar.HideWhenInactive || gauge.HasSetsu || gauge.HasGetsu || gauge.HasKa)
{
var order = Config.SenBar.SenOrder;
var hasSen = new[] { gauge.HasSetsu ? 1 : 0, gauge.HasGetsu ? 1 : 0, gauge.HasKa ? 1 : 0 };
var colors = new[] { Config.SenBar.SetsuColor, Config.SenBar.GetsuColor, Config.SenBar.KaColor };
var sen = new Tuple<PluginConfigColor, float, LabelConfig?>[3];
for (int i = 0; i < 3; i++)
{
sen[i] = new Tuple<PluginConfigColor, float, LabelConfig?>(colors[order[i]], hasSen[order[i]], null);
}
BarHud[] bars = BarUtilities.GetChunkedBars(Config.SenBar, sen, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.SenBar.StrataLevel));
}
}
}
private void DrawMeditationBar(Vector2 origin, IPlayerCharacter player)
{
SAMGauge gauge = Plugin.JobGauges.Get<SAMGauge>();
if (!Config.MeditationBar.HideWhenInactive || gauge.MeditationStacks > 0)
{
BarHud[] bars = BarUtilities.GetChunkedBars(Config.MeditationBar, 3, gauge.MeditationStacks, 3f, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.MeditationBar.StrataLevel));
}
}
}
}
[Section("Job Specific Bars")]
[SubSection("Melee", 0)]
[SubSection("Samurai", 1)]
public class SamuraiConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.SAM;
public new static SamuraiConfig DefaultConfig()
{
var config = new SamuraiConfig();
config.HiganbanaBar.ThresholdConfig.Enabled = true;
return config;
}
[NestedConfig("Sen Bar", 40)]
public SamuraiSenBarConfig SenBar = new SamuraiSenBarConfig(
new(0, -17),
new(254, 10),
new PluginConfigColor(new Vector4(0, 0, 0, 0))
);
[NestedConfig("Fuka Bar", 45)]
public ProgressBarConfig ShifuBar = new ProgressBarConfig(
new(-64, -56),
new(126, 20),
new PluginConfigColor(new(219f / 255f, 211f / 255f, 136f / 255f, 100f / 100f))
);
[NestedConfig("Fugetsu Bar", 50)]
public ProgressBarConfig JinpuBar = new ProgressBarConfig(
new(64, -56),
new(126, 20),
new PluginConfigColor(new(136f / 255f, 146f / 255f, 219f / 255f, 100f / 100f))
);
[NestedConfig("Kenki Bar", 55)]
public ProgressBarConfig KenkiBar = new ProgressBarConfig(
new(0, -34),
new(254, 20),
new PluginConfigColor(new(255f / 255f, 82f / 255f, 82f / 255f, 53f / 100f))
);
[NestedConfig("Higanbana Bar", 60)]
public ProgressBarConfig HiganbanaBar = new ProgressBarConfig(
new(0, -78),
new(254, 20),
new PluginConfigColor(new(237f / 255f, 141f / 255f, 7f / 255f, 100f / 100f)),
BarDirection.Right,
new PluginConfigColor(new(230f / 255f, 33f / 255f, 33f / 255f, 53f / 100f)),
15f
);
[NestedConfig("Meditation Bar", 65)]
public ChunkedBarConfig MeditationBar = new ChunkedBarConfig(
new(0, -5),
new(254, 10),
new PluginConfigColor(new(247f / 255f, 163f / 255f, 89f / 255f, 100f / 100f))
);
}
[DisableParentSettings("FillColor")]
[Exportable(false)]
public class SamuraiSenBarConfig : ChunkedBarConfig
{
[ColorEdit4("Setsu", spacing = true)]
[Order(60)]
public PluginConfigColor SetsuColor = new PluginConfigColor(new(89f / 255f, 234f / 255f, 247f / 255f, 100f / 100f));
[ColorEdit4("Getsu")]
[Order(65)]
public PluginConfigColor GetsuColor = new PluginConfigColor(new(89f / 255f, 126f / 255f, 247f / 255f, 100f / 100f));
[ColorEdit4("Ka")]
[Order(70)]
public PluginConfigColor KaColor = new PluginConfigColor(new(247f / 255f, 89f / 255f, 89f / 255f, 100f / 100f));
[DragDropHorizontal("Order", "Setsu", "Getsu", "Ka")]
[Order(75)]
public int[] SenOrder = new int[] { 0, 1, 2 };
public SamuraiSenBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor, 2) { }
}
}
+209
View File
@@ -0,0 +1,209 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class ScholarHud : JobHud
{
private new ScholarConfig Config => (ScholarConfig)_config;
private static readonly List<uint> BioDoTIDs = new() { 179, 189, 1895 };
private static readonly List<float> BioDoTDurations = new() { 30, 30, 30 };
public ScholarHud(ScholarConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.AetherflowBar.Enabled)
{
positions.Add(Config.Position + Config.AetherflowBar.Position);
sizes.Add(Config.AetherflowBar.Size);
}
if (Config.FairyGaugeBar.Enabled)
{
positions.Add(Config.Position + Config.FairyGaugeBar.Position);
sizes.Add(Config.FairyGaugeBar.Size);
}
if (Config.BioBar.Enabled)
{
positions.Add(Config.Position + Config.BioBar.Position);
sizes.Add(Config.BioBar.Size);
}
if (Config.SacredSoilBar.Enabled)
{
positions.Add(Config.Position + Config.SacredSoilBar.Position);
sizes.Add(Config.SacredSoilBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.BioBar.Enabled)
{
DrawBioBar(pos, player);
}
if (Config.FairyGaugeBar.Enabled)
{
DrawFairyGaugeBar(pos, player);
}
if (Config.AetherflowBar.Enabled)
{
DrawAetherBar(pos, player);
}
if (Config.SacredSoilBar.Enabled)
{
DrawSacredSoilBar(pos, player);
}
}
private void DrawBioBar(Vector2 origin, IPlayerCharacter player)
{
var target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
BarHud? bar = BarUtilities.GetDoTBar(Config.BioBar, player, target, BioDoTIDs, BioDoTDurations);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.BioBar.StrataLevel));
}
}
private unsafe void DrawFairyGaugeBar(Vector2 origin, IPlayerCharacter player)
{
SCHGauge gauge = Plugin.JobGauges.Get<SCHGauge>();
byte fairyGauge = gauge.FairyGauge;
float seraphDuration = gauge.SeraphTimer;
if (Config.FairyGaugeBar.HideWhenInactive && fairyGauge == 0 && (seraphDuration == 0 || !Config.FairyGaugeBar.ShowSeraph))
{
return;
}
if (Config.FairyGaugeBar.ShowSeraph && seraphDuration > 0)
{
Config.FairyGaugeBar.Label.SetValue(seraphDuration / 1000);
BarHud bar = BarUtilities.GetProgressBar(Config.FairyGaugeBar, seraphDuration / 1000, 22, 0, player, Config.FairyGaugeBar.SeraphColor);
AddDrawActions(bar.GetDrawActions(origin, Config.FairyGaugeBar.StrataLevel));
}
else
{
Config.FairyGaugeBar.Label.SetValue(fairyGauge);
BarHud bar = BarUtilities.GetProgressBar(Config.FairyGaugeBar, fairyGauge, 100, 0, player);
AddDrawActions(bar.GetDrawActions(origin, Config.FairyGaugeBar.StrataLevel));
}
}
private void DrawAetherBar(Vector2 origin, IPlayerCharacter player)
{
ushort stackCount = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 304)?.Param ?? 0;
if (Config.AetherflowBar.HideWhenInactive && stackCount == 0)
{
return;
};
BarHud[] bars = BarUtilities.GetChunkedBars(Config.AetherflowBar, 3, stackCount, 3, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.AetherflowBar.StrataLevel));
}
}
private void DrawSacredSoilBar(Vector2 origin, IPlayerCharacter player)
{
float sacredSoilDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 298 or 1944 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.SacredSoilBar.HideWhenInactive || sacredSoilDuration > 0)
{
Config.SacredSoilBar.Label.SetValue(sacredSoilDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.SacredSoilBar, sacredSoilDuration, 15f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.SacredSoilBar.StrataLevel));
}
}
}
[Section("Job Specific Bars")]
[SubSection("Healer", 0)]
[SubSection("Scholar", 1)]
public class ScholarConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.SCH;
public new static ScholarConfig DefaultConfig()
{
var config = new ScholarConfig();
config.UseDefaultPrimaryResourceBar = true;
return config;
}
[NestedConfig("Bio Bar", 30)]
public ProgressBarConfig BioBar = new ProgressBarConfig(
new(0, -10),
new(254, 20),
new(new Vector4(50f / 255f, 93f / 255f, 37f / 255f, 1f))
);
[NestedConfig("Fairy Gauge", 35)]
public ScholarFairyGaugeBarConfig FairyGaugeBar = new ScholarFairyGaugeBarConfig(
new(0, -32),
new(254, 20),
new(new Vector4(69f / 255f, 199 / 255f, 164f / 255f, 100f / 100f))
);
[NestedConfig("Aetherflow Bar", 40)]
public ChunkedBarConfig AetherflowBar = new ChunkedBarConfig(
new(0, -54),
new(254, 20),
new(new Vector4(0f / 255f, 255f / 255f, 0f / 255f, 100f / 100f))
);
[NestedConfig("Sacred Soil Bar", 45)]
public ProgressBarConfig SacredSoilBar = new ProgressBarConfig(
new(-0, -76),
new(254, 20),
new PluginConfigColor(new(241f / 255f, 217f / 255f, 125f / 255f, 100f / 100f))
);
}
[Exportable(false)]
public class ScholarFairyGaugeBarConfig : ProgressBarConfig
{
[Checkbox("Show Seraph", spacing = true)]
[Order(50)]
public bool ShowSeraph = true;
[ColorEdit4("Color" + "##SeraphColor")]
[Order(55)]
public PluginConfigColor SeraphColor = new(new Vector4(232f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
public ScholarFairyGaugeBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
}
+429
View File
@@ -0,0 +1,429 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
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 FFXIVClientStructs.FFXIV.Common.Lua;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class SummonerHud : JobHud
{
private new SummonerConfig Config => (SummonerConfig)_config;
public SummonerHud(SummonerConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new();
List<Vector2> sizes = new();
if (Config.AetherflowBar.Enabled)
{
positions.Add(Config.Position + Config.AetherflowBar.Position);
sizes.Add(Config.AetherflowBar.Size);
}
if (Config.TranceBar.Enabled)
{
positions.Add(Config.Position + Config.TranceBar.Position);
sizes.Add(Config.TranceBar.Size);
}
if (Config.IfritBar.Enabled)
{
positions.Add(Config.Position + Config.IfritBar.Position);
sizes.Add(Config.IfritBar.Size);
}
if (Config.TitanBar.Enabled)
{
positions.Add(Config.Position + Config.TitanBar.Position);
sizes.Add(Config.TitanBar.Size);
}
if (Config.GarudaBar.Enabled)
{
positions.Add(Config.Position + Config.GarudaBar.Position);
sizes.Add(Config.GarudaBar.Size);
}
if (Config.StacksBar.Enabled)
{
positions.Add(Config.Position + Config.StacksBar.Position);
sizes.Add(Config.StacksBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.AetherflowBar.Enabled)
{
DrawAetherBar(pos, player);
}
if (Config.TranceBar.Enabled)
{
DrawTranceBar(pos, player);
}
if (Config.IfritBar.Enabled)
{
DrawIfritBar(pos, player);
}
if (Config.TitanBar.Enabled)
{
DrawTitanBar(pos, player);
}
if (Config.GarudaBar.Enabled)
{
DrawGarudaBar(pos, player);
}
if (Config.StacksBar.Enabled)
{
HandleAttunementStacks(pos, player);
}
}
private unsafe void DrawIfritBar(Vector2 origin, IPlayerCharacter player)
{
SMNGauge gauge = Plugin.JobGauges.Get<SMNGauge>();
int stackCount = gauge.IsIfritReady ? 1 : 0;
if (!Config.IfritBar.HideWhenInactive || stackCount > 1)
{
BarHud[] bars = BarUtilities.GetChunkedBars(Config.IfritBar, 1, stackCount, 1, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.IfritBar.StrataLevel));
}
}
}
private void DrawTitanBar(Vector2 origin, IPlayerCharacter player)
{
SMNGauge gauge = Plugin.JobGauges.Get<SMNGauge>();
int stackCount = gauge.IsTitanReady ? 1 : 0;
if (!Config.TitanBar.HideWhenInactive || stackCount > 1)
{
BarHud[] bars = BarUtilities.GetChunkedBars(Config.TitanBar, 1, stackCount, 1, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.TitanBar.StrataLevel));
}
}
}
private void DrawGarudaBar(Vector2 origin, IPlayerCharacter player)
{
SMNGauge gauge = Plugin.JobGauges.Get<SMNGauge>();
int stackCount = gauge.IsGarudaReady ? 1 : 0;
if (!Config.GarudaBar.HideWhenInactive || stackCount > 1)
{
BarHud[] bars = BarUtilities.GetChunkedBars(Config.GarudaBar, 1, stackCount, 1, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.GarudaBar.StrataLevel));
}
}
}
private enum Primal
{
None = 0,
Ifrit = 1,
Titan = 2,
Garuda = 3
}
private unsafe void HandleAttunementStacks(Vector2 origin, IPlayerCharacter player)
{
SMNGauge gauge = Plugin.JobGauges.Get<SMNGauge>();
byte value = *((byte*)(new IntPtr(gauge.Address) + 0xE));
Primal primal = (Primal)(value & 3);
int stacks = ((value >> 2) & 7);
if (primal == Primal.Ifrit && Config.StacksBar.ShowIfritStacks)
{
DrawStacksBar(origin, player, stacks, 2, Config.StacksBar.IfritStackColor);
}
else if (primal == Primal.Titan && Config.StacksBar.ShowTitanStacks)
{
DrawStacksBar(origin, player, stacks, 4, Config.StacksBar.TitanStackColor);
}
else if (primal == Primal.Garuda && Config.StacksBar.ShowGarudaStacks)
{
DrawStacksBar(origin, player, stacks, 4, Config.StacksBar.GarudaStackColor);
}
else if (!Config.StacksBar.HideWhenInactive)
{
DrawStacksBar(origin, player, 0, 1, Config.StacksBar.FillColor);
}
}
private void DrawAetherBar(Vector2 origin, IPlayerCharacter player)
{
ushort stackCount = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId == 304)?.Param ?? 0;
if (Config.AetherflowBar.HideWhenInactive && stackCount == 0)
{
return;
}
BarHud[] bars = BarUtilities.GetChunkedBars(Config.AetherflowBar, 2, stackCount, 2, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.AetherflowBar.StrataLevel));
}
}
private unsafe void DrawTranceBar(Vector2 origin, IPlayerCharacter player)
{
SMNGauge gauge = Plugin.JobGauges.Get<SMNGauge>();
PluginConfigColor tranceColor;
uint spellID = 0;
float maxDuration = 0f;
float currentCooldown = 0f;
float tranceDuration = 0f;
tranceColor = Config.TranceBar.FillColor;
// Dawntrail Fixes
bool isSolarBahamutReady = gauge.AetherFlags.HasFlag(AetherFlags.None + 0x8) || // 0x8 Formerly Titan Attuned
gauge.AetherFlags.HasFlag(AetherFlags.None + 0xC); // 0xC Formerly Garuda Attuned
bool isPhoenixReady = gauge.AetherFlags.HasFlag(AetherFlags.None + 0x4); // 0x4 Formerly Ifrit Attuned
bool isNormalBahamutReady = !isSolarBahamutReady && !isPhoenixReady; // You'd think it would be 0x10, but thats unused now
byte summonedPrimal = *((byte*)(new IntPtr(gauge.Address) + 0xE)); // Formally Attunement, now...?
Primal primal = (Primal)(summonedPrimal & 3);
if (primal != Primal.None)
{
tranceColor = primal == Primal.Ifrit ? Config.TranceBar.IfritColor : primal == Primal.Titan ? Config.TranceBar.TitanColor : primal == Primal.Garuda ? Config.TranceBar.GarudaColor : Config.TranceBar.FillColor;
tranceDuration = gauge.AttunementTimerRemaining;
maxDuration = 30f;
}
else
{
if (isSolarBahamutReady)
{
tranceColor = Config.TranceBar.SolarBahamutColor;
tranceDuration = gauge.SummonTimerRemaining;
spellID = 36992;
maxDuration = 15f;
}
else if (isNormalBahamutReady)
{
tranceColor = Config.TranceBar.BahamutColor;
tranceDuration = gauge.SummonTimerRemaining;
spellID = 7427;
maxDuration = 15f;
}
else if (isPhoenixReady)
{
tranceColor = Config.TranceBar.PhoenixColor;
tranceDuration = gauge.SummonTimerRemaining;
spellID = 25831;
maxDuration = 15f;
}
}
if (tranceDuration != 0)
{
if (gauge.AttunementTimerRemaining > 0 && Config.TranceBar.HidePrimals)
{
return;
}
Config.TranceBar.Label.SetValue(tranceDuration / 1000f);
BarHud bar = BarUtilities.GetProgressBar(Config.TranceBar, tranceDuration / 1000f, maxDuration, 0, player, tranceColor);
AddDrawActions(bar.GetDrawActions(origin, Config.TranceBar.StrataLevel));
}
else
{
if (!Config.TranceBar.HideWhenInactive)
{
if (gauge.AttunementTimerRemaining == 0)
{
maxDuration = SpellHelper.Instance.GetRecastTime(spellID);
float tranceCooldown = SpellHelper.Instance.GetSpellCooldown(spellID);
currentCooldown = maxDuration - tranceCooldown;
Config.TranceBar.Label.SetValue(maxDuration - currentCooldown);
if (currentCooldown == maxDuration)
{
Config.TranceBar.Label.SetText("READY");
}
BarHud bar = BarUtilities.GetProgressBar(Config.TranceBar, currentCooldown, maxDuration, 0, player, tranceColor);
AddDrawActions(bar.GetDrawActions(origin, Config.TranceBar.StrataLevel));
}
}
}
}
private void DrawStacksBar(Vector2 origin, IPlayerCharacter player, int amount, int max, PluginConfigColor stackColor, BarGlowConfig? glowConfig = null)
{
SummonerStacksBarConfig 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.StacksBar.StrataLevel));
}
}
}
[Section("Job Specific Bars")]
[SubSection("Caster", 0)]
[SubSection("Summoner", 1)]
public class SummonerConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.SMN;
public new static SummonerConfig DefaultConfig()
{
var config = new SummonerConfig();
config.TranceBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.UseDefaultPrimaryResourceBar = true;
return config;
}
[NestedConfig("Aetherflow Bar", 40)]
public ChunkedBarConfig AetherflowBar = new ChunkedBarConfig(
new(-0, -7),
new(254, 14),
new(new Vector4(255f / 255f, 177f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Trance Bar", 45)]
public SummonerTranceBarConfig TranceBar = new SummonerTranceBarConfig(
new(0, -23),
new(254, 14),
new(new Vector4(128f / 255f, 255f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Ifrit Bar", 50)]
public ChunkedBarConfig IfritBar = new ChunkedBarConfig(
new(-85, -39),
new(84, 14),
new(new Vector4(200f / 255f, 40f / 255f, 0f / 255f, 100f / 100f))
);
[NestedConfig("Titan Bar", 55)]
public ChunkedBarConfig TitanBar = new ChunkedBarConfig(
new(0, -39),
new(84, 14),
new(new Vector4(210f / 255f, 150f / 255f, 26f / 255f, 100f / 100f))
);
[NestedConfig("Garuda Bar", 60)]
public ChunkedBarConfig GarudaBar = new ChunkedBarConfig(
new(85, -39),
new(84, 14),
new(new Vector4(60f / 255f, 160f / 255f, 100f / 255f, 100f / 100f))
);
[NestedConfig("Attunement Stacks Bar", 65)]
public SummonerStacksBarConfig StacksBar = new SummonerStacksBarConfig(
new(0, -55),
new(254, 14),
new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 0f / 100f))
);
}
[Exportable(false)]
public class SummonerTranceBarConfig : ProgressBarConfig
{
[ColorEdit4("Bahamut Color")]
[Order(26)]
public PluginConfigColor BahamutColor = new(new Vector4(128f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
[ColorEdit4("Phoenix Color")]
[Order(27)]
public PluginConfigColor PhoenixColor = new(new Vector4(240f / 255f, 100f / 255f, 10f / 255f, 100f / 100f));
[ColorEdit4("Solar Bahamut Color")]
[Order(28)]
public PluginConfigColor SolarBahamutColor = new(new Vector4(235f / 255f, 241f / 255f, 252f / 255f, 100f / 100f));
[ColorEdit4("Ifrit Color")]
[Order(29)]
public PluginConfigColor IfritColor = new(new Vector4(200f / 255f, 40f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Titan Color")]
[Order(30)]
public PluginConfigColor TitanColor = new(new Vector4(210f / 255f, 150f / 255f, 26f / 255f, 100f / 100f));
[ColorEdit4("Garuda Color")]
[Order(31)]
public PluginConfigColor GarudaColor = new(new Vector4(60f / 255f, 160f / 255f, 100f / 255f, 100f / 100f));
[Checkbox("Hide Primals")]
[Order(45)]
public bool HidePrimals = false;
public SummonerTranceBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
[Exportable(false)]
public class SummonerStacksBarConfig : ChunkedBarConfig
{
[Checkbox("Ifrit Stacks", separator = false, spacing = false)]
[Order(51)]
public bool ShowIfritStacks = true;
[Checkbox("Titan Stacks", separator = false, spacing = false)]
[Order(53)]
public bool ShowTitanStacks = true;
[Checkbox("Garuda Stacks", separator = false, spacing = false)]
[Order(55)]
public bool ShowGarudaStacks = true;
[ColorEdit4("Ifrit Stacks Color")]
[Order(56)]
public PluginConfigColor IfritStackColor = new(new Vector4(200f / 255f, 40f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Titan Stacks Color")]
[Order(57)]
public PluginConfigColor TitanStackColor = new(new Vector4(210f / 255f, 150f / 255f, 26f / 255f, 100f / 100f));
[ColorEdit4("Garuda Stacks Color")]
[Order(58)]
public PluginConfigColor GarudaStackColor = new(new Vector4(60f / 255f, 160f / 255f, 100f / 255f, 100f / 100f));
public SummonerStacksBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
}
+373
View File
@@ -0,0 +1,373 @@
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.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 ViperHud : JobHud
{
private new ViperConfig Config => (ViperConfig)_config;
public ViperHud(ViperConfig config, string? displayName = null) : base(config, displayName) { }
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new List<Vector2>();
List<Vector2> sizes = new List<Vector2>();
if (Config.RattlingCoilGauge.Enabled)
{
positions.Add(Config.Position + Config.RattlingCoilGauge.Position);
sizes.Add(Config.RattlingCoilGauge.Size);
}
if (Config.Vipersight.Enabled)
{
positions.Add(Config.Position + Config.Vipersight.Position);
sizes.Add(Config.Vipersight.Size);
}
if (Config.AnguineTribute.Enabled)
{
positions.Add(Config.Position + Config.AnguineTribute.Position);
sizes.Add(Config.AnguineTribute.Size);
}
if (Config.SerpentOfferings.Enabled)
{
positions.Add(Config.Position + Config.SerpentOfferings.Position);
sizes.Add(Config.SerpentOfferings.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
if (Config.RattlingCoilGauge.Enabled)
{
DrawRattlingCoilGauge(origin + Config.Position, player);
}
if (Config.Vipersight.Enabled)
{
DrawVipersightBar(origin + Config.Position, player);
}
if (Config.SerpentOfferings.Enabled)
{
DrawSerpentOfferingsBar(origin + Config.Position, player);
}
if (Config.AnguineTribute.Enabled)
{
DrawAnguineTributeGauge(origin + Config.Position, player);
}
}
private unsafe void DrawVipersightBar(Vector2 origin, IPlayerCharacter player)
{
ViperCombo lastUsedActionId = (ViperCombo)SpellHelper.Instance.GetLastUsedActionId();
ViperComboState comboState;
bool isAoE = false;
switch (lastUsedActionId)
{
case ViperCombo.SteelMaw:
case ViperCombo.DreadMaw:
isAoE = true;
comboState = ViperComboState.Started;
break;
case ViperCombo.SteelFangs:
case ViperCombo.DreadFangs:
comboState = ViperComboState.Started;
break;
case ViperCombo.HuntersBite:
case ViperCombo.SwiftskinsBite:
isAoE = true;
comboState = ViperComboState.Finisher;
break;
case ViperCombo.HuntersSting:
case ViperCombo.SwiftskinsSting:
comboState = ViperComboState.Finisher;
break;
default:
comboState = ViperComboState.None;
break;
}
if (Config.Vipersight.HideWhenInactive && comboState == ViperComboState.None)
{
return;
}
uint leftId = SpellHelper.Instance.GetSpellActionId(isAoE ? (uint)ViperCombo.SteelMaw : (uint)ViperCombo.SteelFangs);
bool isLeftGlowing = SpellHelper.Instance.IsActionHighlighted(leftId);
uint rightId = SpellHelper.Instance.GetSpellActionId(isAoE ? (uint)ViperCombo.DreadMaw : (uint)ViperCombo.DreadFangs);
bool isRightGlowing = SpellHelper.Instance.IsActionHighlighted(rightId);
List<Tuple<PluginConfigColor, float, LabelConfig?>> chunks = new();
List<bool> glows = new();
Tuple<PluginConfigColor, float, LabelConfig?> empty = new(PluginConfigColor.Empty, 1, null);
Tuple<PluginConfigColor, float, LabelConfig?> start = new(Config.Vipersight.ComboStartColor, 1, null);
Tuple<PluginConfigColor, float, LabelConfig?> endFlank = new(Config.Vipersight.ComboEndFlankColor, 1, null);
Tuple<PluginConfigColor, float, LabelConfig?> endHind = new(Config.Vipersight.ComboEndHindColor, 1, null);
Tuple<PluginConfigColor, float, LabelConfig?> endAoE = new(Config.Vipersight.ComboEndAOEColor, 1, null);
bool isFlankEnder = Utils.StatusListForBattleChara(player).Any(o => o.StatusId is 3645 or 3646);
bool isHindEnder = Utils.StatusListForBattleChara(player).Any(o => o.StatusId is 3647 or 3648);
bool noEnder = !isFlankEnder && !isHindEnder;
switch (comboState)
{
case ViperComboState.None:
{
chunks = [empty, empty, empty, empty];
glows = [false, isLeftGlowing, isRightGlowing, false];
break;
}
case ViperComboState.Started:
{
chunks = [empty, start, start, empty];
glows = [false, isLeftGlowing || isAoE, isRightGlowing || isAoE, false];
break;
}
case ViperComboState.Finisher:
{
bool isFlankChain = lastUsedActionId == ViperCombo.HuntersSting;
bool isHindChain = lastUsedActionId == ViperCombo.SwiftskinsSting;
Tuple<PluginConfigColor, float, LabelConfig?> end;
if (isFlankEnder)
{
end = isHindChain ? endHind : endFlank;
}
else if (isHindEnder)
{
end = isFlankChain ? endFlank : endHind;
}
else
{
end = isFlankChain ? endFlank : isHindChain ? endHind : endAoE;
}
chunks = [end, start, start, end];
glows = [isLeftGlowing, isLeftGlowing, isRightGlowing, isRightGlowing];
break;
}
}
if (Config.Vipersight.Invert)
{
chunks.Reverse();
glows.Reverse();
}
BarHud[] bars = BarUtilities.GetChunkedBars(
Config.Vipersight,
chunks.ToArray(),
player,
Config.Vipersight.GlowConfig,
glows.ToArray()
);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.Vipersight.StrataLevel));
}
}
private unsafe void DrawRattlingCoilGauge(Vector2 origin, IPlayerCharacter player)
{
VPRGauge gauge = Plugin.JobGauges.Get<VPRGauge>();
if (Config.RattlingCoilGauge.HideWhenInactive && gauge.RattlingCoilStacks <= 0)
{
return;
}
int maxStacks = player.Level >= 88 ? 3 : 2;
BarHud[] bars = BarUtilities.GetChunkedBars(Config.RattlingCoilGauge, maxStacks, gauge.RattlingCoilStacks, maxStacks, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.RattlingCoilGauge.StrataLevel));
}
}
private unsafe void DrawAnguineTributeGauge(Vector2 origin, IPlayerCharacter player)
{
VPRGauge gauge = Plugin.JobGauges.Get<VPRGauge>();
if (Config.AnguineTribute.HideWhenInactive && gauge.AnguineTribute <= 0)
{
return;
}
int maxStacks = player.Level >= 96 ? 5 : 4;
BarHud[] bars = BarUtilities.GetChunkedBars(Config.AnguineTribute, maxStacks, gauge.AnguineTribute, maxStacks, 0, player);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.AnguineTribute.StrataLevel));
}
}
private unsafe void DrawSerpentOfferingsBar(Vector2 origin, IPlayerCharacter player)
{
ViperConfig.SerpentOfferingsBarConfig config = Config.SerpentOfferings;
VPRGauge gauge = Plugin.JobGauges.Get<VPRGauge>();
float reawakenedDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 3670 or 4094 && o.RemainingTime > 0f)?.RemainingTime ?? 0f;
bool reAwakenedReady = Utils.StatusListForBattleChara(player).Any(o => o.StatusId is 3671) || gauge.SerpentOffering >= 50;
bool isReawakened = reawakenedDuration > 0;
bool showReawakened = isReawakened && config.EnableAwakenedTimer;
float serpentOffering = showReawakened && isReawakened ? reawakenedDuration : gauge.SerpentOffering;
if (Config.SerpentOfferings.HideWhenInactive && gauge.SerpentOffering <= 0)
{
return;
}
Config.SerpentOfferings.Label.SetValue(serpentOffering);
BarHud[] bars = BarUtilities.GetChunkedProgressBars(
config,
showReawakened ? 1 : 2,
showReawakened ? reawakenedDuration : serpentOffering,
showReawakened ? 30f : 100f,
fillColor: reAwakenedReady ? config.AwakenedColor : config.FillColor
); ;
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.SerpentOfferings.StrataLevel));
}
}
}
public enum ViperCombo
{
SteelFangs = 34606,
DreadFangs = 34607,
HuntersSting = 34608,
SwiftskinsSting = 34609,
SteelMaw = 34614,
DreadMaw = 34615,
HuntersBite = 34616,
SwiftskinsBite = 34617
}
public enum ViperComboState
{
None,
Started,
Finisher
}
[Section("Job Specific Bars")]
[SubSection("Melee", 0)]
[SubSection("Viper", 1)]
public class ViperConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.VPR;
public new static ViperConfig DefaultConfig()
{
var config = new ViperConfig();
config.SerpentOfferings.UseChunks = false;
return config;
}
[NestedConfig("Vipersight Bar", 30)]
public VipersightBarConfig Vipersight = new VipersightBarConfig(
new(0, -10),
new(254, 10),
new(new Vector4(237f / 255f, 141f / 255f, 7f / 255f, 100f / 100f))
);
[NestedConfig("Rattling Coil Bar", 40)]
public ChunkedBarConfig RattlingCoilGauge = new ChunkedBarConfig(
new(0, -34),
new(254, 10),
new(new Vector4(204f / 255f, 40f / 255f, 40f / 255f, 1f))
);
[NestedConfig("Serpent Offerings Bar", 45)]
public SerpentOfferingsBarConfig SerpentOfferings = new SerpentOfferingsBarConfig(
new(0, -46),
new(254, 10),
new(new Vector4(69f / 255f, 115f / 255f, 202f / 255f, 1f))
);
[NestedConfig("Anguine Tribute Bar", 50)]
public ChunkedBarConfig AnguineTribute = new ChunkedBarConfig(
new(0, -58),
new(254, 10),
new(new Vector4(69f / 255f, 115f / 255f, 202f / 255f, 1f))
);
[Exportable(false)]
public class VipersightBarConfig : ChunkedBarConfig
{
[NestedConfig("Show Glow", 39, separator = false, spacing = true)]
public BarGlowConfig GlowConfig = new BarGlowConfig();
[ColorEdit4("Combo Start", spacing = true)]
[Order(41)]
public PluginConfigColor ComboStartColor = new(new Vector4(230f / 255f, 33f / 255f, 33f / 255f, 100f / 100f));
[ColorEdit4("Flank Ender")]
[Order(42)]
public PluginConfigColor ComboEndFlankColor = new(new Vector4(46f / 255f, 228f / 255f, 42f / 255f, 1f));
[ColorEdit4("Hind Ender")]
[Order(43)]
public PluginConfigColor ComboEndHindColor = new(new Vector4(230f / 255f, 33f / 255f, 33f / 255f, 1f));
[ColorEdit4("Grim/Default Ender")]
[Order(44)]
public PluginConfigColor ComboEndAOEColor = new(new Vector4(69f / 255f, 115f / 255f, 202f / 255f, 1f));
[Checkbox("Invert", spacing = true)]
[Order(45)]
public bool Invert = false;
public VipersightBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
[Exportable(false)]
public class SerpentOfferingsBarConfig : ChunkedProgressBarConfig
{
[Checkbox("Enable Awakened Timer", spacing = true)]
[Order(46)]
public bool EnableAwakenedTimer = true;
[ColorEdit4("Ready to Reawaken Color")]
[Order(47)]
public PluginConfigColor AwakenedColor = new(new Vector4(69f / 255f, 115f / 255f, 202f / 255f, 1f));
public SerpentOfferingsBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
}
}
}
}
+279
View File
@@ -0,0 +1,279 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using HSUI.Interface.GeneralElements;
namespace HSUI.Interface.Jobs
{
public class WarriorHud : JobHud
{
private new WarriorConfig Config => (WarriorConfig)_config;
public WarriorHud(WarriorConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new();
List<Vector2> sizes = new();
if (Config.SurgingTempestBar.Enabled)
{
positions.Add(Config.Position + Config.SurgingTempestBar.Position);
sizes.Add(Config.SurgingTempestBar.Size);
}
if (Config.BeastGauge.Enabled)
{
positions.Add(Config.Position + Config.BeastGauge.Position);
sizes.Add(Config.BeastGauge.Size);
}
if (Config.InnerReleaseBar.Enabled)
{
positions.Add(Config.Position + Config.InnerReleaseBar.Position);
sizes.Add(Config.InnerReleaseBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.SurgingTempestBar.Enabled)
{
DrawSurgingTempestBar(pos, player);
}
if (Config.BeastGauge.Enabled)
{
DrawBeastGauge(pos, player);
}
if (Config.InnerReleaseBar.Enabled)
{
DrawInnerReleaseBar(pos, player);
}
}
private void DrawSurgingTempestBar(Vector2 origin, IPlayerCharacter player)
{
float surgingTempestDuration = Math.Abs(Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 2677)?.RemainingTime ?? 0f);
if (!Config.SurgingTempestBar.HideWhenInactive || surgingTempestDuration > 0)
{
Config.SurgingTempestBar.Label.SetValue(surgingTempestDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.SurgingTempestBar, surgingTempestDuration, 60f, 0, player);
AddDrawActions(bar.GetDrawActions(origin, Config.SurgingTempestBar.StrataLevel));
}
}
private void DrawBeastGauge(Vector2 origin, IPlayerCharacter player)
{
WARGauge gauge = Plugin.JobGauges.Get<WARGauge>();
var nascentChaosDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1897)?.RemainingTime ?? 0f;
if (!Config.BeastGauge.HideWhenInactive || gauge.BeastGauge > 0 || nascentChaosDuration > 0)
{
Config.BeastGauge.Label.SetValue(gauge.BeastGauge);
var color = nascentChaosDuration == 0 ? Config.BeastGauge.BeastGaugeColor : Config.BeastGauge.NascentChaosColor;
BarHud[] bars = BarUtilities.GetChunkedProgressBars(Config.BeastGauge, 2, gauge.BeastGauge, 100, 0, player, fillColor: color);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.BeastGauge.StrataLevel));
}
}
}
private void DrawInnerReleaseBar(Vector2 origin, IPlayerCharacter player)
{
var innerReleaseStatus = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1177 or 86);
float innerReleaseDuration = Math.Max(innerReleaseStatus?.RemainingTime ?? 0f, 0f);
int innerReleaseStacks = innerReleaseStatus?.Param ?? 0;
BarGlowConfig? primalRendGlow = null;
if (Config.InnerReleaseBar.PrimalRendReadyGlowConfig.Enabled)
{
bool isPrimalRendReady = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 2624)?.RemainingTime > 0;
if (isPrimalRendReady)
{
primalRendGlow = Config.InnerReleaseBar.PrimalRendReadyGlowConfig;
}
}
if (innerReleaseStacks == 0 && Config.InnerReleaseBar.ShowCooldown)
{
uint spellID = 7389;
float maxDuration = SpellHelper.Instance.GetRecastTime(spellID);
float cooldown = SpellHelper.Instance.GetSpellCooldown(spellID);
float currentCooldown = maxDuration - cooldown;
Config.InnerReleaseBar.Label.SetValue(maxDuration - currentCooldown);
if (currentCooldown == maxDuration)
{
if (!Config.InnerReleaseBar.HideWhenInactive)
{
BarHud[] bars = BarUtilities.GetChunkedProgressBars(
Config.InnerReleaseBar,
1,
1,
1,
0,
player,
fillColor: Config.InnerReleaseBar.CooldownFinishedColor,
glowConfig: primalRendGlow,
chunksToGlow: new[] { true }
);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.InnerReleaseBar.StrataLevel));
}
}
}
else
{
BarHud[] bars = BarUtilities.GetChunkedProgressBars(
Config.InnerReleaseBar,
1,
currentCooldown,
maxDuration,
0,
player,
fillColor: Config.InnerReleaseBar.CooldownInProgressColor,
glowConfig: primalRendGlow,
chunksToGlow: new[] { true }
);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.InnerReleaseBar.StrataLevel));
}
}
return;
}
if (!Config.InnerReleaseBar.HideWhenInactive || innerReleaseStacks > 0)
{
float innerReleaseMaxDuration = 15f;
var chunks = new Tuple<PluginConfigColor, float, LabelConfig?>[3];
for (int i = 0; i < 3; i++)
{
chunks[i] = new(Config.InnerReleaseBar.FillColor, i < innerReleaseStacks ? 1 : 0, i == 1 ? Config.InnerReleaseBar.Label : null);
}
innerReleaseDuration = !Config.InnerReleaseBar.ShowBuffTimerOnActiveChunk
? innerReleaseMaxDuration
: Math.Min(innerReleaseDuration, innerReleaseMaxDuration);
Config.InnerReleaseBar.Label.SetValue(innerReleaseDuration);
BarHud[] bars = BarUtilities.GetChunkedBars(Config.InnerReleaseBar, chunks, player, primalRendGlow, new[] { true, true, true });
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.InnerReleaseBar.StrataLevel));
}
}
}
}
[Section("Job Specific Bars")]
[SubSection("Tank", 0)]
[SubSection("Warrior", 1)]
public class WarriorConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.WAR;
public new static WarriorConfig DefaultConfig()
{
var config = new WarriorConfig();
config.BeastGauge.UsePartialFillColor = true;
config.InnerReleaseBar.LabelMode = LabelMode.ActiveChunk;
config.InnerReleaseBar.PrimalRendReadyGlowConfig.Color = new PluginConfigColor(new Vector4(246f / 255f, 30f / 255f, 136f / 255f, 100f / 100f));
return config;
}
[NestedConfig("Surging Tempest Bar", 30)]
public ProgressBarConfig SurgingTempestBar = new ProgressBarConfig(
new(0, -32),
new(254, 20),
new PluginConfigColor(new Vector4(255f / 255f, 136f / 255f, 146f / 255f, 100f / 100f))
);
[NestedConfig("Beast Gauge", 35)]
public WarriorBeastGaugeConfig BeastGauge = new WarriorBeastGaugeConfig(
new(0, -10),
new(254, 20),
new PluginConfigColor(new Vector4(0, 0, 0, 0))
);
[NestedConfig("Inner Release Bar", 40)]
public WarriorInnerReleaseBarConfig InnerReleaseBar = new WarriorInnerReleaseBarConfig(
new(0, -54),
new(254, 20),
new PluginConfigColor(new Vector4(255f / 255f, 136f / 255f, 146f / 255f, 100f / 100f))
);
}
[DisableParentSettings("FillColor")]
[Exportable(false)]
public class WarriorBeastGaugeConfig : ChunkedProgressBarConfig
{
[ColorEdit4("Beast Gauge Color", spacing = true)]
[Order(65)]
public PluginConfigColor BeastGaugeColor = new(new Vector4(201f / 255f, 13f / 255f, 13f / 255f, 100f / 100f));
[ColorEdit4("Nascent Chaos Color")]
[Order(70)]
public PluginConfigColor NascentChaosColor = new(new Vector4(240f / 255f, 176f / 255f, 0f / 255f, 100f / 100f));
public WarriorBeastGaugeConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor)
{
}
}
[Exportable(false)]
public class WarriorInnerReleaseBarConfig : ChunkedProgressBarConfig
{
[Checkbox("Show Buff Timer On Active Chunk", spacing = true)]
[Order(80)]
public bool ShowBuffTimerOnActiveChunk;
[Checkbox("Show Inner Release Cooldown", spacing = true)]
[Order(85)]
public bool ShowCooldown;
[ColorEdit4("Inner Release On Cooldown Color")]
[Order(90)]
public PluginConfigColor CooldownInProgressColor = new(new Vector4(240f / 255f, 176f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Inner Release Ready Color")]
[Order(95)]
public PluginConfigColor CooldownFinishedColor = new(new Vector4(38f / 255f, 192f / 255f, 94f / 255f, 100f / 100f));
[NestedConfig("Glow Color (when Primal Rend is ready)", 100, separator = false, spacing = true)]
public BarGlowConfig PrimalRendReadyGlowConfig = new();
public WarriorInnerReleaseBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor) : base(position, size, fillColor)
{
}
}
}
+298
View File
@@ -0,0 +1,298 @@
using Dalamud.Game.ClientState.JobGauge.Types;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.GeneralElements;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Jobs
{
public class WhiteMageHud : JobHud
{
private new WhiteMageConfig Config => (WhiteMageConfig)_config;
private static readonly List<uint> DiaIDs = new() { 143, 144, 1871 };
private static readonly List<float> DiaDurations = new() { 30, 30, 30 };
public WhiteMageHud(WhiteMageConfig config, string? displayName = null) : base(config, displayName)
{
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
List<Vector2> positions = new();
List<Vector2> sizes = new();
if (Config.LilyBar.Enabled)
{
positions.Add(Config.Position + Config.LilyBar.Position);
sizes.Add(Config.LilyBar.Size);
}
if (Config.DiaBar.Enabled)
{
positions.Add(Config.Position + Config.DiaBar.Position);
sizes.Add(Config.DiaBar.Size);
}
if (Config.AsylumBar.Enabled)
{
positions.Add(Config.Position + Config.AsylumBar.Position);
sizes.Add(Config.AsylumBar.Size);
}
if (Config.PresenceOfMindBar.Enabled)
{
positions.Add(Config.Position + Config.PresenceOfMindBar.Position);
sizes.Add(Config.PresenceOfMindBar.Size);
}
if (Config.PlenaryBar.Enabled)
{
positions.Add(Config.Position + Config.PlenaryBar.Position);
sizes.Add(Config.PlenaryBar.Size);
}
if (Config.TemperanceBar.Enabled)
{
positions.Add(Config.Position + Config.TemperanceBar.Position);
sizes.Add(Config.TemperanceBar.Size);
}
return (positions, sizes);
}
public override void DrawJobHud(Vector2 origin, IPlayerCharacter player)
{
Vector2 pos = origin + Config.Position;
if (Config.LilyBar.Enabled)
{
DrawLilyBar(pos, player);
}
if (Config.DiaBar.Enabled)
{
DrawDiaBar(pos, player);
}
if (Config.AsylumBar.Enabled)
{
DrawAsylumBar(pos, player);
}
if (Config.PresenceOfMindBar.Enabled)
{
DrawPresenceOfMindBar(pos, player);
}
if (Config.PlenaryBar.Enabled)
{
DrawPlenaryBar(pos, player);
}
if (Config.TemperanceBar.Enabled)
{
DrawTemperanceBar(pos, player);
}
}
private void DrawDiaBar(Vector2 origin, IPlayerCharacter player)
{
var target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
BarHud? bar = BarUtilities.GetDoTBar(Config.DiaBar, player, target, DiaIDs, DiaDurations);
if (bar != null)
{
AddDrawActions(bar.GetDrawActions(origin, Config.DiaBar.StrataLevel));
}
}
private void DrawLilyBar(Vector2 origin, IPlayerCharacter player)
{
WHMGauge gauge = Plugin.JobGauges.Get<WHMGauge>();
const float lilyCooldown = 20000f;
float GetScale(int num, float timer) => num + (timer / lilyCooldown);
float lilyScale = GetScale(gauge.Lily, gauge.LilyTimer);
if (!Config.LilyBar.HideWhenInactive || lilyScale > 0)
{
BarHud[] bars = BarUtilities.GetChunkedBars(Config.LilyBar, 3, lilyScale, 3, 0, player, partialFillColor: Config.LilyBar.PartialFillColor);
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.LilyBar.StrataLevel));
}
}
if (!Config.BloodLilyBar.HideWhenInactive && Config.BloodLilyBar.Enabled || gauge.BloodLily > 0)
{
BarGlowConfig? glow = gauge.BloodLily == 3 ? Config.BloodLilyBar.GlowConfig : null;
BarHud[] bars = BarUtilities.GetChunkedBars(Config.BloodLilyBar, 3, gauge.BloodLily, 3, 0, player, glowConfig: glow, chunksToGlow: new[] { true, true, true });
foreach (BarHud bar in bars)
{
AddDrawActions(bar.GetDrawActions(origin, Config.BloodLilyBar.StrataLevel));
}
}
}
private void DrawAsylumBar(Vector2 origin, IPlayerCharacter player)
{
float asylymDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 739 or 1911 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.AsylumBar.HideWhenInactive || asylymDuration > 0)
{
Config.AsylumBar.Label.SetValue(asylymDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.AsylumBar, asylymDuration, 24f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.AsylumBar.StrataLevel));
}
}
private void DrawPresenceOfMindBar(Vector2 origin, IPlayerCharacter player)
{
float presenceOfMindDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 157 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.PresenceOfMindBar.HideWhenInactive || presenceOfMindDuration > 0)
{
Config.PresenceOfMindBar.Label.SetValue(presenceOfMindDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.PresenceOfMindBar, presenceOfMindDuration, 15f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.PresenceOfMindBar.StrataLevel));
}
}
private void DrawPlenaryBar(Vector2 origin, IPlayerCharacter player)
{
float plenaryDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1219 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.PlenaryBar.HideWhenInactive || plenaryDuration > 0)
{
Config.PlenaryBar.Label.SetValue(plenaryDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.PlenaryBar, plenaryDuration, 10f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.PlenaryBar.StrataLevel));
}
}
private void DrawTemperanceBar(Vector2 origin, IPlayerCharacter player)
{
float temperanceDuration = Utils.StatusListForBattleChara(player).FirstOrDefault(o => o.StatusId is 1872 && o.SourceId == player.GameObjectId)?.RemainingTime ?? 0f;
if (!Config.TemperanceBar.HideWhenInactive || temperanceDuration > 0)
{
Config.TemperanceBar.Label.SetValue(temperanceDuration);
BarHud bar = BarUtilities.GetProgressBar(Config.TemperanceBar, temperanceDuration, 20f, 0f, player);
AddDrawActions(bar.GetDrawActions(origin, Config.TemperanceBar.StrataLevel));
}
}
}
[Section("Job Specific Bars")]
[SubSection("Healer", 0)]
[SubSection("White Mage", 1)]
public class WhiteMageConfig : JobConfig
{
[JsonIgnore] public override uint JobId => JobIDs.WHM;
public new static WhiteMageConfig DefaultConfig()
{
var config = new WhiteMageConfig();
config.UseDefaultPrimaryResourceBar = true;
config.AsylumBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.PresenceOfMindBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.PlenaryBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
config.TemperanceBar.Label.FontID = FontsConfig.DefaultMediumFontKey;
return config;
}
[NestedConfig("Lily Bar", 30)]
public LilyBarConfig LilyBar = new LilyBarConfig(
new(-64, -32),
new(126, 20),
new PluginConfigColor(new(0f / 255f, 64f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Blood Lily Bar", 35)]
public BloodLilyBarConfig BloodLilyBar = new BloodLilyBarConfig(
new(64, -32),
new(126, 20),
new PluginConfigColor(new(199f / 255f, 40f / 255f, 9f / 255f, 100f / 100f))
);
[NestedConfig("Dia Bar", 40)]
public ProgressBarConfig DiaBar = new ProgressBarConfig(
new(0, -10),
new(254, 20),
new PluginConfigColor(new(0f / 255f, 64f / 255f, 255f / 255f, 100f / 100f))
);
[NestedConfig("Asylum Bar", 45)]
public ProgressBarConfig AsylumBar = new ProgressBarConfig(
new(-96, -52),
new(62, 15),
new PluginConfigColor(new(241f / 255f, 217f / 255f, 125f / 255f, 100f / 100f))
);
[NestedConfig("Presence of Mind Bar", 50)]
public ProgressBarConfig PresenceOfMindBar = new ProgressBarConfig(
new(-32, -52),
new(62, 15),
new PluginConfigColor(new(213f / 255f, 124f / 255f, 97f / 255f, 100f / 100f))
);
[NestedConfig("Plenary Bar", 55)]
public ProgressBarConfig PlenaryBar = new ProgressBarConfig(
new(32, -52),
new(62, 15),
new PluginConfigColor(new(26f / 255f, 167f / 255f, 109f / 255f, 100f / 100f))
);
[NestedConfig("Temperance Bar", 60)]
public ProgressBarConfig TemperanceBar = new ProgressBarConfig(
new(96, -52),
new(62, 15),
new PluginConfigColor(new(100f / 255f, 207f / 255f, 211f / 255f, 100f / 100f))
);
}
[Exportable(false)]
public class LilyBarConfig : ChunkedBarConfig
{
[Checkbox("Use Partial Fill Color", spacing = true)]
[Order(65)]
public bool UsePartialFillColor = false;
[ColorEdit4("Partial Fill Color")]
[Order(66, collapseWith = nameof(UsePartialFillColor))]
public PluginConfigColor PartialFillColor;
public LilyBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
PartialFillColor = new PluginConfigColor(new(0f / 255f, 64f / 255f, 255f / 255f, 50f / 100f));
}
}
[Exportable(false)]
public class BloodLilyBarConfig : ChunkedBarConfig
{
[NestedConfig("Glow Color (when Misery ready)", 60, separator = false, spacing = true)]
public BarGlowConfig GlowConfig = new();
public BloodLilyBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor)
: base(position, size, fillColor)
{
GlowConfig.Color = new PluginConfigColor(new(247f / 255f, 177f / 255f, 67f / 255f, 100f / 100f));
}
}
}
+639
View File
@@ -0,0 +1,639 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
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.Collections.Generic;
using System.Numerics;
using Action = System.Action;
using Character = Dalamud.Game.ClientState.Objects.Types.ICharacter;
using StructsCharacter = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
namespace HSUI.Interface.Nameplates
{
public class Nameplate
{
protected NameplateConfig _config;
public bool Enabled => _config.Enabled;
protected LabelHud _nameLabelHud;
protected LabelHud _titleLabelHud;
public Nameplate(NameplateConfig config)
{
_config = config;
_nameLabelHud = new LabelHud(config.NameLabelConfig);
_titleLabelHud = new LabelHud(config.TitleLabelConfig);
}
protected bool IsVisible(IGameObject? actor)
{
if (!_config.Enabled ||
actor == null ||
!_config.VisibilityConfig.IsElementVisible(null) ||
(_config.OnlyShowWhenTargeted && actor.Address != Plugin.TargetManager.Target?.Address))
{
return false;
}
return true;
}
public virtual List<(StrataLevel, Action)> GetElementsDrawActions(NameplateData data)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
if (!IsVisible(data.GameObject)) { return drawActions; }
drawActions.AddRange(GetMainLabelDrawActions(data));
return drawActions;
}
protected List<(StrataLevel, Action)> GetMainLabelDrawActions(NameplateData data, NameplateAnchor? barAnchor = null)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
Vector2 origin = _config.Position + (barAnchor?.Position ?? data.ScreenPosition);
Vector2 swapOffset = Vector2.Zero;
if (_config.SwapLabelsWhenNeeded && (data.IsTitlePrefix || data.Title.Length == 0))
{
swapOffset = _config.TitleLabelConfig.Position - _config.NameLabelConfig.Position;
}
// name
float nameAlpha = _config.RangeConfig.AlphaForDistance(data.Distance, _config.NameLabelConfig.Color.Vector.W);
var (nameText, namePos, nameSize, nameColor) = _nameLabelHud.PreCalculate(origin + swapOffset, barAnchor?.Size, data.GameObject, data.Name, isPlayerName: data.Kind == ObjectKind.Player);
drawActions.Add((_config.NameLabelConfig.StrataLevel, () =>
{
_nameLabelHud.DrawLabel(nameText, namePos, nameSize, nameColor, nameAlpha);
}
));
// title
float titleAlpha = _config.RangeConfig.AlphaForDistance(data.Distance, _config.TitleLabelConfig.Color.Vector.W);
var (titleText, titlePos, titleSize, titleColor) = _titleLabelHud.PreCalculate(origin - swapOffset, barAnchor?.Size, data.GameObject, title: data.Title);
if (data.Title.Length > 0)
{
drawActions.Add((_config.TitleLabelConfig.StrataLevel, () =>
{
_titleLabelHud.DrawLabel(titleText, titlePos, titleSize, titleColor, titleAlpha);
}
));
}
return drawActions;
}
}
public class NameplateWithBar : Nameplate
{
protected NameplateBarConfig BarConfig => ((NameplateWithBarConfig)_config).GetBarConfig();
private LabelHud _leftLabelHud;
private LabelHud _rightLabelHud;
private LabelHud _optionalLabelHud;
public NameplateWithBar(NameplateConfig config) : base(config)
{
_leftLabelHud = new LabelHud(BarConfig.LeftLabelConfig);
_rightLabelHud = new LabelHud(BarConfig.RightLabelConfig);
_optionalLabelHud = new LabelHud(BarConfig.OptionalLabelConfig);
}
public (bool, bool) GetMouseoverState(NameplateData data)
{
if (data.GameObject is not ICharacter character) { return (false, false); }
if (!BarConfig.IsVisible(character.CurrentHp, character.MaxHp) || BarConfig.DisableInteraction)
{
return (false, false);
}
bool targeted = Plugin.TargetManager.Target?.Address == character.Address;
Vector2 barSize = BarConfig.GetSize(targeted);
Vector2 origin = _config.Position + data.ScreenPosition;
Vector2 barPos = Utils.GetAnchoredPosition(origin, barSize, BarConfig.Anchor) + BarConfig.Position;
var (areaStart, areaEnd) = BarConfig.MouseoverAreaConfig.GetArea(barPos, barSize);
bool isHovering = ImGui.IsMouseHoveringRect(areaStart, areaEnd);
bool ignoreMouseover = BarConfig.MouseoverAreaConfig.Enabled && BarConfig.MouseoverAreaConfig.Ignore;
return (isHovering, ignoreMouseover);
}
public unsafe List<(StrataLevel, Action)> GetBarDrawActions(NameplateData data)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
if (!IsVisible(data.GameObject)) { return drawActions; }
if (data.GameObject is not Character character) { return drawActions; }
uint currentHp = character.CurrentHp;
uint maxHp = character.MaxHp;
if (!BarConfig.IsVisible(currentHp, maxHp)) { return drawActions; }
// colors
PluginConfigColor fillColor = GetFillColor(character, currentHp, maxHp);
fillColor = fillColor.WithAlpha(_config.RangeConfig.AlphaForDistance(data.Distance, fillColor.Vector.W));
PluginConfigColor bgColor = GetBackgroundColor(character);
bgColor = bgColor.WithAlpha(_config.RangeConfig.AlphaForDistance(data.Distance, bgColor.Vector.W));
bool targeted = character.Address == Plugin.TargetManager.Target?.Address;
PluginConfigColor borderColor = targeted ? BarConfig.TargetedBorderColor : BarConfig.BorderColor;
borderColor = borderColor.WithAlpha(
_config.RangeConfig.AlphaForDistance(data.Distance, BarConfig.BorderColor.Vector.W)
);
// bar
Vector2 barSize = BarConfig.GetSize(targeted);
Rect background = new Rect(BarConfig.Position, barSize, bgColor);
Rect healthFill = BarUtilities.GetFillRect(BarConfig.Position, barSize, BarConfig.FillDirection, fillColor, currentHp, maxHp);
BarHud bar = new BarHud(
BarConfig.ID,
BarConfig.DrawBorder,
borderColor,
targeted ? BarConfig.TargetedBorderThickness : BarConfig.BorderThickness,
BarConfig.Anchor,
character,
current: currentHp,
max: maxHp,
shadowConfig: BarConfig.ShadowConfig,
barTextureName: BarConfig.BarTextureName,
barTextureDrawMode: BarConfig.BarTextureDrawMode
);
bar.NeedsInputs = true;
bar.SetBackground(background);
bar.AddForegrounds(healthFill);
// shield
PluginConfigColor shieldColor = BarConfig.ShieldConfig.Color.WithAlpha(
_config.RangeConfig.AlphaForDistance(data.Distance, BarConfig.ShieldConfig.Color.Vector.W)
);
BarUtilities.AddShield(bar, BarConfig, BarConfig.ShieldConfig, character, healthFill.Size, shieldColor);
// draw bar
Vector2 origin = _config.Position + data.ScreenPosition;
drawActions.AddRange(bar.GetDrawActions(origin, _config.StrataLevel));
// mouseover area
BarHud? mouseoverAreaBar = BarConfig.MouseoverAreaConfig.GetBar(
BarConfig.Position,
barSize,
BarConfig.ID + "_mouseoverArea",
BarConfig.Anchor
);
if (mouseoverAreaBar != null)
{
drawActions.AddRange(mouseoverAreaBar.GetDrawActions(origin, StrataLevel.HIGHEST));
}
// labels
Vector2 barPos = Utils.GetAnchoredPosition(origin, barSize, BarConfig.Anchor) + BarConfig.Position;
LabelHud[] labels = GetLabels(maxHp);
foreach (LabelHud label in labels)
{
LabelConfig labelConfig = (LabelConfig)label.GetConfig();
float alpha = _config.RangeConfig.AlphaForDistance(data.Distance, labelConfig.Color.Vector.W);
var (labelText, labelPos, labelSize, labelColor) = label.PreCalculate(barPos, barSize, data.GameObject, data.Name, currentHp, maxHp, data.Kind == ObjectKind.Player);
drawActions.Add((labelConfig.StrataLevel, () =>
{
label.DrawLabel(labelText, labelPos, labelSize, labelColor, alpha);
}
));
}
return drawActions;
}
public override List<(StrataLevel, Action)> GetElementsDrawActions(NameplateData data)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
if (!IsVisible(data.GameObject)) { return drawActions; }
NameplateAnchor? barAnchor = GetBarAnchor(data);
drawActions.AddRange(GetMainLabelDrawActions(data, barAnchor));
return drawActions;
}
protected virtual NameplateAnchor? GetBarAnchor(NameplateData data)
{
if (data.GameObject is Character chara &&
BarConfig.IsVisible(chara.CurrentHp, chara.MaxHp))
{
bool targeted = Plugin.TargetManager.Target?.Address == data.GameObject.Address;
Vector2 size = BarConfig.GetSize(targeted);
Vector2 pos = Utils.GetAnchoredPosition(data.ScreenPosition + BarConfig.Position, size, BarConfig.Anchor);
return new NameplateAnchor(pos, size);
}
return null;
}
private LabelHud[] GetLabels(uint maxHp)
{
List<LabelHud> labels = new List<LabelHud>();
if (BarConfig.HideHealthIfPossible && maxHp <= 0)
{
if (!Utils.IsHealthLabel(BarConfig.LeftLabelConfig))
{
labels.Add(_leftLabelHud);
}
if (!Utils.IsHealthLabel(BarConfig.RightLabelConfig))
{
labels.Add(_rightLabelHud);
}
if (!Utils.IsHealthLabel(BarConfig.OptionalLabelConfig))
{
labels.Add(_optionalLabelHud);
}
}
else
{
labels.Add(_leftLabelHud);
labels.Add(_rightLabelHud);
labels.Add(_optionalLabelHud);
}
return labels.ToArray();
}
protected virtual PluginConfigColor GetFillColor(Character character, uint currentHp, uint maxHp)
{
return ColorUtils.ColorForCharacter(
character,
currentHp,
maxHp,
false,
false,
BarConfig.ColorByHealth
) ?? BarConfig.FillColor;
}
protected virtual PluginConfigColor GetBackgroundColor(Character character)
{
return BarConfig.BackgroundColor;
}
}
public class NameplateWithBarAndExtras : NameplateWithBar
{
public NameplateWithBarAndExtras(NameplateConfig config) : base(config)
{
}
public override List<(StrataLevel, Action)> GetElementsDrawActions(NameplateData data)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
if (!IsVisible(data.GameObject)) { return drawActions; }
NameplateAnchor? barAnchor = GetBarAnchor(data);
Vector2 origin = _config.Position + (barAnchor?.Position ?? data.ScreenPosition);
Vector2 swapOffset = Vector2.Zero;
if (_config.SwapLabelsWhenNeeded && (data.IsTitlePrefix || data.Title.Length == 0))
{
swapOffset = _config.TitleLabelConfig.Position - _config.NameLabelConfig.Position;
}
// name
float nameAlpha = _config.RangeConfig.AlphaForDistance(data.Distance, _config.NameLabelConfig.Color.Vector.W);
var (nameText, namePos, nameSize, nameColor) = _nameLabelHud.PreCalculate(origin + swapOffset, barAnchor?.Size, data.GameObject, data.Name, isPlayerName: data.Kind == ObjectKind.Player);
drawActions.Add((_config.NameLabelConfig.StrataLevel, () =>
{
_nameLabelHud.DrawLabel(nameText, namePos, nameSize, nameColor, nameAlpha);
}
));
// title
float titleAlpha = _config.RangeConfig.AlphaForDistance(data.Distance, _config.TitleLabelConfig.Color.Vector.W);
var (titleText, titlePos, titleSize, titleColor) = _titleLabelHud.PreCalculate(origin - swapOffset, barAnchor?.Size, data.GameObject, title: data.Title);
if (data.Title.Length > 0)
{
drawActions.Add((_config.TitleLabelConfig.StrataLevel, () =>
{
_titleLabelHud.DrawLabel(titleText, titlePos, titleSize, titleColor, titleAlpha);
}
));
}
// extras anchor
NameplateExtrasAnchors extrasAnchors = new NameplateExtrasAnchors(
barAnchor,
_config.NameLabelConfig.Enabled ? new NameplateAnchor(namePos, nameSize) : null,
_config.TitleLabelConfig.Enabled && data.Title.Length > 0 ? new NameplateAnchor(titlePos, titleSize) : null
);
drawActions.AddRange(GetExtrasDrawActions(data, extrasAnchors));
return drawActions;
}
protected virtual List<(StrataLevel, Action)> GetExtrasDrawActions(NameplateData data, NameplateExtrasAnchors anchors)
{
// override
return new List<(StrataLevel, Action)>();
}
}
public class NameplateWithPlayerBar : NameplateWithBarAndExtras
{
private NameplateWithPlayerBarConfig Config => (NameplateWithPlayerBarConfig)_config;
public NameplateWithPlayerBar(NameplateWithPlayerBarConfig config) : base(config)
{
}
protected override List<(StrataLevel, Action)> GetExtrasDrawActions(NameplateData data, NameplateExtrasAnchors anchors)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
if (data.GameObject is not IPlayerCharacter character) { return drawActions; }
float alpha = _config.RangeConfig.AlphaForDistance(data.Distance);
// role/job icon
if (Config.RoleIconConfig.Enabled)
{
NameplateAnchor? anchor = anchors.GetAnchor(Config.RoleIconConfig.NameplateLabelAnchor, Config.RoleIconConfig.PrioritizeHealthBarAnchor);
anchor = anchor ?? new NameplateAnchor(data.ScreenPosition, Vector2.Zero);
uint jobId = character.ClassJob.RowId;
uint iconId = Config.RoleIconConfig.UseRoleIcons ?
JobsHelper.RoleIconIDForJob(jobId, Config.RoleIconConfig.UseSpecificDPSRoleIcons) :
JobsHelper.IconIDForJob(jobId, (uint)Config.RoleIconConfig.Style);
if (iconId > 0)
{
var pos = Utils.GetAnchoredPosition(anchor.Value.Position, -anchor.Value.Size, Config.RoleIconConfig.FrameAnchor);
var iconPos = Utils.GetAnchoredPosition(pos + Config.RoleIconConfig.Position, Config.RoleIconConfig.Size, Config.RoleIconConfig.Anchor);
drawActions.Add((Config.RoleIconConfig.StrataLevel, () =>
{
DrawHelper.DrawInWindow(_config.ID + "_jobIcon", iconPos, Config.RoleIconConfig.Size, false, (drawList) =>
{
DrawHelper.DrawIcon(iconId, iconPos, Config.RoleIconConfig.Size, false, alpha, drawList);
});
}
));
}
}
// state icon
if (Config.StateIconConfig.Enabled &&
data.NamePlateIconId > 0 &&
Config.StateIconConfig.ShouldDrawIcon(data.NamePlateIconId))
{
NameplateAnchor? anchor = anchors.GetAnchor(Config.StateIconConfig.NameplateLabelAnchor, Config.StateIconConfig.PrioritizeHealthBarAnchor);
anchor = anchor ?? new NameplateAnchor(data.ScreenPosition, Vector2.Zero);
var pos = Utils.GetAnchoredPosition(anchor.Value.Position, -anchor.Value.Size, Config.StateIconConfig.FrameAnchor);
var iconPos = Utils.GetAnchoredPosition(pos + Config.StateIconConfig.Position, Config.StateIconConfig.Size, Config.StateIconConfig.Anchor);
drawActions.Add((Config.StateIconConfig.StrataLevel, () =>
{
DrawHelper.DrawInWindow(_config.ID + "_stateIcon", iconPos, Config.StateIconConfig.Size, false, (drawList) =>
{
DrawHelper.DrawIcon((uint)data.NamePlateIconId, iconPos, Config.StateIconConfig.Size, false, alpha, drawList);
});
}
));
}
return drawActions;
}
protected override PluginConfigColor GetFillColor(Character character, uint currentHp, uint maxHp)
{
NameplatePlayerBarConfig config = (NameplatePlayerBarConfig)BarConfig;
return ColorUtils.ColorForCharacter(
character,
currentHp,
maxHp,
config.UseJobColor,
config.UseRoleColor,
config.ColorByHealth
) ?? config.FillColor;
}
protected override PluginConfigColor GetBackgroundColor(Character character)
{
NameplatePlayerBarConfig config = (NameplatePlayerBarConfig)BarConfig;
if (config.UseJobColorAsBackgroundColor)
{
return GlobalColors.Instance.SafeColorForJobId(character.ClassJob.RowId);
}
else if (config.UseRoleColorAsBackgroundColor)
{
return GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId);
}
return config.BackgroundColor;
}
}
public class NameplateWithEnemyBar : NameplateWithBarAndExtras
{
private NameplateWithEnemyBarConfig Config => (NameplateWithEnemyBarConfig)_config;
private LabelHud _orderLabelHud;
private StatusEffectsListHud _debuffsHud;
private NameplateCastbarHud _castbarHud;
public NameplateWithEnemyBar(NameplateWithEnemyBarConfig config) : base(config)
{
_orderLabelHud = new LabelHud(config.BarConfig.OrderLabelConfig);
_debuffsHud = new StatusEffectsListHud(config.DebuffsConfig);
_castbarHud = new NameplateCastbarHud(config.CastbarConfig);
}
public void StopPreview()
{
_debuffsHud.StopPreview();
_castbarHud.StopPreview();
}
protected override List<(StrataLevel, Action)> GetExtrasDrawActions(NameplateData data, NameplateExtrasAnchors anchors)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
if (data.GameObject is not Character character) { return drawActions; }
NameplateEnemyBarConfig barConfig = Config.BarConfig;
NameplateAnchor? anchor = barConfig.IsVisible(character.CurrentHp, character.MaxHp) ? anchors.BarAnchor : anchors.NameLabelAnchor;
anchor = anchor ?? new NameplateAnchor(_config.Position + data.ScreenPosition, Vector2.Zero);
// order label
float alpha = _config.RangeConfig.AlphaForDistance(data.Distance, barConfig.OrderLabelConfig.Color.Vector.W);
barConfig.OrderLabelConfig.SetText(data.Order);
var (labelText, labelPos, labelSize, labelColor) = _orderLabelHud.PreCalculate(anchor.Value.Position, anchor.Value.Size, data.GameObject);
drawActions.Add((barConfig.OrderLabelConfig.StrataLevel, () =>
{
_orderLabelHud.DrawLabel(labelText, labelPos, labelSize, labelColor, alpha);
}
));
// debuffs
Vector2 buffsPos = Utils.GetAnchoredPosition(anchor.Value.Position, -anchor.Value.Size, Config.DebuffsConfig.HealthBarAnchor);
drawActions.Add((Config.DebuffsConfig.StrataLevel, () =>
{
_debuffsHud.Actor = character;
_debuffsHud.PrepareForDraw(buffsPos);
_debuffsHud.Draw(buffsPos);
}
));
// castbar
Vector2 castbarPos = Utils.GetAnchoredPosition(anchor.Value.Position, -anchor.Value.Size, Config.CastbarConfig.HealthBarAnchor);
drawActions.Add((Config.CastbarConfig.StrataLevel, () =>
{
_castbarHud.ParentSize = anchor.Value.Size;
_castbarHud.Actor = character;
_castbarHud.PrepareForDraw(castbarPos);
_castbarHud.Draw(castbarPos);
}
));
// icon
if (Config.IconConfig.Enabled && data.NamePlateIconId > 0)
{
anchor = anchors.GetAnchor(Config.IconConfig.NameplateLabelAnchor, Config.IconConfig.PrioritizeHealthBarAnchor);
anchor = anchor ?? new NameplateAnchor(data.ScreenPosition, Vector2.Zero);
var pos = Utils.GetAnchoredPosition(_config.Position + anchor.Value.Position, -anchor.Value.Size, Config.IconConfig.FrameAnchor);
var iconPos = Utils.GetAnchoredPosition(pos + Config.IconConfig.Position, Config.IconConfig.Size, Config.IconConfig.Anchor);
drawActions.Add((Config.IconConfig.StrataLevel, () =>
{
DrawHelper.DrawInWindow(_config.ID + "_enemyIcon", iconPos, Config.IconConfig.Size, false, (drawList) =>
{
DrawHelper.DrawIcon((uint)data.NamePlateIconId, iconPos, Config.IconConfig.Size, false, alpha, drawList);
});
}
));
}
return drawActions;
}
protected override unsafe PluginConfigColor GetFillColor(Character character, uint currentHp, uint maxHp)
{
NameplateEnemyBarConfig config = (NameplateEnemyBarConfig)BarConfig;
bool targetingPlayer = character.TargetObjectId == Plugin.ObjectTable.LocalPlayer?.GameObjectId;
if (targetingPlayer && config.UseCustomColorWhenBeingTargeted)
{
return config.CustomColorWhenBeingTargeted;
}
if (config.UseStateColor)
{
StructsCharacter* chara = (StructsCharacter*)character.Address;
byte nameplateColorId = chara->GetNamePlateColorType();
switch (nameplateColorId) {
case 7: return (character.StatusFlags & StatusFlags.Hostile) != 0 ? config.UnengagedHostileColor : config.UnengagedColor;
case 9: return config.EngagedColor;
case 10: return config.ClaimedColor;
case 11: return config.UnclaimedColor;
default: break;
}
}
return base.GetFillColor(character, currentHp, maxHp);
}
}
#region utils
public struct NameplateAnchor
{
public Vector2 Position;
public Vector2 Size;
internal NameplateAnchor(Vector2 position, Vector2 size)
{
Position = position;
Size = size;
}
}
public struct NameplateExtrasAnchors
{
public NameplateAnchor? BarAnchor;
public NameplateAnchor? NameLabelAnchor;
public NameplateAnchor? TitleLabelAnchor;
public NameplateAnchor? HighestLabelAnchor;
public NameplateAnchor? LowestLabelAnchor;
private NameplateAnchor? DefaultLabelAnchor;
internal NameplateExtrasAnchors(NameplateAnchor? barAnchor, NameplateAnchor? nameLabelAnchor, NameplateAnchor? titleLabelAnchor)
{
BarAnchor = barAnchor;
NameLabelAnchor = nameLabelAnchor;
TitleLabelAnchor = titleLabelAnchor;
DefaultLabelAnchor = nameLabelAnchor;
float nameY = -1;
if (nameLabelAnchor.HasValue)
{
nameY = nameLabelAnchor.Value.Position.Y;
}
float titleY = -1;
if (titleLabelAnchor.HasValue)
{
titleY = titleLabelAnchor.Value.Position.Y;
}
if (nameY == -1)
{
DefaultLabelAnchor = titleLabelAnchor;
}
else if (nameY < titleY)
{
HighestLabelAnchor = nameLabelAnchor;
LowestLabelAnchor = titleLabelAnchor;
}
else if (nameY > titleY)
{
HighestLabelAnchor = titleLabelAnchor;
LowestLabelAnchor = nameLabelAnchor;
}
}
internal NameplateAnchor? GetAnchor(NameplateLabelAnchor label, bool prioritizeHealthBar)
{
if (prioritizeHealthBar && BarAnchor != null) { return BarAnchor; }
NameplateAnchor? labelAnchor = null;
switch (label)
{
case NameplateLabelAnchor.Name: labelAnchor = NameLabelAnchor; break;
case NameplateLabelAnchor.Title: labelAnchor = TitleLabelAnchor; break;
case NameplateLabelAnchor.Highest: labelAnchor = HighestLabelAnchor; break;
case NameplateLabelAnchor.Lowest: labelAnchor = LowestLabelAnchor; break;
}
return labelAnchor ?? DefaultLabelAnchor;
}
#endregion
}
}
+788
View File
@@ -0,0 +1,788 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.StatusEffects;
using System;
using System.Numerics;
namespace HSUI.Interface.GeneralElements
{
public enum NameplatesOcclusionMode
{
None = 0,
Simple = 1,
Full
};
public enum NameplatesOcclusionType
{
Walls = 0,
WallsAndObjects = 1
};
[DisableParentSettings("Strata", "Position")]
[Section("Nameplates")]
[SubSection("General", 0)]
public class NameplatesGeneralConfig : MovablePluginConfigObject
{
public new static NameplatesGeneralConfig DefaultConfig() => new NameplatesGeneralConfig();
[Combo("Occlusion Mode", new string[] { "Disabled", "Simple", "Full" }, help = "This controls wheter you'll see nameplates through walls and objects.\n\nDisabled: Nameplates will always be seen for units in range.\nSimple: Uses simple calculations to check if a nameplate is being covered by walls or objects. Use this for better performance.\nFull: Uses more complex calculations to check if a nameplate is being covered by walls or objects. Use this for better results.")]
[Order(10)]
public NameplatesOcclusionMode OcclusionMode = NameplatesOcclusionMode.Full;
[Combo("Occlusion Type", new string[] { "Walls", "Walls and Objects" }, help = "This controls which kind of objects will cover nameplates.\n\n\nWalls: Default setting. Only walls will cover nameplates.\n\nWalls and Objects: Some objects like columns and trees will also cover nameplates.\nThis Occlusion Type can yield some unexpected results like nameplates for NPCs behind counters not being visible.")]
[Order(11)]
public NameplatesOcclusionType OcclusionType = NameplatesOcclusionType.Walls;
[Checkbox("Try to keep nameplates on screen", spacing = true, help = "Disclaimer: HSUI relies heavily on the the game's default nameplates so this setting won't be a huge improvement.\nThis setting tries to prevent nameplates from being cutoff in the border of the screen, but it won't keep showing nameplates that the game wouldn't.")]
[Order(20)]
public bool ClampToScreen = true;
[Checkbox("Always show nameplate for target")]
[Order(21)]
public bool AlwaysShowTargetNameplate = true;
public int RaycastFlag() => OcclusionType == NameplatesOcclusionType.WallsAndObjects ? 0x2000 : 0x4000;
}
[DisableParentSettings("HideWhenInactive")]
[Section("Nameplates")]
[SubSection("Player", 0)]
public class PlayerNameplateConfig : NameplateWithPlayerBarConfig
{
public PlayerNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplatePlayerBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig, barConfig)
{
}
public new static PlayerNameplateConfig DefaultConfig()
{
return NameplatesHelper.GetNameplateWithBarConfig<PlayerNameplateConfig, NameplatePlayerBarConfig>(
0xFFD0E5E0,
0xFF30444A,
HUDConstants.DefaultPlayerNameplateBarSize
);
}
}
[DisableParentSettings("HideWhenInactive", "TitleLabelConfig", "SwapLabelsWhenNeeded")]
[Section("Nameplates")]
[SubSection("Enemies", 0)]
public class EnemyNameplateConfig : NameplateWithEnemyBarConfig
{
public EnemyNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplateEnemyBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig, barConfig)
{
}
public new static EnemyNameplateConfig DefaultConfig()
{
EnemyNameplateConfig config = NameplatesHelper.GetNameplateWithBarConfig<EnemyNameplateConfig, NameplateEnemyBarConfig>(
0xFF993535,
0xFF000000,
HUDConstants.DefaultEnemyNameplateBarSize
);
config.SwapLabelsWhenNeeded = false;
config.NameLabelConfig.Position = new Vector2(-8, 0);
config.NameLabelConfig.Text = "Lv[level] [name]";
config.NameLabelConfig.FrameAnchor = DrawAnchor.TopRight;
config.NameLabelConfig.TextAnchor = DrawAnchor.Right;
config.NameLabelConfig.Color = PluginConfigColor.FromHex(0xFFFFFFFF);
config.BarConfig.LeftLabelConfig.Enabled = true;
config.BarConfig.OnlyShowWhenNotFull = false;
// debuffs
LabelConfig durationConfig = new LabelConfig(new Vector2(0, -4), "", DrawAnchor.Bottom, DrawAnchor.Center);
durationConfig.FontID = FontsConfig.DefaultMediumFontKey;
LabelConfig stacksConfig = new LabelConfig(new Vector2(-3, 4), "", DrawAnchor.TopRight, DrawAnchor.Center);
durationConfig.FontID = FontsConfig.DefaultMediumFontKey;
stacksConfig.Color = new(Vector4.UnitW);
stacksConfig.OutlineColor = new(Vector4.One);
StatusEffectIconConfig iconConfig = new StatusEffectIconConfig(durationConfig, stacksConfig);
iconConfig.Size = new Vector2(30, 30);
iconConfig.DispellableBorderConfig.Enabled = false;
Vector2 pos = new Vector2(2, -20);
Vector2 size = new Vector2(230, 70);
EnemyNameplateStatusEffectsListConfig debuffs = new EnemyNameplateStatusEffectsListConfig(
DrawAnchor.TopLeft,
pos,
size,
false,
true,
false,
GrowthDirections.Right | GrowthDirections.Up,
iconConfig
);
debuffs.Limit = 7;
debuffs.ShowPermanentEffects = true;
debuffs.IconConfig.DispellableBorderConfig.Enabled = false;
debuffs.IconPadding = new Vector2(1, 6);
debuffs.ShowOnlyMine = true;
debuffs.ShowTooltips = false;
debuffs.DisableInteraction = true;
config.DebuffsConfig = debuffs;
// castbar
Vector2 castbarSize = new Vector2(config.BarConfig.Size.X, 10);
LabelConfig castNameConfig = new LabelConfig(new Vector2(0, -1), "", DrawAnchor.Center, DrawAnchor.Center);
castNameConfig.FontID = FontsConfig.DefaultSmallFontKey;
NumericLabelConfig castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
castTimeConfig.Enabled = false;
castTimeConfig.FontID = FontsConfig.DefaultSmallFontKey;
castTimeConfig.NumberFormat = 1;
NameplateCastbarConfig castbarConfig = new NameplateCastbarConfig(Vector2.Zero, castbarSize, castNameConfig, castTimeConfig);
castbarConfig.HealthBarAnchor = DrawAnchor.BottomLeft;
castbarConfig.Anchor = DrawAnchor.TopLeft;
castbarConfig.ShowIcon = false;
config.CastbarConfig = castbarConfig;
return config;
}
}
[DisableParentSettings("HideWhenInactive")]
[Section("Nameplates")]
[SubSection("Party Members", 0)]
public class PartyMembersNameplateConfig : NameplateWithPlayerBarConfig
{
public PartyMembersNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplatePlayerBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig, barConfig)
{
}
public new static PartyMembersNameplateConfig DefaultConfig()
{
PartyMembersNameplateConfig config = NameplatesHelper.GetNameplateWithBarConfig<PartyMembersNameplateConfig, NameplatePlayerBarConfig>(
0xFFD0E5E0,
0xFF000000,
HUDConstants.DefaultPlayerNameplateBarSize
);
config.BarConfig.UseRoleColor = true;
config.NameLabelConfig.UseRoleColor = true;
config.TitleLabelConfig.UseRoleColor = true;
return config;
}
}
[DisableParentSettings("HideWhenInactive")]
[Section("Nameplates")]
[SubSection("Alliance Members", 0)]
public class AllianceMembersNameplateConfig : NameplateWithPlayerBarConfig
{
public AllianceMembersNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplatePlayerBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig, barConfig)
{
}
public new static AllianceMembersNameplateConfig DefaultConfig()
{
return NameplatesHelper.GetNameplateWithBarConfig<AllianceMembersNameplateConfig, NameplatePlayerBarConfig>(
0xFF99BE46,
0xFF3D4C1C,
HUDConstants.DefaultPlayerNameplateBarSize
);
}
}
[DisableParentSettings("HideWhenInactive")]
[Section("Nameplates")]
[SubSection("Friends", 0)]
public class FriendPlayerNameplateConfig : NameplateWithPlayerBarConfig
{
public FriendPlayerNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplatePlayerBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig, barConfig)
{
}
public new static FriendPlayerNameplateConfig DefaultConfig()
{
return NameplatesHelper.GetNameplateWithBarConfig<FriendPlayerNameplateConfig, NameplatePlayerBarConfig>(
0xFFEB6211,
0xFF4A2008,
HUDConstants.DefaultPlayerNameplateBarSize
);
}
}
[DisableParentSettings("HideWhenInactive")]
[Section("Nameplates")]
[SubSection("Other Players", 0)]
public class OtherPlayerNameplateConfig : NameplateWithPlayerBarConfig
{
public OtherPlayerNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplatePlayerBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig, barConfig)
{
}
public new static OtherPlayerNameplateConfig DefaultConfig()
{
return NameplatesHelper.GetNameplateWithBarConfig<OtherPlayerNameplateConfig, NameplatePlayerBarConfig>(
0xFF91BBD8,
0xFF33434E,
HUDConstants.DefaultPlayerNameplateBarSize
);
}
}
[DisableParentSettings("HideWhenInactive")]
[Section("Nameplates")]
[SubSection("Pets", 0)]
public class PetNameplateConfig : NameplateWithNPCBarConfig
{
public PetNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplateBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig, barConfig)
{
}
public new static PetNameplateConfig DefaultConfig()
{
PetNameplateConfig config = NameplatesHelper.GetNameplateWithBarConfig<PetNameplateConfig, NameplateBarConfig>(
0xFFD1E5C8,
0xFF2A2F28,
HUDConstants.DefaultPlayerNameplateBarSize
);
config.OnlyShowWhenTargeted = true;
config.SwapLabelsWhenNeeded = false;
config.NameLabelConfig.Text = "Lv[level] [name]";
config.NameLabelConfig.FontID = FontsConfig.DefaultSmallFontKey;
config.TitleLabelConfig.FontID = FontsConfig.DefaultSmallFontKey;
return config;
}
}
[DisableParentSettings("HideWhenInactive")]
[Section("Nameplates")]
[SubSection("NPCs", 0)]
public class NPCNameplateConfig : NameplateWithNPCBarConfig
{
public NPCNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplateBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig, barConfig)
{
}
public new static NPCNameplateConfig DefaultConfig()
{
NPCNameplateConfig config = NameplatesHelper.GetNameplateWithBarConfig<NPCNameplateConfig, NameplateBarConfig>(
0xFFD1E5C8,
0xFF3A4b1E,
HUDConstants.DefaultPlayerNameplateBarSize
);
config.NameLabelConfig.Position = new Vector2(0, -20);
config.TitleLabelConfig.Position = Vector2.Zero;
return config;
}
}
[DisableParentSettings("HideWhenInactive", "SwapLabelsWhenNeeded")]
[Section("Nameplates")]
[SubSection("Minions", 0)]
public class MinionNPCNameplateConfig : NameplateConfig
{
public MinionNPCNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig)
: base(position, nameLabel, titleLabelConfig)
{
}
public new static MinionNPCNameplateConfig DefaultConfig()
{
MinionNPCNameplateConfig config = NameplatesHelper.GetNameplateConfig<MinionNPCNameplateConfig>(0xFFFFFFFF, 0xFF000000);
config.OnlyShowWhenTargeted = true;
config.SwapLabelsWhenNeeded = false;
config.NameLabelConfig.Position = new Vector2(0, -17);
config.NameLabelConfig.FontID = FontsConfig.DefaultSmallFontKey;
config.TitleLabelConfig.Position = new Vector2(0, 0);
config.TitleLabelConfig.FontID = FontsConfig.DefaultSmallFontKey;
return config;
}
}
[DisableParentSettings("HideWhenInactive", "SwapLabelsWhenNeeded")]
[Section("Nameplates")]
[SubSection("Objects", 0)]
public class ObjectsNameplateConfig : NameplateConfig
{
public ObjectsNameplateConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig)
: base(position, nameLabel, titleLabelConfig)
{
}
public new static ObjectsNameplateConfig DefaultConfig()
{
ObjectsNameplateConfig config = NameplatesHelper.GetNameplateConfig<ObjectsNameplateConfig>(0xFFFFFFFF, 0xFF000000);
config.SwapLabelsWhenNeeded = false;
return config;
}
}
public class NameplateConfig : MovablePluginConfigObject
{
[Checkbox("Only show when targeted")]
[Order(1)]
public bool OnlyShowWhenTargeted = false;
[Checkbox("Swap Name and Title labels when needed", spacing = true, help = "This will swap the contents of these labels depending on if the title goes before or after the name of a player.")]
[Order(20)]
public bool SwapLabelsWhenNeeded = true;
[NestedConfig("Name Label", 21)]
public EditableLabelConfig NameLabelConfig = null!;
[NestedConfig("Title Label", 22)]
public EditableNonFormattableLabelConfig TitleLabelConfig = null!;
[NestedConfig("Change Alpha Based on Range", 145)]
public NameplateRangeConfig RangeConfig = new();
[NestedConfig("Visibility", 200)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public NameplateConfig(Vector2 position, EditableLabelConfig nameLabelConfig, EditableNonFormattableLabelConfig titleLabelConfig)
: base()
{
Position = position;
NameLabelConfig = nameLabelConfig;
TitleLabelConfig = titleLabelConfig;
}
public NameplateConfig() : base() { } // don't remove
}
public interface NameplateWithBarConfig
{
public NameplateBarConfig GetBarConfig();
}
public class NameplateWithNPCBarConfig : NameplateConfig, NameplateWithBarConfig
{
[NestedConfig("Health Bar", 40)]
public NameplateBarConfig BarConfig = null!;
public NameplateBarConfig GetBarConfig() => BarConfig;
public NameplateWithNPCBarConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplateBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig)
{
BarConfig = barConfig;
}
public NameplateWithNPCBarConfig() : base() { } // don't remove
}
public class NameplateWithPlayerBarConfig : NameplateConfig, NameplateWithBarConfig
{
[NestedConfig("Health Bar", 40)]
public NameplatePlayerBarConfig BarConfig = null!;
[NestedConfig("Role/Job Icon", 50)]
public NameplateRoleJobIconConfig RoleIconConfig = new NameplateRoleJobIconConfig(
new Vector2(-5, 0),
new Vector2(30, 30),
DrawAnchor.Right,
DrawAnchor.Left
)
{ Strata = StrataLevel.LOWEST };
[NestedConfig("Player State Icon", 55)]
public NameplatePlayerIconConfig StateIconConfig = new NameplatePlayerIconConfig(
new Vector2(5, 0),
new Vector2(30, 30),
DrawAnchor.Left,
DrawAnchor.Right
)
{ Strata = StrataLevel.LOWEST };
public NameplateBarConfig GetBarConfig() => BarConfig;
public NameplateWithPlayerBarConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplatePlayerBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig)
{
BarConfig = barConfig;
}
public NameplateWithPlayerBarConfig() : base() { } // don't remove
}
public class NameplateWithEnemyBarConfig : NameplateConfig, NameplateWithBarConfig
{
[NestedConfig("Health Bar", 40)]
public NameplateEnemyBarConfig BarConfig = null!;
[NestedConfig("Icon", 45)]
public NameplateIconConfig IconConfig = new NameplateIconConfig(
new Vector2(0, 0),
new Vector2(40, 40),
DrawAnchor.Right,
DrawAnchor.Left
)
{ PrioritizeHealthBarAnchor = true, Strata = StrataLevel.LOWEST };
[NestedConfig("Debuffs", 50)]
public EnemyNameplateStatusEffectsListConfig DebuffsConfig = null!;
[NestedConfig("Castbar", 55)]
public NameplateCastbarConfig CastbarConfig = null!;
public NameplateBarConfig GetBarConfig() => BarConfig;
public NameplateWithEnemyBarConfig(
Vector2 position,
EditableLabelConfig nameLabel,
EditableNonFormattableLabelConfig titleLabelConfig,
NameplateEnemyBarConfig barConfig)
: base(position, nameLabel, titleLabelConfig)
{
BarConfig = barConfig;
}
public NameplateWithEnemyBarConfig() : base() { } // don't remove
}
[DisableParentSettings("HideWhenInactive")]
public class NameplateBarConfig : BarConfig
{
[Checkbox("Only Show when not at full Health")]
[Order(1)]
public bool OnlyShowWhenNotFull = true;
[Checkbox("Hide Health when fully depleted", help = "This will hide the healthbar when the characters HP has been brought to zero")]
[Order(2)]
public bool HideHealthAtZero = true;
[Checkbox("Disable Interaction")]
[Order(3)]
public bool DisableInteraction = false;
[Checkbox("Use Different Size when targeted", spacing = true)]
[Order(31)]
public bool UseDifferentSizeWhenTargeted = false;
[DragInt2("Size When Targeted", min = 1, max = 4000)]
[Order(32, collapseWith = nameof(UseDifferentSizeWhenTargeted))]
public Vector2 SizeWhenTargeted;
[ColorEdit4("Targeted Border Color")]
[Order(38, collapseWith = nameof(DrawBorder))]
public PluginConfigColor TargetedBorderColor = PluginConfigColor.FromHex(0xFFFFFFFF);
[DragInt("Targeted Border Thickness", min = 1, max = 10)]
[Order(39, collapseWith = nameof(DrawBorder))]
public int TargetedBorderThickness = 2;
[NestedConfig("Color Based On Health Value", 50, collapsingHeader = false)]
public ColorByHealthValueConfig ColorByHealth = new ColorByHealthValueConfig();
[Checkbox("Hide Health if Possible", spacing = true, help = "This will hide any label that has a health tag if the character doesn't have health (ie minions, friendly npcs, etc)")]
[Order(121)]
public bool HideHealthIfPossible = true;
[NestedConfig("Left Text", 125)]
public EditableLabelConfig LeftLabelConfig = null!;
[NestedConfig("Right Text", 130)]
public EditableLabelConfig RightLabelConfig = null!;
[NestedConfig("Optional Text", 131)]
public EditableLabelConfig OptionalLabelConfig = null!;
[NestedConfig("Shields", 140)]
public ShieldConfig ShieldConfig = new ShieldConfig();
[NestedConfig("Custom Mouseover Area", 150)]
public MouseoverAreaConfig MouseoverAreaConfig = new MouseoverAreaConfig();
public NameplateBarConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
: base(position, size, new PluginConfigColor(new(40f / 255f, 40f / 255f, 40f / 255f, 100f / 100f)))
{
Position = position;
Size = size;
LeftLabelConfig = leftLabelConfig;
RightLabelConfig = rightLabelConfig;
OptionalLabelConfig = optionalLabelConfig;
BackgroundColor = new PluginConfigColor(new(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
ColorByHealth.Enabled = false;
MouseoverAreaConfig.Enabled = false;
}
public bool IsVisible(uint hp, uint maxHp)
{
return Enabled && (!OnlyShowWhenNotFull || hp < maxHp) && !(HideHealthAtZero && hp <= 0);
}
public Vector2 GetSize(bool targeted)
{
return targeted && UseDifferentSizeWhenTargeted ? SizeWhenTargeted : Size;
}
public NameplateBarConfig() : base(Vector2.Zero, Vector2.Zero, PluginConfigColor.Empty) { } // don't remove
}
public class NameplatePlayerBarConfig : NameplateBarConfig
{
[Checkbox("Use Job Color", spacing = true)]
[Order(45)]
public bool UseJobColor = false;
[Checkbox("Use Role Color")]
[Order(46)]
public bool UseRoleColor = false;
[Checkbox("Job Color As Background Color", spacing = true)]
[Order(50)]
public bool UseJobColorAsBackgroundColor = false;
[Checkbox("Role Color As Background Color")]
[Order(51)]
public bool UseRoleColorAsBackgroundColor = false;
public NameplatePlayerBarConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
{
}
}
public class NameplateEnemyBarConfig : NameplateBarConfig
{
[Checkbox("Use State Colors", spacing = true)]
[Order(45)]
public bool UseStateColor = true;
[ColorEdit4("Unengaged")]
[Order(46, collapseWith = nameof(UseStateColor))]
public PluginConfigColor UnengagedColor = PluginConfigColor.FromHex(0xFFDA9D2E);
[ColorEdit4("Unengaged (Hostile)")]
[Order(47, collapseWith = nameof(UseStateColor))]
public PluginConfigColor UnengagedHostileColor = PluginConfigColor.FromHex(0xFF994B35);
[ColorEdit4("Engaged")]
[Order(48, collapseWith = nameof(UseStateColor))]
public PluginConfigColor EngagedColor = PluginConfigColor.FromHex(0xFF993535);
[ColorEdit4("Claimed")]
[Order(49, collapseWith = nameof(UseStateColor))]
public PluginConfigColor ClaimedColor = PluginConfigColor.FromHex(0xFFEA93EA);
[ColorEdit4("Unclaimed")]
[Order(50, collapseWith = nameof(UseStateColor))]
public PluginConfigColor UnclaimedColor = PluginConfigColor.FromHex(0xFFE5BB9E);
[Checkbox("Use Custom Color when being targeted", spacing = true, help = "This will change the color of the bar when the enemy is targeting the player.")]
[Order(51)]
public bool UseCustomColorWhenBeingTargeted = false;
[ColorEdit4("Targeted")]
[Order(52, collapseWith = nameof(UseCustomColorWhenBeingTargeted))]
public PluginConfigColor CustomColorWhenBeingTargeted = PluginConfigColor.FromHex(0xFFC4216D);
[NestedConfig("Order Label", 132)]
public DefaultFontLabelConfig OrderLabelConfig = new DefaultFontLabelConfig(new Vector2(5, 0), "", DrawAnchor.Right, DrawAnchor.Left)
{
Strata = StrataLevel.LOWEST
};
public NameplateEnemyBarConfig(Vector2 position, Vector2 size, EditableLabelConfig leftLabelConfig, EditableLabelConfig rightLabelConfig, EditableLabelConfig optionalLabelConfig)
: base(position, size, leftLabelConfig, rightLabelConfig, optionalLabelConfig)
{
}
}
[Exportable(false)]
public class NameplateRangeConfig : PluginConfigObject
{
[DragInt("Fade start range (yalms)", min = 1, max = 500)]
[Order(5)]
public int StartRange = 50;
[DragInt("Fade end range (yalms)", min = 1, max = 500)]
[Order(10)]
public int EndRange = 64;
public float AlphaForDistance(float distance, float maxAlpha = 1f)
{
float diff = distance - StartRange;
if (!Enabled || diff <= 0)
{
return maxAlpha;
}
float a = diff / (EndRange - StartRange);
return Math.Max(0, Math.Min(maxAlpha, 1 - a));
}
}
public class EnemyNameplateStatusEffectsListConfig : StatusEffectsListConfig
{
[Anchor("Health Bar Anchor")]
[Order(4)]
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
public EnemyNameplateStatusEffectsListConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
HealthBarAnchor = anchor;
}
}
[DisableParentSettings("AnchorToUnitFrame", "UnitFrameAnchor", "HideWhenInactive", "FillDirection")]
public class NameplateCastbarConfig : TargetCastbarConfig
{
[Checkbox("Match Width with Health Bar")]
[Order(11)]
public bool MatchWidth = false;
[Checkbox("Match Height with Health Bar")]
[Order(12)]
public bool MatchHeight = false;
[Anchor("Health Bar Anchor")]
[Order(16)]
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
public NameplateCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
: base(position, size, castNameConfig, castTimeConfig)
{
}
}
internal static class NameplatesHelper
{
internal static T GetNameplateConfig<T>(uint bgColor, uint borderColor) where T : NameplateConfig
{
EditableLabelConfig nameLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "[name]", DrawAnchor.Top, DrawAnchor.Bottom)
{
Color = PluginConfigColor.FromHex(bgColor),
OutlineColor = PluginConfigColor.FromHex(borderColor),
FontID = FontsConfig.DefaultMediumFontKey
};
EditableNonFormattableLabelConfig titleLabelConfig = new EditableNonFormattableLabelConfig(new Vector2(0, -25), "<[title]>", DrawAnchor.Top, DrawAnchor.Bottom)
{
Color = PluginConfigColor.FromHex(bgColor),
OutlineColor = PluginConfigColor.FromHex(borderColor),
FontID = FontsConfig.DefaultMediumFontKey
};
return (T)Activator.CreateInstance(typeof(T), Vector2.Zero, nameLabelConfig, titleLabelConfig)!;
}
internal static T GetNameplateWithBarConfig<T, B>(uint bgColor, uint borderColor, Vector2 barSize)
where T : NameplateConfig
where B : NameplateBarConfig
{
EditableLabelConfig leftLabelConfig = new EditableLabelConfig(new Vector2(5, 0), "[health:current-short]", DrawAnchor.Left, DrawAnchor.Left)
{
Enabled = false,
FontID = FontsConfig.DefaultMediumFontKey,
Strata = StrataLevel.LOWEST
};
EditableLabelConfig rightLabelConfig = new EditableLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right)
{
Enabled = false,
FontID = FontsConfig.DefaultMediumFontKey,
Strata = StrataLevel.LOWEST
};
EditableLabelConfig optionalLabelConfig = new EditableLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center)
{
Enabled = false,
FontID = FontsConfig.DefaultSmallFontKey,
Strata = StrataLevel.LOWEST
};
var barConfig = Activator.CreateInstance(typeof(B), new Vector2(0, -5), barSize, leftLabelConfig, rightLabelConfig, optionalLabelConfig)!;
if (barConfig is BarConfig bar)
{
bar.FillColor = PluginConfigColor.FromHex(bgColor);
bar.BackgroundColor = PluginConfigColor.FromHex(0xAA000000);
}
if (barConfig is NameplateBarConfig nameplateBar)
{
nameplateBar.SizeWhenTargeted = nameplateBar.Size;
}
EditableLabelConfig nameLabelConfig = new EditableLabelConfig(new Vector2(0, -20), "[name]", DrawAnchor.Top, DrawAnchor.Bottom)
{
Color = PluginConfigColor.FromHex(bgColor),
OutlineColor = PluginConfigColor.FromHex(borderColor),
FontID = FontsConfig.DefaultMediumFontKey,
Strata = StrataLevel.LOWEST
};
EditableNonFormattableLabelConfig titleLabelConfig = new EditableNonFormattableLabelConfig(new Vector2(0, 0), "<[title]>", DrawAnchor.Top, DrawAnchor.Bottom)
{
Color = PluginConfigColor.FromHex(bgColor),
OutlineColor = PluginConfigColor.FromHex(borderColor),
FontID = FontsConfig.DefaultMediumFontKey,
Strata = StrataLevel.LOWEST
};
return (T)Activator.CreateInstance(typeof(T), Vector2.Zero, nameLabelConfig, titleLabelConfig, barConfig)!;
}
}
}
+232
View File
@@ -0,0 +1,232 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using HSUI.Config;
using HSUI.Helpers;
using HSUI.Interface.GeneralElements;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Graphics;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
using System.Numerics;
namespace HSUI.Interface.Nameplates
{
internal class NameplatesHud : HudElement
{
private NameplatesGeneralConfig Config => (NameplatesGeneralConfig)_config;
private NameplateWithPlayerBar _playerHud;
private NameplateWithEnemyBar _enemyHud;
private NameplateWithPlayerBar _partyMemberHud;
private NameplateWithPlayerBar _allianceMemberHud;
private NameplateWithPlayerBar _friendsHud;
private NameplateWithPlayerBar _otherPlayersHud;
private NameplateWithBar _petHud;
private NameplateWithBar _npcHud;
private Nameplate _minionNPCHud;
private Nameplate _objectHud;
private bool _wasHovering;
public NameplatesHud(NameplatesGeneralConfig config) : base(config)
{
ConfigurationManager manager = ConfigurationManager.Instance;
_playerHud = new NameplateWithPlayerBar(manager.GetConfigObject<PlayerNameplateConfig>());
_enemyHud = new NameplateWithEnemyBar(manager.GetConfigObject<EnemyNameplateConfig>());
_partyMemberHud = new NameplateWithPlayerBar(manager.GetConfigObject<PartyMembersNameplateConfig>());
_allianceMemberHud = new NameplateWithPlayerBar(manager.GetConfigObject<AllianceMembersNameplateConfig>());
_friendsHud = new NameplateWithPlayerBar(manager.GetConfigObject<FriendPlayerNameplateConfig>());
_otherPlayersHud = new NameplateWithPlayerBar(manager.GetConfigObject<OtherPlayerNameplateConfig>());
_petHud = new NameplateWithBar(manager.GetConfigObject<PetNameplateConfig>());
_npcHud = new NameplateWithBar(manager.GetConfigObject<NPCNameplateConfig>());
_minionNPCHud = new Nameplate(manager.GetConfigObject<MinionNPCNameplateConfig>());
_objectHud = new Nameplate(manager.GetConfigObject<ObjectsNameplateConfig>());
}
public void StopPreview()
{
_enemyHud.StopPreview();
}
public void StopMouseover()
{
if (_wasHovering)
{
InputsHelper.Instance.ClearTarget();
_wasHovering = false;
}
}
protected override void CreateDrawActions(Vector2 origin)
{
if (!_config.Enabled || NameplatesManager.Instance == null)
{
StopMouseover();
return;
}
IGameObject? mouseoveredActor = null;
bool ignoreMouseover = false;
foreach (NameplateData data in NameplatesManager.Instance.Data)
{
Nameplate? nameplate = GetNameplate(data);
if (nameplate == null || !nameplate.Enabled) { continue; }
// raycasting
if (IsPointObstructed(data)) { continue; }
if (nameplate is NameplateWithBar nameplateWithBar)
{
// draw bar
AddDrawActions(nameplateWithBar.GetBarDrawActions(data));
// find mouseovered nameplate
var (isHovering, ignore) = nameplateWithBar.GetMouseoverState(data);
if (isHovering)
{
mouseoveredActor = data.GameObject;
ignoreMouseover = ignore;
}
}
// draw elements
AddDrawActions(nameplate.GetElementsDrawActions(data));
}
// mouseover
if (mouseoveredActor != null)
{
_wasHovering = true;
InputsHelper.Instance.SetTarget(mouseoveredActor, ignoreMouseover);
if (InputsHelper.Instance.LeftButtonClicked)
{
Plugin.TargetManager.Target = mouseoveredActor;
InputsHelper.Instance.ClearClicks();
}
else if (InputsHelper.Instance.RightButtonClicked)
{
InputsHelper.Instance.ClearClicks();
}
}
else if (_wasHovering)
{
InputsHelper.Instance.ClearTarget();
_wasHovering = false;
}
}
private unsafe Nameplate? GetNameplate(NameplateData data)
{
switch (data.Kind)
{
case ObjectKind.Player:
if (data.GameObject?.EntityId == Plugin.ObjectTable.LocalPlayer?.EntityId)
{
return _playerHud;
}
if (data.GameObject is ICharacter character)
{
if ((character.StatusFlags & StatusFlags.PartyMember) != 0) // PartyMember
{
return _partyMemberHud;
}
else if ((character.StatusFlags & StatusFlags.AllianceMember) != 0) // AllianceMember
{
return _allianceMemberHud;
}
else if ((character.StatusFlags & StatusFlags.Friend) != 0) // Friend
{
return _friendsHud;
}
}
return _otherPlayersHud;
case ObjectKind.BattleNpc:
if (data.GameObject is IBattleNpc battleNpc)
{
if ((BattleNpcSubKind)battleNpc.SubKind == BattleNpcSubKind.Pet ||
(BattleNpcSubKind)battleNpc.SubKind == BattleNpcSubKind.Chocobo)
{
return _petHud;
}
else if ((BattleNpcSubKind)battleNpc.SubKind == BattleNpcSubKind.Enemy ||
(BattleNpcSubKind)battleNpc.SubKind == BattleNpcSubKind.BattleNpcPart)
{
return Utils.IsHostile(data.GameObject) ? _enemyHud : _npcHud;
}
else if (battleNpc.SubKind == 10) // island released minions
{
return _npcHud;
}
}
break;
case ObjectKind.EventNpc: return _npcHud;
case ObjectKind.Companion: return _minionNPCHud;
default: return _objectHud;
}
return null;
}
private unsafe bool IsPointObstructed(NameplateData data)
{
if (data.GameObject == null) { return true; }
if (Config.OcclusionMode == NameplatesOcclusionMode.None || data.IgnoreOcclusion) { return false; }
Camera camera = Control.Instance()->CameraManager.Camera->CameraBase.SceneCamera;
Vector3 cameraPos = camera.Object.Position;
BGCollisionModule* collisionModule = Framework.Instance()->BGCollisionModule;
if (collisionModule == null)
{
return false;
}
int flag = Config.RaycastFlag();
int* flags = stackalloc int[] { flag, 0, flag, 0 };
bool obstructed = false;
// simple mode
if (Config.OcclusionMode == NameplatesOcclusionMode.Simple)
{
Vector3 direction = Vector3.Normalize(data.WorldPosition - cameraPos);
RaycastHit hit;
obstructed = collisionModule->RaycastMaterialFilter(&hit, &cameraPos, &direction, data.Distance, 1, flags);
}
// full mode
else
{
int obstructionCount = 0;
Vector2[] points = new Vector2[]
{
data.ScreenPosition + new Vector2(-30, 0), // left
data.ScreenPosition + new Vector2(30, 0), // right
};
foreach (Vector2 point in points)
{
Ray ray = camera.ScreenPointToRay(point);
RaycastHit hit;
Vector3 origin = new Vector3(ray.Origin.X, ray.Origin.Y, ray.Origin.Z);
Vector3 direction = new Vector3(ray.Direction.X, ray.Direction.Y, ray.Direction.Z);
if (collisionModule->RaycastMaterialFilter(&hit, &origin, &direction, data.Distance, 1, flags))
{
obstructionCount++;
}
}
obstructed = obstructionCount == points.Length;
}
return obstructed;
}
}
}
+365
View File
@@ -0,0 +1,365 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Memory;
using HSUI.Config;
using HSUI.Helpers;
using HSUI.Interface.GeneralElements;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;
using static FFXIVClientStructs.FFXIV.Client.UI.AddonNamePlate;
using static FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkModule;
using static FFXIVClientStructs.FFXIV.Client.UI.UI3DModule;
using StructsFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
using StructsGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace HSUI.Interface.Nameplates
{
internal class NameplatesManager : IDisposable
{
#region Singleton
public static NameplatesManager Instance { get; private set; } = null!;
private NameplatesGeneralConfig _config = null!;
private NameplatesManager()
{
Plugin.ClientState.TerritoryChanged -= ClientStateOnTerritoryChangedEvent;
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
OnConfigReset(ConfigurationManager.Instance);
}
public static void Initialize()
{
Instance = new NameplatesManager();
}
~NameplatesManager()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
Plugin.ClientState.TerritoryChanged -= ClientStateOnTerritoryChangedEvent;
Instance = null!;
}
private void OnConfigReset(ConfigurationManager sender)
{
_config = sender.GetConfigObject<NameplatesGeneralConfig>();
}
#endregion Singleton
private const int NameplateCount = 50;
private const int NameplateDataArrayIndex = 4; // TODO: Rework to use NamePlateStringArray if it exists or use AtkStage.Instance()->GetStringArrayData(StringArrayType.NamePlate)
private Vector2 _averageNameplateSize = new Vector2(250, 150);
private List<NameplateData> _data = new List<NameplateData>();
public IReadOnlyCollection<NameplateData> Data => _data.AsReadOnly();
private NameplatesCache _cache = new NameplatesCache(50);
private void ClientStateOnTerritoryChangedEvent(ushort territoryId)
{
_cache.Clear();
}
public unsafe void Update()
{
if (!_config.Enabled) { return; }
UIModule* uiModule = StructsFramework.Instance()->GetUIModule();
if (uiModule == null) { return; }
UI3DModule* ui3DModule = uiModule->GetUI3DModule();
if (ui3DModule == null) { return; }
AddonNamePlate* addon = (AddonNamePlate*)Plugin.GameGui.GetAddonByName("NamePlate", 1).Address;
if (addon == null) { return; }
RaptureAtkModule* atkModule = uiModule->GetRaptureAtkModule();
if (atkModule == null || atkModule->AtkModule.AtkArrayDataHolder.StringArrayCount <= NameplateDataArrayIndex) { return; }
StringArrayData* stringArray = atkModule->AtkModule.AtkArrayDataHolder.StringArrays[NameplateDataArrayIndex];
Span<NamePlateInfo> infoArray = atkModule->NamePlateInfoEntries;
Camera camera = Control.Instance()->CameraManager.Camera->CameraBase.SceneCamera;
IGameObject? target = Plugin.TargetManager.Target;
bool foundTarget = false;
NameplateData? targetData = null;
_data = new List<NameplateData>();
int activeCount = ui3DModule->NamePlateObjectInfoCount;
for (int i = 0; i < activeCount; i++)
{
try
{
ObjectInfo* objectInfo = ui3DModule->NamePlateObjectInfoPointers[i];
if (objectInfo == null || objectInfo->NamePlateIndex >= NameplateCount) { continue; }
// actor
StructsGameObject* obj = objectInfo->GameObject;
if (obj == null) { continue; }
bool isTarget = false;
IGameObject? gameObject = Plugin.ObjectTable.CreateObjectReference(new IntPtr(obj));
if (target != null && new IntPtr(obj) == target.Address)
{
isTarget = true;
foundTarget = true;
}
// ui nameplate
NamePlateObject nameplateObject = addon->NamePlateObjectArray[objectInfo->NamePlateIndex];
// position
Vector2 screenPos = new Vector2(
nameplateObject.RootComponentNode->AtkResNode.X + nameplateObject.RootComponentNode->AtkResNode.Width / 2f,
nameplateObject.RootComponentNode->AtkResNode.Y + nameplateObject.RootComponentNode->AtkResNode.Height
);
screenPos = ClampScreenPosition(screenPos);
Vector3 worldPos = new Vector3(obj->Position.X, obj->Position.Y + obj->Height * 2.2f, obj->Position.Z);
// distance
float distance = Vector3.Distance(camera.Object.Position, worldPos);
// name
NamePlateInfo info = infoArray[objectInfo->NamePlateIndex];
string name = info.Name.ToString();
// title
string title = info.Title.ToString();
bool isTitlePrefix = info.IsPrefixTitle;
// Get the title from Honorific, if it exists
TitleData? customTitleData = HonorificHelper.Instance?.GetTitle(gameObject);
if (customTitleData != null)
{
title = customTitleData.Title;
isTitlePrefix = customTitleData.IsPrefix;
}
// state icon
int iconId = 0;
AtkUldAsset* textureInfo = nameplateObject.NameIcon->PartsList->Parts[nameplateObject.NameIcon->PartId].UldAsset;
if (textureInfo != null && textureInfo->AtkTexture.Resource != null)
{
iconId = (int)textureInfo->AtkTexture.Resource->IconId;
}
// order
int arrayIndex = 200 + (activeCount - nameplateObject.Priority - 1);
string order = "";
try
{
if (stringArray->AtkArrayData.Size > arrayIndex && stringArray->StringArray[arrayIndex] != null)
{
order = MemoryHelper.ReadSeStringNullTerminated(new IntPtr(stringArray->StringArray[arrayIndex])).ToString();
}
}
catch { }
NameplateData data = new NameplateData(
gameObject,
name,
title,
isTitlePrefix,
iconId,
order,
(ObjectKind)obj->ObjectKind,
obj->SubKind,
screenPos,
worldPos,
distance
);
if (isTarget)
{
targetData = data;
}
else
{
_data.Add(data);
}
_cache.Add(obj->GetGameObjectId().ObjectId, data);
}
catch { }
}
_data.Reverse();
// add target nameplate last
if (foundTarget && targetData.HasValue)
{
_data.Add(targetData.Value);
}
// create nameplate for target?
else if (_config.AlwaysShowTargetNameplate && target != null && !foundTarget)
{
StructsGameObject* obj = (StructsGameObject*)target.Address;
NameplateData? cachedData = _cache[(uint)target.GameObjectId];
Vector3 worldPos = new Vector3(target.Position.X, target.Position.Y + obj->Height * 2.2f, target.Position.Z);
float distance = Vector3.Distance(camera.Object.Position, worldPos);
Plugin.GameGui.WorldToScreen(worldPos, out Vector2 screenPos);
screenPos = ClampScreenPosition(screenPos);
targetData = new NameplateData(
target,
target.Name.ToString(),
cachedData?.Title ?? "",
cachedData?.IsTitlePrefix ?? true,
cachedData?.NamePlateIconId ?? 0,
cachedData?.Order ?? "",
target.ObjectKind,
target.SubKind,
screenPos,
worldPos,
distance,
true
);
_data.Add(targetData.Value);
}
}
private Vector2 ClampScreenPosition(Vector2 pos)
{
if (!_config.ClampToScreen) { return pos; }
Vector2 screenSize = ImGui.GetMainViewport().Size;
Vector2 nameplateSize = _averageNameplateSize / 2f;
float margin = 20;
if (pos.X + nameplateSize.X > screenSize.X)
{
pos.X = screenSize.X - nameplateSize.X - margin;
}
else if (pos.X - nameplateSize.X < 0)
{
pos.X = nameplateSize.X + margin;
}
if (pos.Y + nameplateSize.Y > screenSize.Y)
{
pos.Y = screenSize.Y - nameplateSize.Y - margin;
}
else if (pos.Y - nameplateSize.Y < 0)
{
pos.Y = nameplateSize.Y + margin;
}
return pos;
}
}
#region utils
public class NameplatesCache
{
private int _limit;
private Dictionary<uint, NameplateData> _dict;
private Queue<uint> _queue;
public NameplatesCache(int limit)
{
_limit = limit;
_dict = new Dictionary<uint, NameplateData>(limit);
_queue = new Queue<uint>(limit);
}
public void Add(uint key, NameplateData data)
{
if (key == 0 || key == 0xE0000000) { return; }
if (_dict.Count == _limit)
{
uint oldestKey = _queue.Dequeue();
_dict.Remove(oldestKey);
}
if (_dict.ContainsKey(key))
{
_dict[key] = data;
}
else
{
_dict.Add(key, data);
_queue.Enqueue(key);
}
}
public void Clear()
{
_dict.Clear();
_queue.Clear();
}
public NameplateData? this[uint key]
{
get
{
if (_dict.TryGetValue(key, out NameplateData data))
{
return data;
}
return null;
}
}
}
public struct NameplateData
{
public IGameObject? GameObject;
public string Name;
public string Title;
public bool IsTitlePrefix;
public int NamePlateIconId;
public string Order;
public ObjectKind Kind;
public byte SubKind;
public Vector2 ScreenPosition;
public Vector3 WorldPosition;
public float Distance;
public bool IgnoreOcclusion;
public NameplateData(IGameObject? gameObject, string name, string title, bool isTitlePrefix, int namePlateIconId, string order, ObjectKind kind, byte subKind, Vector2 screenPosition, Vector3 worldPosition, float distance, bool ignoreOcclusion = false)
{
GameObject = gameObject;
Name = name;
Title = title;
IsTitlePrefix = isTitlePrefix;
NamePlateIconId = namePlateIconId;
Order = order;
Kind = kind;
SubKind = subKind;
ScreenPosition = screenPosition;
WorldPosition = worldPosition;
Distance = distance;
IgnoreOcclusion = ignoreOcclusion;
}
}
#endregion
}
+748
View File
@@ -0,0 +1,748 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Statuses;
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.Numerics;
namespace HSUI.Interface.Party
{
public class PartyFramesBar
{
public delegate void PartyFramesBarEventHandler(PartyFramesBar bar);
public PartyFramesBarEventHandler? OpenContextMenuEvent;
private PartyFramesConfigs _configs;
private LabelHud _nameLabelHud;
private LabelHud _healthLabelHud;
private LabelHud _orderLabelHud;
private LabelHud _statusLabelHud;
private LabelHud _raiseLabelHud;
private LabelHud _invulnLabelHud;
private PrimaryResourceHud _manaBarHud;
private CastbarHud _castbarHud;
private StatusEffectsListHud _buffsListHud;
private StatusEffectsListHud _debuffsListHud;
private PartyFramesCooldownListHud _cooldownListHud;
private IDalamudTextureWrap? _readyCheckTexture =>
TexturesHelper.GetTextureFromPath("ui/uld/ReadyCheck_hr1.tex") ??
TexturesHelper.GetTextureFromPath("ui/uld/ReadyCheck.tex");
public bool Visible = false;
public Vector2 Position;
private SmoothHPHelper _smoothHPHelper = new SmoothHPHelper();
private bool _wasHovering = false;
public IPartyFramesMember? Member;
public PartyFramesBar(string id, PartyFramesConfigs configs)
{
_configs = configs;
_nameLabelHud = new LabelHud(_configs.HealthBar.NameLabelConfig);
_healthLabelHud = new LabelHud(_configs.HealthBar.HealthLabelConfig);
_orderLabelHud = new LabelHud(_configs.HealthBar.OrderNumberConfig);
_statusLabelHud = new LabelHud(PlayerStatus.Label);
_raiseLabelHud = new LabelHud(RaiseTracker.Icon.NumericLabel);
_invulnLabelHud = new LabelHud(InvulnTracker.Icon.NumericLabel);
_manaBarHud = new PrimaryResourceHud(_configs.ManaBar);
_castbarHud = new CastbarHud(_configs.CastBar);
_buffsListHud = new StatusEffectsListHud(_configs.Buffs);
_debuffsListHud = new StatusEffectsListHud(_configs.Debuffs);
_cooldownListHud = new PartyFramesCooldownListHud(_configs.CooldownList);
}
public PluginConfigColor GetColor(float scale)
{
if (Member == null || Member.MaxHP <= 0)
{
return _configs.HealthBar.ColorsConfig.OutOfReachBackgroundColor;
}
bool cleanseCheck = true;
if (CleanseTracker.CleanseJobsOnly)
{
cleanseCheck = Utils.IsOnCleanseJob();
}
if (CleanseTracker.Enabled && CleanseTracker.ChangeHealthBarCleanseColor && Member.HasDispellableDebuff && cleanseCheck)
{
return CleanseTracker.HealthBarColor;
}
else if (_configs.HealthBar.ColorsConfig.ColorByHealth.Enabled)
{
if (_configs.HealthBar.ColorsConfig.ColorByHealth.UseJobColorAsMaxHealth)
{
return ColorUtils.GetColorByScale(scale, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColor,
GlobalColors.Instance.SafeColorForJobId(Member.JobId), _configs.HealthBar.ColorsConfig.ColorByHealth.UseMaxHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.BlendMode);
}
else if (_configs.HealthBar.ColorsConfig.ColorByHealth.UseRoleColorAsMaxHealth)
{
return ColorUtils.GetColorByScale(scale, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColor,
GlobalColors.Instance.SafeRoleColorForJobId(Member.JobId), _configs.HealthBar.ColorsConfig.ColorByHealth.UseMaxHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.BlendMode);
}
return ColorUtils.GetColorByScale(scale, _configs.HealthBar.ColorsConfig.ColorByHealth);
}
else if (Member.JobId > 0)
{
return _configs.HealthBar.ColorsConfig.UseRoleColors switch
{
true => GlobalColors.Instance.SafeRoleColorForJobId(Member.JobId),
_ => GlobalColors.Instance.SafeColorForJobId(Member.JobId)
};
}
return Member.Character?.ObjectKind switch
{
ObjectKind.BattleNpc => GlobalColors.Instance.NPCFriendlyColor,
_ => _configs.HealthBar.ColorsConfig.OutOfReachBackgroundColor
};
}
public void StopPreview()
{
_castbarHud.StopPreview();
_buffsListHud.StopPreview();
_debuffsListHud.StopPreview();
_cooldownListHud.StopPreview();
_configs.HealthBar.MouseoverAreaConfig.Preview = false;
}
public void StopMouseover()
{
if (_wasHovering)
{
InputsHelper.Instance.ClearTarget();
_wasHovering = false;
}
}
public void Dispose()
{
_cooldownListHud.Dispose();
}
public List<(StrataLevel, Action)> GetBarDrawActions(Vector2 origin, PluginConfigColor? borderColor = null)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
if (!Visible || Member is null)
{
StopMouseover();
return drawActions;
}
// click
var (areaStart, areaEnd) = _configs.HealthBar.MouseoverAreaConfig.GetArea(Position, _configs.HealthBar.Size);
bool isHovering = ImGui.IsMouseHoveringRect(areaStart, areaEnd);
bool ignoreMouseover = _configs.HealthBar.MouseoverAreaConfig.Enabled && _configs.HealthBar.MouseoverAreaConfig.Ignore;
ICharacter? character = Member.Character;
if (isHovering)
{
_wasHovering = true;
InputsHelper.Instance.SetTarget(character, ignoreMouseover);
// left click
if (InputsHelper.Instance.LeftButtonClicked && character != null)
{
Plugin.TargetManager.Target = character;
}
// right click (context menu)
else if (InputsHelper.Instance.RightButtonClicked)
{
OpenContextMenuEvent?.Invoke(this);
}
}
else if (_wasHovering)
{
InputsHelper.Instance.ClearTarget();
_wasHovering = false;
}
// bg
PluginConfigColor bgColor = _configs.HealthBar.ColorsConfig.BackgroundColor;
if (Member.RaiseTime != null && RaiseTracker.Enabled && RaiseTracker.ChangeBackgroundColorWhenRaised)
{
bgColor = RaiseTracker.BackgroundColor;
}
else if (Member.InvulnStatus?.InvulnTime != null && InvulnTracker.Enabled && InvulnTracker.ChangeBackgroundColorWhenInvuln)
{
bgColor = Member.InvulnStatus?.InvulnId == 811 ? InvulnTracker.WalkingDeadBackgroundColor : InvulnTracker.BackgroundColor;
}
else if (_configs.HealthBar.ColorsConfig.UseDeathIndicatorBackgroundColor && Member.HP <= 0 && character != null)
{
bgColor = _configs.HealthBar.RangeConfig.Enabled
? GetDistanceColor(character, _configs.HealthBar.ColorsConfig.DeathIndicatorBackgroundColor)
: _configs.HealthBar.ColorsConfig.DeathIndicatorBackgroundColor;
}
else if (_configs.HealthBar.ColorsConfig.UseJobColorAsBackgroundColor && character is IBattleChara)
{
bgColor = GlobalColors.Instance.SafeColorForJobId(character.ClassJob.RowId);
}
else if (_configs.HealthBar.ColorsConfig.UseRoleColorAsBackgroundColor && character is IBattleChara)
{
bgColor = _configs.HealthBar.RangeConfig.Enabled
? GetDistanceColor(character, GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId))
: GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId);
}
Rect background = new Rect(Position, _configs.HealthBar.Size, bgColor);
// hp
uint currentHp = Member.HP;
uint maxHp = Member.MaxHP;
if (_configs.HealthBar.SmoothHealthConfig.Enabled)
{
currentHp = _smoothHPHelper.GetNextHp((int)currentHp, (int)maxHp, _configs.HealthBar.SmoothHealthConfig.Velocity);
}
float hpScale = maxHp > 0 ? (float)currentHp / (float)maxHp : 1;
PluginConfigColor? hpColor = _configs.HealthBar.RangeConfig.Enabled && character != null
? GetDistanceColor(character, GetColor(hpScale))
: GetColor(hpScale);
Rect healthFill = BarUtilities.GetFillRect(Position, _configs.HealthBar.Size, _configs.HealthBar.FillDirection, hpColor, currentHp, maxHp);
// bar
int thickness = borderColor != null ? _configs.HealthBar.ColorsConfig.ActiveBorderThickness : _configs.HealthBar.ColorsConfig.InactiveBorderThickness;
if (WhosTalkingIcon.ChangeBorders && Member.WhosTalkingState != WhosTalkingState.None)
{
thickness = WhosTalkingIcon.BorderThickness;
}
borderColor = borderColor ?? GetBorderColor(character);
BarHud bar = new BarHud(
_configs.HealthBar.ID,
_configs.HealthBar.ColorsConfig.ShowBorder,
borderColor,
thickness,
actor: character,
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);
// missing health
if (_configs.HealthBar.ColorsConfig.UseMissingHealthBar)
{
Vector2 healthMissingSize = _configs.HealthBar.Size - BarUtilities.GetFillDirectionOffset(healthFill.Size, _configs.HealthBar.FillDirection);
Vector2 healthMissingPos = _configs.HealthBar.FillDirection.IsInverted() ? Position : Position + BarUtilities.GetFillDirectionOffset(healthFill.Size, _configs.HealthBar.FillDirection);
PluginConfigColor? missingHealthColor = _configs.HealthBar.ColorsConfig.UseJobColorAsMissingHealthColor && character is IBattleChara
? GlobalColors.Instance.SafeColorForJobId(character!.ClassJob.RowId)
: _configs.HealthBar.ColorsConfig.UseRoleColorAsMissingHealthColor && character is IBattleChara
? GlobalColors.Instance.SafeRoleColorForJobId(character!.ClassJob.RowId)
: _configs.HealthBar.ColorsConfig.HealthMissingColor;
if (_configs.HealthBar.ColorsConfig.UseDeathIndicatorBackgroundColor && Member.HP <= 0 && character != null)
{
missingHealthColor = _configs.HealthBar.ColorsConfig.DeathIndicatorBackgroundColor;
}
if (_configs.Trackers.Invuln.ChangeBackgroundColorWhenInvuln && character is IBattleChara battleChara)
{
IStatus? tankInvuln = Utils.GetTankInvulnerabilityID(battleChara);
if (tankInvuln is not null)
{
missingHealthColor = _configs.Trackers.Invuln.BackgroundColor;
}
}
if (_configs.HealthBar.RangeConfig.Enabled)
{
missingHealthColor = GetDistanceColor(character, missingHealthColor);
}
bar.AddForegrounds(new Rect(healthMissingPos, healthMissingSize, missingHealthColor));
}
// shield
if (_configs.HealthBar.ShieldConfig.Enabled)
{
if (Member.Shield > 0f)
{
bar.AddForegrounds(
BarUtilities.GetShieldForeground(
_configs.HealthBar.ShieldConfig,
Position,
_configs.HealthBar.Size,
healthFill.Size,
_configs.HealthBar.FillDirection,
Member.Shield,
currentHp,
maxHp
)
);
}
}
// highlight
bool isSoftTarget = character != null && character == Plugin.TargetManager.SoftTarget;
if (_configs.HealthBar.ColorsConfig.ShowHighlight && (isHovering || isSoftTarget))
{
Rect highlight = new Rect(Position, _configs.HealthBar.Size, _configs.HealthBar.ColorsConfig.HighlightColor);
bar.AddForegrounds(highlight);
}
drawActions = bar.GetDrawActions(Vector2.Zero, _configs.HealthBar.StrataLevel);
// mouseover area
BarHud? mouseoverAreaBar = _configs.HealthBar.MouseoverAreaConfig.GetBar(
Position,
_configs.HealthBar.Size,
_configs.HealthBar.ID + "_mouseoverArea"
);
if (mouseoverAreaBar != null)
{
drawActions.AddRange(mouseoverAreaBar.GetDrawActions(Vector2.Zero, StrataLevel.HIGHEST));
}
return drawActions;
}
private PluginConfigColor GetBorderColor(ICharacter? character)
{
IGameObject? target = Plugin.TargetManager.Target ?? Plugin.TargetManager.SoftTarget;
return character != null && character == target ? _configs.HealthBar.ColorsConfig.TargetBordercolor : _configs.HealthBar.ColorsConfig.BorderColor;
}
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);
}
// need to separate elements that have their own window so clipping doesn't get messy
public List<(StrataLevel, Action)> GetElementsDrawActions(Vector2 origin)
{
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
if (!Visible || Member is null || player == null)
{
StopMouseover();
return drawActions;
}
ICharacter? character = Member.Character;
// who's talking
bool drawingWhosTalking = false;
if (WhosTalkingIcon.Enabled && WhosTalkingIcon.Icon.Enabled && WhosTalkingIcon.EnabledForState(Member.WhosTalkingState))
{
IDalamudTextureWrap? texture = WhosTalkingHelper.Instance.GetTextureForState(Member.WhosTalkingState);
if (texture != null)
{
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, WhosTalkingIcon.Icon.FrameAnchor);
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + WhosTalkingIcon.Icon.Position, WhosTalkingIcon.Icon.Size, WhosTalkingIcon.Icon.Anchor);
drawActions.Add((WhosTalkingIcon.Icon.StrataLevel, () =>
{
DrawHelper.DrawInWindow(WhosTalkingIcon.Icon.ID, iconPos, WhosTalkingIcon.Icon.Size, false, (drawList) =>
{
ImGui.SetCursorPos(iconPos);
ImGui.Image(texture.Handle, WhosTalkingIcon.Icon.Size);
});
}
));
drawingWhosTalking = true;
}
}
// role/job icon
if (RoleIcon.Enabled && (!drawingWhosTalking || !WhosTalkingIcon.ReplaceRoleJobIcon))
{
uint iconId = 0;
// chocobo icon
if (character is IBattleNpc battleNpc && battleNpc.BattleNpcKind == BattleNpcSubKind.Chocobo)
{
iconId = JobsHelper.RoleIconIDForBattleCompanion + (uint)RoleIcon.Style * 100;
}
// role/job icon
else if (Member.JobId > 0)
{
iconId = RoleIcon.UseRoleIcons ?
JobsHelper.RoleIconIDForJob(Member.JobId, RoleIcon.UseSpecificDPSRoleIcons) :
JobsHelper.IconIDForJob(Member.JobId, (uint)RoleIcon.Style);
}
if (iconId > 0)
{
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, RoleIcon.FrameAnchor);
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + RoleIcon.Position, RoleIcon.Size, RoleIcon.Anchor);
drawActions.Add((RoleIcon.StrataLevel, () =>
{
DrawHelper.DrawInWindow(RoleIcon.ID, iconPos, RoleIcon.Size, false, (drawList) =>
{
DrawHelper.DrawIcon(iconId, iconPos, RoleIcon.Size, false, drawList);
});
}
));
}
}
// sign icon
if (SignIcon.Enabled)
{
uint? iconId = SignIcon.IconID(character);
if (iconId.HasValue)
{
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, SignIcon.FrameAnchor);
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + SignIcon.Position, SignIcon.Size, SignIcon.Anchor);
drawActions.Add((SignIcon.StrataLevel, () =>
{
DrawHelper.DrawInWindow(SignIcon.ID, iconPos, SignIcon.Size, false, (drawList) =>
{
DrawHelper.DrawIcon(iconId.Value, iconPos, SignIcon.Size, false, drawList);
});
}
));
}
}
// leader icon
if (LeaderIcon.Enabled && Member.IsPartyLeader)
{
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, LeaderIcon.FrameAnchor);
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + LeaderIcon.Position, LeaderIcon.Size, LeaderIcon.Anchor);
drawActions.Add((LeaderIcon.StrataLevel, () =>
{
DrawHelper.DrawInWindow(LeaderIcon.ID, iconPos, LeaderIcon.Size, false, (drawList) =>
{
DrawHelper.DrawIcon(61521, iconPos, LeaderIcon.Size, false, drawList);
});
}
));
}
// player status icon
if (PlayerStatus.Enabled && PlayerStatus.Icon.Enabled)
{
uint? iconId = IconIdForStatus(Member.Status);
if (iconId.HasValue)
{
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, PlayerStatus.Icon.FrameAnchor);
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + PlayerStatus.Icon.Position, PlayerStatus.Icon.Size, PlayerStatus.Icon.Anchor);
drawActions.Add((PlayerStatus.Icon.StrataLevel, () =>
{
DrawHelper.DrawInWindow(PlayerStatus.Icon.ID, iconPos, PlayerStatus.Icon.Size, false, (drawList) =>
{
DrawHelper.DrawIcon(iconId.Value, iconPos, PlayerStatus.Icon.Size, false, drawList);
});
}
));
}
}
// ready check status icon
if (Member.ReadyCheckStatus != ReadyCheckStatus.None && ReadyCheckIcon.Enabled && ReadyCheckIcon.Icon.Enabled && _readyCheckTexture != null)
{
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, ReadyCheckIcon.Icon.FrameAnchor);
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + ReadyCheckIcon.Icon.Position, ReadyCheckIcon.Icon.Size, ReadyCheckIcon.Icon.Anchor);
drawActions.Add((ReadyCheckIcon.Icon.StrataLevel, () =>
{
DrawHelper.DrawInWindow(ReadyCheckIcon.Icon.ID, iconPos, ReadyCheckIcon.Icon.Size, false, (drawList) =>
{
Vector2 uv0 = new Vector2(0.5f * (int)Member.ReadyCheckStatus, 0f);
Vector2 uv1 = new Vector2(0.5f + 0.5f * (int)Member.ReadyCheckStatus, 1f);
drawList.AddImage(_readyCheckTexture.Handle, iconPos, iconPos + ReadyCheckIcon.Icon.Size, uv0, uv1);
});
}
));
}
// raise icon
bool showingRaise = ShowingRaise();
if (showingRaise && RaiseTracker.Icon.Enabled)
{
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, RaiseTracker.Icon.FrameAnchor);
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + RaiseTracker.Icon.Position, RaiseTracker.Icon.Size, RaiseTracker.Icon.Anchor);
drawActions.Add((RaiseTracker.Icon.StrataLevel, () =>
{
DrawHelper.DrawInWindow(RaiseTracker.Icon.ID, iconPos, RaiseTracker.Icon.Size, false, (drawList) =>
{
DrawHelper.DrawIcon(411, iconPos, RaiseTracker.Icon.Size, true, drawList);
});
}
));
}
// invuln icon
bool showingInvuln = ShowingInvuln();
if (showingInvuln && InvulnTracker.Icon.Enabled)
{
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, InvulnTracker.Icon.FrameAnchor);
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + InvulnTracker.Icon.Position, InvulnTracker.Icon.Size, InvulnTracker.Icon.Anchor);
drawActions.Add((InvulnTracker.Icon.StrataLevel, () =>
{
DrawHelper.DrawInWindow(InvulnTracker.Icon.ID, iconPos, InvulnTracker.Icon.Size, false, (drawList) =>
{
DrawHelper.DrawIcon(Member.InvulnStatus!.InvulnIcon, iconPos, InvulnTracker.Icon.Size, true, drawList);
});
}
));
}
// mana
if (ShowMana())
{
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.ManaBar.HealthBarAnchor);
drawActions.Add((_configs.ManaBar.StrataLevel, () =>
{
_manaBarHud.Actor = character;
_manaBarHud.PartyMember = Member;
_manaBarHud.PrepareForDraw(parentPos);
_manaBarHud.Draw(parentPos);
}
));
}
// buffs / debuffs
Vector2 buffsPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.Buffs.HealthBarAnchor);
drawActions.Add((_configs.Buffs.StrataLevel, () =>
{
_buffsListHud.Actor = character;
_buffsListHud.PrepareForDraw(buffsPos);
_buffsListHud.Draw(buffsPos);
}
));
Vector2 debuffsPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.Debuffs.HealthBarAnchor);
drawActions.Add((_configs.Debuffs.StrataLevel, () =>
{
_debuffsListHud.Actor = character;
_debuffsListHud.PrepareForDraw(debuffsPos);
_debuffsListHud.Draw(debuffsPos);
}
));
// cooldown list
Vector2 cooldownListPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.CooldownList.HealthBarAnchor);
drawActions.Add((_configs.CooldownList.StrataLevel, () =>
{
_cooldownListHud.Actor = character;
_cooldownListHud.PrepareForDraw(cooldownListPos);
_cooldownListHud.Draw(cooldownListPos);
}
));
// castbar
Vector2 castbarPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.CastBar.HealthBarAnchor);
drawActions.Add((_configs.CastBar.StrataLevel, () =>
{
_castbarHud.Actor = character;
_castbarHud.PrepareForDraw(castbarPos);
_castbarHud.Draw(castbarPos);
}
));
// name
bool drawName = ShouldDrawName(character, showingRaise, showingInvuln);
if (drawName)
{
drawActions.Add((_configs.HealthBar.NameLabelConfig.StrataLevel, () =>
{
bool? playerName = null;
if (character == null || character.ObjectKind == ObjectKind.Player)
{
playerName = true;
}
_nameLabelHud.Draw(Position, _configs.HealthBar.Size, character, Member.Name, isPlayerName: playerName);
}
));
}
// health label
if (Member.MaxHP > 0)
{
drawActions.Add((_configs.HealthBar.HealthLabelConfig.StrataLevel, () =>
{
_healthLabelHud.Draw(Position, _configs.HealthBar.Size, character, null, Member.HP, Member.MaxHP);
}
));
}
// order
if (character == null || character?.ObjectKind != ObjectKind.BattleNpc)
{
string str = char.ConvertFromUtf32(0xE090 + Member.Order).ToString();
drawActions.Add((_configs.HealthBar.OrderNumberConfig.StrataLevel, () =>
{
_configs.HealthBar.OrderNumberConfig.SetText(str);
_orderLabelHud.Draw(Position, _configs.HealthBar.Size, character);
}
));
}
// status
string? statusString = StringForStatus(Member.Status);
if (PlayerStatus.Enabled && PlayerStatus.Label.Enabled && statusString != null)
{
drawActions.Add((PlayerStatus.Label.StrataLevel, () =>
{
PlayerStatus.Label.SetText(statusString);
_statusLabelHud.Draw(Position, _configs.HealthBar.Size);
}
));
}
// raise label
if (showingRaise)
{
float duration = Math.Abs(Member.RaiseTime!.Value);
drawActions.Add((RaiseTracker.Icon.NumericLabel.StrataLevel, () =>
{
RaiseTracker.Icon.NumericLabel.SetValue(duration);
_raiseLabelHud.Draw(Position, _configs.HealthBar.Size);
}
));
}
// invuln label
if (showingInvuln)
{
float duration = Math.Abs(Member.InvulnStatus!.InvulnTime);
drawActions.Add((InvulnTracker.Icon.NumericLabel.StrataLevel, () =>
{
InvulnTracker.Icon.NumericLabel.SetValue(duration);
_invulnLabelHud.Draw(Position, _configs.HealthBar.Size);
}
));
}
return drawActions;
}
private bool ShouldDrawName(ICharacter? character, bool showingRaise, bool showingInvuln)
{
if (showingRaise && RaiseTracker.HideNameWhenRaised)
{
return false;
}
if (showingInvuln && InvulnTracker.HideNameWhenInvuln)
{
return false;
}
if (Member != null && PlayerStatus.Enabled && PlayerStatus.HideName && Member.Status != PartyMemberStatus.None)
{
return false;
}
if (Member != null && ReadyCheckIcon.Enabled && ReadyCheckIcon.HideName && Member.ReadyCheckStatus != ReadyCheckStatus.None)
{
return false;
}
if (Utils.IsActorCasting(character) && _configs.CastBar.Enabled && _configs.CastBar.HideNameWhenCasting)
{
return false;
}
return true;
}
private bool ShowingRaise() =>
Member != null && Member.RaiseTime.HasValue && RaiseTracker.Enabled &&
(Member.RaiseTime.Value > 0 || RaiseTracker.KeepIconAfterCastFinishes);
private bool ShowingInvuln() => Member != null && Member.InvulnStatus != null && InvulnTracker.Enabled && Member.InvulnStatus.InvulnTime > 0;
private bool ShowMana()
{
if (Member == null)
{
return false;
}
var isHealer = JobsHelper.IsJobHealer(Member.JobId);
return _configs.ManaBar.Enabled && Member.MaxHP > 0 && _configs.ManaBar.ManaBarDisplayMode switch
{
PartyFramesManaBarDisplayMode.Always => true,
PartyFramesManaBarDisplayMode.HealersOnly => isHealer,
PartyFramesManaBarDisplayMode.HealersAndRaiseJobs => isHealer || JobsHelper.IsJobWithRaise(Member.JobId, Member.Level),
_ => true
};
}
private static uint? IconIdForStatus(PartyMemberStatus status)
{
return status switch
{
PartyMemberStatus.ViewingCutscene => 61508,
PartyMemberStatus.Offline => 61504,
PartyMemberStatus.Dead => 61502,
_ => null
};
}
private static string? StringForStatus(PartyMemberStatus status)
{
return status switch
{
PartyMemberStatus.ViewingCutscene => "[Viewing Cutscene]",
PartyMemberStatus.Offline => "[Offline]",
PartyMemberStatus.Dead => "[Dead]",
_ => null
};
}
#region convenience
private PartyFramesRoleIconConfig RoleIcon => _configs.Icons.Role;
private SignIconConfig SignIcon => _configs.Icons.Sign;
private PartyFramesLeaderIconConfig LeaderIcon => _configs.Icons.Leader;
private PartyFramesPlayerStatusConfig PlayerStatus => _configs.Icons.PlayerStatus;
private PartyFramesReadyCheckStatusConfig ReadyCheckIcon => _configs.Icons.ReadyCheckStatus;
private PartyFramesWhosTalkingConfig WhosTalkingIcon => _configs.Icons.WhosTalking;
private PartyFramesRaiseTrackerConfig RaiseTracker => _configs.Trackers.Raise;
private PartyFramesInvulnTrackerConfig InvulnTracker => _configs.Trackers.Invuln;
private PartyFramesCleanseTrackerConfig CleanseTracker => _configs.Trackers.Cleanse;
#endregion
}
}
@@ -0,0 +1,80 @@
using Dalamud.Game.ClientState.Objects.Types;
using HSUI.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Helpers;
using Dalamud.Game.ClientState.Statuses;
namespace HSUI.Interface.Party
{
public class PartyFramesCleanseTracker : IDisposable
{
private PartyFramesCleanseTrackerConfig _config = null!;
public PartyFramesCleanseTracker()
{
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
OnConfigReset(ConfigurationManager.Instance);
}
~PartyFramesCleanseTracker()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
}
public void OnConfigReset(ConfigurationManager sender)
{
_config = ConfigurationManager.Instance.GetConfigObject<PartyFramesTrackersConfig>().Cleanse;
}
public void Update(List<IPartyFramesMember> partyMembers)
{
if (!_config.Enabled)
{
return;
}
foreach (var member in partyMembers)
{
member.HasDispellableDebuff = false;
if (member.Character is not IBattleChara battleChara)
{
continue;
}
// check for disspellable debuff
IEnumerable<IStatus> statusList = Utils.StatusListForBattleChara(battleChara);
foreach (IStatus status in statusList)
{
if (!status.GameData.Value.CanDispel)
{
continue;
}
// apply raise data based on buff
member.HasDispellableDebuff = true;
break;
}
}
}
}
}
+852
View File
@@ -0,0 +1,852 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.PartyCooldowns;
using HSUI.Interface.StatusEffects;
using Dalamud.Bindings.ImGui;
using System;
using System.Numerics;
namespace HSUI.Interface.Party
{
[Exportable(false)]
[Section("Party Frames", true)]
[SubSection("General", 0)]
public class PartyFramesConfig : MovablePluginConfigObject
{
public new static PartyFramesConfig DefaultConfig()
{
var config = new PartyFramesConfig();
config.Position = new Vector2(-ImGui.GetMainViewport().Size.X / 3 - 180, -120);
return config;
}
[Checkbox("Preview", isMonitored = true)]
[Order(4)]
public bool Preview = false;
[DragInt("Rows", spacing = true, isMonitored = true, min = 1, max = 8, velocity = 0.2f)]
[Order(10)]
public int Rows = 4;
[DragInt("Columns", isMonitored = true, min = 1, max = 8, velocity = 0.2f)]
[Order(11)]
public int Columns = 2;
[Anchor("Bars Anchor", isMonitored = true, spacing = true)]
[Order(15)]
public DrawAnchor BarsAnchor = DrawAnchor.TopLeft;
[Checkbox("Fill Rows First", isMonitored = true)]
[Order(20)]
public bool FillRowsFirst = true;
[Checkbox("Show When Solo", spacing = true)]
[Order(50)]
public bool ShowWhenSolo = false;
[Checkbox("Show Chocobo", isMonitored = true)]
[Order(55)]
public bool ShowChocobo = true;
[NestedConfig("Party Title Label", 60)]
public PartyFramesTitleLabel ShowPartyTitleConfig = new PartyFramesTitleLabel(Vector2.Zero, "", DrawAnchor.Left, DrawAnchor.Left);
[NestedConfig("Visibility", 200)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
}
[Exportable(false)]
[DisableParentSettings("FrameAnchor", "UseJobColor", "UseRoleColor")]
public class PartyFramesTitleLabel : LabelConfig
{
public PartyFramesTitleLabel(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor) : base(position, text, frameAnchor, textAnchor)
{
}
}
[Exportable(false)]
[Disableable(false)]
[DisableParentSettings("Position", "Anchor", "BackgroundColor", "FillColor", "HideWhenInactive", "DrawBorder", "BorderColor", "BorderThickness")]
[Section("Party Frames", true)]
[SubSection("Health Bar", 0)]
public class PartyFramesHealthBarsConfig : BarConfig
{
public new static PartyFramesHealthBarsConfig DefaultConfig()
{
var config = new PartyFramesHealthBarsConfig(Vector2.Zero, new(180, 80), PluginConfigColor.Empty);
config.MouseoverAreaConfig.Enabled = false;
return config;
}
[DragInt2("Padding", isMonitored = true, min = 0)]
[Order(31)]
public Vector2 Padding = new Vector2(0, 0);
[NestedConfig("Name Label", 44)]
public EditableLabelConfig NameLabelConfig = new EditableLabelConfig(Vector2.Zero, "[name:initials].", DrawAnchor.Center, DrawAnchor.Center);
[NestedConfig("Health Label", 45)]
public EditableLabelConfig HealthLabelConfig = new EditableLabelConfig(Vector2.Zero, "[health:current-short]", DrawAnchor.Right, DrawAnchor.Right);
[NestedConfig("Order Label", 50)]
public DefaultFontLabelConfig OrderNumberConfig = new DefaultFontLabelConfig(new Vector2(2, 4), "", DrawAnchor.TopLeft, DrawAnchor.TopLeft);
[NestedConfig("Colors", 55)]
public PartyFramesColorsConfig ColorsConfig = new PartyFramesColorsConfig();
[NestedConfig("Shield", 60)]
public ShieldConfig ShieldConfig = new ShieldConfig();
[NestedConfig("Change Alpha Based on Range", 65)]
public PartyFramesRangeConfig RangeConfig = new PartyFramesRangeConfig();
[NestedConfig("Use Smooth Transitions", 70)]
public SmoothHealthConfig SmoothHealthConfig = new SmoothHealthConfig();
[NestedConfig("Custom Mouseover Area", 75)]
public MouseoverAreaConfig MouseoverAreaConfig = new MouseoverAreaConfig();
public PartyFramesHealthBarsConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
: base(position, size, fillColor, fillDirection)
{
}
}
[Disableable(false)]
[Exportable(false)]
public class PartyFramesColorsConfig : PluginConfigObject
{
[Checkbox("Show Border")]
[Order(4)]
public bool ShowBorder = true;
[ColorEdit4("Border Color")]
[Order(5, collapseWith = nameof(ShowBorder))]
public PluginConfigColor BorderColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Target Border Color")]
[Order(6, collapseWith = nameof(ShowBorder))]
public PluginConfigColor TargetBordercolor = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
[DragInt("Inactive Border Thickness", min = 1, max = 10, help = "This is the border thickness that will be used when the border is in the default state (aka not targetted, not showing enmity, etc).")]
[Order(6, collapseWith = nameof(ShowBorder))]
public int InactiveBorderThickness = 1;
[DragInt("Active Border Thickness", min = 1, max = 10, help = "This is the border thickness that will be used when the border active (aka targetted, showing enmity, etc).")]
[Order(7, collapseWith = nameof(ShowBorder))]
public int ActiveBorderThickness = 1;
[ColorEdit4("Background Color", spacing = true)]
[Order(15)]
public PluginConfigColor BackgroundColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 70f / 100f));
[ColorEdit4("Out of Reach Background Color", help = "This background color will be used when the player's data couldn't be retreived (i.e. player is disconnected)")]
[Order(15)]
public PluginConfigColor OutOfReachBackgroundColor = new PluginConfigColor(new Vector4(50f / 255f, 50f / 255f, 50f / 255f, 70f / 100f));
[Checkbox("Use Death Indicator Background Color", isMonitored = true, spacing = true)]
[Order(18)]
public bool UseDeathIndicatorBackgroundColor = false;
[ColorEdit4("Death Indicator Background Color")]
[Order(19, collapseWith = nameof(UseDeathIndicatorBackgroundColor))]
public PluginConfigColor DeathIndicatorBackgroundColor = new PluginConfigColor(new Vector4(204f / 255f, 3f / 255f, 3f / 255f, 80f / 100f));
[Checkbox("Use Role Colors", isMonitored = true, spacing = true)]
[Order(20)]
public bool UseRoleColors = false;
[NestedConfig("Color Based On Health Value", 30, collapsingHeader = false)]
public ColorByHealthValueConfig ColorByHealth = new ColorByHealthValueConfig();
[Checkbox("Highlight When Hovering With Cursor Or Soft Targeting", spacing = true)]
[Order(40)]
public bool ShowHighlight = true;
[ColorEdit4("Highlight Color")]
[Order(45, collapseWith = nameof(ShowHighlight))]
public PluginConfigColor HighlightColor = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 5f / 100f));
[Checkbox("Missing Health Color", spacing = true)]
[Order(46)]
public bool UseMissingHealthBar = false;
[Checkbox("Job Color As Missing Health Color")]
[Order(47, collapseWith = nameof(UseMissingHealthBar))]
public bool UseJobColorAsMissingHealthColor = false;
[Checkbox("Role Color As Missing Health Color")]
[Order(48, collapseWith = nameof(UseMissingHealthBar))]
public bool UseRoleColorAsMissingHealthColor = false;
[ColorEdit4("Color" + "##MissingHealth")]
[Order(49, collapseWith = nameof(UseMissingHealthBar))]
public PluginConfigColor HealthMissingColor = new(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[Checkbox("Job Color As Background Color")]
[Order(50)]
public bool UseJobColorAsBackgroundColor = false;
[Checkbox("Role Color As Background Color")]
[Order(51)]
public bool UseRoleColorAsBackgroundColor = false;
[Checkbox("Show Enmity Border Colors", spacing = true)]
[Order(54)]
public bool ShowEnmityBorderColors = true;
[ColorEdit4("Enmity Leader Color")]
[Order(55, collapseWith = nameof(ShowEnmityBorderColors))]
public PluginConfigColor EnmityLeaderBordercolor = new PluginConfigColor(new Vector4(255f / 255f, 40f / 255f, 40f / 255f, 100f / 100f));
[Checkbox("Show Second Enmity")]
[Order(60, collapseWith = nameof(ShowEnmityBorderColors))]
public bool ShowSecondEnmity = true;
[Checkbox("Hide Second Enmity in Light Parties")]
[Order(65, collapseWith = nameof(ShowSecondEnmity))]
public bool HideSecondEnmityInLightParties = true;
[ColorEdit4("Enmity Second Color")]
[Order(70, collapseWith = nameof(ShowSecondEnmity))]
public PluginConfigColor EnmitySecondBordercolor = new PluginConfigColor(new Vector4(255f / 255f, 175f / 255f, 40f / 255f, 100f / 100f));
}
[Exportable(false)]
public class PartyFramesRangeConfig : PluginConfigObject
{
[DragInt("Range (yalms)", min = 1, max = 500)]
[Order(5)]
public int Range = 30;
[DragFloat("Alpha", min = 1, max = 100)]
[Order(10)]
public float Alpha = 25;
[Checkbox("Use Additional Range Check")]
[Order(15)]
public bool UseAdditionalRangeCheck = false;
[DragInt("Additional Range (yalms)", min = 1, max = 500)]
[Order(20, collapseWith = nameof(UseAdditionalRangeCheck))]
public int AdditionalRange = 15;
[DragFloat("Additional Alpha", min = 1, max = 100)]
[Order(25, collapseWith = nameof(UseAdditionalRangeCheck))]
public float AdditionalAlpha = 60;
public float AlphaForDistance(int distance, float alpha = 100f)
{
if (!Enabled)
{
return 100f;
}
if (!UseAdditionalRangeCheck)
{
return distance > Range ? Alpha : alpha;
}
if (Range > AdditionalRange)
{
return distance > Range ? Alpha : (distance > AdditionalRange ? AdditionalAlpha : alpha);
}
return distance > AdditionalRange ? AdditionalAlpha : (distance > Range ? Alpha : alpha);
}
}
public class PartyFramesManaBarConfigConverter : PluginConfigObjectConverter
{
public PartyFramesManaBarConfigConverter()
{
NewTypeFieldConverter<bool, PartyFramesManaBarDisplayMode> converter;
converter = new NewTypeFieldConverter<bool, PartyFramesManaBarDisplayMode>(
"PartyFramesManaBarDisplayMode",
PartyFramesManaBarDisplayMode.HealersOnly,
(oldValue) =>
{
return oldValue ? PartyFramesManaBarDisplayMode.HealersOnly : PartyFramesManaBarDisplayMode.Always;
});
FieldConvertersMap.Add("ShowOnlyForHealers", converter);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(PartyFramesManaBarConfig);
}
}
public enum PartyFramesManaBarDisplayMode
{
HealersAndRaiseJobs,
HealersOnly,
Always,
}
[DisableParentSettings("HideWhenInactive", "Label")]
[Exportable(false)]
[Section("Party Frames", true)]
[SubSection("Mana Bar", 0)]
public class PartyFramesManaBarConfig : PrimaryResourceConfig
{
public new static PartyFramesManaBarConfig DefaultConfig()
{
var config = new PartyFramesManaBarConfig(Vector2.Zero, new(180, 6));
config.HealthBarAnchor = DrawAnchor.Bottom;
config.Anchor = DrawAnchor.Bottom;
config.ValueLabel.Enabled = false;
return config;
}
[Anchor("Health Bar Anchor")]
[Order(14)]
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
[RadioSelector("Show For All Jobs With Raise", "Show Only For Healers", "Show For All Jobs")]
[Order(42)]
public PartyFramesManaBarDisplayMode ManaBarDisplayMode = PartyFramesManaBarDisplayMode.HealersOnly;
public PartyFramesManaBarConfig(Vector2 position, Vector2 size)
: base(position, size)
{
}
}
[Exportable(false)]
[Section("Party Frames", true)]
[SubSection("Castbar", 0)]
public class PartyFramesCastbarConfig : CastbarConfig
{
public new static PartyFramesCastbarConfig DefaultConfig()
{
var size = new Vector2(182, 10);
var pos = new Vector2(-1, 0);
var castNameConfig = new LabelConfig(new Vector2(5, 0), "", DrawAnchor.Left, DrawAnchor.Left);
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
castTimeConfig.Enabled = false;
castTimeConfig.NumberFormat = 1;
var config = new PartyFramesCastbarConfig(pos, size, castNameConfig, castTimeConfig);
config.HealthBarAnchor = DrawAnchor.BottomLeft;
config.Anchor = DrawAnchor.TopLeft;
config.ShowIcon = false;
config.Enabled = false;
return config;
}
[Checkbox("Hide Name When Casting")]
[Order(6)]
public bool HideNameWhenCasting = false;
[Anchor("Health Bar Anchor")]
[Order(16)]
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
public PartyFramesCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
: base(position, size, castNameConfig, castTimeConfig)
{
}
}
[Disableable(false)]
[Exportable(false)]
[Section("Party Frames", true)]
[SubSection("Icons", 0)]
public class PartyFramesIconsConfig : PluginConfigObject
{
public new static PartyFramesIconsConfig DefaultConfig() { return new PartyFramesIconsConfig(); }
[NestedConfig("Role / Job", 10, separator = false)]
public PartyFramesRoleIconConfig Role = new PartyFramesRoleIconConfig(
new Vector2(20, 0),
new Vector2(20, 20),
DrawAnchor.TopLeft,
DrawAnchor.TopLeft
);
[NestedConfig("Sign", 11)]
public SignIconConfig Sign = new SignIconConfig(
new Vector2(0, -10),
new Vector2(30, 30),
DrawAnchor.Top,
DrawAnchor.Top
);
[NestedConfig("Leader", 12)]
public PartyFramesLeaderIconConfig Leader = new PartyFramesLeaderIconConfig(
new Vector2(-12, -12),
new Vector2(24, 24),
DrawAnchor.TopLeft,
DrawAnchor.TopLeft
);
[NestedConfig("Player Status", 13)]
public PartyFramesPlayerStatusConfig PlayerStatus = new PartyFramesPlayerStatusConfig();
[NestedConfig("Ready Check Status", 14)]
public PartyFramesReadyCheckStatusConfig ReadyCheckStatus = new PartyFramesReadyCheckStatusConfig();
[NestedConfig("Who's Talking", 15)]
public PartyFramesWhosTalkingConfig WhosTalking = new PartyFramesWhosTalkingConfig();
}
[Exportable(false)]
public class PartyFramesRoleIconConfig : RoleJobIconConfig
{
public PartyFramesRoleIconConfig() : base() { }
public PartyFramesRoleIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
}
}
[Exportable(false)]
public class PartyFramesLeaderIconConfig : IconConfig
{
public PartyFramesLeaderIconConfig() : base() { }
public PartyFramesLeaderIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
: base(position, size, anchor, frameAnchor)
{
}
}
[Exportable(false)]
public class PartyFramesPlayerStatusConfig : PluginConfigObject
{
public new static PartyFramesPlayerStatusConfig DefaultConfig()
{
var config = new PartyFramesPlayerStatusConfig();
config.Label.Enabled = false;
return config;
}
[Checkbox("Hide Name When Showing Status")]
[Order(5)]
public bool HideName = false;
[NestedConfig("Icon", 10)]
public IconConfig Icon = new IconConfig(
new Vector2(0, 5),
new Vector2(16, 16),
DrawAnchor.Top,
DrawAnchor.Top
);
[NestedConfig("Label", 15)]
public LabelConfig Label = new LabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
}
[Exportable(false)]
public class PartyFramesReadyCheckStatusConfig : PluginConfigObject
{
public new static PartyFramesReadyCheckStatusConfig DefaultConfig() => new PartyFramesReadyCheckStatusConfig();
[Checkbox("Hide Name When Showing Status")]
[Order(5)]
public bool HideName = false;
[DragInt("Duration (seconds)", min = 1, max = 60, help = "Determines for how long the icons will show after a ready check is finished.")]
[Order(6)]
public int Duration = 10;
[NestedConfig("Icon", 10)]
public IconConfig Icon = new IconConfig(
new Vector2(0, 0),
new Vector2(24, 24),
DrawAnchor.TopRight,
DrawAnchor.TopRight
);
}
[Exportable(false)]
public class PartyFramesWhosTalkingConfig : PluginConfigObject
{
public new static PartyFramesWhosTalkingConfig DefaultConfig() => new PartyFramesWhosTalkingConfig();
[Checkbox("Replace Role/Job Icon when active")]
[Order(5)]
public bool ReplaceRoleJobIcon = false;
[Checkbox("Show Speaking State", spacing = true)]
[Order(10)]
public bool ShowSpeaking = true;
[Checkbox("Show Muted State")]
[Order(10)]
public bool ShowMuted = true;
[Checkbox("Show Deafened State")]
[Order(10)]
public bool ShowDeafened = true;
[NestedConfig("Icon", 20)]
public IconConfig Icon = new IconConfig(
new Vector2(0, 0),
new Vector2(24, 24),
DrawAnchor.TopRight,
DrawAnchor.TopRight
);
[Checkbox("Change Health Bar Border when active", spacing = true, help = "Enabling this will override other border settings!")]
[Order(30)]
public bool ChangeBorders = false;
[DragInt("Border Thickness", min = 1, max = 10)]
[Order(31, collapseWith = nameof(ChangeBorders))]
public int BorderThickness = 1;
[ColorEdit4("Speaking Border Color")]
[Order(32, collapseWith = nameof(ChangeBorders))]
public PluginConfigColor SpeakingBorderColor = PluginConfigColor.FromHex(0xFF40BB40);
[ColorEdit4("Muted Border Color")]
[Order(33, collapseWith = nameof(ChangeBorders))]
public PluginConfigColor MutedBorderColor = PluginConfigColor.FromHex(0xFF008080);
[ColorEdit4("Deafened Border Color")]
[Order(34, collapseWith = nameof(ChangeBorders))]
public PluginConfigColor DeafenedBorderColor = PluginConfigColor.FromHex(0xFFFF4444);
public bool EnabledForState(WhosTalkingState state)
{
switch (state)
{
case WhosTalkingState.Speaking: return ShowSpeaking;
case WhosTalkingState.Muted: return ShowMuted;
case WhosTalkingState.Deafened: return ShowDeafened;
}
return false;
}
public PluginConfigColor? ColorForState(WhosTalkingState state)
{
if (state == WhosTalkingState.Speaking && ShowSpeaking) { return SpeakingBorderColor; }
if (state == WhosTalkingState.Muted && ShowMuted) { return MutedBorderColor; }
if (ShowDeafened) { return DeafenedBorderColor; }
return null;
}
}
[Exportable(false)]
[Section("Party Frames", true)]
[SubSection("Buffs", 0)]
public class PartyFramesBuffsConfig : PartyFramesStatusEffectsListConfig
{
public new static PartyFramesBuffsConfig DefaultConfig()
{
var durationConfig = new LabelConfig(new Vector2(0, -4), "", DrawAnchor.Bottom, DrawAnchor.Center);
var stacksConfig = new LabelConfig(new Vector2(-3, 4), "", DrawAnchor.TopRight, DrawAnchor.Center);
stacksConfig.Color = new(Vector4.UnitW);
stacksConfig.OutlineColor = new(Vector4.One);
var iconConfig = new StatusEffectIconConfig(durationConfig, stacksConfig);
iconConfig.DispellableBorderConfig.Enabled = false;
iconConfig.Size = new Vector2(24, 24);
var pos = new Vector2(-2, 2);
var size = new Vector2(iconConfig.Size.X * 4 + 6, iconConfig.Size.Y);
var config = new PartyFramesBuffsConfig(DrawAnchor.TopRight, pos, size, true, false, false, GrowthDirections.Left | GrowthDirections.Down, iconConfig);
config.Limit = 4;
return config;
}
public PartyFramesBuffsConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(anchor, position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
[Exportable(false)]
[Section("Party Frames", true)]
[SubSection("Debuffs", 0)]
public class PartyFramesDebuffsConfig : PartyFramesStatusEffectsListConfig
{
public new static PartyFramesDebuffsConfig DefaultConfig()
{
var durationConfig = new LabelConfig(new Vector2(0, -4), "", DrawAnchor.Bottom, DrawAnchor.Center);
var stacksConfig = new LabelConfig(new Vector2(-3, 4), "", DrawAnchor.TopRight, DrawAnchor.Center);
stacksConfig.Color = new(Vector4.UnitW);
stacksConfig.OutlineColor = new(Vector4.One);
var iconConfig = new StatusEffectIconConfig(durationConfig, stacksConfig);
iconConfig.Size = new Vector2(24, 24);
var pos = new Vector2(-2, -2);
var size = new Vector2(iconConfig.Size.X * 4 + 6, iconConfig.Size.Y);
var config = new PartyFramesDebuffsConfig(DrawAnchor.BottomRight, pos, size, false, true, false, GrowthDirections.Left | GrowthDirections.Up, iconConfig);
config.Limit = 4;
return config;
}
public PartyFramesDebuffsConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(anchor, position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
public class PartyFramesStatusEffectsListConfig : StatusEffectsListConfig
{
[Anchor("Health Bar Anchor")]
[Order(4)]
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
public PartyFramesStatusEffectsListConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
HealthBarAnchor = anchor;
}
}
[Disableable(false)]
[Exportable(false)]
[Section("Party Frames", true)]
[SubSection("Trackers", 0)]
public class PartyFramesTrackersConfig : PluginConfigObject
{
public new static PartyFramesTrackersConfig DefaultConfig() { return new PartyFramesTrackersConfig(); }
[NestedConfig("Raise Tracker", 10, separator = false)]
public PartyFramesRaiseTrackerConfig Raise = new PartyFramesRaiseTrackerConfig();
[NestedConfig("Invulnerabilities Tracker", 15)]
public PartyFramesInvulnTrackerConfig Invuln = new PartyFramesInvulnTrackerConfig();
[NestedConfig("Cleanse Tracker", 15)]
public PartyFramesCleanseTrackerConfig Cleanse = new PartyFramesCleanseTrackerConfig();
}
[Exportable(false)]
public class PartyFramesRaiseTrackerConfig : PluginConfigObject
{
public new static PartyFramesRaiseTrackerConfig DefaultConfig() { return new PartyFramesRaiseTrackerConfig(); }
[Checkbox("Hide Name When Raised")]
[Order(10)]
public bool HideNameWhenRaised = true;
[Checkbox("Keep Icon After Cast Finishes")]
[Order(15)]
public bool KeepIconAfterCastFinishes = true;
[Checkbox("Change Background Color When Raised", spacing = true)]
[Order(20)]
public bool ChangeBackgroundColorWhenRaised = true;
[ColorEdit4("Raise Background Color")]
[Order(25, collapseWith = nameof(ChangeBackgroundColorWhenRaised))]
public PluginConfigColor BackgroundColor = new(new Vector4(211f / 255f, 235f / 255f, 215f / 245f, 50f / 100f));
[Checkbox("Change Border Color When Raised", spacing = true)]
[Order(30)]
public bool ChangeBorderColorWhenRaised = true;
[ColorEdit4("Raise Border Color")]
[Order(35, collapseWith = nameof(ChangeBorderColorWhenRaised))]
public PluginConfigColor BorderColor = new(new Vector4(47f / 255f, 169f / 255f, 215f / 255f, 100f / 100f));
[NestedConfig("Icon", 50)]
public IconWithLabelConfig Icon = new IconWithLabelConfig(
new Vector2(0, 0),
new Vector2(50, 50),
DrawAnchor.Center,
DrawAnchor.Center
);
}
[Exportable(false)]
public class PartyFramesInvulnTrackerConfig : PluginConfigObject
{
public new static PartyFramesInvulnTrackerConfig DefaultConfig() { return new PartyFramesInvulnTrackerConfig(); }
[Checkbox("Hide Name When Invuln is Up")]
[Order(10)]
public bool HideNameWhenInvuln = true;
[Checkbox("Change Background Color When Invuln is Up", spacing = true)]
[Order(15)]
public bool ChangeBackgroundColorWhenInvuln = true;
[ColorEdit4("Invuln Background Color")]
[Order(20, collapseWith = nameof(ChangeBackgroundColorWhenInvuln))]
public PluginConfigColor BackgroundColor = new(new Vector4(211f / 255f, 235f / 255f, 215f / 245f, 50f / 100f));
[Checkbox("Walking Dead Custom Color")]
[Order(25, collapseWith = nameof(ChangeBackgroundColorWhenInvuln))]
public bool UseCustomWalkingDeadColor = true;
[ColorEdit4("Walking Dead Background Color")]
[Order(30, collapseWith = nameof(UseCustomWalkingDeadColor))]
public PluginConfigColor WalkingDeadBackgroundColor = new(new Vector4(158f / 255f, 158f / 255f, 158f / 255f, 50f / 100f));
[NestedConfig("Icon", 50)]
public IconWithLabelConfig Icon = new IconWithLabelConfig(
new Vector2(0, 0),
new Vector2(50, 50),
DrawAnchor.Center,
DrawAnchor.Center
);
}
public class PartyFramesTrackerConfigConverter : PluginConfigObjectConverter
{
public PartyFramesTrackerConfigConverter()
{
SameTypeFieldConverter<Vector2> pos = new SameTypeFieldConverter<Vector2>("Icon.Position", Vector2.Zero);
FieldConvertersMap.Add("Position", pos);
SameTypeFieldConverter<Vector2> size = new SameTypeFieldConverter<Vector2>("Icon.Size", new Vector2(50, 50));
FieldConvertersMap.Add("IconSize", size);
SameTypeFieldConverter<DrawAnchor> anchor = new SameTypeFieldConverter<DrawAnchor>("Icon.Anchor", DrawAnchor.Center);
FieldConvertersMap.Add("Anchor", anchor);
SameTypeFieldConverter<DrawAnchor> frameAnchor = new SameTypeFieldConverter<DrawAnchor>("Icon.FrameAnchor", DrawAnchor.Center);
FieldConvertersMap.Add("HealthBarAnchor", frameAnchor);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(PartyFramesRaiseTrackerConfig) ||
objectType == typeof(PartyFramesInvulnTrackerConfig);
}
}
[DisableParentSettings("Position", "Strata")]
[Exportable(false)]
public class PartyFramesCleanseTrackerConfig : MovablePluginConfigObject
{
public new static PartyFramesCleanseTrackerConfig DefaultConfig() { return new PartyFramesCleanseTrackerConfig(); }
[Checkbox("Show only on jobs with cleanses", spacing = true)]
[Order(10)]
public bool CleanseJobsOnly = true;
[Checkbox("Change Health Bar Color ", spacing = true)]
[Order(15)]
public bool ChangeHealthBarCleanseColor = true;
[ColorEdit4("Health Bar Color")]
[Order(20, collapseWith = nameof(ChangeHealthBarCleanseColor))]
public PluginConfigColor HealthBarColor = new(new Vector4(255f / 255f, 0f / 255f, 104f / 255f, 100f / 100f));
[Checkbox("Change Border Color", spacing = true)]
[Order(25)]
public bool ChangeBorderCleanseColor = true;
[ColorEdit4("Border Color")]
[Order(30, collapseWith = nameof(ChangeBorderCleanseColor))]
public PluginConfigColor BorderColor = new(new Vector4(255f / 255f, 0f / 255f, 104f / 255f, 100f / 100f));
}
[Exportable(false)]
[DisableParentSettings("Anchor")]
[Section("Party Frames", true)]
[SubSection("Cooldowns", 0)]
public class PartyFramesCooldownListConfig : AnchorablePluginConfigObject
{
public new static PartyFramesCooldownListConfig DefaultConfig()
{
PartyFramesCooldownListConfig config = new PartyFramesCooldownListConfig();
config.Position = new Vector2(-2, 0);
config.Size = new Vector2(40 * 8 + 6, 40);
return config;
}
[Anchor("Health Bar Anchor")]
[Order(3)]
public DrawAnchor HealthBarAnchor = DrawAnchor.Left;
[Checkbox("Tooltips", spacing = true)]
[Order(20)]
public bool ShowTooltips = true;
[Checkbox("Preview", isMonitored = true)]
[Order(21)]
public bool Preview;
[DragInt2("Icon Size", min = 1, max = 4000, spacing = true)]
[Order(30)]
public Vector2 IconSize = new Vector2(40, 40);
[DragInt2("Icon Padding", min = 0, max = 500)]
[Order(31)]
public Vector2 IconPadding = new(4, 4);
[Checkbox("Fill Rows First")]
[Order(32)]
public bool FillRowsFirst = true;
[Combo("Icons Growth Direction",
"Right and Down",
"Right and Up",
"Left and Down",
"Left and Up",
"Centered and Up",
"Centered and Down",
"Centered and Left",
"Centered and Right"
)]
[Order(33)]
public int Directions = 3; // left & up
[Checkbox("Show Border", spacing = true)]
[Order(35)]
public bool DrawBorder = true;
[ColorEdit4("Border Color")]
[Order(36, collapseWith = nameof(DrawBorder))]
public PluginConfigColor BorderColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[DragInt("Border Thickness", min = 1, max = 10)]
[Order(37, collapseWith = nameof(DrawBorder))]
public int BorderThickness = 1;
[Checkbox("Change Icon Border When Active")]
[Order(45, collapseWith = nameof(DrawBorder))]
public bool ChangeIconBorderWhenActive = true;
[ColorEdit4("Icon Active Border Color")]
[Order(46, collapseWith = nameof(ChangeIconBorderWhenActive))]
public PluginConfigColor IconActiveBorderColor = new PluginConfigColor(new Vector4(255f / 255f, 200f / 255f, 35f / 255f, 100f / 100f));
[DragInt("Icon Active Border Thickness", min = 1, max = 10)]
[Order(47, collapseWith = nameof(ChangeIconBorderWhenActive))]
public int IconActiveBorderThickness = 3;
[Checkbox("Change Label Color When Active", spacing = true)]
[Order(50)]
public bool ChangeLabelsColorWhenActive = false;
[ColorEdit4("Label Active Color")]
[Order(51, collapseWith = nameof(ChangeLabelsColorWhenActive))]
public PluginConfigColor LabelsActiveColor = new PluginConfigColor(new Vector4(255f / 255f, 200f / 255f, 35f / 255f, 100f / 100f));
[NestedConfig("Time Label", 80)]
public PartyCooldownTimeLabelConfig TimeLabel = new PartyCooldownTimeLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center) { NumberFormat = 1 };
}
}
@@ -0,0 +1,295 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.PartyCooldowns;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.Party
{
public class PartyFramesCooldownListHud : ParentAnchoredDraggableHudElement, IHudElementWithActor, IHudElementWithAnchorableParent, IHudElementWithPreview
{
private PartyFramesCooldownListConfig Config => (PartyFramesCooldownListConfig)_config;
private PartyCooldownsDataConfig _dataConfig = null!;
private LabelHud _timeLabel;
private bool _needsUpdate = true;
private LayoutInfo _layoutInfo;
private List<PartyCooldown> _cooldowns = new List<PartyCooldown>();
private List<PartyCooldown>? _fakeCooldowns = null;
public IGameObject? Actor { get; set; }
protected override bool AnchorToParent => true;
protected override DrawAnchor ParentAnchor => Config is PartyFramesCooldownListConfig config ? config.HealthBarAnchor : DrawAnchor.Center;
public PartyFramesCooldownListHud(PartyFramesCooldownListConfig config, string? displayName = null) : base(config, displayName)
{
_timeLabel = new LabelHud(config.TimeLabel);
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
PartyCooldownsManager.Instance.CooldownsChangedEvent += OnCooldownsChanged;
_config.ValueChangeEvent += OnConfigPropertyChanged;
OnConfigReset(ConfigurationManager.Instance);
}
private void OnConfigReset(ConfigurationManager sender)
{
if (_dataConfig != null)
_dataConfig.CooldownsDataChangedEvent -= OnCooldownsDataChanged;
_dataConfig = ConfigurationManager.Instance.GetConfigObject<PartyCooldownsDataConfig>();
_dataConfig.CooldownsDataChangedEvent += OnCooldownsDataChanged;
}
protected override void InternalDispose()
{
ConfigurationManager.Instance?.ResetEvent -= OnConfigReset;
_config.ValueChangeEvent -= OnConfigPropertyChanged;
_dataConfig?.CooldownsDataChangedEvent -= OnCooldownsDataChanged;
PartyCooldownsManager.Instance?.CooldownsChangedEvent -= OnCooldownsChanged;
}
private void OnConfigPropertyChanged(object? sender, OnChangeBaseArgs args)
{
if (args.PropertyName == "Preview")
{
UpdatePreview();
}
}
private unsafe void UpdatePreview()
{
if (!Config.Preview)
{
_fakeCooldowns = null;
return;
}
var RNG = new Random((int)ImGui.GetTime());
_fakeCooldowns = new List<PartyCooldown>();
for (int i = 0; i < 10; i++)
{
int index = RNG.Next(0, _dataConfig.Cooldowns.Count);
PartyCooldown cooldown = new PartyCooldown(_dataConfig.Cooldowns[index], 0, 90, null);
int rng = RNG.Next(100);
if (rng > 80)
{
cooldown.LastTimeUsed = ImGui.GetTime() - 30;
}
else if (rng > 50)
{
cooldown.LastTimeUsed = ImGui.GetTime() + 1;
}
_fakeCooldowns.Add(cooldown);
}
}
public void StopPreview()
{
Config.Preview = false;
UpdatePreview();
}
private void OnCooldownsDataChanged(PartyCooldownsDataConfig sender)
{
_needsUpdate = true;
}
private void OnCooldownsChanged(PartyCooldownsManager sender)
{
_needsUpdate = true;
}
private void UpdateCooldowns()
{
_cooldowns.Clear();
if (Actor == null || PartyCooldownsManager.Instance?.CooldownsMap == null) { return; }
if (PartyCooldownsManager.Instance.CooldownsMap.TryGetValue((uint)Actor.GameObjectId, out Dictionary<uint, PartyCooldown>? dict) && dict != null)
{
_cooldowns = dict.Values.Where(o => o.Data.IsEnabledForPartyFrames()).ToList();
}
_cooldowns.Sort((a, b) =>
{
int aOrder = a.Data.Column * 1000 + a.Data.Priority;
int bOrder = b.Data.Column * 1000 + b.Data.Priority;
return aOrder.CompareTo(bOrder);
});
_needsUpdate = false;
}
private void CalculateLayout(uint count)
{
if (count <= 0) { return; }
_layoutInfo = LayoutHelper.CalculateLayout(
Config.Size,
Config.IconSize,
count,
Config.IconPadding,
LayoutHelper.GetFillsRowsFirst(Config.FillRowsFirst, LayoutHelper.GrowthDirectionsFromIndex(Config.Directions))
);
}
public override void DrawChildren(Vector2 origin)
{
if (!Config.Enabled) { return; }
if (_needsUpdate)
{
UpdateCooldowns();
}
List<PartyCooldown> list = _fakeCooldowns != null ? _fakeCooldowns : _cooldowns;
if (list.Count == 0) { return; }
// area
GrowthDirections growthDirections = LayoutHelper.GrowthDirectionsFromIndex(Config.Directions);
Vector2 position = origin + GetAnchoredPosition(Config.Position, Config.Size, DrawAnchor.TopLeft);
Vector2 areaPos = LayoutHelper.CalculateStartPosition(position, Config.Size, growthDirections);
Vector2 margin = new Vector2(14, 10);
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
// calculate icon positions
uint count = (uint)list.Count;
CalculateLayout(count);
var (iconPositions, minPos, maxPos) = LayoutHelper.CalculateIconPositions(
growthDirections,
count,
position,
Config.Size,
Config.IconSize,
Config.IconPadding,
LayoutHelper.GetFillsRowsFirst(Config.FillRowsFirst, growthDirections),
_layoutInfo
);
// window
// imgui clips the left and right borders inside windows for some reason
// we make the window bigger so the actual drawable size is the expected one
Vector2 windowPos = minPos - margin;
Vector2 windowSize = maxPos - minPos;
AddDrawAction(Config.StrataLevel, () =>
{
DrawHelper.DrawInWindow(ID, windowPos, windowSize + margin * 2, false, (drawList) =>
{
// area
if (Config.Preview)
{
drawList.AddRectFilled(areaPos, areaPos + Config.Size, 0x88000000);
}
for (int i = 0; i < count; i++)
{
Vector2 iconPos = iconPositions[i];
PartyCooldown cooldown = list[i];
float cooldownTime = cooldown.CooldownTimeRemaining();
float effectTime = cooldown.EffectTimeRemaining();
// icon
bool recharging = effectTime == 0 && cooldownTime > 0;
uint color = recharging ? 0xAAFFFFFF : 0xFFFFFFFF;
bool shouldDrawCooldown = ClipRectsHelper.Instance.GetClipRectForArea(iconPos, Config.IconSize) == null;
DrawHelper.DrawIcon(cooldown.Data.IconId, iconPos, Config.IconSize, false, color, drawList);
if (shouldDrawCooldown && effectTime == 0 && cooldownTime > 0)
{
DrawHelper.DrawIconCooldown(iconPos, Config.IconSize, cooldownTime, cooldown.Data.CooldownDuration, drawList);
}
// border
if (Config.DrawBorder)
{
bool active = effectTime > 0 && Config.ChangeIconBorderWhenActive;
uint iconBorderColor = active ? Config.IconActiveBorderColor.Base : Config.BorderColor.Base;
int thickness = active ? Config.IconActiveBorderThickness : Config.BorderThickness;
drawList.AddRect(iconPos, iconPos + Config.IconSize, iconBorderColor, 0, ImDrawFlags.None, thickness);
}
}
});
});
PartyCooldown? hoveringCooldown = null;
IGameObject? character = Actor;
// labels need to be drawn separated since they have their own window for clipping
for (var i = 0; i < count; i++)
{
Vector2 iconPos = iconPositions[i];
PartyCooldown cooldown = list[i];
float cooldownTime = cooldown.CooldownTimeRemaining();
float effectTime = cooldown.EffectTimeRemaining();
PluginConfigColor? labelColor = effectTime > 0 && Config.ChangeLabelsColorWhenActive ? Config.LabelsActiveColor : null;
// time
AddDrawAction(Config.TimeLabel.StrataLevel, () =>
{
PluginConfigColor realColor = Config.TimeLabel.Color;
Config.TimeLabel.Color = labelColor ?? realColor;
Config.TimeLabel.SetText("");
if (effectTime > 0)
{
if (Config.TimeLabel.ShowEffectDuration)
{
Config.TimeLabel.SetValue(effectTime);
}
}
else if (cooldownTime > 0)
{
if (Config.TimeLabel.ShowRemainingCooldown)
{
Config.TimeLabel.SetText(Utils.DurationToString(cooldownTime, Config.TimeLabel.NumberFormat));
}
}
_timeLabel.Draw(iconPos, Config.IconSize, character);
Config.TimeLabel.Color = realColor;
});
// tooltips / interaction
if (ImGui.IsMouseHoveringRect(iconPos, iconPos + Config.IconSize))
{
hoveringCooldown = cooldown;
}
}
if (hoveringCooldown != null)
{
// tooltip
if (Config.ShowTooltips)
{
TooltipsHelper.Instance.ShowTooltipOnCursor(
hoveringCooldown.TooltipText(),
hoveringCooldown.Data.Name,
hoveringCooldown.Data.ActionId
);
}
}
}
}
}
+465
View File
@@ -0,0 +1,465 @@
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<PartyFramesBar> 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<PartyFramesBar>(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<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
return (new List<Vector2>() { Config.Position + Size / 2f }, new List<Vector2>() { 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<int> raisedIndexes = new List<int>();
List<int> cleanseIndexes = new List<int>();
List<int> whosTalkingIndexes = new List<int>();
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<PartyFramesHealthBarsConfig>(),
ConfigurationManager.Instance.GetConfigObject<PartyFramesManaBarConfig>(),
ConfigurationManager.Instance.GetConfigObject<PartyFramesCastbarConfig>(),
ConfigurationManager.Instance.GetConfigObject<PartyFramesIconsConfig>(),
ConfigurationManager.Instance.GetConfigObject<PartyFramesBuffsConfig>(),
ConfigurationManager.Instance.GetConfigObject<PartyFramesDebuffsConfig>(),
ConfigurationManager.Instance.GetConfigObject<PartyFramesTrackersConfig>(),
ConfigurationManager.Instance.GetConfigObject<PartyFramesCooldownListConfig>()
);
}
}
#endregion
}
+111
View File
@@ -0,0 +1,111 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Statuses;
using HSUI.Config;
using HSUI.Helpers;
using System;
using System.Collections.Generic;
namespace HSUI.Interface.Party
{
public class InvulnStatus
{
public readonly uint InvulnIcon;
public readonly float InvulnTime;
public readonly uint InvulnId;
public InvulnStatus(uint invulnIcon, float invulnTime, uint invulnId)
{
InvulnIcon = invulnIcon;
InvulnTime = invulnTime;
InvulnId = invulnId;
}
}
public class PartyFramesInvulnTracker : IDisposable
{
private PartyFramesInvulnTrackerConfig _config = null!;
public PartyFramesInvulnTracker()
{
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
OnConfigReset(ConfigurationManager.Instance);
}
~PartyFramesInvulnTracker()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
}
public void OnConfigReset(ConfigurationManager sender)
{
_config = ConfigurationManager.Instance.GetConfigObject<PartyFramesTrackersConfig>().Invuln;
}
public void Update(List<IPartyFramesMember> partyMembers)
{
if (!_config.Enabled)
{
return;
}
foreach (var member in partyMembers)
{
if (member.Character == null || member.ObjectId == 0)
{
member.InvulnStatus = null;
continue;
}
if (member.Character is not IBattleChara battleChara || member.HP <= 0)
{
member.InvulnStatus = null;
continue;
}
// check invuln buff
IStatus? tankInvuln = Utils.GetTankInvulnerabilityID(battleChara);
if (tankInvuln == null)
{
member.InvulnStatus = null;
continue;
}
// apply invuln data based on buff
member.InvulnStatus = new InvulnStatus(InvulnMap[tankInvuln.StatusId], tankInvuln.RemainingTime, tankInvuln.StatusId);
}
}
#region invuln ids
//these need to be mapped instead
private static Dictionary<uint, uint> InvulnMap = new Dictionary<uint, uint>()
{
{ 810, 003077 }, // LIVING DEAD
{ 3255, 003077}, // UNDEAD REBIRTH
{ 811, 003077 }, // WALKING DEAD
{ 1302, 002502 }, // HALLOWED GROUND
{ 82, 002502 }, // HALLOWED GROUND
{ 409, 000266 }, // HOLMGANG
{ 1836, 003416 } // SUPERBOLIDE
};
#endregion
}
}
+235
View File
@@ -0,0 +1,235 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Party;
using HSUI.Helpers;
using Dalamud.Bindings.ImGui;
using System;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
namespace HSUI.Interface.Party
{
public enum EnmityLevel : byte
{
Leader = 1,
Second = 2,
Last = 255
}
public enum PartyMemberStatus : byte
{
None,
ViewingCutscene,
Offline,
Dead
}
public unsafe class PartyFramesMember : IPartyFramesMember
{
protected IPartyMember? _partyMember = null;
private string _name = "";
private uint _jobId = 0;
private uint _objectID = 0;
public uint ObjectId => _partyMember != null ? _partyMember.EntityId : _objectID;
public ICharacter? Character { get; private set; }
public CrossRealmMember? CrossCharacter { get; private set; }
public int Index { get; set; }
public int Order { get; set; }
public string Name => _partyMember != null ? _partyMember.Name.ToString() : (Character != null ? Character.Name.ToString() : _name);
public uint Level => _partyMember != null ? _partyMember.Level : (Character != null ? Character.Level : (uint)0);
public uint JobId => _partyMember != null ? _partyMember.ClassJob.RowId : (Character != null ? Character.ClassJob.RowId : _jobId);
public uint HP => _partyMember != null ? _partyMember.CurrentHP : (Character != null ? Character.CurrentHp : (uint)0);
public uint MaxHP => _partyMember != null ? _partyMember.MaxHP : (Character != null ? Character.MaxHp : (uint)0);
public uint MP => _partyMember != null ? _partyMember.CurrentMP : JobsHelper.CurrentPrimaryResource(Character);
public uint MaxMP => _partyMember != null ? _partyMember.MaxMP : JobsHelper.MaxPrimaryResource(Character);
public float Shield => Utils.ActorShieldValue(Character);
public EnmityLevel EnmityLevel { get; private set; } = EnmityLevel.Last;
public PartyMemberStatus Status { get; private set; } = PartyMemberStatus.None;
public ReadyCheckStatus ReadyCheckStatus { get; private set; } = ReadyCheckStatus.None;
public bool IsPartyLeader { get; private set; } = false;
public bool IsChocobo { get; private set; } = false;
public float? RaiseTime { get; set; }
public InvulnStatus? InvulnStatus { get; set; }
public bool HasDispellableDebuff { get; set; } = false;
public WhosTalkingState WhosTalkingState => WhosTalkingHelper.Instance?.GetUserState(Name) ?? WhosTalkingState.None;
public PartyFramesMember(IPartyMember partyMember, int index, int order, EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, bool isChocobo = false)
{
_partyMember = partyMember;
Index = index;
Order = order;
EnmityLevel = enmityLevel;
Status = status;
ReadyCheckStatus = readyCheckStatus;
IsPartyLeader = isPartyLeader;
IsChocobo = isChocobo;
var gameObject = partyMember.GameObject;
if (gameObject is ICharacter character)
{
Character = character;
}
}
public PartyFramesMember(ICharacter character, int index, int order, EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, bool isChocobo = false)
{
Index = index;
Order = order;
EnmityLevel = enmityLevel;
Status = status;
ReadyCheckStatus = readyCheckStatus;
IsPartyLeader = isPartyLeader;
IsChocobo = isChocobo;
_objectID = (uint)character.GameObjectId;
Character = character;
}
public PartyFramesMember(uint objectId, int index, int order, EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, bool isChocobo = false)
{
Index = index;
Order = order;
EnmityLevel = enmityLevel;
Status = status;
ReadyCheckStatus = readyCheckStatus;
IsPartyLeader = isPartyLeader;
IsChocobo = isChocobo;
_objectID = objectId;
var gameObject = Plugin.ObjectTable.SearchById(ObjectId);
Character = gameObject is ICharacter ? (ICharacter)gameObject : null;
}
public PartyFramesMember(CrossRealmMember member, int index, int order, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, bool isChocobo = false)
{
Index = index;
Order = order;
Status = status;
ReadyCheckStatus = readyCheckStatus;
IsPartyLeader = isPartyLeader;
IsChocobo = isChocobo;
_objectID = (uint)member.EntityId;
CrossCharacter = member;
_name = member.NameString;
_jobId = member.ClassJobId;
}
public void Update(EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, uint jobId = 0)
{
EnmityLevel = enmityLevel;
Status = status;
ReadyCheckStatus = readyCheckStatus;
IsPartyLeader = isPartyLeader;
if (ObjectId == 0)
{
Character = null;
return;
}
var gameObject = Plugin.ObjectTable.SearchById(ObjectId);
Character = gameObject is ICharacter ? (ICharacter)gameObject : null;
if (jobId > 0)
{
_jobId = jobId;
}
else if (Character != null)
{
_jobId = Character.ClassJob.RowId;
}
if (status == PartyMemberStatus.None && Character != null && MaxHP > 0 && HP <= 0)
{
Status = PartyMemberStatus.Dead;
}
}
}
public class FakePartyFramesMember : IPartyFramesMember
{
public static readonly Random RNG = new Random((int)ImGui.GetTime());
public uint ObjectId => 0xE0000000;
public ICharacter? Character => null;
public int Index { get; set; }
public int Order { get; set; }
public string Name { get; private set; }
public uint Level { get; private set; }
public uint JobId { get; private set; }
public uint HP { get; private set; }
public uint MaxHP { get; private set; }
public uint MP { get; private set; }
public uint MaxMP { get; private set; }
public float Shield { get; private set; }
public EnmityLevel EnmityLevel { get; private set; }
public PartyMemberStatus Status { get; private set; }
public ReadyCheckStatus ReadyCheckStatus { get; private set; }
public bool IsPartyLeader { get; }
public bool IsChocobo { get; }
public float? RaiseTime { get; set; }
public InvulnStatus? InvulnStatus { get; set; }
public bool HasDispellableDebuff { get; set; }
public WhosTalkingState WhosTalkingState { get; set; }
public FakePartyFramesMember(int order)
{
Name = RNG.Next(0, 2) == 1 ? "Fake Name" : "FakeLonger MockedName";
Index = order;
Order = order + 1;
Level = (uint)RNG.Next(1, 80);
JobId = (uint)RNG.Next(19, 41);
MaxHP = (uint)RNG.Next(90000, 150000);
HP = order == 2 || order == 3 ? 0 : (uint)(MaxHP * RNG.Next(50, 100) / 100f);
MaxMP = 10000;
MP = order == 2 || order == 3 ? 0 : (uint)(MaxMP * RNG.Next(100) / 100f);
Shield = order == 2 || order == 3 ? 0 : RNG.Next(30) / 100f;
EnmityLevel = order <= 1 ? (EnmityLevel)order + 1 : EnmityLevel.Last;
Status = order < 3 ? PartyMemberStatus.None : (order == 3 ? PartyMemberStatus.Dead : (PartyMemberStatus)RNG.Next(0, 3));
ReadyCheckStatus = (ReadyCheckStatus)RNG.Next(0, 3);
IsPartyLeader = order == 0;
IsChocobo = RNG.Next(0, 8) == 1;
HasDispellableDebuff = RNG.Next(0, 2) == 1;
RaiseTime = order == 2 ? RNG.Next(0, 60) : null;
InvulnStatus = order == 0 ? new InvulnStatus(3077, RNG.Next(0, 10), 810) : null;
WhosTalkingState = (WhosTalkingState)RNG.Next(0, 4);
}
public void Update(EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, uint jobId = 0)
{
}
}
public interface IPartyFramesMember
{
public uint ObjectId { get; }
public ICharacter? Character { get; }
public int Index { get; }
public int Order { get; }
public string Name { get; }
public uint Level { get; }
public uint JobId { get; }
public uint HP { get; }
public uint MaxHP { get; }
public uint MP { get; }
public uint MaxMP { get; }
public float Shield { get; }
public EnmityLevel EnmityLevel { get; }
public PartyMemberStatus Status { get; }
public ReadyCheckStatus ReadyCheckStatus { get; }
public bool IsPartyLeader { get; }
public bool IsChocobo { get; }
public float? RaiseTime { get; set; }
public InvulnStatus? InvulnStatus { get; set; }
public bool HasDispellableDebuff { get; set; }
public WhosTalkingState WhosTalkingState { get; }
public void Update(EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, uint jobId = 0);
}
}
+185
View File
@@ -0,0 +1,185 @@
using Dalamud.Game.ClientState.Objects.Types;
using HSUI.Config;
using HSUI.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
namespace HSUI.Interface.Party
{
public class PartyFramesRaiseTracker : IDisposable
{
private PartyFramesRaiseTrackerConfig _config = null!;
public PartyFramesRaiseTracker()
{
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
OnConfigReset(ConfigurationManager.Instance);
}
~PartyFramesRaiseTracker()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
}
public void OnConfigReset(ConfigurationManager sender)
{
_config = sender.GetConfigObject<PartyFramesTrackersConfig>().Raise;
}
public void Update(List<IPartyFramesMember> partyMembers)
{
if (!_config.Enabled)
{
return;
}
Dictionary<uint, IPartyFramesMember> deadAndNotRaised = new Dictionary<uint, IPartyFramesMember>();
float? limitBreakTime = null;
Dictionary<uint, float> raiseTimeMap = new Dictionary<uint, float>();
foreach (var member in partyMembers)
{
if (member.Character == null || member.ObjectId == 0)
{
member.RaiseTime = null;
continue;
}
if (member.HP > 0)
{
member.RaiseTime = null;
}
if (member.Character is not IBattleChara battleChara)
{
continue;
}
// check raise casts
if (Utils.IsActorCasting(battleChara))
{
var remaining = Math.Max(0, battleChara.TotalCastTime - battleChara.CurrentCastTime);
// check limit break
if (IsRaiseLimitBreakAction(battleChara.CastActionId) &&
(limitBreakTime.HasValue && limitBreakTime.Value > remaining))
{
limitBreakTime = remaining;
}
// check regular raise
else if (IsRaiseAction(battleChara.CastActionId))
{
if (raiseTimeMap.TryGetValue((uint)battleChara.CastTargetObjectId, out float raiseTime))
{
if (raiseTime > remaining)
{
raiseTimeMap[(uint)battleChara.CastTargetObjectId] = remaining;
}
}
else
{
raiseTimeMap.Add((uint)battleChara.CastTargetObjectId, remaining);
}
}
}
// check raise buff
if (member.HP <= 0)
{
bool hasBuff = false;
var statusList = Utils.StatusListForBattleChara(battleChara);
foreach (var status in statusList)
{
if (status == null || (status.StatusId != 148 && status.StatusId != 1140))
{
continue;
}
// apply raise data based on buff
member.RaiseTime = status.RemainingTime;
hasBuff = true;
break;
}
if (!hasBuff)
{
deadAndNotRaised.Add(member.ObjectId, member);
}
}
}
// apply raise data based on casts
foreach (var memberId in deadAndNotRaised.Keys)
{
var member = deadAndNotRaised[memberId];
if (raiseTimeMap.TryGetValue(memberId, out float raiseTime))
{
if (limitBreakTime.HasValue && limitBreakTime.Value < raiseTime)
{
member.RaiseTime = limitBreakTime;
}
else
{
member.RaiseTime = raiseTime;
}
}
else
{
member.RaiseTime = limitBreakTime; // its fine if this is null here
}
}
}
#region raise ids
private static bool IsRaiseLimitBreakAction(uint actionId)
{
return LimitBreakIds.Contains(actionId);
}
private static bool IsRaiseAction(uint actionId)
{
return RaiseIds.Contains(actionId);
}
private static List<uint> RaiseIds = new List<uint>()
{
173, // ACN, SMN, SCH
125, // CNH, WHM
3603, // AST
18317, // BLU
22345, // Lost Sacrifice, Bozja
20730, // Lost Arise, Bozja
12996, // Raise L, Eureka
24287 // SGE
};
private static List<uint> LimitBreakIds = new List<uint>()
{
208, // WHM
4247, // SCH
4248, // AST
24859 // SGE
};
#endregion
}
}
+805
View File
@@ -0,0 +1,805 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Memory;
using HSUI.Config;
using HSUI.Helpers;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
using Lumina.Excel.Sheets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.UI.Arrays;
using FFXIVClientStructs.FFXIV.Component.GUI;
using static FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
using DalamudPartyMember = Dalamud.Game.ClientState.Party.IPartyMember;
using StructsPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
namespace HSUI.Interface.Party
{
public delegate void PartyMembersChangedEventHandler(PartyManager sender);
public unsafe class PartyManager : IDisposable
{
#region Singleton
public static PartyManager Instance { get; private set; } = null!;
private PartyFramesConfig _config = null!;
private PartyFramesIconsConfig _iconsConfig = null!;
private PartyManager()
{
_readyCheckHelper = new PartyReadyCheckHelper();
_raiseTracker = new PartyFramesRaiseTracker();
_invulnTracker = new PartyFramesInvulnTracker();
_cleanseTracker = new PartyFramesCleanseTracker();
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
OnConfigReset(ConfigurationManager.Instance);
UpdatePreview();
// find offline string for active language
if (Plugin.DataManager.GetExcelSheet<Addon>().TryGetRow(9836, out Addon row))
{
_offlineString = row.Text.ToString();
}
}
public static void Initialize()
{
Instance = new PartyManager();
}
~PartyManager()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
_readyCheckHelper.Dispose();
_raiseTracker.Dispose();
_invulnTracker.Dispose();
_cleanseTracker.Dispose();
_config.ValueChangeEvent -= OnConfigPropertyChanged;
Instance = null!;
}
private void OnConfigReset(ConfigurationManager sender)
{
if (_config != null)
{
_config.ValueChangeEvent -= OnConfigPropertyChanged;
}
_config = sender.GetConfigObject<PartyFramesConfig>();
_config.ValueChangeEvent += OnConfigPropertyChanged;
_iconsConfig = ConfigurationManager.Instance.GetConfigObject<PartyFramesIconsConfig>();
}
#endregion Singleton
public AddonPartyList* PartyListAddon { get; private set; } = null;
public IntPtr HudAgent { get; private set; } = IntPtr.Zero;
private RaptureAtkModule* _raptureAtkModule = null;
private const int PartyListInfoOffset = 0x0D40;
private const int PartyListMemberRawInfoSize = 0x28;
private const int PartyMembersInfoIndex = 12; // TODO: Should be reworked to use PartyMemberListStringArray.Instance()
private List<IPartyFramesMember> _groupMembers = new List<IPartyFramesMember>();
public IReadOnlyCollection<IPartyFramesMember> GroupMembers => _groupMembers.AsReadOnly();
private List<IPartyFramesMember> _sortedGroupMembers = new List<IPartyFramesMember>();
public IReadOnlyCollection<IPartyFramesMember> SortedGroupMembers => _sortedGroupMembers.AsReadOnly();
public uint MemberCount => (uint)_groupMembers.Count;
private string? _partyTitle = null;
public string PartyTitle => _partyTitle ?? "";
private int _groupMemberCount => GroupManager.Instance()->MainGroup.MemberCount;
private int _realMemberCount => PartyListAddon != null ? PartyListAddon->MemberCount : Plugin.PartyList.Length;
private int _realMemberAndChocoboCount => PartyListAddon != null ? PartyListAddon->MemberCount + Math.Max(1, (int)PartyListAddon->ChocoboCount) : Plugin.PartyList.Length;
private Dictionary<string, InternalMemberData> _prevDataMap = new();
private bool _wasRealGroup = false;
private bool _wasCrossWorld = false;
private InfoProxyCrossRealm* _crossRealmInfo => InfoProxyCrossRealm.Instance();
private Group _mainGroup => GroupManager.Instance()->MainGroup;
private string _offlineString = "offline";
private PartyReadyCheckHelper _readyCheckHelper;
private PartyFramesRaiseTracker _raiseTracker;
private PartyFramesInvulnTracker _invulnTracker;
private PartyFramesCleanseTracker _cleanseTracker;
public event PartyMembersChangedEventHandler? MembersChangedEvent;
public bool Previewing => _config.Preview;
public bool IsSoloParty()
{
if (!_config.ShowWhenSolo) { return false; }
return _groupMembers.Count <= 1 ||
(_groupMembers.Count == 2 && _config.ShowChocobo &&
_groupMembers[1].Character is IBattleNpc npc && npc.BattleNpcKind == BattleNpcSubKind.Chocobo);
}
public void Update()
{
// find party list hud agent
PartyListAddon = (AddonPartyList*)Plugin.GameGui.GetAddonByName("_PartyList", 1).Address;
HudAgent = Plugin.GameGui.FindAgentInterface(PartyListAddon);
if (PartyListAddon == null || HudAgent == IntPtr.Zero)
{
if (_groupMembers.Count > 0)
{
_groupMembers.Clear();
MembersChangedEvent?.Invoke(this);
}
return;
}
_raptureAtkModule = RaptureAtkModule.Instance();
// no need to update on preview mode
if (_config.Preview)
{
return;
}
InternalUpdate();
}
private void InternalUpdate()
{
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
if (player is null || player is not IPlayerCharacter)
{
return;
}
bool isCrossWorld = IsCrossWorldParty();
// ready check update
if (_iconsConfig.ReadyCheckStatus.Enabled)
{
_readyCheckHelper.Update(_iconsConfig.ReadyCheckStatus.Duration);
}
try
{
// title
_partyTitle = GetPartyListTitle();
// solo
if (_realMemberCount <= 1 && PartyListAddon->TrustCount == 0)
{
if (_config.ShowWhenSolo)
{
UpdateSoloParty(player);
}
else if (_groupMembers.Count > 0)
{
_groupMembers.Clear();
MembersChangedEvent?.Invoke(this);
}
_wasRealGroup = false;
}
else
{
// player maps
Dictionary<string, InternalMemberData> dataMap = GetMembersDataMap(player, isCrossWorld);
bool partyChanged = _prevDataMap.Count != dataMap.Count;
if (!partyChanged)
{
foreach (string key in dataMap.Keys)
{
InternalMemberData newData = dataMap[key];
if (!_prevDataMap.TryGetValue(key, out InternalMemberData? oldData) ||
oldData == null ||
newData.Order != oldData.Order)
{
partyChanged = true;
break;
}
}
}
_prevDataMap = dataMap;
// trust
if (PartyListAddon->TrustCount > 0)
{
UpdateTrustParty(player, dataMap, partyChanged);
}
// cross world party/alliance
else if (isCrossWorld)
{
UpdateCrossWorldParty(player, dataMap, partyChanged);
}
// regular party
else
{
UpdateRegularParty(player, dataMap, partyChanged);
}
_wasRealGroup = true;
}
UpdateTrackers();
}
catch (Exception e)
{
Plugin.Logger.Warning(e.Message);
}
_wasCrossWorld = isCrossWorld;
}
private Dictionary<string, InternalMemberData> GetMembersDataMap(IPlayerCharacter player, bool isCrossWorld)
{
Dictionary<string, InternalMemberData> dataMap = new Dictionary<string, InternalMemberData>();
if (_raptureAtkModule == null || _raptureAtkModule->AtkModule.AtkArrayDataHolder.StringArrayCount <= PartyMembersInfoIndex)
{
return dataMap;
}
// raw info
int allianceNum = FindAlliance(player);
int count = isCrossWorld ? _crossRealmInfo->CrossRealmGroups[allianceNum].GroupMemberCount : _realMemberCount + PartyListAddon->TrustCount;
for (int i = 0; i < count; i++)
{
InternalMemberData data = new InternalMemberData();
data.Index = i;
if (!isCrossWorld)
{
PartyListMemberRawInfo* info = (PartyListMemberRawInfo*)(HudAgent + (PartyListInfoOffset + PartyListMemberRawInfoSize * i));
data.ObjectId = info->ObjectId;
data.ContentId = info->ContentId;
data.Name = info->Name;
data.Order = info->Order;
}
else
{
CrossRealmMember member = _crossRealmInfo->CrossRealmGroups[allianceNum].GroupMembers[i];
data.ObjectId = member.EntityId;
data.ContentId = (long)member.ContentId;
data.Name = member.NameString;
data.Order = i;
}
if (!dataMap.ContainsKey(data.Name))
{
dataMap.Add(data.Name, data);
}
}
// status string
var stringArrayData = _raptureAtkModule->AtkModule.AtkArrayDataHolder.StringArrays[PartyMembersInfoIndex];
for (int i = 0; i < count; i++)
{
int index = i * 5;
if (stringArrayData->AtkArrayData.Size <= index + 3 ||
stringArrayData->StringArray[index] == null ||
stringArrayData->StringArray[index + 3] == null) { break; }
IntPtr ptr = new IntPtr(stringArrayData->StringArray[index]);
string name = MemoryHelper.ReadSeStringNullTerminated(ptr).ToString();
ptr = new IntPtr(stringArrayData->StringArray[index + 3]);
string a = MemoryHelper.ReadSeStringNullTerminated(ptr).ToString();
if (dataMap.TryGetValue(name, out InternalMemberData? data) && data != null)
{
data.Status = MemoryHelper.ReadSeStringNullTerminated(ptr).ToString();
}
}
return dataMap;
}
private bool IsCrossWorldParty()
{
return _crossRealmInfo->IsCrossRealm && _crossRealmInfo->GroupCount > 0 && _mainGroup.MemberCount == 0;
}
private ReadyCheckStatus GetReadyCheckStatus(ulong contentId)
{
return _readyCheckHelper.GetStatusForContentId(contentId);
}
private void UpdateTrustParty(IPlayerCharacter player, Dictionary<string, InternalMemberData> dataMap, bool forced)
{
bool softUpdate = true;
if (_groupMembers.Count != dataMap.Count || forced)
{
_groupMembers.Clear();
softUpdate = false;
}
if (softUpdate)
{
foreach (IPartyFramesMember member in _groupMembers)
{
if (member.ObjectId == player.GameObjectId)
{
member.Update(EnmityForIndex(member.Index), PartyMemberStatus.None, ReadyCheckStatus.None, true, player.ClassJob.RowId);
}
else
{
member.Update(EnmityForTrustMemberIndex(member.Index - 1), PartyMemberStatus.None, ReadyCheckStatus.None, false, 0);
}
}
}
else
{
string[] keys = dataMap.Keys.ToArray();
for (int i = 0; i < keys.Length; i++)
{
InternalMemberData data = dataMap[keys[i]];
if (keys[i] == player.Name.ToString())
{
PartyFramesMember playerMember = new PartyFramesMember(player, data.Index, data.Order, EnmityForIndex(i), PartyMemberStatus.None, ReadyCheckStatus.None, true);
_groupMembers.Add(playerMember);
}
else
{
ICharacter? trustChara = Utils.GetGameObjectByName(keys[i]) as ICharacter;
if (trustChara != null)
{
_groupMembers.Add(new PartyFramesMember(trustChara, data.Index, data.Order, EnmityForTrustMemberIndex(i), PartyMemberStatus.None, ReadyCheckStatus.None, false));
}
}
}
}
if (!softUpdate)
{
SortGroupMembers(player);
MembersChangedEvent?.Invoke(this);
}
}
private void UpdateSoloParty(IPlayerCharacter player)
{
ICharacter? chocobo = null;
if (_config.ShowChocobo)
{
var gameObject = Utils.GetBattleChocobo(player);
if (gameObject != null && gameObject is ICharacter)
{
chocobo = (ICharacter)gameObject;
}
}
bool needsUpdate =
_groupMembers.Count == 0 ||
(_groupMembers.Count != 2 && _config.ShowChocobo && chocobo != null) ||
(_groupMembers.Count > 1 && !_config.ShowChocobo) ||
(_groupMembers.Count > 1 && chocobo == null) ||
(_groupMembers.Count == 2 && _config.ShowChocobo && _groupMembers[1].ObjectId != chocobo?.EntityId);
EnmityLevel playerEnmity = PartyListAddon->EnmityLeaderIndex == 0 ? EnmityLevel.Leader : EnmityLevel.Last;
// for some reason chocobos never get a proper enmity value even though they have aggro
// if the player enmity is set to first, but the "leader index" is invalid
// we can pretty much deduce that the chocobo is the one with aggro
// this might fail on some cases when there are other players not in party hitting the same thing
// but the edge case is so minor we should be fine
EnmityLevel chocoboEnmity = PartyListAddon->EnmityLeaderIndex == -1 && PartyListAddon->PartyMembers[0].EmnityByte == 1 ? EnmityLevel.Leader : EnmityLevel.Last;
if (needsUpdate)
{
_groupMembers.Clear();
_groupMembers.Add(new PartyFramesMember(player, 0, 0, playerEnmity, PartyMemberStatus.None, ReadyCheckStatus.None, true));
if (chocobo != null)
{
_groupMembers.Add(new PartyFramesMember(chocobo, 1, 1, chocoboEnmity, PartyMemberStatus.None, ReadyCheckStatus.None, false));
}
SortGroupMembers(player);
MembersChangedEvent?.Invoke(this);
}
else
{
for (int i = 0; i < _groupMembers.Count; i++)
{
_groupMembers[i].Update(i == 0 ? playerEnmity : chocoboEnmity, PartyMemberStatus.None, ReadyCheckStatus.None, i == 0, i == 0 ? player.ClassJob.RowId : 0);
}
}
}
private void UpdateCrossWorldParty(IPlayerCharacter player, Dictionary<string, InternalMemberData> dataMap, bool forced)
{
bool softUpdate = true;
int allianceNum = FindAlliance(player);
int count = _crossRealmInfo->CrossRealmGroups[allianceNum].GroupMemberCount;
if (!_wasCrossWorld || count != _groupMembers.Count || forced)
{
_groupMembers.Clear();
softUpdate = false;
}
// create new members array with cross world data
for (int i = 0; i < count; i++)
{
CrossRealmMember member = _crossRealmInfo->CrossRealmGroups[allianceNum].GroupMembers[i];
string memberName = member.NameString;
if (!dataMap.TryGetValue(memberName, out InternalMemberData? data) || data == null)
{
continue;
}
bool isPlayer = member.EntityId == player.EntityId;
bool isLeader = member.IsPartyLeader;
PartyMemberStatus status = data.Status != null ? StatusForCrossWorldMember(data.Status) : PartyMemberStatus.None;
ReadyCheckStatus readyCheckStatus = GetReadyCheckStatus(member.ContentId);
if (softUpdate)
{
IPartyFramesMember groupMember = _groupMembers.ElementAt(i);
groupMember.Update(EnmityLevel.Last, status, readyCheckStatus, isLeader, member.ClassJobId);
}
else
{
PartyFramesMember partyMember = isPlayer ?
new PartyFramesMember(player, i, data.Order, EnmityLevel.Last, status, readyCheckStatus, isLeader) :
new PartyFramesMember(member, i, data.Order, status, readyCheckStatus, isLeader);
_groupMembers.Add(partyMember);
}
}
if (!softUpdate)
{
SortGroupMembers(player);
MembersChangedEvent?.Invoke(this);
}
}
private int FindAlliance(IPlayerCharacter player)
{
for (int i = 0; i < _crossRealmInfo->CrossRealmGroups.Length; i++)
{
for (int j = 0; j < _crossRealmInfo->CrossRealmGroups[i].GroupMemberCount; j++)
{
CrossRealmMember member = _crossRealmInfo->CrossRealmGroups[i].GroupMembers[j];
if (member.EntityId == player.EntityId)
{
return i;
}
}
}
return 0;
}
private void UpdateRegularParty(IPlayerCharacter player, Dictionary<string, InternalMemberData> dataMap, bool forced)
{
bool softUpdate = true;
if (!_wasRealGroup || _realMemberCount != _groupMembers.Count || forced)
{
_groupMembers.Clear();
softUpdate = false;
}
string[] keys = dataMap.Keys.ToArray();
for (int i = 0; i < keys.Length; i++)
{
if (!dataMap.TryGetValue(keys[i], out InternalMemberData? data) || data == null)
{
continue;
}
bool isPlayer = data.ObjectId == player.GameObjectId;
bool isLeader = IsPartyLeader(data.Order);
EnmityLevel enmity = EnmityForIndex(data.Index);
PartyMemberStatus status = data.Status != null ? StatusForMember(data.Status, data.Name) : PartyMemberStatus.None;
ReadyCheckStatus readyCheckStatus = GetReadyCheckStatus((ulong)data.ContentId);
if (softUpdate)
{
IPartyFramesMember groupMember = _groupMembers.ElementAt(i);
groupMember.Update(enmity, status, readyCheckStatus, isLeader);
}
else
{
PartyFramesMember partyMember;
var member = GetDalamudPartyMember(data.Name);
if (member.HasValue && member.Value.Item1 is DalamudPartyMember dalamudPartyMember)
{
partyMember = new PartyFramesMember(dalamudPartyMember, i, data.Order, enmity, status, readyCheckStatus, isLeader);
}
else
{
partyMember = new PartyFramesMember(data.ObjectId, i, data.Order, enmity, status, readyCheckStatus, isLeader);
}
_groupMembers.Add(partyMember);
}
}
// player's chocobo (always last)
if (_config.ShowChocobo)
{
IGameObject? companion = Utils.GetBattleChocobo(player);
if (softUpdate && _groupMembers.FirstOrDefault(o => o.IsChocobo) is PartyFramesMember chocoboMember)
{
if (companion is ICharacter)
{
chocoboMember.Update(EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, false);
}
else
{
_groupMembers.Remove(chocoboMember);
}
}
else if (companion is ICharacter companionCharacter)
{
_groupMembers.Add(new PartyFramesMember(companionCharacter, _groupMemberCount, 10, EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, false, true));
}
}
if (!softUpdate)
{
SortGroupMembers(player);
MembersChangedEvent?.Invoke(this);
}
}
private void SortGroupMembers(IPlayerCharacter? player = null)
{
_sortedGroupMembers.Clear();
_sortedGroupMembers.AddRange(_groupMembers);
_sortedGroupMembers.Sort((a, b) =>
{
if (a.Order == b.Order)
{
if (a.ObjectId == player?.GameObjectId)
{
return 1;
}
else if (b.ObjectId == player?.GameObjectId)
{
return -1;
}
return a.Name.CompareTo(b.Name);
}
if (a.Order < b.Order)
{
return -1;
}
return 1;
});
}
private (DalamudPartyMember?, int)? GetDalamudPartyMember(string name)
{
for (int i = 0; i < Plugin.PartyList.Length; i++)
{
DalamudPartyMember? member = Plugin.PartyList[i];
if (member != null && member.Name.ToString() == name)
{
return (member, i);
}
}
return null;
}
private void UpdateTrackers()
{
_raiseTracker.Update(_groupMembers);
_invulnTracker.Update(_groupMembers);
_cleanseTracker.Update(_groupMembers);
}
#region utils
private bool IsPartyLeader(int index)
{
if (PartyListAddon == null)
{
return false;
}
// we use the icon Y coordinate in the party list to know the index (lmao)
uint partyLeadIndex = (uint)PartyListAddon->LeaderMarkResNode->ChildNode->Y / 40;
return index == partyLeadIndex;
}
private PartyMemberStatus StatusForCrossWorldMember(string statusStr)
{
// offline status
if (statusStr.Contains(_offlineString, StringComparison.InvariantCultureIgnoreCase))
{
return PartyMemberStatus.Offline;
}
return PartyMemberStatus.None;
}
private PartyMemberStatus StatusForMember(string statusStr, string name)
{
// offline status
if (statusStr.Contains(_offlineString, StringComparison.InvariantCultureIgnoreCase))
{
return PartyMemberStatus.Offline;
}
// viewing cutscene status
for (int i = 0; i < _mainGroup.MemberCount; i++)
{
if (_mainGroup.PartyMembers[i].NameString == name)
{
if ((_mainGroup.PartyMembers[i].Flags & 0x10) != 0)
{
return PartyMemberStatus.ViewingCutscene;
}
break;
}
}
return PartyMemberStatus.None;
}
private EnmityLevel EnmityForIndex(int index)
{
if (PartyListAddon == null || index < 0 || index > 7)
{
return EnmityLevel.Last;
}
EnmityLevel enmityLevel = (EnmityLevel)PartyListAddon->PartyMembers[index].EmnityByte;
if (enmityLevel == EnmityLevel.Leader && PartyListAddon->EnmityLeaderIndex != index)
{
enmityLevel = EnmityLevel.Last;
}
return enmityLevel;
}
private EnmityLevel EnmityForTrustMemberIndex(int index)
{
if (PartyListAddon == null || index < 0 || index > 6)
{
return EnmityLevel.Last;
}
return (EnmityLevel)PartyListAddon->TrustMembers[index].EmnityByte;
}
private static unsafe string? GetPartyListTitle()
{
AgentModule* agentModule = AgentModule.Instance();
if (agentModule == null) { return ""; }
AgentHUD* agentHUD = agentModule->GetAgentHUD();
if (agentHUD == null) { return ""; }
Lumina.Excel.ExcelSheet<Addon> sheet = Plugin.DataManager.GetExcelSheet<Addon>();
if (sheet.TryGetRow(agentHUD->PartyTitleAddonId, out Addon row))
{
return row.Text.ToString();
}
return null;
}
#endregion
#region events
private void OnConfigPropertyChanged(object sender, OnChangeBaseArgs args)
{
if (args.PropertyName == "Preview")
{
UpdatePreview();
}
}
public void UpdatePreview()
{
_iconsConfig.Sign.Preview = _config.Preview;
if (!_config.Preview)
{
_groupMembers.Clear();
MembersChangedEvent?.Invoke(this);
return;
}
// fill list with fake members for UI testing
_groupMembers.Clear();
if (_config.Preview)
{
int count = FakePartyFramesMember.RNG.Next(4, 9);
for (int i = 0; i < count; i++)
{
_groupMembers.Add(new FakePartyFramesMember(i));
}
}
SortGroupMembers();
MembersChangedEvent?.Invoke(this);
}
#endregion
}
internal class InternalMemberData
{
internal uint ObjectId = 0;
internal long ContentId = 0;
internal string Name = "";
internal uint JobId = 0;
internal int Order = 0;
internal int Index = 0;
internal string? Status = null;
public InternalMemberData()
{
}
}
[StructLayout(LayoutKind.Explicit, Size = 28)]
public unsafe struct PartyListMemberRawInfo
{
[FieldOffset(0x00)] public byte* NamePtr;
[FieldOffset(0x08)] public long ContentId;
[FieldOffset(0x10)] public uint ObjectId;
// some kind of type
// 1 = player
// 2 = party member?
// 3 = unknown
// 4 = chocobo
// 5 = summon?
[FieldOffset(0x14)] public byte Type;
[FieldOffset(0x18)] public byte Order;
public string Name => Marshal.PtrToStringUTF8(new IntPtr(NamePtr)) ?? "";
}
}
+171
View File
@@ -0,0 +1,171 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Helpers;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using System.Collections.Generic;
namespace HSUI.Interface.Party
{
public static class PartyOrderHelper
{
private enum PartySortingSetting
{
Tank_Healer_DPS = 0,
Tank_DPS_Healer = 1,
Healer_Tank_DPS = 2,
Healer_DPS_Tank = 3,
DPS_Tank_Healer = 4,
DPS_Healer_Tank = 5,
Count = 6
}
private class PartyRoles
{
internal int Tank;
internal int Healer;
internal int DPS;
internal int Other;
public PartyRoles()
{
Tank = 0;
Healer = 0;
DPS = 0;
Other = 0;
}
public PartyRoles(int tank, int healer, int dps, int other)
{
Tank = tank;
Healer = healer;
DPS = dps;
Other = other;
}
}
// calcualates the position for the player if they select the
// option to always appear as the first of their current role
// in the party frames
public static int? GetRoleFirstOrder(List<IPartyFramesMember> members)
{
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
if (player == null) { return null; }
JobRoles role = JobsHelper.RoleForJob(player.ClassJob.RowId);
PartySortingSetting? setting = GetPartySortingSetting(role);
if (!setting.HasValue) { return null; }
PartyRoles rolesCount = GetPartyCountByRole(members);
PartyRoles roleWeights = GetRoleWeights(role, setting.Value);
return rolesCount.Tank * roleWeights.Tank +
rolesCount.Healer * roleWeights.Healer +
rolesCount.DPS * roleWeights.DPS +
rolesCount.Other * roleWeights.Other;
}
private static unsafe PartySortingSetting? GetPartySortingSetting(JobRoles role)
{
ConfigModule* config = ConfigModule.Instance();
if (config == null) { return null; }
ConfigOption option;
switch (role)
{
case JobRoles.Tank: option = ConfigOption.PartyListSortTypeTank; break;
case JobRoles.Healer: option = ConfigOption.PartyListSortTypeHealer; break;
case JobRoles.DPSMelee:
case JobRoles.DPSRanged:
case JobRoles.DPSCaster: option = ConfigOption.PartyListSortTypeDps; break;
default: option = ConfigOption.PartyListSortTypeOther; break;
}
Framework* framework = Framework.Instance();
if (framework == null || framework->SystemConfig.SystemConfigBase.UiConfig.ConfigCount <= (int)option) {
return PartySortingSetting.Tank_Healer_DPS;
}
uint value = framework->SystemConfig.SystemConfigBase.UiConfig.ConfigEntry[(int)option].Value.UInt;
if (value < 0 || value > (int)PartySortingSetting.Count) { return null; }
return (PartySortingSetting)value;
}
private static unsafe PartyRoles GetPartyCountByRole(List<IPartyFramesMember> members)
{
PartyRoles rolesCount = new PartyRoles();
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
if (player == null) { return rolesCount; }
foreach (IPartyFramesMember member in members)
{
if (member.ObjectId == player.GameObjectId) { continue; }
JobRoles role = JobsHelper.RoleForJob(member.JobId);
switch (role)
{
case JobRoles.Tank: rolesCount.Tank++; break;
case JobRoles.Healer: rolesCount.Healer++; break;
case JobRoles.DPSMelee:
case JobRoles.DPSRanged:
case JobRoles.DPSCaster: rolesCount.DPS++; break;
default: rolesCount.Other++; break;
}
}
return rolesCount;
}
private static unsafe PartyRoles GetRoleWeights(JobRoles role, PartySortingSetting setting)
{
if (role == JobRoles.Crafter || role == JobRoles.Gatherer || role == JobRoles.Unknown)
{
return new PartyRoles(1, 1, 1, 0);
}
JobRoles mapRole = role == JobRoles.DPSRanged || role == JobRoles.DPSCaster ? JobRoles.DPSMelee : role;
return RoleWeights[mapRole][setting];
}
private static Dictionary<JobRoles, Dictionary<PartySortingSetting, PartyRoles>> RoleWeights = new Dictionary<JobRoles, Dictionary<PartySortingSetting, PartyRoles>>()
{
[JobRoles.Tank] = new Dictionary<PartySortingSetting, PartyRoles>()
{
[PartySortingSetting.Tank_Healer_DPS] = new PartyRoles(),
[PartySortingSetting.Tank_DPS_Healer] = new PartyRoles(),
[PartySortingSetting.Healer_Tank_DPS] = new PartyRoles(0, 1, 0, 0),
[PartySortingSetting.Healer_DPS_Tank] = new PartyRoles(0, 1, 1, 0),
[PartySortingSetting.DPS_Tank_Healer] = new PartyRoles(0, 0, 1, 0),
[PartySortingSetting.DPS_Healer_Tank] = new PartyRoles(0, 1, 1, 0)
},
[JobRoles.Healer] = new Dictionary<PartySortingSetting, PartyRoles>()
{
[PartySortingSetting.Tank_Healer_DPS] = new PartyRoles(1, 0, 0, 0),
[PartySortingSetting.Tank_DPS_Healer] = new PartyRoles(1, 0, 1, 0),
[PartySortingSetting.Healer_Tank_DPS] = new PartyRoles(),
[PartySortingSetting.Healer_DPS_Tank] = new PartyRoles(),
[PartySortingSetting.DPS_Tank_Healer] = new PartyRoles(1, 0, 1, 0),
[PartySortingSetting.DPS_Healer_Tank] = new PartyRoles(0, 0, 1, 0)
},
[JobRoles.DPSMelee] = new Dictionary<PartySortingSetting, PartyRoles>()
{
[PartySortingSetting.Tank_Healer_DPS] = new PartyRoles(1, 1, 0, 0),
[PartySortingSetting.Tank_DPS_Healer] = new PartyRoles(1, 0, 0, 0),
[PartySortingSetting.Healer_Tank_DPS] = new PartyRoles(1, 1, 0, 0),
[PartySortingSetting.Healer_DPS_Tank] = new PartyRoles(0, 1, 0, 0),
[PartySortingSetting.DPS_Tank_Healer] = new PartyRoles(),
[PartySortingSetting.DPS_Healer_Tank] = new PartyRoles()
}
};
}
}
+156
View File
@@ -0,0 +1,156 @@
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Info;
using Dalamud.Bindings.ImGui;
using System;
namespace HSUI.Interface.Party
{
public enum ReadyCheckStatus
{
Ready = 0,
NotReady = 1,
None = 2
}
public class PartyReadyCheckHelper : IDisposable
{
private delegate void ReadyCheckDelegate(IntPtr ptr);
private Hook<AgentReadyCheck.Delegates.InitiateReadyCheck>? _onReadyCheckStartHook;
private Hook<AgentReadyCheck.Delegates.EndReadyCheck>? _onReadyCheckEndHook;
private delegate void ActorControlDelegate(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12);
private Hook<ActorControlDelegate>? _actorControlHook;
private bool _readyCheckOngoing = false;
private double _lastReadyCheckEndTime = -1;
public unsafe PartyReadyCheckHelper()
{
try
{
_onReadyCheckStartHook = Plugin.GameInteropProvider.HookFromAddress<AgentReadyCheck.Delegates.InitiateReadyCheck>(
AgentReadyCheck.MemberFunctionPointers.InitiateReadyCheck,
OnReadyCheckStart
);
_onReadyCheckStartHook?.Enable();
_onReadyCheckEndHook = Plugin.GameInteropProvider.HookFromAddress<AgentReadyCheck.Delegates.EndReadyCheck>(
AgentReadyCheck.MemberFunctionPointers.EndReadyCheck,
OnReadycheckEnd
);
_onReadyCheckEndHook?.Enable();
_actorControlHook = Plugin.GameInteropProvider.HookFromSignature<ActorControlDelegate>(
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
OnActorControl
);
_actorControlHook?.Enable();
}
catch (Exception e)
{
Plugin.Logger.Error("Error initiating ready check sigs!!!\n" + e.Message);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
_onReadyCheckStartHook?.Disable();
_onReadyCheckStartHook?.Dispose();
_onReadyCheckEndHook?.Disable();
_onReadyCheckEndHook?.Dispose();
_actorControlHook?.Disable();
_actorControlHook?.Dispose();
}
private unsafe void OnReadyCheckStart(AgentReadyCheck *ptr)
{
_onReadyCheckStartHook?.Original(ptr);
_readyCheckOngoing = true;
_lastReadyCheckEndTime = -1;
}
private unsafe void OnReadycheckEnd(AgentReadyCheck *ptr)
{
_onReadyCheckEndHook?.Original(ptr);
_lastReadyCheckEndTime = ImGui.GetTime();
}
private void OnActorControl(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12)
{
_actorControlHook?.Original(entityId, type, buffID, direct, actionId, sourceId, arg7, arg8, arg9, arg10, targetId, arg12);
// I'm not exactly sure what id == 503 means, but its always triggered when the fight starts
// which is all I care about
if (type == 503)
{
_readyCheckOngoing = false;
}
}
public void Update(double maxDuration)
{
if (_readyCheckOngoing &&
_lastReadyCheckEndTime != -1 &&
ImGui.GetTime() - _lastReadyCheckEndTime >= maxDuration)
{
_readyCheckOngoing = false;
}
}
public unsafe ReadyCheckStatus GetStatusForContentId(ulong contentId)
{
if (!_readyCheckOngoing)
{
return ReadyCheckStatus.None;
}
try
{
for (int i = 0; i < 8; i++)
{
ReadyCheckEntry entry = AgentReadyCheck.Instance()->ReadyCheckEntries[i];
if (entry.ContentId == contentId)
{
return ParseStatus(entry);
}
}
}
catch { }
return ReadyCheckStatus.None;
}
private ReadyCheckStatus ParseStatus(ReadyCheckEntry entry)
{
if (entry.Status == FFXIVClientStructs.FFXIV.Client.UI.Agent.ReadyCheckStatus.Ready)
{
return ReadyCheckStatus.Ready;
}
else if (entry.Status == FFXIVClientStructs.FFXIV.Client.UI.Agent.ReadyCheckStatus.NotReady ||
entry.Status == FFXIVClientStructs.FFXIV.Client.UI.Agent.ReadyCheckStatus.MemberNotPresent)
{
return ReadyCheckStatus.NotReady;
}
return ReadyCheckStatus.None;
}
}
}
+220
View File
@@ -0,0 +1,220 @@
using HSUI.Helpers;
using HSUI.Interface.Party;
using Dalamud.Bindings.ImGui;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace HSUI.Interface.PartyCooldowns
{
public enum PartyCooldownEnabled
{
PartyCooldownsAndPartyFrames = 0,
PartyCooldowns = 1,
PartyFrames = 2,
Disabled = 3
}
public class PartyCooldown
{
public readonly PartyCooldownData Data;
public readonly uint SourceId;
public readonly uint MemberLevel;
public readonly IPartyFramesMember? Member;
public double LastTimeUsed = 0;
public double OverridenCooldownStartTime = -1;
public bool IgnoreNextUse = false;
public PartyCooldown(PartyCooldownData data, uint sourceID, uint level, IPartyFramesMember? member)
{
Data = data;
SourceId = sourceID;
MemberLevel = level;
Member = member;
}
public float EffectTimeRemaining()
{
int duration = GetEffectDuration();
double timeSinceUse = ImGui.GetTime() - LastTimeUsed;
if (IgnoreNextUse || timeSinceUse > duration)
{
return 0;
}
return duration - (float)timeSinceUse;
}
public float CooldownTimeRemaining()
{
float cooldown = GetCooldown();
double timeSinceUse = OverridenCooldownStartTime != -1 ? ImGui.GetTime() - OverridenCooldownStartTime : ImGui.GetTime() - LastTimeUsed;
if (timeSinceUse > cooldown)
{
OverridenCooldownStartTime = -1;
LastTimeUsed = 0;
return 0;
}
return cooldown - (float)timeSinceUse;
}
public int GetCooldown()
{
// special case for troubadour, shield samba and tactician
if (Data.ActionId == 7405 || Data.ActionId == 16012 || Data.ActionId == 16889)
{
return MemberLevel < 88 ? Data.CooldownDuration : 90;
}
// special case for swiftcast
else if (Data.ActionId == 7561)
{
return MemberLevel < 94 ? Data.CooldownDuration : 40;
}
return Data.CooldownDuration;
}
public int GetEffectDuration()
{
// special case for reprisal, feint and addle
if (MemberLevel < 98) { return Data.EffectDuration; }
if (Data.ActionId != 7535 && Data.ActionId != 7549 && Data.ActionId != 7560) { return Data.EffectDuration; }
return 15;
}
public string TooltipText()
{
int duration = GetEffectDuration();
string effectDuration = duration > 0 ? $"Duration: {duration}s \n" : "";
return $"{effectDuration}Recast Time: {GetCooldown()}s";
}
}
public class PartyCooldownData : IEquatable<PartyCooldownData>
{
public PartyCooldownEnabled EnabledV2 = PartyCooldownEnabled.PartyCooldownsAndPartyFrames;
public uint ActionId = 0;
public uint RequiredLevel = 0;
public uint DisabledAfterLevel = 0;
public uint JobId = 0; // keep this for backwards compatibility
public List<uint>? JobIds = null;
public JobRoles Role = JobRoles.Unknown; // keep this for backwards compatibility
public List<JobRoles>? Roles = null;
public HashSet<uint> ExcludedJobIds = new();
public int CooldownDuration = 0;
public int EffectDuration = 0;
public int Priority = 0;
public int Column = 1;
public List<uint> SharedActionIds = new();
[JsonIgnore] public uint IconId = 0;
[JsonIgnore] public string Name = "";
[JsonIgnore] public string? OverriddenCooldownText = null;
[JsonIgnore] public string? OverriddenDurationText = null;
public virtual bool IsUsableBy(uint jobId)
{
JobRoles roleForJob = JobsHelper.RoleForJob(jobId);
if (Roles != null)
{
foreach (JobRoles role in Roles)
{
if (role == roleForJob)
{
return true;
}
}
return false;
}
if (Role != JobRoles.Unknown)
{
return Role == roleForJob;
}
if (JobIds != null)
{
foreach (uint id in JobIds)
{
if (id == jobId)
{
return true;
}
}
}
return JobId == jobId;
}
public bool HasRole(JobRoles role)
{
if (Roles != null)
{
return Roles.Contains(role);
}
if (Role != JobRoles.Unknown)
{
return Role == role;
}
if (JobIds != null)
{
foreach (uint jobId in JobIds)
{
JobRoles roleForJob = JobsHelper.RoleForJob(jobId);
if (roleForJob == role)
{
return true;
}
}
return false;
}
return JobsHelper.RoleForJob(JobId) == role;
}
public bool IsEnabledForPartyCooldowns()
{
return EnabledV2 == PartyCooldownEnabled.PartyCooldownsAndPartyFrames ||
EnabledV2 == PartyCooldownEnabled.PartyCooldowns;
}
public bool IsEnabledForPartyFrames()
{
return EnabledV2 == PartyCooldownEnabled.PartyCooldownsAndPartyFrames ||
EnabledV2 == PartyCooldownEnabled.PartyFrames;
}
public bool Equals(PartyCooldownData? other)
{
if (other == null) { return false; }
return
ActionId == other.ActionId &&
RequiredLevel == other.RequiredLevel &&
DisabledAfterLevel == other.DisabledAfterLevel &&
JobId == other.JobId &&
(JobIds == null && other.JobIds == null || (JobIds != null && other.JobIds != null && JobIds.Equals(other.JobIds))) &&
Role == other.Role &&
(Roles == null && other.Roles == null || (Roles != null && other.Roles != null && Roles.Equals(other.Roles))) &&
CooldownDuration == other.CooldownDuration &&
EffectDuration == other.EffectDuration &&
SharedActionIds == other.SharedActionIds;
}
}
}
@@ -0,0 +1,884 @@
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.GeneralElements;
using Dalamud.Bindings.ImGui;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using LuminaAction = Lumina.Excel.Sheets.Action;
namespace HSUI.Interface.PartyCooldowns
{
public enum PartyCooldownsGrowthDirection
{
Down = 0,
Up,
Right,
Left
}
[Exportable(false)]
[Section("Party Cooldowns", true)]
[SubSection("General", 0)]
public class PartyCooldownsConfig : MovablePluginConfigObject
{
public new static PartyCooldownsConfig DefaultConfig()
{
var config = new PartyCooldownsConfig();
config.Position = new Vector2(-ImGui.GetMainViewport().Size.X / 2 + 100, -ImGui.GetMainViewport().Size.Y / 2 + 100);
return config;
}
[Checkbox("Preview", isMonitored = true)]
[Order(4)]
public bool Preview = false;
[Combo("Sections Growth Direction", "Down", "Up", "Right", "Left", spacing = true)]
[Order(20)]
public PartyCooldownsGrowthDirection GrowthDirection = PartyCooldownsGrowthDirection.Down;
[DragInt2("Padding", min = -1000, max = 1000)]
[Order(15)]
public Vector2 Padding = new Vector2(0, -1);
[Checkbox("Tooltips", spacing = true)]
[Order(16)]
public bool ShowTooltips = true;
[Checkbox("Show Only in Duties", spacing = true, isMonitored = true)]
[Order(20)]
public bool ShowOnlyInDuties = true;
[Checkbox("Show When Solo", isMonitored = true)]
[Order(21)]
public bool ShowWhenSolo = false;
[NestedConfig("Visibility", 200)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
}
[Disableable(false)]
[Exportable(false)]
[DisableParentSettings("Position", "Anchor", "HideWhenInactive", "FillColor", "Background", "FillDirection")]
[Section("Party Cooldowns", true)]
[SubSection("Cooldown Bar", 0)]
public class PartyCooldownsBarConfig : BarConfig
{
[Checkbox("Show Bar", spacing = true)]
[Order(70)]
public bool ShowBar = true;
[ColorEdit4("Available Color")]
[Order(71, collapseWith = nameof(ShowBar))]
public PluginConfigColor AvailableColor = new PluginConfigColor(new Vector4(0f / 255f, 150f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Available Background Color")]
[Order(72, collapseWith = nameof(ShowBar))]
public PluginConfigColor AvailableBackgroundColor = new PluginConfigColor(new Vector4(0f / 255f, 150f / 255f, 0f / 255f, 25f / 100f));
[ColorEdit4("Recharging Color")]
[Order(73, collapseWith = nameof(ShowBar))]
public PluginConfigColor RechargingColor = new PluginConfigColor(new Vector4(150f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
[ColorEdit4("Recharging Background Color")]
[Order(74, collapseWith = nameof(ShowBar))]
public PluginConfigColor RechargingBackgroundColor = new PluginConfigColor(new Vector4(150f / 255f, 0f / 255f, 0f / 255f, 25f / 100f));
[Checkbox("Use Job Colors")]
[Order(75, collapseWith = nameof(ShowBar))]
public bool UseJobColors = false;
[Checkbox("Show Icon", spacing = true)]
[Order(80)]
public bool ShowIcon = true;
[Checkbox("Show Icon Cooldown Animation")]
[Order(81, collapseWith = nameof(ShowIcon))]
public bool ShowIconCooldownAnimation = false;
[Checkbox("Change Icon Border When Active")]
[Order(82, collapseWith = nameof(ShowIcon))]
public bool ChangeIconBorderWhenActive = false;
[ColorEdit4("Icon Active Border Color")]
[Order(83, collapseWith = nameof(ChangeIconBorderWhenActive))]
public PluginConfigColor IconActiveBorderColor = new PluginConfigColor(new Vector4(255f / 255f, 200f / 255f, 35f / 255f, 100f / 100f));
[DragInt("Icon Active Border Thickness", min = 1, max = 10)]
[Order(84, collapseWith = nameof(ChangeIconBorderWhenActive))]
public int IconActiveBorderThickness = 3;
[Checkbox("Change Labels Color When Active", spacing = true)]
[Order(85)]
public bool ChangeLabelsColorWhenActive = false;
[ColorEdit4("Labels Active Color")]
[Order(86, collapseWith = nameof(ChangeLabelsColorWhenActive))]
public PluginConfigColor LabelsActiveColor = new PluginConfigColor(new Vector4(255f / 255f, 200f / 255f, 35f / 255f, 100f / 100f));
[NestedConfig("Name Label", 100)]
public EditableLabelConfig NameLabel = new EditableLabelConfig(new Vector2(5, 0), "[name:initials]", DrawAnchor.Left, DrawAnchor.Left);
[NestedConfig("Time Label", 105)]
public PartyCooldownTimeLabelConfig TimeLabel = new PartyCooldownTimeLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
public new static PartyCooldownsBarConfig DefaultConfig()
{
Vector2 size = new Vector2(150, 24);
PartyCooldownsBarConfig config = new PartyCooldownsBarConfig(Vector2.Zero, size, PluginConfigColor.Empty);
config.NameLabel.FontID = FontsConfig.DefaultMediumFontKey;
config.TimeLabel.FontID = FontsConfig.DefaultMediumFontKey;
config.TimeLabel.NumberFormat = 1;
return config;
}
public PartyCooldownsBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
: base(position, size, fillColor, fillDirection)
{
}
}
public class PartyCooldownTimeLabelConfig : NumericLabelConfig
{
public PartyCooldownTimeLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
: base(position, text, frameAnchor, textAnchor)
{
}
[Checkbox("Show Effect Duration", spacing = true)]
[Order(70)]
public bool ShowEffectDuration = true;
[Checkbox("Show Remainin Cooldown")]
[Order(71)]
public bool ShowRemainingCooldown = true;
}
[Exportable(false)]
[Disableable(false)]
[Section("Party Cooldowns", true)]
[SubSection("Cooldowns Tracked", 0)]
public class PartyCooldownsDataConfig : PluginConfigObject
{
public List<PartyCooldownData> Cooldowns = new List<PartyCooldownData>();
private List<uint> _removedIds = new() { 7398 };
private JobRoles _roleFilter = JobRoles.Unknown;
private uint _tankFilter = 0;
private uint _healerFilter = 0;
private uint _meleeFilter = 0;
private uint _rangedFilter = 0;
private uint _casterFilter = 0;
private bool _needsPopupOpen = false;
private PartyCooldownData? _popupCooldown = null;
public const int ColumnCount = 5;
public delegate void CooldownsDataChangedEventHandler(PartyCooldownsDataConfig sender);
public event CooldownsDataChangedEventHandler? CooldownsDataChangedEvent;
public delegate void CooldownsDataEnabledChangedEventHandler(PartyCooldownsDataConfig sender);
public event CooldownsDataEnabledChangedEventHandler? CooldownsDataEnabledChangedEvent;
public new static PartyCooldownsDataConfig DefaultConfig() => new PartyCooldownsDataConfig();
private string[] _enabledOptions = new string[] {
"Enabled",
"Party Cooldowns Only",
"Party Frames Only",
"Disabled"
};
public void UpdateDataIfNeeded()
{
bool needsSave = false;
// remove old cooldowns that are not valid anymore
foreach (uint id in _removedIds)
{
PartyCooldownData? data = Cooldowns.FirstOrDefault(data => data.ActionId == id);
if (data != null)
{
Cooldowns.Remove(data);
}
}
// update data using the game files
foreach (uint key in DefaultCooldowns.Keys)
{
PartyCooldownData? data = Cooldowns.FirstOrDefault(data => data.ActionId == key);
PartyCooldownData defaultData = DefaultCooldowns[key];
if (data == null)
{
Cooldowns.Add(defaultData);
needsSave = true;
}
else if (data != null && !data.Equals(defaultData))
{
data.RequiredLevel = defaultData.RequiredLevel;
data.DisabledAfterLevel = defaultData.DisabledAfterLevel;
data.JobId = defaultData.JobId;
data.JobIds = defaultData.JobIds;
data.Role = defaultData.Role;
data.Roles = defaultData.Roles;
data.CooldownDuration = defaultData.CooldownDuration;
data.EffectDuration = defaultData.EffectDuration;
data.SharedActionIds = defaultData.SharedActionIds;
needsSave = true;
}
}
ExcelSheet<LuminaAction>? sheet = Plugin.DataManager.GetExcelSheet<LuminaAction>();
ExcelSheet<ActionTransient>? descriptionsSheet = Plugin.DataManager.GetExcelSheet<ActionTransient>();
foreach (PartyCooldownData cooldown in Cooldowns)
{
LuminaAction? action = sheet?.GetRow(cooldown.ActionId);
if (!action.HasValue) { continue; }
// get real cooldown from data
// keep hardcoded value for technical finish
if (action.Value.Recast100ms > 0 && cooldown.ActionId != 16004)
{
cooldown.CooldownDuration = action.Value.Recast100ms / 10;
}
// not happy about this but didn't want to over-complicate things
// special case for troubadour, shield samba and tactician
if (cooldown.ActionId == 7405 || cooldown.ActionId == 16012 || cooldown.ActionId == 16889)
{
cooldown.OverriddenCooldownText = "90-120";
}
// reprisal, feint, addle
else if (cooldown.ActionId == 7535 || cooldown.ActionId == 7549 || cooldown.ActionId == 7560)
{
cooldown.OverriddenDurationText = "10-15";
}
// swiftcast
else if (cooldown.ActionId == 7561)
{
cooldown.OverriddenCooldownText = "40-60";
}
// special case for starry muse
if (cooldown.ActionId == 35349)
{
cooldown.IconId = 3826;
}
else
{
cooldown.IconId = action.Value.Icon;
}
cooldown.Name = action.Value.Name.ToString();
}
if (needsSave)
{
ConfigurationManager.Instance.SaveConfigurations(true);
}
}
[ManualDraw]
public bool Draw(ref bool changed)
{
ImGuiHelper.NewLineAndTab();
// filter
ImGui.SameLine();
ImGui.Text("Filter: ");
DrawFilter("All", JobRoles.Unknown);
DrawFilter("Tanks", JobRoles.Tank);
DrawFilter("Healers", JobRoles.Healer);
DrawFilter("Melee", JobRoles.DPSMelee);
DrawFilter("Ranged", JobRoles.DPSRanged);
DrawFilter("Casters", JobRoles.DPSCaster);
DrawJobFilters();
// table
ImGuiHelper.NewLineAndTab();
ImGui.SameLine();
var flags =
ImGuiTableFlags.RowBg |
ImGuiTableFlags.Borders |
ImGuiTableFlags.BordersOuter |
ImGuiTableFlags.BordersInner |
ImGuiTableFlags.ScrollY |
ImGuiTableFlags.SizingFixedSame;
ExcelSheet<LuminaAction>? sheet = Plugin.DataManager.GetExcelSheet<LuminaAction>();
var iconSize = new Vector2(30, 30);
if (ImGui.BeginTable("##DelvUI_PartyCooldownsTable", 8, flags, new Vector2(900, 500)))
{
ImGui.TableSetupColumn("Enabled", ImGuiTableColumnFlags.WidthStretch, 22, 0);
ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthStretch, 5, 1);
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 24, 2);
ImGui.TableSetupColumn("Cooldown", ImGuiTableColumnFlags.WidthStretch, 8, 3);
ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthStretch, 8, 4);
ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthStretch, 11, 5);
ImGui.TableSetupColumn("Section", ImGuiTableColumnFlags.WidthStretch, 11, 6);
ImGui.TableSetupColumn("Exclude Jobs", ImGuiTableColumnFlags.WidthStretch, 11, 7);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
foreach (PartyCooldownData cooldown in Cooldowns)
{
// apply filter
if (_roleFilter != JobRoles.Unknown)
{
if (!cooldown.HasRole(_roleFilter)) { continue; }
if (_roleFilter == JobRoles.Tank && _tankFilter != 0 && !cooldown.IsUsableBy(_tankFilter)) { continue; }
if (_roleFilter == JobRoles.Healer && _healerFilter != 0 && !cooldown.IsUsableBy(_healerFilter)) { continue; }
if (_roleFilter == JobRoles.DPSMelee && _meleeFilter != 0 && !cooldown.IsUsableBy(_meleeFilter)) { continue; }
if (_roleFilter == JobRoles.DPSRanged && _rangedFilter != 0 && !cooldown.IsUsableBy(_rangedFilter)) { continue; }
if (_roleFilter == JobRoles.DPSCaster && _casterFilter != 0 && !cooldown.IsUsableBy(_casterFilter)) { continue; }
}
LuminaAction? action = sheet?.GetRow(cooldown.ActionId);
ImGui.PushID(cooldown.ActionId.ToString());
ImGui.TableNextRow(ImGuiTableRowFlags.None, iconSize.Y);
// enabled
if (ImGui.TableSetColumnIndex(0))
{
ImGui.PushItemWidth(178);
ImGui.SetCursorPos(new Vector2(ImGui.GetCursorPosX() + 2, ImGui.GetCursorPosY() + 2));
int enabled = (int)cooldown.EnabledV2;
if (ImGui.Combo($"##{cooldown.ActionId}_enabled", ref enabled, _enabledOptions))
{
changed = true;
cooldown.EnabledV2 = (PartyCooldownEnabled)enabled;
CooldownsDataEnabledChangedEvent?.Invoke(this);
}
}
// icon
if (ImGui.TableSetColumnIndex(1) && action != null)
{
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 3);
DrawHelper.DrawIcon<LuminaAction>(action, ImGui.GetCursorPos(), iconSize, false, false);
}
// name
if (ImGui.TableSetColumnIndex(2))
{
ImGui.Text(cooldown.Name);
}
// cooldown
if (ImGui.TableSetColumnIndex(3))
{
string cooldownText = cooldown.OverriddenCooldownText != null ? cooldown.OverriddenCooldownText : $"{cooldown.CooldownDuration}";
ImGui.Text(cooldownText);
}
// duration
if (ImGui.TableSetColumnIndex(4))
{
string durationText = cooldown.OverriddenDurationText != null ? cooldown.OverriddenDurationText : $"{cooldown.EffectDuration}";
ImGui.Text(durationText);
}
// priority
if (ImGui.TableSetColumnIndex(5))
{
ImGui.PushItemWidth(86);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2);
if (ImGui.DragInt($"##{cooldown.ActionId}_priority", ref cooldown.Priority, 1, 0, 100))
{
cooldown.Priority = Math.Clamp(cooldown.Priority, 0, 100);
changed = true;
CooldownsDataChangedEvent?.Invoke(this);
}
ImGuiHelper.SetTooltip("Priority determines which cooldows show first on the list.");
}
// column
if (ImGui.TableSetColumnIndex(6))
{
ImGui.PushItemWidth(86);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2);
if (ImGui.DragInt($"##{cooldown.ActionId}_column", ref cooldown.Column, 0.1f, 1, ColumnCount, "%i", ImGuiSliderFlags.NoInput))
{
changed = true;
CooldownsDataChangedEvent?.Invoke(this);
}
ImGuiHelper.SetTooltip("Allows to separate cooldowns in different columns.");
}
// exlude
if (ImGui.TableSetColumnIndex(7) && (cooldown.Roles != null || cooldown.Role != JobRoles.Unknown))
{
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2);
if (ImGui.Button("Select##Exclude", new Vector2(86, 24)))
{
_needsPopupOpen = true;
_popupCooldown = cooldown;
}
}
ImGui.PopID();
}
ImGui.EndTable();
}
if (_needsPopupOpen)
{
ImGui.OpenPopup("Exclude Jobs##HSUI");
_needsPopupOpen = false;
}
if (_popupCooldown != null)
{
DrawJobsListForCooldown(_popupCooldown);
}
return false;
}
private List<uint> IgnoredJobIds = new List<uint>()
{
JobIDs.GLA, JobIDs.MRD,
JobIDs.CNJ, JobIDs.WHM,
JobIDs.PGL, JobIDs.LNC, JobIDs.ROG,
JobIDs.ARC,
JobIDs.THM, JobIDs.ACN, JobIDs.BLU,
};
private void DrawJobsListForCooldown(PartyCooldownData cooldown)
{
List<JobRoles> roles = cooldown.Roles ?? new List<JobRoles>() { cooldown.Role };
List<uint> jobIds = new List<uint>();
foreach (JobRoles role in roles)
{
jobIds.AddRange(JobsHelper.JobsByRole[role]);
}
jobIds = jobIds.Where(id => !IgnoredJobIds.Contains(id)).ToList();
string title = cooldown.Name + ":";
float width = Math.Max(100, ImGui.CalcTextSize(title).X + 10);
ImGui.SetNextWindowSize(new(width, jobIds.Count * 30 + 32));
if (ImGui.BeginPopup("Exclude Jobs##HSUI", ImGuiWindowFlags.NoMove))
{
ImGui.Text(title);
foreach (uint jobId in jobIds)
{
bool selected = !cooldown.ExcludedJobIds.Contains(jobId);
if (ImGui.Checkbox(JobsHelper.JobNames[jobId] + "##popup", ref selected))
{
if (selected)
{
cooldown.ExcludedJobIds.Remove(jobId);
}
else
{
cooldown.ExcludedJobIds.Add(jobId);
}
CooldownsDataEnabledChangedEvent?.Invoke(this);
}
}
ImGui.EndPopup();
}
}
private void DrawFilter(string name, JobRoles role)
{
ImGui.SameLine();
if (ImGui.RadioButton(name, _roleFilter == role))
{
_roleFilter = role;
}
}
private void DrawJobFilter(string name, uint job, ref uint filter)
{
ImGui.SameLine();
if (ImGui.RadioButton(name, filter == job))
{
filter = job;
}
}
private void DrawJobFilters()
{
if (_roleFilter == JobRoles.Unknown) { return; }
ImGui.Text("\t\t\t\t ");
if (_roleFilter == JobRoles.Tank)
{
DrawJobFilter("All##tank", 0, ref _tankFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.PLD], JobIDs.PLD, ref _tankFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.WAR], JobIDs.WAR, ref _tankFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.DRK], JobIDs.DRK, ref _tankFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.GNB], JobIDs.GNB, ref _tankFilter);
}
else if (_roleFilter == JobRoles.Healer)
{
DrawJobFilter("All##healer", 0, ref _healerFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.WHM], JobIDs.WHM, ref _healerFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.SCH], JobIDs.SCH, ref _healerFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.AST], JobIDs.AST, ref _healerFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.SGE], JobIDs.SGE, ref _healerFilter);
}
else if (_roleFilter == JobRoles.DPSMelee)
{
DrawJobFilter("All##melee", 0, ref _meleeFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.MNK], JobIDs.MNK, ref _meleeFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.DRG], JobIDs.DRG, ref _meleeFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.NIN], JobIDs.NIN, ref _meleeFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.SAM], JobIDs.SAM, ref _meleeFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.RPR], JobIDs.RPR, ref _meleeFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.VPR], JobIDs.VPR, ref _meleeFilter);
}
else if (_roleFilter == JobRoles.DPSRanged)
{
DrawJobFilter("All##ranged", 0, ref _rangedFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.BRD], JobIDs.BRD, ref _rangedFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.MCH], JobIDs.MCH, ref _rangedFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.DNC], JobIDs.DNC, ref _rangedFilter);
}
else if (_roleFilter == JobRoles.DPSCaster)
{
DrawJobFilter("All##caster", 0, ref _casterFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.BLM], JobIDs.BLM, ref _casterFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.SMN], JobIDs.SMN, ref _casterFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.RDM], JobIDs.RDM, ref _casterFilter);
DrawJobFilter(JobsHelper.JobNames[JobIDs.PCT], JobIDs.PCT, ref _casterFilter);
}
}
public static Dictionary<uint, PartyCooldownData> DefaultCooldowns = new Dictionary<uint, PartyCooldownData>()
{
// PARTY-WIDE EFFECTS
// Default columns:
// 1. Role mitigations (Reprisal, Addle, Feint)
// 2. Job specific mitigations
// 3. Damage buffs / burst window cooldowns
// 4. Personal mitigations / immunities / externals
// 5. Misc (Provoke, Shirk, Switcast, Rescue, etc)
// TANKS -------------------------------------------------------------------------------------------------
[7535] = NewData(7535, JobRoles.Tank, 22, 60, 10, 100, 1, PartyCooldownEnabled.PartyFrames), // reprisal
[7531] = NewData(7531, JobRoles.Tank, 8, 90, 20, 50, 4, PartyCooldownEnabled.PartyFrames), // rampart
[7533] = NewData(7533, JobRoles.Tank, 15, 30, 1, 50, 5, PartyCooldownEnabled.PartyFrames), // provoke
[7537] = NewData(7537, JobRoles.Tank, 48, 120, 1, 50, 5, PartyCooldownEnabled.PartyFrames), // shirk
// PLD
[30] = NewData(30, JobIDs.PLD, 50, 420, 10, 100, 4, PartyCooldownEnabled.PartyFrames), // hallowed ground
[3540] = NewData(3540, JobIDs.PLD, 56, 90, 30, 90, 2, PartyCooldownEnabled.PartyFrames), // divine veil
[7385] = NewData(7385, JobIDs.PLD, 70, 120, 18, 90, 2, PartyCooldownEnabled.PartyFrames), // passage of arms
[20] = NewData(20, JobIDs.PLD, 2, 60, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // fight or flight
[22] = NewData(22, JobIDs.PLD, 52, 90, 10, 90, 4, PartyCooldownEnabled.PartyFrames), // bulwark
[27] = NewData(27, JobIDs.PLD, 45, 120, 12, 90, 4, PartyCooldownEnabled.PartyFrames), // cover
[17] = NewData(17, JobIDs.PLD, 38, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // sentinel
[36920] = NewData(36920, JobIDs.PLD, 92, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // guardian
// WAR
[43] = NewData(43, JobIDs.WAR, 42, 240, 10, 100, 4, PartyCooldownEnabled.PartyFrames), // holmgang
[7388] = NewData(7388, JobIDs.WAR, 68, 90, 30, 90, 2, PartyCooldownEnabled.PartyFrames), // shake it off
[52] = NewData(52, JobIDs.WAR, 50, 60, 30, 10, 3, PartyCooldownEnabled.PartyFrames), // infuriate
[38] = NewData(38, JobIDs.WAR, 6, 60, 30, 10, 3, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 70), // berserk
[7389] = NewData(7389, JobIDs.WAR, 70, 60, 30, 10, 3, PartyCooldownEnabled.PartyFrames), // inner release
[40] = NewData(40, JobIDs.WAR, 30, 90, 10, 90, 4, PartyCooldownEnabled.PartyFrames), // thrill of battle
[3552] = NewData(3552, JobIDs.WAR, 58, 60, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // equilibrium
[3551] = NewData(3551, JobIDs.WAR, 56, 25, 8, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 82, sharedActionIds: new() { 16464 }), // raw intuition
[25751] = NewData(25751, JobIDs.WAR, 82, 25, 8, 90, 4, PartyCooldownEnabled.PartyFrames, sharedActionIds: new() { 16464 }), // bloodwhetting
[16464] = NewData(16464, JobIDs.WAR, 76, 25, 8, 90, 4, PartyCooldownEnabled.PartyFrames, sharedActionIds: new() { 3551, 25751 }), // nascent flash
[44] = NewData(44, JobIDs.WAR, 38, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // vengeance
[36923] = NewData(36923, JobIDs.WAR, 92, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // damnation
// DRK
[3638] = NewData(3638, JobIDs.DRK, 50, 300, 10, 100, 4, PartyCooldownEnabled.PartyFrames), // living dead
[16471] = NewData(16471, JobIDs.DRK, 66, 90, 15, 90, 2, PartyCooldownEnabled.PartyFrames), // dark missionary
[16472] = NewData(16472, JobIDs.DRK, 80, 120, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // living shadow
[3634] = NewData(3634, JobIDs.DRK, 45, 60, 10, 90, 4, PartyCooldownEnabled.PartyFrames), // dark mind
[25754] = NewData(25754, JobIDs.DRK, 82, 60, 10, 90, 4, PartyCooldownEnabled.PartyFrames), // oblation
[3625] = NewData(3625, JobIDs.DRK, 35, 60, 15, 10, 3, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 68), // blood weapon
[7390] = NewData(7390, JobIDs.DRK, 68, 60, 15, 10, 3, PartyCooldownEnabled.PartyFrames), // delirium
[3636] = NewData(3636, JobIDs.DRK, 38, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // shadow wall
[36927] = NewData(36927, JobIDs.DRK, 92, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // shadowed vigil
// GNB
[16152] = NewData(16152, JobIDs.GNB, 50, 360, 10, 100, 4, PartyCooldownEnabled.PartyFrames), // superbolide
[16160] = NewData(16160, JobIDs.GNB, 64, 90, 15, 90, 2, PartyCooldownEnabled.PartyFrames), // heart of light
[16164] = NewData(16164, JobIDs.GNB, 76, 120, 1, 10, 3, PartyCooldownEnabled.PartyFrames), // bloodfest
[16138] = NewData(16138, JobIDs.GNB, 2, 60, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // no mercy
[16140] = NewData(16140, JobIDs.GNB, 6, 90, 20, 90, 4, PartyCooldownEnabled.PartyFrames), // camouflage
[16161] = NewData(16161, JobIDs.GNB, 68, 25, 7, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 82), // heart of stone
[25758] = NewData(25758, JobIDs.GNB, 82, 25, 7, 90, 4, PartyCooldownEnabled.PartyFrames), // heart of corundum
[16148] = NewData(16148, JobIDs.GNB, 38, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // nebula
[36935] = NewData(36935, JobIDs.GNB, 92, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // great nebula
// HEALER -------------------------------------------------------------------------------------------------
[7571] = NewData(7571, JobRoles.Healer, 48, 120, 0, 80, 5, PartyCooldownEnabled.Disabled), // rescue
// AST
[16552] = NewData(16552, JobIDs.AST, 50, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // divination
[3613] = NewData(3613, JobIDs.AST, 58, 60, 18, 80, 2, PartyCooldownEnabled.PartyFrames), // collective unconscious
[16553] = NewData(16553, JobIDs.AST, 60, 60, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // celestial opposition
[7439] = NewData(7439, JobIDs.AST, 62, 60, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // earthly star (stellar detonation = 8324)
[16559] = NewData(16559, JobIDs.AST, 80, 120, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // neutral sect
[25873] = NewData(25873, JobIDs.AST, 86, 60, 8, 80, 2, PartyCooldownEnabled.PartyFrames), // exaltation
[25874] = NewData(25874, JobIDs.AST, 90, 180, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // macrocosmos (microcosmos = 25875)
[3606] = NewData(3606, JobIDs.AST, 6, 90, 15, 10, 4, PartyCooldownEnabled.PartyFrames), // lightspeed
// SCH
[7436] = NewData(7436, JobIDs.SCH, 66, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // chain stratagem
[805] = NewData(805, JobIDs.SCH, 40, 120, 20, 50, 2, PartyCooldownEnabled.PartyFrames), // fey illumination
[188] = NewData(188, JobIDs.SCH, 50, 30, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // sacred soil
[3585] = NewData(3585, JobIDs.SCH, 56, 90, 1, 80, 2, PartyCooldownEnabled.PartyFrames), // deployment tactics
[25867] = NewData(25867, JobIDs.SCH, 86, 60, 10, 80, 2, PartyCooldownEnabled.PartyFrames), // protraction
[25868] = NewData(25868, JobIDs.SCH, 90, 120, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // expedient
[7434] = NewData(7434, JobIDs.SCH, 62, 45, 1, 50, 4, PartyCooldownEnabled.PartyFrames), // excogitation
[37014] = NewData(37014, JobIDs.SCH, 100, 180, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // seraphism
// WHM
[16536] = NewData(16536, JobIDs.WHM, 80, 120, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // temperance
[3569] = NewData(3569, JobIDs.WHM, 52, 90, 24, 50, 2, PartyCooldownEnabled.PartyFrames), // asylum
[25862] = NewData(25862, JobIDs.WHM, 90, 180, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // liturgy of the bell
[136] = NewData(136, JobIDs.WHM, 30, 120, 15, 10, 4, PartyCooldownEnabled.PartyFrames), // presence of mind
[140] = NewData(140, JobIDs.WHM, 50, 180, 1, 10, 4, PartyCooldownEnabled.PartyFrames), // benediction
[3570] = NewData(3570, JobIDs.WHM, 60, 60, 1, 10, 4, PartyCooldownEnabled.PartyFrames), // tetragrammaton
[25861] = NewData(25861, JobIDs.WHM, 86, 60, 8, 10, 4, PartyCooldownEnabled.PartyFrames), // aquaveil
// SGE
[24298] = NewData(24298, JobIDs.SGE, 50, 30, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // kerachole
[24302] = NewData(24302, JobIDs.SGE, 60, 60, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // physis ii
[24310] = NewData(24310, JobIDs.SGE, 76, 120, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // holos
[24311] = NewData(24311, JobIDs.SGE, 80, 120, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // panhaima
[24318] = NewData(24318, JobIDs.SGE, 90, 120, 1, 80, 2, PartyCooldownEnabled.PartyFrames), // pneuma
[24303] = NewData(24303, JobIDs.SGE, 62, 45, 15, 10, 4, PartyCooldownEnabled.PartyFrames), // taurochole
[24305] = NewData(24305, JobIDs.SGE, 70, 120, 15, 10, 4, PartyCooldownEnabled.PartyFrames), // haima
[24317] = NewData(24317, JobIDs.SGE, 86, 60, 10, 10, 4, PartyCooldownEnabled.PartyFrames), // krasis
[37035] = NewData(37035, JobIDs.SGE, 100, 180, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // philosophia
// MELEE -------------------------------------------------------------------------------------------------
[7549] = NewData(7549, JobRoles.DPSMelee, 22, 90, 10, 100, 1, PartyCooldownEnabled.PartyFrames), // feint
[7542] = NewData(7542, JobRoles.DPSMelee, 12, 90, 20, 10, 4, PartyCooldownEnabled.PartyFrames), // bloodbath
// SAM
// lol?
// VPR
// lol?
// NIN
[2248] = NewData(2248, JobIDs.NIN, 15, 120, 20, 30, 3, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 68), // mug
[36957] = NewData(36957, JobIDs.NIN, 68, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // dokumori
[2258] = NewData(2258, JobIDs.NIN, 18, 60, 15, 10, 3, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // trick attack
[36958] = NewData(36958, JobIDs.NIN, 92, 60, 15, 10, 3, PartyCooldownEnabled.PartyFrames), // kunai's bane
[2241] = NewData(2241, JobIDs.NIN, 2, 120, 20, 20, 4, PartyCooldownEnabled.PartyFrames), // shade shift
// DRG
[3557] = NewData(3557, JobIDs.DRG, 52, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // battle litany
[85] = NewData(85, JobIDs.DRG, 30, 60, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // lance charge
// MNK
[65] = NewData(65, JobIDs.MNK, 42, 90, 15, 50, 2, PartyCooldownEnabled.PartyFrames), // mantra
[7396] = NewData(7396, JobIDs.MNK, 70, 120, 20, 90, 3, PartyCooldownEnabled.PartyCooldowns), // brotherhood
[7395] = NewData(7395, JobIDs.MNK, 68, 60, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // riddle of fire
[7394] = NewData(7394, JobIDs.MNK, 64, 120, 15, 20, 4, PartyCooldownEnabled.PartyFrames), // riddle of earth
// RPR
[24405] = NewData(24405, JobIDs.RPR, 72, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // arcane circle
[24404] = NewData(24404, JobIDs.RPR, 40, 30, 5, 10, 4, PartyCooldownEnabled.PartyFrames), // arcane crest
// RANGED -------------------------------------------------------------------------------------------------
// BRD
[118] = NewData(118, JobIDs.BRD, 50, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // battle voice
[7405] = NewData(7405, JobIDs.BRD, 62, 90, 15, 70, 2, PartyCooldownEnabled.PartyFrames, "90-120"), // troubadour
[7408] = NewData(7408, JobIDs.BRD, 66, 120, 15, 40, 2, PartyCooldownEnabled.PartyFrames), // nature's minne
[25785] = NewData(25785, JobIDs.BRD, 90, 110, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // radiant finale
[101] = NewData(101, JobIDs.BRD, 4, 120, 20, 90, 3, PartyCooldownEnabled.PartyFrames), // raging strikes
// DNC
[16012] = NewData(16012, JobIDs.DNC, 56, 90, 15, 70, 2, PartyCooldownEnabled.PartyFrames, "90-120"), // shield samba
[16004] = NewData(16004, JobIDs.DNC, 70, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // technical step / finish
[16011] = NewData(16011, JobIDs.DNC, 62, 120, 20, 90, 3, PartyCooldownEnabled.PartyFrames), // devilment
// MCH
[16889] = NewData(16889, JobIDs.MCH, 56, 90, 15, 70, 2, PartyCooldownEnabled.PartyFrames, "90-120"), // tactician
[2887] = NewData(2887, JobIDs.MCH, 62, 120, 10, 70, 2, PartyCooldownEnabled.PartyFrames), // dismantle
// CASTER -------------------------------------------------------------------------------------------------
[7560] = NewData(7560, JobRoles.DPSCaster, 8, 90, 10, 100, 1, PartyCooldownEnabled.PartyFrames), // addle
// RDM
[25857] = NewData(25857, JobIDs.RDM, 86, 120, 10, 70, 2, PartyCooldownEnabled.PartyCooldownsAndPartyFrames), // magick barrier
[7520] = NewData(7520, JobIDs.RDM, 58, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // embolden
// SMN
[25801] = NewData(25801, JobIDs.SMN, 66, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // searing light
[25799] = NewData(25799, JobIDs.SMN, 2, 60, 30, 10, 3, PartyCooldownEnabled.PartyFrames), // radiant aegis
// BLM
[3573] = NewData(3573, JobIDs.BLM, 52, 120, 30, 90, 3, PartyCooldownEnabled.PartyFrames), // ley lines
[157] = NewData(157, JobIDs.BLM, 38, 120, 20, 10, 4, PartyCooldownEnabled.PartyFrames), // manaward
// PCT
[35349] = NewData(35349, JobIDs.PCT, 70, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // scenic muse
[34685] = NewData(34685, JobIDs.PCT, 10, 120, 10, 10, 4, PartyCooldownEnabled.PartyFrames), // tempera coat
// MULTI-ROLE -------------------------------------------------------------------------------------------------
[7541] = NewData(7541, new List<JobRoles>() { JobRoles.DPSMelee, JobRoles.DPSRanged }, 8, 120, 0, 80, 4, PartyCooldownEnabled.PartyFrames), // second wind
[7561] = NewData(7561, new List<JobRoles>() { JobRoles.Healer, JobRoles.DPSCaster }, 18, 60, 1, 80, 5, PartyCooldownEnabled.PartyFrames, null, new HashSet<uint>() { JobIDs.BLM, JobIDs.SMN, JobIDs.RDM }), // swiftcast
[7562] = NewData(7562, new List<JobRoles>() { JobRoles.Healer, JobRoles.DPSCaster }, 14, 60, 21, 80, 5, PartyCooldownEnabled.Disabled), // lucid dreaming
};
#region helpers
private static PartyCooldownData NewData(
uint actionId,
uint jobId,
uint level,
int cooldown,
int effectDuration,
int priority,
int column,
PartyCooldownEnabled enabled,
string? overriddenCooldownText = null,
HashSet<uint>? excludedJobIds = null,
uint disabledAfterLevel = 0,
List<uint>? sharedActionIds = null)
{
PartyCooldownData data = NewData(
actionId,
level,
cooldown,
effectDuration,
priority,
column,
enabled,
overriddenCooldownText,
excludedJobIds,
disabledAfterLevel,
sharedActionIds
);
data.JobId = jobId;
data.Role = JobRoles.Unknown;
return data;
}
private static PartyCooldownData NewData(uint actionId, JobRoles role, uint level, int cooldown, int effectDuration, int priority, int column, PartyCooldownEnabled enabled, string? overriddenCooldownText = null, HashSet<uint>? excludedJobIds = null)
{
PartyCooldownData data = NewData(
actionId,
level,
cooldown,
effectDuration,
priority,
column,
enabled,
overriddenCooldownText,
excludedJobIds
);
data.JobId = 0;
data.Role = role;
return data;
}
private static PartyCooldownData NewData(uint actionId, List<JobRoles> roles, uint level, int cooldown, int effectDuration, int priority, int column, PartyCooldownEnabled enabled, string? overriddenCooldownText = null, HashSet<uint>? excludedJobIds = null)
{
PartyCooldownData data = NewData(
actionId,
level,
cooldown,
effectDuration,
priority,
column,
enabled,
overriddenCooldownText,
excludedJobIds
);
data.JobId = 0;
data.Role = JobRoles.Unknown;
data.Roles = roles;
return data;
}
private static PartyCooldownData NewData(
uint actionId,
uint level,
int cooldown,
int effectDuration,
int priority,
int column,
PartyCooldownEnabled enabled,
string? overriddenCooldownText = null,
HashSet<uint>? excludedJobIds = null,
uint disabledAfterLevel = 0,
List<uint>? sharedActionIds = null
)
{
PartyCooldownData data = new PartyCooldownData();
data.ActionId = actionId;
data.RequiredLevel = level;
data.DisabledAfterLevel = disabledAfterLevel;
data.CooldownDuration = cooldown;
data.EffectDuration = effectDuration;
data.Priority = priority;
data.Column = column;
data.EnabledV2 = enabled;
data.OverriddenCooldownText = overriddenCooldownText;
data.SharedActionIds = sharedActionIds ?? new();
if (excludedJobIds != null)
{
data.ExcludedJobIds = excludedJobIds;
}
return data;
}
#endregion
}
}
@@ -0,0 +1,334 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.GeneralElements;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace HSUI.Interface.PartyCooldowns
{
public class PartyCooldownsHud : DraggableHudElement, IHudElementWithPreview, IHudElementWithVisibilityConfig
{
private PartyCooldownsConfig Config => (PartyCooldownsConfig)_config;
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
private PartyCooldownsBarConfig _barConfig = null!;
private PartyCooldownsDataConfig _dataConfig = null!;
private List<List<PartyCooldown>> _cooldowns = new List<List<PartyCooldown>>();
private LabelHud _nameLabelHud;
private LabelHud _timeLabelHud;
public PartyCooldownsHud(PartyCooldownsConfig config, string displayName) : base(config, displayName)
{
_barConfig = ConfigurationManager.Instance.GetConfigObject<PartyCooldownsBarConfig>();
_dataConfig = ConfigurationManager.Instance.GetConfigObject<PartyCooldownsDataConfig>();
_dataConfig.CooldownsDataChangedEvent += OnCooldownsDataChanged;
PartyCooldownsManager.Instance.CooldownsChangedEvent += OnCooldownsChanged;
_nameLabelHud = new LabelHud(_barConfig.NameLabel);
_timeLabelHud = new LabelHud(_barConfig.TimeLabel);
UpdateCooldowns();
}
protected override void InternalDispose()
{
_dataConfig.CooldownsDataChangedEvent -= OnCooldownsDataChanged;
PartyCooldownsManager.Instance.CooldownsChangedEvent -= OnCooldownsChanged;
}
public void StopPreview()
{
Config.Preview = false;
PartyCooldownsManager.Instance?.UpdatePreview();
}
private void OnCooldownsDataChanged(PartyCooldownsDataConfig sender)
{
UpdateCooldowns();
}
private void OnCooldownsChanged(PartyCooldownsManager sender)
{
UpdateCooldowns();
}
private void UpdateCooldowns()
{
_cooldowns.Clear();
int columnCount = PartyCooldownsDataConfig.ColumnCount;
for (int i = 0; i < columnCount; i++)
{
_cooldowns.Add(new List<PartyCooldown>());
}
foreach (Dictionary<uint, PartyCooldown> memberCooldownList in PartyCooldownsManager.Instance.CooldownsMap.Values)
{
foreach (PartyCooldown cooldown in memberCooldownList.Values)
{
if (!cooldown.Data.IsEnabledForPartyCooldowns()) { continue; }
int columnIndex = Math.Min(columnCount - 1, cooldown.Data.Column - 1);
_cooldowns[columnIndex].Add(cooldown);
}
}
foreach (List<PartyCooldown> list in _cooldowns)
{
list.Sort((a, b) =>
{
if (a.Data.Priority == b.Data.Priority)
{
if (a.Data.ActionId == b.Data.ActionId)
{
return a.SourceId.CompareTo(b.SourceId);
}
else
{
return a.Data.ActionId.CompareTo(b.Data.ActionId);
}
}
if (a.Data.Priority < b.Data.Priority)
{
return -1;
}
return 1;
});
}
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
// hardcoded just for draggable area purposes
const int columnCount = 3;
const int rowCount = 4;
Vector2 size = new Vector2(
_barConfig.Size.X * columnCount + Config.Padding.X * (columnCount - 1),
_barConfig.Size.Y * rowCount + Config.Padding.Y * (rowCount - 1));
Vector2 pos = Config.GrowthDirection == PartyCooldownsGrowthDirection.Down ? Config.Position : Config.Position - new Vector2(0, size.Y);
return (new List<Vector2>() { pos + size / 2f }, new List<Vector2>() { size });
}
public override void DrawChildren(Vector2 origin)
{
if (!Config.Enabled) { return; }
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
if (player == null) { return; }
float offset = 0;
bool addedOffset = true;
bool isVertical = Config.GrowthDirection == PartyCooldownsGrowthDirection.Up || Config.GrowthDirection == PartyCooldownsGrowthDirection.Down;
foreach (List<PartyCooldown> list in _cooldowns)
{
if (list.Count == 0) { continue; }
int i = 0;
foreach (PartyCooldown cooldown in list)
{
if (!addedOffset)
{
offset += isVertical ? Config.Padding.X + _barConfig.Size.X : Config.Padding.Y + _barConfig.Size.Y;
addedOffset = true;
}
string barId = _barConfig.ID + $"_{offset + i}";
// cooldown bar
float cooldownTime = cooldown.CooldownTimeRemaining();
float effectTime = cooldown.EffectTimeRemaining();
float max = effectTime > 0 ? cooldown.GetEffectDuration() : cooldown.GetCooldown();
float current = effectTime > 0 ? effectTime : cooldownTime;
float sizeX = Math.Max(1, _barConfig.Size.X - _barConfig.Size.Y);
Vector2 size = new Vector2(sizeX, _barConfig.Size.Y);
Vector2 pos;
if (isVertical)
{
int direction = Config.GrowthDirection == PartyCooldownsGrowthDirection.Down ? 1 : -1;
pos = new Vector2(
Config.Position.X + size.Y + offset - 1,
Config.Position.Y + i * direction * size.Y + i * direction * Config.Padding.Y
);
}
else
{
int direction = Config.GrowthDirection == PartyCooldownsGrowthDirection.Right ? 1 : -1;
pos = new Vector2(
size.Y + Config.Position.X + i * direction * size.X + i * direction * size.Y + i * direction * Config.Padding.X,
Config.Position.Y + offset - 1
);
}
if (_barConfig.ShowBar)
{
PluginConfigColor fillColor = effectTime > 0 ? _barConfig.AvailableColor : _barConfig.RechargingColor;
PluginConfigColor bgColor = effectTime > 0 || cooldownTime == 0 ? _barConfig.AvailableBackgroundColor : _barConfig.RechargingBackgroundColor;
if (_barConfig.UseJobColors)
{
uint? jobId = GetJobId(cooldown, player);
if (jobId.HasValue)
{
PluginConfigColor jobColor = GlobalColors.Instance.SafeColorForJobId(jobId.Value);
PluginConfigColor bgJobColor = jobColor.WithAlpha(40f / 100f);
PluginConfigColor rechargeJobColor = jobColor.WithAlpha(25f / 100f);
PluginConfigColor nonActive = PluginConfigColor.FromHex(0x88000000);
fillColor = effectTime > 0 ? jobColor : rechargeJobColor;
bgColor = effectTime > 0 || cooldownTime == 0 ? bgJobColor : nonActive;
}
}
Rect background = new Rect(pos, size, bgColor);
Rect fill = BarUtilities.GetFillRect(pos, size, _barConfig.FillDirection, fillColor, current, max);
BarHud bar = new BarHud(
barId,
_barConfig.DrawBorder,
_barConfig.BorderColor,
_barConfig.BorderThickness,
DrawAnchor.TopLeft,
current: current,
max: max,
barTextureName: _barConfig.BarTextureName,
barTextureDrawMode: _barConfig.BarTextureDrawMode
);
bar.SetBackground(background);
bar.AddForegrounds(fill);
AddDrawActions(bar.GetDrawActions(origin, _barConfig.StrataLevel));
}
// icon
if (_barConfig.ShowIcon)
{
Vector2 iconPos = origin + new Vector2(pos.X - size.Y + 1, pos.Y);
Vector2 iconSize = new Vector2(size.Y);
bool recharging = effectTime == 0 && cooldownTime > 0;
bool shouldDrawCooldown = ClipRectsHelper.Instance.GetClipRectForArea(iconPos, iconSize) == null;
AddDrawAction(_barConfig.StrataLevel, () =>
{
DrawHelper.DrawInWindow(barId + "_icon", iconPos, iconSize, false, (drawList) =>
{
uint color = recharging ? 0xAAFFFFFF : 0xFFFFFFFF;
DrawHelper.DrawIcon(cooldown.Data.IconId, iconPos, iconSize, false, color, drawList);
// cooldown
if (shouldDrawCooldown &&
_barConfig.ShowIconCooldownAnimation &&
effectTime == 0 &&
cooldownTime > 0)
{
DrawHelper.DrawIconCooldown(iconPos, iconSize, cooldownTime, cooldown.GetCooldown(), drawList);
}
if (_barConfig.DrawBorder)
{
bool active = effectTime > 0 && _barConfig.ChangeIconBorderWhenActive;
uint iconBorderColor = active ? _barConfig.IconActiveBorderColor.Base : _barConfig.BorderColor.Base;
int thickness = active ? _barConfig.IconActiveBorderThickness : _barConfig.BorderThickness;
drawList.AddRect(iconPos, iconPos + iconSize, iconBorderColor, 0, ImDrawFlags.None, thickness);
}
});
});
}
// name
PluginConfigColor? labelColor = effectTime > 0 && _barConfig.ChangeLabelsColorWhenActive ? _barConfig.LabelsActiveColor : null;
ICharacter? character = cooldown.Member?.Character;
if (character == null && cooldown.SourceId == player.GameObjectId)
{
character = player;
}
Vector2 labelPos = origin + pos;
AddDrawAction(_barConfig.NameLabel.StrataLevel, () =>
{
PluginConfigColor realColor = _barConfig.NameLabel.Color;
_barConfig.NameLabel.Color = labelColor ?? realColor;
string? name = character == null ? "Fake Name" : null;
_nameLabelHud.Draw(labelPos, size, character, name);
_barConfig.NameLabel.Color = realColor;
});
// time
AddDrawAction(_barConfig.TimeLabel.StrataLevel, () =>
{
PluginConfigColor realColor = _barConfig.TimeLabel.Color;
_barConfig.TimeLabel.Color = labelColor ?? realColor;
_barConfig.TimeLabel.SetText("");
if (effectTime > 0)
{
if (_barConfig.TimeLabel.ShowEffectDuration)
{
_barConfig.TimeLabel.SetValue(effectTime);
}
}
else if (cooldownTime > 0)
{
if (_barConfig.TimeLabel.ShowRemainingCooldown)
{
_barConfig.TimeLabel.SetText(Utils.DurationToString(cooldownTime, _barConfig.TimeLabel.NumberFormat));
}
}
_timeLabelHud.Draw(labelPos, size, character);
_barConfig.TimeLabel.Color = realColor;
});
// tooltip
pos = origin + new Vector2(pos.X - size.Y + 1, pos.Y);
if (Config.ShowTooltips && ImGui.IsMouseHoveringRect(pos, pos + _barConfig.Size))
{
TooltipsHelper.Instance.ShowTooltipOnCursor(
cooldown.TooltipText(),
cooldown.Data.Name,
cooldown.Data.ActionId
);
}
i++;
}
addedOffset = false;
}
}
private uint? GetJobId(PartyCooldown cooldown, IPlayerCharacter player)
{
uint jobId = cooldown.Data.JobId;
if (jobId != 0) { return jobId; }
if (cooldown.Member != null) { return cooldown.Member.JobId; }
if (cooldown.SourceId == player.GameObjectId) { return player.ClassJob.RowId; }
ICharacter? chara = Plugin.ObjectTable.SearchById(cooldown.SourceId) as ICharacter;
return chara?.ClassJob.RowId;
}
}
}
@@ -0,0 +1,412 @@
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Hooking;
using HSUI.Config;
using HSUI.Helpers;
using HSUI.Interface.Party;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;
using static FFXIVClientStructs.FFXIV.Client.Game.Character.ActionEffectHandler;
namespace HSUI.Interface.PartyCooldowns
{
public unsafe class PartyCooldownsManager
{
#region Singleton
public static PartyCooldownsManager Instance { get; private set; } = null!;
private PartyCooldownsConfig _config = null!;
private PartyCooldownsDataConfig _dataConfig = null!;
private PartyCooldownsManager()
{
try
{
_onActionUsedHook = Plugin.GameInteropProvider.HookFromAddress<ActionEffectHandler.Delegates.Receive>(
ActionEffectHandler.MemberFunctionPointers.Receive,
OnActionUsed
);
_onActionUsedHook?.Enable();
}
catch
{
Plugin.Logger.Error("PartyCooldowns OnActionUsed Hook failed!!!");
}
try
{
_actorControlHook = Plugin.GameInteropProvider.HookFromSignature<ActorControlDelegate>(
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
OnActorControl
);
_actorControlHook?.Enable();
}
catch
{
Plugin.Logger.Error("PartyCooldowns OnActorControl Hook failed!!!");
}
PartyManager.Instance.MembersChangedEvent += OnMembersChanged;
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
Plugin.JobChangedEvent += OnJobChanged;
Plugin.ClientState.TerritoryChanged += OnTerritoryChanged;
ConfigReset(ConfigurationManager.Instance, false);
UpdatePreview();
}
public static void Initialize()
{
Instance = new PartyCooldownsManager();
}
~PartyCooldownsManager()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
_onActionUsedHook?.Disable();
_onActionUsedHook?.Dispose();
_actorControlHook?.Disable();
_actorControlHook?.Dispose();
PartyManager.Instance.MembersChangedEvent -= OnMembersChanged;
Plugin.JobChangedEvent -= OnJobChanged;
_config.ValueChangeEvent -= OnConfigPropertyChanged;
_dataConfig.CooldownsDataEnabledChangedEvent -= OnCooldownEnabledChanged;
Plugin.ClientState.TerritoryChanged -= OnTerritoryChanged;
Instance = null!;
}
private void OnConfigReset(ConfigurationManager sender)
{
ConfigReset(sender);
}
private void ConfigReset(ConfigurationManager sender, bool forceUpdate = true)
{
if (_config != null)
{
_config.ValueChangeEvent -= OnConfigPropertyChanged;
}
_config = sender.GetConfigObject<PartyCooldownsConfig>();
_config.ValueChangeEvent += OnConfigPropertyChanged;
if (_dataConfig != null)
{
_dataConfig.CooldownsDataEnabledChangedEvent -= OnCooldownEnabledChanged;
}
_dataConfig = sender.GetConfigObject<PartyCooldownsDataConfig>();
_dataConfig.CooldownsDataEnabledChangedEvent += OnCooldownEnabledChanged;
_dataConfig.UpdateDataIfNeeded();
if (forceUpdate)
{
ForcedUpdate();
}
}
#endregion Singleton
private Hook<ActionEffectHandler.Delegates.Receive>? _onActionUsedHook;
private delegate void ActorControlDelegate(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12);
private Hook<ActorControlDelegate>? _actorControlHook;
private Dictionary<uint, Dictionary<uint, PartyCooldown>>? _oldMap;
private Dictionary<uint, Dictionary<uint, PartyCooldown>> _cooldownsMap = new Dictionary<uint, Dictionary<uint, PartyCooldown>>();
public IReadOnlyDictionary<uint, Dictionary<uint, PartyCooldown>> CooldownsMap => _cooldownsMap;
private Dictionary<uint, double> _technicalStepMap = new Dictionary<uint, double>();
public delegate void PartyCooldownsChangedEventHandler(PartyCooldownsManager sender);
public event PartyCooldownsChangedEventHandler? CooldownsChangedEvent;
private bool _wasInDuty = false;
private void OnActorControl(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12)
{
_actorControlHook?.Original(entityId, type, buffID, direct, actionId, sourceId, arg7, arg8, arg9, arg10, targetId, arg12);
// detect wipe fadeouts (not 100% reliable but good enough)
if (type == 0x4000000F)
{
ResetCooldowns();
}
}
private void ResetCooldowns()
{
foreach (uint actorId in _cooldownsMap.Keys)
{
foreach (PartyCooldown cooldown in _cooldownsMap[actorId].Values)
{
cooldown.LastTimeUsed = 0;
}
}
}
private unsafe void OnActionUsed(uint actorId, Character* casterPtr, Vector3* targetPos, Header* header, TargetEffects* effects, GameObjectId* targetEntityIds)
{
_onActionUsedHook?.Original(actorId, casterPtr, targetPos, header, effects, targetEntityIds);
// check if its an action
if ((ActionType)header->ActionType != ActionType.Action ) { return; }
// check if its a member in the party
if (!_cooldownsMap.ContainsKey(actorId))
{
// check if its a party member's pet
IGameObject? actor = Plugin.ObjectTable.SearchById(actorId);
if (actor is IBattleNpc battleNpc && _cooldownsMap.ContainsKey(battleNpc.OwnerId))
{
actorId = battleNpc.OwnerId;
}
else
{
actorId = 0;
}
}
if (actorId <= 0) { return; }
uint actionID = header->ActionId;
// special case for starry muse > set id to scenic muse
if (actionID == 34675)
{
actionID = 35349;
}
// special case for technical step / finish
// we detect when technical step is pressed and save the time
// so we can properly calculate the cooldown once finish is pressed
if (actionID == 16193 || actionID == 16194 || actionID == 16195 || actionID == 16196)
{
actionID = 16004;
}
if (actionID == 15998)
{
_technicalStepMap[actorId] = ImGui.GetTime();
}
else
{
// check if its an action we track
if (_cooldownsMap[actorId].TryGetValue(actionID, out PartyCooldown? cooldown) && cooldown != null)
{
// if its technical finish, we set the cooldown start time to
// the time when step was pressed
if (_technicalStepMap.TryGetValue(actorId, out double stepStartTime) && actionID == 16004)
{
cooldown.OverridenCooldownStartTime = stepStartTime;
_technicalStepMap.Remove(actorId);
}
double now = ImGui.GetTime();
cooldown.LastTimeUsed = now;
cooldown.IgnoreNextUse = false;
foreach (uint id in cooldown.Data.SharedActionIds)
{
if (_cooldownsMap[actorId].TryGetValue(id, out PartyCooldown? sharedCooldown) && sharedCooldown != null)
{
sharedCooldown.LastTimeUsed = now;
sharedCooldown.IgnoreNextUse = true;
}
}
}
}
}
public void ForcedUpdate()
{
OnMembersChanged(PartyManager.Instance);
}
private void OnMembersChanged(PartyManager sender)
{
Plugin.Framework.RunOnFrameworkThread(() =>
{
if (sender.Previewing || _config.Preview) { return; }
_cooldownsMap.Clear();
if (_config.ShowOnlyInDuties && !Plugin.Condition[ConditionFlag.BoundByDuty])
{
CooldownsChangedEvent?.Invoke(this);
return;
}
// show when solo
if (sender.IsSoloParty() || sender.MemberCount == 0)
{
var player = Plugin.ObjectTable.LocalPlayer;
if (_config.ShowWhenSolo && player != null)
{
_cooldownsMap.Add((uint)player.GameObjectId, CooldownsForMember((uint)player.GameObjectId, player.ClassJob.RowId, player.Level, null));
}
}
else if (!_config.ShowOnlyInDuties || Plugin.Condition[ConditionFlag.BoundByDuty])
{
// add new members
foreach (IPartyFramesMember member in sender.GroupMembers)
{
if (member.ObjectId > 0)
{
_cooldownsMap.Add(member.ObjectId, CooldownsForMember(member));
}
}
}
CooldownsChangedEvent?.Invoke(this);
});
}
private Dictionary<uint, PartyCooldown> CooldownsForMember(IPartyFramesMember member)
{
return CooldownsForMember(member.ObjectId, member.JobId, member.Level, member);
}
private Dictionary<uint, PartyCooldown> CooldownsForMember(uint objectId, uint jobId, uint level, IPartyFramesMember? member)
{
Dictionary<uint, PartyCooldown> cooldowns = new Dictionary<uint, PartyCooldown>();
foreach (PartyCooldownData data in _dataConfig.Cooldowns)
{
if (data.EnabledV2 != PartyCooldownEnabled.Disabled &&
level >= data.RequiredLevel &&
(data.DisabledAfterLevel == 0 || level < data.DisabledAfterLevel) &&
data.IsUsableBy(jobId) &&
!data.ExcludedJobIds.Contains(jobId))
{
cooldowns.Add(data.ActionId, new PartyCooldown(data, objectId, level, member));
}
}
return cooldowns;
}
#region events
private void OnConfigPropertyChanged(object sender, OnChangeBaseArgs args)
{
if (args.PropertyName == "Preview")
{
UpdatePreview();
}
else if (args.PropertyName == "ShowWhenSolo" && PartyManager.Instance?.MemberCount == 0)
{
OnMembersChanged(PartyManager.Instance);
}
else if (args.PropertyName == "ShowOnlyInDuties" && PartyManager.Instance != null)
{
OnMembersChanged(PartyManager.Instance);
}
}
private void OnJobChanged(uint jobId)
{
ForcedUpdate();
}
private void OnCooldownEnabledChanged(PartyCooldownsDataConfig config)
{
ForcedUpdate();
}
private void OnTerritoryChanged(ushort territoryId)
{
bool isInDuty = Plugin.Condition[ConditionFlag.BoundByDuty];
if (_config.ShowOnlyInDuties && _wasInDuty != isInDuty)
{
ForcedUpdate();
}
_wasInDuty = isInDuty;
}
public void UpdatePreview()
{
if (!_config.Preview)
{
if (_oldMap != null)
{
_cooldownsMap = _oldMap;
}
else
{
_cooldownsMap.Clear();
}
if (PartyManager.Instance.Previewing)
{
CooldownsChangedEvent?.Invoke(this);
}
else
{
OnMembersChanged(PartyManager.Instance);
}
return;
}
if (PartyManager.Instance?.Previewing == false)
{
_oldMap = _cooldownsMap;
}
_cooldownsMap.Clear();
Random RNG = new Random((int)ImGui.GetTime());
for (uint i = 1; i < 9; i++)
{
Dictionary<uint, PartyCooldown> cooldowns = new Dictionary<uint, PartyCooldown>();
JobRoles role = i < 3 ? JobRoles.Tank : (i < 5 ? JobRoles.Healer : JobRoles.Unknown);
role = role == JobRoles.Unknown ? JobRoles.DPSMelee + RNG.Next(3) : role;
int jobCount = JobsHelper.JobsByRole[role].Count;
int jobIndex = RNG.Next(jobCount);
uint jobId = JobsHelper.JobsByRole[role][jobIndex];
_cooldownsMap.Add(i, CooldownsForMember(i, jobId, 90, null));
foreach (PartyCooldown cooldown in _cooldownsMap[i].Values)
{
int rng = RNG.Next(100);
if (rng > 80)
{
cooldown.LastTimeUsed = ImGui.GetTime() - 30;
}
else if (rng > 50)
{
cooldown.LastTimeUsed = ImGui.GetTime() + 1;
}
}
}
CooldownsChangedEvent?.Invoke(this);
}
#endregion
}
}
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Types;
namespace HSUI.Interface.StatusEffects
{
public class CustomEffectsListHud : StatusEffectsListHud
{
public CustomEffectsListHud(StatusEffectsListConfig config, string displayName) : base(config, displayName)
{
}
public IGameObject? TargetActor { get; set; } = null!;
protected override List<StatusEffectData> StatusEffectsData()
{
var list = StatusEffectDataList(TargetActor);
list.AddRange(StatusEffectDataList(Actor));
// cull duplicate statuses from the same source
list = list.GroupBy(s => new { s.Status.StatusId, s.Status.SourceObject.Id })
.Select(status => status.First())
.ToList();
// show mine or permanent first
if (Config.ShowMineFirst || Config.ShowPermanentFirst)
{
return OrderByMineOrPermanentFirst(list);
}
return list;
}
}
}
@@ -0,0 +1,859 @@
using Dalamud.Interface;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.GeneralElements;
using Dalamud.Bindings.ImGui;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace HSUI.Interface.StatusEffects
{
[Section("Buffs and Debuffs")]
[SubSection("Player Buffs", 0)]
public class PlayerBuffsListConfig : UnitFrameStatusEffectsListConfig
{
public new static PlayerBuffsListConfig DefaultConfig()
{
var screenSize = ImGui.GetMainViewport().Size;
var pos = new Vector2(screenSize.X * 0.38f, -screenSize.Y * 0.45f);
var iconConfig = new StatusEffectIconConfig();
iconConfig.DispellableBorderConfig.Enabled = false;
return new PlayerBuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, true, false, true, GrowthDirections.Left | GrowthDirections.Down, iconConfig);
}
public PlayerBuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
[Section("Buffs and Debuffs")]
[SubSection("Player Debuffs", 0)]
public class PlayerDebuffsListConfig : UnitFrameStatusEffectsListConfig
{
public new static PlayerDebuffsListConfig DefaultConfig()
{
var screenSize = ImGui.GetMainViewport().Size;
var pos = new Vector2(screenSize.X * 0.38f, -screenSize.Y * 0.45f + HUDConstants.DefaultStatusEffectsListSize.Y);
var iconConfig = new StatusEffectIconConfig();
return new PlayerDebuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, false, true, true, GrowthDirections.Left | GrowthDirections.Down, iconConfig);
}
public PlayerDebuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
[Section("Buffs and Debuffs")]
[SubSection("Target Buffs", 0)]
public class TargetBuffsListConfig : UnitFrameStatusEffectsListConfig
{
public new static TargetBuffsListConfig DefaultConfig()
{
var pos = new Vector2(0, -1);
var iconConfig = new StatusEffectIconConfig();
iconConfig.DispellableBorderConfig.Enabled = false;
var config = new TargetBuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, true, false, true, GrowthDirections.Right | GrowthDirections.Up, iconConfig);
config.AnchorToUnitFrame = true;
config.UnitFrameAnchor = DrawAnchor.TopLeft;
return config;
}
public TargetBuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
[Section("Buffs and Debuffs")]
[SubSection("Target Debuffs", 0)]
public class TargetDebuffsListConfig : UnitFrameStatusEffectsListConfig
{
public new static TargetDebuffsListConfig DefaultConfig()
{
var pos = new Vector2(0, -85);
var iconConfig = new StatusEffectIconConfig();
iconConfig.DispellableBorderConfig.Enabled = false;
var config = new TargetDebuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, false, true, true, GrowthDirections.Right | GrowthDirections.Up, iconConfig);
config.AnchorToUnitFrame = true;
config.UnitFrameAnchor = DrawAnchor.TopLeft;
return config;
}
public TargetDebuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
[Section("Buffs and Debuffs")]
[SubSection("Focus Target Buffs", 0)]
public class FocusTargetBuffsListConfig : UnitFrameStatusEffectsListConfig
{
public new static FocusTargetBuffsListConfig DefaultConfig()
{
var pos = new Vector2(0, -1);
var iconConfig = new StatusEffectIconConfig();
iconConfig.DispellableBorderConfig.Enabled = false;
var config = new FocusTargetBuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, true, false, true, GrowthDirections.Right | GrowthDirections.Up, iconConfig);
config.AnchorToUnitFrame = true;
config.UnitFrameAnchor = DrawAnchor.TopLeft;
return config;
}
public FocusTargetBuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
[Section("Buffs and Debuffs")]
[SubSection("Focus Target Debuffs", 0)]
public class FocusTargetDebuffsListConfig : UnitFrameStatusEffectsListConfig
{
public new static FocusTargetDebuffsListConfig DefaultConfig()
{
var pos = new Vector2(0, -85);
var iconConfig = new StatusEffectIconConfig();
iconConfig.DispellableBorderConfig.Enabled = false;
var config = new FocusTargetDebuffsListConfig(pos, HUDConstants.DefaultStatusEffectsListSize, false, true, true, GrowthDirections.Right | GrowthDirections.Up, iconConfig);
config.AnchorToUnitFrame = true;
config.UnitFrameAnchor = DrawAnchor.TopLeft;
return config;
}
public FocusTargetDebuffsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
public abstract class UnitFrameStatusEffectsListConfig : StatusEffectsListConfig
{
[Checkbox("Anchor to Unit Frame")]
[Order(16)]
public bool AnchorToUnitFrame = false;
[Anchor("Unit Frame Anchor")]
[Order(17, collapseWith = nameof(AnchorToUnitFrame))]
public DrawAnchor UnitFrameAnchor = DrawAnchor.TopLeft;
[NestedConfig("Visibility", 200)]
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
public UnitFrameStatusEffectsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
public class StatusEffectsListConfig : MovablePluginConfigObject
{
public bool ShowBuffs;
public bool ShowDebuffs;
[DragInt2("Size", min = 1, max = 4000)]
[Order(15)]
public Vector2 Size;
[DragInt2("Icon Padding", min = 0, max = 500)]
[Order(19)]
public Vector2 IconPadding = new(2, 2);
[Checkbox("Preview", isMonitored = true)]
[Order(20)]
public bool Preview;
[Checkbox("Fill Rows First", spacing = true)]
[Order(25)]
public bool FillRowsFirst = true;
[Combo("Icons Growth Direction",
"Right and Down",
"Right and Up",
"Left and Down",
"Left and Up",
"Centered and Up",
"Centered and Down",
"Centered and Left",
"Centered and Right"
)]
[Order(30)]
public int Directions;
[DragInt("Limit (-1 for no limit)", min = -1, max = 1000)]
[Order(35)]
public int Limit = -1;
[Checkbox("Permanent Effects", spacing = true)]
[Order(40)]
public bool ShowPermanentEffects;
[Checkbox("Permanent Effects First")]
[Order(41)]
public bool ShowPermanentFirst;
[Checkbox("Only My Effects", spacing = true)]
[Order(42)]
public bool ShowOnlyMine = false;
[Checkbox("My Effects First")]
[Order(43)]
public bool ShowMineFirst = false;
[Checkbox("Pet As Own Effect")]
[Order(44)]
public bool IncludePetAsOwn = false;
[Checkbox("Sort by Duration", spacing = true, help = "If enabled, \"Permanent Effects First\" and \"My Effects First\" will be ignored!")]
[Order(45)]
public bool SortByDuration = false;
[RadioSelector("Ascending", "Descending")]
[Order(46, collapseWith = nameof(SortByDuration))]
public StatusEffectDurationSortType DurationSortType = StatusEffectDurationSortType.Ascending;
[Checkbox("Tooltips", spacing = true)]
[Order(47)]
public bool ShowTooltips = true;
[Checkbox("Disable Interaction", help = "Enabling this will disable right clicking buffs off, or the shortcut to blacklist/whitelist a status effect.")]
[Order(48)]
public bool DisableInteraction = false;
[Checkbox("Hide when Dead")]
[Order(49)]
public bool HideWhenDead = true;
[NestedConfig("Icons", 65)]
public StatusEffectIconConfig IconConfig;
[NestedConfig("Filter Status Effects", 70, separator = true, spacing = false, collapsingHeader = false)]
public StatusEffectsBlacklistConfig BlacklistConfig = new StatusEffectsBlacklistConfig();
public StatusEffectsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
{
Position = position;
Size = size;
ShowBuffs = showBuffs;
ShowDebuffs = showDebuffs;
ShowPermanentEffects = showPermanentEffects;
SetGrowthDirections(growthDirections);
IconConfig = iconConfig;
Strata = StrataLevel.HIGH;
}
private void SetGrowthDirections(GrowthDirections growthDirections)
{
Directions = LayoutHelper.IndexFromGrowthDirections(growthDirections);
}
}
[Exportable(false)]
[Disableable(false)]
public class StatusEffectIconConfig : PluginConfigObject
{
[DragInt2("Icon Size", min = 1, max = 1000)]
[Order(5)]
public Vector2 Size = new(40, 40);
[Checkbox("Crop Icon", spacing = true)]
[Order(20)]
public bool CropIcon = true;
[NestedConfig("Border", 25, collapseWith = nameof(CropIcon), collapsingHeader = false)]
public StatusEffectIconBorderConfig BorderConfig = new();
[NestedConfig("Shadow", 26, collapseWith = nameof(CropIcon), collapsingHeader = false)]
public ShadowConfig ShadowConfig = new ShadowConfig() { Enabled = false };
[NestedConfig("Dispellable Effects Border", 30, collapseWith = nameof(CropIcon), collapsingHeader = false)]
public StatusEffectIconBorderConfig DispellableBorderConfig = new(new PluginConfigColor(new Vector4(141f / 255f, 206f / 255f, 229f / 255f, 100f / 100f)), 2);
[NestedConfig("My Effects Border", 35, collapseWith = nameof(CropIcon), collapsingHeader = false)]
public StatusEffectIconBorderConfig OwnedBorderConfig = new(new PluginConfigColor(new Vector4(35f / 255f, 179f / 255f, 69f / 255f, 100f / 100f)), 1);
[NestedConfig("Duration", 50)]
public LabelConfig DurationLabelConfig;
[NestedConfig("Stacks", 60)]
public LabelConfig StacksLabelConfig;
public StatusEffectIconConfig(LabelConfig? durationLabelConfig = null, LabelConfig? stacksLabelConfig = null)
{
DurationLabelConfig = durationLabelConfig ?? StatusEffectsListsDefaults.DefaultDurationLabelConfig();
StacksLabelConfig = stacksLabelConfig ?? StatusEffectsListsDefaults.DefaultStacksLabelConfig();
}
}
[Exportable(false)]
public class StatusEffectIconBorderConfig : PluginConfigObject
{
[ColorEdit4("Color")]
[Order(5)]
public PluginConfigColor Color = new(Vector4.UnitW);
[DragInt("Thickness", min = 1, max = 100)]
[Order(10)]
public int Thickness = 1;
public StatusEffectIconBorderConfig()
{
}
public StatusEffectIconBorderConfig(PluginConfigColor color, int thickness)
{
Color = color;
Thickness = thickness;
}
}
internal class StatusEffectsListsDefaults
{
internal static LabelConfig DefaultDurationLabelConfig()
{
return new LabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
}
internal static LabelConfig DefaultStacksLabelConfig()
{
var config = new LabelConfig(new Vector2(16, -11), "", DrawAnchor.Center, DrawAnchor.Center);
config.Color = new(Vector4.UnitW);
config.OutlineColor = new(Vector4.One);
config.ShadowConfig.Color = new(Vector4.One);
return config;
}
}
public enum FilterType
{
Blacklist,
Whitelist
}
[Exportable(false)]
public class StatusEffectsBlacklistConfig : PluginConfigObject
{
[RadioSelector(typeof(FilterType))]
[Order(5)]
public FilterType FilterType;
public SortedList<string, uint> List = new SortedList<string, uint>();
[JsonIgnore] private string? _errorMessage = null;
[JsonIgnore] private string? _importString = null;
[JsonIgnore] private bool _clearingList = false;
private string KeyName(Status status)
{
return $"{status.Name}[{status.RowId.ToString()}]";
}
public bool StatusAllowed(Status status)
{
bool inList = List.Any(pair => pair.Value == status.RowId);
if ((inList && FilterType == FilterType.Blacklist) || (!inList && FilterType == FilterType.Whitelist))
{
return false;
}
return true;
}
public bool AddNewEntry(Status? status)
{
if (status != null && status.HasValue && !List.ContainsKey(KeyName(status.Value)))
{
List.Add(KeyName(status.Value), status.Value.RowId);
_input = "";
return true;
}
return false;
}
private bool AddNewEntry(string input, ExcelSheet<Status>? sheet)
{
if (input.Length > 0 && sheet != null)
{
List<Status> statusToAdd = new List<Status>();
// try id
if (uint.TryParse(input, out uint uintValue))
{
if (uintValue > 0)
{
Status? status = sheet.GetRow(uintValue);
if (status != null && status.HasValue)
{
statusToAdd.Add(status.Value);
}
}
}
// try name
if (statusToAdd.Count == 0)
{
var enumerator = sheet.GetEnumerator();
while (enumerator.MoveNext())
{
Status item = enumerator.Current;
if (item.Name.ToString().ToLower() == input.ToLower())
{
statusToAdd.Add(item);
}
}
}
bool added = false;
foreach (Status status in statusToAdd)
{
added |= AddNewEntry(status);
}
return added;
}
return false;
}
private string ExportList()
{
string exportString = "";
for (int i = 0; i < List.Keys.Count; i++)
{
exportString += List.Keys[i] + "|";
exportString += List.Values[i] + "|";
}
return exportString;
}
private string? ImportList(string importString)
{
SortedList<string, uint> tmpList = new SortedList<string, uint>();
try
{
string[] strings = importString.Trim().Split("|", StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < strings.Length; i += 2)
{
if (i + 1 >= strings.Length)
{
break;
}
string key = strings[i];
uint value = uint.Parse(strings[i + 1]);
tmpList.Add(key, value);
}
}
catch
{
return "Error importing list!";
}
List = tmpList;
return null;
}
[JsonIgnore]
private string _input = "";
[ManualDraw]
public bool Draw(ref bool changed)
{
if (!Enabled)
{
return false;
}
var flags =
ImGuiTableFlags.RowBg |
ImGuiTableFlags.Borders |
ImGuiTableFlags.BordersOuter |
ImGuiTableFlags.BordersInner |
ImGuiTableFlags.ScrollY |
ImGuiTableFlags.SizingFixedSame;
var sheet = Plugin.DataManager.GetExcelSheet<Status>();
var iconSize = new Vector2(30, 30);
var indexToRemove = -1;
if (ImGui.BeginChild("Filter Effects", new Vector2(0, 360), false, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
{
ImGui.Text(" ");
ImGui.SameLine();
ImGui.Text("Type an ID or Name");
ImGui.Text(" ");
ImGui.SameLine();
ImGui.PushItemWidth(300);
if (ImGui.InputText("", ref _input, 64, ImGuiInputTextFlags.EnterReturnsTrue))
{
changed |= AddNewEntry(_input, sheet);
ImGui.SetKeyboardFocusHere(-1);
}
// add
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(0, 0)))
{
changed |= AddNewEntry(_input, sheet);
ImGui.SetKeyboardFocusHere(-2);
}
// export
ImGui.SameLine();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 154);
if (ImGui.Button(FontAwesomeIcon.Upload.ToIconString(), new Vector2(0, 0)))
{
ImGui.SetClipboardText(ExportList());
ImGui.OpenPopup("export_succes_popup");
}
ImGui.PopFont();
ImGuiHelper.SetTooltip("Export List to Clipboard");
// export success popup
if (ImGui.BeginPopup("export_succes_popup"))
{
ImGui.Text("List exported to clipboard!");
ImGui.EndPopup();
}
// import
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Download.ToIconString(), new Vector2(0, 0)))
{
_importString = ImGui.GetClipboardText();
}
ImGui.PopFont();
ImGuiHelper.SetTooltip("Import List from Clipboard");
// clear
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), new Vector2(0, 0)))
{
_clearingList = true;
}
ImGui.PopFont();
ImGuiHelper.SetTooltip("Clear List");
ImGui.Text(" ");
ImGui.SameLine();
if (ImGui.BeginTable("table", 4, flags, new Vector2(583, List.Count > 0 ? 200 : 40)))
{
ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthFixed, 0, 0);
ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed, 0, 1);
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 0, 2);
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.WidthFixed, 0, 3);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
for (int i = 0; i < List.Count; i++)
{
var id = List.Values[i];
var name = List.Keys[i];
var row = sheet?.GetRow(id);
if (_input != "" && !name.ToUpper().Contains(_input.ToUpper()))
{
continue;
}
ImGui.PushID(i.ToString());
ImGui.TableNextRow(ImGuiTableRowFlags.None, iconSize.Y);
// icon
if (ImGui.TableSetColumnIndex(0))
{
if (row != null)
{
DrawHelper.DrawIcon<Status>(row, ImGui.GetCursorPos(), iconSize, false, true);
}
}
// id
if (ImGui.TableSetColumnIndex(1))
{
ImGui.Text(id.ToString());
}
// name
if (ImGui.TableSetColumnIndex(2))
{
ImGui.Text(row != null && row.HasValue ? row.Value.Name.ToString() : name);
}
// remove
if (ImGui.TableSetColumnIndex(3))
{
ImGui.PushFont(UiBuilder.IconFont);
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero);
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), iconSize))
{
changed = true;
indexToRemove = i;
}
ImGui.PopFont();
ImGui.PopStyleColor(3);
}
ImGui.PopID();
}
ImGui.EndTable();
}
ImGui.Text(" ");
ImGui.SameLine();
ImGui.Text("Tip: You can [Ctrl + Alt + Shift] + Left Click on a status effect to automatically add it to the list.");
}
if (indexToRemove >= 0)
{
List.RemoveAt(indexToRemove);
}
ImGui.EndChild();
// error message
if (_errorMessage != null)
{
if (ImGuiHelper.DrawErrorModal(_errorMessage))
{
_errorMessage = null;
}
}
// import confirmation
if (_importString != null)
{
string[] message = new string[] {
"All the elements in the list will be replaced.",
"Are you sure you want to import?"
};
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Import?", message);
if (didConfirm)
{
_errorMessage = ImportList(_importString);
changed = true;
}
if (didConfirm || didClose)
{
_importString = null;
}
}
// clear confirmation
if (_clearingList)
{
string message = "Are you sure you want to clear the list?";
var (didConfirm, didClose) = ImGuiHelper.DrawConfirmationModal("Clear List?", message);
if (didConfirm)
{
List.Clear();
changed = true;
}
if (didConfirm || didClose)
{
_clearingList = false;
}
}
return false;
}
}
public class StatusEffectsBlacklistConfigConverter : PluginConfigObjectConverter
{
public StatusEffectsBlacklistConfigConverter()
{
NewTypeFieldConverter<bool, FilterType> converter;
converter = new NewTypeFieldConverter<bool, FilterType>("FilterType", FilterType.Blacklist, (oldValue) =>
{
return oldValue ? FilterType.Whitelist : FilterType.Blacklist;
});
FieldConvertersMap.Add("UseAsWhitelist", converter);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(StatusEffectsBlacklistConfig);
}
}
[Section("Buffs and Debuffs")]
[SubSection("Custom Effects", 0)]
public class CustomEffectsListConfig : StatusEffectsListConfig
{
public new static CustomEffectsListConfig DefaultConfig()
{
var iconConfig = new StatusEffectIconConfig();
iconConfig.DispellableBorderConfig.Enabled = false;
iconConfig.Size = new Vector2(30, 30);
var pos = new Vector2(0, HUDConstants.BaseHUDOffsetY);
var size = new Vector2(250, iconConfig.Size.Y * 3 + 10);
var config = new CustomEffectsListConfig(pos, size, true, true, false, GrowthDirections.Centered | GrowthDirections.Up, iconConfig);
config.Enabled = false;
config.Directions = 5;
// pre-populated white list
config.BlacklistConfig.FilterType = FilterType.Whitelist;
ExcelSheet<Status>? sheet = Plugin.DataManager.GetExcelSheet<Status>();
if (sheet != null)
{
// Left Eye
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1184));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1454));
// Battle Litany
config.BlacklistConfig.AddNewEntry(sheet.GetRow(786));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1414));
// Brotherhood
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1185));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2174));
// Battle Voice
config.BlacklistConfig.AddNewEntry(sheet.GetRow(141));
// Devilment
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1825));
// Technical Finish
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1822));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2050));
// Standard Finish
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1821));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2024));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2105));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2113));
// Embolden
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1239));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1297));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2282));
// Devotion
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1213));
// Divination
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1878));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2034));
// Chain Stratagem
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1221));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1406));
// Radiant Finale
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2722));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2964));
// Arcane Circle
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2599));
// Searing Light
config.BlacklistConfig.AddNewEntry(sheet.GetRow(2703));
// Trick Attack
config.BlacklistConfig.AddNewEntry(sheet.GetRow(638));
// ------ AST Card Buffs -------
// The Balance
config.BlacklistConfig.AddNewEntry(sheet.GetRow(829));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1338));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1882));
// The Bole
config.BlacklistConfig.AddNewEntry(sheet.GetRow(830));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1339));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1883));
// The Arrow
config.BlacklistConfig.AddNewEntry(sheet.GetRow(831));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1884));
// The Spear
config.BlacklistConfig.AddNewEntry(sheet.GetRow(832));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1885));
// The Ewer
config.BlacklistConfig.AddNewEntry(sheet.GetRow(833));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1340));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1886));
// The Spire
config.BlacklistConfig.AddNewEntry(sheet.GetRow(834));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1341));
config.BlacklistConfig.AddNewEntry(sheet.GetRow(1887));
}
return config;
}
public CustomEffectsListConfig(Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
{
}
}
public enum StatusEffectDurationSortType
{
Ascending,
Descending
}
}
@@ -0,0 +1,589 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Utility;
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 FFXIVClientStructs.FFXIV.Client.Game;
using LuminaStatus = Lumina.Excel.Sheets.Status;
using StatusStruct = FFXIVClientStructs.FFXIV.Client.Game.Status;
namespace HSUI.Interface.StatusEffects
{
public class StatusEffectsListHud : ParentAnchoredDraggableHudElement, IHudElementWithActor, IHudElementWithAnchorableParent, IHudElementWithPreview, IHudElementWithMouseOver, IHudElementWithVisibilityConfig
{
protected StatusEffectsListConfig Config => (StatusEffectsListConfig)_config;
public VisibilityConfig? VisibilityConfig => Config is UnitFrameStatusEffectsListConfig config ? config.VisibilityConfig : null;
private LayoutInfo _layoutInfo;
internal static int StatusEffectListsSize = 60;
private StatusStruct[]? _fakeEffects = null;
private LabelHud _durationLabel;
private LabelHud _stacksLabel;
public IGameObject? Actor { get; set; } = null;
private bool _wasHovering = false;
private bool NeedsSpecialInput => !ClipRectsHelper.Instance.Enabled || ClipRectsHelper.Instance.Mode == WindowClippingMode.Performance;
protected override bool AnchorToParent => Config is UnitFrameStatusEffectsListConfig config ? config.AnchorToUnitFrame : false;
protected override DrawAnchor ParentAnchor => Config is UnitFrameStatusEffectsListConfig config ? config.UnitFrameAnchor : DrawAnchor.Center;
public StatusEffectsListHud(StatusEffectsListConfig config, string? displayName = null) : base(config, displayName)
{
_config.ValueChangeEvent += OnConfigPropertyChanged;
_durationLabel = new LabelHud(config.IconConfig.DurationLabelConfig);
_stacksLabel = new LabelHud(config.IconConfig.StacksLabelConfig);
UpdatePreview();
}
~StatusEffectsListHud()
{
_config.ValueChangeEvent -= OnConfigPropertyChanged;
}
public void StopPreview()
{
Config.Preview = false;
UpdatePreview();
}
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
{
Vector2 pos = LayoutHelper.CalculateStartPosition(Config.Position, Config.Size, LayoutHelper.GrowthDirectionsFromIndex(Config.Directions));
return (new List<Vector2>() { pos + Config.Size / 2f }, new List<Vector2>() { Config.Size });
}
public void StopMouseover()
{
if (_wasHovering && NeedsSpecialInput)
{
InputsHelper.Instance.StopHandlingInputs();
_wasHovering = false;
}
}
private uint CalculateLayout(List<StatusEffectData> list)
{
var effectCount = (uint)list.Count;
var count = Config.Limit >= 0 ? Math.Min((uint)Config.Limit, effectCount) : effectCount;
if (count <= 0)
{
return 0;
}
_layoutInfo = LayoutHelper.CalculateLayout(
Config.Size,
Config.IconConfig.Size,
count,
Config.IconPadding,
LayoutHelper.GetFillsRowsFirst(Config.FillRowsFirst, LayoutHelper.GrowthDirectionsFromIndex(Config.Directions))
);
return count;
}
protected string GetStatusActorName(StatusStruct status)
{
var character = Plugin.ObjectTable.SearchById(status.SourceObject.Id);
return character == null ? "" : character.Name.ToString();
}
protected virtual List<StatusEffectData> StatusEffectsData()
{
var list = StatusEffectDataList(Actor);
// sort by duration
if (Config.SortByDuration)
{
list.Sort((a, b) =>
{
float aTime = a.Data.IsPermanent || a.Data.IsFcBuff ? float.MaxValue : a.Status.RemainingTime;
float bTime = b.Data.IsPermanent || b.Data.IsFcBuff ? float.MaxValue : b.Status.RemainingTime;
if (Config.DurationSortType == StatusEffectDurationSortType.Ascending)
{
return aTime.CompareTo(bTime);
}
else
{
return bTime.CompareTo(aTime);
}
});
}
// show mine or permanent first
else if (Config.ShowMineFirst || Config.ShowPermanentFirst)
{
return OrderByMineOrPermanentFirst(list);
}
return list;
}
protected unsafe List<StatusEffectData> StatusEffectDataList(IGameObject? actor)
{
List<StatusEffectData> list = new List<StatusEffectData>();
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
IBattleChara? character = null;
int count = StatusEffectListsSize;
if (_fakeEffects == null)
{
if (actor == null || actor is not IBattleChara battleChara)
{
return list;
}
if (Config.HideWhenDead && (battleChara.IsDead || battleChara.CurrentHp <= 0))
{
return list;
}
character = (IBattleChara)actor;
try
{
count = Math.Min(count, character.StatusList.Length);
}
catch { }
}
else
{
count = Config.Limit == -1 ? _fakeEffects.Length : Math.Min(Config.Limit, _fakeEffects.Length);
}
for (int i = 0; i < count; i++)
{
// status
StatusStruct* status = null;
if (_fakeEffects != null)
{
var fakeStruct = _fakeEffects![i];
status = &fakeStruct;
}
else
{
try
{
status = character?.StatusList[i] == null ? null : (StatusStruct*)character.StatusList[i]!.Address;
}
catch { }
}
if (status == null || status->StatusId == 0)
{
continue;
}
// data
LuminaStatus? data = null;
if (_fakeEffects != null)
{
data = Plugin.DataManager.GetExcelSheet<LuminaStatus>()?.GetRow(status->StatusId);
}
else
{
try
{
data = character?.StatusList[i]?.GameData.Value;
} catch { }
}
if (data == null || !data.HasValue)
{
continue;
}
// filter "invisible" status effects
if (data.Value.Icon == 0 || data.Value.Name.ToString().Length == 0)
{
continue;
}
// dont filter anything on preview mode
if (_fakeEffects != null)
{
list.Add(new StatusEffectData(*status, data.Value));
continue;
}
// buffs
if (!Config.ShowBuffs && data.Value.StatusCategory == 1)
{
continue;
}
// debuffs
if (!Config.ShowDebuffs && data.Value.StatusCategory != 1)
{
continue;
}
// permanent
if (!Config.ShowPermanentEffects && data.Value.IsPermanent)
{
continue;
}
// only mine
var mine = player?.GameObjectId == status->SourceObject.Id;
if (Config.IncludePetAsOwn)
{
mine = player?.GameObjectId == status->SourceObject.Id || IsStatusFromPlayerPet(*status);
}
if (Config.ShowOnlyMine && !mine)
{
continue;
}
// blacklist
if (Config.BlacklistConfig.Enabled && !Config.BlacklistConfig.StatusAllowed(data.Value))
{
continue;
}
list.Add(new StatusEffectData(*status, data.Value));
}
return list;
}
protected bool IsStatusFromPlayerPet(StatusStruct status)
{
var buddy = Plugin.BuddyList.PetBuddy;
if (buddy == null)
{
return false;
}
return buddy.EntityId == status.SourceObject.Id;
}
protected List<StatusEffectData> OrderByMineOrPermanentFirst(List<StatusEffectData> list)
{
var player = Plugin.ObjectTable.LocalPlayer;
if (player == null)
{
return list;
}
if (Config.ShowMineFirst && Config.ShowPermanentFirst)
{
return list.OrderByDescending(x => x.Status.SourceObject.Id == player.GameObjectId && x.Data.IsPermanent || x.Data.IsFcBuff)
.ThenByDescending(x => x.Status.SourceObject.Id == player.GameObjectId)
.ThenByDescending(x => x.Data.IsPermanent)
.ThenByDescending(x => x.Data.IsFcBuff)
.ToList();
}
else if (Config.ShowMineFirst && !Config.ShowPermanentFirst)
{
return list.OrderByDescending(x => x.Status.SourceObject.Id == player.GameObjectId)
.ToList();
}
else if (!Config.ShowMineFirst && Config.ShowPermanentFirst)
{
return list.OrderByDescending(x => x.Data.IsPermanent)
.ThenByDescending(x => x.Data.IsFcBuff)
.ToList();
}
return list;
}
public override void DrawChildren(Vector2 origin)
{
if (!Config.Enabled)
{
return;
}
if (_fakeEffects == null && (Actor == null || Actor.ObjectKind != ObjectKind.Player && Actor.ObjectKind != ObjectKind.BattleNpc))
{
return;
}
// calculate layout
List<StatusEffectData> list = StatusEffectsData();
// area
GrowthDirections growthDirections = LayoutHelper.GrowthDirectionsFromIndex(Config.Directions);
Vector2 position = origin + GetAnchoredPosition(Config.Position, Config.Size, DrawAnchor.TopLeft);
Vector2 areaPos = LayoutHelper.CalculateStartPosition(position, Config.Size, growthDirections);
Vector2 margin = new Vector2(14, 10);
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
// no need to do anything else if there are no effects
if (list.Count == 0)
{
if (_wasHovering && NeedsSpecialInput)
{
_wasHovering = false;
InputsHelper.Instance.StopHandlingInputs();
}
return;
}
// calculate icon positions
uint count = CalculateLayout(list);
var (iconPositions, minPos, maxPos) = LayoutHelper.CalculateIconPositions(
growthDirections,
count,
position,
Config.Size,
Config.IconConfig.Size,
Config.IconPadding,
LayoutHelper.GetFillsRowsFirst(Config.FillRowsFirst, growthDirections),
_layoutInfo
);
// window
// imgui clips the left and right borders inside windows for some reason
// we make the window bigger so the actual drawable size is the expected one
Vector2 windowPos = minPos - margin;
Vector2 windowSize = maxPos - minPos;
AddDrawAction(Config.StrataLevel, () =>
{
DrawHelper.DrawInWindow(ID, windowPos, windowSize + margin * 2, !Config.DisableInteraction, (drawList) =>
{
// area
if (Config.Preview)
{
drawList.AddRectFilled(areaPos, areaPos + Config.Size, 0x88000000);
}
for (var i = 0; i < count; i++)
{
Vector2 iconPos = iconPositions[i];
var statusEffectData = list[i];
// shadow
if (Config.IconConfig.ShadowConfig! != null && Config.IconConfig.ShadowConfig.Enabled)
{
// Right Side
drawList.AddRectFilled(iconPos + new Vector2(Config.IconConfig.Size.X, Config.IconConfig.ShadowConfig.Offset), iconPos + Config.IconConfig.Size + new Vector2(Config.IconConfig.ShadowConfig.Offset, Config.IconConfig.ShadowConfig.Offset) + new Vector2(Config.IconConfig.ShadowConfig.Thickness - 1, Config.IconConfig.ShadowConfig.Thickness - 1), Config.IconConfig.ShadowConfig.Color.Base);
// Bottom Size
drawList.AddRectFilled(iconPos + new Vector2(Config.IconConfig.ShadowConfig.Offset, Config.IconConfig.Size.Y), iconPos + Config.IconConfig.Size + new Vector2(Config.IconConfig.ShadowConfig.Offset, Config.IconConfig.ShadowConfig.Offset) + new Vector2(Config.IconConfig.ShadowConfig.Thickness - 1, Config.IconConfig.ShadowConfig.Thickness - 1), Config.IconConfig.ShadowConfig.Color.Base);
}
// icon
var cropIcon = Config.IconConfig.CropIcon;
int stackCount = cropIcon ? 1 : statusEffectData.Data.MaxStacks > 0 ? statusEffectData.Status.Param : 0;
DrawHelper.DrawIcon<LuminaStatus>(drawList, statusEffectData.Data, iconPos, Config.IconConfig.Size, false, cropIcon, stackCount);
// border
var borderConfig = GetBorderConfig(statusEffectData);
if (borderConfig != null && cropIcon)
{
drawList.AddRect(iconPos, iconPos + Config.IconConfig.Size, borderConfig.Color.Base, 0, ImDrawFlags.None, borderConfig.Thickness);
}
// Draw dispell indicator above dispellable status effect on uncropped icons
if (borderConfig != null && !cropIcon && statusEffectData.Data.CanDispel)
{
var dispellIndicatorColor = new Vector4(141f / 255f, 206f / 255f, 229f / 255f, 100f / 100f);
// 24x32
drawList.AddRectFilled(
iconPos + new Vector2(Config.IconConfig.Size.X * .07f, Config.IconConfig.Size.Y * .07f),
iconPos + new Vector2(Config.IconConfig.Size.X * .93f, Config.IconConfig.Size.Y * .14f),
ImGui.ColorConvertFloat4ToU32(dispellIndicatorColor),
8f
);
}
}
});
});
StatusEffectData? hoveringData = null;
IGameObject? character = Actor;
// labels need to be drawn separated since they have their own window for clipping
for (var i = 0; i < count; i++)
{
Vector2 iconPos = iconPositions[i];
StatusEffectData statusEffectData = list[i];
// duration
if (Config.IconConfig.DurationLabelConfig.Enabled &&
!statusEffectData.Data.IsPermanent &&
!statusEffectData.Data.IsFcBuff)
{
AddDrawAction(Config.IconConfig.DurationLabelConfig.StrataLevel, () =>
{
double duration = Math.Round(Math.Abs(statusEffectData.Status.RemainingTime));
Config.IconConfig.DurationLabelConfig.SetText(Utils.DurationToString(duration));
_durationLabel.Draw(iconPos, Config.IconConfig.Size, character);
});
}
// stacks
if (Config.IconConfig.StacksLabelConfig.Enabled &&
statusEffectData.Data.MaxStacks > 0 &&
statusEffectData.Status.Param > 0 &&
!statusEffectData.Data.IsFcBuff)
{
AddDrawAction(Config.IconConfig.StacksLabelConfig.StrataLevel, () =>
{
Config.IconConfig.StacksLabelConfig.SetText($"{statusEffectData.Status.Param}");
_stacksLabel.Draw(iconPos, Config.IconConfig.Size, character);
});
}
// tooltips / interaction
if (ImGui.IsMouseHoveringRect(iconPos, iconPos + Config.IconConfig.Size))
{
hoveringData = statusEffectData;
}
}
if (hoveringData.HasValue)
{
StatusEffectData data = hoveringData.Value;
if (NeedsSpecialInput)
{
_wasHovering = true;
InputsHelper.Instance.StartHandlingInputs();
}
// tooltip
if (Config.ShowTooltips)
{
TooltipsHelper.Instance.ShowTooltipOnCursor(
EncryptedStringsHelper.GetString(data.Data.Description.ToDalamudString().ToString()),
EncryptedStringsHelper.GetString(data.Data.Name.ToString()),
data.Status.StatusId,
GetStatusActorName(data.Status)
);
}
bool leftClick = InputsHelper.Instance.HandlingMouseInputs ? InputsHelper.Instance.LeftButtonClicked : ImGui.GetIO().MouseClicked[0];
bool rightClick = InputsHelper.Instance.HandlingMouseInputs ? InputsHelper.Instance.RightButtonClicked : ImGui.GetIO().MouseClicked[1];
// remove buff on right click
bool isFromPlayer = data.Status.SourceObject.Id == Plugin.ObjectTable.LocalPlayer?.GameObjectId;
bool isTheEcho = data.Status.SourceObject.Id is 42 or 239;
if (data.Data.StatusCategory == 1 && (isFromPlayer || isTheEcho) && rightClick)
{
StatusManager.ExecuteStatusOff(data.Status.StatusId, data.Status.SourceObject.ObjectId);
if (NeedsSpecialInput)
{
_wasHovering = false;
InputsHelper.Instance.StopHandlingInputs();
}
}
// automatic add to black list with ctrl+alt+shift click
if (Config.BlacklistConfig.Enabled &&
ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyAlt && ImGui.GetIO().KeyShift && leftClick)
{
Config.BlacklistConfig.AddNewEntry(data.Data);
ConfigurationManager.Instance.ForceNeedsSave();
if (NeedsSpecialInput)
{
_wasHovering = false;
InputsHelper.Instance.StopHandlingInputs();
}
}
}
else if (_wasHovering && NeedsSpecialInput)
{
_wasHovering = false;
InputsHelper.Instance.StopHandlingInputs();
}
}
public StatusEffectIconBorderConfig? GetBorderConfig(StatusEffectData statusEffectData)
{
StatusEffectIconBorderConfig? borderConfig = null;
bool isFromPlayerPet = false;
if (Config.IncludePetAsOwn)
{
isFromPlayerPet = IsStatusFromPlayerPet(statusEffectData.Status);
}
if (Config.IconConfig.OwnedBorderConfig.Enabled && (statusEffectData.Status.SourceObject.Id == Plugin.ObjectTable.LocalPlayer?.GameObjectId || isFromPlayerPet))
{
borderConfig = Config.IconConfig.OwnedBorderConfig;
}
else if (Config.IconConfig.DispellableBorderConfig.Enabled && statusEffectData.Data.CanDispel)
{
borderConfig = Config.IconConfig.DispellableBorderConfig;
}
else if (Config.IconConfig.BorderConfig.Enabled)
{
borderConfig = Config.IconConfig.BorderConfig;
}
return borderConfig;
}
private void OnConfigPropertyChanged(object? sender, OnChangeBaseArgs args)
{
if (args.PropertyName == "Preview")
{
UpdatePreview();
}
}
private unsafe void UpdatePreview()
{
if (!Config.Preview)
{
_fakeEffects = null;
return;
}
var RNG = new Random((int)ImGui.GetTime());
_fakeEffects = new StatusStruct[StatusEffectListsSize];
for (int i = 0; i < StatusEffectListsSize; i++)
{
var fakeStruct = new StatusStruct();
// forcing "triplecast" buff first to always be able to test stacks
fakeStruct.StatusId = i == 0 ? (ushort)1211 : (ushort)RNG.Next(1, 200);
fakeStruct.RemainingTime = RNG.Next(1, 30);
fakeStruct.Param = (byte)RNG.Next(1, 3);
fakeStruct.SourceObject.Id = 0;
_fakeEffects[i] = fakeStruct;
}
}
}
public struct StatusEffectData
{
public StatusStruct Status;
public LuminaStatus Data;
public StatusEffectData(StatusStruct status, LuminaStatus data)
{
Status = status;
Data = data;
}
}
}