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
+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
}