punish v6.8.18.0

This commit is contained in:
alydev 2025-10-09 07:47:19 +10:00
commit cfb4dea47e
316 changed files with 554088 additions and 0 deletions

View file

@ -0,0 +1,153 @@
using System;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Model.Common;
using Questionable.Model.Questing;
namespace Questionable.Functions;
internal sealed class AetheryteFunctions
{
private const uint TeleportAction = 5u;
private const uint ReturnAction = 6u;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<AetheryteFunctions> _logger;
private readonly IDataManager _dataManager;
private readonly IClientState _clientState;
public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
public AetheryteFunctions(IServiceProvider serviceProvider, ILogger<AetheryteFunctions> logger, IDataManager dataManager, IClientState clientState)
{
_serviceProvider = serviceProvider;
_logger = logger;
_dataManager = dataManager;
_clientState = clientState;
}
public unsafe bool IsAetheryteUnlocked(uint aetheryteId, out byte subIndex)
{
subIndex = 0;
UIState* ptr = UIState.Instance();
if (ptr != null)
{
return ptr->IsAetheryteUnlocked(aetheryteId);
}
return false;
}
public bool IsAetheryteUnlocked(EAetheryteLocation aetheryteLocation)
{
if (aetheryteLocation.IsFirmamentAetheryte())
{
return _serviceProvider.GetRequiredService<QuestFunctions>().IsQuestComplete(new QuestId(3672));
}
byte subIndex;
return IsAetheryteUnlocked((uint)aetheryteLocation, out subIndex);
}
public unsafe bool CanTeleport(EAetheryteLocation aetheryteLocation)
{
if ((ushort)aetheryteLocation == PlayerState.Instance()->HomeAetheryteId && ActionManager.Instance()->GetActionStatus(ActionType.Action, 6u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
{
return true;
}
return ActionManager.Instance()->GetActionStatus(ActionType.Action, 5u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0;
}
public unsafe bool IsTeleportUnlocked()
{
uint rowId = _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Action>().GetRow(5u).UnlockLink.RowId;
return UIState.Instance()->IsUnlockLinkUnlocked(rowId);
}
public unsafe bool TeleportAetheryte(uint aetheryteId)
{
_logger.LogDebug("Attempting to teleport to aetheryte {AetheryteId}", aetheryteId);
if (IsAetheryteUnlocked(aetheryteId, out var subIndex))
{
if (aetheryteId == PlayerState.Instance()->HomeAetheryteId && ActionManager.Instance()->GetActionStatus(ActionType.Action, 6u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
{
ReturnRequestedAt = DateTime.Now;
if (ActionManager.Instance()->UseAction(ActionType.Action, 6u, 3758096384uL, 0u, ActionManager.UseActionMode.None, 0u, null))
{
_logger.LogInformation("Using 'return' for home aetheryte");
return true;
}
}
if (ActionManager.Instance()->GetActionStatus(ActionType.Action, 5u, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0)
{
_logger.LogInformation("Teleporting to aetheryte {AetheryteId}", aetheryteId);
return Telepo.Instance()->Teleport(aetheryteId, subIndex);
}
}
return false;
}
public bool TeleportAetheryte(EAetheryteLocation aetheryteLocation)
{
return TeleportAetheryte((uint)aetheryteLocation);
}
public unsafe bool IsFreeAetheryte(EAetheryteLocation aetheryteLocation)
{
PlayerState* ptr = PlayerState.Instance();
if (ptr != null)
{
if ((EAetheryteLocation)ptr->FreeAetheryteId != aetheryteLocation)
{
return (EAetheryteLocation)ptr->FreeAetherytePlayStationPlus == aetheryteLocation;
}
return true;
}
return false;
}
public unsafe AetheryteRegistrationResult CanRegisterFreeOrFavoriteAetheryte(EAetheryteLocation aetheryteLocation)
{
if (_clientState.LocalPlayer == null)
{
return AetheryteRegistrationResult.NotPossible;
}
PlayerState* ptr = PlayerState.Instance();
if (ptr == null)
{
return AetheryteRegistrationResult.NotPossible;
}
if (IsFreeAetheryte(aetheryteLocation))
{
return AetheryteRegistrationResult.NotPossible;
}
bool flag = false;
for (int i = 0; i < ptr->FavouriteAetheryteCount; i++)
{
if (ptr->FavouriteAetherytes[i] == (ushort)aetheryteLocation)
{
return AetheryteRegistrationResult.NotPossible;
}
if (ptr->FavouriteAetherytes[i] == 0)
{
flag = true;
break;
}
}
if (ptr->IsPlayerStateFlagSet(PlayerStateFlag.IsLoginSecurityToken) && ptr->FreeAetheryteId == 0)
{
return AetheryteRegistrationResult.SecurityTokenFreeDestinationAvailable;
}
if (!flag)
{
return AetheryteRegistrationResult.NotPossible;
}
return AetheryteRegistrationResult.FavoredDestinationAvailable;
}
}

View file

@ -0,0 +1,8 @@
namespace Questionable.Functions;
public enum AetheryteRegistrationResult
{
NotPossible,
SecurityTokenFreeDestinationAvailable,
FavoredDestinationAvailable
}

View file

@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Functions;
internal sealed class AlliedSocietyQuestFunctions
{
private sealed class NpcData
{
public required uint IssuerDataId { get; init; }
public required List<QuestInfo> AllQuests { get; init; } = new List<QuestInfo>();
}
private record struct Rng(uint S0, uint S1 = 0u, uint S2 = 0u, uint S3 = 0u)
{
public int Next(int range)
{
uint s = S3;
uint s2 = Transform(S0, S1);
uint s3 = S1;
uint s4 = S2;
S0 = s;
S1 = s2;
S2 = s3;
S3 = s4;
return (int)(S1 % range);
}
private static uint Transform(uint s0, uint s1)
{
uint num = s0 ^ (s0 << 11);
return s1 ^ num ^ ((num ^ (s1 >> 11)) >> 8);
}
}
private readonly ILogger<AlliedSocietyQuestFunctions> _logger;
private readonly Dictionary<EAlliedSociety, List<NpcData>> _questsByAlliedSociety = new Dictionary<EAlliedSociety, List<NpcData>>();
private readonly Dictionary<(uint NpcDataId, byte Seed, bool OutranksAll, bool RankedUp), List<QuestId>> _dailyQuests = new Dictionary<(uint, byte, bool, bool), List<QuestId>>();
public AlliedSocietyQuestFunctions(QuestData questData, ILogger<AlliedSocietyQuestFunctions> logger)
{
_logger = logger;
foreach (EAlliedSociety item in from x in Enum.GetValues<EAlliedSociety>()
where x != EAlliedSociety.None
select x)
{
foreach (KeyValuePair<uint, List<QuestInfo>> item2 in (from x in questData.GetAllByAlliedSociety(item)
where x.IsRepeatable
group x by x.IssuerDataId).ToDictionary((IGrouping<uint, QuestInfo> x) => x.Key, (IGrouping<uint, QuestInfo> x) => (from y in x
orderby y.AlliedSocietyQuestGroup == 3, y.QuestId
select y).ToList()))
{
item2.Deconstruct(out var key, out var value);
uint issuerDataId = key;
List<QuestInfo> allQuests = value;
NpcData npcData = new NpcData
{
IssuerDataId = issuerDataId,
AllQuests = allQuests
};
if (_questsByAlliedSociety.TryGetValue(item, out List<NpcData> value2))
{
value2.Add(npcData);
continue;
}
Dictionary<EAlliedSociety, List<NpcData>> questsByAlliedSociety = _questsByAlliedSociety;
int num = 1;
List<NpcData> list = new List<NpcData>(num);
CollectionsMarshal.SetCount(list, num);
Span<NpcData> span = CollectionsMarshal.AsSpan(list);
int index = 0;
span[index] = npcData;
questsByAlliedSociety[item] = list;
}
}
}
public unsafe List<QuestId> GetAvailableAlliedSocietyQuests(EAlliedSociety alliedSociety)
{
byte rank = QuestManager.Instance()->BeastReputation[(int)(alliedSociety - 1)].Rank;
byte currentRank = (byte)(rank & 0x7F);
if (currentRank == 0)
{
return new List<QuestId>();
}
bool flag = (rank & 0x80) != 0;
byte dailyQuestSeed = QuestManager.Instance()->DailyQuestSeed;
List<QuestId> list = new List<QuestId>();
foreach (NpcData item in _questsByAlliedSociety[alliedSociety])
{
bool flag2 = item.AllQuests.All((QuestInfo x) => currentRank > x.AlliedSocietyRank);
(uint, byte, bool, bool) key = (item.IssuerDataId, dailyQuestSeed, flag2, flag);
if (_dailyQuests.TryGetValue(key, out List<QuestId> value))
{
list.AddRange(value);
continue;
}
List<QuestId> list2 = CalculateAvailableQuests(item.AllQuests, dailyQuestSeed, flag2, currentRank, flag);
_logger.LogInformation("Available for {Tribe} (Seed: {Seed}, Issuer: {IssuerId}): {Quests}", alliedSociety, dailyQuestSeed, item.IssuerDataId, string.Join(", ", list2));
_dailyQuests[key] = list2;
list.AddRange(list2);
}
return list;
}
private static List<QuestId> CalculateAvailableQuests(List<QuestInfo> allQuests, byte seed, bool outranksAll, byte currentRank, bool rankedUp)
{
List<QuestInfo> list = allQuests.Where((QuestInfo q) => IsEligible(q, currentRank, rankedUp)).ToList();
List<QuestInfo> list2 = new List<QuestInfo>();
if (list.Count == 0)
{
return new List<QuestId>();
}
Rng rng = new Rng(seed);
if (outranksAll)
{
int num = 0;
for (int num2 = Math.Min(list.Count, 3); num < num2; num++)
{
int num3 = rng.Next(list.Count);
while (list2.Contains(list[num3]))
{
num3 = (num3 + 1) % list.Count;
}
list2.Add(list[num3]);
}
}
else
{
int num4 = list.FindIndex((QuestInfo q) => q.AlliedSocietyQuestGroup == 3);
if (num4 >= 0)
{
list2.Add(list[num4 + rng.Next(list.Count - num4)]);
}
else
{
num4 = list.Count;
}
int num5 = list2.Count;
for (int num6 = Math.Min(num4, 3); num5 < num6; num5++)
{
int num7 = rng.Next(num4);
while (list2.Contains(list[num7]))
{
num7 = (num7 + 1) % num4;
}
list2.Add(list[num7]);
}
}
return list2.Select((QuestInfo x) => (QuestId)x.QuestId).ToList();
}
private static bool IsEligible(QuestInfo questInfo, byte currentRank, bool rankedUp)
{
if (!rankedUp)
{
return questInfo.AlliedSocietyRank <= currentRank;
}
return questInfo.AlliedSocietyRank == currentRank;
}
}

View file

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Game;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Logging;
using Questionable.Model.Questing;
namespace Questionable.Functions;
internal sealed class ChatFunctions
{
private delegate void ProcessChatBoxDelegate(nint uiModule, nint message, nint unused, byte a4);
private static class Signatures
{
internal const string SendChat = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F2 48 8B F9 45 84 C9";
}
[StructLayout(LayoutKind.Explicit)]
private readonly struct ChatPayload : IDisposable
{
[FieldOffset(0)]
private readonly nint textPtr;
[FieldOffset(16)]
private readonly ulong textLen;
[FieldOffset(8)]
private readonly ulong unk1;
[FieldOffset(24)]
private readonly ulong unk2;
internal ChatPayload(byte[] stringBytes)
{
textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30);
Marshal.Copy(stringBytes, 0, textPtr, stringBytes.Length);
Marshal.WriteByte(textPtr + stringBytes.Length, 0);
textLen = (ulong)(stringBytes.Length + 1);
unk1 = 64uL;
unk2 = 0uL;
}
public void Dispose()
{
Marshal.FreeHGlobal(textPtr);
}
}
private readonly ReadOnlyDictionary<EEmote, string> _emoteCommands;
private readonly GameFunctions _gameFunctions;
private readonly ITargetManager _targetManager;
private readonly ILogger<ChatFunctions> _logger;
private readonly ProcessChatBoxDelegate _processChatBox;
public ChatFunctions(ISigScanner sigScanner, IDataManager dataManager, GameFunctions gameFunctions, ITargetManager targetManager, ILogger<ChatFunctions> logger)
{
_gameFunctions = gameFunctions;
_targetManager = targetManager;
_logger = logger;
_processChatBox = Marshal.GetDelegateForFunctionPointer<ProcessChatBoxDelegate>(sigScanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F2 48 8B F9 45 84 C9"));
_emoteCommands = (from x in dataManager.GetExcelSheet<Emote>()
where x.RowId != 0
where x.TextCommand.IsValid
select (RowId: x.RowId, Command: x.TextCommand.Value.Command.ToString()) into x
where !string.IsNullOrEmpty(x.Command) && x.Command.StartsWith('/')
select x).ToDictionary<(uint, string), EEmote, string>(((uint RowId, string Command) x) => (EEmote)x.RowId, ((uint RowId, string Command) x) => x.Command).AsReadOnly();
}
private unsafe void SendMessageUnsafe(byte[] message)
{
nint uIModule = (nint)Framework.Instance()->GetUIModule();
using ChatPayload structure = new ChatPayload(message);
nint num = Marshal.AllocHGlobal(400);
Marshal.StructureToPtr(structure, num, fDeleteOld: false);
_processChatBox(uIModule, num, IntPtr.Zero, 0);
Marshal.FreeHGlobal(num);
}
private void SendMessage(string message)
{
_logger.LogDebug("Attempting to send chat message '{Message}'", message);
byte[] bytes = Encoding.UTF8.GetBytes(message);
if (bytes.Length == 0)
{
throw new ArgumentException("message is empty", "message");
}
if (bytes.Length > 500)
{
throw new ArgumentException("message is longer than 500 bytes", "message");
}
if (message.Length != SanitiseText(message).Length)
{
throw new ArgumentException("message contained invalid characters", "message");
}
SendMessageUnsafe(bytes);
}
private unsafe string SanitiseText(string text)
{
Utf8String* intPtr = Utf8String.FromString(text);
intPtr->SanitizeString(AllowedEntities.UppercaseLetters | AllowedEntities.LowercaseLetters | AllowedEntities.Numbers | AllowedEntities.SpecialCharacters | AllowedEntities.CharacterList | AllowedEntities.OtherCharacters | AllowedEntities.Payloads | AllowedEntities.Unknown9, null);
string result = intPtr->ToString();
intPtr->Dtor();
IMemorySpace.Free(intPtr);
return result;
}
public void ExecuteCommand(string command)
{
if (command.StartsWith('/'))
{
SendMessage(command);
}
}
public void UseEmote(uint dataId, EEmote emote)
{
IGameObject gameObject = _gameFunctions.FindObjectByDataId(dataId);
if (gameObject != null)
{
_targetManager.Target = gameObject;
ExecuteCommand(_emoteCommands[emote] + " motion");
}
}
public void UseEmote(EEmote emote)
{
ExecuteCommand(_emoteCommands[emote] + " motion");
}
}

View file

@ -0,0 +1,103 @@
using System;
using System.Linq;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using LLib;
using Lumina.Excel.Exceptions;
using Lumina.Excel.Sheets;
using Lumina.Text.ReadOnly;
using Microsoft.Extensions.Logging;
using Questionable.Model;
namespace Questionable.Functions;
internal sealed class ExcelFunctions
{
private readonly IDataManager _dataManager;
private readonly ILogger<ExcelFunctions> _logger;
public ExcelFunctions(IDataManager dataManager, ILogger<ExcelFunctions> logger)
{
_dataManager = dataManager;
_logger = logger;
}
public StringOrRegex GetDialogueText(Questionable.Model.Quest? currentQuest, string? excelSheetName, string key, bool isRegex)
{
ReadOnlySeString? rawDialogueText = GetRawDialogueText(currentQuest, excelSheetName, key);
if (isRegex)
{
return new StringOrRegex(rawDialogueText.ToRegex());
}
return new StringOrRegex(rawDialogueText?.WithCertainMacroCodeReplacements());
}
public ReadOnlySeString? GetRawDialogueText(Questionable.Model.Quest? currentQuest, string? excelSheetName, string key)
{
if (currentQuest != null && excelSheetName == null)
{
Lumina.Excel.Sheets.Quest? rowOrDefault = _dataManager.GetExcelSheet<Lumina.Excel.Sheets.Quest>().GetRowOrDefault((uint)(currentQuest.Id.Value + 65536));
if (!rowOrDefault.HasValue)
{
_logger.LogError("Could not find quest row for {QuestId}", currentQuest.Id);
return null;
}
excelSheetName = $"quest/{currentQuest.Id.Value / 100:000}/{rowOrDefault.Value.Id}";
}
ArgumentNullException.ThrowIfNull(excelSheetName, "excelSheetName");
try
{
IDataManager dataManager = _dataManager;
string name = excelSheetName;
return dataManager.GetExcelSheet<QuestDialogueText>(null, name).Cast<QuestDialogueText?>().FirstOrDefault((QuestDialogueText? x) => x.Value.Key == key)?.Value;
}
catch (SheetNotFoundException innerException)
{
throw new SheetNotFoundException("Sheet '" + excelSheetName + "' not found", innerException);
}
}
public StringOrRegex GetDialogueTextByRowId(string? excelSheet, uint rowId, bool isRegex)
{
ReadOnlySeString? rawDialogueTextByRowId = GetRawDialogueTextByRowId(excelSheet, rowId);
if (isRegex)
{
return new StringOrRegex(rawDialogueTextByRowId.ToRegex());
}
return new StringOrRegex(rawDialogueTextByRowId?.ToDalamudString().ToString());
}
public ReadOnlySeString? GetRawDialogueTextByRowId(string? excelSheet, uint rowId)
{
bool flag;
switch (excelSheet)
{
case "GimmickYesNo":
return _dataManager.GetExcelSheet<GimmickYesNo>().GetRowOrDefault(rowId)?.Unknown0;
case "Warp":
return _dataManager.GetExcelSheet<Warp>().GetRowOrDefault(rowId)?.Name;
case "Addon":
return _dataManager.GetExcelSheet<Addon>().GetRowOrDefault(rowId)?.Text;
case "EventPathMove":
return _dataManager.GetExcelSheet<EventPathMove>().GetRowOrDefault(rowId)?.Unknown0;
case "GilShop":
return _dataManager.GetExcelSheet<GilShop>().GetRowOrDefault(rowId)?.Name;
default:
if (excelSheet != null)
{
flag = false;
break;
}
goto case "ContentTalk";
case "ContentTalk":
flag = true;
break;
}
if (flag)
{
return _dataManager.GetExcelSheet<ContentTalk>().GetRowOrDefault(rowId)?.Text;
}
throw new ArgumentOutOfRangeException("excelSheet", "Unsupported excel sheet " + excelSheet);
}
}

View file

@ -0,0 +1,543 @@
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.Object;
using FFXIVClientStructs.FFXIV.Client.Game.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.DataId == 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.DataId, 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.DataId != 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;
}
}

View file

@ -0,0 +1,9 @@
namespace Questionable.Functions;
public enum MainScenarioQuestState
{
Unavailable,
Available,
Complete,
LoadingScreen
}

View file

@ -0,0 +1,8 @@
using Questionable.Model.Questing;
namespace Questionable.Functions;
internal sealed record PriorityQuestInfo(ElementId QuestId, string? UnavailableReason = null)
{
public bool IsAvailable => UnavailableReason == null;
}

View file

@ -0,0 +1,959 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Dalamud.Game.ClientState.Aetherytes;
using Dalamud.Game.Text;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameData;
using LLib.GameUI;
using Lumina.Excel.Sheets;
using Questionable.Controller;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.Common;
using Questionable.Model.Questing;
namespace Questionable.Functions;
internal sealed class QuestFunctions
{
private readonly QuestRegistry _questRegistry;
private readonly QuestData _questData;
private readonly AetheryteFunctions _aetheryteFunctions;
private readonly AlliedSocietyQuestFunctions _alliedSocietyQuestFunctions;
private readonly AlliedSocietyData _alliedSocietyData;
private readonly Configuration _configuration;
private readonly IDataManager _dataManager;
private readonly IClientState _clientState;
private readonly IGameGui _gameGui;
private readonly IAetheryteList _aetheryteList;
public QuestFunctions(QuestRegistry questRegistry, QuestData questData, AetheryteFunctions aetheryteFunctions, AlliedSocietyQuestFunctions alliedSocietyQuestFunctions, AlliedSocietyData alliedSocietyData, Configuration configuration, IDataManager dataManager, IClientState clientState, IGameGui gameGui, IAetheryteList aetheryteList)
{
_questRegistry = questRegistry;
_questData = questData;
_aetheryteFunctions = aetheryteFunctions;
_alliedSocietyQuestFunctions = alliedSocietyQuestFunctions;
_alliedSocietyData = alliedSocietyData;
_configuration = configuration;
_dataManager = dataManager;
_clientState = clientState;
_gameGui = gameGui;
_aetheryteList = aetheryteList;
}
public unsafe QuestReference GetCurrentQuest(bool allowNewMsq = true)
{
QuestReference currentQuestInternal = GetCurrentQuestInternal(allowNewMsq);
currentQuestInternal.Deconstruct(out ElementId CurrentQuest, out byte Sequence, out MainScenarioQuestState State);
ElementId elementId = CurrentQuest;
byte sequence = Sequence;
MainScenarioQuestState state = State;
PlayerState* ptr = PlayerState.Instance();
if (elementId == null || elementId.Value == 0)
{
if (_clientState.TerritoryType == 181)
{
return new QuestReference(new QuestId(107), 0, MainScenarioQuestState.Available);
}
if (_clientState.TerritoryType == 182)
{
return new QuestReference(new QuestId(594), 0, MainScenarioQuestState.Available);
}
if (_clientState.TerritoryType == 183)
{
return new QuestReference(new QuestId(39), 0, MainScenarioQuestState.Available);
}
return QuestReference.NoQuest(state);
}
if (elementId.Value == 681)
{
if (IsQuestAccepted(elementId) || IsQuestComplete(elementId))
{
return new QuestReference(elementId, sequence, state);
}
return _configuration.General.GrandCompany switch
{
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.TwinAdder => new QuestReference(new QuestId(680), 0, state),
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.Maelstrom => new QuestReference(new QuestId(681), 0, state),
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.ImmortalFlames => new QuestReference(new QuestId(682), 0, state),
_ => QuestReference.NoQuest(MainScenarioQuestState.Unavailable),
};
}
if (elementId.Value == 3856 && !ptr->IsMountUnlocked(1u))
{
ushort num = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)ptr->GrandCompany switch
{
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.TwinAdder => 700,
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.Maelstrom => 701,
FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.ImmortalFlames => 702,
_ => 0,
};
if (num != 0 && !QuestManager.IsQuestComplete(num))
{
return new QuestReference(new QuestId(num), QuestManager.GetQuestSequence(num), state);
}
}
else if (elementId.Value == 801)
{
QuestId questId = new QuestId(802);
if (IsQuestAccepted(questId))
{
return new QuestReference(questId, QuestManager.GetQuestSequence(questId.Value), state);
}
}
return currentQuestInternal;
}
public unsafe QuestReference GetCurrentQuestInternal(bool allowNewMsq)
{
QuestManager* ptr = QuestManager.Instance();
if (ptr == null)
{
return QuestReference.NoQuest(MainScenarioQuestState.Unavailable);
}
QuestReference questReference = GetMainScenarioQuest().Item1;
if (questReference.CurrentQuest != null && !_questRegistry.IsKnownQuest(questReference.CurrentQuest))
{
questReference = QuestReference.NoQuest(questReference.State);
}
if (questReference.CurrentQuest != null && !IsQuestAccepted(questReference.CurrentQuest))
{
if (allowNewMsq)
{
return questReference;
}
questReference = QuestReference.NoQuest(questReference.State);
}
List<(ElementId, byte)> list = new List<(ElementId, byte)>();
for (int num = ptr->TrackedQuests.Length - 1; num >= 0; num--)
{
TrackingWork trackingWork = ptr->TrackedQuests[num];
switch (trackingWork.QuestType)
{
case 1:
{
ElementId elementId = new QuestId(ptr->NormalQuests[trackingWork.Index].QuestId);
if (_questRegistry.IsKnownQuest(elementId))
{
list.Add((elementId, QuestManager.GetQuestSequence(elementId.Value)));
}
break;
}
}
}
if (_configuration.General.SkipLowPriorityDuties && list.Count > 0)
{
IReadOnlyList<(uint ContentFinderConditionId, ElementId QuestId, int Sequence)> lowPriorityQuests = _questRegistry.LowPriorityContentFinderConditionQuests;
list.RemoveAll(((ElementId Quest, byte Sequence) x) => lowPriorityQuests.Any<(uint, ElementId, int)>(((uint ContentFinderConditionId, ElementId QuestId, int Sequence) y) => x.Quest == y.QuestId && x.Sequence == y.Sequence));
}
if (list.Count > 0)
{
(ElementId, byte) tuple = list.First();
ElementId item = tuple.Item1;
byte item2 = tuple.Item2;
EAlliedSociety firstTrackedAlliedSociety = _alliedSocietyData.GetCommonAlliedSocietyTurnIn(item);
if (firstTrackedAlliedSociety != EAlliedSociety.None)
{
List<(ElementId, byte)> list2 = (from quest in list.Skip(1)
where _alliedSocietyData.GetCommonAlliedSocietyTurnIn(quest.Quest) == firstTrackedAlliedSociety
select quest).ToList();
if (list2.Count > 0)
{
if (item2 == byte.MaxValue)
{
foreach (var (currentQuest, b) in list2)
{
if (b != byte.MaxValue)
{
return new QuestReference(currentQuest, b, questReference.State);
}
}
}
else if (!IsOnAlliedSocietyMount())
{
list2.Insert(0, (item, item2));
_alliedSocietyData.GetCommonAlliedSocietyNpcs(firstTrackedAlliedSociety, out uint[] normalNpcs, out uint[] _);
if (normalNpcs.Length != 0)
{
(ElementId, byte)? tuple3 = (from x in list2
where x.Sequence < byte.MaxValue
where IsInteractSequence(x.Quest, x.Sequence, normalNpcs)
select x).Cast<(ElementId, byte)?>().FirstOrDefault();
if (tuple3.HasValue)
{
return new QuestReference(tuple3.Value.Item1, tuple3.Value.Item2, questReference.State);
}
}
}
}
}
return new QuestReference(item, item2, questReference.State);
}
ElementId elementId2 = (from x in GetNextPriorityQuestsThatCanBeAccepted()
where x.IsAvailable
select x.QuestId).FirstOrDefault();
if (elementId2 != null)
{
return new QuestReference(elementId2, QuestManager.GetQuestSequence(elementId2.Value), questReference.State);
}
if (questReference.CurrentQuest != null)
{
return questReference;
}
return QuestReference.NoQuest(questReference.State);
}
public unsafe (QuestReference, string?) GetMainScenarioQuest()
{
if (QuestManager.IsQuestComplete(3759))
{
AgentInterface* agentByInternalId = AgentModule.Instance()->GetAgentByInternalId(AgentId.QuestRedoHud);
if (agentByInternalId != null && agentByInternalId->IsAgentActive())
{
AtkUnitBase* addonPtr;
bool flag = _gameGui.TryGetAddonByName<AtkUnitBase>("QuestRedoHud", out addonPtr) && addonPtr->AtkValuesCount == 4;
if (flag)
{
uint uInt = addonPtr->AtkValues->UInt;
bool flag2 = ((uInt == 0 || uInt - 2 <= 2) ? true : false);
flag = flag2;
}
if (flag)
{
ushort num = MemoryHelper.Read<ushort>((nint)((byte*)agentByInternalId + 46));
return (new QuestReference(new QuestId(num), QuestManager.GetQuestSequence(num), MainScenarioQuestState.Available), "NG+");
}
}
}
AgentScenarioTree* ptr = AgentScenarioTree.Instance();
if (ptr == null)
{
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "No Scenario Tree");
}
if (ptr->Data == null)
{
return (QuestReference.NoQuest(MainScenarioQuestState.LoadingScreen), "Scenario Tree Data is null");
}
QuestId questId = new QuestId(ptr->Data->CurrentScenarioQuest);
string item = $"sq: {questId}";
if (questId.Value == 0)
{
if (IsMainScenarioQuestComplete())
{
return (QuestReference.NoQuest(MainScenarioQuestState.Complete), "Main Scenario is complete");
}
PlayerState* playerState = PlayerState.Instance();
List<QuestInfo> list = (from q in _questData.MainScenarioQuests
where q.StartingCity == 0 || q.StartingCity == playerState->StartTown
where IsReadyToAcceptQuest(q.QuestId, ignoreLevel: true)
select q).ToList();
if (list.Count == 0)
{
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "No potential quests found");
}
if (list.Count > 1)
{
if (list.All(delegate(QuestInfo x)
{
ushort value = x.QuestId.Value;
return (uint)(value - 680) <= 2u;
}))
{
questId = new QuestId(681);
}
else if (list.Any((QuestInfo x) => x.QuestId.Value == 1583))
{
questId = new QuestId(1583);
}
else if (list.Any((QuestInfo x) => x.QuestId.Value == 2451))
{
questId = new QuestId(2451);
}
else if (list.Any((QuestInfo x) => x.QuestId.Value == 3282))
{
questId = new QuestId(3282);
}
else if (list.Any((QuestInfo x) => x.QuestId.Value == 4359))
{
questId = new QuestId(4359);
}
else if (list.Any((QuestInfo x) => x.QuestId.Value == 4865))
{
questId = new QuestId(4865);
}
if (list.Count != 1)
{
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "Multiple potential quests found: " + string.Join(", ", list.Select((QuestInfo x) => x.QuestId.Value)));
}
}
else
{
questId = (QuestId)list.Single().QuestId;
}
}
QuestManager* ptr2 = QuestManager.Instance();
if (IsQuestAccepted(questId) && ptr2->GetQuestById(questId.Value)->IsHidden)
{
return (QuestReference.NoQuest(MainScenarioQuestState.Available), "Quest accepted but hidden");
}
if (IsQuestComplete(questId))
{
return (new QuestReference(questId, byte.MaxValue, MainScenarioQuestState.Available), $"Quest {questId.Value} complete");
}
if (!IsReadyToAcceptQuest(questId))
{
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), $"Not readdy to accept quest {questId.Value}");
}
byte? b = _clientState.LocalPlayer?.Level;
if (!b.HasValue)
{
return (QuestReference.NoQuest(MainScenarioQuestState.LoadingScreen), "In loading screen");
}
if (_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest) && quest.Info.Level > b)
{
return (QuestReference.NoQuest(MainScenarioQuestState.Unavailable), "Low level");
}
return (new QuestReference(questId, QuestManager.GetQuestSequence(questId.Value), MainScenarioQuestState.Available), item);
}
private unsafe bool IsOnAlliedSocietyMount()
{
BattleChara* ptr = (BattleChara*)(_clientState.LocalPlayer?.Address ?? 0);
if (ptr != null && ptr->Mount.MountId != 0)
{
return _alliedSocietyData.Mounts.ContainsKey(ptr->Mount.MountId);
}
return false;
}
private bool IsInteractSequence(ElementId questId, byte sequenceNo, uint[] dataIds)
{
if (_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest))
{
return quest.FindSequence(sequenceNo)?.Steps.All(delegate(QuestStep x)
{
if (x == null || x.InteractionType != EInteractionType.WalkTo)
{
if (x != null && x.InteractionType == EInteractionType.Interact)
{
uint? dataId = x.DataId;
if (dataId.HasValue)
{
uint valueOrDefault = dataId.GetValueOrDefault();
return dataIds.Contains(valueOrDefault);
}
}
return false;
}
return true;
}) ?? false;
}
return false;
}
public unsafe QuestProgressInfo? GetQuestProgressInfo(ElementId elementId)
{
if (elementId is QuestId questId)
{
QuestWork* questById = QuestManager.Instance()->GetQuestById(questId.Value);
if (questById == null)
{
return null;
}
return new QuestProgressInfo(*questById);
}
return null;
}
public unsafe List<PriorityQuestInfo> GetNextPriorityQuestsThatCanBeAccepted()
{
InventoryManager* ptr = InventoryManager.Instance();
int gil = ptr->GetItemCountInContainer(1u, InventoryType.Currency, isHq: false, 0);
return (from x in GetPriorityQuests()
where IsReadyToAcceptQuest(x)
select x).Select(delegate(ElementId x)
{
if (!_questRegistry.TryGetQuest(x, out Questionable.Model.Quest quest))
{
return new PriorityQuestInfo(x, "Unknown quest");
}
QuestStep questStep = quest.FindSequence(0)?.FindStep(0);
if (questStep == null)
{
return new PriorityQuestInfo(x, "No sequence 0 with steps");
}
if (!_aetheryteFunctions.IsTeleportUnlocked())
{
return new PriorityQuestInfo(x, "Teleport not unlocked");
}
if (!questStep.IsTeleportableForPriorityQuests())
{
return new PriorityQuestInfo(x, "Can't teleport to start");
}
DefaultInterpolatedStringHandler handler;
if (gil < EstimateTeleportCosts(quest))
{
IFormatProvider invariantCulture = CultureInfo.InvariantCulture;
handler = new DefaultInterpolatedStringHandler(32, 2, invariantCulture);
handler.AppendLiteral("Not enough gil, estimated cost: ");
handler.AppendFormatted(EstimateTeleportCosts(quest), "N0");
handler.AppendFormatted(SeIconChar.Gil.ToIconString());
return new PriorityQuestInfo(x, string.Create(invariantCulture, ref handler));
}
EAetheryteLocation? value = quest.AllSteps().Select<(QuestSequence, int, QuestStep), EAetheryteLocation?>(delegate((QuestSequence Sequence, int StepId, QuestStep Step) y)
{
EAetheryteLocation? aetheryteShortcut = y.Step.AetheryteShortcut;
if (aetheryteShortcut.HasValue)
{
EAetheryteLocation valueOrDefault = aetheryteShortcut.GetValueOrDefault();
if (!_aetheryteFunctions.IsAetheryteUnlocked(valueOrDefault))
{
SkipConditions? skipConditions = y.Step.SkipConditions;
if (skipConditions == null || skipConditions.AetheryteShortcutIf?.AetheryteLocked != valueOrDefault)
{
return valueOrDefault;
}
}
}
AethernetShortcut aethernetShortcut = y.Step.AethernetShortcut;
if (aethernetShortcut != null)
{
if (!_aetheryteFunctions.IsAetheryteUnlocked(aethernetShortcut.From))
{
return aethernetShortcut.From;
}
if (!_aetheryteFunctions.IsAetheryteUnlocked(aethernetShortcut.To))
{
return aethernetShortcut.To;
}
}
return (EAetheryteLocation?)null;
}).FirstOrDefault((EAetheryteLocation? y) => y.HasValue);
if (value.HasValue)
{
handler = new DefaultInterpolatedStringHandler(18, 1);
handler.AppendLiteral("Aetheryte locked: ");
handler.AppendFormatted(value);
return new PriorityQuestInfo(x, handler.ToStringAndClear());
}
return new PriorityQuestInfo(x);
}).ToList();
}
private int EstimateTeleportCosts(Questionable.Model.Quest quest)
{
Dictionary<EAetheryteLocation, float> cheapTeleports = _aetheryteList.Where((IAetheryteEntry x) => x.IsFavourite || _aetheryteFunctions.IsFreeAetheryte((EAetheryteLocation)x.AetheryteId)).ToDictionary((IAetheryteEntry x) => (EAetheryteLocation)x.AetheryteId, (IAetheryteEntry x) => _aetheryteFunctions.IsFreeAetheryte((EAetheryteLocation)x.AetheryteId) ? 0f : 0.5f);
int baseCost = ((quest.Info.Expansion == EExpansionVersion.ARealmReborn) ? 300 : 1000);
return (from x in quest.AllSteps()
where x.Step.AetheryteShortcut.HasValue
select x).Sum<(QuestSequence, int, QuestStep)>(((QuestSequence Sequence, int StepId, QuestStep Step) x) => (int)((float)baseCost * cheapTeleports.GetValueOrDefault(x.Step.AetheryteShortcut.Value, 1f)));
}
public List<ElementId> GetPriorityQuests(bool onlyClassAndRoleQuests = false)
{
List<ElementId> list = new List<ElementId>();
if (!onlyClassAndRoleQuests)
{
if (!_configuration.Advanced.SkipARealmRebornHardModePrimals)
{
list.AddRange(QuestData.HardModePrimals.Skip(1));
}
if (!_configuration.Advanced.SkipCrystalTowerRaids)
{
list.AddRange(QuestData.CrystalTowerQuests);
}
}
if (!_configuration.Advanced.SkipClassJobQuests)
{
EClassJob valueOrDefault = ((EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId).GetValueOrDefault();
uint[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select((IReadOnlyList<uint> x) => x[0]).ToArray();
if (valueOrDefault != EClassJob.Adventurer)
{
list.AddRange(from x in _questRegistry.GetKnownClassJobQuests(valueOrDefault).Where(delegate(QuestInfo x)
{
if (_questRegistry.TryGetQuest(x.QuestId, out Questionable.Model.Quest quest))
{
IQuestInfo info = quest.Info;
QuestInfo questInfo = info as QuestInfo;
if (questInfo != null)
{
if (shadowbringersRoleQuestChapters.Contains(questInfo.NewGamePlusChapter))
{
return !QuestData.FinalShadowbringersRoleQuests.Any(IsQuestComplete);
}
if (QuestData.AllRoleQuestChapters.Any((IReadOnlyList<uint> y) => y.Contains(questInfo.NewGamePlusChapter)))
{
return false;
}
return true;
}
}
return false;
})
select x.QuestId);
}
}
return list.Where(_questRegistry.IsKnownQuest).ToList();
}
public unsafe bool IsReadyToAcceptQuest(ElementId questId, bool ignoreLevel = false)
{
_questRegistry.TryGetQuest(questId, out Questionable.Model.Quest quest);
if (quest != null)
{
IQuestInfo info = quest.Info;
if (info != null && info.IsRepeatable)
{
if (IsQuestAccepted(questId))
{
return false;
}
if (questId is QuestId questId2 && IsDailyAlliedSocietyQuest(questId2))
{
if (QuestManager.Instance()->IsDailyQuestCompleted(questId.Value))
{
return false;
}
if (!IsDailyAlliedSocietyQuestAndAvailableToday(questId2))
{
return false;
}
}
else if (IsQuestComplete(questId))
{
return false;
}
goto IL_0077;
}
}
if (IsQuestAcceptedOrComplete(questId))
{
return false;
}
goto IL_0077;
IL_0077:
if (IsQuestLocked(questId))
{
return false;
}
if (!ignoreLevel)
{
byte b = _clientState.LocalPlayer?.Level ?? 0;
if (b != 0 && quest != null && quest.Info.Level > b)
{
return false;
}
}
return true;
}
public bool IsQuestAcceptedOrComplete(ElementId elementId)
{
if (!IsQuestComplete(elementId))
{
return IsQuestAccepted(elementId);
}
return true;
}
public bool IsQuestAccepted(ElementId elementId)
{
if (elementId is QuestId questId)
{
return IsQuestAccepted(questId);
}
if (elementId is SatisfactionSupplyNpcId)
{
return false;
}
if (elementId is AlliedSocietyDailyId)
{
return false;
}
if (elementId is UnlockLinkId)
{
return false;
}
throw new ArgumentOutOfRangeException("elementId");
}
public unsafe bool IsQuestAccepted(QuestId questId)
{
return QuestManager.Instance()->IsQuestAccepted(questId.Value);
}
public bool IsQuestComplete(ElementId elementId)
{
if (elementId is QuestId questId)
{
return IsQuestComplete(questId);
}
if (elementId is SatisfactionSupplyNpcId)
{
return false;
}
if (elementId is AlliedSocietyDailyId)
{
return false;
}
if (elementId is UnlockLinkId unlockLinkId)
{
return IsQuestComplete(unlockLinkId);
}
throw new ArgumentOutOfRangeException("elementId");
}
public bool IsQuestComplete(QuestId questId)
{
return QuestManager.IsQuestComplete(questId.Value);
}
public unsafe bool IsQuestComplete(UnlockLinkId unlockLinkId)
{
return UIState.Instance()->IsUnlockLinkUnlocked(unlockLinkId.Value);
}
public bool IsQuestLocked(ElementId elementId, ElementId? extraCompletedQuest = null)
{
if (elementId is QuestId questId)
{
return IsQuestLocked(questId, extraCompletedQuest);
}
if (elementId is SatisfactionSupplyNpcId satisfactionSupplyNpcId)
{
return IsQuestLocked(satisfactionSupplyNpcId);
}
if (elementId is AlliedSocietyDailyId alliedSocietyDailyId)
{
return IsQuestLocked(alliedSocietyDailyId);
}
if (elementId is UnlockLinkId unlockLinkId)
{
return IsQuestLocked(unlockLinkId);
}
throw new ArgumentOutOfRangeException("elementId");
}
private unsafe bool IsQuestLocked(QuestId questId, ElementId? extraCompletedQuest = null)
{
if (IsQuestUnobtainable(questId, extraCompletedQuest))
{
return true;
}
QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
if (questInfo.GrandCompany != FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany.None && questInfo.GrandCompany != GetGrandCompany())
{
return true;
}
if (questInfo.AlliedSociety != EAlliedSociety.None && questInfo.IsRepeatable)
{
return !IsDailyAlliedSocietyQuestAndAvailableToday(questId);
}
if (questInfo.IsMoogleDeliveryQuest)
{
byte b = PlayerState.Instance()->DeliveryLevel;
if (extraCompletedQuest != null && _questData.TryGetQuestInfo(extraCompletedQuest, out IQuestInfo questInfo2) && questInfo2 is QuestInfo { IsMoogleDeliveryQuest: not false })
{
b++;
}
if (questInfo.MoogleDeliveryLevel > b)
{
return true;
}
}
if (HasCompletedPreviousQuests(questInfo, extraCompletedQuest))
{
return !HasCompletedPreviousInstances(questInfo);
}
return true;
}
private bool IsQuestLocked(SatisfactionSupplyNpcId satisfactionSupplyNpcId)
{
SatisfactionSupplyInfo questInfo = (SatisfactionSupplyInfo)_questData.GetQuestInfo(satisfactionSupplyNpcId);
return !HasCompletedPreviousQuests(questInfo, null);
}
private unsafe bool IsQuestLocked(AlliedSocietyDailyId alliedSocietyDailyId)
{
byte beastTribeRank = PlayerState.Instance()->GetBeastTribeRank(alliedSocietyDailyId.AlliedSociety);
if (beastTribeRank != 0)
{
return beastTribeRank < alliedSocietyDailyId.Rank;
}
return true;
}
private static bool IsQuestLocked(UnlockLinkId unlockLinkId)
{
return IsQuestUnobtainable(unlockLinkId);
}
public bool IsDailyAlliedSocietyQuest(QuestId questId)
{
QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
if (questInfo.AlliedSociety != EAlliedSociety.None)
{
return questInfo.IsRepeatable;
}
return false;
}
public bool IsDailyAlliedSocietyQuestAndAvailableToday(QuestId questId)
{
if (!IsDailyAlliedSocietyQuest(questId))
{
return false;
}
QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
return _alliedSocietyQuestFunctions.GetAvailableAlliedSocietyQuests(questInfo.AlliedSociety).Contains(questId);
}
public bool IsQuestUnobtainable(ElementId elementId, ElementId? extraCompletedQuest = null)
{
if (elementId is QuestId questId)
{
return IsQuestUnobtainable(questId, extraCompletedQuest);
}
if (elementId is UnlockLinkId unlockLinkId)
{
return IsQuestUnobtainable(unlockLinkId);
}
return false;
}
public unsafe bool IsQuestUnobtainable(QuestId questId, ElementId? extraCompletedQuest = null)
{
QuestInfo questInfo = (QuestInfo)_questData.GetQuestInfo(questId);
if ((int)questInfo.Expansion > (int)PlayerState.Instance()->MaxExpansion)
{
return true;
}
if (questInfo.QuestLocks.Count > 0)
{
int num = questInfo.QuestLocks.Count((QuestId x) => IsQuestComplete(x) || x.Equals(extraCompletedQuest));
if (questInfo.QuestLockJoin == EQuestJoin.All && questInfo.QuestLocks.Count == num)
{
return true;
}
if (questInfo.QuestLockJoin == EQuestJoin.AtLeastOne && num > 0)
{
return true;
}
}
if (_questData.GetLockedClassQuests().Contains(questId))
{
return true;
}
byte startTown = PlayerState.Instance()->StartTown;
if (questInfo.StartingCity > 0 && questInfo.StartingCity != startTown)
{
return true;
}
if (questId.Value == 674 && startTown == 3)
{
return true;
}
if (questId.Value == 673 && startTown != 3)
{
return true;
}
Dictionary<ushort, EClassJob> dictionary = new Dictionary<ushort, EClassJob>
{
{
108,
EClassJob.Marauder
},
{
109,
EClassJob.Arcanist
},
{
85,
EClassJob.Lancer
},
{
123,
EClassJob.Archer
},
{
124,
EClassJob.Conjurer
},
{
568,
EClassJob.Gladiator
},
{
569,
EClassJob.Pugilist
},
{
570,
EClassJob.Thaumaturge
}
};
if (dictionary.TryGetValue(questId.Value, out var value) && dictionary.Any((KeyValuePair<ushort, EClassJob> x) => IsQuestAcceptedOrComplete(new QuestId(x.Key))) && (EClassJob)PlayerState.Instance()->FirstClass != value)
{
return true;
}
if (IsQuestRemoved(questId))
{
return true;
}
return false;
}
private static bool IsQuestUnobtainable(UnlockLinkId unlockLinkId)
{
if (unlockLinkId.Value == 506)
{
return !IsFestivalActive(160, (ushort)2);
}
if (unlockLinkId.Value == 568)
{
return !IsFestivalActive(160, (ushort)3);
}
return true;
}
private unsafe static bool IsFestivalActive(ushort id, ushort? phase = null)
{
for (int i = 0; i < GameMain.Instance()->ActiveFestivals.Length; i++)
{
GameMain.Festival festival = GameMain.Instance()->ActiveFestivals[i];
if (festival.Id == id)
{
if (phase.HasValue)
{
return festival.Phase == phase;
}
return true;
}
}
return false;
}
public bool IsQuestRemoved(ElementId elementId)
{
if (elementId is QuestId questId)
{
return IsQuestRemoved(questId);
}
return false;
}
private bool IsQuestRemoved(QuestId questId)
{
ushort value = questId.Value;
if (value == 487 || (uint)(value - 1428) <= 1u)
{
return true;
}
return false;
}
private bool HasCompletedPreviousQuests(IQuestInfo questInfo, ElementId? extraCompletedQuest)
{
if (questInfo.PreviousQuests.Count == 0)
{
return true;
}
int num = questInfo.PreviousQuests.Count((PreviousQuestInfo x) => HasEnoughProgressOnPreviousQuest(x) || x.QuestId.Equals(extraCompletedQuest));
if (questInfo.PreviousQuestJoin == EQuestJoin.All && questInfo.PreviousQuests.Count == num)
{
return true;
}
if (questInfo.PreviousQuestJoin == EQuestJoin.AtLeastOne && num > 0)
{
return true;
}
return false;
}
private bool HasEnoughProgressOnPreviousQuest(PreviousQuestInfo previousQuestInfo)
{
if (IsQuestComplete(previousQuestInfo.QuestId))
{
return true;
}
if (previousQuestInfo.Sequence != 0 && IsQuestAccepted(previousQuestInfo.QuestId))
{
QuestProgressInfo questProgressInfo = GetQuestProgressInfo(previousQuestInfo.QuestId);
if (questProgressInfo != null)
{
return questProgressInfo.Sequence >= previousQuestInfo.Sequence;
}
return false;
}
return false;
}
private static bool HasCompletedPreviousInstances(QuestInfo questInfo)
{
if (questInfo.PreviousInstanceContent.Count == 0)
{
return true;
}
int num = questInfo.PreviousInstanceContent.Count((ushort x) => UIState.IsInstanceContentCompleted(x));
if (questInfo.PreviousInstanceContentJoin == EQuestJoin.All && questInfo.PreviousInstanceContent.Count == num)
{
return true;
}
if (questInfo.PreviousInstanceContentJoin == EQuestJoin.AtLeastOne && num > 0)
{
return true;
}
return false;
}
public unsafe bool IsClassJobUnlocked(EClassJob classJob)
{
ClassJob row = _dataManager.GetExcelSheet<ClassJob>().GetRow((uint)classJob);
ushort num = (ushort)row.UnlockQuest.RowId;
if (num != 0)
{
return IsQuestComplete(new QuestId(num));
}
PlayerState* ptr = PlayerState.Instance();
if (ptr != null)
{
return ptr->ClassJobLevels[row.ExpArrayIndex] > 0;
}
return false;
}
public bool IsJobUnlocked(EClassJob classJob)
{
return IsClassJobUnlocked((EClassJob)_dataManager.GetExcelSheet<ClassJob>().GetRow((uint)classJob).ClassJobParent.RowId);
}
public unsafe FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany GetGrandCompany()
{
return (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)PlayerState.Instance()->GrandCompany;
}
public bool IsMainScenarioQuestComplete()
{
return IsQuestComplete(_questData.LastMainScenarioQuestId);
}
}

View file

@ -0,0 +1,11 @@
using Questionable.Model.Questing;
namespace Questionable.Functions;
public sealed record QuestReference(ElementId? CurrentQuest, byte Sequence, MainScenarioQuestState State)
{
public static QuestReference NoQuest(MainScenarioQuestState state)
{
return new QuestReference(null, 0, state);
}
}