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

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-01-30 23:52:46 -05:00
commit f37369cdda
202 changed files with 40137 additions and 0 deletions
@@ -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
}
}