1
0
Fork 0
forked from aly/qstbak

muffin v7.4.10

This commit is contained in:
alydev 2026-01-19 08:31:23 +10:00
parent 2df81c5d15
commit b8dd142c23
47 changed files with 3604 additions and 1058 deletions

View file

@ -0,0 +1,63 @@
using System;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
using FFXIVClientStructs.FFXIV.Common.Lua;
using Microsoft.Extensions.Logging;
namespace Questionable.Controller.GameUi;
internal sealed class AutoSnipeHandler : IDisposable
{
private unsafe delegate ulong EnqueueSnipeTaskDelegate(EventSceneModuleImplBase* scene, lua_State* state);
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly ILogger<AutoSnipeHandler> _logger;
private readonly Hook<EnqueueSnipeTaskDelegate>? _enqueueSnipeTaskHook;
public unsafe AutoSnipeHandler(QuestController questController, Configuration configuration, IGameInteropProvider gameInteropProvider, ILogger<AutoSnipeHandler> logger)
{
_questController = questController;
_configuration = configuration;
_logger = logger;
try
{
_enqueueSnipeTaskHook = gameInteropProvider.HookFromSignature<EnqueueSnipeTaskDelegate>("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 50 48 8B F9 48 8D 4C 24 ??", EnqueueSnipeTask);
_enqueueSnipeTaskHook.Enable();
_logger.LogInformation("AutoSnipeHandler enabled");
}
catch (Exception exception)
{
_logger.LogError(exception, "Failed to initialize AutoSnipeHandler");
_enqueueSnipeTaskHook = null;
}
}
private unsafe ulong EnqueueSnipeTask(EventSceneModuleImplBase* scene, lua_State* state)
{
if (_configuration.General.AutoSnipe && _questController.IsRunning)
{
_logger.LogDebug("Auto-completing snipe task");
TValue* top = state->top;
top->tt = 3;
top->value.n = 1.0;
state->top++;
return 1uL;
}
return _enqueueSnipeTaskHook.Original(scene, state);
}
public void Dispose()
{
if (_enqueueSnipeTaskHook != null)
{
_enqueueSnipeTaskHook.Disable();
_enqueueSnipeTaskHook.Dispose();
_logger.LogInformation("AutoSnipeHandler disabled");
}
}
}

View file

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Model.Questing;
namespace Questionable.Controller.GameUi;
internal sealed class ChocoboNameHandler : IDisposable
{
private static readonly HashSet<ElementId> ChocoboQuestIds = new HashSet<ElementId>
{
new QuestId(700),
new QuestId(701),
new QuestId(702)
};
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly IAddonLifecycle _addonLifecycle;
private readonly ILogger<ChocoboNameHandler> _logger;
private bool _awaitingConfirmation;
public ChocoboNameHandler(QuestController questController, Configuration configuration, IAddonLifecycle addonLifecycle, ILogger<ChocoboNameHandler> logger)
{
_questController = questController;
_configuration = configuration;
_addonLifecycle = addonLifecycle;
_logger = logger;
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "InputString", OnInputStringSetup);
_addonLifecycle.RegisterListener(AddonEvent.PostSetup, "SelectYesno", OnSelectYesNoSetup);
}
public void Dispose()
{
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "SelectYesno", OnSelectYesNoSetup);
_addonLifecycle.UnregisterListener(AddonEvent.PostSetup, "InputString", OnInputStringSetup);
}
private bool IsChocoboQuestActive()
{
QuestController.QuestProgress currentQuest = _questController.CurrentQuest;
if (currentQuest != null)
{
return ChocoboQuestIds.Contains(currentQuest.Quest.Id);
}
return false;
}
private unsafe void OnInputStringSetup(AddonEvent type, AddonArgs args)
{
if (_questController.IsRunning && IsChocoboQuestActive())
{
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
if (LAddon.IsAddonReady(address))
{
string validatedChocoboName = GetValidatedChocoboName(_configuration.Advanced.ChocoboName);
_logger.LogInformation("Entering chocobo name: {Name}", validatedChocoboName);
FireInputStringCallback(address, validatedChocoboName);
_awaitingConfirmation = true;
}
}
}
private unsafe void OnSelectYesNoSetup(AddonEvent type, AddonArgs args)
{
if (_questController.IsRunning && _awaitingConfirmation)
{
AtkUnitBase* address = (AtkUnitBase*)args.Addon.Address;
if (LAddon.IsAddonReady(address))
{
_logger.LogInformation("Confirming chocobo name");
ClickYes(address);
_awaitingConfirmation = false;
}
}
}
private static string GetValidatedChocoboName(string? configuredName)
{
if (string.IsNullOrWhiteSpace(configuredName))
{
return "Kweh";
}
string text = configuredName.Trim();
if (text.Length < 2 || text.Length > 20)
{
return "Kweh";
}
string text2 = text.Substring(0, 1).ToUpperInvariant();
string text3;
if (text.Length <= 1)
{
text3 = string.Empty;
}
else
{
string text4 = text;
text3 = text4.Substring(1, text4.Length - 1).ToLowerInvariant();
}
return text2 + text3;
}
private unsafe void FireInputStringCallback(AtkUnitBase* addon, string text)
{
AtkComponentNode* componentNodeById = addon->GetComponentNodeById(9u);
if (componentNodeById == null)
{
_logger.LogWarning("Could not find text input node in InputString addon");
return;
}
AtkComponentTextInput* asAtkComponentTextInput = componentNodeById->GetAsAtkComponentTextInput();
if (asAtkComponentTextInput == null)
{
_logger.LogWarning("Node is not a text input component");
return;
}
asAtkComponentTextInput->SetText(text);
AtkValue* ptr = stackalloc AtkValue[2];
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr->Int = 0;
byte[] bytes = Encoding.UTF8.GetBytes(text);
nint num = Marshal.AllocHGlobal(bytes.Length + 1);
Marshal.Copy(bytes, 0, num, bytes.Length);
Marshal.WriteByte(num + bytes.Length, 0);
ptr[1].Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.String;
ptr[1].String = (byte*)num;
addon->FireCallback(2u, ptr);
Marshal.FreeHGlobal(num);
}
private unsafe static void ClickYes(AtkUnitBase* addon)
{
AtkValue* ptr = stackalloc AtkValue[1];
ptr->Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int;
ptr->Int = 0;
addon->FireCallback(1u, ptr);
}
}

