Initial release: HSUI v1.0.0.0 - HUD replacement with configurable hotbars
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user