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,748 @@
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Statuses;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using HSUI.Config;
|
||||
using HSUI.Enums;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.Bars;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using HSUI.Interface.StatusEffects;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public class PartyFramesBar
|
||||
{
|
||||
public delegate void PartyFramesBarEventHandler(PartyFramesBar bar);
|
||||
public PartyFramesBarEventHandler? OpenContextMenuEvent;
|
||||
|
||||
private PartyFramesConfigs _configs;
|
||||
|
||||
private LabelHud _nameLabelHud;
|
||||
private LabelHud _healthLabelHud;
|
||||
private LabelHud _orderLabelHud;
|
||||
private LabelHud _statusLabelHud;
|
||||
private LabelHud _raiseLabelHud;
|
||||
private LabelHud _invulnLabelHud;
|
||||
private PrimaryResourceHud _manaBarHud;
|
||||
private CastbarHud _castbarHud;
|
||||
private StatusEffectsListHud _buffsListHud;
|
||||
private StatusEffectsListHud _debuffsListHud;
|
||||
private PartyFramesCooldownListHud _cooldownListHud;
|
||||
|
||||
private IDalamudTextureWrap? _readyCheckTexture =>
|
||||
TexturesHelper.GetTextureFromPath("ui/uld/ReadyCheck_hr1.tex") ??
|
||||
TexturesHelper.GetTextureFromPath("ui/uld/ReadyCheck.tex");
|
||||
|
||||
public bool Visible = false;
|
||||
public Vector2 Position;
|
||||
|
||||
private SmoothHPHelper _smoothHPHelper = new SmoothHPHelper();
|
||||
|
||||
private bool _wasHovering = false;
|
||||
|
||||
public IPartyFramesMember? Member;
|
||||
|
||||
public PartyFramesBar(string id, PartyFramesConfigs configs)
|
||||
{
|
||||
_configs = configs;
|
||||
|
||||
_nameLabelHud = new LabelHud(_configs.HealthBar.NameLabelConfig);
|
||||
_healthLabelHud = new LabelHud(_configs.HealthBar.HealthLabelConfig);
|
||||
_orderLabelHud = new LabelHud(_configs.HealthBar.OrderNumberConfig);
|
||||
_statusLabelHud = new LabelHud(PlayerStatus.Label);
|
||||
_raiseLabelHud = new LabelHud(RaiseTracker.Icon.NumericLabel);
|
||||
_invulnLabelHud = new LabelHud(InvulnTracker.Icon.NumericLabel);
|
||||
|
||||
_manaBarHud = new PrimaryResourceHud(_configs.ManaBar);
|
||||
_castbarHud = new CastbarHud(_configs.CastBar);
|
||||
_buffsListHud = new StatusEffectsListHud(_configs.Buffs);
|
||||
_debuffsListHud = new StatusEffectsListHud(_configs.Debuffs);
|
||||
|
||||
_cooldownListHud = new PartyFramesCooldownListHud(_configs.CooldownList);
|
||||
}
|
||||
|
||||
public PluginConfigColor GetColor(float scale)
|
||||
{
|
||||
if (Member == null || Member.MaxHP <= 0)
|
||||
{
|
||||
return _configs.HealthBar.ColorsConfig.OutOfReachBackgroundColor;
|
||||
}
|
||||
|
||||
bool cleanseCheck = true;
|
||||
if (CleanseTracker.CleanseJobsOnly)
|
||||
{
|
||||
cleanseCheck = Utils.IsOnCleanseJob();
|
||||
}
|
||||
|
||||
if (CleanseTracker.Enabled && CleanseTracker.ChangeHealthBarCleanseColor && Member.HasDispellableDebuff && cleanseCheck)
|
||||
{
|
||||
return CleanseTracker.HealthBarColor;
|
||||
}
|
||||
else if (_configs.HealthBar.ColorsConfig.ColorByHealth.Enabled)
|
||||
{
|
||||
if (_configs.HealthBar.ColorsConfig.ColorByHealth.UseJobColorAsMaxHealth)
|
||||
{
|
||||
return ColorUtils.GetColorByScale(scale, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColor,
|
||||
GlobalColors.Instance.SafeColorForJobId(Member.JobId), _configs.HealthBar.ColorsConfig.ColorByHealth.UseMaxHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.BlendMode);
|
||||
}
|
||||
else if (_configs.HealthBar.ColorsConfig.ColorByHealth.UseRoleColorAsMaxHealth)
|
||||
{
|
||||
return ColorUtils.GetColorByScale(scale, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColor,
|
||||
GlobalColors.Instance.SafeRoleColorForJobId(Member.JobId), _configs.HealthBar.ColorsConfig.ColorByHealth.UseMaxHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.BlendMode);
|
||||
}
|
||||
return ColorUtils.GetColorByScale(scale, _configs.HealthBar.ColorsConfig.ColorByHealth);
|
||||
}
|
||||
else if (Member.JobId > 0)
|
||||
{
|
||||
return _configs.HealthBar.ColorsConfig.UseRoleColors switch
|
||||
{
|
||||
true => GlobalColors.Instance.SafeRoleColorForJobId(Member.JobId),
|
||||
_ => GlobalColors.Instance.SafeColorForJobId(Member.JobId)
|
||||
};
|
||||
}
|
||||
|
||||
return Member.Character?.ObjectKind switch
|
||||
{
|
||||
ObjectKind.BattleNpc => GlobalColors.Instance.NPCFriendlyColor,
|
||||
_ => _configs.HealthBar.ColorsConfig.OutOfReachBackgroundColor
|
||||
};
|
||||
}
|
||||
|
||||
public void StopPreview()
|
||||
{
|
||||
_castbarHud.StopPreview();
|
||||
_buffsListHud.StopPreview();
|
||||
_debuffsListHud.StopPreview();
|
||||
_cooldownListHud.StopPreview();
|
||||
_configs.HealthBar.MouseoverAreaConfig.Preview = false;
|
||||
}
|
||||
|
||||
public void StopMouseover()
|
||||
{
|
||||
if (_wasHovering)
|
||||
{
|
||||
InputsHelper.Instance.ClearTarget();
|
||||
_wasHovering = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cooldownListHud.Dispose();
|
||||
}
|
||||
|
||||
public List<(StrataLevel, Action)> GetBarDrawActions(Vector2 origin, PluginConfigColor? borderColor = null)
|
||||
{
|
||||
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
|
||||
|
||||
if (!Visible || Member is null)
|
||||
{
|
||||
StopMouseover();
|
||||
return drawActions;
|
||||
}
|
||||
|
||||
// click
|
||||
var (areaStart, areaEnd) = _configs.HealthBar.MouseoverAreaConfig.GetArea(Position, _configs.HealthBar.Size);
|
||||
bool isHovering = ImGui.IsMouseHoveringRect(areaStart, areaEnd);
|
||||
bool ignoreMouseover = _configs.HealthBar.MouseoverAreaConfig.Enabled && _configs.HealthBar.MouseoverAreaConfig.Ignore;
|
||||
ICharacter? character = Member.Character;
|
||||
|
||||
if (isHovering)
|
||||
{
|
||||
_wasHovering = true;
|
||||
InputsHelper.Instance.SetTarget(character, ignoreMouseover);
|
||||
|
||||
// left click
|
||||
if (InputsHelper.Instance.LeftButtonClicked && character != null)
|
||||
{
|
||||
Plugin.TargetManager.Target = character;
|
||||
}
|
||||
// right click (context menu)
|
||||
else if (InputsHelper.Instance.RightButtonClicked)
|
||||
{
|
||||
OpenContextMenuEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
else if (_wasHovering)
|
||||
{
|
||||
InputsHelper.Instance.ClearTarget();
|
||||
_wasHovering = false;
|
||||
}
|
||||
|
||||
// bg
|
||||
PluginConfigColor bgColor = _configs.HealthBar.ColorsConfig.BackgroundColor;
|
||||
if (Member.RaiseTime != null && RaiseTracker.Enabled && RaiseTracker.ChangeBackgroundColorWhenRaised)
|
||||
{
|
||||
bgColor = RaiseTracker.BackgroundColor;
|
||||
}
|
||||
else if (Member.InvulnStatus?.InvulnTime != null && InvulnTracker.Enabled && InvulnTracker.ChangeBackgroundColorWhenInvuln)
|
||||
{
|
||||
bgColor = Member.InvulnStatus?.InvulnId == 811 ? InvulnTracker.WalkingDeadBackgroundColor : InvulnTracker.BackgroundColor;
|
||||
}
|
||||
else if (_configs.HealthBar.ColorsConfig.UseDeathIndicatorBackgroundColor && Member.HP <= 0 && character != null)
|
||||
{
|
||||
bgColor = _configs.HealthBar.RangeConfig.Enabled
|
||||
? GetDistanceColor(character, _configs.HealthBar.ColorsConfig.DeathIndicatorBackgroundColor)
|
||||
: _configs.HealthBar.ColorsConfig.DeathIndicatorBackgroundColor;
|
||||
}
|
||||
else if (_configs.HealthBar.ColorsConfig.UseJobColorAsBackgroundColor && character is IBattleChara)
|
||||
{
|
||||
bgColor = GlobalColors.Instance.SafeColorForJobId(character.ClassJob.RowId);
|
||||
}
|
||||
else if (_configs.HealthBar.ColorsConfig.UseRoleColorAsBackgroundColor && character is IBattleChara)
|
||||
{
|
||||
bgColor = _configs.HealthBar.RangeConfig.Enabled
|
||||
? GetDistanceColor(character, GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId))
|
||||
: GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId);
|
||||
}
|
||||
|
||||
Rect background = new Rect(Position, _configs.HealthBar.Size, bgColor);
|
||||
|
||||
// hp
|
||||
uint currentHp = Member.HP;
|
||||
uint maxHp = Member.MaxHP;
|
||||
|
||||
if (_configs.HealthBar.SmoothHealthConfig.Enabled)
|
||||
{
|
||||
currentHp = _smoothHPHelper.GetNextHp((int)currentHp, (int)maxHp, _configs.HealthBar.SmoothHealthConfig.Velocity);
|
||||
}
|
||||
|
||||
float hpScale = maxHp > 0 ? (float)currentHp / (float)maxHp : 1;
|
||||
PluginConfigColor? hpColor = _configs.HealthBar.RangeConfig.Enabled && character != null
|
||||
? GetDistanceColor(character, GetColor(hpScale))
|
||||
: GetColor(hpScale);
|
||||
|
||||
Rect healthFill = BarUtilities.GetFillRect(Position, _configs.HealthBar.Size, _configs.HealthBar.FillDirection, hpColor, currentHp, maxHp);
|
||||
|
||||
// bar
|
||||
int thickness = borderColor != null ? _configs.HealthBar.ColorsConfig.ActiveBorderThickness : _configs.HealthBar.ColorsConfig.InactiveBorderThickness;
|
||||
|
||||
if (WhosTalkingIcon.ChangeBorders && Member.WhosTalkingState != WhosTalkingState.None)
|
||||
{
|
||||
thickness = WhosTalkingIcon.BorderThickness;
|
||||
}
|
||||
|
||||
borderColor = borderColor ?? GetBorderColor(character);
|
||||
|
||||
BarHud bar = new BarHud(
|
||||
_configs.HealthBar.ID,
|
||||
_configs.HealthBar.ColorsConfig.ShowBorder,
|
||||
borderColor,
|
||||
thickness,
|
||||
actor: character,
|
||||
current: currentHp,
|
||||
max: maxHp,
|
||||
shadowConfig: _configs.HealthBar.ShadowConfig,
|
||||
barTextureName: _configs.HealthBar.BarTextureName,
|
||||
barTextureDrawMode: _configs.HealthBar.BarTextureDrawMode
|
||||
);
|
||||
|
||||
bar.NeedsInputs = true;
|
||||
bar.SetBackground(background);
|
||||
bar.AddForegrounds(healthFill);
|
||||
|
||||
// missing health
|
||||
if (_configs.HealthBar.ColorsConfig.UseMissingHealthBar)
|
||||
{
|
||||
Vector2 healthMissingSize = _configs.HealthBar.Size - BarUtilities.GetFillDirectionOffset(healthFill.Size, _configs.HealthBar.FillDirection);
|
||||
Vector2 healthMissingPos = _configs.HealthBar.FillDirection.IsInverted() ? Position : Position + BarUtilities.GetFillDirectionOffset(healthFill.Size, _configs.HealthBar.FillDirection);
|
||||
|
||||
PluginConfigColor? missingHealthColor = _configs.HealthBar.ColorsConfig.UseJobColorAsMissingHealthColor && character is IBattleChara
|
||||
? GlobalColors.Instance.SafeColorForJobId(character!.ClassJob.RowId)
|
||||
: _configs.HealthBar.ColorsConfig.UseRoleColorAsMissingHealthColor && character is IBattleChara
|
||||
? GlobalColors.Instance.SafeRoleColorForJobId(character!.ClassJob.RowId)
|
||||
: _configs.HealthBar.ColorsConfig.HealthMissingColor;
|
||||
|
||||
if (_configs.HealthBar.ColorsConfig.UseDeathIndicatorBackgroundColor && Member.HP <= 0 && character != null)
|
||||
{
|
||||
missingHealthColor = _configs.HealthBar.ColorsConfig.DeathIndicatorBackgroundColor;
|
||||
}
|
||||
|
||||
if (_configs.Trackers.Invuln.ChangeBackgroundColorWhenInvuln && character is IBattleChara battleChara)
|
||||
{
|
||||
IStatus? tankInvuln = Utils.GetTankInvulnerabilityID(battleChara);
|
||||
if (tankInvuln is not null)
|
||||
{
|
||||
missingHealthColor = _configs.Trackers.Invuln.BackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (_configs.HealthBar.RangeConfig.Enabled)
|
||||
{
|
||||
missingHealthColor = GetDistanceColor(character, missingHealthColor);
|
||||
}
|
||||
|
||||
bar.AddForegrounds(new Rect(healthMissingPos, healthMissingSize, missingHealthColor));
|
||||
}
|
||||
|
||||
// shield
|
||||
if (_configs.HealthBar.ShieldConfig.Enabled)
|
||||
{
|
||||
if (Member.Shield > 0f)
|
||||
{
|
||||
bar.AddForegrounds(
|
||||
BarUtilities.GetShieldForeground(
|
||||
_configs.HealthBar.ShieldConfig,
|
||||
Position,
|
||||
_configs.HealthBar.Size,
|
||||
healthFill.Size,
|
||||
_configs.HealthBar.FillDirection,
|
||||
Member.Shield,
|
||||
currentHp,
|
||||
maxHp
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// highlight
|
||||
bool isSoftTarget = character != null && character == Plugin.TargetManager.SoftTarget;
|
||||
if (_configs.HealthBar.ColorsConfig.ShowHighlight && (isHovering || isSoftTarget))
|
||||
{
|
||||
Rect highlight = new Rect(Position, _configs.HealthBar.Size, _configs.HealthBar.ColorsConfig.HighlightColor);
|
||||
bar.AddForegrounds(highlight);
|
||||
}
|
||||
|
||||
drawActions = bar.GetDrawActions(Vector2.Zero, _configs.HealthBar.StrataLevel);
|
||||
|
||||
// mouseover area
|
||||
BarHud? mouseoverAreaBar = _configs.HealthBar.MouseoverAreaConfig.GetBar(
|
||||
Position,
|
||||
_configs.HealthBar.Size,
|
||||
_configs.HealthBar.ID + "_mouseoverArea"
|
||||
);
|
||||
|
||||
if (mouseoverAreaBar != null)
|
||||
{
|
||||
drawActions.AddRange(mouseoverAreaBar.GetDrawActions(Vector2.Zero, StrataLevel.HIGHEST));
|
||||
}
|
||||
|
||||
return drawActions;
|
||||
}
|
||||
|
||||
private PluginConfigColor GetBorderColor(ICharacter? character)
|
||||
{
|
||||
IGameObject? target = Plugin.TargetManager.Target ?? Plugin.TargetManager.SoftTarget;
|
||||
|
||||
return character != null && character == target ? _configs.HealthBar.ColorsConfig.TargetBordercolor : _configs.HealthBar.ColorsConfig.BorderColor;
|
||||
}
|
||||
|
||||
private PluginConfigColor GetDistanceColor(ICharacter? character, PluginConfigColor color)
|
||||
{
|
||||
byte distance = character != null ? character.YalmDistanceX : byte.MaxValue;
|
||||
float currentAlpha = color.Vector.W * 100f;
|
||||
float alpha = _configs.HealthBar.RangeConfig.AlphaForDistance(distance, currentAlpha) / 100f;
|
||||
|
||||
return color.WithAlpha(alpha);
|
||||
}
|
||||
|
||||
// need to separate elements that have their own window so clipping doesn't get messy
|
||||
public List<(StrataLevel, Action)> GetElementsDrawActions(Vector2 origin)
|
||||
{
|
||||
List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
|
||||
|
||||
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
|
||||
if (!Visible || Member is null || player == null)
|
||||
{
|
||||
StopMouseover();
|
||||
return drawActions;
|
||||
}
|
||||
|
||||
ICharacter? character = Member.Character;
|
||||
|
||||
// who's talking
|
||||
bool drawingWhosTalking = false;
|
||||
if (WhosTalkingIcon.Enabled && WhosTalkingIcon.Icon.Enabled && WhosTalkingIcon.EnabledForState(Member.WhosTalkingState))
|
||||
{
|
||||
IDalamudTextureWrap? texture = WhosTalkingHelper.Instance.GetTextureForState(Member.WhosTalkingState);
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, WhosTalkingIcon.Icon.FrameAnchor);
|
||||
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + WhosTalkingIcon.Icon.Position, WhosTalkingIcon.Icon.Size, WhosTalkingIcon.Icon.Anchor);
|
||||
|
||||
drawActions.Add((WhosTalkingIcon.Icon.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(WhosTalkingIcon.Icon.ID, iconPos, WhosTalkingIcon.Icon.Size, false, (drawList) =>
|
||||
{
|
||||
ImGui.SetCursorPos(iconPos);
|
||||
ImGui.Image(texture.Handle, WhosTalkingIcon.Icon.Size);
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
drawingWhosTalking = true;
|
||||
}
|
||||
}
|
||||
|
||||
// role/job icon
|
||||
if (RoleIcon.Enabled && (!drawingWhosTalking || !WhosTalkingIcon.ReplaceRoleJobIcon))
|
||||
{
|
||||
uint iconId = 0;
|
||||
|
||||
// chocobo icon
|
||||
if (character is IBattleNpc battleNpc && battleNpc.BattleNpcKind == BattleNpcSubKind.Chocobo)
|
||||
{
|
||||
iconId = JobsHelper.RoleIconIDForBattleCompanion + (uint)RoleIcon.Style * 100;
|
||||
}
|
||||
// role/job icon
|
||||
else if (Member.JobId > 0)
|
||||
{
|
||||
iconId = RoleIcon.UseRoleIcons ?
|
||||
JobsHelper.RoleIconIDForJob(Member.JobId, RoleIcon.UseSpecificDPSRoleIcons) :
|
||||
JobsHelper.IconIDForJob(Member.JobId, (uint)RoleIcon.Style);
|
||||
}
|
||||
|
||||
if (iconId > 0)
|
||||
{
|
||||
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, RoleIcon.FrameAnchor);
|
||||
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + RoleIcon.Position, RoleIcon.Size, RoleIcon.Anchor);
|
||||
|
||||
drawActions.Add((RoleIcon.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(RoleIcon.ID, iconPos, RoleIcon.Size, false, (drawList) =>
|
||||
{
|
||||
DrawHelper.DrawIcon(iconId, iconPos, RoleIcon.Size, false, drawList);
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// sign icon
|
||||
if (SignIcon.Enabled)
|
||||
{
|
||||
uint? iconId = SignIcon.IconID(character);
|
||||
if (iconId.HasValue)
|
||||
{
|
||||
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, SignIcon.FrameAnchor);
|
||||
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + SignIcon.Position, SignIcon.Size, SignIcon.Anchor);
|
||||
|
||||
drawActions.Add((SignIcon.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(SignIcon.ID, iconPos, SignIcon.Size, false, (drawList) =>
|
||||
{
|
||||
DrawHelper.DrawIcon(iconId.Value, iconPos, SignIcon.Size, false, drawList);
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// leader icon
|
||||
if (LeaderIcon.Enabled && Member.IsPartyLeader)
|
||||
{
|
||||
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, LeaderIcon.FrameAnchor);
|
||||
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + LeaderIcon.Position, LeaderIcon.Size, LeaderIcon.Anchor);
|
||||
|
||||
drawActions.Add((LeaderIcon.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(LeaderIcon.ID, iconPos, LeaderIcon.Size, false, (drawList) =>
|
||||
{
|
||||
DrawHelper.DrawIcon(61521, iconPos, LeaderIcon.Size, false, drawList);
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// player status icon
|
||||
if (PlayerStatus.Enabled && PlayerStatus.Icon.Enabled)
|
||||
{
|
||||
uint? iconId = IconIdForStatus(Member.Status);
|
||||
if (iconId.HasValue)
|
||||
{
|
||||
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, PlayerStatus.Icon.FrameAnchor);
|
||||
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + PlayerStatus.Icon.Position, PlayerStatus.Icon.Size, PlayerStatus.Icon.Anchor);
|
||||
|
||||
drawActions.Add((PlayerStatus.Icon.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(PlayerStatus.Icon.ID, iconPos, PlayerStatus.Icon.Size, false, (drawList) =>
|
||||
{
|
||||
DrawHelper.DrawIcon(iconId.Value, iconPos, PlayerStatus.Icon.Size, false, drawList);
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// ready check status icon
|
||||
if (Member.ReadyCheckStatus != ReadyCheckStatus.None && ReadyCheckIcon.Enabled && ReadyCheckIcon.Icon.Enabled && _readyCheckTexture != null)
|
||||
{
|
||||
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, ReadyCheckIcon.Icon.FrameAnchor);
|
||||
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + ReadyCheckIcon.Icon.Position, ReadyCheckIcon.Icon.Size, ReadyCheckIcon.Icon.Anchor);
|
||||
|
||||
drawActions.Add((ReadyCheckIcon.Icon.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(ReadyCheckIcon.Icon.ID, iconPos, ReadyCheckIcon.Icon.Size, false, (drawList) =>
|
||||
{
|
||||
Vector2 uv0 = new Vector2(0.5f * (int)Member.ReadyCheckStatus, 0f);
|
||||
Vector2 uv1 = new Vector2(0.5f + 0.5f * (int)Member.ReadyCheckStatus, 1f);
|
||||
drawList.AddImage(_readyCheckTexture.Handle, iconPos, iconPos + ReadyCheckIcon.Icon.Size, uv0, uv1);
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// raise icon
|
||||
bool showingRaise = ShowingRaise();
|
||||
if (showingRaise && RaiseTracker.Icon.Enabled)
|
||||
{
|
||||
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, RaiseTracker.Icon.FrameAnchor);
|
||||
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + RaiseTracker.Icon.Position, RaiseTracker.Icon.Size, RaiseTracker.Icon.Anchor);
|
||||
|
||||
drawActions.Add((RaiseTracker.Icon.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(RaiseTracker.Icon.ID, iconPos, RaiseTracker.Icon.Size, false, (drawList) =>
|
||||
{
|
||||
DrawHelper.DrawIcon(411, iconPos, RaiseTracker.Icon.Size, true, drawList);
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// invuln icon
|
||||
bool showingInvuln = ShowingInvuln();
|
||||
if (showingInvuln && InvulnTracker.Icon.Enabled)
|
||||
{
|
||||
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, InvulnTracker.Icon.FrameAnchor);
|
||||
Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + InvulnTracker.Icon.Position, InvulnTracker.Icon.Size, InvulnTracker.Icon.Anchor);
|
||||
|
||||
drawActions.Add((InvulnTracker.Icon.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(InvulnTracker.Icon.ID, iconPos, InvulnTracker.Icon.Size, false, (drawList) =>
|
||||
{
|
||||
DrawHelper.DrawIcon(Member.InvulnStatus!.InvulnIcon, iconPos, InvulnTracker.Icon.Size, true, drawList);
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// mana
|
||||
if (ShowMana())
|
||||
{
|
||||
Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.ManaBar.HealthBarAnchor);
|
||||
drawActions.Add((_configs.ManaBar.StrataLevel, () =>
|
||||
{
|
||||
_manaBarHud.Actor = character;
|
||||
_manaBarHud.PartyMember = Member;
|
||||
_manaBarHud.PrepareForDraw(parentPos);
|
||||
_manaBarHud.Draw(parentPos);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// buffs / debuffs
|
||||
Vector2 buffsPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.Buffs.HealthBarAnchor);
|
||||
drawActions.Add((_configs.Buffs.StrataLevel, () =>
|
||||
{
|
||||
_buffsListHud.Actor = character;
|
||||
_buffsListHud.PrepareForDraw(buffsPos);
|
||||
_buffsListHud.Draw(buffsPos);
|
||||
}
|
||||
));
|
||||
|
||||
Vector2 debuffsPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.Debuffs.HealthBarAnchor);
|
||||
drawActions.Add((_configs.Debuffs.StrataLevel, () =>
|
||||
{
|
||||
_debuffsListHud.Actor = character;
|
||||
_debuffsListHud.PrepareForDraw(debuffsPos);
|
||||
_debuffsListHud.Draw(debuffsPos);
|
||||
}
|
||||
));
|
||||
|
||||
// cooldown list
|
||||
Vector2 cooldownListPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.CooldownList.HealthBarAnchor);
|
||||
drawActions.Add((_configs.CooldownList.StrataLevel, () =>
|
||||
{
|
||||
_cooldownListHud.Actor = character;
|
||||
_cooldownListHud.PrepareForDraw(cooldownListPos);
|
||||
_cooldownListHud.Draw(cooldownListPos);
|
||||
}
|
||||
));
|
||||
|
||||
// castbar
|
||||
Vector2 castbarPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.CastBar.HealthBarAnchor);
|
||||
drawActions.Add((_configs.CastBar.StrataLevel, () =>
|
||||
{
|
||||
_castbarHud.Actor = character;
|
||||
_castbarHud.PrepareForDraw(castbarPos);
|
||||
_castbarHud.Draw(castbarPos);
|
||||
}
|
||||
));
|
||||
|
||||
// name
|
||||
bool drawName = ShouldDrawName(character, showingRaise, showingInvuln);
|
||||
if (drawName)
|
||||
{
|
||||
drawActions.Add((_configs.HealthBar.NameLabelConfig.StrataLevel, () =>
|
||||
{
|
||||
bool? playerName = null;
|
||||
if (character == null || character.ObjectKind == ObjectKind.Player)
|
||||
{
|
||||
playerName = true;
|
||||
}
|
||||
|
||||
_nameLabelHud.Draw(Position, _configs.HealthBar.Size, character, Member.Name, isPlayerName: playerName);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// health label
|
||||
if (Member.MaxHP > 0)
|
||||
{
|
||||
drawActions.Add((_configs.HealthBar.HealthLabelConfig.StrataLevel, () =>
|
||||
{
|
||||
_healthLabelHud.Draw(Position, _configs.HealthBar.Size, character, null, Member.HP, Member.MaxHP);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// order
|
||||
if (character == null || character?.ObjectKind != ObjectKind.BattleNpc)
|
||||
{
|
||||
string str = char.ConvertFromUtf32(0xE090 + Member.Order).ToString();
|
||||
|
||||
drawActions.Add((_configs.HealthBar.OrderNumberConfig.StrataLevel, () =>
|
||||
{
|
||||
_configs.HealthBar.OrderNumberConfig.SetText(str);
|
||||
_orderLabelHud.Draw(Position, _configs.HealthBar.Size, character);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// status
|
||||
string? statusString = StringForStatus(Member.Status);
|
||||
if (PlayerStatus.Enabled && PlayerStatus.Label.Enabled && statusString != null)
|
||||
{
|
||||
drawActions.Add((PlayerStatus.Label.StrataLevel, () =>
|
||||
{
|
||||
PlayerStatus.Label.SetText(statusString);
|
||||
_statusLabelHud.Draw(Position, _configs.HealthBar.Size);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// raise label
|
||||
if (showingRaise)
|
||||
{
|
||||
float duration = Math.Abs(Member.RaiseTime!.Value);
|
||||
|
||||
drawActions.Add((RaiseTracker.Icon.NumericLabel.StrataLevel, () =>
|
||||
{
|
||||
RaiseTracker.Icon.NumericLabel.SetValue(duration);
|
||||
_raiseLabelHud.Draw(Position, _configs.HealthBar.Size);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// invuln label
|
||||
if (showingInvuln)
|
||||
{
|
||||
float duration = Math.Abs(Member.InvulnStatus!.InvulnTime);
|
||||
|
||||
drawActions.Add((InvulnTracker.Icon.NumericLabel.StrataLevel, () =>
|
||||
{
|
||||
InvulnTracker.Icon.NumericLabel.SetValue(duration);
|
||||
_invulnLabelHud.Draw(Position, _configs.HealthBar.Size);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
return drawActions;
|
||||
}
|
||||
|
||||
private bool ShouldDrawName(ICharacter? character, bool showingRaise, bool showingInvuln)
|
||||
{
|
||||
if (showingRaise && RaiseTracker.HideNameWhenRaised)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (showingInvuln && InvulnTracker.HideNameWhenInvuln)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Member != null && PlayerStatus.Enabled && PlayerStatus.HideName && Member.Status != PartyMemberStatus.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Member != null && ReadyCheckIcon.Enabled && ReadyCheckIcon.HideName && Member.ReadyCheckStatus != ReadyCheckStatus.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Utils.IsActorCasting(character) && _configs.CastBar.Enabled && _configs.CastBar.HideNameWhenCasting)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ShowingRaise() =>
|
||||
Member != null && Member.RaiseTime.HasValue && RaiseTracker.Enabled &&
|
||||
(Member.RaiseTime.Value > 0 || RaiseTracker.KeepIconAfterCastFinishes);
|
||||
|
||||
private bool ShowingInvuln() => Member != null && Member.InvulnStatus != null && InvulnTracker.Enabled && Member.InvulnStatus.InvulnTime > 0;
|
||||
|
||||
private bool ShowMana()
|
||||
{
|
||||
if (Member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var isHealer = JobsHelper.IsJobHealer(Member.JobId);
|
||||
|
||||
return _configs.ManaBar.Enabled && Member.MaxHP > 0 && _configs.ManaBar.ManaBarDisplayMode switch
|
||||
{
|
||||
PartyFramesManaBarDisplayMode.Always => true,
|
||||
PartyFramesManaBarDisplayMode.HealersOnly => isHealer,
|
||||
PartyFramesManaBarDisplayMode.HealersAndRaiseJobs => isHealer || JobsHelper.IsJobWithRaise(Member.JobId, Member.Level),
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
private static uint? IconIdForStatus(PartyMemberStatus status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
PartyMemberStatus.ViewingCutscene => 61508,
|
||||
PartyMemberStatus.Offline => 61504,
|
||||
PartyMemberStatus.Dead => 61502,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static string? StringForStatus(PartyMemberStatus status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
PartyMemberStatus.ViewingCutscene => "[Viewing Cutscene]",
|
||||
PartyMemberStatus.Offline => "[Offline]",
|
||||
PartyMemberStatus.Dead => "[Dead]",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
#region convenience
|
||||
private PartyFramesRoleIconConfig RoleIcon => _configs.Icons.Role;
|
||||
private SignIconConfig SignIcon => _configs.Icons.Sign;
|
||||
private PartyFramesLeaderIconConfig LeaderIcon => _configs.Icons.Leader;
|
||||
private PartyFramesPlayerStatusConfig PlayerStatus => _configs.Icons.PlayerStatus;
|
||||
private PartyFramesReadyCheckStatusConfig ReadyCheckIcon => _configs.Icons.ReadyCheckStatus;
|
||||
private PartyFramesWhosTalkingConfig WhosTalkingIcon => _configs.Icons.WhosTalking;
|
||||
private PartyFramesRaiseTrackerConfig RaiseTracker => _configs.Trackers.Raise;
|
||||
private PartyFramesInvulnTrackerConfig InvulnTracker => _configs.Trackers.Invuln;
|
||||
private PartyFramesCleanseTrackerConfig CleanseTracker => _configs.Trackers.Cleanse;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using HSUI.Config;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using HSUI.Helpers;
|
||||
using Dalamud.Game.ClientState.Statuses;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public class PartyFramesCleanseTracker : IDisposable
|
||||
{
|
||||
private PartyFramesCleanseTrackerConfig _config = null!;
|
||||
|
||||
public PartyFramesCleanseTracker()
|
||||
{
|
||||
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||
OnConfigReset(ConfigurationManager.Instance);
|
||||
}
|
||||
|
||||
~PartyFramesCleanseTracker()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
|
||||
}
|
||||
|
||||
public void OnConfigReset(ConfigurationManager sender)
|
||||
{
|
||||
_config = ConfigurationManager.Instance.GetConfigObject<PartyFramesTrackersConfig>().Cleanse;
|
||||
}
|
||||
|
||||
public void Update(List<IPartyFramesMember> partyMembers)
|
||||
{
|
||||
if (!_config.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var member in partyMembers)
|
||||
{
|
||||
member.HasDispellableDebuff = false;
|
||||
|
||||
if (member.Character is not IBattleChara battleChara)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for disspellable debuff
|
||||
IEnumerable<IStatus> statusList = Utils.StatusListForBattleChara(battleChara);
|
||||
foreach (IStatus status in statusList)
|
||||
{
|
||||
if (!status.GameData.Value.CanDispel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// apply raise data based on buff
|
||||
member.HasDispellableDebuff = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,852 @@
|
||||
using HSUI.Config;
|
||||
using HSUI.Config.Attributes;
|
||||
using HSUI.Enums;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.Bars;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using HSUI.Interface.PartyCooldowns;
|
||||
using HSUI.Interface.StatusEffects;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
[Exportable(false)]
|
||||
[Section("Party Frames", true)]
|
||||
[SubSection("General", 0)]
|
||||
public class PartyFramesConfig : MovablePluginConfigObject
|
||||
{
|
||||
public new static PartyFramesConfig DefaultConfig()
|
||||
{
|
||||
var config = new PartyFramesConfig();
|
||||
config.Position = new Vector2(-ImGui.GetMainViewport().Size.X / 3 - 180, -120);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
[Checkbox("Preview", isMonitored = true)]
|
||||
[Order(4)]
|
||||
public bool Preview = false;
|
||||
|
||||
[DragInt("Rows", spacing = true, isMonitored = true, min = 1, max = 8, velocity = 0.2f)]
|
||||
[Order(10)]
|
||||
public int Rows = 4;
|
||||
|
||||
[DragInt("Columns", isMonitored = true, min = 1, max = 8, velocity = 0.2f)]
|
||||
[Order(11)]
|
||||
public int Columns = 2;
|
||||
|
||||
[Anchor("Bars Anchor", isMonitored = true, spacing = true)]
|
||||
[Order(15)]
|
||||
public DrawAnchor BarsAnchor = DrawAnchor.TopLeft;
|
||||
|
||||
[Checkbox("Fill Rows First", isMonitored = true)]
|
||||
[Order(20)]
|
||||
public bool FillRowsFirst = true;
|
||||
|
||||
[Checkbox("Show When Solo", spacing = true)]
|
||||
[Order(50)]
|
||||
public bool ShowWhenSolo = false;
|
||||
|
||||
[Checkbox("Show Chocobo", isMonitored = true)]
|
||||
[Order(55)]
|
||||
public bool ShowChocobo = true;
|
||||
|
||||
[NestedConfig("Party Title Label", 60)]
|
||||
public PartyFramesTitleLabel ShowPartyTitleConfig = new PartyFramesTitleLabel(Vector2.Zero, "", DrawAnchor.Left, DrawAnchor.Left);
|
||||
|
||||
[NestedConfig("Visibility", 200)]
|
||||
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
[DisableParentSettings("FrameAnchor", "UseJobColor", "UseRoleColor")]
|
||||
public class PartyFramesTitleLabel : LabelConfig
|
||||
{
|
||||
public PartyFramesTitleLabel(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor) : base(position, text, frameAnchor, textAnchor)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
[Disableable(false)]
|
||||
[DisableParentSettings("Position", "Anchor", "BackgroundColor", "FillColor", "HideWhenInactive", "DrawBorder", "BorderColor", "BorderThickness")]
|
||||
[Section("Party Frames", true)]
|
||||
[SubSection("Health Bar", 0)]
|
||||
public class PartyFramesHealthBarsConfig : BarConfig
|
||||
{
|
||||
public new static PartyFramesHealthBarsConfig DefaultConfig()
|
||||
{
|
||||
var config = new PartyFramesHealthBarsConfig(Vector2.Zero, new(180, 80), PluginConfigColor.Empty);
|
||||
config.MouseoverAreaConfig.Enabled = false;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
[DragInt2("Padding", isMonitored = true, min = 0)]
|
||||
[Order(31)]
|
||||
public Vector2 Padding = new Vector2(0, 0);
|
||||
|
||||
[NestedConfig("Name Label", 44)]
|
||||
public EditableLabelConfig NameLabelConfig = new EditableLabelConfig(Vector2.Zero, "[name:initials].", DrawAnchor.Center, DrawAnchor.Center);
|
||||
|
||||
[NestedConfig("Health Label", 45)]
|
||||
public EditableLabelConfig HealthLabelConfig = new EditableLabelConfig(Vector2.Zero, "[health:current-short]", DrawAnchor.Right, DrawAnchor.Right);
|
||||
|
||||
[NestedConfig("Order Label", 50)]
|
||||
public DefaultFontLabelConfig OrderNumberConfig = new DefaultFontLabelConfig(new Vector2(2, 4), "", DrawAnchor.TopLeft, DrawAnchor.TopLeft);
|
||||
|
||||
[NestedConfig("Colors", 55)]
|
||||
public PartyFramesColorsConfig ColorsConfig = new PartyFramesColorsConfig();
|
||||
|
||||
[NestedConfig("Shield", 60)]
|
||||
public ShieldConfig ShieldConfig = new ShieldConfig();
|
||||
|
||||
[NestedConfig("Change Alpha Based on Range", 65)]
|
||||
public PartyFramesRangeConfig RangeConfig = new PartyFramesRangeConfig();
|
||||
|
||||
[NestedConfig("Use Smooth Transitions", 70)]
|
||||
public SmoothHealthConfig SmoothHealthConfig = new SmoothHealthConfig();
|
||||
|
||||
[NestedConfig("Custom Mouseover Area", 75)]
|
||||
public MouseoverAreaConfig MouseoverAreaConfig = new MouseoverAreaConfig();
|
||||
|
||||
public PartyFramesHealthBarsConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
|
||||
: base(position, size, fillColor, fillDirection)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Disableable(false)]
|
||||
[Exportable(false)]
|
||||
public class PartyFramesColorsConfig : PluginConfigObject
|
||||
{
|
||||
[Checkbox("Show Border")]
|
||||
[Order(4)]
|
||||
public bool ShowBorder = true;
|
||||
|
||||
[ColorEdit4("Border Color")]
|
||||
[Order(5, collapseWith = nameof(ShowBorder))]
|
||||
public PluginConfigColor BorderColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||
|
||||
[ColorEdit4("Target Border Color")]
|
||||
[Order(6, collapseWith = nameof(ShowBorder))]
|
||||
public PluginConfigColor TargetBordercolor = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));
|
||||
|
||||
[DragInt("Inactive Border Thickness", min = 1, max = 10, help = "This is the border thickness that will be used when the border is in the default state (aka not targetted, not showing enmity, etc).")]
|
||||
[Order(6, collapseWith = nameof(ShowBorder))]
|
||||
public int InactiveBorderThickness = 1;
|
||||
|
||||
[DragInt("Active Border Thickness", min = 1, max = 10, help = "This is the border thickness that will be used when the border active (aka targetted, showing enmity, etc).")]
|
||||
[Order(7, collapseWith = nameof(ShowBorder))]
|
||||
public int ActiveBorderThickness = 1;
|
||||
|
||||
[ColorEdit4("Background Color", spacing = true)]
|
||||
[Order(15)]
|
||||
public PluginConfigColor BackgroundColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 70f / 100f));
|
||||
|
||||
[ColorEdit4("Out of Reach Background Color", help = "This background color will be used when the player's data couldn't be retreived (i.e. player is disconnected)")]
|
||||
[Order(15)]
|
||||
public PluginConfigColor OutOfReachBackgroundColor = new PluginConfigColor(new Vector4(50f / 255f, 50f / 255f, 50f / 255f, 70f / 100f));
|
||||
|
||||
[Checkbox("Use Death Indicator Background Color", isMonitored = true, spacing = true)]
|
||||
[Order(18)]
|
||||
public bool UseDeathIndicatorBackgroundColor = false;
|
||||
|
||||
[ColorEdit4("Death Indicator Background Color")]
|
||||
[Order(19, collapseWith = nameof(UseDeathIndicatorBackgroundColor))]
|
||||
public PluginConfigColor DeathIndicatorBackgroundColor = new PluginConfigColor(new Vector4(204f / 255f, 3f / 255f, 3f / 255f, 80f / 100f));
|
||||
|
||||
[Checkbox("Use Role Colors", isMonitored = true, spacing = true)]
|
||||
[Order(20)]
|
||||
public bool UseRoleColors = false;
|
||||
|
||||
[NestedConfig("Color Based On Health Value", 30, collapsingHeader = false)]
|
||||
public ColorByHealthValueConfig ColorByHealth = new ColorByHealthValueConfig();
|
||||
|
||||
[Checkbox("Highlight When Hovering With Cursor Or Soft Targeting", spacing = true)]
|
||||
[Order(40)]
|
||||
public bool ShowHighlight = true;
|
||||
|
||||
[ColorEdit4("Highlight Color")]
|
||||
[Order(45, collapseWith = nameof(ShowHighlight))]
|
||||
public PluginConfigColor HighlightColor = new PluginConfigColor(new Vector4(255f / 255f, 255f / 255f, 255f / 255f, 5f / 100f));
|
||||
|
||||
|
||||
[Checkbox("Missing Health Color", spacing = true)]
|
||||
[Order(46)]
|
||||
public bool UseMissingHealthBar = false;
|
||||
|
||||
[Checkbox("Job Color As Missing Health Color")]
|
||||
[Order(47, collapseWith = nameof(UseMissingHealthBar))]
|
||||
public bool UseJobColorAsMissingHealthColor = false;
|
||||
|
||||
[Checkbox("Role Color As Missing Health Color")]
|
||||
[Order(48, collapseWith = nameof(UseMissingHealthBar))]
|
||||
public bool UseRoleColorAsMissingHealthColor = false;
|
||||
|
||||
[ColorEdit4("Color" + "##MissingHealth")]
|
||||
[Order(49, collapseWith = nameof(UseMissingHealthBar))]
|
||||
public PluginConfigColor HealthMissingColor = new(new Vector4(255f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||
|
||||
[Checkbox("Job Color As Background Color")]
|
||||
[Order(50)]
|
||||
public bool UseJobColorAsBackgroundColor = false;
|
||||
|
||||
[Checkbox("Role Color As Background Color")]
|
||||
[Order(51)]
|
||||
public bool UseRoleColorAsBackgroundColor = false;
|
||||
|
||||
[Checkbox("Show Enmity Border Colors", spacing = true)]
|
||||
[Order(54)]
|
||||
public bool ShowEnmityBorderColors = true;
|
||||
|
||||
[ColorEdit4("Enmity Leader Color")]
|
||||
[Order(55, collapseWith = nameof(ShowEnmityBorderColors))]
|
||||
public PluginConfigColor EnmityLeaderBordercolor = new PluginConfigColor(new Vector4(255f / 255f, 40f / 255f, 40f / 255f, 100f / 100f));
|
||||
|
||||
[Checkbox("Show Second Enmity")]
|
||||
[Order(60, collapseWith = nameof(ShowEnmityBorderColors))]
|
||||
public bool ShowSecondEnmity = true;
|
||||
|
||||
[Checkbox("Hide Second Enmity in Light Parties")]
|
||||
[Order(65, collapseWith = nameof(ShowSecondEnmity))]
|
||||
public bool HideSecondEnmityInLightParties = true;
|
||||
|
||||
[ColorEdit4("Enmity Second Color")]
|
||||
[Order(70, collapseWith = nameof(ShowSecondEnmity))]
|
||||
public PluginConfigColor EnmitySecondBordercolor = new PluginConfigColor(new Vector4(255f / 255f, 175f / 255f, 40f / 255f, 100f / 100f));
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class PartyFramesRangeConfig : PluginConfigObject
|
||||
{
|
||||
[DragInt("Range (yalms)", min = 1, max = 500)]
|
||||
[Order(5)]
|
||||
public int Range = 30;
|
||||
|
||||
[DragFloat("Alpha", min = 1, max = 100)]
|
||||
[Order(10)]
|
||||
public float Alpha = 25;
|
||||
|
||||
[Checkbox("Use Additional Range Check")]
|
||||
[Order(15)]
|
||||
public bool UseAdditionalRangeCheck = false;
|
||||
|
||||
[DragInt("Additional Range (yalms)", min = 1, max = 500)]
|
||||
[Order(20, collapseWith = nameof(UseAdditionalRangeCheck))]
|
||||
public int AdditionalRange = 15;
|
||||
|
||||
[DragFloat("Additional Alpha", min = 1, max = 100)]
|
||||
[Order(25, collapseWith = nameof(UseAdditionalRangeCheck))]
|
||||
public float AdditionalAlpha = 60;
|
||||
|
||||
public float AlphaForDistance(int distance, float alpha = 100f)
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
return 100f;
|
||||
}
|
||||
|
||||
if (!UseAdditionalRangeCheck)
|
||||
{
|
||||
return distance > Range ? Alpha : alpha;
|
||||
}
|
||||
|
||||
if (Range > AdditionalRange)
|
||||
{
|
||||
return distance > Range ? Alpha : (distance > AdditionalRange ? AdditionalAlpha : alpha);
|
||||
}
|
||||
|
||||
return distance > AdditionalRange ? AdditionalAlpha : (distance > Range ? Alpha : alpha);
|
||||
}
|
||||
}
|
||||
|
||||
public class PartyFramesManaBarConfigConverter : PluginConfigObjectConverter
|
||||
{
|
||||
public PartyFramesManaBarConfigConverter()
|
||||
{
|
||||
NewTypeFieldConverter<bool, PartyFramesManaBarDisplayMode> converter;
|
||||
converter = new NewTypeFieldConverter<bool, PartyFramesManaBarDisplayMode>(
|
||||
"PartyFramesManaBarDisplayMode",
|
||||
PartyFramesManaBarDisplayMode.HealersOnly,
|
||||
(oldValue) =>
|
||||
{
|
||||
return oldValue ? PartyFramesManaBarDisplayMode.HealersOnly : PartyFramesManaBarDisplayMode.Always;
|
||||
});
|
||||
|
||||
FieldConvertersMap.Add("ShowOnlyForHealers", converter);
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(PartyFramesManaBarConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public enum PartyFramesManaBarDisplayMode
|
||||
{
|
||||
HealersAndRaiseJobs,
|
||||
HealersOnly,
|
||||
Always,
|
||||
}
|
||||
|
||||
[DisableParentSettings("HideWhenInactive", "Label")]
|
||||
[Exportable(false)]
|
||||
[Section("Party Frames", true)]
|
||||
[SubSection("Mana Bar", 0)]
|
||||
public class PartyFramesManaBarConfig : PrimaryResourceConfig
|
||||
{
|
||||
public new static PartyFramesManaBarConfig DefaultConfig()
|
||||
{
|
||||
var config = new PartyFramesManaBarConfig(Vector2.Zero, new(180, 6));
|
||||
config.HealthBarAnchor = DrawAnchor.Bottom;
|
||||
config.Anchor = DrawAnchor.Bottom;
|
||||
config.ValueLabel.Enabled = false;
|
||||
return config;
|
||||
}
|
||||
|
||||
[Anchor("Health Bar Anchor")]
|
||||
[Order(14)]
|
||||
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
|
||||
|
||||
[RadioSelector("Show For All Jobs With Raise", "Show Only For Healers", "Show For All Jobs")]
|
||||
[Order(42)]
|
||||
public PartyFramesManaBarDisplayMode ManaBarDisplayMode = PartyFramesManaBarDisplayMode.HealersOnly;
|
||||
|
||||
public PartyFramesManaBarConfig(Vector2 position, Vector2 size)
|
||||
: base(position, size)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
[Section("Party Frames", true)]
|
||||
[SubSection("Castbar", 0)]
|
||||
public class PartyFramesCastbarConfig : CastbarConfig
|
||||
{
|
||||
public new static PartyFramesCastbarConfig DefaultConfig()
|
||||
{
|
||||
var size = new Vector2(182, 10);
|
||||
var pos = new Vector2(-1, 0);
|
||||
|
||||
var castNameConfig = new LabelConfig(new Vector2(5, 0), "", DrawAnchor.Left, DrawAnchor.Left);
|
||||
var castTimeConfig = new NumericLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
|
||||
castTimeConfig.Enabled = false;
|
||||
castTimeConfig.NumberFormat = 1;
|
||||
|
||||
var config = new PartyFramesCastbarConfig(pos, size, castNameConfig, castTimeConfig);
|
||||
config.HealthBarAnchor = DrawAnchor.BottomLeft;
|
||||
config.Anchor = DrawAnchor.TopLeft;
|
||||
config.ShowIcon = false;
|
||||
config.Enabled = false;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
[Checkbox("Hide Name When Casting")]
|
||||
[Order(6)]
|
||||
public bool HideNameWhenCasting = false;
|
||||
|
||||
[Anchor("Health Bar Anchor")]
|
||||
[Order(16)]
|
||||
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
|
||||
|
||||
public PartyFramesCastbarConfig(Vector2 position, Vector2 size, LabelConfig castNameConfig, NumericLabelConfig castTimeConfig)
|
||||
: base(position, size, castNameConfig, castTimeConfig)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[Disableable(false)]
|
||||
[Exportable(false)]
|
||||
[Section("Party Frames", true)]
|
||||
[SubSection("Icons", 0)]
|
||||
public class PartyFramesIconsConfig : PluginConfigObject
|
||||
{
|
||||
public new static PartyFramesIconsConfig DefaultConfig() { return new PartyFramesIconsConfig(); }
|
||||
|
||||
[NestedConfig("Role / Job", 10, separator = false)]
|
||||
public PartyFramesRoleIconConfig Role = new PartyFramesRoleIconConfig(
|
||||
new Vector2(20, 0),
|
||||
new Vector2(20, 20),
|
||||
DrawAnchor.TopLeft,
|
||||
DrawAnchor.TopLeft
|
||||
);
|
||||
|
||||
[NestedConfig("Sign", 11)]
|
||||
public SignIconConfig Sign = new SignIconConfig(
|
||||
new Vector2(0, -10),
|
||||
new Vector2(30, 30),
|
||||
DrawAnchor.Top,
|
||||
DrawAnchor.Top
|
||||
);
|
||||
|
||||
[NestedConfig("Leader", 12)]
|
||||
public PartyFramesLeaderIconConfig Leader = new PartyFramesLeaderIconConfig(
|
||||
new Vector2(-12, -12),
|
||||
new Vector2(24, 24),
|
||||
DrawAnchor.TopLeft,
|
||||
DrawAnchor.TopLeft
|
||||
);
|
||||
|
||||
[NestedConfig("Player Status", 13)]
|
||||
public PartyFramesPlayerStatusConfig PlayerStatus = new PartyFramesPlayerStatusConfig();
|
||||
|
||||
[NestedConfig("Ready Check Status", 14)]
|
||||
public PartyFramesReadyCheckStatusConfig ReadyCheckStatus = new PartyFramesReadyCheckStatusConfig();
|
||||
|
||||
[NestedConfig("Who's Talking", 15)]
|
||||
public PartyFramesWhosTalkingConfig WhosTalking = new PartyFramesWhosTalkingConfig();
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class PartyFramesRoleIconConfig : RoleJobIconConfig
|
||||
{
|
||||
public PartyFramesRoleIconConfig() : base() { }
|
||||
|
||||
public PartyFramesRoleIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||
: base(position, size, anchor, frameAnchor)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class PartyFramesLeaderIconConfig : IconConfig
|
||||
{
|
||||
public PartyFramesLeaderIconConfig() : base() { }
|
||||
|
||||
public PartyFramesLeaderIconConfig(Vector2 position, Vector2 size, DrawAnchor anchor, DrawAnchor frameAnchor)
|
||||
: base(position, size, anchor, frameAnchor)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class PartyFramesPlayerStatusConfig : PluginConfigObject
|
||||
{
|
||||
public new static PartyFramesPlayerStatusConfig DefaultConfig()
|
||||
{
|
||||
var config = new PartyFramesPlayerStatusConfig();
|
||||
config.Label.Enabled = false;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
[Checkbox("Hide Name When Showing Status")]
|
||||
[Order(5)]
|
||||
public bool HideName = false;
|
||||
|
||||
[NestedConfig("Icon", 10)]
|
||||
public IconConfig Icon = new IconConfig(
|
||||
new Vector2(0, 5),
|
||||
new Vector2(16, 16),
|
||||
DrawAnchor.Top,
|
||||
DrawAnchor.Top
|
||||
);
|
||||
|
||||
[NestedConfig("Label", 15)]
|
||||
public LabelConfig Label = new LabelConfig(Vector2.Zero, "", DrawAnchor.Center, DrawAnchor.Center);
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class PartyFramesReadyCheckStatusConfig : PluginConfigObject
|
||||
{
|
||||
public new static PartyFramesReadyCheckStatusConfig DefaultConfig() => new PartyFramesReadyCheckStatusConfig();
|
||||
|
||||
[Checkbox("Hide Name When Showing Status")]
|
||||
[Order(5)]
|
||||
public bool HideName = false;
|
||||
|
||||
[DragInt("Duration (seconds)", min = 1, max = 60, help = "Determines for how long the icons will show after a ready check is finished.")]
|
||||
[Order(6)]
|
||||
public int Duration = 10;
|
||||
|
||||
[NestedConfig("Icon", 10)]
|
||||
public IconConfig Icon = new IconConfig(
|
||||
new Vector2(0, 0),
|
||||
new Vector2(24, 24),
|
||||
DrawAnchor.TopRight,
|
||||
DrawAnchor.TopRight
|
||||
);
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class PartyFramesWhosTalkingConfig : PluginConfigObject
|
||||
{
|
||||
public new static PartyFramesWhosTalkingConfig DefaultConfig() => new PartyFramesWhosTalkingConfig();
|
||||
|
||||
[Checkbox("Replace Role/Job Icon when active")]
|
||||
[Order(5)]
|
||||
public bool ReplaceRoleJobIcon = false;
|
||||
|
||||
[Checkbox("Show Speaking State", spacing = true)]
|
||||
[Order(10)]
|
||||
public bool ShowSpeaking = true;
|
||||
|
||||
[Checkbox("Show Muted State")]
|
||||
[Order(10)]
|
||||
public bool ShowMuted = true;
|
||||
|
||||
[Checkbox("Show Deafened State")]
|
||||
[Order(10)]
|
||||
public bool ShowDeafened = true;
|
||||
|
||||
[NestedConfig("Icon", 20)]
|
||||
public IconConfig Icon = new IconConfig(
|
||||
new Vector2(0, 0),
|
||||
new Vector2(24, 24),
|
||||
DrawAnchor.TopRight,
|
||||
DrawAnchor.TopRight
|
||||
);
|
||||
|
||||
[Checkbox("Change Health Bar Border when active", spacing = true, help = "Enabling this will override other border settings!")]
|
||||
[Order(30)]
|
||||
public bool ChangeBorders = false;
|
||||
|
||||
[DragInt("Border Thickness", min = 1, max = 10)]
|
||||
[Order(31, collapseWith = nameof(ChangeBorders))]
|
||||
public int BorderThickness = 1;
|
||||
|
||||
[ColorEdit4("Speaking Border Color")]
|
||||
[Order(32, collapseWith = nameof(ChangeBorders))]
|
||||
public PluginConfigColor SpeakingBorderColor = PluginConfigColor.FromHex(0xFF40BB40);
|
||||
|
||||
[ColorEdit4("Muted Border Color")]
|
||||
[Order(33, collapseWith = nameof(ChangeBorders))]
|
||||
public PluginConfigColor MutedBorderColor = PluginConfigColor.FromHex(0xFF008080);
|
||||
|
||||
[ColorEdit4("Deafened Border Color")]
|
||||
[Order(34, collapseWith = nameof(ChangeBorders))]
|
||||
public PluginConfigColor DeafenedBorderColor = PluginConfigColor.FromHex(0xFFFF4444);
|
||||
|
||||
public bool EnabledForState(WhosTalkingState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case WhosTalkingState.Speaking: return ShowSpeaking;
|
||||
case WhosTalkingState.Muted: return ShowMuted;
|
||||
case WhosTalkingState.Deafened: return ShowDeafened;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public PluginConfigColor? ColorForState(WhosTalkingState state)
|
||||
{
|
||||
if (state == WhosTalkingState.Speaking && ShowSpeaking) { return SpeakingBorderColor; }
|
||||
if (state == WhosTalkingState.Muted && ShowMuted) { return MutedBorderColor; }
|
||||
if (ShowDeafened) { return DeafenedBorderColor; }
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
[Section("Party Frames", true)]
|
||||
[SubSection("Buffs", 0)]
|
||||
public class PartyFramesBuffsConfig : PartyFramesStatusEffectsListConfig
|
||||
{
|
||||
public new static PartyFramesBuffsConfig DefaultConfig()
|
||||
{
|
||||
var durationConfig = new LabelConfig(new Vector2(0, -4), "", DrawAnchor.Bottom, DrawAnchor.Center);
|
||||
var stacksConfig = new LabelConfig(new Vector2(-3, 4), "", DrawAnchor.TopRight, DrawAnchor.Center);
|
||||
stacksConfig.Color = new(Vector4.UnitW);
|
||||
stacksConfig.OutlineColor = new(Vector4.One);
|
||||
|
||||
var iconConfig = new StatusEffectIconConfig(durationConfig, stacksConfig);
|
||||
iconConfig.DispellableBorderConfig.Enabled = false;
|
||||
iconConfig.Size = new Vector2(24, 24);
|
||||
|
||||
var pos = new Vector2(-2, 2);
|
||||
var size = new Vector2(iconConfig.Size.X * 4 + 6, iconConfig.Size.Y);
|
||||
|
||||
var config = new PartyFramesBuffsConfig(DrawAnchor.TopRight, pos, size, true, false, false, GrowthDirections.Left | GrowthDirections.Down, iconConfig);
|
||||
config.Limit = 4;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public PartyFramesBuffsConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(anchor, position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
[Section("Party Frames", true)]
|
||||
[SubSection("Debuffs", 0)]
|
||||
public class PartyFramesDebuffsConfig : PartyFramesStatusEffectsListConfig
|
||||
{
|
||||
public new static PartyFramesDebuffsConfig DefaultConfig()
|
||||
{
|
||||
var durationConfig = new LabelConfig(new Vector2(0, -4), "", DrawAnchor.Bottom, DrawAnchor.Center);
|
||||
var stacksConfig = new LabelConfig(new Vector2(-3, 4), "", DrawAnchor.TopRight, DrawAnchor.Center);
|
||||
stacksConfig.Color = new(Vector4.UnitW);
|
||||
stacksConfig.OutlineColor = new(Vector4.One);
|
||||
|
||||
var iconConfig = new StatusEffectIconConfig(durationConfig, stacksConfig);
|
||||
iconConfig.Size = new Vector2(24, 24);
|
||||
|
||||
var pos = new Vector2(-2, -2);
|
||||
var size = new Vector2(iconConfig.Size.X * 4 + 6, iconConfig.Size.Y);
|
||||
|
||||
var config = new PartyFramesDebuffsConfig(DrawAnchor.BottomRight, pos, size, false, true, false, GrowthDirections.Left | GrowthDirections.Up, iconConfig);
|
||||
config.Limit = 4;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public PartyFramesDebuffsConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(anchor, position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class PartyFramesStatusEffectsListConfig : StatusEffectsListConfig
|
||||
{
|
||||
[Anchor("Health Bar Anchor")]
|
||||
[Order(4)]
|
||||
public DrawAnchor HealthBarAnchor = DrawAnchor.BottomLeft;
|
||||
|
||||
public PartyFramesStatusEffectsListConfig(DrawAnchor anchor, Vector2 position, Vector2 size, bool showBuffs, bool showDebuffs, bool showPermanentEffects,
|
||||
GrowthDirections growthDirections, StatusEffectIconConfig iconConfig)
|
||||
: base(position, size, showBuffs, showDebuffs, showPermanentEffects, growthDirections, iconConfig)
|
||||
{
|
||||
HealthBarAnchor = anchor;
|
||||
}
|
||||
}
|
||||
|
||||
[Disableable(false)]
|
||||
[Exportable(false)]
|
||||
[Section("Party Frames", true)]
|
||||
[SubSection("Trackers", 0)]
|
||||
public class PartyFramesTrackersConfig : PluginConfigObject
|
||||
{
|
||||
public new static PartyFramesTrackersConfig DefaultConfig() { return new PartyFramesTrackersConfig(); }
|
||||
|
||||
[NestedConfig("Raise Tracker", 10, separator = false)]
|
||||
public PartyFramesRaiseTrackerConfig Raise = new PartyFramesRaiseTrackerConfig();
|
||||
|
||||
[NestedConfig("Invulnerabilities Tracker", 15)]
|
||||
public PartyFramesInvulnTrackerConfig Invuln = new PartyFramesInvulnTrackerConfig();
|
||||
|
||||
[NestedConfig("Cleanse Tracker", 15)]
|
||||
public PartyFramesCleanseTrackerConfig Cleanse = new PartyFramesCleanseTrackerConfig();
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class PartyFramesRaiseTrackerConfig : PluginConfigObject
|
||||
{
|
||||
public new static PartyFramesRaiseTrackerConfig DefaultConfig() { return new PartyFramesRaiseTrackerConfig(); }
|
||||
|
||||
[Checkbox("Hide Name When Raised")]
|
||||
[Order(10)]
|
||||
public bool HideNameWhenRaised = true;
|
||||
|
||||
[Checkbox("Keep Icon After Cast Finishes")]
|
||||
[Order(15)]
|
||||
public bool KeepIconAfterCastFinishes = true;
|
||||
|
||||
[Checkbox("Change Background Color When Raised", spacing = true)]
|
||||
[Order(20)]
|
||||
public bool ChangeBackgroundColorWhenRaised = true;
|
||||
|
||||
[ColorEdit4("Raise Background Color")]
|
||||
[Order(25, collapseWith = nameof(ChangeBackgroundColorWhenRaised))]
|
||||
public PluginConfigColor BackgroundColor = new(new Vector4(211f / 255f, 235f / 255f, 215f / 245f, 50f / 100f));
|
||||
|
||||
[Checkbox("Change Border Color When Raised", spacing = true)]
|
||||
[Order(30)]
|
||||
public bool ChangeBorderColorWhenRaised = true;
|
||||
|
||||
[ColorEdit4("Raise Border Color")]
|
||||
[Order(35, collapseWith = nameof(ChangeBorderColorWhenRaised))]
|
||||
public PluginConfigColor BorderColor = new(new Vector4(47f / 255f, 169f / 255f, 215f / 255f, 100f / 100f));
|
||||
|
||||
[NestedConfig("Icon", 50)]
|
||||
public IconWithLabelConfig Icon = new IconWithLabelConfig(
|
||||
new Vector2(0, 0),
|
||||
new Vector2(50, 50),
|
||||
DrawAnchor.Center,
|
||||
DrawAnchor.Center
|
||||
);
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
public class PartyFramesInvulnTrackerConfig : PluginConfigObject
|
||||
{
|
||||
public new static PartyFramesInvulnTrackerConfig DefaultConfig() { return new PartyFramesInvulnTrackerConfig(); }
|
||||
|
||||
[Checkbox("Hide Name When Invuln is Up")]
|
||||
[Order(10)]
|
||||
public bool HideNameWhenInvuln = true;
|
||||
|
||||
[Checkbox("Change Background Color When Invuln is Up", spacing = true)]
|
||||
[Order(15)]
|
||||
public bool ChangeBackgroundColorWhenInvuln = true;
|
||||
|
||||
[ColorEdit4("Invuln Background Color")]
|
||||
[Order(20, collapseWith = nameof(ChangeBackgroundColorWhenInvuln))]
|
||||
public PluginConfigColor BackgroundColor = new(new Vector4(211f / 255f, 235f / 255f, 215f / 245f, 50f / 100f));
|
||||
|
||||
[Checkbox("Walking Dead Custom Color")]
|
||||
[Order(25, collapseWith = nameof(ChangeBackgroundColorWhenInvuln))]
|
||||
public bool UseCustomWalkingDeadColor = true;
|
||||
|
||||
[ColorEdit4("Walking Dead Background Color")]
|
||||
[Order(30, collapseWith = nameof(UseCustomWalkingDeadColor))]
|
||||
public PluginConfigColor WalkingDeadBackgroundColor = new(new Vector4(158f / 255f, 158f / 255f, 158f / 255f, 50f / 100f));
|
||||
|
||||
[NestedConfig("Icon", 50)]
|
||||
public IconWithLabelConfig Icon = new IconWithLabelConfig(
|
||||
new Vector2(0, 0),
|
||||
new Vector2(50, 50),
|
||||
DrawAnchor.Center,
|
||||
DrawAnchor.Center
|
||||
);
|
||||
}
|
||||
|
||||
public class PartyFramesTrackerConfigConverter : PluginConfigObjectConverter
|
||||
{
|
||||
public PartyFramesTrackerConfigConverter()
|
||||
{
|
||||
SameTypeFieldConverter<Vector2> pos = new SameTypeFieldConverter<Vector2>("Icon.Position", Vector2.Zero);
|
||||
FieldConvertersMap.Add("Position", pos);
|
||||
|
||||
SameTypeFieldConverter<Vector2> size = new SameTypeFieldConverter<Vector2>("Icon.Size", new Vector2(50, 50));
|
||||
FieldConvertersMap.Add("IconSize", size);
|
||||
|
||||
SameTypeFieldConverter<DrawAnchor> anchor = new SameTypeFieldConverter<DrawAnchor>("Icon.Anchor", DrawAnchor.Center);
|
||||
FieldConvertersMap.Add("Anchor", anchor);
|
||||
|
||||
SameTypeFieldConverter<DrawAnchor> frameAnchor = new SameTypeFieldConverter<DrawAnchor>("Icon.FrameAnchor", DrawAnchor.Center);
|
||||
FieldConvertersMap.Add("HealthBarAnchor", frameAnchor);
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(PartyFramesRaiseTrackerConfig) ||
|
||||
objectType == typeof(PartyFramesInvulnTrackerConfig);
|
||||
}
|
||||
}
|
||||
|
||||
[DisableParentSettings("Position", "Strata")]
|
||||
[Exportable(false)]
|
||||
public class PartyFramesCleanseTrackerConfig : MovablePluginConfigObject
|
||||
{
|
||||
public new static PartyFramesCleanseTrackerConfig DefaultConfig() { return new PartyFramesCleanseTrackerConfig(); }
|
||||
|
||||
[Checkbox("Show only on jobs with cleanses", spacing = true)]
|
||||
[Order(10)]
|
||||
public bool CleanseJobsOnly = true;
|
||||
|
||||
[Checkbox("Change Health Bar Color ", spacing = true)]
|
||||
[Order(15)]
|
||||
public bool ChangeHealthBarCleanseColor = true;
|
||||
|
||||
[ColorEdit4("Health Bar Color")]
|
||||
[Order(20, collapseWith = nameof(ChangeHealthBarCleanseColor))]
|
||||
public PluginConfigColor HealthBarColor = new(new Vector4(255f / 255f, 0f / 255f, 104f / 255f, 100f / 100f));
|
||||
|
||||
[Checkbox("Change Border Color", spacing = true)]
|
||||
[Order(25)]
|
||||
public bool ChangeBorderCleanseColor = true;
|
||||
|
||||
[ColorEdit4("Border Color")]
|
||||
[Order(30, collapseWith = nameof(ChangeBorderCleanseColor))]
|
||||
public PluginConfigColor BorderColor = new(new Vector4(255f / 255f, 0f / 255f, 104f / 255f, 100f / 100f));
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
[DisableParentSettings("Anchor")]
|
||||
[Section("Party Frames", true)]
|
||||
[SubSection("Cooldowns", 0)]
|
||||
public class PartyFramesCooldownListConfig : AnchorablePluginConfigObject
|
||||
{
|
||||
public new static PartyFramesCooldownListConfig DefaultConfig()
|
||||
{
|
||||
PartyFramesCooldownListConfig config = new PartyFramesCooldownListConfig();
|
||||
config.Position = new Vector2(-2, 0);
|
||||
config.Size = new Vector2(40 * 8 + 6, 40);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
[Anchor("Health Bar Anchor")]
|
||||
[Order(3)]
|
||||
public DrawAnchor HealthBarAnchor = DrawAnchor.Left;
|
||||
|
||||
[Checkbox("Tooltips", spacing = true)]
|
||||
[Order(20)]
|
||||
public bool ShowTooltips = true;
|
||||
|
||||
[Checkbox("Preview", isMonitored = true)]
|
||||
[Order(21)]
|
||||
public bool Preview;
|
||||
|
||||
[DragInt2("Icon Size", min = 1, max = 4000, spacing = true)]
|
||||
[Order(30)]
|
||||
public Vector2 IconSize = new Vector2(40, 40);
|
||||
|
||||
[DragInt2("Icon Padding", min = 0, max = 500)]
|
||||
[Order(31)]
|
||||
public Vector2 IconPadding = new(4, 4);
|
||||
|
||||
[Checkbox("Fill Rows First")]
|
||||
[Order(32)]
|
||||
public bool FillRowsFirst = true;
|
||||
|
||||
[Combo("Icons Growth Direction",
|
||||
"Right and Down",
|
||||
"Right and Up",
|
||||
"Left and Down",
|
||||
"Left and Up",
|
||||
"Centered and Up",
|
||||
"Centered and Down",
|
||||
"Centered and Left",
|
||||
"Centered and Right"
|
||||
)]
|
||||
[Order(33)]
|
||||
public int Directions = 3; // left & up
|
||||
|
||||
[Checkbox("Show Border", spacing = true)]
|
||||
[Order(35)]
|
||||
public bool DrawBorder = true;
|
||||
|
||||
[ColorEdit4("Border Color")]
|
||||
[Order(36, collapseWith = nameof(DrawBorder))]
|
||||
public PluginConfigColor BorderColor = new PluginConfigColor(new Vector4(0f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||
|
||||
[DragInt("Border Thickness", min = 1, max = 10)]
|
||||
[Order(37, collapseWith = nameof(DrawBorder))]
|
||||
public int BorderThickness = 1;
|
||||
|
||||
[Checkbox("Change Icon Border When Active")]
|
||||
[Order(45, collapseWith = nameof(DrawBorder))]
|
||||
public bool ChangeIconBorderWhenActive = true;
|
||||
|
||||
[ColorEdit4("Icon Active Border Color")]
|
||||
[Order(46, collapseWith = nameof(ChangeIconBorderWhenActive))]
|
||||
public PluginConfigColor IconActiveBorderColor = new PluginConfigColor(new Vector4(255f / 255f, 200f / 255f, 35f / 255f, 100f / 100f));
|
||||
|
||||
[DragInt("Icon Active Border Thickness", min = 1, max = 10)]
|
||||
[Order(47, collapseWith = nameof(ChangeIconBorderWhenActive))]
|
||||
public int IconActiveBorderThickness = 3;
|
||||
|
||||
[Checkbox("Change Label Color When Active", spacing = true)]
|
||||
[Order(50)]
|
||||
public bool ChangeLabelsColorWhenActive = false;
|
||||
|
||||
[ColorEdit4("Label Active Color")]
|
||||
[Order(51, collapseWith = nameof(ChangeLabelsColorWhenActive))]
|
||||
public PluginConfigColor LabelsActiveColor = new PluginConfigColor(new Vector4(255f / 255f, 200f / 255f, 35f / 255f, 100f / 100f));
|
||||
|
||||
[NestedConfig("Time Label", 80)]
|
||||
public PartyCooldownTimeLabelConfig TimeLabel = new PartyCooldownTimeLabelConfig(new Vector2(0, 0), "", DrawAnchor.Center, DrawAnchor.Center) { NumberFormat = 1 };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin.Services;
|
||||
using HSUI.Config;
|
||||
using HSUI.Enums;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using HSUI.Interface.PartyCooldowns;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public class PartyFramesCooldownListHud : ParentAnchoredDraggableHudElement, IHudElementWithActor, IHudElementWithAnchorableParent, IHudElementWithPreview
|
||||
{
|
||||
private PartyFramesCooldownListConfig Config => (PartyFramesCooldownListConfig)_config;
|
||||
private PartyCooldownsDataConfig _dataConfig = null!;
|
||||
|
||||
private LabelHud _timeLabel;
|
||||
private bool _needsUpdate = true;
|
||||
private LayoutInfo _layoutInfo;
|
||||
|
||||
private List<PartyCooldown> _cooldowns = new List<PartyCooldown>();
|
||||
private List<PartyCooldown>? _fakeCooldowns = null;
|
||||
|
||||
public IGameObject? Actor { get; set; }
|
||||
|
||||
protected override bool AnchorToParent => true;
|
||||
protected override DrawAnchor ParentAnchor => Config is PartyFramesCooldownListConfig config ? config.HealthBarAnchor : DrawAnchor.Center;
|
||||
|
||||
public PartyFramesCooldownListHud(PartyFramesCooldownListConfig config, string? displayName = null) : base(config, displayName)
|
||||
{
|
||||
_timeLabel = new LabelHud(config.TimeLabel);
|
||||
|
||||
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||
PartyCooldownsManager.Instance.CooldownsChangedEvent += OnCooldownsChanged;
|
||||
_config.ValueChangeEvent += OnConfigPropertyChanged;
|
||||
|
||||
OnConfigReset(ConfigurationManager.Instance);
|
||||
}
|
||||
|
||||
private void OnConfigReset(ConfigurationManager sender)
|
||||
{
|
||||
if (_dataConfig != null)
|
||||
_dataConfig.CooldownsDataChangedEvent -= OnCooldownsDataChanged;
|
||||
_dataConfig = ConfigurationManager.Instance.GetConfigObject<PartyCooldownsDataConfig>();
|
||||
_dataConfig.CooldownsDataChangedEvent += OnCooldownsDataChanged;
|
||||
}
|
||||
|
||||
protected override void InternalDispose()
|
||||
{
|
||||
ConfigurationManager.Instance?.ResetEvent -= OnConfigReset;
|
||||
_config.ValueChangeEvent -= OnConfigPropertyChanged;
|
||||
_dataConfig?.CooldownsDataChangedEvent -= OnCooldownsDataChanged;
|
||||
PartyCooldownsManager.Instance?.CooldownsChangedEvent -= OnCooldownsChanged;
|
||||
}
|
||||
|
||||
private void OnConfigPropertyChanged(object? sender, OnChangeBaseArgs args)
|
||||
{
|
||||
if (args.PropertyName == "Preview")
|
||||
{
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void UpdatePreview()
|
||||
{
|
||||
if (!Config.Preview)
|
||||
{
|
||||
_fakeCooldowns = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var RNG = new Random((int)ImGui.GetTime());
|
||||
|
||||
_fakeCooldowns = new List<PartyCooldown>();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
int index = RNG.Next(0, _dataConfig.Cooldowns.Count);
|
||||
|
||||
PartyCooldown cooldown = new PartyCooldown(_dataConfig.Cooldowns[index], 0, 90, null);
|
||||
|
||||
int rng = RNG.Next(100);
|
||||
if (rng > 80)
|
||||
{
|
||||
cooldown.LastTimeUsed = ImGui.GetTime() - 30;
|
||||
}
|
||||
else if (rng > 50)
|
||||
{
|
||||
cooldown.LastTimeUsed = ImGui.GetTime() + 1;
|
||||
}
|
||||
|
||||
_fakeCooldowns.Add(cooldown);
|
||||
}
|
||||
}
|
||||
|
||||
public void StopPreview()
|
||||
{
|
||||
Config.Preview = false;
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
private void OnCooldownsDataChanged(PartyCooldownsDataConfig sender)
|
||||
{
|
||||
_needsUpdate = true;
|
||||
}
|
||||
|
||||
private void OnCooldownsChanged(PartyCooldownsManager sender)
|
||||
{
|
||||
_needsUpdate = true;
|
||||
}
|
||||
|
||||
private void UpdateCooldowns()
|
||||
{
|
||||
_cooldowns.Clear();
|
||||
|
||||
if (Actor == null || PartyCooldownsManager.Instance?.CooldownsMap == null) { return; }
|
||||
|
||||
if (PartyCooldownsManager.Instance.CooldownsMap.TryGetValue((uint)Actor.GameObjectId, out Dictionary<uint, PartyCooldown>? dict) && dict != null)
|
||||
{
|
||||
_cooldowns = dict.Values.Where(o => o.Data.IsEnabledForPartyFrames()).ToList();
|
||||
}
|
||||
|
||||
_cooldowns.Sort((a, b) =>
|
||||
{
|
||||
int aOrder = a.Data.Column * 1000 + a.Data.Priority;
|
||||
int bOrder = b.Data.Column * 1000 + b.Data.Priority;
|
||||
|
||||
return aOrder.CompareTo(bOrder);
|
||||
});
|
||||
|
||||
_needsUpdate = false;
|
||||
}
|
||||
|
||||
private void CalculateLayout(uint count)
|
||||
{
|
||||
if (count <= 0) { return; }
|
||||
|
||||
_layoutInfo = LayoutHelper.CalculateLayout(
|
||||
Config.Size,
|
||||
Config.IconSize,
|
||||
count,
|
||||
Config.IconPadding,
|
||||
LayoutHelper.GetFillsRowsFirst(Config.FillRowsFirst, LayoutHelper.GrowthDirectionsFromIndex(Config.Directions))
|
||||
);
|
||||
}
|
||||
|
||||
public override void DrawChildren(Vector2 origin)
|
||||
{
|
||||
if (!Config.Enabled) { return; }
|
||||
|
||||
if (_needsUpdate)
|
||||
{
|
||||
UpdateCooldowns();
|
||||
}
|
||||
|
||||
List<PartyCooldown> list = _fakeCooldowns != null ? _fakeCooldowns : _cooldowns;
|
||||
|
||||
if (list.Count == 0) { return; }
|
||||
|
||||
// area
|
||||
GrowthDirections growthDirections = LayoutHelper.GrowthDirectionsFromIndex(Config.Directions);
|
||||
Vector2 position = origin + GetAnchoredPosition(Config.Position, Config.Size, DrawAnchor.TopLeft);
|
||||
Vector2 areaPos = LayoutHelper.CalculateStartPosition(position, Config.Size, growthDirections);
|
||||
Vector2 margin = new Vector2(14, 10);
|
||||
|
||||
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
|
||||
|
||||
// calculate icon positions
|
||||
uint count = (uint)list.Count;
|
||||
CalculateLayout(count);
|
||||
var (iconPositions, minPos, maxPos) = LayoutHelper.CalculateIconPositions(
|
||||
growthDirections,
|
||||
count,
|
||||
position,
|
||||
Config.Size,
|
||||
Config.IconSize,
|
||||
Config.IconPadding,
|
||||
LayoutHelper.GetFillsRowsFirst(Config.FillRowsFirst, growthDirections),
|
||||
_layoutInfo
|
||||
);
|
||||
|
||||
// window
|
||||
// imgui clips the left and right borders inside windows for some reason
|
||||
// we make the window bigger so the actual drawable size is the expected one
|
||||
Vector2 windowPos = minPos - margin;
|
||||
Vector2 windowSize = maxPos - minPos;
|
||||
|
||||
AddDrawAction(Config.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(ID, windowPos, windowSize + margin * 2, false, (drawList) =>
|
||||
{
|
||||
// area
|
||||
if (Config.Preview)
|
||||
{
|
||||
drawList.AddRectFilled(areaPos, areaPos + Config.Size, 0x88000000);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Vector2 iconPos = iconPositions[i];
|
||||
PartyCooldown cooldown = list[i];
|
||||
|
||||
float cooldownTime = cooldown.CooldownTimeRemaining();
|
||||
float effectTime = cooldown.EffectTimeRemaining();
|
||||
|
||||
// icon
|
||||
bool recharging = effectTime == 0 && cooldownTime > 0;
|
||||
uint color = recharging ? 0xAAFFFFFF : 0xFFFFFFFF;
|
||||
bool shouldDrawCooldown = ClipRectsHelper.Instance.GetClipRectForArea(iconPos, Config.IconSize) == null;
|
||||
|
||||
DrawHelper.DrawIcon(cooldown.Data.IconId, iconPos, Config.IconSize, false, color, drawList);
|
||||
|
||||
if (shouldDrawCooldown && effectTime == 0 && cooldownTime > 0)
|
||||
{
|
||||
DrawHelper.DrawIconCooldown(iconPos, Config.IconSize, cooldownTime, cooldown.Data.CooldownDuration, drawList);
|
||||
}
|
||||
|
||||
// border
|
||||
if (Config.DrawBorder)
|
||||
{
|
||||
bool active = effectTime > 0 && Config.ChangeIconBorderWhenActive;
|
||||
uint iconBorderColor = active ? Config.IconActiveBorderColor.Base : Config.BorderColor.Base;
|
||||
int thickness = active ? Config.IconActiveBorderThickness : Config.BorderThickness;
|
||||
drawList.AddRect(iconPos, iconPos + Config.IconSize, iconBorderColor, 0, ImDrawFlags.None, thickness);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
PartyCooldown? hoveringCooldown = null;
|
||||
IGameObject? character = Actor;
|
||||
|
||||
// labels need to be drawn separated since they have their own window for clipping
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
Vector2 iconPos = iconPositions[i];
|
||||
PartyCooldown cooldown = list[i];
|
||||
|
||||
float cooldownTime = cooldown.CooldownTimeRemaining();
|
||||
float effectTime = cooldown.EffectTimeRemaining();
|
||||
|
||||
PluginConfigColor? labelColor = effectTime > 0 && Config.ChangeLabelsColorWhenActive ? Config.LabelsActiveColor : null;
|
||||
|
||||
// time
|
||||
AddDrawAction(Config.TimeLabel.StrataLevel, () =>
|
||||
{
|
||||
PluginConfigColor realColor = Config.TimeLabel.Color;
|
||||
Config.TimeLabel.Color = labelColor ?? realColor;
|
||||
Config.TimeLabel.SetText("");
|
||||
|
||||
if (effectTime > 0)
|
||||
{
|
||||
if (Config.TimeLabel.ShowEffectDuration)
|
||||
{
|
||||
Config.TimeLabel.SetValue(effectTime);
|
||||
}
|
||||
}
|
||||
else if (cooldownTime > 0)
|
||||
{
|
||||
if (Config.TimeLabel.ShowRemainingCooldown)
|
||||
{
|
||||
Config.TimeLabel.SetText(Utils.DurationToString(cooldownTime, Config.TimeLabel.NumberFormat));
|
||||
}
|
||||
}
|
||||
|
||||
_timeLabel.Draw(iconPos, Config.IconSize, character);
|
||||
Config.TimeLabel.Color = realColor;
|
||||
});
|
||||
|
||||
// tooltips / interaction
|
||||
if (ImGui.IsMouseHoveringRect(iconPos, iconPos + Config.IconSize))
|
||||
{
|
||||
hoveringCooldown = cooldown;
|
||||
}
|
||||
}
|
||||
|
||||
if (hoveringCooldown != null)
|
||||
{
|
||||
// tooltip
|
||||
if (Config.ShowTooltips)
|
||||
{
|
||||
TooltipsHelper.Instance.ShowTooltipOnCursor(
|
||||
hoveringCooldown.TooltipText(),
|
||||
hoveringCooldown.Data.Name,
|
||||
hoveringCooldown.Data.ActionId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using HSUI.Config;
|
||||
using HSUI.Enums;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public class PartyFramesHud : DraggableHudElement, IHudElementWithMouseOver, IHudElementWithPreview, IHudElementWithVisibilityConfig
|
||||
{
|
||||
private PartyFramesConfig Config => (PartyFramesConfig)_config;
|
||||
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
|
||||
private PartyFramesConfigs Configs;
|
||||
|
||||
private Vector2 _contentMargin = new Vector2(2, 2);
|
||||
private static readonly int MaxMemberCount = 9; // 8 players + chocobo
|
||||
|
||||
// layout
|
||||
private Vector2 _origin;
|
||||
private LayoutInfo _layoutInfo;
|
||||
private uint _memberCount = 0;
|
||||
private bool _layoutDirty = true;
|
||||
|
||||
private readonly List<PartyFramesBar> bars;
|
||||
private LabelHud _titleLabelHud;
|
||||
|
||||
private bool Locked => !ConfigurationManager.Instance.IsConfigWindowOpened;
|
||||
|
||||
|
||||
public PartyFramesHud(PartyFramesConfig config, string displayName) : base(config, displayName)
|
||||
{
|
||||
Configs = PartyFramesConfigs.GetConfigs();
|
||||
|
||||
config.ValueChangeEvent += OnLayoutPropertyChanged;
|
||||
Configs.HealthBar.ValueChangeEvent += OnLayoutPropertyChanged;
|
||||
Configs.HealthBar.ColorsConfig.ValueChangeEvent += OnLayoutPropertyChanged;
|
||||
|
||||
bars = new List<PartyFramesBar>(MaxMemberCount);
|
||||
for (int i = 0; i < bars.Capacity; i++)
|
||||
{
|
||||
PartyFramesBar bar = new PartyFramesBar("DelvUI_partyFramesBar" + i, Configs);
|
||||
bar.OpenContextMenuEvent += OnOpenContextMenu;
|
||||
|
||||
bars.Add(bar);
|
||||
}
|
||||
|
||||
_titleLabelHud = new LabelHud(config.ShowPartyTitleConfig);
|
||||
|
||||
PartyManager.Instance.MembersChangedEvent += OnMembersChanged;
|
||||
UpdateBars(Vector2.Zero);
|
||||
}
|
||||
|
||||
protected override void InternalDispose()
|
||||
{
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
try { bar.Dispose(); }
|
||||
catch (Exception ex) { Plugin.Logger.Error($"Error disposing PartyFramesBar: {ex.Message}"); }
|
||||
}
|
||||
bars.Clear();
|
||||
|
||||
_config.ValueChangeEvent -= OnLayoutPropertyChanged;
|
||||
Configs.HealthBar.ValueChangeEvent -= OnLayoutPropertyChanged;
|
||||
Configs.HealthBar.ColorsConfig.ValueChangeEvent -= OnLayoutPropertyChanged;
|
||||
PartyManager.Instance.MembersChangedEvent -= OnMembersChanged;
|
||||
}
|
||||
|
||||
private unsafe void OnOpenContextMenu(PartyFramesBar bar)
|
||||
{
|
||||
if (bar.Member == null || Plugin.ObjectTable.LocalPlayer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (PartyManager.Instance.PartyListAddon == null || PartyManager.Instance.HudAgent == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int addonId = PartyManager.Instance.PartyListAddon->AtkUnitBase.Id;
|
||||
AgentModule.Instance()->GetAgentHUD()->OpenContextMenuFromPartyAddon(addonId, bar.Member.Index);
|
||||
}
|
||||
|
||||
private void OnLayoutPropertyChanged(object sender, OnChangeBaseArgs args)
|
||||
{
|
||||
if (args.PropertyName == "Size" ||
|
||||
args.PropertyName == "FillRowsFirst" ||
|
||||
args.PropertyName == "BarsAnchor" ||
|
||||
args.PropertyName == "Padding" ||
|
||||
args.PropertyName == "Rows" ||
|
||||
args.PropertyName == "Columns")
|
||||
{
|
||||
_layoutDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMembersChanged(PartyManager sender)
|
||||
{
|
||||
UpdateBars(_origin);
|
||||
}
|
||||
|
||||
public void UpdateBars(Vector2 origin)
|
||||
{
|
||||
uint memberCount = PartyManager.Instance.MemberCount;
|
||||
uint row = 0;
|
||||
uint col = 0;
|
||||
|
||||
for (int i = 0; i < bars.Count; i++)
|
||||
{
|
||||
PartyFramesBar bar = bars[i];
|
||||
if (i >= memberCount)
|
||||
{
|
||||
bar.Visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// update bar
|
||||
IPartyFramesMember member = PartyManager.Instance.SortedGroupMembers.ElementAt(i);
|
||||
bar.Member = member;
|
||||
bar.Visible = true;
|
||||
|
||||
// anchor and position
|
||||
CalculateBarPosition(origin, Size, out var x, out var y);
|
||||
bar.Position = new Vector2(
|
||||
x + Configs.HealthBar.Size.X * col + (Configs.HealthBar.Padding.X - 1) * col,
|
||||
y + Configs.HealthBar.Size.Y * row + (Configs.HealthBar.Padding.Y - 1) * row
|
||||
);
|
||||
|
||||
// layout
|
||||
if (Config.FillRowsFirst)
|
||||
{
|
||||
col = col + 1;
|
||||
if (col >= _layoutInfo.TotalColCount)
|
||||
{
|
||||
col = 0;
|
||||
row = row + 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
row = row + 1;
|
||||
if (row >= _layoutInfo.TotalRowCount)
|
||||
{
|
||||
row = 0;
|
||||
col = col + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateBarPosition(Vector2 position, Vector2 spaceSize, out float x, out float y)
|
||||
{
|
||||
x = position.X;
|
||||
y = position.Y;
|
||||
|
||||
if (Config.BarsAnchor == DrawAnchor.Top ||
|
||||
Config.BarsAnchor == DrawAnchor.Center ||
|
||||
Config.BarsAnchor == DrawAnchor.Bottom)
|
||||
{
|
||||
x += (spaceSize.X - _layoutInfo.ContentSize.X) / 2f;
|
||||
}
|
||||
else if (Config.BarsAnchor == DrawAnchor.TopRight ||
|
||||
Config.BarsAnchor == DrawAnchor.Right ||
|
||||
Config.BarsAnchor == DrawAnchor.BottomRight)
|
||||
{
|
||||
x += spaceSize.X - _layoutInfo.ContentSize.X;
|
||||
}
|
||||
|
||||
if (Config.BarsAnchor == DrawAnchor.Left ||
|
||||
Config.BarsAnchor == DrawAnchor.Center ||
|
||||
Config.BarsAnchor == DrawAnchor.Right)
|
||||
{
|
||||
y += (spaceSize.Y - _layoutInfo.ContentSize.Y) / 2f;
|
||||
}
|
||||
else if (Config.BarsAnchor == DrawAnchor.BottomLeft ||
|
||||
Config.BarsAnchor == DrawAnchor.Bottom ||
|
||||
Config.BarsAnchor == DrawAnchor.BottomRight)
|
||||
{
|
||||
y += spaceSize.Y - _layoutInfo.ContentSize.Y;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBarsPosition(Vector2 delta)
|
||||
{
|
||||
foreach (PartyFramesBar bar in bars)
|
||||
{
|
||||
bar.Position = bar.Position + delta;
|
||||
}
|
||||
}
|
||||
|
||||
public void StopPreview()
|
||||
{
|
||||
Config.Preview = false;
|
||||
PartyManager.Instance?.UpdatePreview();
|
||||
|
||||
foreach (PartyFramesBar bar in bars)
|
||||
{
|
||||
bar.StopPreview();
|
||||
}
|
||||
}
|
||||
|
||||
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||
{
|
||||
return (new List<Vector2>() { Config.Position + Size / 2f }, new List<Vector2>() { Size });
|
||||
}
|
||||
|
||||
public void StopMouseover()
|
||||
{
|
||||
foreach (PartyFramesBar bar in bars)
|
||||
{
|
||||
bar.StopMouseover();
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 Size => new Vector2(
|
||||
Config.Columns * Configs.HealthBar.Size.X + (Config.Columns - 1) * Configs.HealthBar.Padding.X,
|
||||
Config.Rows * Configs.HealthBar.Size.Y + (Config.Rows - 1) * Configs.HealthBar.Padding.Y
|
||||
);
|
||||
|
||||
private void UpdateLayout(Vector2 origin)
|
||||
{
|
||||
Vector2 contentStartPos = origin + Config.Position;
|
||||
uint count = PartyManager.Instance.MemberCount;
|
||||
|
||||
if (_layoutDirty || _memberCount != count)
|
||||
{
|
||||
_layoutInfo = LayoutHelper.CalculateLayout(
|
||||
Size,
|
||||
Configs.HealthBar.Size,
|
||||
count,
|
||||
Configs.HealthBar.Padding,
|
||||
Config.FillRowsFirst
|
||||
);
|
||||
UpdateBars(contentStartPos);
|
||||
}
|
||||
else if (_origin != contentStartPos)
|
||||
{
|
||||
UpdateBarsPosition(contentStartPos - _origin);
|
||||
}
|
||||
|
||||
_layoutDirty = false;
|
||||
_origin = contentStartPos;
|
||||
_memberCount = count;
|
||||
}
|
||||
|
||||
public override void DrawChildren(Vector2 origin)
|
||||
{
|
||||
if (!_config.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// area bg
|
||||
if (Config.Preview)
|
||||
{
|
||||
AddDrawAction(StrataLevel.LOWEST, () =>
|
||||
{
|
||||
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
|
||||
Vector2 bgPos = origin + Config.Position - _contentMargin;
|
||||
Vector2 bgSize = Size + _contentMargin * 2;
|
||||
|
||||
drawList.AddRectFilled(bgPos, bgPos + bgSize, 0x66000000);
|
||||
drawList.AddRect(bgPos, bgPos + bgSize, 0x66FFFFFF);
|
||||
});
|
||||
}
|
||||
|
||||
uint count = PartyManager.Instance.MemberCount;
|
||||
if (count < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateLayout(origin);
|
||||
|
||||
// draw bars
|
||||
// check borders to determine the order in which the bars are drawn
|
||||
// which is necessary for grid-like party frames
|
||||
|
||||
IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
|
||||
int targetIndex = -1;
|
||||
int enmityLeaderIndex = -1;
|
||||
int enmitySecondIndex = -1;
|
||||
|
||||
List<int> raisedIndexes = new List<int>();
|
||||
List<int> cleanseIndexes = new List<int>();
|
||||
List<int> whosTalkingIndexes = new List<int>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
IPartyFramesMember? member = bars[i].Member;
|
||||
|
||||
if (member != null)
|
||||
{
|
||||
// whos talking
|
||||
if (Configs.Icons.WhosTalking.ChangeBorders && member.WhosTalkingState != WhosTalkingState.None)
|
||||
{
|
||||
whosTalkingIndexes.Add(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// target
|
||||
if (target != null && member.ObjectId == target.GameObjectId)
|
||||
{
|
||||
targetIndex = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
// cleanse
|
||||
bool cleanseCheck = true;
|
||||
if (Configs.Trackers.Cleanse.CleanseJobsOnly)
|
||||
{
|
||||
cleanseCheck = Utils.IsOnCleanseJob();
|
||||
}
|
||||
|
||||
if (Configs.Trackers.Cleanse.Enabled && Configs.Trackers.Cleanse.ChangeBorderCleanseColor && member.HasDispellableDebuff && cleanseCheck)
|
||||
{
|
||||
cleanseIndexes.Add(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// raise
|
||||
if (Configs.Trackers.Raise.Enabled && Configs.Trackers.Raise.ChangeBorderColorWhenRaised && member.RaiseTime.HasValue)
|
||||
{
|
||||
raisedIndexes.Add(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// enmity
|
||||
if (Configs.HealthBar.ColorsConfig.ShowEnmityBorderColors)
|
||||
{
|
||||
if (member.EnmityLevel == EnmityLevel.Leader)
|
||||
{
|
||||
enmityLeaderIndex = i;
|
||||
continue;
|
||||
}
|
||||
else if (Configs.HealthBar.ColorsConfig.ShowSecondEnmity && member.EnmityLevel == EnmityLevel.Second &&
|
||||
(count > 4 || !Configs.HealthBar.ColorsConfig.HideSecondEnmityInLightParties))
|
||||
{
|
||||
enmitySecondIndex = i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no special border
|
||||
AddDrawActions(bars[i].GetBarDrawActions(origin));
|
||||
}
|
||||
|
||||
// special colors for borders
|
||||
|
||||
// 2nd enmity
|
||||
if (enmitySecondIndex >= 0)
|
||||
{
|
||||
AddDrawActions(bars[enmitySecondIndex].GetBarDrawActions(origin, Configs.HealthBar.ColorsConfig.EnmitySecondBordercolor));
|
||||
}
|
||||
|
||||
// 1st enmity
|
||||
if (enmityLeaderIndex >= 0)
|
||||
{
|
||||
AddDrawActions(bars[enmityLeaderIndex].GetBarDrawActions(origin, Configs.HealthBar.ColorsConfig.EnmityLeaderBordercolor));
|
||||
}
|
||||
|
||||
// raise
|
||||
foreach (int index in raisedIndexes)
|
||||
{
|
||||
AddDrawActions(bars[index].GetBarDrawActions(origin, Configs.Trackers.Raise.BorderColor));
|
||||
}
|
||||
|
||||
// target
|
||||
if (targetIndex >= 0)
|
||||
{
|
||||
AddDrawActions(bars[targetIndex].GetBarDrawActions(origin, Configs.HealthBar.ColorsConfig.TargetBordercolor));
|
||||
}
|
||||
|
||||
// cleanseable debuff
|
||||
foreach (int index in cleanseIndexes)
|
||||
{
|
||||
AddDrawActions(bars[index].GetBarDrawActions(origin, Configs.Trackers.Cleanse.BorderColor));
|
||||
}
|
||||
|
||||
// whos talking
|
||||
foreach (int index in whosTalkingIndexes)
|
||||
{
|
||||
IPartyFramesMember? member = bars[index].Member;
|
||||
if (member != null)
|
||||
{
|
||||
AddDrawActions(bars[index].GetBarDrawActions(origin, Configs.Icons.WhosTalking.ColorForState(member.WhosTalkingState)));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddDrawActions(bars[index].GetBarDrawActions(origin));
|
||||
}
|
||||
}
|
||||
|
||||
// extra elements
|
||||
foreach (PartyFramesBar bar in bars)
|
||||
{
|
||||
AddDrawActions(bar.GetElementsDrawActions(origin));
|
||||
}
|
||||
|
||||
AddDrawAction(Config.ShowPartyTitleConfig.StrataLevel, () =>
|
||||
{
|
||||
Config.ShowPartyTitleConfig.SetText(PartyManager.Instance.PartyTitle);
|
||||
_titleLabelHud.Draw(origin + Config.Position);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#region utils
|
||||
public struct PartyFramesConfigs
|
||||
{
|
||||
public PartyFramesHealthBarsConfig HealthBar;
|
||||
public PartyFramesManaBarConfig ManaBar;
|
||||
public PartyFramesCastbarConfig CastBar;
|
||||
public PartyFramesIconsConfig Icons;
|
||||
public PartyFramesBuffsConfig Buffs;
|
||||
public PartyFramesDebuffsConfig Debuffs;
|
||||
public PartyFramesTrackersConfig Trackers;
|
||||
public PartyFramesCooldownListConfig CooldownList;
|
||||
|
||||
public PartyFramesConfigs(
|
||||
PartyFramesHealthBarsConfig healthBar,
|
||||
PartyFramesManaBarConfig manaBar,
|
||||
PartyFramesCastbarConfig castBar,
|
||||
PartyFramesIconsConfig icons,
|
||||
PartyFramesBuffsConfig buffs,
|
||||
PartyFramesDebuffsConfig debuffs,
|
||||
PartyFramesTrackersConfig trackers,
|
||||
PartyFramesCooldownListConfig cooldownList)
|
||||
{
|
||||
HealthBar = healthBar;
|
||||
ManaBar = manaBar;
|
||||
CastBar = castBar;
|
||||
Icons = icons;
|
||||
Buffs = buffs;
|
||||
Debuffs = debuffs;
|
||||
Trackers = trackers;
|
||||
CooldownList = cooldownList;
|
||||
}
|
||||
|
||||
public static PartyFramesConfigs GetConfigs()
|
||||
{
|
||||
return new PartyFramesConfigs(
|
||||
ConfigurationManager.Instance.GetConfigObject<PartyFramesHealthBarsConfig>(),
|
||||
ConfigurationManager.Instance.GetConfigObject<PartyFramesManaBarConfig>(),
|
||||
ConfigurationManager.Instance.GetConfigObject<PartyFramesCastbarConfig>(),
|
||||
ConfigurationManager.Instance.GetConfigObject<PartyFramesIconsConfig>(),
|
||||
ConfigurationManager.Instance.GetConfigObject<PartyFramesBuffsConfig>(),
|
||||
ConfigurationManager.Instance.GetConfigObject<PartyFramesDebuffsConfig>(),
|
||||
ConfigurationManager.Instance.GetConfigObject<PartyFramesTrackersConfig>(),
|
||||
ConfigurationManager.Instance.GetConfigObject<PartyFramesCooldownListConfig>()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Statuses;
|
||||
using HSUI.Config;
|
||||
using HSUI.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public class InvulnStatus
|
||||
{
|
||||
public readonly uint InvulnIcon;
|
||||
public readonly float InvulnTime;
|
||||
public readonly uint InvulnId;
|
||||
|
||||
public InvulnStatus(uint invulnIcon, float invulnTime, uint invulnId)
|
||||
{
|
||||
InvulnIcon = invulnIcon;
|
||||
InvulnTime = invulnTime;
|
||||
InvulnId = invulnId;
|
||||
}
|
||||
}
|
||||
public class PartyFramesInvulnTracker : IDisposable
|
||||
{
|
||||
private PartyFramesInvulnTrackerConfig _config = null!;
|
||||
|
||||
public PartyFramesInvulnTracker()
|
||||
{
|
||||
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||
OnConfigReset(ConfigurationManager.Instance);
|
||||
}
|
||||
|
||||
~PartyFramesInvulnTracker()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
|
||||
}
|
||||
|
||||
public void OnConfigReset(ConfigurationManager sender)
|
||||
{
|
||||
_config = ConfigurationManager.Instance.GetConfigObject<PartyFramesTrackersConfig>().Invuln;
|
||||
}
|
||||
|
||||
public void Update(List<IPartyFramesMember> partyMembers)
|
||||
{
|
||||
if (!_config.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
foreach (var member in partyMembers)
|
||||
{
|
||||
|
||||
if (member.Character == null || member.ObjectId == 0)
|
||||
{
|
||||
member.InvulnStatus = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member.Character is not IBattleChara battleChara || member.HP <= 0)
|
||||
{
|
||||
member.InvulnStatus = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check invuln buff
|
||||
IStatus? tankInvuln = Utils.GetTankInvulnerabilityID(battleChara);
|
||||
if (tankInvuln == null)
|
||||
{
|
||||
member.InvulnStatus = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// apply invuln data based on buff
|
||||
|
||||
member.InvulnStatus = new InvulnStatus(InvulnMap[tankInvuln.StatusId], tankInvuln.RemainingTime, tankInvuln.StatusId);
|
||||
}
|
||||
}
|
||||
|
||||
#region invuln ids
|
||||
//these need to be mapped instead
|
||||
private static Dictionary<uint, uint> InvulnMap = new Dictionary<uint, uint>()
|
||||
{
|
||||
{ 810, 003077 }, // LIVING DEAD
|
||||
{ 3255, 003077}, // UNDEAD REBIRTH
|
||||
{ 811, 003077 }, // WALKING DEAD
|
||||
{ 1302, 002502 }, // HALLOWED GROUND
|
||||
{ 82, 002502 }, // HALLOWED GROUND
|
||||
{ 409, 000266 }, // HOLMGANG
|
||||
{ 1836, 003416 } // SUPERBOLIDE
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Party;
|
||||
using HSUI.Helpers;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public enum EnmityLevel : byte
|
||||
{
|
||||
Leader = 1,
|
||||
Second = 2,
|
||||
Last = 255
|
||||
}
|
||||
|
||||
public enum PartyMemberStatus : byte
|
||||
{
|
||||
None,
|
||||
ViewingCutscene,
|
||||
Offline,
|
||||
Dead
|
||||
}
|
||||
|
||||
public unsafe class PartyFramesMember : IPartyFramesMember
|
||||
{
|
||||
protected IPartyMember? _partyMember = null;
|
||||
private string _name = "";
|
||||
private uint _jobId = 0;
|
||||
private uint _objectID = 0;
|
||||
|
||||
public uint ObjectId => _partyMember != null ? _partyMember.EntityId : _objectID;
|
||||
public ICharacter? Character { get; private set; }
|
||||
public CrossRealmMember? CrossCharacter { get; private set; }
|
||||
|
||||
public int Index { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Name => _partyMember != null ? _partyMember.Name.ToString() : (Character != null ? Character.Name.ToString() : _name);
|
||||
public uint Level => _partyMember != null ? _partyMember.Level : (Character != null ? Character.Level : (uint)0);
|
||||
public uint JobId => _partyMember != null ? _partyMember.ClassJob.RowId : (Character != null ? Character.ClassJob.RowId : _jobId);
|
||||
public uint HP => _partyMember != null ? _partyMember.CurrentHP : (Character != null ? Character.CurrentHp : (uint)0);
|
||||
public uint MaxHP => _partyMember != null ? _partyMember.MaxHP : (Character != null ? Character.MaxHp : (uint)0);
|
||||
public uint MP => _partyMember != null ? _partyMember.CurrentMP : JobsHelper.CurrentPrimaryResource(Character);
|
||||
public uint MaxMP => _partyMember != null ? _partyMember.MaxMP : JobsHelper.MaxPrimaryResource(Character);
|
||||
public float Shield => Utils.ActorShieldValue(Character);
|
||||
public EnmityLevel EnmityLevel { get; private set; } = EnmityLevel.Last;
|
||||
public PartyMemberStatus Status { get; private set; } = PartyMemberStatus.None;
|
||||
public ReadyCheckStatus ReadyCheckStatus { get; private set; } = ReadyCheckStatus.None;
|
||||
public bool IsPartyLeader { get; private set; } = false;
|
||||
public bool IsChocobo { get; private set; } = false;
|
||||
public float? RaiseTime { get; set; }
|
||||
public InvulnStatus? InvulnStatus { get; set; }
|
||||
public bool HasDispellableDebuff { get; set; } = false;
|
||||
public WhosTalkingState WhosTalkingState => WhosTalkingHelper.Instance?.GetUserState(Name) ?? WhosTalkingState.None;
|
||||
|
||||
public PartyFramesMember(IPartyMember partyMember, int index, int order, EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, bool isChocobo = false)
|
||||
{
|
||||
_partyMember = partyMember;
|
||||
Index = index;
|
||||
Order = order;
|
||||
EnmityLevel = enmityLevel;
|
||||
Status = status;
|
||||
ReadyCheckStatus = readyCheckStatus;
|
||||
IsPartyLeader = isPartyLeader;
|
||||
IsChocobo = isChocobo;
|
||||
|
||||
var gameObject = partyMember.GameObject;
|
||||
if (gameObject is ICharacter character)
|
||||
{
|
||||
Character = character;
|
||||
}
|
||||
}
|
||||
|
||||
public PartyFramesMember(ICharacter character, int index, int order, EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, bool isChocobo = false)
|
||||
{
|
||||
Index = index;
|
||||
Order = order;
|
||||
EnmityLevel = enmityLevel;
|
||||
Status = status;
|
||||
ReadyCheckStatus = readyCheckStatus;
|
||||
IsPartyLeader = isPartyLeader;
|
||||
IsChocobo = isChocobo;
|
||||
|
||||
_objectID = (uint)character.GameObjectId;
|
||||
Character = character;
|
||||
}
|
||||
|
||||
public PartyFramesMember(uint objectId, int index, int order, EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, bool isChocobo = false)
|
||||
{
|
||||
Index = index;
|
||||
Order = order;
|
||||
EnmityLevel = enmityLevel;
|
||||
Status = status;
|
||||
ReadyCheckStatus = readyCheckStatus;
|
||||
IsPartyLeader = isPartyLeader;
|
||||
IsChocobo = isChocobo;
|
||||
|
||||
_objectID = objectId;
|
||||
var gameObject = Plugin.ObjectTable.SearchById(ObjectId);
|
||||
Character = gameObject is ICharacter ? (ICharacter)gameObject : null;
|
||||
}
|
||||
|
||||
public PartyFramesMember(CrossRealmMember member, int index, int order, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, bool isChocobo = false)
|
||||
{
|
||||
Index = index;
|
||||
Order = order;
|
||||
Status = status;
|
||||
ReadyCheckStatus = readyCheckStatus;
|
||||
IsPartyLeader = isPartyLeader;
|
||||
IsChocobo = isChocobo;
|
||||
|
||||
_objectID = (uint)member.EntityId;
|
||||
CrossCharacter = member;
|
||||
_name = member.NameString;
|
||||
_jobId = member.ClassJobId;
|
||||
}
|
||||
|
||||
|
||||
public void Update(EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, uint jobId = 0)
|
||||
{
|
||||
EnmityLevel = enmityLevel;
|
||||
Status = status;
|
||||
ReadyCheckStatus = readyCheckStatus;
|
||||
IsPartyLeader = isPartyLeader;
|
||||
|
||||
if (ObjectId == 0)
|
||||
{
|
||||
Character = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var gameObject = Plugin.ObjectTable.SearchById(ObjectId);
|
||||
Character = gameObject is ICharacter ? (ICharacter)gameObject : null;
|
||||
|
||||
if (jobId > 0)
|
||||
{
|
||||
_jobId = jobId;
|
||||
}
|
||||
else if (Character != null)
|
||||
{
|
||||
_jobId = Character.ClassJob.RowId;
|
||||
}
|
||||
|
||||
if (status == PartyMemberStatus.None && Character != null && MaxHP > 0 && HP <= 0)
|
||||
{
|
||||
Status = PartyMemberStatus.Dead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FakePartyFramesMember : IPartyFramesMember
|
||||
{
|
||||
public static readonly Random RNG = new Random((int)ImGui.GetTime());
|
||||
|
||||
public uint ObjectId => 0xE0000000;
|
||||
public ICharacter? Character => null;
|
||||
|
||||
public int Index { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Name { get; private set; }
|
||||
public uint Level { get; private set; }
|
||||
public uint JobId { get; private set; }
|
||||
public uint HP { get; private set; }
|
||||
public uint MaxHP { get; private set; }
|
||||
public uint MP { get; private set; }
|
||||
public uint MaxMP { get; private set; }
|
||||
public float Shield { get; private set; }
|
||||
public EnmityLevel EnmityLevel { get; private set; }
|
||||
public PartyMemberStatus Status { get; private set; }
|
||||
public ReadyCheckStatus ReadyCheckStatus { get; private set; }
|
||||
public bool IsPartyLeader { get; }
|
||||
public bool IsChocobo { get; }
|
||||
public float? RaiseTime { get; set; }
|
||||
public InvulnStatus? InvulnStatus { get; set; }
|
||||
public bool HasDispellableDebuff { get; set; }
|
||||
public WhosTalkingState WhosTalkingState { get; set; }
|
||||
|
||||
public FakePartyFramesMember(int order)
|
||||
{
|
||||
Name = RNG.Next(0, 2) == 1 ? "Fake Name" : "FakeLonger MockedName";
|
||||
Index = order;
|
||||
Order = order + 1;
|
||||
Level = (uint)RNG.Next(1, 80);
|
||||
JobId = (uint)RNG.Next(19, 41);
|
||||
MaxHP = (uint)RNG.Next(90000, 150000);
|
||||
HP = order == 2 || order == 3 ? 0 : (uint)(MaxHP * RNG.Next(50, 100) / 100f);
|
||||
MaxMP = 10000;
|
||||
MP = order == 2 || order == 3 ? 0 : (uint)(MaxMP * RNG.Next(100) / 100f);
|
||||
Shield = order == 2 || order == 3 ? 0 : RNG.Next(30) / 100f;
|
||||
EnmityLevel = order <= 1 ? (EnmityLevel)order + 1 : EnmityLevel.Last;
|
||||
Status = order < 3 ? PartyMemberStatus.None : (order == 3 ? PartyMemberStatus.Dead : (PartyMemberStatus)RNG.Next(0, 3));
|
||||
ReadyCheckStatus = (ReadyCheckStatus)RNG.Next(0, 3);
|
||||
IsPartyLeader = order == 0;
|
||||
IsChocobo = RNG.Next(0, 8) == 1;
|
||||
HasDispellableDebuff = RNG.Next(0, 2) == 1;
|
||||
RaiseTime = order == 2 ? RNG.Next(0, 60) : null;
|
||||
InvulnStatus = order == 0 ? new InvulnStatus(3077, RNG.Next(0, 10), 810) : null;
|
||||
WhosTalkingState = (WhosTalkingState)RNG.Next(0, 4);
|
||||
}
|
||||
|
||||
public void Update(EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, uint jobId = 0)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPartyFramesMember
|
||||
{
|
||||
public uint ObjectId { get; }
|
||||
public ICharacter? Character { get; }
|
||||
|
||||
public int Index { get; }
|
||||
public int Order { get; }
|
||||
public string Name { get; }
|
||||
public uint Level { get; }
|
||||
public uint JobId { get; }
|
||||
public uint HP { get; }
|
||||
public uint MaxHP { get; }
|
||||
public uint MP { get; }
|
||||
public uint MaxMP { get; }
|
||||
public float Shield { get; }
|
||||
public EnmityLevel EnmityLevel { get; }
|
||||
public PartyMemberStatus Status { get; }
|
||||
public ReadyCheckStatus ReadyCheckStatus { get; }
|
||||
public bool IsPartyLeader { get; }
|
||||
public bool IsChocobo { get; }
|
||||
public float? RaiseTime { get; set; }
|
||||
public InvulnStatus? InvulnStatus { get; set; }
|
||||
public bool HasDispellableDebuff { get; set; }
|
||||
|
||||
public WhosTalkingState WhosTalkingState { get; }
|
||||
|
||||
public void Update(EnmityLevel enmityLevel, PartyMemberStatus status, ReadyCheckStatus readyCheckStatus, bool isPartyLeader, uint jobId = 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using HSUI.Config;
|
||||
using HSUI.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public class PartyFramesRaiseTracker : IDisposable
|
||||
{
|
||||
private PartyFramesRaiseTrackerConfig _config = null!;
|
||||
|
||||
public PartyFramesRaiseTracker()
|
||||
{
|
||||
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||
OnConfigReset(ConfigurationManager.Instance);
|
||||
}
|
||||
|
||||
~PartyFramesRaiseTracker()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
|
||||
}
|
||||
|
||||
public void OnConfigReset(ConfigurationManager sender)
|
||||
{
|
||||
_config = sender.GetConfigObject<PartyFramesTrackersConfig>().Raise;
|
||||
}
|
||||
|
||||
public void Update(List<IPartyFramesMember> partyMembers)
|
||||
{
|
||||
if (!_config.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<uint, IPartyFramesMember> deadAndNotRaised = new Dictionary<uint, IPartyFramesMember>();
|
||||
float? limitBreakTime = null;
|
||||
Dictionary<uint, float> raiseTimeMap = new Dictionary<uint, float>();
|
||||
|
||||
foreach (var member in partyMembers)
|
||||
{
|
||||
if (member.Character == null || member.ObjectId == 0)
|
||||
{
|
||||
member.RaiseTime = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (member.HP > 0)
|
||||
{
|
||||
member.RaiseTime = null;
|
||||
}
|
||||
|
||||
if (member.Character is not IBattleChara battleChara)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// check raise casts
|
||||
if (Utils.IsActorCasting(battleChara))
|
||||
{
|
||||
var remaining = Math.Max(0, battleChara.TotalCastTime - battleChara.CurrentCastTime);
|
||||
|
||||
// check limit break
|
||||
if (IsRaiseLimitBreakAction(battleChara.CastActionId) &&
|
||||
(limitBreakTime.HasValue && limitBreakTime.Value > remaining))
|
||||
{
|
||||
limitBreakTime = remaining;
|
||||
}
|
||||
// check regular raise
|
||||
else if (IsRaiseAction(battleChara.CastActionId))
|
||||
{
|
||||
if (raiseTimeMap.TryGetValue((uint)battleChara.CastTargetObjectId, out float raiseTime))
|
||||
{
|
||||
if (raiseTime > remaining)
|
||||
{
|
||||
raiseTimeMap[(uint)battleChara.CastTargetObjectId] = remaining;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
raiseTimeMap.Add((uint)battleChara.CastTargetObjectId, remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check raise buff
|
||||
if (member.HP <= 0)
|
||||
{
|
||||
bool hasBuff = false;
|
||||
|
||||
var statusList = Utils.StatusListForBattleChara(battleChara);
|
||||
foreach (var status in statusList)
|
||||
{
|
||||
if (status == null || (status.StatusId != 148 && status.StatusId != 1140))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// apply raise data based on buff
|
||||
member.RaiseTime = status.RemainingTime;
|
||||
hasBuff = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hasBuff)
|
||||
{
|
||||
deadAndNotRaised.Add(member.ObjectId, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply raise data based on casts
|
||||
foreach (var memberId in deadAndNotRaised.Keys)
|
||||
{
|
||||
var member = deadAndNotRaised[memberId];
|
||||
|
||||
if (raiseTimeMap.TryGetValue(memberId, out float raiseTime))
|
||||
{
|
||||
if (limitBreakTime.HasValue && limitBreakTime.Value < raiseTime)
|
||||
{
|
||||
member.RaiseTime = limitBreakTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
member.RaiseTime = raiseTime;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
member.RaiseTime = limitBreakTime; // its fine if this is null here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region raise ids
|
||||
private static bool IsRaiseLimitBreakAction(uint actionId)
|
||||
{
|
||||
return LimitBreakIds.Contains(actionId);
|
||||
}
|
||||
|
||||
private static bool IsRaiseAction(uint actionId)
|
||||
{
|
||||
return RaiseIds.Contains(actionId);
|
||||
}
|
||||
|
||||
|
||||
private static List<uint> RaiseIds = new List<uint>()
|
||||
{
|
||||
173, // ACN, SMN, SCH
|
||||
125, // CNH, WHM
|
||||
3603, // AST
|
||||
18317, // BLU
|
||||
22345, // Lost Sacrifice, Bozja
|
||||
20730, // Lost Arise, Bozja
|
||||
12996, // Raise L, Eureka
|
||||
24287 // SGE
|
||||
};
|
||||
|
||||
private static List<uint> LimitBreakIds = new List<uint>()
|
||||
{
|
||||
208, // WHM
|
||||
4247, // SCH
|
||||
4248, // AST
|
||||
24859 // SGE
|
||||
};
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,805 @@
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Memory;
|
||||
using HSUI.Config;
|
||||
using HSUI.Helpers;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
using Lumina.Excel.Sheets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Arrays;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using static FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
|
||||
using DalamudPartyMember = Dalamud.Game.ClientState.Party.IPartyMember;
|
||||
using StructsPartyMember = FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public delegate void PartyMembersChangedEventHandler(PartyManager sender);
|
||||
|
||||
public unsafe class PartyManager : IDisposable
|
||||
{
|
||||
#region Singleton
|
||||
public static PartyManager Instance { get; private set; } = null!;
|
||||
private PartyFramesConfig _config = null!;
|
||||
private PartyFramesIconsConfig _iconsConfig = null!;
|
||||
|
||||
private PartyManager()
|
||||
{
|
||||
_readyCheckHelper = new PartyReadyCheckHelper();
|
||||
_raiseTracker = new PartyFramesRaiseTracker();
|
||||
_invulnTracker = new PartyFramesInvulnTracker();
|
||||
_cleanseTracker = new PartyFramesCleanseTracker();
|
||||
|
||||
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||
|
||||
OnConfigReset(ConfigurationManager.Instance);
|
||||
UpdatePreview();
|
||||
|
||||
// find offline string for active language
|
||||
if (Plugin.DataManager.GetExcelSheet<Addon>().TryGetRow(9836, out Addon row))
|
||||
{
|
||||
_offlineString = row.Text.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Instance = new PartyManager();
|
||||
}
|
||||
|
||||
~PartyManager()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_readyCheckHelper.Dispose();
|
||||
_raiseTracker.Dispose();
|
||||
_invulnTracker.Dispose();
|
||||
_cleanseTracker.Dispose();
|
||||
|
||||
_config.ValueChangeEvent -= OnConfigPropertyChanged;
|
||||
|
||||
Instance = null!;
|
||||
}
|
||||
|
||||
private void OnConfigReset(ConfigurationManager sender)
|
||||
{
|
||||
if (_config != null)
|
||||
{
|
||||
_config.ValueChangeEvent -= OnConfigPropertyChanged;
|
||||
}
|
||||
|
||||
_config = sender.GetConfigObject<PartyFramesConfig>();
|
||||
_config.ValueChangeEvent += OnConfigPropertyChanged;
|
||||
|
||||
_iconsConfig = ConfigurationManager.Instance.GetConfigObject<PartyFramesIconsConfig>();
|
||||
}
|
||||
|
||||
#endregion Singleton
|
||||
|
||||
public AddonPartyList* PartyListAddon { get; private set; } = null;
|
||||
public IntPtr HudAgent { get; private set; } = IntPtr.Zero;
|
||||
|
||||
private RaptureAtkModule* _raptureAtkModule = null;
|
||||
|
||||
private const int PartyListInfoOffset = 0x0D40;
|
||||
private const int PartyListMemberRawInfoSize = 0x28;
|
||||
|
||||
private const int PartyMembersInfoIndex = 12; // TODO: Should be reworked to use PartyMemberListStringArray.Instance()
|
||||
|
||||
private List<IPartyFramesMember> _groupMembers = new List<IPartyFramesMember>();
|
||||
public IReadOnlyCollection<IPartyFramesMember> GroupMembers => _groupMembers.AsReadOnly();
|
||||
|
||||
private List<IPartyFramesMember> _sortedGroupMembers = new List<IPartyFramesMember>();
|
||||
public IReadOnlyCollection<IPartyFramesMember> SortedGroupMembers => _sortedGroupMembers.AsReadOnly();
|
||||
|
||||
public uint MemberCount => (uint)_groupMembers.Count;
|
||||
|
||||
private string? _partyTitle = null;
|
||||
public string PartyTitle => _partyTitle ?? "";
|
||||
|
||||
private int _groupMemberCount => GroupManager.Instance()->MainGroup.MemberCount;
|
||||
private int _realMemberCount => PartyListAddon != null ? PartyListAddon->MemberCount : Plugin.PartyList.Length;
|
||||
private int _realMemberAndChocoboCount => PartyListAddon != null ? PartyListAddon->MemberCount + Math.Max(1, (int)PartyListAddon->ChocoboCount) : Plugin.PartyList.Length;
|
||||
|
||||
private Dictionary<string, InternalMemberData> _prevDataMap = new();
|
||||
|
||||
private bool _wasRealGroup = false;
|
||||
private bool _wasCrossWorld = false;
|
||||
|
||||
private InfoProxyCrossRealm* _crossRealmInfo => InfoProxyCrossRealm.Instance();
|
||||
private Group _mainGroup => GroupManager.Instance()->MainGroup;
|
||||
|
||||
private string _offlineString = "offline";
|
||||
|
||||
private PartyReadyCheckHelper _readyCheckHelper;
|
||||
private PartyFramesRaiseTracker _raiseTracker;
|
||||
private PartyFramesInvulnTracker _invulnTracker;
|
||||
private PartyFramesCleanseTracker _cleanseTracker;
|
||||
|
||||
public event PartyMembersChangedEventHandler? MembersChangedEvent;
|
||||
|
||||
public bool Previewing => _config.Preview;
|
||||
|
||||
public bool IsSoloParty()
|
||||
{
|
||||
if (!_config.ShowWhenSolo) { return false; }
|
||||
|
||||
return _groupMembers.Count <= 1 ||
|
||||
(_groupMembers.Count == 2 && _config.ShowChocobo &&
|
||||
_groupMembers[1].Character is IBattleNpc npc && npc.BattleNpcKind == BattleNpcSubKind.Chocobo);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// find party list hud agent
|
||||
PartyListAddon = (AddonPartyList*)Plugin.GameGui.GetAddonByName("_PartyList", 1).Address;
|
||||
HudAgent = Plugin.GameGui.FindAgentInterface(PartyListAddon);
|
||||
|
||||
if (PartyListAddon == null || HudAgent == IntPtr.Zero)
|
||||
{
|
||||
if (_groupMembers.Count > 0)
|
||||
{
|
||||
_groupMembers.Clear();
|
||||
MembersChangedEvent?.Invoke(this);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_raptureAtkModule = RaptureAtkModule.Instance();
|
||||
|
||||
// no need to update on preview mode
|
||||
if (_config.Preview)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InternalUpdate();
|
||||
}
|
||||
|
||||
private void InternalUpdate()
|
||||
{
|
||||
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
|
||||
if (player is null || player is not IPlayerCharacter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isCrossWorld = IsCrossWorldParty();
|
||||
|
||||
// ready check update
|
||||
if (_iconsConfig.ReadyCheckStatus.Enabled)
|
||||
{
|
||||
_readyCheckHelper.Update(_iconsConfig.ReadyCheckStatus.Duration);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// title
|
||||
_partyTitle = GetPartyListTitle();
|
||||
|
||||
// solo
|
||||
if (_realMemberCount <= 1 && PartyListAddon->TrustCount == 0)
|
||||
{
|
||||
if (_config.ShowWhenSolo)
|
||||
{
|
||||
UpdateSoloParty(player);
|
||||
}
|
||||
else if (_groupMembers.Count > 0)
|
||||
{
|
||||
_groupMembers.Clear();
|
||||
MembersChangedEvent?.Invoke(this);
|
||||
}
|
||||
|
||||
_wasRealGroup = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// player maps
|
||||
Dictionary<string, InternalMemberData> dataMap = GetMembersDataMap(player, isCrossWorld);
|
||||
bool partyChanged = _prevDataMap.Count != dataMap.Count;
|
||||
|
||||
if (!partyChanged)
|
||||
{
|
||||
foreach (string key in dataMap.Keys)
|
||||
{
|
||||
InternalMemberData newData = dataMap[key];
|
||||
if (!_prevDataMap.TryGetValue(key, out InternalMemberData? oldData) ||
|
||||
oldData == null ||
|
||||
newData.Order != oldData.Order)
|
||||
{
|
||||
partyChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_prevDataMap = dataMap;
|
||||
|
||||
// trust
|
||||
if (PartyListAddon->TrustCount > 0)
|
||||
{
|
||||
UpdateTrustParty(player, dataMap, partyChanged);
|
||||
}
|
||||
// cross world party/alliance
|
||||
else if (isCrossWorld)
|
||||
{
|
||||
UpdateCrossWorldParty(player, dataMap, partyChanged);
|
||||
}
|
||||
// regular party
|
||||
else
|
||||
{
|
||||
UpdateRegularParty(player, dataMap, partyChanged);
|
||||
}
|
||||
|
||||
_wasRealGroup = true;
|
||||
}
|
||||
|
||||
UpdateTrackers();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Warning(e.Message);
|
||||
}
|
||||
|
||||
_wasCrossWorld = isCrossWorld;
|
||||
}
|
||||
|
||||
private Dictionary<string, InternalMemberData> GetMembersDataMap(IPlayerCharacter player, bool isCrossWorld)
|
||||
{
|
||||
Dictionary<string, InternalMemberData> dataMap = new Dictionary<string, InternalMemberData>();
|
||||
|
||||
if (_raptureAtkModule == null || _raptureAtkModule->AtkModule.AtkArrayDataHolder.StringArrayCount <= PartyMembersInfoIndex)
|
||||
{
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
// raw info
|
||||
int allianceNum = FindAlliance(player);
|
||||
int count = isCrossWorld ? _crossRealmInfo->CrossRealmGroups[allianceNum].GroupMemberCount : _realMemberCount + PartyListAddon->TrustCount;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
InternalMemberData data = new InternalMemberData();
|
||||
data.Index = i;
|
||||
|
||||
if (!isCrossWorld)
|
||||
{
|
||||
PartyListMemberRawInfo* info = (PartyListMemberRawInfo*)(HudAgent + (PartyListInfoOffset + PartyListMemberRawInfoSize * i));
|
||||
data.ObjectId = info->ObjectId;
|
||||
data.ContentId = info->ContentId;
|
||||
data.Name = info->Name;
|
||||
data.Order = info->Order;
|
||||
}
|
||||
else
|
||||
{
|
||||
CrossRealmMember member = _crossRealmInfo->CrossRealmGroups[allianceNum].GroupMembers[i];
|
||||
data.ObjectId = member.EntityId;
|
||||
data.ContentId = (long)member.ContentId;
|
||||
data.Name = member.NameString;
|
||||
data.Order = i;
|
||||
}
|
||||
|
||||
if (!dataMap.ContainsKey(data.Name))
|
||||
{
|
||||
dataMap.Add(data.Name, data);
|
||||
}
|
||||
}
|
||||
|
||||
// status string
|
||||
var stringArrayData = _raptureAtkModule->AtkModule.AtkArrayDataHolder.StringArrays[PartyMembersInfoIndex];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int index = i * 5;
|
||||
if (stringArrayData->AtkArrayData.Size <= index + 3 ||
|
||||
stringArrayData->StringArray[index] == null ||
|
||||
stringArrayData->StringArray[index + 3] == null) { break; }
|
||||
|
||||
IntPtr ptr = new IntPtr(stringArrayData->StringArray[index]);
|
||||
string name = MemoryHelper.ReadSeStringNullTerminated(ptr).ToString();
|
||||
|
||||
ptr = new IntPtr(stringArrayData->StringArray[index + 3]);
|
||||
|
||||
string a = MemoryHelper.ReadSeStringNullTerminated(ptr).ToString();
|
||||
|
||||
|
||||
if (dataMap.TryGetValue(name, out InternalMemberData? data) && data != null)
|
||||
{
|
||||
data.Status = MemoryHelper.ReadSeStringNullTerminated(ptr).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
private bool IsCrossWorldParty()
|
||||
{
|
||||
return _crossRealmInfo->IsCrossRealm && _crossRealmInfo->GroupCount > 0 && _mainGroup.MemberCount == 0;
|
||||
}
|
||||
|
||||
private ReadyCheckStatus GetReadyCheckStatus(ulong contentId)
|
||||
{
|
||||
return _readyCheckHelper.GetStatusForContentId(contentId);
|
||||
}
|
||||
|
||||
private void UpdateTrustParty(IPlayerCharacter player, Dictionary<string, InternalMemberData> dataMap, bool forced)
|
||||
{
|
||||
bool softUpdate = true;
|
||||
|
||||
if (_groupMembers.Count != dataMap.Count || forced)
|
||||
{
|
||||
_groupMembers.Clear();
|
||||
softUpdate = false;
|
||||
}
|
||||
|
||||
if (softUpdate)
|
||||
{
|
||||
foreach (IPartyFramesMember member in _groupMembers)
|
||||
{
|
||||
if (member.ObjectId == player.GameObjectId)
|
||||
{
|
||||
member.Update(EnmityForIndex(member.Index), PartyMemberStatus.None, ReadyCheckStatus.None, true, player.ClassJob.RowId);
|
||||
}
|
||||
else
|
||||
{
|
||||
member.Update(EnmityForTrustMemberIndex(member.Index - 1), PartyMemberStatus.None, ReadyCheckStatus.None, false, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] keys = dataMap.Keys.ToArray();
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
InternalMemberData data = dataMap[keys[i]];
|
||||
if (keys[i] == player.Name.ToString())
|
||||
{
|
||||
PartyFramesMember playerMember = new PartyFramesMember(player, data.Index, data.Order, EnmityForIndex(i), PartyMemberStatus.None, ReadyCheckStatus.None, true);
|
||||
_groupMembers.Add(playerMember);
|
||||
}
|
||||
else
|
||||
{
|
||||
ICharacter? trustChara = Utils.GetGameObjectByName(keys[i]) as ICharacter;
|
||||
if (trustChara != null)
|
||||
{
|
||||
_groupMembers.Add(new PartyFramesMember(trustChara, data.Index, data.Order, EnmityForTrustMemberIndex(i), PartyMemberStatus.None, ReadyCheckStatus.None, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!softUpdate)
|
||||
{
|
||||
SortGroupMembers(player);
|
||||
MembersChangedEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSoloParty(IPlayerCharacter player)
|
||||
{
|
||||
ICharacter? chocobo = null;
|
||||
if (_config.ShowChocobo)
|
||||
{
|
||||
var gameObject = Utils.GetBattleChocobo(player);
|
||||
if (gameObject != null && gameObject is ICharacter)
|
||||
{
|
||||
chocobo = (ICharacter)gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
bool needsUpdate =
|
||||
_groupMembers.Count == 0 ||
|
||||
(_groupMembers.Count != 2 && _config.ShowChocobo && chocobo != null) ||
|
||||
(_groupMembers.Count > 1 && !_config.ShowChocobo) ||
|
||||
(_groupMembers.Count > 1 && chocobo == null) ||
|
||||
(_groupMembers.Count == 2 && _config.ShowChocobo && _groupMembers[1].ObjectId != chocobo?.EntityId);
|
||||
|
||||
EnmityLevel playerEnmity = PartyListAddon->EnmityLeaderIndex == 0 ? EnmityLevel.Leader : EnmityLevel.Last;
|
||||
|
||||
// for some reason chocobos never get a proper enmity value even though they have aggro
|
||||
// if the player enmity is set to first, but the "leader index" is invalid
|
||||
// we can pretty much deduce that the chocobo is the one with aggro
|
||||
// this might fail on some cases when there are other players not in party hitting the same thing
|
||||
// but the edge case is so minor we should be fine
|
||||
EnmityLevel chocoboEnmity = PartyListAddon->EnmityLeaderIndex == -1 && PartyListAddon->PartyMembers[0].EmnityByte == 1 ? EnmityLevel.Leader : EnmityLevel.Last;
|
||||
|
||||
if (needsUpdate)
|
||||
{
|
||||
_groupMembers.Clear();
|
||||
|
||||
_groupMembers.Add(new PartyFramesMember(player, 0, 0, playerEnmity, PartyMemberStatus.None, ReadyCheckStatus.None, true));
|
||||
|
||||
if (chocobo != null)
|
||||
{
|
||||
_groupMembers.Add(new PartyFramesMember(chocobo, 1, 1, chocoboEnmity, PartyMemberStatus.None, ReadyCheckStatus.None, false));
|
||||
}
|
||||
|
||||
SortGroupMembers(player);
|
||||
MembersChangedEvent?.Invoke(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < _groupMembers.Count; i++)
|
||||
{
|
||||
_groupMembers[i].Update(i == 0 ? playerEnmity : chocoboEnmity, PartyMemberStatus.None, ReadyCheckStatus.None, i == 0, i == 0 ? player.ClassJob.RowId : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCrossWorldParty(IPlayerCharacter player, Dictionary<string, InternalMemberData> dataMap, bool forced)
|
||||
{
|
||||
bool softUpdate = true;
|
||||
int allianceNum = FindAlliance(player);
|
||||
|
||||
int count = _crossRealmInfo->CrossRealmGroups[allianceNum].GroupMemberCount;
|
||||
if (!_wasCrossWorld || count != _groupMembers.Count || forced)
|
||||
{
|
||||
_groupMembers.Clear();
|
||||
softUpdate = false;
|
||||
}
|
||||
|
||||
// create new members array with cross world data
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
CrossRealmMember member = _crossRealmInfo->CrossRealmGroups[allianceNum].GroupMembers[i];
|
||||
string memberName = member.NameString;
|
||||
|
||||
if (!dataMap.TryGetValue(memberName, out InternalMemberData? data) || data == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isPlayer = member.EntityId == player.EntityId;
|
||||
bool isLeader = member.IsPartyLeader;
|
||||
PartyMemberStatus status = data.Status != null ? StatusForCrossWorldMember(data.Status) : PartyMemberStatus.None;
|
||||
ReadyCheckStatus readyCheckStatus = GetReadyCheckStatus(member.ContentId);
|
||||
|
||||
if (softUpdate)
|
||||
{
|
||||
IPartyFramesMember groupMember = _groupMembers.ElementAt(i);
|
||||
groupMember.Update(EnmityLevel.Last, status, readyCheckStatus, isLeader, member.ClassJobId);
|
||||
}
|
||||
else
|
||||
{
|
||||
PartyFramesMember partyMember = isPlayer ?
|
||||
new PartyFramesMember(player, i, data.Order, EnmityLevel.Last, status, readyCheckStatus, isLeader) :
|
||||
new PartyFramesMember(member, i, data.Order, status, readyCheckStatus, isLeader);
|
||||
_groupMembers.Add(partyMember);
|
||||
}
|
||||
}
|
||||
|
||||
if (!softUpdate)
|
||||
{
|
||||
SortGroupMembers(player);
|
||||
MembersChangedEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
private int FindAlliance(IPlayerCharacter player)
|
||||
{
|
||||
for (int i = 0; i < _crossRealmInfo->CrossRealmGroups.Length; i++)
|
||||
{
|
||||
for (int j = 0; j < _crossRealmInfo->CrossRealmGroups[i].GroupMemberCount; j++)
|
||||
{
|
||||
CrossRealmMember member = _crossRealmInfo->CrossRealmGroups[i].GroupMembers[j];
|
||||
if (member.EntityId == player.EntityId)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void UpdateRegularParty(IPlayerCharacter player, Dictionary<string, InternalMemberData> dataMap, bool forced)
|
||||
{
|
||||
bool softUpdate = true;
|
||||
|
||||
if (!_wasRealGroup || _realMemberCount != _groupMembers.Count || forced)
|
||||
{
|
||||
_groupMembers.Clear();
|
||||
softUpdate = false;
|
||||
}
|
||||
|
||||
string[] keys = dataMap.Keys.ToArray();
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
if (!dataMap.TryGetValue(keys[i], out InternalMemberData? data) || data == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isPlayer = data.ObjectId == player.GameObjectId;
|
||||
bool isLeader = IsPartyLeader(data.Order);
|
||||
EnmityLevel enmity = EnmityForIndex(data.Index);
|
||||
PartyMemberStatus status = data.Status != null ? StatusForMember(data.Status, data.Name) : PartyMemberStatus.None;
|
||||
ReadyCheckStatus readyCheckStatus = GetReadyCheckStatus((ulong)data.ContentId);
|
||||
|
||||
if (softUpdate)
|
||||
{
|
||||
IPartyFramesMember groupMember = _groupMembers.ElementAt(i);
|
||||
groupMember.Update(enmity, status, readyCheckStatus, isLeader);
|
||||
}
|
||||
else
|
||||
{
|
||||
PartyFramesMember partyMember;
|
||||
var member = GetDalamudPartyMember(data.Name);
|
||||
if (member.HasValue && member.Value.Item1 is DalamudPartyMember dalamudPartyMember)
|
||||
{
|
||||
partyMember = new PartyFramesMember(dalamudPartyMember, i, data.Order, enmity, status, readyCheckStatus, isLeader);
|
||||
}
|
||||
else
|
||||
{
|
||||
partyMember = new PartyFramesMember(data.ObjectId, i, data.Order, enmity, status, readyCheckStatus, isLeader);
|
||||
}
|
||||
_groupMembers.Add(partyMember);
|
||||
}
|
||||
}
|
||||
|
||||
// player's chocobo (always last)
|
||||
if (_config.ShowChocobo)
|
||||
{
|
||||
IGameObject? companion = Utils.GetBattleChocobo(player);
|
||||
|
||||
if (softUpdate && _groupMembers.FirstOrDefault(o => o.IsChocobo) is PartyFramesMember chocoboMember)
|
||||
{
|
||||
if (companion is ICharacter)
|
||||
{
|
||||
chocoboMember.Update(EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_groupMembers.Remove(chocoboMember);
|
||||
}
|
||||
}
|
||||
else if (companion is ICharacter companionCharacter)
|
||||
{
|
||||
_groupMembers.Add(new PartyFramesMember(companionCharacter, _groupMemberCount, 10, EnmityLevel.Last, PartyMemberStatus.None, ReadyCheckStatus.None, false, true));
|
||||
}
|
||||
}
|
||||
|
||||
if (!softUpdate)
|
||||
{
|
||||
SortGroupMembers(player);
|
||||
MembersChangedEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SortGroupMembers(IPlayerCharacter? player = null)
|
||||
{
|
||||
_sortedGroupMembers.Clear();
|
||||
_sortedGroupMembers.AddRange(_groupMembers);
|
||||
|
||||
_sortedGroupMembers.Sort((a, b) =>
|
||||
{
|
||||
if (a.Order == b.Order)
|
||||
{
|
||||
if (a.ObjectId == player?.GameObjectId)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (b.ObjectId == player?.GameObjectId)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return a.Name.CompareTo(b.Name);
|
||||
}
|
||||
|
||||
if (a.Order < b.Order)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
|
||||
private (DalamudPartyMember?, int)? GetDalamudPartyMember(string name)
|
||||
{
|
||||
for (int i = 0; i < Plugin.PartyList.Length; i++)
|
||||
{
|
||||
DalamudPartyMember? member = Plugin.PartyList[i];
|
||||
if (member != null && member.Name.ToString() == name)
|
||||
{
|
||||
return (member, i);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void UpdateTrackers()
|
||||
{
|
||||
_raiseTracker.Update(_groupMembers);
|
||||
_invulnTracker.Update(_groupMembers);
|
||||
_cleanseTracker.Update(_groupMembers);
|
||||
}
|
||||
|
||||
#region utils
|
||||
private bool IsPartyLeader(int index)
|
||||
{
|
||||
if (PartyListAddon == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// we use the icon Y coordinate in the party list to know the index (lmao)
|
||||
uint partyLeadIndex = (uint)PartyListAddon->LeaderMarkResNode->ChildNode->Y / 40;
|
||||
return index == partyLeadIndex;
|
||||
}
|
||||
|
||||
private PartyMemberStatus StatusForCrossWorldMember(string statusStr)
|
||||
{
|
||||
// offline status
|
||||
if (statusStr.Contains(_offlineString, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return PartyMemberStatus.Offline;
|
||||
}
|
||||
|
||||
return PartyMemberStatus.None;
|
||||
}
|
||||
|
||||
|
||||
private PartyMemberStatus StatusForMember(string statusStr, string name)
|
||||
{
|
||||
// offline status
|
||||
if (statusStr.Contains(_offlineString, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return PartyMemberStatus.Offline;
|
||||
}
|
||||
|
||||
// viewing cutscene status
|
||||
for (int i = 0; i < _mainGroup.MemberCount; i++)
|
||||
{
|
||||
if (_mainGroup.PartyMembers[i].NameString == name)
|
||||
{
|
||||
if ((_mainGroup.PartyMembers[i].Flags & 0x10) != 0)
|
||||
{
|
||||
return PartyMemberStatus.ViewingCutscene;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return PartyMemberStatus.None;
|
||||
}
|
||||
|
||||
private EnmityLevel EnmityForIndex(int index)
|
||||
{
|
||||
if (PartyListAddon == null || index < 0 || index > 7)
|
||||
{
|
||||
return EnmityLevel.Last;
|
||||
}
|
||||
|
||||
EnmityLevel enmityLevel = (EnmityLevel)PartyListAddon->PartyMembers[index].EmnityByte;
|
||||
if (enmityLevel == EnmityLevel.Leader && PartyListAddon->EnmityLeaderIndex != index)
|
||||
{
|
||||
enmityLevel = EnmityLevel.Last;
|
||||
}
|
||||
|
||||
return enmityLevel;
|
||||
}
|
||||
|
||||
private EnmityLevel EnmityForTrustMemberIndex(int index)
|
||||
{
|
||||
if (PartyListAddon == null || index < 0 || index > 6)
|
||||
{
|
||||
return EnmityLevel.Last;
|
||||
}
|
||||
|
||||
return (EnmityLevel)PartyListAddon->TrustMembers[index].EmnityByte;
|
||||
}
|
||||
|
||||
private static unsafe string? GetPartyListTitle()
|
||||
{
|
||||
AgentModule* agentModule = AgentModule.Instance();
|
||||
if (agentModule == null) { return ""; }
|
||||
|
||||
AgentHUD* agentHUD = agentModule->GetAgentHUD();
|
||||
if (agentHUD == null) { return ""; }
|
||||
|
||||
Lumina.Excel.ExcelSheet<Addon> sheet = Plugin.DataManager.GetExcelSheet<Addon>();
|
||||
if (sheet.TryGetRow(agentHUD->PartyTitleAddonId, out Addon row))
|
||||
{
|
||||
return row.Text.ToString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region events
|
||||
private void OnConfigPropertyChanged(object sender, OnChangeBaseArgs args)
|
||||
{
|
||||
if (args.PropertyName == "Preview")
|
||||
{
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePreview()
|
||||
{
|
||||
_iconsConfig.Sign.Preview = _config.Preview;
|
||||
|
||||
if (!_config.Preview)
|
||||
{
|
||||
_groupMembers.Clear();
|
||||
MembersChangedEvent?.Invoke(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// fill list with fake members for UI testing
|
||||
_groupMembers.Clear();
|
||||
|
||||
if (_config.Preview)
|
||||
{
|
||||
int count = FakePartyFramesMember.RNG.Next(4, 9);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
_groupMembers.Add(new FakePartyFramesMember(i));
|
||||
}
|
||||
}
|
||||
|
||||
SortGroupMembers();
|
||||
MembersChangedEvent?.Invoke(this);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class InternalMemberData
|
||||
{
|
||||
internal uint ObjectId = 0;
|
||||
internal long ContentId = 0;
|
||||
internal string Name = "";
|
||||
internal uint JobId = 0;
|
||||
internal int Order = 0;
|
||||
internal int Index = 0;
|
||||
internal string? Status = null;
|
||||
|
||||
public InternalMemberData()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 28)]
|
||||
public unsafe struct PartyListMemberRawInfo
|
||||
{
|
||||
[FieldOffset(0x00)] public byte* NamePtr;
|
||||
[FieldOffset(0x08)] public long ContentId;
|
||||
[FieldOffset(0x10)] public uint ObjectId;
|
||||
|
||||
// some kind of type
|
||||
// 1 = player
|
||||
// 2 = party member?
|
||||
// 3 = unknown
|
||||
// 4 = chocobo
|
||||
// 5 = summon?
|
||||
[FieldOffset(0x14)] public byte Type;
|
||||
|
||||
[FieldOffset(0x18)] public byte Order;
|
||||
|
||||
public string Name => Marshal.PtrToStringUTF8(new IntPtr(NamePtr)) ?? "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using HSUI.Helpers;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public static class PartyOrderHelper
|
||||
{
|
||||
private enum PartySortingSetting
|
||||
{
|
||||
Tank_Healer_DPS = 0,
|
||||
Tank_DPS_Healer = 1,
|
||||
Healer_Tank_DPS = 2,
|
||||
Healer_DPS_Tank = 3,
|
||||
DPS_Tank_Healer = 4,
|
||||
DPS_Healer_Tank = 5,
|
||||
Count = 6
|
||||
}
|
||||
|
||||
private class PartyRoles
|
||||
{
|
||||
internal int Tank;
|
||||
internal int Healer;
|
||||
internal int DPS;
|
||||
internal int Other;
|
||||
|
||||
public PartyRoles()
|
||||
{
|
||||
Tank = 0;
|
||||
Healer = 0;
|
||||
DPS = 0;
|
||||
Other = 0;
|
||||
}
|
||||
|
||||
public PartyRoles(int tank, int healer, int dps, int other)
|
||||
{
|
||||
Tank = tank;
|
||||
Healer = healer;
|
||||
DPS = dps;
|
||||
Other = other;
|
||||
}
|
||||
}
|
||||
|
||||
// calcualates the position for the player if they select the
|
||||
// option to always appear as the first of their current role
|
||||
// in the party frames
|
||||
public static int? GetRoleFirstOrder(List<IPartyFramesMember> members)
|
||||
{
|
||||
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
|
||||
if (player == null) { return null; }
|
||||
|
||||
JobRoles role = JobsHelper.RoleForJob(player.ClassJob.RowId);
|
||||
|
||||
PartySortingSetting? setting = GetPartySortingSetting(role);
|
||||
if (!setting.HasValue) { return null; }
|
||||
|
||||
PartyRoles rolesCount = GetPartyCountByRole(members);
|
||||
PartyRoles roleWeights = GetRoleWeights(role, setting.Value);
|
||||
|
||||
return rolesCount.Tank * roleWeights.Tank +
|
||||
rolesCount.Healer * roleWeights.Healer +
|
||||
rolesCount.DPS * roleWeights.DPS +
|
||||
rolesCount.Other * roleWeights.Other;
|
||||
}
|
||||
|
||||
private static unsafe PartySortingSetting? GetPartySortingSetting(JobRoles role)
|
||||
{
|
||||
ConfigModule* config = ConfigModule.Instance();
|
||||
if (config == null) { return null; }
|
||||
|
||||
ConfigOption option;
|
||||
switch (role)
|
||||
{
|
||||
case JobRoles.Tank: option = ConfigOption.PartyListSortTypeTank; break;
|
||||
|
||||
case JobRoles.Healer: option = ConfigOption.PartyListSortTypeHealer; break;
|
||||
|
||||
case JobRoles.DPSMelee:
|
||||
case JobRoles.DPSRanged:
|
||||
case JobRoles.DPSCaster: option = ConfigOption.PartyListSortTypeDps; break;
|
||||
|
||||
default: option = ConfigOption.PartyListSortTypeOther; break;
|
||||
}
|
||||
|
||||
Framework* framework = Framework.Instance();
|
||||
if (framework == null || framework->SystemConfig.SystemConfigBase.UiConfig.ConfigCount <= (int)option) {
|
||||
return PartySortingSetting.Tank_Healer_DPS;
|
||||
}
|
||||
|
||||
uint value = framework->SystemConfig.SystemConfigBase.UiConfig.ConfigEntry[(int)option].Value.UInt;
|
||||
if (value < 0 || value > (int)PartySortingSetting.Count) { return null; }
|
||||
|
||||
return (PartySortingSetting)value;
|
||||
}
|
||||
|
||||
private static unsafe PartyRoles GetPartyCountByRole(List<IPartyFramesMember> members)
|
||||
{
|
||||
PartyRoles rolesCount = new PartyRoles();
|
||||
|
||||
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
|
||||
if (player == null) { return rolesCount; }
|
||||
|
||||
foreach (IPartyFramesMember member in members)
|
||||
{
|
||||
if (member.ObjectId == player.GameObjectId) { continue; }
|
||||
|
||||
JobRoles role = JobsHelper.RoleForJob(member.JobId);
|
||||
switch (role)
|
||||
{
|
||||
case JobRoles.Tank: rolesCount.Tank++; break;
|
||||
|
||||
case JobRoles.Healer: rolesCount.Healer++; break;
|
||||
|
||||
case JobRoles.DPSMelee:
|
||||
case JobRoles.DPSRanged:
|
||||
case JobRoles.DPSCaster: rolesCount.DPS++; break;
|
||||
|
||||
default: rolesCount.Other++; break;
|
||||
}
|
||||
}
|
||||
|
||||
return rolesCount;
|
||||
}
|
||||
|
||||
private static unsafe PartyRoles GetRoleWeights(JobRoles role, PartySortingSetting setting)
|
||||
{
|
||||
if (role == JobRoles.Crafter || role == JobRoles.Gatherer || role == JobRoles.Unknown)
|
||||
{
|
||||
return new PartyRoles(1, 1, 1, 0);
|
||||
}
|
||||
|
||||
JobRoles mapRole = role == JobRoles.DPSRanged || role == JobRoles.DPSCaster ? JobRoles.DPSMelee : role;
|
||||
return RoleWeights[mapRole][setting];
|
||||
}
|
||||
|
||||
private static Dictionary<JobRoles, Dictionary<PartySortingSetting, PartyRoles>> RoleWeights = new Dictionary<JobRoles, Dictionary<PartySortingSetting, PartyRoles>>()
|
||||
{
|
||||
[JobRoles.Tank] = new Dictionary<PartySortingSetting, PartyRoles>()
|
||||
{
|
||||
[PartySortingSetting.Tank_Healer_DPS] = new PartyRoles(),
|
||||
[PartySortingSetting.Tank_DPS_Healer] = new PartyRoles(),
|
||||
[PartySortingSetting.Healer_Tank_DPS] = new PartyRoles(0, 1, 0, 0),
|
||||
[PartySortingSetting.Healer_DPS_Tank] = new PartyRoles(0, 1, 1, 0),
|
||||
[PartySortingSetting.DPS_Tank_Healer] = new PartyRoles(0, 0, 1, 0),
|
||||
[PartySortingSetting.DPS_Healer_Tank] = new PartyRoles(0, 1, 1, 0)
|
||||
},
|
||||
|
||||
[JobRoles.Healer] = new Dictionary<PartySortingSetting, PartyRoles>()
|
||||
{
|
||||
[PartySortingSetting.Tank_Healer_DPS] = new PartyRoles(1, 0, 0, 0),
|
||||
[PartySortingSetting.Tank_DPS_Healer] = new PartyRoles(1, 0, 1, 0),
|
||||
[PartySortingSetting.Healer_Tank_DPS] = new PartyRoles(),
|
||||
[PartySortingSetting.Healer_DPS_Tank] = new PartyRoles(),
|
||||
[PartySortingSetting.DPS_Tank_Healer] = new PartyRoles(1, 0, 1, 0),
|
||||
[PartySortingSetting.DPS_Healer_Tank] = new PartyRoles(0, 0, 1, 0)
|
||||
},
|
||||
|
||||
[JobRoles.DPSMelee] = new Dictionary<PartySortingSetting, PartyRoles>()
|
||||
{
|
||||
[PartySortingSetting.Tank_Healer_DPS] = new PartyRoles(1, 1, 0, 0),
|
||||
[PartySortingSetting.Tank_DPS_Healer] = new PartyRoles(1, 0, 0, 0),
|
||||
[PartySortingSetting.Healer_Tank_DPS] = new PartyRoles(1, 1, 0, 0),
|
||||
[PartySortingSetting.Healer_DPS_Tank] = new PartyRoles(0, 1, 0, 0),
|
||||
[PartySortingSetting.DPS_Tank_Healer] = new PartyRoles(),
|
||||
[PartySortingSetting.DPS_Healer_Tank] = new PartyRoles()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
|
||||
namespace HSUI.Interface.Party
|
||||
{
|
||||
public enum ReadyCheckStatus
|
||||
{
|
||||
Ready = 0,
|
||||
NotReady = 1,
|
||||
None = 2
|
||||
}
|
||||
|
||||
public class PartyReadyCheckHelper : IDisposable
|
||||
{
|
||||
private delegate void ReadyCheckDelegate(IntPtr ptr);
|
||||
private Hook<AgentReadyCheck.Delegates.InitiateReadyCheck>? _onReadyCheckStartHook;
|
||||
private Hook<AgentReadyCheck.Delegates.EndReadyCheck>? _onReadyCheckEndHook;
|
||||
|
||||
private delegate void ActorControlDelegate(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12);
|
||||
private Hook<ActorControlDelegate>? _actorControlHook;
|
||||
|
||||
private bool _readyCheckOngoing = false;
|
||||
private double _lastReadyCheckEndTime = -1;
|
||||
|
||||
|
||||
public unsafe PartyReadyCheckHelper()
|
||||
{
|
||||
try
|
||||
{
|
||||
_onReadyCheckStartHook = Plugin.GameInteropProvider.HookFromAddress<AgentReadyCheck.Delegates.InitiateReadyCheck>(
|
||||
AgentReadyCheck.MemberFunctionPointers.InitiateReadyCheck,
|
||||
OnReadyCheckStart
|
||||
);
|
||||
_onReadyCheckStartHook?.Enable();
|
||||
|
||||
_onReadyCheckEndHook = Plugin.GameInteropProvider.HookFromAddress<AgentReadyCheck.Delegates.EndReadyCheck>(
|
||||
AgentReadyCheck.MemberFunctionPointers.EndReadyCheck,
|
||||
OnReadycheckEnd
|
||||
);
|
||||
_onReadyCheckEndHook?.Enable();
|
||||
|
||||
_actorControlHook = Plugin.GameInteropProvider.HookFromSignature<ActorControlDelegate>(
|
||||
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
|
||||
OnActorControl
|
||||
);
|
||||
_actorControlHook?.Enable();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Logger.Error("Error initiating ready check sigs!!!\n" + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_onReadyCheckStartHook?.Disable();
|
||||
_onReadyCheckStartHook?.Dispose();
|
||||
|
||||
_onReadyCheckEndHook?.Disable();
|
||||
_onReadyCheckEndHook?.Dispose();
|
||||
|
||||
_actorControlHook?.Disable();
|
||||
_actorControlHook?.Dispose();
|
||||
}
|
||||
|
||||
private unsafe void OnReadyCheckStart(AgentReadyCheck *ptr)
|
||||
{
|
||||
_onReadyCheckStartHook?.Original(ptr);
|
||||
_readyCheckOngoing = true;
|
||||
_lastReadyCheckEndTime = -1;
|
||||
}
|
||||
|
||||
private unsafe void OnReadycheckEnd(AgentReadyCheck *ptr)
|
||||
{
|
||||
_onReadyCheckEndHook?.Original(ptr);
|
||||
_lastReadyCheckEndTime = ImGui.GetTime();
|
||||
}
|
||||
|
||||
private void OnActorControl(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12)
|
||||
{
|
||||
_actorControlHook?.Original(entityId, type, buffID, direct, actionId, sourceId, arg7, arg8, arg9, arg10, targetId, arg12);
|
||||
|
||||
// I'm not exactly sure what id == 503 means, but its always triggered when the fight starts
|
||||
// which is all I care about
|
||||
if (type == 503)
|
||||
{
|
||||
_readyCheckOngoing = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(double maxDuration)
|
||||
{
|
||||
if (_readyCheckOngoing &&
|
||||
_lastReadyCheckEndTime != -1 &&
|
||||
ImGui.GetTime() - _lastReadyCheckEndTime >= maxDuration)
|
||||
{
|
||||
_readyCheckOngoing = false;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe ReadyCheckStatus GetStatusForContentId(ulong contentId)
|
||||
{
|
||||
if (!_readyCheckOngoing)
|
||||
{
|
||||
return ReadyCheckStatus.None;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
ReadyCheckEntry entry = AgentReadyCheck.Instance()->ReadyCheckEntries[i];
|
||||
if (entry.ContentId == contentId)
|
||||
{
|
||||
return ParseStatus(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return ReadyCheckStatus.None;
|
||||
}
|
||||
|
||||
private ReadyCheckStatus ParseStatus(ReadyCheckEntry entry)
|
||||
{
|
||||
if (entry.Status == FFXIVClientStructs.FFXIV.Client.UI.Agent.ReadyCheckStatus.Ready)
|
||||
{
|
||||
return ReadyCheckStatus.Ready;
|
||||
}
|
||||
else if (entry.Status == FFXIVClientStructs.FFXIV.Client.UI.Agent.ReadyCheckStatus.NotReady ||
|
||||
entry.Status == FFXIVClientStructs.FFXIV.Client.UI.Agent.ReadyCheckStatus.MemberNotPresent)
|
||||
{
|
||||
return ReadyCheckStatus.NotReady;
|
||||
}
|
||||
|
||||
return ReadyCheckStatus.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user