View file

@ -0,0 +1,152 @@
using System;
using Dalamud.Plugin.Services;
using LLib.Shop;
using Microsoft.Extensions.Logging;
using Questionable.Model.Questing;
namespace Questionable.Controller.GameUi;
internal sealed class GCShopHandler : IDisposable
{
private enum GCShopState
{
Inactive,
Purchasing,
Completed
}
private readonly QuestController _questController;
private readonly GrandCompanyShop _gcShop;
private readonly IObjectTable _objectTable;
private readonly IFramework _framework;
private readonly ILogger<GCShopHandler> _logger;
private GCShopState _state;
private GCPurchaseInfo? _currentPurchase;
private DateTime _lastActionTime = DateTime.MinValue;
private bool _purchaseCompletedThisStep;
public bool IsActive => _state != GCShopState.Inactive;
public GCShopHandler(QuestController questController, GrandCompanyShop gcShop, IObjectTable objectTable, IFramework framework, ILogger<GCShopHandler> logger)
{
_questController = questController;
_gcShop = gcShop;
_objectTable = objectTable;
_framework = framework;
_logger = logger;
_gcShop.PurchaseCompleted += OnPurchaseCompleted;
_framework.Update += OnFrameworkUpdate;
}
public void Dispose()
{
_framework.Update -= OnFrameworkUpdate;
_gcShop.PurchaseCompleted -= OnPurchaseCompleted;
}
private void OnFrameworkUpdate(IFramework framework)
{
if (_objectTable.LocalPlayer == null)
{
return;
}
if (!_questController.IsRunning)
{
if (_state != GCShopState.Inactive)
{
Reset();
}
return;
}
CheckForGCPurchase();
if (_state == GCShopState.Purchasing)
{
HandlePurchasing();
}
else if (_state == GCShopState.Completed)
{
_logger.LogInformation("GC shop purchase completed!");
_state = GCShopState.Inactive;
_lastActionTime = DateTime.MinValue;
}
}
private void CheckForGCPurchase()
{
QuestController.QuestProgress currentQuest = _questController.CurrentQuest;
if (currentQuest == null)
{
if (_currentPurchase != null)
{
Reset();
}
return;
}
GCPurchaseInfo gCPurchaseInfo = (currentQuest.Quest.FindSequence(currentQuest.Sequence)?.FindStep(currentQuest.Step))?.GCPurchase;
if (gCPurchaseInfo == null)
{
if (_currentPurchase != null)
{
_currentPurchase = null;
_purchaseCompletedThisStep = false;
}
if (_gcShop.IsOpen)
{
_logger.LogDebug("GC shop is open but current step has no GCPurchase. Quest={QuestId}, Seq={Sequence}, Step={Step}", currentQuest.Quest.Id, currentQuest.Sequence, currentQuest.Step);
}
}
else
{
if (_currentPurchase != gCPurchaseInfo)
{
_currentPurchase = gCPurchaseInfo;
_purchaseCompletedThisStep = false;
}
if (_gcShop.IsOpen && _state == GCShopState.Inactive && !_purchaseCompletedThisStep)
{
_logger.LogInformation("GC shop is open. Starting purchase of item {ItemId}.", gCPurchaseInfo.ItemId);
_state = GCShopState.Purchasing;
}
}
}
private void HandlePurchasing()
{
if (_gcShop.IsOpen && _currentPurchase != null && !((DateTime.Now - _lastActionTime).TotalMilliseconds < 500.0))
{
if (!_gcShop.IsPurchaseInProgress)
{
_gcShop.StartPurchase(_currentPurchase.ItemId, _currentPurchase.RankTab, _currentPurchase.CategoryTab);
_lastActionTime = DateTime.Now;
}
_gcShop.ProcessPurchase();
}
}
private void OnPurchaseCompleted(object? sender, PurchaseCompletedEventArgs e)
{
if (_state == GCShopState.Purchasing && _currentPurchase != null && _questController.IsRunning && e.Success && e.ItemId == _currentPurchase.ItemId)
{
_logger.LogInformation("GC purchase of item {ItemId} completed successfully", e.ItemId);
_gcShop.CloseShop();
_purchaseCompletedThisStep = true;
_state = GCShopState.Completed;
}
}
private void Reset()
{
_state = GCShopState.Inactive;
_currentPurchase = null;
_lastActionTime = DateTime.MinValue;
_purchaseCompletedThisStep = false;
}
}

