f3e10f27d2
- Add controller hotbars: 8 cross bars (L2/R2 style), separate from normal hotbars 1-8 - Controller bar slot data stored in config (not game StandardHotbars) so layouts can differ per mode - Drag-and-drop on controller bars: from game, shift+drag rearrange, release outside to clear - Independent controller bar keybinds with modifier+trigger combinations (e.g. L2+South) - Optional 'Sync bar mode with game client': follow Character Config Mouse/Gamepad toggle (PadMode) - Clone/copy actions: normal hotbars ↔ controller bars - Restore controller bar layout button; deploy to devPlugins on Release build Made-with: Cursor
511 lines
26 KiB
C#
511 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using Dalamud.Game.ClientState.Objects.Types;
|
|
using HSUI.Config;
|
|
using HSUI.Enums;
|
|
using HSUI.Helpers;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface.Utility;
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
using static FFXIVClientStructs.FFXIV.Client.Game.ActionManager;
|
|
using LuminaAction = Lumina.Excel.Sheets.Action;
|
|
|
|
namespace HSUI.Interface.GeneralElements
|
|
{
|
|
/// <summary>Draws one controller cross hotbar (8 slots in cross layout). Visible only when controller hotbars are enabled and trigger state matches.</summary>
|
|
public class CrossBarHud : DraggableHudElement, IHudElementWithActor, IHudElementWithVisibilityConfig
|
|
{
|
|
private ControllerBarConfig Config => (ControllerBarConfig)_config;
|
|
|
|
private bool _wasDragging;
|
|
private uint _dragId;
|
|
private RaptureHotbarModule.HotbarSlotType _dragSlotType;
|
|
private int _pendingSlotIconIndex = -1;
|
|
private uint _pendingSlotIconId;
|
|
private int _pendingSlotIconFramesLeft;
|
|
private int _lastFrameDroppedOnSlot = -1;
|
|
private bool _suppressSlotClicksAfterDragRelease;
|
|
|
|
public IGameObject? Actor { get; set; }
|
|
|
|
public VisibilityConfig VisibilityConfig => Config.VisibilityConfig;
|
|
|
|
public CrossBarHud(ControllerBarConfig config, string displayName) : base(config, displayName) { }
|
|
|
|
protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes()
|
|
{
|
|
var size = BarSize();
|
|
return (new List<Vector2> { Config.Position }, new List<Vector2> { size });
|
|
}
|
|
|
|
private Vector2 BarSize()
|
|
{
|
|
// Two crosses side by side: each cross is 3 cells (slot+pad); gap between crosses
|
|
float cellW = Config.SlotSize.X + Config.SlotPadding;
|
|
float cellH = Config.SlotSize.Y + Config.SlotPadding;
|
|
float gap = Config.SlotPadding * 2;
|
|
float w = 3 * cellW + gap + 3 * cellW;
|
|
float h = 3 * cellH;
|
|
return new Vector2(w, h);
|
|
}
|
|
|
|
/// <summary>Cross layout like the game: left cross (slots 0-3) = top/left/right/bottom around center; right cross (slots 4-7) same.</summary>
|
|
private Vector2 GetSlotPositionCross(int slotIndex, Vector2 slotSize, int pad)
|
|
{
|
|
float cellW = slotSize.X + pad;
|
|
float cellH = slotSize.Y + pad;
|
|
float leftCrossWidth = 3 * cellW;
|
|
float gap = pad * 2f;
|
|
|
|
if (slotIndex < 4)
|
|
{
|
|
// Left cross: 3x3 grid, slots at (1,0), (0,1), (2,1), (1,2)
|
|
int col = slotIndex switch { 0 => 1, 1 => 0, 2 => 2, _ => 1 };
|
|
int row = slotIndex switch { 0 => 0, 1 => 1, 2 => 1, _ => 2 };
|
|
return new Vector2(col * cellW, row * cellH);
|
|
}
|
|
else
|
|
{
|
|
// Right cross: same 3x3 pattern, offset by left cross + gap
|
|
int i = slotIndex - 4;
|
|
int col = i switch { 0 => 1, 1 => 0, 2 => 2, _ => 1 };
|
|
int row = i switch { 0 => 0, 1 => 1, 2 => 1, _ => 2 };
|
|
return new Vector2(leftCrossWidth + gap + col * cellW, row * cellH);
|
|
}
|
|
}
|
|
|
|
private const float DropMargin = 24f;
|
|
|
|
/// <summary>Hit test: returns slot index 0-7 if pos is over a cross slot, else -1.</summary>
|
|
private int GetSlotIndexAtPositionCross(Vector2 topLeft, Vector2 slotSize, int pad, Vector2 pos)
|
|
{
|
|
Vector2 barSize = new Vector2(
|
|
3 * (slotSize.X + pad) + pad * 2f + 3 * (slotSize.X + pad),
|
|
3 * (slotSize.Y + pad));
|
|
if (pos.X < topLeft.X || pos.X >= topLeft.X + barSize.X || pos.Y < topLeft.Y || pos.Y >= topLeft.Y + barSize.Y)
|
|
return -1;
|
|
int best = -1;
|
|
float bestDistSq = float.MaxValue;
|
|
for (int i = 0; i < ControllerBarConfig.CrossSlotCount; i++)
|
|
{
|
|
var slotOffset = GetSlotPositionCross(i, slotSize, pad);
|
|
float cx = topLeft.X + slotOffset.X + slotSize.X * 0.5f;
|
|
float cy = topLeft.Y + slotOffset.Y + slotSize.Y * 0.5f;
|
|
float dx = pos.X - cx, dy = pos.Y - cy;
|
|
float slotHalfW = slotSize.X * 0.5f + DropMargin;
|
|
float slotHalfH = slotSize.Y * 0.5f + DropMargin;
|
|
if (Math.Abs(dx) <= slotHalfW && Math.Abs(dy) <= slotHalfH)
|
|
{
|
|
float distSq = dx * dx + dy * dy;
|
|
if (distSq < bestDistSq) { bestDistSq = distSq; best = i; }
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
private unsafe void HandleDragDropCross(Vector2 topLeft, Vector2 barSize, Vector2 slotSize, int pad)
|
|
{
|
|
if (_pendingSlotIconIndex >= 0 && _pendingSlotIconFramesLeft > 0)
|
|
{
|
|
_pendingSlotIconFramesLeft--;
|
|
if (_pendingSlotIconFramesLeft <= 0)
|
|
_pendingSlotIconIndex = -1;
|
|
}
|
|
else if (_pendingSlotIconIndex >= 0)
|
|
_pendingSlotIconIndex = -1;
|
|
|
|
var stage = AtkStage.Instance();
|
|
if (stage == null) return;
|
|
var dm = (AtkDragDropManager*)Unsafe.AsPointer(ref stage->DragDropManager);
|
|
bool dragging = dm->IsDragging;
|
|
|
|
if (dragging)
|
|
{
|
|
if (ActionBarsHud.GetHotbarsConfig()?.GeneralOptions?.EnableDragDropFromGame != false &&
|
|
ActionBarsHud.TryGetDragPayload(out var slotType, out var id))
|
|
{
|
|
_dragSlotType = slotType;
|
|
_dragId = id;
|
|
_wasDragging = true;
|
|
|
|
var mp = ImGui.GetMousePos();
|
|
int idx = GetSlotIndexAtPositionCross(topLeft, slotSize, pad, mp);
|
|
if (idx >= 0)
|
|
InputsHelper.SuppressExecuteSlotByIdAfterDrop((uint)(ActionBarsManager.ScratchBarIndex - 1), (uint)ActionBarsManager.ScratchSlotIndex, 500);
|
|
|
|
if (ImGui.IsMouseReleased(ImGuiMouseButton.Left))
|
|
{
|
|
int releaseIdx = GetSlotIndexAtPositionCross(topLeft, slotSize, pad, mp);
|
|
if (releaseIdx >= 0 && slotType != RaptureHotbarModule.HotbarSlotType.Empty &&
|
|
(id != 0 || slotType == RaptureHotbarModule.HotbarSlotType.Macro || slotType == RaptureHotbarModule.HotbarSlotType.GearSet))
|
|
{
|
|
if (!ImGui.GetIO().KeyShift)
|
|
{
|
|
try
|
|
{
|
|
ActionBarsManager.Instance?.PlacePayloadOnControllerSlot(Config.HotbarIndex, releaseIdx, slotType, id);
|
|
uint iconId = ActionBarsHud.GetIconIdForPayload(slotType, id);
|
|
if (iconId != 0)
|
|
{
|
|
_pendingSlotIconIndex = releaseIdx;
|
|
_pendingSlotIconId = iconId;
|
|
_pendingSlotIconFramesLeft = 300;
|
|
}
|
|
_lastFrameDroppedOnSlot = releaseIdx;
|
|
InputsHelper.SuppressExecuteSlotByIdAfterDrop((uint)(ActionBarsManager.ScratchBarIndex - 1), (uint)ActionBarsManager.ScratchSlotIndex, 300);
|
|
if (slotType == RaptureHotbarModule.HotbarSlotType.Action ||
|
|
slotType == RaptureHotbarModule.HotbarSlotType.GeneralAction ||
|
|
slotType == RaptureHotbarModule.HotbarSlotType.CraftAction ||
|
|
slotType == RaptureHotbarModule.HotbarSlotType.PetAction)
|
|
InputsHelper.SuppressUseActionAfterDrop(id, 300);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Plugin.Logger.Warning($"[HSUI CrossBar DragDrop] Drop failed: {ex.Message}");
|
|
}
|
|
}
|
|
try { dm->CancelDragDrop(true, true); }
|
|
catch { }
|
|
}
|
|
else
|
|
{
|
|
try { dm->CancelDragDrop(true, true); }
|
|
catch { }
|
|
}
|
|
_wasDragging = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_dragSlotType = RaptureHotbarModule.HotbarSlotType.Empty;
|
|
_dragId = 0;
|
|
_wasDragging = true;
|
|
if (ImGui.IsMouseReleased(ImGuiMouseButton.Left))
|
|
{
|
|
try { dm->CancelDragDrop(true, true); }
|
|
catch { }
|
|
_wasDragging = false;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (_wasDragging)
|
|
{
|
|
_suppressSlotClicksAfterDragRelease = true;
|
|
var mp = ImGui.GetMousePos();
|
|
int idx = GetSlotIndexAtPositionCross(topLeft, slotSize, pad, mp);
|
|
if (idx >= 0)
|
|
{
|
|
var slotType = _dragSlotType;
|
|
var id = _dragId;
|
|
bool hasValidPayload = slotType != RaptureHotbarModule.HotbarSlotType.Empty &&
|
|
(id != 0 || slotType == RaptureHotbarModule.HotbarSlotType.Macro || slotType == RaptureHotbarModule.HotbarSlotType.GearSet);
|
|
if (hasValidPayload)
|
|
{
|
|
if (ImGui.GetIO().KeyShift)
|
|
{
|
|
try { dm->CancelDragDrop(true, true); }
|
|
catch { }
|
|
ActionBarsManager.Instance?.ClearControllerSlot(Config.HotbarIndex, idx);
|
|
_lastFrameDroppedOnSlot = idx;
|
|
_wasDragging = false;
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
ActionBarsManager.Instance?.PlacePayloadOnControllerSlot(Config.HotbarIndex, idx, slotType, id);
|
|
try { dm->CancelDragDrop(true, true); }
|
|
catch { }
|
|
uint iconId = ActionBarsHud.GetIconIdForPayload(slotType, id);
|
|
if (iconId != 0)
|
|
{
|
|
_pendingSlotIconIndex = idx;
|
|
_pendingSlotIconId = iconId;
|
|
_pendingSlotIconFramesLeft = 300;
|
|
}
|
|
_lastFrameDroppedOnSlot = idx;
|
|
InputsHelper.SuppressExecuteSlotByIdAfterDrop((uint)(ActionBarsManager.ScratchBarIndex - 1), (uint)ActionBarsManager.ScratchSlotIndex, 300);
|
|
if (slotType == RaptureHotbarModule.HotbarSlotType.Action ||
|
|
slotType == RaptureHotbarModule.HotbarSlotType.GeneralAction ||
|
|
slotType == RaptureHotbarModule.HotbarSlotType.CraftAction ||
|
|
slotType == RaptureHotbarModule.HotbarSlotType.PetAction)
|
|
InputsHelper.SuppressUseActionAfterDrop(id, 300);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Plugin.Logger.Warning($"[HSUI CrossBar DragDrop] Drop failed: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
_wasDragging = false;
|
|
}
|
|
}
|
|
|
|
public override void DrawChildren(Vector2 origin)
|
|
{
|
|
if (!ControllerHotbarsConfig.GetEffectiveControllerHotbarsEnabled() || Actor == null)
|
|
return;
|
|
if (ControllerHotbarHelper.GetCurrentTrigger() != Config.Trigger)
|
|
return;
|
|
if (ActionBarsManager.Instance == null)
|
|
return;
|
|
|
|
_lastFrameDroppedOnSlot = -1;
|
|
_suppressSlotClicksAfterDragRelease = false;
|
|
|
|
var barSize = BarSize();
|
|
var topLeft = Utils.GetAnchoredPosition(origin + Config.Position, barSize, Config.Anchor);
|
|
var slotSize = Config.SlotSize;
|
|
int pad = Config.SlotPadding;
|
|
bool showCd = Config.ShowCooldownOverlay;
|
|
bool showCdNumbers = Config.ShowCooldownNumbers;
|
|
bool showBorder = Config.ShowBorder;
|
|
|
|
AddDrawAction(Config.StrataLevel, () =>
|
|
{
|
|
HandleDragDropCross(topLeft, barSize, slotSize, pad);
|
|
var slots = ActionBarsManager.Instance.GetControllerSlotData(Config.HotbarIndex, ControllerBarConfig.CrossSlotCount);
|
|
|
|
void DrawBarContent(ImDrawListPtr drawList)
|
|
{
|
|
using (FontsManager.Instance.PushFont(FontsConfig.DefaultSmallFontKey))
|
|
{
|
|
for (int i = 0; i < slots.Count; i++)
|
|
{
|
|
var slot = slots[i];
|
|
uint displayIconId = slot.IconId;
|
|
bool isEmpty = slot.IsEmpty;
|
|
bool usePending = _pendingSlotIconIndex == i && _pendingSlotIconId != 0;
|
|
if (usePending)
|
|
{
|
|
displayIconId = _pendingSlotIconId;
|
|
isEmpty = false;
|
|
}
|
|
|
|
var slotOffset = GetSlotPositionCross(i, slotSize, pad);
|
|
var pos = topLeft + slotOffset;
|
|
var size = new Vector2(slotSize.X, slotSize.Y);
|
|
bool hovered = !isEmpty && ImGui.IsMouseHoveringRect(pos, pos + size);
|
|
|
|
if (isEmpty)
|
|
{
|
|
drawList.AddRectFilled(pos, pos + size, 0x22000000);
|
|
if (showBorder)
|
|
drawList.AddRect(pos, pos + size, 0xFF444444);
|
|
}
|
|
else
|
|
{
|
|
uint color = hovered ? 0xFFFFFFFF : (slot.IsUsable ? 0xFFFFFFFF : 0xAA888888);
|
|
bool isComboNext = Config.ShowComboHighlight && IsComboNextAction(slot);
|
|
DrawHelper.DrawIcon(displayIconId, pos, size, showBorder && !hovered && !isComboNext, color, drawList);
|
|
|
|
if (hovered && showBorder)
|
|
drawList.AddRect(pos, pos + size, 0xFF88CCFF, 0, ImDrawFlags.None, 2);
|
|
else if (isComboNext)
|
|
{
|
|
var ch = Config.ComboHighlightConfig;
|
|
DrawHelper.DrawComboHighlightRect(drawList, pos, pos + size,
|
|
ch.Color.Base, ch.Thickness, ch.ShowGlow, ch.LineStyle);
|
|
}
|
|
|
|
if (showCd && slot.CooldownPercent > 0 && _pendingSlotIconIndex != i)
|
|
{
|
|
float total = 100f;
|
|
float elapsed = total - slot.CooldownPercent;
|
|
DrawHelper.DrawIconCooldown(pos, size, elapsed, total, drawList);
|
|
if (showCdNumbers && slot.CooldownSecondsLeft > 0)
|
|
{
|
|
string cdText = slot.CooldownSecondsLeft.ToString();
|
|
Vector2 textSize = ImGui.CalcTextSize(cdText);
|
|
Vector2 textPos = pos + (size - textSize) * 0.5f;
|
|
DrawHelper.DrawOutlinedText(cdText, textPos, 0xFFFFFFFF, 0xFF000000, drawList, 1);
|
|
}
|
|
}
|
|
|
|
if (Config.ShowKeypressFlash)
|
|
{
|
|
float flashAlpha = InputsHelper.GetKeypressFlashAlpha(Config.HotbarIndex, i, 0.2);
|
|
if (flashAlpha > 0.001f)
|
|
{
|
|
byte a = (byte)(flashAlpha * 0.5f * 255);
|
|
drawList.AddRectFilled(pos, pos + size, (uint)(a << 24 | 0xFFFFFF));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Config.ShowSlotNumbers && !slot.IsEmpty)
|
|
{
|
|
string label = !string.IsNullOrWhiteSpace(slot.KeybindHint)
|
|
? slot.KeybindHint
|
|
: ActionBarsManager.GetDefaultKeybindLabel(Config.HotbarIndex, i);
|
|
Vector2 labelSize = ImGui.CalcTextSize(label);
|
|
Vector2 labelPos = new Vector2(pos.X + size.X - labelSize.X - 2, pos.Y + size.Y - labelSize.Y - 2);
|
|
DrawHelper.DrawOutlinedText(label, labelPos, 0xFFFFFFFF, 0xFF000000, drawList, 1);
|
|
}
|
|
|
|
if (Config.ShowChargeCount && !slot.IsEmpty && slot.MaxCharges > 1)
|
|
{
|
|
string chargeText = slot.CurrentCharges.ToString();
|
|
Vector2 chargeSize = ImGui.CalcTextSize(chargeText);
|
|
Vector2 chargePos = new Vector2(pos.X + 2, pos.Y + 2);
|
|
DrawHelper.DrawOutlinedText(chargeText, chargePos, 0xFFFFFFFF, 0xFF000000, drawList, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DrawHelper.DrawInWindow(ID + "_crossbar", topLeft, barSize, false, DrawBarContent);
|
|
});
|
|
|
|
// Overlay for click, drag-drop and tooltip
|
|
AddDrawAction(StrataLevel.HIGHEST, () =>
|
|
{
|
|
var slots = ActionBarsManager.Instance.GetControllerSlotData(Config.HotbarIndex, ControllerBarConfig.CrossSlotCount);
|
|
var flags = ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground
|
|
| ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoSavedSettings
|
|
| ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBringToFrontOnFocus
|
|
| ImGuiWindowFlags.NoNav;
|
|
|
|
string overlayId = $"HSUI_CrossBar{Config.HotbarIndex}_input";
|
|
ImGui.SetCursorScreenPos(topLeft);
|
|
if (!ImGui.BeginChild(overlayId, barSize, false, flags))
|
|
{
|
|
ImGui.EndChild();
|
|
return;
|
|
}
|
|
|
|
int frame = ImGui.GetFrameCount();
|
|
if (frame != ActionBarsHud._lastOverlayFrame)
|
|
{
|
|
ActionBarsHud._lastOverlayFrame = frame;
|
|
ActionBarsHud._anyHotbarAcceptedDrop = false;
|
|
if (ActionBarsHud._pendingReleaseOutsideSlotId >= 0 && ActionBarsHud.GetHotbarsConfig()?.GeneralOptions?.EnableReleaseOutsideToClear != false)
|
|
{
|
|
var (bar, slot) = ActionBarsHud.FromSlotId(ActionBarsHud._pendingReleaseOutsideSlotId);
|
|
if (bar >= 1 && bar <= 8 && slot >= 0 && slot < ControllerBarConfig.CrossSlotCount)
|
|
ActionBarsManager.Instance?.ClearControllerSlot(bar, slot);
|
|
ActionBarsHud._pendingReleaseOutsideSlotId = -1;
|
|
}
|
|
}
|
|
|
|
bool acceptedDrop = false;
|
|
for (int i = 0; i < ControllerBarConfig.CrossSlotCount; i++)
|
|
{
|
|
var slotOffset = GetSlotPositionCross(i, slotSize, pad);
|
|
var slotPos = topLeft + slotOffset;
|
|
ImGui.SetCursorScreenPos(slotPos);
|
|
bool clicked = ImGui.InvisibleButton($"##{overlayId}_slot_{i}", slotSize);
|
|
bool isHovered = ImGui.IsItemHovered();
|
|
bool isActive = ImGui.IsItemActive();
|
|
|
|
bool shiftHeld = ImGui.GetIO().KeyShift;
|
|
int targetSlotId = ActionBarsHud.ToSlotId(Config.HotbarIndex, i);
|
|
|
|
if (ActionBarsHud.GetHotbarsConfig()?.GeneralOptions?.EnableShiftDragToRearrange != false && ImGui.BeginDragDropTarget())
|
|
{
|
|
var payload = ImGui.AcceptDragDropPayload(ActionBarsHud.SlotPayloadType);
|
|
if (payload != ImGuiPayloadPtr.Null && payload.IsDataType(ActionBarsHud.SlotPayloadType) && ActionBarsHud.TryGetSlotPayload(payload, out var src))
|
|
{
|
|
acceptedDrop = true;
|
|
ActionBarsHud._anyHotbarAcceptedDrop = true;
|
|
ActionBarsHud._imGuiDragSourceSlotId = -1;
|
|
ActionBarsHud._pendingReleaseOutsideSlotId = -1;
|
|
ActionBarsHud._lastLoggedPickupSlotId = -1;
|
|
if (src.SlotId != targetSlotId)
|
|
{
|
|
var (srcBar, srcSlot) = ActionBarsHud.FromSlotId(src.SlotId);
|
|
if (srcBar >= 1 && srcSlot >= 0 && srcBar <= 8 && srcSlot < ControllerBarConfig.CrossSlotCount)
|
|
ActionBarsManager.Instance?.SwapControllerSlots(srcBar, srcSlot, Config.HotbarIndex, i);
|
|
}
|
|
}
|
|
ImGui.EndDragDropTarget();
|
|
}
|
|
|
|
if (ActionBarsHud.GetHotbarsConfig()?.GeneralOptions?.EnableShiftDragToRearrange != false && !ActionBarsHud.IsGameDragging() && shiftHeld && i < slots.Count && !slots[i].IsEmpty && isActive && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
|
{
|
|
if (ImGui.BeginDragDropSource(ImGuiDragDropFlags.None))
|
|
{
|
|
ActionBarsHud._imGuiDragSourceSlotId = targetSlotId;
|
|
ActionBarsHud.SetSlotPayload(new ActionBarsHud.SlotDragPayload { SlotId = targetSlotId });
|
|
var icon = TexturesHelper.GetTextureFromIconId(slots[i].IconId);
|
|
if (icon != null)
|
|
ImGui.Image(icon.Handle, new Vector2(32, 32));
|
|
ImGui.EndDragDropSource();
|
|
}
|
|
}
|
|
|
|
if (clicked && !acceptedDrop && !ImGui.IsMouseDragging(ImGuiMouseButton.Left) && i < slots.Count && !slots[i].IsEmpty)
|
|
{
|
|
if (_suppressSlotClicksAfterDragRelease || i == _lastFrameDroppedOnSlot)
|
|
{
|
|
_suppressSlotClicksAfterDragRelease = false;
|
|
_lastFrameDroppedOnSlot = -1;
|
|
}
|
|
else if (shiftHeld)
|
|
{
|
|
ActionBarsManager.Instance?.ClearControllerSlot(Config.HotbarIndex, i);
|
|
}
|
|
else
|
|
{
|
|
ActionBarsManager.Instance?.ExecuteControllerSlot(Config.HotbarIndex, i);
|
|
}
|
|
}
|
|
|
|
if (Config.ShowTooltips && isHovered && i < slots.Count)
|
|
{
|
|
var slot = slots[i];
|
|
if (!slot.IsEmpty)
|
|
{
|
|
var (title, text) = ActionBarsHud.GetSlotTooltipPublic(slot);
|
|
if (!string.IsNullOrEmpty(title) || !string.IsNullOrEmpty(text))
|
|
{
|
|
string body = string.IsNullOrEmpty(text) ? title : text;
|
|
var tooltipConfig = ConfigurationManager.Instance?.GetConfigObject<TooltipsConfig>();
|
|
var formatted = tooltipConfig != null ? TooltipsHelper.BuildFormattedActionTooltipBody(body, tooltipConfig) : null;
|
|
TooltipsHelper.Instance.ShowTooltipOnCursor(body, title, slot.ActionId, "", slot.IconId > 0 ? slot.IconId : null, TooltipIdKind.Action, formatted);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ActionBarsHud.GetHotbarsConfig()?.GeneralOptions?.EnableReleaseOutsideToClear != false && ImGui.GetIO().KeyShift && ActionBarsHud._imGuiDragSourceSlotId >= 0 &&
|
|
ImGui.IsMouseReleased(ImGuiMouseButton.Left) && !ActionBarsHud._anyHotbarAcceptedDrop)
|
|
{
|
|
ActionBarsHud._pendingReleaseOutsideSlotId = ActionBarsHud._imGuiDragSourceSlotId;
|
|
ActionBarsHud._imGuiDragSourceSlotId = -1;
|
|
ActionBarsHud._lastLoggedPickupSlotId = -1;
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
});
|
|
}
|
|
|
|
private static bool IsComboNextAction(ActionBarsManager.SlotInfo slot)
|
|
{
|
|
if (slot.IsEmpty || slot.ActionId == 0) return false;
|
|
try
|
|
{
|
|
var slotType = slot.SlotType;
|
|
var actionType = slotType switch
|
|
{
|
|
RaptureHotbarModule.HotbarSlotType.Action => ActionType.Action,
|
|
RaptureHotbarModule.HotbarSlotType.GeneralAction => ActionType.GeneralAction,
|
|
RaptureHotbarModule.HotbarSlotType.CraftAction => ActionType.CraftAction,
|
|
RaptureHotbarModule.HotbarSlotType.PetAction => ActionType.PetAction,
|
|
_ => (ActionType?)null
|
|
};
|
|
if (!actionType.HasValue) return false;
|
|
return SpellHelper.Instance.IsActionHighlighted(slot.ActionId, actionType.Value);
|
|
}
|
|
catch { return false; }
|
|
}
|
|
}
|
|
}
|