forked from aly/qstbak
480 lines
15 KiB
C#
480 lines
15 KiB
C#
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);
|
||
}
|
||
}
|