View file

@ -0,0 +1,113 @@
using System;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
using FFXIVClientStructs.Interop;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Utils;
namespace Questionable.Controller.GameUi;
internal sealed class QteController : IDisposable
{
private readonly Configuration _configuration;
private readonly IGameGui _gameGui;
private readonly IGameConfig _gameConfig;
private readonly IFramework _framework;
private readonly ILogger<QteController> _logger;
private long _throttler = Environment.TickCount64;
private readonly Random _random = new Random();
private bool _hasDirectChat;
private bool _directChatWasEnabled;
public QteController(Configuration configuration, IGameGui gameGui, IGameConfig gameConfig, IFramework framework, ILogger<QteController> logger)
{
_configuration = configuration;
_gameGui = gameGui;
_gameConfig = gameConfig;
_framework = framework;
_logger = logger;
_framework.Update += OnUpdate;
}
private unsafe void OnUpdate(IFramework framework)
{
AtkUnitBase* addonPtr;
if (!_configuration.General.AutoSolveQte)
{
EnableDirectChatIfNeeded();
}
else if (_gameGui.TryGetAddonByName<AtkUnitBase>("QTE", out addonPtr) && LAddon.IsAddonReady(addonPtr) && addonPtr->IsVisible)
{
DisableDirectChatIfNeeded();
if (Environment.TickCount64 >= _throttler)
{
if (IsChatLogFocused())
{
WindowsKeypress.SendKeypress(WindowsKeypress.LimitedKeys.Escape);
}
WindowsKeypress.SendKeypress(WindowsKeypress.LimitedKeys.A);
_throttler = Environment.TickCount64 + _random.Next(25, 50);
}
}
else
{
EnableDirectChatIfNeeded();
}
}
private unsafe bool IsChatLogFocused()
{
AtkStage* ptr = AtkStage.Instance();
if (ptr == null)
{
return false;
}
Span<Pointer<AtkUnitBase>> entries = ptr->RaptureAtkUnitManager->AtkUnitManager.FocusedUnitsList.Entries;
for (int i = 0; i < entries.Length; i++)
{
Pointer<AtkUnitBase> pointer = entries[i];
if (pointer.Value != null && pointer.Value->NameString == "ChatLog")
{
return true;
}
}
return false;
}
private void EnableDirectChatIfNeeded()
{
if (_hasDirectChat)
{
_gameConfig.UiControl.Set("DirectChat", _directChatWasEnabled);
_hasDirectChat = false;
}
}
private void DisableDirectChatIfNeeded()
{
if (!_hasDirectChat && _gameConfig.UiControl.TryGetBool("DirectChat", out var value))
{
_directChatWasEnabled = value;
if (value)
{
_gameConfig.UiControl.Set("DirectChat", value: false);
}
_hasDirectChat = true;
}
}
public void Dispose()
{
_framework.Update -= OnUpdate;
EnableDirectChatIfNeeded();
}
}