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,220 @@
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.Party;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HSUI.Interface.PartyCooldowns
|
||||
{
|
||||
public enum PartyCooldownEnabled
|
||||
{
|
||||
PartyCooldownsAndPartyFrames = 0,
|
||||
PartyCooldowns = 1,
|
||||
PartyFrames = 2,
|
||||
Disabled = 3
|
||||
}
|
||||
|
||||
public class PartyCooldown
|
||||
{
|
||||
public readonly PartyCooldownData Data;
|
||||
public readonly uint SourceId;
|
||||
public readonly uint MemberLevel;
|
||||
public readonly IPartyFramesMember? Member;
|
||||
|
||||
public double LastTimeUsed = 0;
|
||||
public double OverridenCooldownStartTime = -1;
|
||||
public bool IgnoreNextUse = false;
|
||||
|
||||
public PartyCooldown(PartyCooldownData data, uint sourceID, uint level, IPartyFramesMember? member)
|
||||
{
|
||||
Data = data;
|
||||
SourceId = sourceID;
|
||||
MemberLevel = level;
|
||||
Member = member;
|
||||
}
|
||||
|
||||
public float EffectTimeRemaining()
|
||||
{
|
||||
int duration = GetEffectDuration();
|
||||
double timeSinceUse = ImGui.GetTime() - LastTimeUsed;
|
||||
if (IgnoreNextUse || timeSinceUse > duration)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return duration - (float)timeSinceUse;
|
||||
}
|
||||
|
||||
public float CooldownTimeRemaining()
|
||||
{
|
||||
float cooldown = GetCooldown();
|
||||
double timeSinceUse = OverridenCooldownStartTime != -1 ? ImGui.GetTime() - OverridenCooldownStartTime : ImGui.GetTime() - LastTimeUsed;
|
||||
|
||||
if (timeSinceUse > cooldown)
|
||||
{
|
||||
OverridenCooldownStartTime = -1;
|
||||
LastTimeUsed = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return cooldown - (float)timeSinceUse;
|
||||
}
|
||||
|
||||
public int GetCooldown()
|
||||
{
|
||||
// special case for troubadour, shield samba and tactician
|
||||
if (Data.ActionId == 7405 || Data.ActionId == 16012 || Data.ActionId == 16889)
|
||||
{
|
||||
return MemberLevel < 88 ? Data.CooldownDuration : 90;
|
||||
}
|
||||
// special case for swiftcast
|
||||
else if (Data.ActionId == 7561)
|
||||
{
|
||||
return MemberLevel < 94 ? Data.CooldownDuration : 40;
|
||||
}
|
||||
|
||||
return Data.CooldownDuration;
|
||||
}
|
||||
|
||||
public int GetEffectDuration()
|
||||
{
|
||||
// special case for reprisal, feint and addle
|
||||
if (MemberLevel < 98) { return Data.EffectDuration; }
|
||||
if (Data.ActionId != 7535 && Data.ActionId != 7549 && Data.ActionId != 7560) { return Data.EffectDuration; }
|
||||
|
||||
return 15;
|
||||
}
|
||||
|
||||
public string TooltipText()
|
||||
{
|
||||
int duration = GetEffectDuration();
|
||||
string effectDuration = duration > 0 ? $"Duration: {duration}s \n" : "";
|
||||
return $"{effectDuration}Recast Time: {GetCooldown()}s";
|
||||
}
|
||||
}
|
||||
|
||||
public class PartyCooldownData : IEquatable<PartyCooldownData>
|
||||
{
|
||||
public PartyCooldownEnabled EnabledV2 = PartyCooldownEnabled.PartyCooldownsAndPartyFrames;
|
||||
|
||||
public uint ActionId = 0;
|
||||
public uint RequiredLevel = 0;
|
||||
public uint DisabledAfterLevel = 0;
|
||||
|
||||
public uint JobId = 0; // keep this for backwards compatibility
|
||||
public List<uint>? JobIds = null;
|
||||
|
||||
public JobRoles Role = JobRoles.Unknown; // keep this for backwards compatibility
|
||||
public List<JobRoles>? Roles = null;
|
||||
|
||||
public HashSet<uint> ExcludedJobIds = new();
|
||||
|
||||
public int CooldownDuration = 0;
|
||||
public int EffectDuration = 0;
|
||||
|
||||
public int Priority = 0;
|
||||
public int Column = 1;
|
||||
|
||||
public List<uint> SharedActionIds = new();
|
||||
|
||||
[JsonIgnore] public uint IconId = 0;
|
||||
[JsonIgnore] public string Name = "";
|
||||
[JsonIgnore] public string? OverriddenCooldownText = null;
|
||||
[JsonIgnore] public string? OverriddenDurationText = null;
|
||||
|
||||
public virtual bool IsUsableBy(uint jobId)
|
||||
{
|
||||
JobRoles roleForJob = JobsHelper.RoleForJob(jobId);
|
||||
|
||||
if (Roles != null)
|
||||
{
|
||||
foreach (JobRoles role in Roles)
|
||||
{
|
||||
if (role == roleForJob)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Role != JobRoles.Unknown)
|
||||
{
|
||||
return Role == roleForJob;
|
||||
}
|
||||
|
||||
if (JobIds != null)
|
||||
{
|
||||
foreach (uint id in JobIds)
|
||||
{
|
||||
if (id == jobId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JobId == jobId;
|
||||
}
|
||||
|
||||
public bool HasRole(JobRoles role)
|
||||
{
|
||||
if (Roles != null)
|
||||
{
|
||||
return Roles.Contains(role);
|
||||
}
|
||||
|
||||
if (Role != JobRoles.Unknown)
|
||||
{
|
||||
return Role == role;
|
||||
}
|
||||
|
||||
if (JobIds != null)
|
||||
{
|
||||
foreach (uint jobId in JobIds)
|
||||
{
|
||||
JobRoles roleForJob = JobsHelper.RoleForJob(jobId);
|
||||
if (roleForJob == role)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return JobsHelper.RoleForJob(JobId) == role;
|
||||
}
|
||||
|
||||
public bool IsEnabledForPartyCooldowns()
|
||||
{
|
||||
return EnabledV2 == PartyCooldownEnabled.PartyCooldownsAndPartyFrames ||
|
||||
EnabledV2 == PartyCooldownEnabled.PartyCooldowns;
|
||||
}
|
||||
|
||||
public bool IsEnabledForPartyFrames()
|
||||
{
|
||||
return EnabledV2 == PartyCooldownEnabled.PartyCooldownsAndPartyFrames ||
|
||||
EnabledV2 == PartyCooldownEnabled.PartyFrames;
|
||||
}
|
||||
|
||||
public bool Equals(PartyCooldownData? other)
|
||||
{
|
||||
if (other == null) { return false; }
|
||||
|
||||
return
|
||||
ActionId == other.ActionId &&
|
||||
RequiredLevel == other.RequiredLevel &&
|
||||
DisabledAfterLevel == other.DisabledAfterLevel &&
|
||||
JobId == other.JobId &&
|
||||
(JobIds == null && other.JobIds == null || (JobIds != null && other.JobIds != null && JobIds.Equals(other.JobIds))) &&
|
||||
Role == other.Role &&
|
||||
(Roles == null && other.Roles == null || (Roles != null && other.Roles != null && Roles.Equals(other.Roles))) &&
|
||||
CooldownDuration == other.CooldownDuration &&
|
||||
EffectDuration == other.EffectDuration &&
|
||||
SharedActionIds == other.SharedActionIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,884 @@
|
||||
using HSUI.Config;
|
||||
using HSUI.Config.Attributes;
|
||||
using HSUI.Enums;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.Bars;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using LuminaAction = Lumina.Excel.Sheets.Action;
|
||||
|
||||
namespace HSUI.Interface.PartyCooldowns
|
||||
{
|
||||
public enum PartyCooldownsGrowthDirection
|
||||
{
|
||||
Down = 0,
|
||||
Up,
|
||||
Right,
|
||||
Left
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
[Section("Party Cooldowns", true)]
|
||||
[SubSection("General", 0)]
|
||||
public class PartyCooldownsConfig : MovablePluginConfigObject
|
||||
{
|
||||
public new static PartyCooldownsConfig DefaultConfig()
|
||||
{
|
||||
var config = new PartyCooldownsConfig();
|
||||
config.Position = new Vector2(-ImGui.GetMainViewport().Size.X / 2 + 100, -ImGui.GetMainViewport().Size.Y / 2 + 100);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
[Checkbox("Preview", isMonitored = true)]
|
||||
[Order(4)]
|
||||
public bool Preview = false;
|
||||
|
||||
[Combo("Sections Growth Direction", "Down", "Up", "Right", "Left", spacing = true)]
|
||||
[Order(20)]
|
||||
public PartyCooldownsGrowthDirection GrowthDirection = PartyCooldownsGrowthDirection.Down;
|
||||
|
||||
[DragInt2("Padding", min = -1000, max = 1000)]
|
||||
[Order(15)]
|
||||
public Vector2 Padding = new Vector2(0, -1);
|
||||
|
||||
[Checkbox("Tooltips", spacing = true)]
|
||||
[Order(16)]
|
||||
public bool ShowTooltips = true;
|
||||
|
||||
[Checkbox("Show Only in Duties", spacing = true, isMonitored = true)]
|
||||
[Order(20)]
|
||||
public bool ShowOnlyInDuties = true;
|
||||
|
||||
[Checkbox("Show When Solo", isMonitored = true)]
|
||||
[Order(21)]
|
||||
public bool ShowWhenSolo = false;
|
||||
|
||||
[NestedConfig("Visibility", 200)]
|
||||
public VisibilityConfig VisibilityConfig = new VisibilityConfig();
|
||||
}
|
||||
|
||||
[Disableable(false)]
|
||||
[Exportable(false)]
|
||||
[DisableParentSettings("Position", "Anchor", "HideWhenInactive", "FillColor", "Background", "FillDirection")]
|
||||
[Section("Party Cooldowns", true)]
|
||||
[SubSection("Cooldown Bar", 0)]
|
||||
public class PartyCooldownsBarConfig : BarConfig
|
||||
{
|
||||
[Checkbox("Show Bar", spacing = true)]
|
||||
[Order(70)]
|
||||
public bool ShowBar = true;
|
||||
|
||||
[ColorEdit4("Available Color")]
|
||||
[Order(71, collapseWith = nameof(ShowBar))]
|
||||
public PluginConfigColor AvailableColor = new PluginConfigColor(new Vector4(0f / 255f, 150f / 255f, 0f / 255f, 100f / 100f));
|
||||
|
||||
[ColorEdit4("Available Background Color")]
|
||||
[Order(72, collapseWith = nameof(ShowBar))]
|
||||
public PluginConfigColor AvailableBackgroundColor = new PluginConfigColor(new Vector4(0f / 255f, 150f / 255f, 0f / 255f, 25f / 100f));
|
||||
|
||||
[ColorEdit4("Recharging Color")]
|
||||
[Order(73, collapseWith = nameof(ShowBar))]
|
||||
public PluginConfigColor RechargingColor = new PluginConfigColor(new Vector4(150f / 255f, 0f / 255f, 0f / 255f, 100f / 100f));
|
||||
|
||||
[ColorEdit4("Recharging Background Color")]
|
||||
[Order(74, collapseWith = nameof(ShowBar))]
|
||||
public PluginConfigColor RechargingBackgroundColor = new PluginConfigColor(new Vector4(150f / 255f, 0f / 255f, 0f / 255f, 25f / 100f));
|
||||
|
||||
[Checkbox("Use Job Colors")]
|
||||
[Order(75, collapseWith = nameof(ShowBar))]
|
||||
public bool UseJobColors = false;
|
||||
|
||||
[Checkbox("Show Icon", spacing = true)]
|
||||
[Order(80)]
|
||||
public bool ShowIcon = true;
|
||||
|
||||
[Checkbox("Show Icon Cooldown Animation")]
|
||||
[Order(81, collapseWith = nameof(ShowIcon))]
|
||||
public bool ShowIconCooldownAnimation = false;
|
||||
|
||||
[Checkbox("Change Icon Border When Active")]
|
||||
[Order(82, collapseWith = nameof(ShowIcon))]
|
||||
public bool ChangeIconBorderWhenActive = false;
|
||||
|
||||
[ColorEdit4("Icon Active Border Color")]
|
||||
[Order(83, collapseWith = nameof(ChangeIconBorderWhenActive))]
|
||||
public PluginConfigColor IconActiveBorderColor = new PluginConfigColor(new Vector4(255f / 255f, 200f / 255f, 35f / 255f, 100f / 100f));
|
||||
|
||||
[DragInt("Icon Active Border Thickness", min = 1, max = 10)]
|
||||
[Order(84, collapseWith = nameof(ChangeIconBorderWhenActive))]
|
||||
public int IconActiveBorderThickness = 3;
|
||||
|
||||
[Checkbox("Change Labels Color When Active", spacing = true)]
|
||||
[Order(85)]
|
||||
public bool ChangeLabelsColorWhenActive = false;
|
||||
|
||||
[ColorEdit4("Labels Active Color")]
|
||||
[Order(86, collapseWith = nameof(ChangeLabelsColorWhenActive))]
|
||||
public PluginConfigColor LabelsActiveColor = new PluginConfigColor(new Vector4(255f / 255f, 200f / 255f, 35f / 255f, 100f / 100f));
|
||||
|
||||
[NestedConfig("Name Label", 100)]
|
||||
public EditableLabelConfig NameLabel = new EditableLabelConfig(new Vector2(5, 0), "[name:initials]", DrawAnchor.Left, DrawAnchor.Left);
|
||||
|
||||
[NestedConfig("Time Label", 105)]
|
||||
public PartyCooldownTimeLabelConfig TimeLabel = new PartyCooldownTimeLabelConfig(new Vector2(-5, 0), "", DrawAnchor.Right, DrawAnchor.Right);
|
||||
|
||||
public new static PartyCooldownsBarConfig DefaultConfig()
|
||||
{
|
||||
Vector2 size = new Vector2(150, 24);
|
||||
|
||||
PartyCooldownsBarConfig config = new PartyCooldownsBarConfig(Vector2.Zero, size, PluginConfigColor.Empty);
|
||||
|
||||
config.NameLabel.FontID = FontsConfig.DefaultMediumFontKey;
|
||||
config.TimeLabel.FontID = FontsConfig.DefaultMediumFontKey;
|
||||
config.TimeLabel.NumberFormat = 1;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public PartyCooldownsBarConfig(Vector2 position, Vector2 size, PluginConfigColor fillColor, BarDirection fillDirection = BarDirection.Right)
|
||||
: base(position, size, fillColor, fillDirection)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class PartyCooldownTimeLabelConfig : NumericLabelConfig
|
||||
{
|
||||
public PartyCooldownTimeLabelConfig(Vector2 position, string text, DrawAnchor frameAnchor, DrawAnchor textAnchor)
|
||||
: base(position, text, frameAnchor, textAnchor)
|
||||
{
|
||||
}
|
||||
|
||||
[Checkbox("Show Effect Duration", spacing = true)]
|
||||
[Order(70)]
|
||||
public bool ShowEffectDuration = true;
|
||||
|
||||
[Checkbox("Show Remainin Cooldown")]
|
||||
[Order(71)]
|
||||
public bool ShowRemainingCooldown = true;
|
||||
}
|
||||
|
||||
[Exportable(false)]
|
||||
[Disableable(false)]
|
||||
[Section("Party Cooldowns", true)]
|
||||
[SubSection("Cooldowns Tracked", 0)]
|
||||
public class PartyCooldownsDataConfig : PluginConfigObject
|
||||
{
|
||||
public List<PartyCooldownData> Cooldowns = new List<PartyCooldownData>();
|
||||
|
||||
private List<uint> _removedIds = new() { 7398 };
|
||||
|
||||
private JobRoles _roleFilter = JobRoles.Unknown;
|
||||
private uint _tankFilter = 0;
|
||||
private uint _healerFilter = 0;
|
||||
private uint _meleeFilter = 0;
|
||||
private uint _rangedFilter = 0;
|
||||
private uint _casterFilter = 0;
|
||||
|
||||
private bool _needsPopupOpen = false;
|
||||
private PartyCooldownData? _popupCooldown = null;
|
||||
|
||||
public const int ColumnCount = 5;
|
||||
|
||||
public delegate void CooldownsDataChangedEventHandler(PartyCooldownsDataConfig sender);
|
||||
public event CooldownsDataChangedEventHandler? CooldownsDataChangedEvent;
|
||||
|
||||
public delegate void CooldownsDataEnabledChangedEventHandler(PartyCooldownsDataConfig sender);
|
||||
public event CooldownsDataEnabledChangedEventHandler? CooldownsDataEnabledChangedEvent;
|
||||
|
||||
public new static PartyCooldownsDataConfig DefaultConfig() => new PartyCooldownsDataConfig();
|
||||
|
||||
private string[] _enabledOptions = new string[] {
|
||||
"Enabled",
|
||||
"Party Cooldowns Only",
|
||||
"Party Frames Only",
|
||||
"Disabled"
|
||||
};
|
||||
|
||||
|
||||
public void UpdateDataIfNeeded()
|
||||
{
|
||||
bool needsSave = false;
|
||||
|
||||
// remove old cooldowns that are not valid anymore
|
||||
foreach (uint id in _removedIds)
|
||||
{
|
||||
PartyCooldownData? data = Cooldowns.FirstOrDefault(data => data.ActionId == id);
|
||||
if (data != null)
|
||||
{
|
||||
Cooldowns.Remove(data);
|
||||
}
|
||||
}
|
||||
|
||||
// update data using the game files
|
||||
foreach (uint key in DefaultCooldowns.Keys)
|
||||
{
|
||||
PartyCooldownData? data = Cooldowns.FirstOrDefault(data => data.ActionId == key);
|
||||
PartyCooldownData defaultData = DefaultCooldowns[key];
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
Cooldowns.Add(defaultData);
|
||||
needsSave = true;
|
||||
}
|
||||
else if (data != null && !data.Equals(defaultData))
|
||||
{
|
||||
data.RequiredLevel = defaultData.RequiredLevel;
|
||||
data.DisabledAfterLevel = defaultData.DisabledAfterLevel;
|
||||
data.JobId = defaultData.JobId;
|
||||
data.JobIds = defaultData.JobIds;
|
||||
data.Role = defaultData.Role;
|
||||
data.Roles = defaultData.Roles;
|
||||
data.CooldownDuration = defaultData.CooldownDuration;
|
||||
data.EffectDuration = defaultData.EffectDuration;
|
||||
data.SharedActionIds = defaultData.SharedActionIds;
|
||||
|
||||
needsSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
ExcelSheet<LuminaAction>? sheet = Plugin.DataManager.GetExcelSheet<LuminaAction>();
|
||||
ExcelSheet<ActionTransient>? descriptionsSheet = Plugin.DataManager.GetExcelSheet<ActionTransient>();
|
||||
|
||||
foreach (PartyCooldownData cooldown in Cooldowns)
|
||||
{
|
||||
LuminaAction? action = sheet?.GetRow(cooldown.ActionId);
|
||||
if (!action.HasValue) { continue; }
|
||||
|
||||
// get real cooldown from data
|
||||
// keep hardcoded value for technical finish
|
||||
if (action.Value.Recast100ms > 0 && cooldown.ActionId != 16004)
|
||||
{
|
||||
cooldown.CooldownDuration = action.Value.Recast100ms / 10;
|
||||
}
|
||||
|
||||
// not happy about this but didn't want to over-complicate things
|
||||
// special case for troubadour, shield samba and tactician
|
||||
if (cooldown.ActionId == 7405 || cooldown.ActionId == 16012 || cooldown.ActionId == 16889)
|
||||
{
|
||||
cooldown.OverriddenCooldownText = "90-120";
|
||||
}
|
||||
|
||||
// reprisal, feint, addle
|
||||
else if (cooldown.ActionId == 7535 || cooldown.ActionId == 7549 || cooldown.ActionId == 7560)
|
||||
{
|
||||
cooldown.OverriddenDurationText = "10-15";
|
||||
}
|
||||
|
||||
// swiftcast
|
||||
else if (cooldown.ActionId == 7561)
|
||||
{
|
||||
cooldown.OverriddenCooldownText = "40-60";
|
||||
}
|
||||
|
||||
// special case for starry muse
|
||||
if (cooldown.ActionId == 35349)
|
||||
{
|
||||
cooldown.IconId = 3826;
|
||||
}
|
||||
else
|
||||
{
|
||||
cooldown.IconId = action.Value.Icon;
|
||||
}
|
||||
|
||||
cooldown.Name = action.Value.Name.ToString();
|
||||
}
|
||||
|
||||
if (needsSave)
|
||||
{
|
||||
ConfigurationManager.Instance.SaveConfigurations(true);
|
||||
}
|
||||
}
|
||||
|
||||
[ManualDraw]
|
||||
public bool Draw(ref bool changed)
|
||||
{
|
||||
ImGuiHelper.NewLineAndTab();
|
||||
|
||||
// filter
|
||||
ImGui.SameLine();
|
||||
ImGui.Text("Filter: ");
|
||||
|
||||
DrawFilter("All", JobRoles.Unknown);
|
||||
DrawFilter("Tanks", JobRoles.Tank);
|
||||
DrawFilter("Healers", JobRoles.Healer);
|
||||
DrawFilter("Melee", JobRoles.DPSMelee);
|
||||
DrawFilter("Ranged", JobRoles.DPSRanged);
|
||||
DrawFilter("Casters", JobRoles.DPSCaster);
|
||||
|
||||
DrawJobFilters();
|
||||
|
||||
// table
|
||||
ImGuiHelper.NewLineAndTab();
|
||||
ImGui.SameLine();
|
||||
|
||||
var flags =
|
||||
ImGuiTableFlags.RowBg |
|
||||
ImGuiTableFlags.Borders |
|
||||
ImGuiTableFlags.BordersOuter |
|
||||
ImGuiTableFlags.BordersInner |
|
||||
ImGuiTableFlags.ScrollY |
|
||||
ImGuiTableFlags.SizingFixedSame;
|
||||
|
||||
ExcelSheet<LuminaAction>? sheet = Plugin.DataManager.GetExcelSheet<LuminaAction>();
|
||||
var iconSize = new Vector2(30, 30);
|
||||
|
||||
if (ImGui.BeginTable("##DelvUI_PartyCooldownsTable", 8, flags, new Vector2(900, 500)))
|
||||
{
|
||||
ImGui.TableSetupColumn("Enabled", ImGuiTableColumnFlags.WidthStretch, 22, 0);
|
||||
ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthStretch, 5, 1);
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 24, 2);
|
||||
ImGui.TableSetupColumn("Cooldown", ImGuiTableColumnFlags.WidthStretch, 8, 3);
|
||||
ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthStretch, 8, 4);
|
||||
ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthStretch, 11, 5);
|
||||
ImGui.TableSetupColumn("Section", ImGuiTableColumnFlags.WidthStretch, 11, 6);
|
||||
ImGui.TableSetupColumn("Exclude Jobs", ImGuiTableColumnFlags.WidthStretch, 11, 7);
|
||||
|
||||
ImGui.TableSetupScrollFreeze(0, 1);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (PartyCooldownData cooldown in Cooldowns)
|
||||
{
|
||||
// apply filter
|
||||
if (_roleFilter != JobRoles.Unknown)
|
||||
{
|
||||
if (!cooldown.HasRole(_roleFilter)) { continue; }
|
||||
|
||||
if (_roleFilter == JobRoles.Tank && _tankFilter != 0 && !cooldown.IsUsableBy(_tankFilter)) { continue; }
|
||||
if (_roleFilter == JobRoles.Healer && _healerFilter != 0 && !cooldown.IsUsableBy(_healerFilter)) { continue; }
|
||||
if (_roleFilter == JobRoles.DPSMelee && _meleeFilter != 0 && !cooldown.IsUsableBy(_meleeFilter)) { continue; }
|
||||
if (_roleFilter == JobRoles.DPSRanged && _rangedFilter != 0 && !cooldown.IsUsableBy(_rangedFilter)) { continue; }
|
||||
if (_roleFilter == JobRoles.DPSCaster && _casterFilter != 0 && !cooldown.IsUsableBy(_casterFilter)) { continue; }
|
||||
}
|
||||
|
||||
LuminaAction? action = sheet?.GetRow(cooldown.ActionId);
|
||||
|
||||
ImGui.PushID(cooldown.ActionId.ToString());
|
||||
ImGui.TableNextRow(ImGuiTableRowFlags.None, iconSize.Y);
|
||||
|
||||
// enabled
|
||||
if (ImGui.TableSetColumnIndex(0))
|
||||
{
|
||||
ImGui.PushItemWidth(178);
|
||||
ImGui.SetCursorPos(new Vector2(ImGui.GetCursorPosX() + 2, ImGui.GetCursorPosY() + 2));
|
||||
int enabled = (int)cooldown.EnabledV2;
|
||||
if (ImGui.Combo($"##{cooldown.ActionId}_enabled", ref enabled, _enabledOptions))
|
||||
{
|
||||
changed = true;
|
||||
cooldown.EnabledV2 = (PartyCooldownEnabled)enabled;
|
||||
CooldownsDataEnabledChangedEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
// icon
|
||||
if (ImGui.TableSetColumnIndex(1) && action != null)
|
||||
{
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 3);
|
||||
DrawHelper.DrawIcon<LuminaAction>(action, ImGui.GetCursorPos(), iconSize, false, false);
|
||||
}
|
||||
|
||||
// name
|
||||
if (ImGui.TableSetColumnIndex(2))
|
||||
{
|
||||
ImGui.Text(cooldown.Name);
|
||||
}
|
||||
|
||||
// cooldown
|
||||
if (ImGui.TableSetColumnIndex(3))
|
||||
{
|
||||
string cooldownText = cooldown.OverriddenCooldownText != null ? cooldown.OverriddenCooldownText : $"{cooldown.CooldownDuration}";
|
||||
ImGui.Text(cooldownText);
|
||||
}
|
||||
|
||||
// duration
|
||||
if (ImGui.TableSetColumnIndex(4))
|
||||
{
|
||||
string durationText = cooldown.OverriddenDurationText != null ? cooldown.OverriddenDurationText : $"{cooldown.EffectDuration}";
|
||||
ImGui.Text(durationText);
|
||||
}
|
||||
|
||||
// priority
|
||||
if (ImGui.TableSetColumnIndex(5))
|
||||
{
|
||||
ImGui.PushItemWidth(86);
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2);
|
||||
|
||||
if (ImGui.DragInt($"##{cooldown.ActionId}_priority", ref cooldown.Priority, 1, 0, 100))
|
||||
{
|
||||
cooldown.Priority = Math.Clamp(cooldown.Priority, 0, 100);
|
||||
changed = true;
|
||||
CooldownsDataChangedEvent?.Invoke(this);
|
||||
}
|
||||
|
||||
ImGuiHelper.SetTooltip("Priority determines which cooldows show first on the list.");
|
||||
}
|
||||
|
||||
// column
|
||||
if (ImGui.TableSetColumnIndex(6))
|
||||
{
|
||||
ImGui.PushItemWidth(86);
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2);
|
||||
|
||||
if (ImGui.DragInt($"##{cooldown.ActionId}_column", ref cooldown.Column, 0.1f, 1, ColumnCount, "%i", ImGuiSliderFlags.NoInput))
|
||||
{
|
||||
changed = true;
|
||||
CooldownsDataChangedEvent?.Invoke(this);
|
||||
}
|
||||
|
||||
ImGuiHelper.SetTooltip("Allows to separate cooldowns in different columns.");
|
||||
}
|
||||
|
||||
// exlude
|
||||
if (ImGui.TableSetColumnIndex(7) && (cooldown.Roles != null || cooldown.Role != JobRoles.Unknown))
|
||||
{
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2);
|
||||
|
||||
if (ImGui.Button("Select##Exclude", new Vector2(86, 24)))
|
||||
{
|
||||
_needsPopupOpen = true;
|
||||
_popupCooldown = cooldown;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PopID();
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
if (_needsPopupOpen)
|
||||
{
|
||||
ImGui.OpenPopup("Exclude Jobs##HSUI");
|
||||
_needsPopupOpen = false;
|
||||
}
|
||||
|
||||
if (_popupCooldown != null)
|
||||
{
|
||||
DrawJobsListForCooldown(_popupCooldown);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<uint> IgnoredJobIds = new List<uint>()
|
||||
{
|
||||
JobIDs.GLA, JobIDs.MRD,
|
||||
JobIDs.CNJ, JobIDs.WHM,
|
||||
JobIDs.PGL, JobIDs.LNC, JobIDs.ROG,
|
||||
JobIDs.ARC,
|
||||
JobIDs.THM, JobIDs.ACN, JobIDs.BLU,
|
||||
};
|
||||
|
||||
private void DrawJobsListForCooldown(PartyCooldownData cooldown)
|
||||
{
|
||||
List<JobRoles> roles = cooldown.Roles ?? new List<JobRoles>() { cooldown.Role };
|
||||
|
||||
List<uint> jobIds = new List<uint>();
|
||||
foreach (JobRoles role in roles)
|
||||
{
|
||||
jobIds.AddRange(JobsHelper.JobsByRole[role]);
|
||||
}
|
||||
jobIds = jobIds.Where(id => !IgnoredJobIds.Contains(id)).ToList();
|
||||
|
||||
string title = cooldown.Name + ":";
|
||||
float width = Math.Max(100, ImGui.CalcTextSize(title).X + 10);
|
||||
ImGui.SetNextWindowSize(new(width, jobIds.Count * 30 + 32));
|
||||
|
||||
if (ImGui.BeginPopup("Exclude Jobs##HSUI", ImGuiWindowFlags.NoMove))
|
||||
{
|
||||
ImGui.Text(title);
|
||||
|
||||
foreach (uint jobId in jobIds)
|
||||
{
|
||||
bool selected = !cooldown.ExcludedJobIds.Contains(jobId);
|
||||
if (ImGui.Checkbox(JobsHelper.JobNames[jobId] + "##popup", ref selected))
|
||||
{
|
||||
if (selected)
|
||||
{
|
||||
cooldown.ExcludedJobIds.Remove(jobId);
|
||||
}
|
||||
else
|
||||
{
|
||||
cooldown.ExcludedJobIds.Add(jobId);
|
||||
}
|
||||
|
||||
CooldownsDataEnabledChangedEvent?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFilter(string name, JobRoles role)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
if (ImGui.RadioButton(name, _roleFilter == role))
|
||||
{
|
||||
_roleFilter = role;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJobFilter(string name, uint job, ref uint filter)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
if (ImGui.RadioButton(name, filter == job))
|
||||
{
|
||||
filter = job;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJobFilters()
|
||||
{
|
||||
if (_roleFilter == JobRoles.Unknown) { return; }
|
||||
|
||||
ImGui.Text("\t\t\t\t ");
|
||||
|
||||
if (_roleFilter == JobRoles.Tank)
|
||||
{
|
||||
DrawJobFilter("All##tank", 0, ref _tankFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.PLD], JobIDs.PLD, ref _tankFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.WAR], JobIDs.WAR, ref _tankFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.DRK], JobIDs.DRK, ref _tankFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.GNB], JobIDs.GNB, ref _tankFilter);
|
||||
}
|
||||
else if (_roleFilter == JobRoles.Healer)
|
||||
{
|
||||
DrawJobFilter("All##healer", 0, ref _healerFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.WHM], JobIDs.WHM, ref _healerFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.SCH], JobIDs.SCH, ref _healerFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.AST], JobIDs.AST, ref _healerFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.SGE], JobIDs.SGE, ref _healerFilter);
|
||||
}
|
||||
else if (_roleFilter == JobRoles.DPSMelee)
|
||||
{
|
||||
DrawJobFilter("All##melee", 0, ref _meleeFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.MNK], JobIDs.MNK, ref _meleeFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.DRG], JobIDs.DRG, ref _meleeFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.NIN], JobIDs.NIN, ref _meleeFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.SAM], JobIDs.SAM, ref _meleeFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.RPR], JobIDs.RPR, ref _meleeFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.VPR], JobIDs.VPR, ref _meleeFilter);
|
||||
}
|
||||
else if (_roleFilter == JobRoles.DPSRanged)
|
||||
{
|
||||
DrawJobFilter("All##ranged", 0, ref _rangedFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.BRD], JobIDs.BRD, ref _rangedFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.MCH], JobIDs.MCH, ref _rangedFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.DNC], JobIDs.DNC, ref _rangedFilter);
|
||||
}
|
||||
else if (_roleFilter == JobRoles.DPSCaster)
|
||||
{
|
||||
DrawJobFilter("All##caster", 0, ref _casterFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.BLM], JobIDs.BLM, ref _casterFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.SMN], JobIDs.SMN, ref _casterFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.RDM], JobIDs.RDM, ref _casterFilter);
|
||||
DrawJobFilter(JobsHelper.JobNames[JobIDs.PCT], JobIDs.PCT, ref _casterFilter);
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<uint, PartyCooldownData> DefaultCooldowns = new Dictionary<uint, PartyCooldownData>()
|
||||
{
|
||||
// PARTY-WIDE EFFECTS
|
||||
|
||||
// Default columns:
|
||||
// 1. Role mitigations (Reprisal, Addle, Feint)
|
||||
// 2. Job specific mitigations
|
||||
// 3. Damage buffs / burst window cooldowns
|
||||
// 4. Personal mitigations / immunities / externals
|
||||
// 5. Misc (Provoke, Shirk, Switcast, Rescue, etc)
|
||||
|
||||
// TANKS -------------------------------------------------------------------------------------------------
|
||||
[7535] = NewData(7535, JobRoles.Tank, 22, 60, 10, 100, 1, PartyCooldownEnabled.PartyFrames), // reprisal
|
||||
[7531] = NewData(7531, JobRoles.Tank, 8, 90, 20, 50, 4, PartyCooldownEnabled.PartyFrames), // rampart
|
||||
[7533] = NewData(7533, JobRoles.Tank, 15, 30, 1, 50, 5, PartyCooldownEnabled.PartyFrames), // provoke
|
||||
[7537] = NewData(7537, JobRoles.Tank, 48, 120, 1, 50, 5, PartyCooldownEnabled.PartyFrames), // shirk
|
||||
|
||||
// PLD
|
||||
[30] = NewData(30, JobIDs.PLD, 50, 420, 10, 100, 4, PartyCooldownEnabled.PartyFrames), // hallowed ground
|
||||
[3540] = NewData(3540, JobIDs.PLD, 56, 90, 30, 90, 2, PartyCooldownEnabled.PartyFrames), // divine veil
|
||||
[7385] = NewData(7385, JobIDs.PLD, 70, 120, 18, 90, 2, PartyCooldownEnabled.PartyFrames), // passage of arms
|
||||
[20] = NewData(20, JobIDs.PLD, 2, 60, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // fight or flight
|
||||
[22] = NewData(22, JobIDs.PLD, 52, 90, 10, 90, 4, PartyCooldownEnabled.PartyFrames), // bulwark
|
||||
[27] = NewData(27, JobIDs.PLD, 45, 120, 12, 90, 4, PartyCooldownEnabled.PartyFrames), // cover
|
||||
|
||||
[17] = NewData(17, JobIDs.PLD, 38, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // sentinel
|
||||
[36920] = NewData(36920, JobIDs.PLD, 92, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // guardian
|
||||
|
||||
// WAR
|
||||
[43] = NewData(43, JobIDs.WAR, 42, 240, 10, 100, 4, PartyCooldownEnabled.PartyFrames), // holmgang
|
||||
[7388] = NewData(7388, JobIDs.WAR, 68, 90, 30, 90, 2, PartyCooldownEnabled.PartyFrames), // shake it off
|
||||
[52] = NewData(52, JobIDs.WAR, 50, 60, 30, 10, 3, PartyCooldownEnabled.PartyFrames), // infuriate
|
||||
[38] = NewData(38, JobIDs.WAR, 6, 60, 30, 10, 3, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 70), // berserk
|
||||
[7389] = NewData(7389, JobIDs.WAR, 70, 60, 30, 10, 3, PartyCooldownEnabled.PartyFrames), // inner release
|
||||
[40] = NewData(40, JobIDs.WAR, 30, 90, 10, 90, 4, PartyCooldownEnabled.PartyFrames), // thrill of battle
|
||||
[3552] = NewData(3552, JobIDs.WAR, 58, 60, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // equilibrium
|
||||
|
||||
[3551] = NewData(3551, JobIDs.WAR, 56, 25, 8, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 82, sharedActionIds: new() { 16464 }), // raw intuition
|
||||
[25751] = NewData(25751, JobIDs.WAR, 82, 25, 8, 90, 4, PartyCooldownEnabled.PartyFrames, sharedActionIds: new() { 16464 }), // bloodwhetting
|
||||
[16464] = NewData(16464, JobIDs.WAR, 76, 25, 8, 90, 4, PartyCooldownEnabled.PartyFrames, sharedActionIds: new() { 3551, 25751 }), // nascent flash
|
||||
|
||||
[44] = NewData(44, JobIDs.WAR, 38, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // vengeance
|
||||
[36923] = NewData(36923, JobIDs.WAR, 92, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // damnation
|
||||
|
||||
// DRK
|
||||
[3638] = NewData(3638, JobIDs.DRK, 50, 300, 10, 100, 4, PartyCooldownEnabled.PartyFrames), // living dead
|
||||
[16471] = NewData(16471, JobIDs.DRK, 66, 90, 15, 90, 2, PartyCooldownEnabled.PartyFrames), // dark missionary
|
||||
[16472] = NewData(16472, JobIDs.DRK, 80, 120, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // living shadow
|
||||
[3634] = NewData(3634, JobIDs.DRK, 45, 60, 10, 90, 4, PartyCooldownEnabled.PartyFrames), // dark mind
|
||||
[25754] = NewData(25754, JobIDs.DRK, 82, 60, 10, 90, 4, PartyCooldownEnabled.PartyFrames), // oblation
|
||||
|
||||
[3625] = NewData(3625, JobIDs.DRK, 35, 60, 15, 10, 3, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 68), // blood weapon
|
||||
[7390] = NewData(7390, JobIDs.DRK, 68, 60, 15, 10, 3, PartyCooldownEnabled.PartyFrames), // delirium
|
||||
|
||||
[3636] = NewData(3636, JobIDs.DRK, 38, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // shadow wall
|
||||
[36927] = NewData(36927, JobIDs.DRK, 92, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // shadowed vigil
|
||||
|
||||
// GNB
|
||||
[16152] = NewData(16152, JobIDs.GNB, 50, 360, 10, 100, 4, PartyCooldownEnabled.PartyFrames), // superbolide
|
||||
[16160] = NewData(16160, JobIDs.GNB, 64, 90, 15, 90, 2, PartyCooldownEnabled.PartyFrames), // heart of light
|
||||
[16164] = NewData(16164, JobIDs.GNB, 76, 120, 1, 10, 3, PartyCooldownEnabled.PartyFrames), // bloodfest
|
||||
[16138] = NewData(16138, JobIDs.GNB, 2, 60, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // no mercy
|
||||
[16140] = NewData(16140, JobIDs.GNB, 6, 90, 20, 90, 4, PartyCooldownEnabled.PartyFrames), // camouflage
|
||||
|
||||
[16161] = NewData(16161, JobIDs.GNB, 68, 25, 7, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 82), // heart of stone
|
||||
[25758] = NewData(25758, JobIDs.GNB, 82, 25, 7, 90, 4, PartyCooldownEnabled.PartyFrames), // heart of corundum
|
||||
|
||||
[16148] = NewData(16148, JobIDs.GNB, 38, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // nebula
|
||||
[36935] = NewData(36935, JobIDs.GNB, 92, 120, 15, 90, 4, PartyCooldownEnabled.PartyFrames), // great nebula
|
||||
|
||||
// HEALER -------------------------------------------------------------------------------------------------
|
||||
[7571] = NewData(7571, JobRoles.Healer, 48, 120, 0, 80, 5, PartyCooldownEnabled.Disabled), // rescue
|
||||
|
||||
// AST
|
||||
[16552] = NewData(16552, JobIDs.AST, 50, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // divination
|
||||
[3613] = NewData(3613, JobIDs.AST, 58, 60, 18, 80, 2, PartyCooldownEnabled.PartyFrames), // collective unconscious
|
||||
[16553] = NewData(16553, JobIDs.AST, 60, 60, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // celestial opposition
|
||||
[7439] = NewData(7439, JobIDs.AST, 62, 60, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // earthly star (stellar detonation = 8324)
|
||||
[16559] = NewData(16559, JobIDs.AST, 80, 120, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // neutral sect
|
||||
[25873] = NewData(25873, JobIDs.AST, 86, 60, 8, 80, 2, PartyCooldownEnabled.PartyFrames), // exaltation
|
||||
[25874] = NewData(25874, JobIDs.AST, 90, 180, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // macrocosmos (microcosmos = 25875)
|
||||
[3606] = NewData(3606, JobIDs.AST, 6, 90, 15, 10, 4, PartyCooldownEnabled.PartyFrames), // lightspeed
|
||||
|
||||
// SCH
|
||||
[7436] = NewData(7436, JobIDs.SCH, 66, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // chain stratagem
|
||||
[805] = NewData(805, JobIDs.SCH, 40, 120, 20, 50, 2, PartyCooldownEnabled.PartyFrames), // fey illumination
|
||||
[188] = NewData(188, JobIDs.SCH, 50, 30, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // sacred soil
|
||||
[3585] = NewData(3585, JobIDs.SCH, 56, 90, 1, 80, 2, PartyCooldownEnabled.PartyFrames), // deployment tactics
|
||||
[25867] = NewData(25867, JobIDs.SCH, 86, 60, 10, 80, 2, PartyCooldownEnabled.PartyFrames), // protraction
|
||||
[25868] = NewData(25868, JobIDs.SCH, 90, 120, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // expedient
|
||||
[7434] = NewData(7434, JobIDs.SCH, 62, 45, 1, 50, 4, PartyCooldownEnabled.PartyFrames), // excogitation
|
||||
[37014] = NewData(37014, JobIDs.SCH, 100, 180, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // seraphism
|
||||
|
||||
// WHM
|
||||
[16536] = NewData(16536, JobIDs.WHM, 80, 120, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // temperance
|
||||
[3569] = NewData(3569, JobIDs.WHM, 52, 90, 24, 50, 2, PartyCooldownEnabled.PartyFrames), // asylum
|
||||
[25862] = NewData(25862, JobIDs.WHM, 90, 180, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // liturgy of the bell
|
||||
[136] = NewData(136, JobIDs.WHM, 30, 120, 15, 10, 4, PartyCooldownEnabled.PartyFrames), // presence of mind
|
||||
[140] = NewData(140, JobIDs.WHM, 50, 180, 1, 10, 4, PartyCooldownEnabled.PartyFrames), // benediction
|
||||
[3570] = NewData(3570, JobIDs.WHM, 60, 60, 1, 10, 4, PartyCooldownEnabled.PartyFrames), // tetragrammaton
|
||||
[25861] = NewData(25861, JobIDs.WHM, 86, 60, 8, 10, 4, PartyCooldownEnabled.PartyFrames), // aquaveil
|
||||
|
||||
// SGE
|
||||
[24298] = NewData(24298, JobIDs.SGE, 50, 30, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // kerachole
|
||||
[24302] = NewData(24302, JobIDs.SGE, 60, 60, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // physis ii
|
||||
[24310] = NewData(24310, JobIDs.SGE, 76, 120, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // holos
|
||||
[24311] = NewData(24311, JobIDs.SGE, 80, 120, 15, 80, 2, PartyCooldownEnabled.PartyFrames), // panhaima
|
||||
[24318] = NewData(24318, JobIDs.SGE, 90, 120, 1, 80, 2, PartyCooldownEnabled.PartyFrames), // pneuma
|
||||
[24303] = NewData(24303, JobIDs.SGE, 62, 45, 15, 10, 4, PartyCooldownEnabled.PartyFrames), // taurochole
|
||||
[24305] = NewData(24305, JobIDs.SGE, 70, 120, 15, 10, 4, PartyCooldownEnabled.PartyFrames), // haima
|
||||
[24317] = NewData(24317, JobIDs.SGE, 86, 60, 10, 10, 4, PartyCooldownEnabled.PartyFrames), // krasis
|
||||
[37035] = NewData(37035, JobIDs.SGE, 100, 180, 20, 80, 2, PartyCooldownEnabled.PartyFrames), // philosophia
|
||||
|
||||
// MELEE -------------------------------------------------------------------------------------------------
|
||||
[7549] = NewData(7549, JobRoles.DPSMelee, 22, 90, 10, 100, 1, PartyCooldownEnabled.PartyFrames), // feint
|
||||
[7542] = NewData(7542, JobRoles.DPSMelee, 12, 90, 20, 10, 4, PartyCooldownEnabled.PartyFrames), // bloodbath
|
||||
|
||||
// SAM
|
||||
// lol?
|
||||
|
||||
// VPR
|
||||
// lol?
|
||||
|
||||
// NIN
|
||||
[2248] = NewData(2248, JobIDs.NIN, 15, 120, 20, 30, 3, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 68), // mug
|
||||
[36957] = NewData(36957, JobIDs.NIN, 68, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // dokumori
|
||||
[2258] = NewData(2258, JobIDs.NIN, 18, 60, 15, 10, 3, PartyCooldownEnabled.PartyFrames, disabledAfterLevel: 92), // trick attack
|
||||
[36958] = NewData(36958, JobIDs.NIN, 92, 60, 15, 10, 3, PartyCooldownEnabled.PartyFrames), // kunai's bane
|
||||
[2241] = NewData(2241, JobIDs.NIN, 2, 120, 20, 20, 4, PartyCooldownEnabled.PartyFrames), // shade shift
|
||||
|
||||
// DRG
|
||||
[3557] = NewData(3557, JobIDs.DRG, 52, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // battle litany
|
||||
[85] = NewData(85, JobIDs.DRG, 30, 60, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // lance charge
|
||||
|
||||
// MNK
|
||||
[65] = NewData(65, JobIDs.MNK, 42, 90, 15, 50, 2, PartyCooldownEnabled.PartyFrames), // mantra
|
||||
[7396] = NewData(7396, JobIDs.MNK, 70, 120, 20, 90, 3, PartyCooldownEnabled.PartyCooldowns), // brotherhood
|
||||
[7395] = NewData(7395, JobIDs.MNK, 68, 60, 20, 10, 3, PartyCooldownEnabled.PartyFrames), // riddle of fire
|
||||
[7394] = NewData(7394, JobIDs.MNK, 64, 120, 15, 20, 4, PartyCooldownEnabled.PartyFrames), // riddle of earth
|
||||
|
||||
// RPR
|
||||
[24405] = NewData(24405, JobIDs.RPR, 72, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // arcane circle
|
||||
[24404] = NewData(24404, JobIDs.RPR, 40, 30, 5, 10, 4, PartyCooldownEnabled.PartyFrames), // arcane crest
|
||||
|
||||
// RANGED -------------------------------------------------------------------------------------------------
|
||||
// BRD
|
||||
[118] = NewData(118, JobIDs.BRD, 50, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // battle voice
|
||||
[7405] = NewData(7405, JobIDs.BRD, 62, 90, 15, 70, 2, PartyCooldownEnabled.PartyFrames, "90-120"), // troubadour
|
||||
[7408] = NewData(7408, JobIDs.BRD, 66, 120, 15, 40, 2, PartyCooldownEnabled.PartyFrames), // nature's minne
|
||||
[25785] = NewData(25785, JobIDs.BRD, 90, 110, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // radiant finale
|
||||
[101] = NewData(101, JobIDs.BRD, 4, 120, 20, 90, 3, PartyCooldownEnabled.PartyFrames), // raging strikes
|
||||
|
||||
// DNC
|
||||
[16012] = NewData(16012, JobIDs.DNC, 56, 90, 15, 70, 2, PartyCooldownEnabled.PartyFrames, "90-120"), // shield samba
|
||||
[16004] = NewData(16004, JobIDs.DNC, 70, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // technical step / finish
|
||||
[16011] = NewData(16011, JobIDs.DNC, 62, 120, 20, 90, 3, PartyCooldownEnabled.PartyFrames), // devilment
|
||||
|
||||
// MCH
|
||||
[16889] = NewData(16889, JobIDs.MCH, 56, 90, 15, 70, 2, PartyCooldownEnabled.PartyFrames, "90-120"), // tactician
|
||||
[2887] = NewData(2887, JobIDs.MCH, 62, 120, 10, 70, 2, PartyCooldownEnabled.PartyFrames), // dismantle
|
||||
|
||||
// CASTER -------------------------------------------------------------------------------------------------
|
||||
[7560] = NewData(7560, JobRoles.DPSCaster, 8, 90, 10, 100, 1, PartyCooldownEnabled.PartyFrames), // addle
|
||||
|
||||
// RDM
|
||||
[25857] = NewData(25857, JobIDs.RDM, 86, 120, 10, 70, 2, PartyCooldownEnabled.PartyCooldownsAndPartyFrames), // magick barrier
|
||||
[7520] = NewData(7520, JobIDs.RDM, 58, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // embolden
|
||||
|
||||
// SMN
|
||||
[25801] = NewData(25801, JobIDs.SMN, 66, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // searing light
|
||||
[25799] = NewData(25799, JobIDs.SMN, 2, 60, 30, 10, 3, PartyCooldownEnabled.PartyFrames), // radiant aegis
|
||||
|
||||
// BLM
|
||||
[3573] = NewData(3573, JobIDs.BLM, 52, 120, 30, 90, 3, PartyCooldownEnabled.PartyFrames), // ley lines
|
||||
[157] = NewData(157, JobIDs.BLM, 38, 120, 20, 10, 4, PartyCooldownEnabled.PartyFrames), // manaward
|
||||
|
||||
// PCT
|
||||
[35349] = NewData(35349, JobIDs.PCT, 70, 120, 20, 30, 3, PartyCooldownEnabled.PartyCooldowns), // scenic muse
|
||||
[34685] = NewData(34685, JobIDs.PCT, 10, 120, 10, 10, 4, PartyCooldownEnabled.PartyFrames), // tempera coat
|
||||
|
||||
// MULTI-ROLE -------------------------------------------------------------------------------------------------
|
||||
[7541] = NewData(7541, new List<JobRoles>() { JobRoles.DPSMelee, JobRoles.DPSRanged }, 8, 120, 0, 80, 4, PartyCooldownEnabled.PartyFrames), // second wind
|
||||
[7561] = NewData(7561, new List<JobRoles>() { JobRoles.Healer, JobRoles.DPSCaster }, 18, 60, 1, 80, 5, PartyCooldownEnabled.PartyFrames, null, new HashSet<uint>() { JobIDs.BLM, JobIDs.SMN, JobIDs.RDM }), // swiftcast
|
||||
[7562] = NewData(7562, new List<JobRoles>() { JobRoles.Healer, JobRoles.DPSCaster }, 14, 60, 21, 80, 5, PartyCooldownEnabled.Disabled), // lucid dreaming
|
||||
};
|
||||
|
||||
#region helpers
|
||||
private static PartyCooldownData NewData(
|
||||
uint actionId,
|
||||
uint jobId,
|
||||
uint level,
|
||||
int cooldown,
|
||||
int effectDuration,
|
||||
int priority,
|
||||
int column,
|
||||
PartyCooldownEnabled enabled,
|
||||
string? overriddenCooldownText = null,
|
||||
HashSet<uint>? excludedJobIds = null,
|
||||
uint disabledAfterLevel = 0,
|
||||
List<uint>? sharedActionIds = null)
|
||||
{
|
||||
PartyCooldownData data = NewData(
|
||||
actionId,
|
||||
level,
|
||||
cooldown,
|
||||
effectDuration,
|
||||
priority,
|
||||
column,
|
||||
enabled,
|
||||
overriddenCooldownText,
|
||||
excludedJobIds,
|
||||
disabledAfterLevel,
|
||||
sharedActionIds
|
||||
);
|
||||
|
||||
data.JobId = jobId;
|
||||
data.Role = JobRoles.Unknown;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static PartyCooldownData NewData(uint actionId, JobRoles role, uint level, int cooldown, int effectDuration, int priority, int column, PartyCooldownEnabled enabled, string? overriddenCooldownText = null, HashSet<uint>? excludedJobIds = null)
|
||||
{
|
||||
PartyCooldownData data = NewData(
|
||||
actionId,
|
||||
level,
|
||||
cooldown,
|
||||
effectDuration,
|
||||
priority,
|
||||
column,
|
||||
enabled,
|
||||
overriddenCooldownText,
|
||||
excludedJobIds
|
||||
);
|
||||
|
||||
data.JobId = 0;
|
||||
data.Role = role;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static PartyCooldownData NewData(uint actionId, List<JobRoles> roles, uint level, int cooldown, int effectDuration, int priority, int column, PartyCooldownEnabled enabled, string? overriddenCooldownText = null, HashSet<uint>? excludedJobIds = null)
|
||||
{
|
||||
PartyCooldownData data = NewData(
|
||||
actionId,
|
||||
level,
|
||||
cooldown,
|
||||
effectDuration,
|
||||
priority,
|
||||
column,
|
||||
enabled,
|
||||
overriddenCooldownText,
|
||||
excludedJobIds
|
||||
);
|
||||
|
||||
data.JobId = 0;
|
||||
data.Role = JobRoles.Unknown;
|
||||
data.Roles = roles;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static PartyCooldownData NewData(
|
||||
uint actionId,
|
||||
uint level,
|
||||
int cooldown,
|
||||
int effectDuration,
|
||||
int priority,
|
||||
int column,
|
||||
PartyCooldownEnabled enabled,
|
||||
string? overriddenCooldownText = null,
|
||||
HashSet<uint>? excludedJobIds = null,
|
||||
uint disabledAfterLevel = 0,
|
||||
List<uint>? sharedActionIds = null
|
||||
)
|
||||
{
|
||||
PartyCooldownData data = new PartyCooldownData();
|
||||
data.ActionId = actionId;
|
||||
data.RequiredLevel = level;
|
||||
data.DisabledAfterLevel = disabledAfterLevel;
|
||||
data.CooldownDuration = cooldown;
|
||||
data.EffectDuration = effectDuration;
|
||||
data.Priority = priority;
|
||||
data.Column = column;
|
||||
data.EnabledV2 = enabled;
|
||||
data.OverriddenCooldownText = overriddenCooldownText;
|
||||
data.SharedActionIds = sharedActionIds ?? new();
|
||||
|
||||
if (excludedJobIds != null)
|
||||
{
|
||||
data.ExcludedJobIds = excludedJobIds;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using HSUI.Config;
|
||||
using HSUI.Enums;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.Bars;
|
||||
using HSUI.Interface.GeneralElements;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace HSUI.Interface.PartyCooldowns
|
||||
{
|
||||
public class PartyCooldownsHud : DraggableHudElement, IHudElementWithPreview, IHudElementWithVisibilityConfig
|
||||
{
|
||||
private PartyCooldownsConfig Config => (PartyCooldownsConfig)_config;
|
||||
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
|
||||
private PartyCooldownsBarConfig _barConfig = null!;
|
||||
private PartyCooldownsDataConfig _dataConfig = null!;
|
||||
|
||||
private List<List<PartyCooldown>> _cooldowns = new List<List<PartyCooldown>>();
|
||||
|
||||
private LabelHud _nameLabelHud;
|
||||
private LabelHud _timeLabelHud;
|
||||
|
||||
public PartyCooldownsHud(PartyCooldownsConfig config, string displayName) : base(config, displayName)
|
||||
{
|
||||
_barConfig = ConfigurationManager.Instance.GetConfigObject<PartyCooldownsBarConfig>();
|
||||
_dataConfig = ConfigurationManager.Instance.GetConfigObject<PartyCooldownsDataConfig>();
|
||||
|
||||
_dataConfig.CooldownsDataChangedEvent += OnCooldownsDataChanged;
|
||||
PartyCooldownsManager.Instance.CooldownsChangedEvent += OnCooldownsChanged;
|
||||
|
||||
_nameLabelHud = new LabelHud(_barConfig.NameLabel);
|
||||
_timeLabelHud = new LabelHud(_barConfig.TimeLabel);
|
||||
|
||||
UpdateCooldowns();
|
||||
}
|
||||
|
||||
protected override void InternalDispose()
|
||||
{
|
||||
_dataConfig.CooldownsDataChangedEvent -= OnCooldownsDataChanged;
|
||||
PartyCooldownsManager.Instance.CooldownsChangedEvent -= OnCooldownsChanged;
|
||||
}
|
||||
|
||||
public void StopPreview()
|
||||
{
|
||||
Config.Preview = false;
|
||||
PartyCooldownsManager.Instance?.UpdatePreview();
|
||||
}
|
||||
|
||||
private void OnCooldownsDataChanged(PartyCooldownsDataConfig sender)
|
||||
{
|
||||
UpdateCooldowns();
|
||||
}
|
||||
|
||||
private void OnCooldownsChanged(PartyCooldownsManager sender)
|
||||
{
|
||||
UpdateCooldowns();
|
||||
}
|
||||
|
||||
private void UpdateCooldowns()
|
||||
{
|
||||
_cooldowns.Clear();
|
||||
|
||||
int columnCount = PartyCooldownsDataConfig.ColumnCount;
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
{
|
||||
_cooldowns.Add(new List<PartyCooldown>());
|
||||
}
|
||||
|
||||
foreach (Dictionary<uint, PartyCooldown> memberCooldownList in PartyCooldownsManager.Instance.CooldownsMap.Values)
|
||||
{
|
||||
foreach (PartyCooldown cooldown in memberCooldownList.Values)
|
||||
{
|
||||
if (!cooldown.Data.IsEnabledForPartyCooldowns()) { continue; }
|
||||
|
||||
int columnIndex = Math.Min(columnCount - 1, cooldown.Data.Column - 1);
|
||||
_cooldowns[columnIndex].Add(cooldown);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (List<PartyCooldown> list in _cooldowns)
|
||||
{
|
||||
list.Sort((a, b) =>
|
||||
{
|
||||
if (a.Data.Priority == b.Data.Priority)
|
||||
{
|
||||
if (a.Data.ActionId == b.Data.ActionId)
|
||||
{
|
||||
return a.SourceId.CompareTo(b.SourceId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return a.Data.ActionId.CompareTo(b.Data.ActionId);
|
||||
}
|
||||
}
|
||||
|
||||
if (a.Data.Priority < b.Data.Priority)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
||||
{
|
||||
// hardcoded just for draggable area purposes
|
||||
const int columnCount = 3;
|
||||
const int rowCount = 4;
|
||||
|
||||
Vector2 size = new Vector2(
|
||||
_barConfig.Size.X * columnCount + Config.Padding.X * (columnCount - 1),
|
||||
_barConfig.Size.Y * rowCount + Config.Padding.Y * (rowCount - 1));
|
||||
|
||||
Vector2 pos = Config.GrowthDirection == PartyCooldownsGrowthDirection.Down ? Config.Position : Config.Position - new Vector2(0, size.Y);
|
||||
|
||||
return (new List<Vector2>() { pos + size / 2f }, new List<Vector2>() { size });
|
||||
}
|
||||
|
||||
public override void DrawChildren(Vector2 origin)
|
||||
{
|
||||
if (!Config.Enabled) { return; }
|
||||
|
||||
IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
|
||||
if (player == null) { return; }
|
||||
|
||||
float offset = 0;
|
||||
bool addedOffset = true;
|
||||
bool isVertical = Config.GrowthDirection == PartyCooldownsGrowthDirection.Up || Config.GrowthDirection == PartyCooldownsGrowthDirection.Down;
|
||||
|
||||
foreach (List<PartyCooldown> list in _cooldowns)
|
||||
{
|
||||
if (list.Count == 0) { continue; }
|
||||
|
||||
int i = 0;
|
||||
|
||||
foreach (PartyCooldown cooldown in list)
|
||||
{
|
||||
if (!addedOffset)
|
||||
{
|
||||
offset += isVertical ? Config.Padding.X + _barConfig.Size.X : Config.Padding.Y + _barConfig.Size.Y;
|
||||
addedOffset = true;
|
||||
}
|
||||
|
||||
string barId = _barConfig.ID + $"_{offset + i}";
|
||||
|
||||
// cooldown bar
|
||||
float cooldownTime = cooldown.CooldownTimeRemaining();
|
||||
float effectTime = cooldown.EffectTimeRemaining();
|
||||
|
||||
float max = effectTime > 0 ? cooldown.GetEffectDuration() : cooldown.GetCooldown();
|
||||
float current = effectTime > 0 ? effectTime : cooldownTime;
|
||||
|
||||
float sizeX = Math.Max(1, _barConfig.Size.X - _barConfig.Size.Y);
|
||||
Vector2 size = new Vector2(sizeX, _barConfig.Size.Y);
|
||||
|
||||
Vector2 pos;
|
||||
|
||||
if (isVertical)
|
||||
{
|
||||
int direction = Config.GrowthDirection == PartyCooldownsGrowthDirection.Down ? 1 : -1;
|
||||
pos = new Vector2(
|
||||
Config.Position.X + size.Y + offset - 1,
|
||||
Config.Position.Y + i * direction * size.Y + i * direction * Config.Padding.Y
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
int direction = Config.GrowthDirection == PartyCooldownsGrowthDirection.Right ? 1 : -1;
|
||||
pos = new Vector2(
|
||||
size.Y + Config.Position.X + i * direction * size.X + i * direction * size.Y + i * direction * Config.Padding.X,
|
||||
Config.Position.Y + offset - 1
|
||||
);
|
||||
}
|
||||
|
||||
if (_barConfig.ShowBar)
|
||||
{
|
||||
PluginConfigColor fillColor = effectTime > 0 ? _barConfig.AvailableColor : _barConfig.RechargingColor;
|
||||
PluginConfigColor bgColor = effectTime > 0 || cooldownTime == 0 ? _barConfig.AvailableBackgroundColor : _barConfig.RechargingBackgroundColor;
|
||||
|
||||
if (_barConfig.UseJobColors)
|
||||
{
|
||||
uint? jobId = GetJobId(cooldown, player);
|
||||
if (jobId.HasValue)
|
||||
{
|
||||
PluginConfigColor jobColor = GlobalColors.Instance.SafeColorForJobId(jobId.Value);
|
||||
PluginConfigColor bgJobColor = jobColor.WithAlpha(40f / 100f);
|
||||
PluginConfigColor rechargeJobColor = jobColor.WithAlpha(25f / 100f);
|
||||
PluginConfigColor nonActive = PluginConfigColor.FromHex(0x88000000);
|
||||
fillColor = effectTime > 0 ? jobColor : rechargeJobColor;
|
||||
bgColor = effectTime > 0 || cooldownTime == 0 ? bgJobColor : nonActive;
|
||||
}
|
||||
}
|
||||
|
||||
Rect background = new Rect(pos, size, bgColor);
|
||||
Rect fill = BarUtilities.GetFillRect(pos, size, _barConfig.FillDirection, fillColor, current, max);
|
||||
|
||||
BarHud bar = new BarHud(
|
||||
barId,
|
||||
_barConfig.DrawBorder,
|
||||
_barConfig.BorderColor,
|
||||
_barConfig.BorderThickness,
|
||||
DrawAnchor.TopLeft,
|
||||
current: current,
|
||||
max: max,
|
||||
barTextureName: _barConfig.BarTextureName,
|
||||
barTextureDrawMode: _barConfig.BarTextureDrawMode
|
||||
);
|
||||
|
||||
bar.SetBackground(background);
|
||||
bar.AddForegrounds(fill);
|
||||
|
||||
AddDrawActions(bar.GetDrawActions(origin, _barConfig.StrataLevel));
|
||||
}
|
||||
|
||||
// icon
|
||||
if (_barConfig.ShowIcon)
|
||||
{
|
||||
Vector2 iconPos = origin + new Vector2(pos.X - size.Y + 1, pos.Y);
|
||||
Vector2 iconSize = new Vector2(size.Y);
|
||||
bool recharging = effectTime == 0 && cooldownTime > 0;
|
||||
bool shouldDrawCooldown = ClipRectsHelper.Instance.GetClipRectForArea(iconPos, iconSize) == null;
|
||||
|
||||
AddDrawAction(_barConfig.StrataLevel, () =>
|
||||
{
|
||||
DrawHelper.DrawInWindow(barId + "_icon", iconPos, iconSize, false, (drawList) =>
|
||||
{
|
||||
uint color = recharging ? 0xAAFFFFFF : 0xFFFFFFFF;
|
||||
DrawHelper.DrawIcon(cooldown.Data.IconId, iconPos, iconSize, false, color, drawList);
|
||||
|
||||
// cooldown
|
||||
if (shouldDrawCooldown &&
|
||||
_barConfig.ShowIconCooldownAnimation &&
|
||||
effectTime == 0 &&
|
||||
cooldownTime > 0)
|
||||
{
|
||||
DrawHelper.DrawIconCooldown(iconPos, iconSize, cooldownTime, cooldown.GetCooldown(), drawList);
|
||||
}
|
||||
|
||||
if (_barConfig.DrawBorder)
|
||||
{
|
||||
bool active = effectTime > 0 && _barConfig.ChangeIconBorderWhenActive;
|
||||
uint iconBorderColor = active ? _barConfig.IconActiveBorderColor.Base : _barConfig.BorderColor.Base;
|
||||
int thickness = active ? _barConfig.IconActiveBorderThickness : _barConfig.BorderThickness;
|
||||
drawList.AddRect(iconPos, iconPos + iconSize, iconBorderColor, 0, ImDrawFlags.None, thickness);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// name
|
||||
PluginConfigColor? labelColor = effectTime > 0 && _barConfig.ChangeLabelsColorWhenActive ? _barConfig.LabelsActiveColor : null;
|
||||
|
||||
ICharacter? character = cooldown.Member?.Character;
|
||||
if (character == null && cooldown.SourceId == player.GameObjectId)
|
||||
{
|
||||
character = player;
|
||||
}
|
||||
|
||||
Vector2 labelPos = origin + pos;
|
||||
AddDrawAction(_barConfig.NameLabel.StrataLevel, () =>
|
||||
{
|
||||
PluginConfigColor realColor = _barConfig.NameLabel.Color;
|
||||
_barConfig.NameLabel.Color = labelColor ?? realColor;
|
||||
|
||||
string? name = character == null ? "Fake Name" : null;
|
||||
_nameLabelHud.Draw(labelPos, size, character, name);
|
||||
|
||||
_barConfig.NameLabel.Color = realColor;
|
||||
});
|
||||
|
||||
// time
|
||||
AddDrawAction(_barConfig.TimeLabel.StrataLevel, () =>
|
||||
{
|
||||
PluginConfigColor realColor = _barConfig.TimeLabel.Color;
|
||||
_barConfig.TimeLabel.Color = labelColor ?? realColor;
|
||||
_barConfig.TimeLabel.SetText("");
|
||||
|
||||
if (effectTime > 0)
|
||||
{
|
||||
if (_barConfig.TimeLabel.ShowEffectDuration)
|
||||
{
|
||||
_barConfig.TimeLabel.SetValue(effectTime);
|
||||
}
|
||||
}
|
||||
else if (cooldownTime > 0)
|
||||
{
|
||||
if (_barConfig.TimeLabel.ShowRemainingCooldown)
|
||||
{
|
||||
_barConfig.TimeLabel.SetText(Utils.DurationToString(cooldownTime, _barConfig.TimeLabel.NumberFormat));
|
||||
}
|
||||
}
|
||||
|
||||
_timeLabelHud.Draw(labelPos, size, character);
|
||||
_barConfig.TimeLabel.Color = realColor;
|
||||
});
|
||||
|
||||
// tooltip
|
||||
pos = origin + new Vector2(pos.X - size.Y + 1, pos.Y);
|
||||
if (Config.ShowTooltips && ImGui.IsMouseHoveringRect(pos, pos + _barConfig.Size))
|
||||
{
|
||||
TooltipsHelper.Instance.ShowTooltipOnCursor(
|
||||
cooldown.TooltipText(),
|
||||
cooldown.Data.Name,
|
||||
cooldown.Data.ActionId
|
||||
);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
addedOffset = false;
|
||||
}
|
||||
}
|
||||
|
||||
private uint? GetJobId(PartyCooldown cooldown, IPlayerCharacter player)
|
||||
{
|
||||
uint jobId = cooldown.Data.JobId;
|
||||
if (jobId != 0) { return jobId; }
|
||||
|
||||
if (cooldown.Member != null) { return cooldown.Member.JobId; }
|
||||
|
||||
if (cooldown.SourceId == player.GameObjectId) { return player.ClassJob.RowId; }
|
||||
|
||||
ICharacter? chara = Plugin.ObjectTable.SearchById(cooldown.SourceId) as ICharacter;
|
||||
return chara?.ClassJob.RowId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Hooking;
|
||||
using HSUI.Config;
|
||||
using HSUI.Helpers;
|
||||
using HSUI.Interface.Party;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using static FFXIVClientStructs.FFXIV.Client.Game.Character.ActionEffectHandler;
|
||||
|
||||
namespace HSUI.Interface.PartyCooldowns
|
||||
{
|
||||
public unsafe class PartyCooldownsManager
|
||||
{
|
||||
#region Singleton
|
||||
public static PartyCooldownsManager Instance { get; private set; } = null!;
|
||||
private PartyCooldownsConfig _config = null!;
|
||||
private PartyCooldownsDataConfig _dataConfig = null!;
|
||||
|
||||
private PartyCooldownsManager()
|
||||
{
|
||||
try
|
||||
{
|
||||
_onActionUsedHook = Plugin.GameInteropProvider.HookFromAddress<ActionEffectHandler.Delegates.Receive>(
|
||||
ActionEffectHandler.MemberFunctionPointers.Receive,
|
||||
OnActionUsed
|
||||
);
|
||||
_onActionUsedHook?.Enable();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Plugin.Logger.Error("PartyCooldowns OnActionUsed Hook failed!!!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_actorControlHook = Plugin.GameInteropProvider.HookFromSignature<ActorControlDelegate>(
|
||||
"E8 ?? ?? ?? ?? 0F B7 0B 83 E9 64",
|
||||
OnActorControl
|
||||
);
|
||||
_actorControlHook?.Enable();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Plugin.Logger.Error("PartyCooldowns OnActorControl Hook failed!!!");
|
||||
}
|
||||
|
||||
PartyManager.Instance.MembersChangedEvent += OnMembersChanged;
|
||||
ConfigurationManager.Instance.ResetEvent += OnConfigReset;
|
||||
Plugin.JobChangedEvent += OnJobChanged;
|
||||
Plugin.ClientState.TerritoryChanged += OnTerritoryChanged;
|
||||
|
||||
ConfigReset(ConfigurationManager.Instance, false);
|
||||
UpdatePreview();
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Instance = new PartyCooldownsManager();
|
||||
}
|
||||
|
||||
~PartyCooldownsManager()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_onActionUsedHook?.Disable();
|
||||
_onActionUsedHook?.Dispose();
|
||||
|
||||
_actorControlHook?.Disable();
|
||||
_actorControlHook?.Dispose();
|
||||
|
||||
PartyManager.Instance.MembersChangedEvent -= OnMembersChanged;
|
||||
Plugin.JobChangedEvent -= OnJobChanged;
|
||||
_config.ValueChangeEvent -= OnConfigPropertyChanged;
|
||||
_dataConfig.CooldownsDataEnabledChangedEvent -= OnCooldownEnabledChanged;
|
||||
Plugin.ClientState.TerritoryChanged -= OnTerritoryChanged;
|
||||
|
||||
Instance = null!;
|
||||
}
|
||||
|
||||
private void OnConfigReset(ConfigurationManager sender)
|
||||
{
|
||||
ConfigReset(sender);
|
||||
}
|
||||
|
||||
private void ConfigReset(ConfigurationManager sender, bool forceUpdate = true)
|
||||
{
|
||||
if (_config != null)
|
||||
{
|
||||
_config.ValueChangeEvent -= OnConfigPropertyChanged;
|
||||
}
|
||||
|
||||
_config = sender.GetConfigObject<PartyCooldownsConfig>();
|
||||
_config.ValueChangeEvent += OnConfigPropertyChanged;
|
||||
|
||||
if (_dataConfig != null)
|
||||
{
|
||||
_dataConfig.CooldownsDataEnabledChangedEvent -= OnCooldownEnabledChanged;
|
||||
}
|
||||
|
||||
_dataConfig = sender.GetConfigObject<PartyCooldownsDataConfig>();
|
||||
_dataConfig.CooldownsDataEnabledChangedEvent += OnCooldownEnabledChanged;
|
||||
_dataConfig.UpdateDataIfNeeded();
|
||||
|
||||
if (forceUpdate)
|
||||
{
|
||||
ForcedUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Singleton
|
||||
|
||||
private Hook<ActionEffectHandler.Delegates.Receive>? _onActionUsedHook;
|
||||
|
||||
private delegate void ActorControlDelegate(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12);
|
||||
private Hook<ActorControlDelegate>? _actorControlHook;
|
||||
|
||||
private Dictionary<uint, Dictionary<uint, PartyCooldown>>? _oldMap;
|
||||
private Dictionary<uint, Dictionary<uint, PartyCooldown>> _cooldownsMap = new Dictionary<uint, Dictionary<uint, PartyCooldown>>();
|
||||
public IReadOnlyDictionary<uint, Dictionary<uint, PartyCooldown>> CooldownsMap => _cooldownsMap;
|
||||
|
||||
private Dictionary<uint, double> _technicalStepMap = new Dictionary<uint, double>();
|
||||
|
||||
public delegate void PartyCooldownsChangedEventHandler(PartyCooldownsManager sender);
|
||||
public event PartyCooldownsChangedEventHandler? CooldownsChangedEvent;
|
||||
|
||||
private bool _wasInDuty = false;
|
||||
|
||||
private void OnActorControl(uint entityId, uint type, uint buffID, uint direct, uint actionId, uint sourceId, uint arg7, uint arg8, uint arg9, uint arg10, ulong targetId, byte arg12)
|
||||
{
|
||||
_actorControlHook?.Original(entityId, type, buffID, direct, actionId, sourceId, arg7, arg8, arg9, arg10, targetId, arg12);
|
||||
|
||||
// detect wipe fadeouts (not 100% reliable but good enough)
|
||||
if (type == 0x4000000F)
|
||||
{
|
||||
ResetCooldowns();
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetCooldowns()
|
||||
{
|
||||
foreach (uint actorId in _cooldownsMap.Keys)
|
||||
{
|
||||
foreach (PartyCooldown cooldown in _cooldownsMap[actorId].Values)
|
||||
{
|
||||
cooldown.LastTimeUsed = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void OnActionUsed(uint actorId, Character* casterPtr, Vector3* targetPos, Header* header, TargetEffects* effects, GameObjectId* targetEntityIds)
|
||||
{
|
||||
_onActionUsedHook?.Original(actorId, casterPtr, targetPos, header, effects, targetEntityIds);
|
||||
|
||||
// check if its an action
|
||||
if ((ActionType)header->ActionType != ActionType.Action ) { return; }
|
||||
|
||||
// check if its a member in the party
|
||||
if (!_cooldownsMap.ContainsKey(actorId))
|
||||
{
|
||||
// check if its a party member's pet
|
||||
IGameObject? actor = Plugin.ObjectTable.SearchById(actorId);
|
||||
|
||||
if (actor is IBattleNpc battleNpc && _cooldownsMap.ContainsKey(battleNpc.OwnerId))
|
||||
{
|
||||
actorId = battleNpc.OwnerId;
|
||||
}
|
||||
else
|
||||
{
|
||||
actorId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (actorId <= 0) { return; }
|
||||
|
||||
uint actionID = header->ActionId;
|
||||
|
||||
// special case for starry muse > set id to scenic muse
|
||||
if (actionID == 34675)
|
||||
{
|
||||
actionID = 35349;
|
||||
}
|
||||
|
||||
// special case for technical step / finish
|
||||
// we detect when technical step is pressed and save the time
|
||||
// so we can properly calculate the cooldown once finish is pressed
|
||||
if (actionID == 16193 || actionID == 16194 || actionID == 16195 || actionID == 16196)
|
||||
{
|
||||
actionID = 16004;
|
||||
}
|
||||
|
||||
if (actionID == 15998)
|
||||
{
|
||||
_technicalStepMap[actorId] = ImGui.GetTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if its an action we track
|
||||
if (_cooldownsMap[actorId].TryGetValue(actionID, out PartyCooldown? cooldown) && cooldown != null)
|
||||
{
|
||||
// if its technical finish, we set the cooldown start time to
|
||||
// the time when step was pressed
|
||||
if (_technicalStepMap.TryGetValue(actorId, out double stepStartTime) && actionID == 16004)
|
||||
{
|
||||
cooldown.OverridenCooldownStartTime = stepStartTime;
|
||||
_technicalStepMap.Remove(actorId);
|
||||
}
|
||||
|
||||
double now = ImGui.GetTime();
|
||||
cooldown.LastTimeUsed = now;
|
||||
cooldown.IgnoreNextUse = false;
|
||||
|
||||
foreach (uint id in cooldown.Data.SharedActionIds)
|
||||
{
|
||||
if (_cooldownsMap[actorId].TryGetValue(id, out PartyCooldown? sharedCooldown) && sharedCooldown != null)
|
||||
{
|
||||
sharedCooldown.LastTimeUsed = now;
|
||||
sharedCooldown.IgnoreNextUse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ForcedUpdate()
|
||||
{
|
||||
OnMembersChanged(PartyManager.Instance);
|
||||
}
|
||||
|
||||
private void OnMembersChanged(PartyManager sender)
|
||||
{
|
||||
Plugin.Framework.RunOnFrameworkThread(() =>
|
||||
{
|
||||
if (sender.Previewing || _config.Preview) { return; }
|
||||
|
||||
_cooldownsMap.Clear();
|
||||
|
||||
if (_config.ShowOnlyInDuties && !Plugin.Condition[ConditionFlag.BoundByDuty])
|
||||
{
|
||||
CooldownsChangedEvent?.Invoke(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// show when solo
|
||||
if (sender.IsSoloParty() || sender.MemberCount == 0)
|
||||
{
|
||||
var player = Plugin.ObjectTable.LocalPlayer;
|
||||
if (_config.ShowWhenSolo && player != null)
|
||||
{
|
||||
_cooldownsMap.Add((uint)player.GameObjectId, CooldownsForMember((uint)player.GameObjectId, player.ClassJob.RowId, player.Level, null));
|
||||
}
|
||||
}
|
||||
else if (!_config.ShowOnlyInDuties || Plugin.Condition[ConditionFlag.BoundByDuty])
|
||||
{
|
||||
// add new members
|
||||
foreach (IPartyFramesMember member in sender.GroupMembers)
|
||||
{
|
||||
if (member.ObjectId > 0)
|
||||
{
|
||||
_cooldownsMap.Add(member.ObjectId, CooldownsForMember(member));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CooldownsChangedEvent?.Invoke(this);
|
||||
});
|
||||
}
|
||||
|
||||
private Dictionary<uint, PartyCooldown> CooldownsForMember(IPartyFramesMember member)
|
||||
{
|
||||
return CooldownsForMember(member.ObjectId, member.JobId, member.Level, member);
|
||||
}
|
||||
|
||||
private Dictionary<uint, PartyCooldown> CooldownsForMember(uint objectId, uint jobId, uint level, IPartyFramesMember? member)
|
||||
{
|
||||
Dictionary<uint, PartyCooldown> cooldowns = new Dictionary<uint, PartyCooldown>();
|
||||
|
||||
foreach (PartyCooldownData data in _dataConfig.Cooldowns)
|
||||
{
|
||||
if (data.EnabledV2 != PartyCooldownEnabled.Disabled &&
|
||||
level >= data.RequiredLevel &&
|
||||
(data.DisabledAfterLevel == 0 || level < data.DisabledAfterLevel) &&
|
||||
data.IsUsableBy(jobId) &&
|
||||
!data.ExcludedJobIds.Contains(jobId))
|
||||
{
|
||||
cooldowns.Add(data.ActionId, new PartyCooldown(data, objectId, level, member));
|
||||
}
|
||||
}
|
||||
|
||||
return cooldowns;
|
||||
}
|
||||
|
||||
#region events
|
||||
private void OnConfigPropertyChanged(object sender, OnChangeBaseArgs args)
|
||||
{
|
||||
if (args.PropertyName == "Preview")
|
||||
{
|
||||
UpdatePreview();
|
||||
}
|
||||
else if (args.PropertyName == "ShowWhenSolo" && PartyManager.Instance?.MemberCount == 0)
|
||||
{
|
||||
OnMembersChanged(PartyManager.Instance);
|
||||
}
|
||||
else if (args.PropertyName == "ShowOnlyInDuties" && PartyManager.Instance != null)
|
||||
{
|
||||
OnMembersChanged(PartyManager.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJobChanged(uint jobId)
|
||||
{
|
||||
ForcedUpdate();
|
||||
}
|
||||
|
||||
private void OnCooldownEnabledChanged(PartyCooldownsDataConfig config)
|
||||
{
|
||||
ForcedUpdate();
|
||||
}
|
||||
|
||||
private void OnTerritoryChanged(ushort territoryId)
|
||||
{
|
||||
bool isInDuty = Plugin.Condition[ConditionFlag.BoundByDuty];
|
||||
if (_config.ShowOnlyInDuties && _wasInDuty != isInDuty)
|
||||
{
|
||||
ForcedUpdate();
|
||||
}
|
||||
|
||||
_wasInDuty = isInDuty;
|
||||
}
|
||||
|
||||
public void UpdatePreview()
|
||||
{
|
||||
if (!_config.Preview)
|
||||
{
|
||||
if (_oldMap != null)
|
||||
{
|
||||
_cooldownsMap = _oldMap;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cooldownsMap.Clear();
|
||||
}
|
||||
|
||||
if (PartyManager.Instance.Previewing)
|
||||
{
|
||||
CooldownsChangedEvent?.Invoke(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnMembersChanged(PartyManager.Instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (PartyManager.Instance?.Previewing == false)
|
||||
{
|
||||
_oldMap = _cooldownsMap;
|
||||
}
|
||||
|
||||
_cooldownsMap.Clear();
|
||||
Random RNG = new Random((int)ImGui.GetTime());
|
||||
|
||||
for (uint i = 1; i < 9; i++)
|
||||
{
|
||||
Dictionary<uint, PartyCooldown> cooldowns = new Dictionary<uint, PartyCooldown>();
|
||||
|
||||
JobRoles role = i < 3 ? JobRoles.Tank : (i < 5 ? JobRoles.Healer : JobRoles.Unknown);
|
||||
role = role == JobRoles.Unknown ? JobRoles.DPSMelee + RNG.Next(3) : role;
|
||||
int jobCount = JobsHelper.JobsByRole[role].Count;
|
||||
int jobIndex = RNG.Next(jobCount);
|
||||
uint jobId = JobsHelper.JobsByRole[role][jobIndex];
|
||||
|
||||
_cooldownsMap.Add(i, CooldownsForMember(i, jobId, 90, null));
|
||||
|
||||
foreach (PartyCooldown cooldown in _cooldownsMap[i].Values)
|
||||
{
|
||||
int rng = RNG.Next(100);
|
||||
if (rng > 80)
|
||||
{
|
||||
cooldown.LastTimeUsed = ImGui.GetTime() - 30;
|
||||
}
|
||||
else if (rng > 50)
|
||||
{
|
||||
cooldown.LastTimeUsed = ImGui.GetTime() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CooldownsChangedEvent?.Invoke(this);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user