+408
@@ -0,0 +1,408 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Interface.Textures;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Action = Lumina.Excel.Sheets.Action;
|
||||
|
||||
namespace ConfigurableCombo;
|
||||
|
||||
public class ConfigWindow : Window
|
||||
{
|
||||
private const float ActionIconSize = 24f;
|
||||
|
||||
private uint _selectedJobId;
|
||||
private JobListKind _jobListKind = JobListKind.Combat;
|
||||
private string _newComboName = "New Combo";
|
||||
private UserComboDefinition? _editingCombo;
|
||||
private int _editingActionIndex = -1;
|
||||
private string _editingActionIdInput = "";
|
||||
private UserComboDefinition? _addFromJobTargetCombo;
|
||||
private string _jobActionFilter = "";
|
||||
private bool _windowWasOpen;
|
||||
|
||||
public ConfigWindow() : base("ConfigurableCombo")
|
||||
{
|
||||
Size = new Vector2(500, 450);
|
||||
SizeCondition = ImGuiCond.FirstUseEver;
|
||||
}
|
||||
|
||||
/// <summary>Set job dropdown to player's current job if it's in the current job-type list; otherwise keep selection if valid or Unspecified.</summary>
|
||||
private void SyncJobDropdownToPlayerOrSelection()
|
||||
{
|
||||
var list = JobActionsHelper.GetJobList(_jobListKind);
|
||||
var playerJobId = Service.ClientState.LocalPlayer?.ClassJob.RowId ?? 0u;
|
||||
if (playerJobId != 0 && list.Any(x => x.JobId == playerJobId))
|
||||
_selectedJobId = playerJobId;
|
||||
else if (list.All(x => x.JobId != _selectedJobId))
|
||||
_selectedJobId = 0;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (!Service.Configuration.EnablePlugin)
|
||||
{
|
||||
ImGui.TextColored(new Vector4(1, 0.4f, 0.4f, 1), "Plugin is disabled. Enable below to use combo replacement.");
|
||||
}
|
||||
|
||||
bool enablePlugin = Service.Configuration.EnablePlugin;
|
||||
if (ImGui.Checkbox("Enable plugin", ref enablePlugin))
|
||||
{
|
||||
Service.Configuration.EnablePlugin = enablePlugin;
|
||||
Service.Configuration.Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(new Vector4(0.6f, 0.7f, 0.8f, 1f), "Tip: Use /ccombo to open this window anytime.");
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
// When window just opened, select the player's current job so their combos are shown by default
|
||||
if (IsOpen && !_windowWasOpen)
|
||||
{
|
||||
_windowWasOpen = true;
|
||||
var player = Service.ClientState.LocalPlayer;
|
||||
var currentJobId = player?.ClassJob.RowId ?? 0u;
|
||||
if (currentJobId != 0)
|
||||
{
|
||||
var combatList = JobActionsHelper.GetJobList(JobListKind.Combat);
|
||||
var craftList = JobActionsHelper.GetJobList(JobListKind.CraftingGathering);
|
||||
if (combatList.Any(x => x.JobId == currentJobId))
|
||||
{
|
||||
_jobListKind = JobListKind.Combat;
|
||||
_selectedJobId = currentJobId;
|
||||
}
|
||||
else if (craftList.Any(x => x.JobId == currentJobId))
|
||||
{
|
||||
_jobListKind = JobListKind.CraftingGathering;
|
||||
_selectedJobId = currentJobId;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!IsOpen)
|
||||
{
|
||||
_windowWasOpen = false;
|
||||
}
|
||||
|
||||
// Job type toggle: Combat vs Crafting & Gathering
|
||||
ImGui.Text("Job type:");
|
||||
ImGui.SameLine();
|
||||
bool showCombatJobs = _jobListKind == JobListKind.Combat;
|
||||
if (ImGui.Checkbox("Combat (DoW/DoM)", ref showCombatJobs))
|
||||
{
|
||||
_jobListKind = showCombatJobs ? JobListKind.Combat : JobListKind.CraftingGathering;
|
||||
SyncJobDropdownToPlayerOrSelection();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
bool showCraftGather = _jobListKind == JobListKind.CraftingGathering;
|
||||
if (ImGui.Checkbox("Crafting & Gathering (DoH/DoL)", ref showCraftGather))
|
||||
{
|
||||
_jobListKind = showCraftGather ? JobListKind.CraftingGathering : JobListKind.Combat;
|
||||
SyncJobDropdownToPlayerOrSelection();
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Switch between battle jobs and crafters/gatherers. Combos are per job.");
|
||||
|
||||
ImGui.Text("Job:");
|
||||
ImGui.SameLine();
|
||||
var jobList = JobActionsHelper.GetJobList(_jobListKind);
|
||||
var currentJobIndex = jobList.FindIndex(x => x.JobId == _selectedJobId);
|
||||
if (currentJobIndex < 0) currentJobIndex = 0;
|
||||
ImGui.SetNextItemWidth(180);
|
||||
if (ImGui.BeginCombo("##job", jobList[currentJobIndex].Name))
|
||||
{
|
||||
for (int j = 0; j < jobList.Count; j++)
|
||||
{
|
||||
var (jobId, name) = jobList[j];
|
||||
if (ImGui.Selectable(name, _selectedJobId == jobId))
|
||||
{
|
||||
_selectedJobId = jobId;
|
||||
}
|
||||
}
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Combos below are for this job. New combos will be saved to this job.");
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Text("Define action sequences. Put the first action on your hotbar; each press advances the sequence.");
|
||||
ImGui.TextColored(new Vector4(0.7f, 0.8f, 1f, 1f),
|
||||
"Sequence resets to step 1 after the last step or after any other action.");
|
||||
ImGui.Spacing();
|
||||
ImGui.TextWrapped("Example (NIN 1-2 with 3a/3b on separate keys): Add a combo with actions 2240, 2242 (Spinning Edge, Gust Slash). Put 2240 on one hotbar slot. Use that slot for 1->2; put Aeolian Edge (2255) and Armor Crush (3563) on other keybinds.");
|
||||
ImGui.Spacing();
|
||||
|
||||
// Add new combo (for selected job)
|
||||
ImGui.SetNextItemWidth(180);
|
||||
ImGui.InputTextWithHint("##name", "Combo name", ref _newComboName, 64);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Add combo"))
|
||||
{
|
||||
Service.Configuration.UserCombos.Add(new UserComboDefinition { Name = _newComboName, JobId = _selectedJobId });
|
||||
Service.Configuration.Save();
|
||||
_newComboName = "New Combo";
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
// List combos for selected job only
|
||||
for (int i = 0; i < Service.Configuration.UserCombos.Count; i++)
|
||||
{
|
||||
var combo = Service.Configuration.UserCombos[i];
|
||||
if (combo.JobId != _selectedJobId) continue;
|
||||
DrawCombo(combo, i);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCombo(UserComboDefinition combo, int index)
|
||||
{
|
||||
using (var id = ImRaii.PushId(index))
|
||||
{
|
||||
bool enabled = combo.Enabled;
|
||||
if (ImGui.Checkbox("##enabled", ref enabled))
|
||||
{
|
||||
combo.Enabled = enabled;
|
||||
Service.Configuration.Save();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
|
||||
bool open = ImGui.TreeNodeEx($"{combo.Name} ({combo.ActionIds.Count} actions)", ImGuiTreeNodeFlags.DefaultOpen);
|
||||
ImGui.SameLine(ImGui.GetContentRegionAvail().X - 80);
|
||||
if (ImGui.SmallButton("Delete"))
|
||||
{
|
||||
Service.Configuration.UserCombos.RemoveAt(index);
|
||||
Service.Configuration.Save();
|
||||
return;
|
||||
}
|
||||
|
||||
if (open)
|
||||
{
|
||||
ImGui.Indent();
|
||||
|
||||
// Name
|
||||
string name = combo.Name;
|
||||
if (ImGui.InputText("Name", ref name, 64))
|
||||
{
|
||||
combo.Name = name;
|
||||
Service.Configuration.Save();
|
||||
}
|
||||
|
||||
// Action list
|
||||
ImGui.Text("Action sequence (first = hotbar slot; order: 1, 2, 3a/3b on other keys...)");
|
||||
ImGui.TextColored(new Vector4(0.9f, 0.85f, 0.4f, 1f), "First action must be the combo starter (e.g. Spinning Edge for NIN), or the icon/state won't advance.");
|
||||
for (int a = 0; a < combo.ActionIds.Count; a++)
|
||||
{
|
||||
uint aid = combo.ActionIds[a];
|
||||
string label = GetActionName(aid);
|
||||
if (ImGui.SmallButton($"{label} (##{a})"))
|
||||
{
|
||||
_editingCombo = combo;
|
||||
_editingActionIndex = a;
|
||||
_editingActionIdInput = aid.ToString();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton($"X##del{a}"))
|
||||
{
|
||||
combo.ActionIds.RemoveAt(a);
|
||||
Service.Configuration.Save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_editingCombo == combo && _editingActionIndex >= 0)
|
||||
{
|
||||
ImGui.SetNextItemWidth(120);
|
||||
if (ImGui.InputText("Action ID", ref _editingActionIdInput, 16, ImGuiInputTextFlags.CharsDecimal))
|
||||
{ }
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("OK"))
|
||||
{
|
||||
if (uint.TryParse(_editingActionIdInput, out uint newId))
|
||||
{
|
||||
if (_editingActionIndex < combo.ActionIds.Count)
|
||||
combo.ActionIds[_editingActionIndex] = newId;
|
||||
else
|
||||
combo.ActionIds.Add(newId);
|
||||
Service.Configuration.Save();
|
||||
}
|
||||
_editingCombo = null;
|
||||
_editingActionIndex = -1;
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Cancel"))
|
||||
{
|
||||
_editingCombo = null;
|
||||
_editingActionIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Text("Add action:");
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Action browser..."))
|
||||
{
|
||||
_addFromJobTargetCombo = combo;
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Browse Job-Related, PvP, or Pet/Other actions. Click an action to add to sequence.");
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Add by ID"))
|
||||
{
|
||||
_editingCombo = combo;
|
||||
_editingActionIndex = combo.ActionIds.Count;
|
||||
_editingActionIdInput = "";
|
||||
}
|
||||
|
||||
if (_addFromJobTargetCombo == combo)
|
||||
{
|
||||
DrawActionBrowser(combo);
|
||||
}
|
||||
|
||||
ImGui.Unindent();
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawActionBrowser(UserComboDefinition targetCombo)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.ChildRounding, 5f))
|
||||
using (ImRaii.Child("ActionBrowser", new Vector2(-1, 340), true))
|
||||
{
|
||||
ImGui.TextColored(new Vector4(0.7f, 0.9f, 1f, 1f), "Action browser — select a category, then click an action to add.");
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("Close"))
|
||||
{
|
||||
_addFromJobTargetCombo = null;
|
||||
return;
|
||||
}
|
||||
ImGui.SetNextItemWidth(220f);
|
||||
ImGui.InputTextWithHint("##filter", "Filter by name or ID...", ref _jobActionFilter, 64);
|
||||
ImGui.Spacing();
|
||||
|
||||
var filter = _jobActionFilter.AsSpan().Trim().ToString();
|
||||
bool hasFilter = !string.IsNullOrEmpty(filter);
|
||||
|
||||
if (ImGui.BeginTabBar("ActionBrowserTabs"))
|
||||
{
|
||||
if (ImGui.BeginTabItem("Job-Related"))
|
||||
{
|
||||
var actions = JobActionsHelper.GetActionsForJob(_selectedJobId);
|
||||
DrawActionTable(actions, targetCombo, filter, hasFilter);
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
if (ImGui.BeginTabItem("PvP Actions"))
|
||||
{
|
||||
var actions = JobActionsHelper.GetPvPActions(_selectedJobId);
|
||||
DrawActionTable(actions, targetCombo, filter, hasFilter);
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
if (ImGui.BeginTabItem("Role Actions"))
|
||||
{
|
||||
var actions = JobActionsHelper.GetRoleActions(_selectedJobId);
|
||||
DrawActionTable(actions, targetCombo, filter, hasFilter);
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
if (ImGui.BeginTabItem("Consumable Items"))
|
||||
{
|
||||
var actions = JobActionsHelper.GetConsumableActions();
|
||||
DrawActionTable(actions, targetCombo, filter, hasFilter);
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
ImGui.EndTabBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawActionTable(
|
||||
List<(uint ActionId, string Name, uint IconId)> actions,
|
||||
UserComboDefinition targetCombo,
|
||||
string filter,
|
||||
bool hasFilter)
|
||||
{
|
||||
if (actions.Count == 0)
|
||||
{
|
||||
ImGui.TextDisabled("No actions in this category.");
|
||||
return;
|
||||
}
|
||||
if (ImGui.BeginTable("ActionBrowserTable", 3, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingStretchProp, new Vector2(-1, -1)))
|
||||
{
|
||||
ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthFixed, ActionIconSize + 8);
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed, 48);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
foreach (var (actionId, name, iconId) in actions)
|
||||
{
|
||||
if (hasFilter)
|
||||
{
|
||||
if (name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0 &&
|
||||
actionId.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
if (iconId > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tex = Service.TextureProvider.GetFromGameIcon(new GameIconLookup(iconId));
|
||||
var wrap = tex.GetWrapOrEmpty();
|
||||
if (wrap != null)
|
||||
ImGui.Image(wrap.Handle, new Vector2(ActionIconSize, ActionIconSize));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Selectable($"{name}##{actionId}", false))
|
||||
{
|
||||
targetCombo.ActionIds.Add(actionId);
|
||||
Service.Configuration.Save();
|
||||
_addFromJobTargetCombo = null;
|
||||
ImGui.EndTable();
|
||||
return;
|
||||
}
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(actionId.ToString());
|
||||
}
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetActionName(uint actionId)
|
||||
{
|
||||
if (actionId == 0) return "0";
|
||||
try
|
||||
{
|
||||
var sheet = Service.DataManager.GetExcelSheet<Action>(ClientLanguage.English);
|
||||
var row = sheet?.GetRow(actionId);
|
||||
if (row != null)
|
||||
{
|
||||
var nameStr = (row as dynamic)?.Name?.ToString() ?? (row as dynamic)?.Singular?.ToString();
|
||||
if (!string.IsNullOrEmpty(nameStr))
|
||||
return $"{nameStr} ({actionId})";
|
||||
}
|
||||
// Fallback: may be an item ID (e.g. from Consumable Items tab)
|
||||
var itemSheet = Service.DataManager.GetExcelSheet<Lumina.Excel.Sheets.Item>(ClientLanguage.English);
|
||||
var itemRow = itemSheet?.GetRow(actionId);
|
||||
if (itemRow != null)
|
||||
{
|
||||
var itemName = (itemRow as dynamic)?.Name?.ToString() ?? (itemRow as dynamic)?.Singular?.ToString();
|
||||
if (!string.IsNullOrEmpty(itemName))
|
||||
return $"{itemName} [Item] ({actionId})";
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return actionId.ToString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user