-
-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
57 additions
and
281 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,304 +1,80 @@ | ||
using FFXIVClientStructs.FFXIV.Component.GUI; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Runtime.InteropServices; | ||
using System.Collections.Generic; | ||
using FFXIVClientStructs.FFXIV.Client.Game; | ||
using FFXIVClientStructs.FFXIV.Client.System.Framework; | ||
using FFXIVClientStructs.FFXIV.Client.UI; | ||
using FFXIVClientStructs.FFXIV.Client.UI.Misc; | ||
using FFXIVClientStructs.Interop; | ||
using SimpleTweaksPlugin.Debugging; | ||
using SimpleTweaksPlugin.Events; | ||
using SimpleTweaksPlugin.TweakSystem; | ||
using SimpleTweaksPlugin.Utility; | ||
|
||
namespace SimpleTweaksPlugin.Tweaks.UiAdjustment; | ||
|
||
public unsafe class ControlHintMirroring : UiAdjustments.SubTweak | ||
{ | ||
|
||
struct HotbarSlotCommand | ||
{ | ||
public uint Id; | ||
public HotbarSlotType Type; | ||
} | ||
|
||
private delegate byte ActionBarBaseUpdate(AddonActionBarBase* addonActionBarBase, NumberArrayData** numberArrayData, StringArrayData** stringArrayData); | ||
|
||
private HookWrapper<ActionBarBaseUpdate> actionBarBaseUpdateHook; | ||
|
||
private ActionManager* actionManager; | ||
|
||
public override string Name => "Duplicate Keybind Hints Between Hotbars"; | ||
public override string Description => "Will display the keybind hint for any hotbar slot onto unbound slots with the same action."; | ||
protected override string Author => "BoredDan"; | ||
|
||
private static readonly string[] allActionBars = { | ||
"_ActionBar", | ||
"_ActionBar01", | ||
"_ActionBar02", | ||
"_ActionBar03", | ||
"_ActionBar04", | ||
"_ActionBar05", | ||
"_ActionBar06", | ||
"_ActionBar07", | ||
"_ActionBar08", | ||
"_ActionBar09", | ||
}; | ||
|
||
private const int StrDataIndex = 5; | ||
private const int StrDataHotbarLength = 16; | ||
private const int StrDataSlotLength = 3; | ||
private const int StrDataHintIndex = 2; | ||
|
||
private static readonly int actionBarLength = 12; | ||
private string[][] recordedControlHints = new string[allActionBars.Length][]; | ||
private HotbarSlotCommand[][] recordedCommands = new HotbarSlotCommand[allActionBars.Length][]; | ||
|
||
protected override void Enable() | ||
{ | ||
UpdateAll(); | ||
actionBarBaseUpdateHook ??= Common.Hook<ActionBarBaseUpdate>("E8 ?? ?? ?? ?? 83 BB ?? ?? ?? ?? ?? 75 09", ActionBarBaseUpdateDetour); | ||
actionBarBaseUpdateHook?.Enable(); | ||
actionManager = ActionManager.Instance(); | ||
base.Enable(); | ||
} | ||
|
||
private static AddonActionBarBase* GetActionBarAddon(string actionBar) | ||
{ | ||
return (AddonActionBarBase*)Service.GameGui.GetAddonByName(actionBar, 1); | ||
} | ||
|
||
private byte ActionBarBaseUpdateDetour(AddonActionBarBase* addonActionBarBase, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) | ||
{ | ||
var changesFound = false; | ||
|
||
var hotbarID = addonActionBarBase->RaptureHotbarId; | ||
|
||
if (hotbarID < allActionBars.Length) | ||
{ | ||
changesFound = UpdateHotbarRecordedControlHints(addonActionBarBase, stringArrayData[StrDataIndex]); | ||
changesFound |= UpdateHotbarCommands(addonActionBarBase); | ||
} | ||
|
||
var ret = actionBarBaseUpdateHook.Original(addonActionBarBase, numberArrayData, stringArrayData); | ||
|
||
if (changesFound) | ||
{ | ||
FillControlHints(); | ||
} | ||
return ret; | ||
} | ||
|
||
private void UpdateAll() | ||
{ | ||
var changesFound = UpdateHotbarRecordedControlHints(); | ||
changesFound |= UpdateCommands(); | ||
|
||
if (changesFound) | ||
{ | ||
FillControlHints(); | ||
} | ||
} | ||
|
||
private void Reset() | ||
{ | ||
foreach (var actionBar in allActionBars) | ||
{ | ||
AddonActionBarBase* ab = GetActionBarAddon(actionBar); | ||
if (ab != null && ab->ActionBarSlots != null) | ||
{ | ||
ResetHotbar(ab); | ||
[TweakName("Duplicate Keybind Hints Between Hotbars")] | ||
[TweakDescription("Will display the keybind hint for any hotbar slot onto unbound slots with the same action.")] | ||
[TweakAuthor("BoredDan")] | ||
[TweakVersion(2)] | ||
[Changelog(UnreleasedVersion, "Rewritten & re-enabled")] | ||
public unsafe class ControlHintMirroring : UiAdjustments.SubTweak { | ||
private readonly Dictionary<(HotbarSlotType Type, uint Id), string> hints = new(); | ||
private readonly Dictionary<(byte BarID, byte SlotIndex), bool> barSlotHasNoHint = new(); | ||
private readonly Dictionary<(byte BarID, byte SlotIndex), bool> barSlotIsSet = new(); | ||
private readonly string[] actionBars = { "_ActionBar09", "_ActionBar", "_ActionBar01", "_ActionBar02", "_ActionBar03", "_ActionBar04", "_ActionBar05", "_ActionBar06", "_ActionBar07", "_ActionBar08" }; | ||
// const arrays please? | ||
[AddonPreRequestedUpdate("_ActionBar09", "_ActionBar", "_ActionBar01", "_ActionBar02", "_ActionBar03", "_ActionBar04", "_ActionBar05", "_ActionBar06", "_ActionBar07", "_ActionBar08")] | ||
private void ActionBarUpdateRequested(AddonActionBarX* addon) { | ||
var barId = addon->AddonActionBarBase.RaptureHotbarId; | ||
if (barId == 9) hints.Clear(); // Relies on the game updating action bars in reverse order | ||
for (var slotIndex = (byte)(addon->AddonActionBarBase.SlotCount - 1); slotIndex < addon->AddonActionBarBase.SlotCount; slotIndex--) { | ||
if (barSlotIsSet.TryGetValue((barId, slotIndex), out var isSet) && isSet) { | ||
addon->AddonActionBarBase.Slot[slotIndex].ControlHintTextNode->SetText(string.Empty); | ||
barSlotIsSet[(barId, slotIndex)] = false; | ||
} | ||
} | ||
recordedControlHints = new string[allActionBars.Length][]; | ||
recordedCommands = new HotbarSlotCommand[allActionBars.Length][]; | ||
} | ||
|
||
private void ResetHotbar(AddonActionBarBase* ab) | ||
{ | ||
if (ab == null || ab->ActionBarSlots == null) return; | ||
|
||
var numSlots = ab->SlotCount; | ||
var slot = RaptureHotbarModule.Instance()->GetSlotById(barId, slotIndex); | ||
if (slot->CommandType == HotbarSlotType.Empty) continue; | ||
|
||
for (int i = 0; i < numSlots; i++) | ||
{ | ||
var slot = ab->ActionBarSlots[i]; | ||
if (slot.ControlHintTextNode != null) | ||
{ | ||
var normalControlHint = recordedControlHints?[ab->RaptureHotbarId]?[i]; | ||
if (normalControlHint != null) | ||
{ | ||
slot.ControlHintTextNode->SetText(normalControlHint); | ||
} | ||
var str = Common.ReadString(slot->KeybindHint, 0x10); | ||
if (string.IsNullOrWhiteSpace(str)) { | ||
barSlotHasNoHint[(barId, slotIndex)] = true; | ||
continue; | ||
} | ||
} | ||
} | ||
|
||
private bool UpdateHotbarRecordedControlHints() | ||
{ | ||
var changeFound = false; | ||
foreach (var actionBar in allActionBars) | ||
{ | ||
AddonActionBarBase* ab = GetActionBarAddon(actionBar); | ||
if (ab != null && ab->ActionBarSlots != null) | ||
{ | ||
changeFound |= UpdateHotbarRecordedControlHints(ab); | ||
} | ||
var commandId = slot->CommandType == HotbarSlotType.Action ? ActionManager.Instance()->GetAdjustedActionId(slot->CommandId) : slot->CommandId; | ||
hints[(slot->CommandType, commandId)] = str; | ||
barSlotHasNoHint[(barId, slotIndex)] = false; | ||
} | ||
return changeFound; | ||
|
||
if (barId == 0 && !Unloading) Service.Framework.RunOnTick(FrameworkUpdate); | ||
} | ||
|
||
private bool UpdateHotbarRecordedControlHints(AddonActionBarBase* ab, StringArrayData* strArrayData = null) | ||
{ | ||
if (ab == null || ab->ActionBarSlots == null) return false; | ||
if (ab->RaptureHotbarId > recordedControlHints.Length) return false; | ||
|
||
if (strArrayData == null) | ||
{ | ||
strArrayData = Framework.Instance()->GetUiModule()->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder.StringArrays[StrDataIndex]; | ||
} | ||
|
||
var numSlots = ab->SlotCount; | ||
|
||
var changeFound = false; | ||
|
||
if (recordedControlHints[ab->RaptureHotbarId] == null || recordedControlHints[ab->RaptureHotbarId].Length != numSlots || recordedControlHints[ab->RaptureHotbarId].Length != numSlots) | ||
{ | ||
recordedControlHints[ab->RaptureHotbarId] = new string[numSlots]; | ||
changeFound = true; | ||
} | ||
|
||
for (int i = 0; i < numSlots; i++) | ||
{ | ||
var currentControlHint = Marshal.PtrToStringUTF8(new IntPtr(strArrayData->StringArray[(ab->RaptureHotbarId * StrDataHotbarLength + i) * StrDataSlotLength + StrDataHintIndex])); | ||
if (recordedControlHints?[ab->RaptureHotbarId]?[i] != currentControlHint) | ||
{ | ||
recordedControlHints[ab->RaptureHotbarId][i] = currentControlHint; | ||
changeFound = true; | ||
} | ||
} | ||
|
||
return changeFound; | ||
} | ||
|
||
private bool UpdateCommands() | ||
{ | ||
var changeFound = false; | ||
foreach (var actionBar in allActionBars) | ||
{ | ||
AddonActionBarBase* ab = GetActionBarAddon(actionBar); | ||
if (ab != null) | ||
{ | ||
changeFound |= UpdateHotbarCommands(ab); | ||
} | ||
} | ||
return changeFound; | ||
} | ||
|
||
private bool UpdateHotbarCommands(AddonActionBarBase* ab) | ||
{ | ||
if (ab == null) return false; | ||
var hotbarModule = Framework.Instance()->GetUiModule()->GetRaptureHotbarModule(); | ||
var name = Marshal.PtrToStringUTF8(new IntPtr(ab->AtkUnitBase.Name)); | ||
if (name == null) return false; | ||
var hotbar = hotbarModule->HotBarsSpan.GetPointer(ab->RaptureHotbarId); | ||
if (hotbar == null) return false; | ||
|
||
var numSlots = ab->SlotCount; | ||
|
||
var changeFound = false; | ||
|
||
if (recordedCommands[ab->RaptureHotbarId] == null || recordedCommands[ab->RaptureHotbarId].Length != numSlots) | ||
{ | ||
recordedCommands[ab->RaptureHotbarId] = new HotbarSlotCommand[numSlots]; | ||
changeFound = true; | ||
} | ||
|
||
for (int i = 0; i < numSlots; i++) | ||
{ | ||
var command = new HotbarSlotCommand(); | ||
var slotStruct = hotbar->SlotsSpan.GetPointer(i); | ||
if (slotStruct != null) | ||
{ | ||
command.Type = slotStruct->CommandType; | ||
command.Id = slotStruct->CommandType == HotbarSlotType.Action ? actionManager->GetAdjustedActionId(slotStruct->CommandId) : slotStruct->CommandId; | ||
} | ||
|
||
if (!command.Equals(recordedCommands[ab->RaptureHotbarId][i])) | ||
{ | ||
recordedCommands[ab->RaptureHotbarId][i] = command; | ||
changeFound = true; | ||
} | ||
} | ||
|
||
return changeFound; | ||
} | ||
|
||
private void FillControlHints() | ||
{ | ||
Dictionary<HotbarSlotCommand, string> commandControlHints = new Dictionary<HotbarSlotCommand, string>(allActionBars.Length * actionBarLength); | ||
for (int i = 0; i < Math.Min(recordedControlHints.Length, recordedCommands.Length); i++) | ||
{ | ||
if (recordedControlHints[i] == null || recordedCommands[i] == null) continue; | ||
for (int j = 0; j < Math.Min(recordedControlHints[i].Length, recordedCommands[i].Length); j++) | ||
{ | ||
if (recordedCommands[i][j].Type != HotbarSlotType.Empty && recordedControlHints[i][j] != null && recordedControlHints[i][j].Length > 0) | ||
{ | ||
commandControlHints.TryAdd(recordedCommands[i][j], recordedControlHints[i][j]); | ||
} | ||
} | ||
} | ||
|
||
foreach (var actionBar in allActionBars) | ||
{ | ||
AddonActionBarBase* ab = GetActionBarAddon(actionBar); | ||
if (ab != null && ab->ActionBarSlots != null) | ||
{ | ||
FillHotbarControlHints(ab, commandControlHints); | ||
} | ||
} | ||
} | ||
|
||
private void FillHotbarControlHints(AddonActionBarBase* ab, Dictionary<HotbarSlotCommand, string> commandControlHints) | ||
{ | ||
if (ab == null || ab->ActionBarSlots == null) return; | ||
var hotbarModule = Framework.Instance()->GetUiModule()->GetRaptureHotbarModule(); | ||
var name = Marshal.PtrToStringUTF8(new IntPtr(ab->AtkUnitBase.Name)); | ||
if (name == null) return; | ||
var hotbar = hotbarModule->HotBarsSpan.GetPointer(ab->RaptureHotbarId); | ||
if (hotbar == null) return; | ||
|
||
for (int i = 0; i < ab->SlotCount; i++) | ||
{ | ||
if (recordedCommands?[ab->RaptureHotbarId]?[i] == null) continue; | ||
|
||
var slot = ab->ActionBarSlots[i]; | ||
if (slot.ControlHintTextNode != null) | ||
{ | ||
var controlHint = recordedControlHints?[ab->RaptureHotbarId]?[i]; | ||
if (controlHint == null || controlHint.Length == 0) | ||
{ | ||
var command = recordedCommands[ab->RaptureHotbarId][i]; | ||
if (commandControlHints.ContainsKey(command)) | ||
{ | ||
controlHint = commandControlHints[command]; | ||
|
||
private void FrameworkUpdate() { | ||
using var _ = PerformanceMonitor.Run(); | ||
foreach (var bar in actionBars) { | ||
if (!Common.GetUnitBase<AddonActionBarX>(out var addon, bar)) continue; | ||
var barId = addon->AddonActionBarBase.RaptureHotbarId; | ||
for (byte slotIndex = 0; slotIndex < addon->AddonActionBarBase.SlotCount; slotIndex++) { | ||
var slot = RaptureHotbarModule.Instance()->GetSlotById(barId, slotIndex); | ||
if (slot->CommandType == HotbarSlotType.Empty) continue; | ||
if (barSlotHasNoHint.TryGetValue((barId, slotIndex), out var noHint) && noHint) { | ||
var commandId = slot->CommandType == HotbarSlotType.Action ? ActionManager.Instance()->GetAdjustedActionId(slot->CommandId) : slot->CommandId; | ||
if (hints.TryGetValue((slot->CommandType, commandId), out var hint)) { | ||
addon->AddonActionBarBase.Slot[slotIndex].ControlHintTextNode->SetText(hint); | ||
barSlotIsSet[(barId, slotIndex)] = true; | ||
} else if (barSlotIsSet.TryGetValue((barId, slotIndex), out var isSet) && isSet) { | ||
addon->AddonActionBarBase.Slot[slotIndex].ControlHintTextNode->SetText(string.Empty); | ||
barSlotIsSet[(barId, slotIndex)] = false; | ||
} | ||
} | ||
|
||
slot.ControlHintTextNode->SetText(controlHint); | ||
} | ||
} | ||
} | ||
|
||
protected override void Disable() | ||
{ | ||
actionBarBaseUpdateHook?.Disable(); | ||
Reset(); | ||
base.Disable(); | ||
private void UpdateAll() { | ||
foreach(var addonName in actionBars) { | ||
if (Common.GetUnitBase<AddonActionBarX>(out var addon, addonName)) ActionBarUpdateRequested(addon); | ||
} | ||
} | ||
|
||
public override void Dispose() | ||
{ | ||
actionBarBaseUpdateHook?.Dispose(); | ||
Reset(); | ||
base.Dispose(); | ||
} | ||
protected override void Enable() => UpdateAll(); | ||
protected override void Disable() => UpdateAll(); | ||
} |