qstbak/Questionable/Questionable.Functions/GameFunctions.cs
2025-10-09 08:41:52 +10:00

616 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Game;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.Fate;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Functions;
internal sealed class GameFunctions
{
private delegate void AbandonDutyDelegate(bool a1);
private static class Signatures
{
internal const string AbandonDuty = "E8 ?? ?? ?? ?? 41 B2 01 EB 39";
}
private readonly QuestFunctions _questFunctions;
private readonly IDataManager _dataManager;
private readonly IObjectTable _objectTable;
private readonly ITargetManager _targetManager;
private readonly ICondition _condition;
private readonly IClientState _clientState;
private readonly IGameGui _gameGui;
private readonly Configuration _configuration;
private readonly ILogger<GameFunctions> _logger;
private readonly AbandonDutyDelegate _abandonDuty;
private readonly ReadOnlyDictionary<ushort, uint> _territoryToAetherCurrentCompFlgSet;
private readonly ReadOnlyDictionary<uint, uint> _contentFinderConditionToContentId;
public GameFunctions(QuestFunctions questFunctions, IDataManager dataManager, IObjectTable objectTable, ITargetManager targetManager, ICondition condition, IClientState clientState, IGameGui gameGui, Configuration configuration, ISigScanner sigScanner, ILogger<GameFunctions> logger)
{
_questFunctions = questFunctions;
_dataManager = dataManager;
_objectTable = objectTable;
_targetManager = targetManager;
_condition = condition;
_clientState = clientState;
_gameGui = gameGui;
_configuration = configuration;
_logger = logger;
_abandonDuty = Marshal.GetDelegateForFunctionPointer<AbandonDutyDelegate>(sigScanner.ScanText("E8 ?? ?? ?? ?? 41 B2 01 EB 39"));
_territoryToAetherCurrentCompFlgSet = (from x in dataManager.GetExcelSheet<TerritoryType>()
where x.RowId != 0
where x.AetherCurrentCompFlgSet.RowId != 0
select x).ToDictionary((TerritoryType x) => (ushort)x.RowId, (TerritoryType x) => x.AetherCurrentCompFlgSet.RowId).AsReadOnly();
_contentFinderConditionToContentId = (from x in dataManager.GetExcelSheet<ContentFinderCondition>()
where x.RowId != 0 && x.Content.RowId != 0
select x).ToDictionary((ContentFinderCondition x) => x.RowId, (ContentFinderCondition x) => x.Content.RowId).AsReadOnly();
}
public unsafe bool IsFlyingUnlocked(ushort territoryId)
{
if (_configuration.Advanced.NeverFly)
{
return false;
}
if (_questFunctions.IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted] && GetMountId() == 198)
{
return true;
}
PlayerState* ptr = PlayerState.Instance();
if (ptr != null && _territoryToAetherCurrentCompFlgSet.TryGetValue(territoryId, out var value))
{
return ptr->IsAetherCurrentZoneComplete(value);
}
return false;
}
public unsafe ushort? GetMountId()
{
BattleChara* ptr = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
if (ptr != null && ptr->Mount.MountId != 0)
{
return ptr->Mount.MountId;
}
return null;
}
public bool IsFlyingUnlockedInCurrentZone()
{
return IsFlyingUnlocked(_clientState.TerritoryType);
}
public unsafe bool IsAetherCurrentUnlocked(uint aetherCurrentId)
{
PlayerState* ptr = PlayerState.Instance();
if (ptr != null)
{
return ptr->IsAetherCurrentUnlocked(aetherCurrentId);
}
return false;
}
public IGameObject? FindObjectByDataId(uint dataId, Dalamud.Game.ClientState.Objects.Enums.ObjectKind? kind = null)
{
foreach (IGameObject item in _objectTable)
{
Dalamud.Game.ClientState.Objects.Enums.ObjectKind objectKind = item.ObjectKind;
bool flag = ((objectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player || objectKind - 8 <= Dalamud.Game.ClientState.Objects.Enums.ObjectKind.BattleNpc || objectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Housing) ? true : false);
if (!flag && (item == null || item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.GatheringPoint || item.IsTargetable) && item.BaseId == dataId && (!kind.HasValue || kind.Value == item.ObjectKind))
{
return item;
}
}
_logger.LogWarning("Could not find GameObject with dataId {DataId}", dataId);
return null;
}
public bool InteractWith(uint dataId, Dalamud.Game.ClientState.Objects.Enums.ObjectKind? kind = null)
{
IGameObject gameObject = FindObjectByDataId(dataId, kind);
if (gameObject != null)
{
return InteractWith(gameObject);
}
_logger.LogDebug("Game object is null");
return false;
}
public unsafe bool InteractWith(IGameObject gameObject)
{
_logger.LogInformation("Setting target with {DataId} to {ObjectId}", gameObject.BaseId, gameObject.EntityId);
_targetManager.Target = null;
_targetManager.Target = gameObject;
if (gameObject.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.GatheringPoint)
{
TargetSystem.Instance()->OpenObjectInteraction((GameObject*)gameObject.Address);
_logger.LogInformation("Interact result: (none) for GatheringPoint");
return true;
}
long num = (long)TargetSystem.Instance()->InteractWithObject((GameObject*)gameObject.Address, checkLineOfSight: false);
_logger.LogInformation("Interact result: {Result}", num);
if (num != 7)
{
return num > 0;
}
return false;
}
public unsafe bool UseItem(uint itemId)
{
long num = AgentInventoryContext.Instance()->UseItem(itemId, InventoryType.Invalid, 0u, 0);
_logger.LogInformation("UseItem result: {Result}", num);
return num == 0;
}
public unsafe bool UseItem(uint dataId, uint itemId)
{
IGameObject gameObject = FindObjectByDataId(dataId);
if (gameObject != null)
{
_targetManager.Target = gameObject;
long num = AgentInventoryContext.Instance()->UseItem(itemId, InventoryType.Invalid, 0u, 0);
_logger.LogInformation("UseItem result on {DataId}: {Result}", dataId, num);
if ((ulong)num <= 1uL)
{
return true;
}
return false;
}
return false;
}
public unsafe bool UseItemOnGround(uint dataId, uint itemId)
{
IGameObject gameObject = FindObjectByDataId(dataId);
if (gameObject != null)
{
Vector3 position = gameObject.Position;
return ActionManager.Instance()->UseActionLocation(ActionType.KeyItem, itemId, 3758096384uL, &position, 0u, 0);
}
return false;
}
public unsafe bool UseItemOnPosition(Vector3 position, uint itemId)
{
return ActionManager.Instance()->UseActionLocation(ActionType.KeyItem, itemId, 3758096384uL, &position, 0u, 0);
}
public unsafe bool UseAction(EAction action)
{
uint num = (uint)(action & (EAction)65535);
ActionType actionType = (((action & (EAction)65536) != (EAction)65536) ? ActionType.Action : ActionType.GeneralAction);
if (actionType == ActionType.Action)
{
num = ActionManager.Instance()->GetAdjustedActionId(num);
}
if (ActionManager.Instance()->GetActionStatus(actionType, num, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
{
bool flag = ActionManager.Instance()->UseAction(actionType, num, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null);
_logger.LogInformation("UseAction {Action} (adjusted: {AdjustedActionId}) result: {Result}", action, num, flag);
return flag;
}
return false;
}
public unsafe bool UseAction(IGameObject gameObject, EAction action, bool checkCanUse = true)
{
uint actionId = (uint)(action & (EAction)65535);
ActionType actionType = (((action & (EAction)65536) != (EAction)65536) ? ActionType.Action : ActionType.GeneralAction);
if (actionType == ActionType.GeneralAction)
{
_logger.LogWarning("Can not use general action {Action} on target {Target}", action, gameObject);
return false;
}
actionId = ActionManager.Instance()->GetAdjustedActionId(actionId);
if (checkCanUse && !ActionManager.CanUseActionOnTarget(actionId, (GameObject*)gameObject.Address))
{
_logger.LogWarning("Can not use action {Action} (adjusted: {AdjustedActionId}) on target {Target}", action, actionId, gameObject);
return false;
}
Lumina.Excel.Sheets.Action row = _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Action>().GetRow(actionId);
_targetManager.Target = gameObject;
if (ActionManager.Instance()->GetActionStatus(actionType, actionId, gameObject.GameObjectId, checkRecastActive: true, checkCastingActive: true, null) == 0)
{
bool flag;
if (row.TargetArea)
{
Vector3 position = gameObject.Position;
flag = ActionManager.Instance()->UseActionLocation(actionType, actionId, 3758096384uL, &position, 0u, 0);
_logger.LogInformation("UseAction {Action} (adjusted: {AdjustedActionId}) on target area {Target} result: {Result}", action, actionId, gameObject, flag);
}
else
{
flag = ActionManager.Instance()->UseAction(actionType, actionId, gameObject.GameObjectId, 0u, ActionManager.UseActionMode.None, 0u, null);
_logger.LogInformation("UseAction {Action} (adjusted: {AdjustedActionId}) on target {Target} result: {Result}", action, actionId, gameObject, flag);
}
return flag;
}
return false;
}
public bool IsObjectAtPosition(uint dataId, Vector3 position, float distance)
{
IGameObject gameObject = FindObjectByDataId(dataId);
if (gameObject != null)
{
return (gameObject.Position - position).Length() < distance;
}
return false;
}
public unsafe bool HasStatusPreventingMount()
{
if (_condition[ConditionFlag.Swimming] && !IsFlyingUnlockedInCurrentZone())
{
return true;
}
PlayerState* ptr = PlayerState.Instance();
if (ptr != null && !ptr->IsMountUnlocked(1u))
{
return true;
}
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
if (localPlayer == null)
{
return false;
}
BattleChara* address = (BattleChara*)localPlayer.Address;
StatusManager* statusManager = address->GetStatusManager();
if (statusManager->HasStatus(1151u) || statusManager->HasStatus(1945u))
{
return true;
}
return HasCharacterStatusPreventingMountOrSprint();
}
public bool HasStatusPreventingSprint()
{
return HasCharacterStatusPreventingMountOrSprint();
}
private unsafe bool HasCharacterStatusPreventingMountOrSprint()
{
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
if (localPlayer == null)
{
return false;
}
BattleChara* address = (BattleChara*)localPlayer.Address;
StatusManager* statusManager = address->GetStatusManager();
if (!statusManager->HasStatus(565u) && !statusManager->HasStatus(404u) && !statusManager->HasStatus(416u) && !statusManager->HasStatus(2729u))
{
return statusManager->HasStatus(2730u);
}
return true;
}
public unsafe bool HasStatus(EStatus statusId)
{
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
if (localPlayer == null)
{
return false;
}
BattleChara* address = (BattleChara*)localPlayer.Address;
return address->GetStatusManager()->HasStatus((uint)statusId);
}
public static bool RemoveStatus(EStatus statusId)
{
return StatusManager.ExecuteStatusOff((uint)statusId);
}
public unsafe bool Mount()
{
if (_condition[ConditionFlag.Mounted])
{
return true;
}
PlayerState* ptr = PlayerState.Instance();
if (ptr != null && _configuration.General.MountId != 0 && ptr->IsMountUnlocked(_configuration.General.MountId))
{
if (ActionManager.Instance()->GetActionStatus(ActionType.Mount, _configuration.General.MountId, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
{
_logger.LogDebug("Attempting to use preferred mount...");
if (ActionManager.Instance()->UseAction(ActionType.Mount, _configuration.General.MountId, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null))
{
_logger.LogInformation("Using preferred mount");
return true;
}
return false;
}
}
else if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 9u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
{
_logger.LogDebug("Attempting to use mount roulette...");
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 9u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null))
{
_logger.LogInformation("Using mount roulette");
return true;
}
return false;
}
return false;
}
public unsafe bool Unmount()
{
if (!_condition[ConditionFlag.Mounted])
{
return true;
}
if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 23u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
{
_logger.LogDebug("Attempting to unmount...");
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 23u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null))
{
_logger.LogInformation("Unmounted");
return true;
}
return false;
}
_logger.LogWarning("Can't unmount right now?");
return false;
}
public unsafe void OpenDutyFinder(uint contentFinderConditionId)
{
if (_contentFinderConditionToContentId.TryGetValue(contentFinderConditionId, out var value))
{
if (UIState.IsInstanceContentUnlocked(value))
{
AgentContentsFinder.Instance()->OpenRegularDuty(contentFinderConditionId);
return;
}
_logger.LogError("Trying to access a locked duty (cf: {ContentFinderId}, content: {ContentId})", contentFinderConditionId, value);
}
else
{
_logger.LogError("Could not find content for content finder condition (cf: {ContentFinderId})", contentFinderConditionId);
}
}
public static bool GameStringEquals(string? a, string? b)
{
if (a == null)
{
return b == null;
}
if (b == null)
{
return false;
}
return a.ReplaceLineEndings().Replace('', '-') == b.ReplaceLineEndings().Replace('', '-');
}
public unsafe bool IsOccupied()
{
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
{
return true;
}
if (IsLoadingScreenVisible())
{
return true;
}
if (_condition[ConditionFlag.Crafting])
{
if (!AgentRecipeNote.Instance()->IsAgentActive())
{
return true;
}
if (!_condition[ConditionFlag.PreparingToCraft])
{
return true;
}
}
if (_condition[ConditionFlag.Unconscious] && _condition[ConditionFlag.SufferingStatusAffliction63] && _clientState.TerritoryType == 1052)
{
return false;
}
if (!_condition[ConditionFlag.Occupied] && !_condition[ConditionFlag.Occupied30] && !_condition[ConditionFlag.Occupied33] && !_condition[ConditionFlag.Occupied38] && !_condition[ConditionFlag.Occupied39] && !_condition[ConditionFlag.OccupiedInEvent] && !_condition[ConditionFlag.OccupiedInQuestEvent] && !_condition[ConditionFlag.OccupiedInCutSceneEvent] && !_condition[ConditionFlag.Casting] && !_condition[ConditionFlag.MountOrOrnamentTransition] && !_condition[ConditionFlag.BetweenAreas] && !_condition[ConditionFlag.BetweenAreas51] && !_condition[ConditionFlag.Jumping61] && !_condition[ConditionFlag.ExecutingGatheringAction])
{
return _condition[ConditionFlag.Jumping];
}
return true;
}
public unsafe bool IsOccupiedWithCustomDeliveryNpc(Questionable.Model.Quest? currentQuest)
{
if (currentQuest == null || !(currentQuest.Info is SatisfactionSupplyInfo))
{
return false;
}
if (_targetManager.Target == null || _targetManager.Target.BaseId != currentQuest.Info.IssuerDataId)
{
return false;
}
if (!AgentSatisfactionSupply.Instance()->IsAgentActive())
{
return false;
}
HashSet<ConditionFlag> hashSet = _condition.AsReadOnlySet().ToHashSet();
hashSet.Remove(ConditionFlag.InDutyQueue);
if (hashSet.Count == 2 && hashSet.Contains(ConditionFlag.NormalConditions))
{
return hashSet.Contains(ConditionFlag.OccupiedInQuestEvent);
}
return false;
}
public unsafe bool IsLoadingScreenVisible()
{
if (_gameGui.TryGetAddonByName<AtkUnitBase>("FadeMiddle", out var addonPtr) && LAddon.IsAddonReady(addonPtr) && addonPtr->IsVisible)
{
return true;
}
if (_gameGui.TryGetAddonByName<AtkUnitBase>("FadeBack", out addonPtr) && LAddon.IsAddonReady(addonPtr) && addonPtr->IsVisible)
{
return true;
}
if (_gameGui.TryGetAddonByName<AtkUnitBase>("NowLoading", out addonPtr) && LAddon.IsAddonReady(addonPtr) && addonPtr->IsVisible)
{
return true;
}
return false;
}
public unsafe int GetFreeInventorySlots()
{
InventoryManager* ptr = InventoryManager.Instance();
if (ptr == null)
{
return 0;
}
int num = 0;
for (InventoryType inventoryType = InventoryType.Inventory1; inventoryType <= InventoryType.Inventory4; inventoryType++)
{
InventoryContainer* inventoryContainer = ptr->GetInventoryContainer(inventoryType);
if (inventoryContainer == null)
{
continue;
}
for (int i = 0; i < inventoryContainer->Size; i++)
{
InventoryItem* inventorySlot = inventoryContainer->GetInventorySlot(i);
if (inventorySlot == null || inventorySlot->ItemId == 0)
{
num++;
}
}
}
return num;
}
public void AbandonDuty()
{
_abandonDuty(a1: false);
}
public unsafe IReadOnlyList<uint> GetUnlockLinks()
{
UIState* ptr = UIState.Instance();
if (ptr == null)
{
_logger.LogError("Could not query unlock links");
return Array.Empty<uint>();
}
List<uint> list = new List<uint>();
for (uint num = 0u; num < ptr->UnlockLinkBitmask.Length * 8; num++)
{
if (ptr->IsUnlockLinkUnlocked(num))
{
list.Add(num);
}
}
_logger.LogInformation("Unlocked unlock links: {UnlockedUnlockLinks}", string.Join(", ", list));
return list;
}
public unsafe bool SyncToFate(uint fateId)
{
IPlayerCharacter localPlayer = _clientState.LocalPlayer;
if (localPlayer == null)
{
_logger.LogWarning("Cannot sync to FATE: LocalPlayer is null");
return false;
}
FateManager* ptr = FateManager.Instance();
if (ptr == null || ptr->CurrentFate == null)
{
return false;
}
FateContext* currentFate = ptr->CurrentFate;
byte maxLevel = currentFate->MaxLevel;
if (localPlayer.Level <= maxLevel)
{
return true;
}
try
{
_logger.LogInformation("Syncing to FATE {FateId} (max level {MaxLevel})", currentFate->FateId, maxLevel);
ExecuteCommand("/lsync");
return true;
}
catch (Exception exception)
{
_logger.LogError(exception, "Failed to sync to FATE {FateId}", fateId);
return false;
}
}
public unsafe ushort GetCurrentFateId()
{
FateManager* ptr = FateManager.Instance();
if (ptr == null || ptr->CurrentFate == null)
{
return 0;
}
return ptr->CurrentFate->FateId;
}
private unsafe void ExecuteCommand(string command)
{
try
{
UIModule* uIModule = Framework.Instance()->GetUIModule();
if (uIModule == null)
{
_logger.LogError("UIModule is null, cannot execute command: {Command}", command);
return;
}
Utf8String utf8String = new Utf8String(command);
try
{
uIModule->ProcessChatBoxEntry(&utf8String, (nint)utf8String.StringLength);
_logger.LogDebug("Executed chat command: {Command}", command);
}
finally
{
((IDisposable)utf8String/*cast due to .constrained prefix*/).Dispose();
}
}
catch (Exception exception)
{
_logger.LogError(exception, "Failed to execute command: {Command}", command);
}
}
}