qstbak/Questionable/Questionable.Functions/GameFunctions.cs
2026-08-17 20:29:32 +10:00

480 lines
15 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 System.Text.RegularExpressions;
using Dalamud.Game.ClientState.Conditions;
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.Event;
using FFXIVClientStructs.FFXIV.Client.Game.Fate;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using LLib;
using LLib.GameData;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model.Questing;
namespace Questionable.Functions;
internal sealed class GameFunctions
{
private delegate void AbandonDutyDelegate(bool a1);
private readonly GameStateHelper _gameState;
private readonly PublicEventHelper _publicEventHelper;
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<uint, uint> _contentFinderConditionToContentId;
private static readonly Regex MacroLiteralRegex = new Regex("\\\\?<[^>]+>", RegexOptions.Compiled);
public GameFunctions(GameStateHelper gameState, PublicEventHelper publicEventHelper, QuestFunctions questFunctions, IDataManager dataManager, IObjectTable objectTable, ITargetManager targetManager, ICondition condition, IClientState clientState, IGameGui gameGui, Configuration configuration, ISigScanner sigScanner, ILogger<GameFunctions> logger)
{
_gameState = gameState;
_publicEventHelper = publicEventHelper;
_questFunctions = questFunctions;
_dataManager = dataManager;
_objectTable = objectTable;
_targetManager = targetManager;
_condition = condition;
_clientState = clientState;
_gameGui = gameGui;
_configuration = configuration;
_logger = logger;
_abandonDuty = Marshal.GetDelegateForFunctionPointer<AbandonDutyDelegate>(EventFramework.Addresses.LeaveCurrentContent.Value);
_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 bool IsFlyingUnlocked(uint territoryId)
{
if (_configuration.Navigation.NeverFly)
{
return false;
}
if (_questFunctions.IsQuestAccepted(new QuestId(3304)) && _condition[ConditionFlag.Mounted] && GetMountId() == 198)
{
return true;
}
return _gameState.IsFlyingUnlocked(territoryId);
}
public bool IsFlyingUnlockedPersistent(uint territoryId)
{
if (_configuration.Navigation.NeverFly)
{
return false;
}
return _gameState.IsFlyingUnlocked(territoryId);
}
public ushort? GetMountId()
{
return _gameState.GetMountId();
}
public bool IsFlyingUnlockedInCurrentZone()
{
return IsFlyingUnlocked(_clientState.TerritoryType);
}
public static bool IsAetherCurrentUnlocked(uint aetherCurrentId)
{
return PlayerStateHelper.IsAetherCurrentUnlocked(aetherCurrentId);
}
public IGameObject? FindObjectByDataId(uint dataId, Dalamud.Game.ClientState.Objects.Enums.ObjectKind? kind = null, bool warnIfMissing = true)
{
for (int i = 0; i < _objectTable.Length; i++)
{
IGameObject gameObject = _objectTable[i];
if (gameObject != null)
{
Dalamud.Game.ClientState.Objects.Enums.ObjectKind objectKind = gameObject.ObjectKind;
bool flag = ((objectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Pc || objectKind - 8 <= Dalamud.Game.ClientState.Objects.Enums.ObjectKind.BattleNpc || objectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.HousingEventObject) ? true : false);
if (!flag && (gameObject == null || gameObject.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.GatheringPoint || gameObject.IsTargetable) && gameObject.BaseId == dataId && (!kind.HasValue || kind.Value == gameObject.ObjectKind))
{
return gameObject;
}
}
}
if (warnIfMissing)
{
_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.LogDebug("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.LogDebug("Interact result: (none) for GatheringPoint");
return true;
}
long num = (long)TargetSystem.Instance()->InteractWithObject((GameObject*)gameObject.Address, checkLineOfSight: false);
_logger.LogDebug("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.LogDebug("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.LogDebug("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.EventItem, itemId, 3758096384uL, &position, 0u, 0);
}
return false;
}
public unsafe bool UseItemOnPosition(Vector3 position, uint itemId)
{
return ActionManager.Instance()->UseActionLocation(ActionType.EventItem, 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.LogDebug("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.LogDebug("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.LogDebug("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 bool HasStatusPreventingMount()
{
return _gameState.HasStatusPreventingMount();
}
public bool HasStatusPreventingSprint()
{
return _gameState.HasStatusPreventingSprint();
}
public unsafe bool HasStatus(EStatus statusId)
{
IPlayerCharacter localPlayer = _objectTable.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 bool Mount()
{
return _gameState.Mount(_configuration.Navigation.MountId);
}
public bool Unmount()
{
return _gameState.Unmount();
}
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;
}
if (a == b)
{
return true;
}
return NormalizeGameString(a) == NormalizeGameString(b);
}
private static string NormalizeGameString(string value)
{
if (value.Contains('<', StringComparison.Ordinal))
{
value = MacroLiteralRegex.Replace(value, string.Empty);
}
return value.ReplaceLineEndings().Replace('', '-').Replace("\u00ad", "", StringComparison.Ordinal)
.Trim();
}
public unsafe bool IsOccupied()
{
if (!_clientState.IsLoggedIn || _objectTable.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 bool IsLoadingScreenVisible()
{
return _gameState.IsLoadingScreenVisible();
}
public int GetFreeInventorySlots()
{
return _gameState.GetFreeInventorySlots().GetValueOrDefault();
}
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->UnlockLinksBitArray.ByteLength * 8; num++)
{
if (ptr->IsUnlockLinkUnlocked(num))
{
list.Add(num);
}
}
_logger.LogDebug("Unlocked unlock links: {UnlockedUnlockLinks}", string.Join(", ", list));
return list;
}
public unsafe bool SyncToFate(uint fateId)
{
IPlayerCharacter localPlayer = _objectTable.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.LogDebug("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 ushort GetCurrentFateId()
{
PublicEvent? currentPlayerEvent = _publicEventHelper.GetCurrentPlayerEvent();
if (currentPlayerEvent.HasValue)
{
PublicEvent valueOrDefault = currentPlayerEvent.GetValueOrDefault();
if (valueOrDefault.Type == EPublicEventType.Fate)
{
return (ushort)valueOrDefault.Id;
}
}
return 0;
}
public bool IsFateStillActive(ushort fateId)
{
return _publicEventHelper.IsFateActive(fateId);
}
public uint GetItemLevel(uint itemId)
{
return _gameState.GetItemLevel(itemId);
}
private static void ExecuteCommand(string command)
{
ChatHelper.SendCommand(command);
}
}