Consider alternative Lv1 class quests locked; don't do priority quests unless we can teleport + aren't broke

This commit is contained in:
Liza 2024-08-13 23:26:13 +02:00
parent f9368ae809
commit 052c366ea0
Signed by: liza
GPG Key ID: 7199F8D727D55F67
4 changed files with 92 additions and 15 deletions

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using LLib.GameData; using LLib.GameData;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Questionable.Model; using Questionable.Model;
@ -21,6 +22,7 @@ internal sealed class QuestData
public static readonly IReadOnlyList<ushort> MeleeRoleQuests = [138, 156, 180]; public static readonly IReadOnlyList<ushort> MeleeRoleQuests = [138, 156, 180];
public static readonly IReadOnlyList<ushort> PhysicalRangedRoleQuests = [138, 157, 181]; public static readonly IReadOnlyList<ushort> PhysicalRangedRoleQuests = [138, 157, 181];
public static readonly IReadOnlyList<ushort> CasterRoleQuests = [139, 158, 182]; public static readonly IReadOnlyList<ushort> CasterRoleQuests = [139, 158, 182];
public static readonly IReadOnlyList<IReadOnlyList<ushort>> AllRoleQuestChapters = public static readonly IReadOnlyList<IReadOnlyList<ushort>> AllRoleQuestChapters =
[ [
TankRoleQuests, TankRoleQuests,
@ -59,7 +61,7 @@ internal sealed class QuestData
_quests = quests.ToDictionary(x => x.QuestId, x => x); _quests = quests.ToDictionary(x => x.QuestId, x => x);
// workaround because the game doesn't require completion of the CT questline through normal means // workaround because the game doesn't require completion of the CT questline through normal means
QuestInfo aTimeToEveryPurpose = (QuestInfo) _quests[new QuestId(425)]; QuestInfo aTimeToEveryPurpose = (QuestInfo)_quests[new QuestId(425)];
aTimeToEveryPurpose.AddPreviousQuest(new QuestId(495)); aTimeToEveryPurpose.AddPreviousQuest(new QuestId(495));
} }
@ -180,4 +182,32 @@ internal sealed class QuestData
.Where(x => chapterIds.Contains(x.NewGamePlusChapter)) .Where(x => chapterIds.Contains(x.NewGamePlusChapter))
.ToList(); .ToList();
} }
public List<QuestId> GetLockedClassQuests()
{
EClassJob startingClass;
unsafe
{
var playerState = PlayerState.Instance();
if (playerState != null)
startingClass = (EClassJob)playerState->FirstClass;
else
startingClass = EClassJob.Adventurer;
}
if (startingClass == EClassJob.Adventurer)
return [];
return
[
startingClass == EClassJob.Gladiator ? new(177) : new(253),
startingClass == EClassJob.Pugilist ? new(178) : new(533),
startingClass == EClassJob.Marauder ? new(179) : new(311),
startingClass == EClassJob.Lancer ? new(180) : new(23),
startingClass == EClassJob.Archer ? new(181) : new(21),
startingClass == EClassJob.Conjurer ? new(182) : new(22),
startingClass == EClassJob.Thaumaturge ? new(183) : new(345),
startingClass == EClassJob.Arcanist ? new(451) : new(453),
];
}
} }

View File

@ -1,6 +1,9 @@
using System; using System;
using System.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Model.Common; using Questionable.Model.Common;
@ -10,13 +13,19 @@ namespace Questionable.Functions;
internal sealed unsafe class AetheryteFunctions internal sealed unsafe class AetheryteFunctions
{ {
private const uint TeleportAction = 5;
private const uint ReturnAction = 8;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly ILogger<AetheryteFunctions> _logger; private readonly ILogger<AetheryteFunctions> _logger;
private readonly IDataManager _dataManager;
public AetheryteFunctions(IServiceProvider serviceProvider, ILogger<AetheryteFunctions> logger) public AetheryteFunctions(IServiceProvider serviceProvider, ILogger<AetheryteFunctions> logger,
IDataManager dataManager)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_logger = logger; _logger = logger;
_dataManager = dataManager;
} }
public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue; public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
@ -39,10 +48,18 @@ internal sealed unsafe class AetheryteFunctions
public bool CanTeleport(EAetheryteLocation aetheryteLocation) public bool CanTeleport(EAetheryteLocation aetheryteLocation)
{ {
if ((ushort)aetheryteLocation == PlayerState.Instance()->HomeAetheryteId && if ((ushort)aetheryteLocation == PlayerState.Instance()->HomeAetheryteId &&
ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 8) == 0) ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, ReturnAction) == 0)
return true; return true;
return ActionManager.Instance()->GetActionStatus(ActionType.Action, 5) == 0; return ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, TeleportAction) == 0;
}
public bool IsTeleportUnlocked()
{
ushort unlockLink = _dataManager.GetExcelSheet<GeneralAction>()!
.Single(x => x.Action.Row == 5)
.UnlockLink;
return UIState.Instance()->IsUnlockLinkUnlocked(unlockLink);
} }
public bool TeleportAetheryte(uint aetheryteId) public bool TeleportAetheryte(uint aetheryteId)
@ -51,17 +68,17 @@ internal sealed unsafe class AetheryteFunctions
if (IsAetheryteUnlocked(aetheryteId, out var subIndex)) if (IsAetheryteUnlocked(aetheryteId, out var subIndex))
{ {
if (aetheryteId == PlayerState.Instance()->HomeAetheryteId && if (aetheryteId == PlayerState.Instance()->HomeAetheryteId &&
ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 8) == 0) ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, ReturnAction) == 0)
{ {
ReturnRequestedAt = DateTime.Now; ReturnRequestedAt = DateTime.Now;
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 8)) if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, ReturnAction))
{ {
_logger.LogInformation("Using 'return' for home aetheryte"); _logger.LogInformation("Using 'return' for home aetheryte");
return true; return true;
} }
} }
if (ActionManager.Instance()->GetActionStatus(ActionType.Action, 5) == 0) if (ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, TeleportAction) == 0)
{ {
// fallback if return isn't available or (more likely) on a different aetheryte // fallback if return isn't available or (more likely) on a different aetheryte
_logger.LogInformation("Teleporting to aetheryte {AetheryteId}", aetheryteId); _logger.LogInformation("Teleporting to aetheryte {AetheryteId}", aetheryteId);

View File

@ -241,8 +241,21 @@ internal sealed unsafe class QuestFunctions
public ElementId? GetNextPriorityQuestThatCanBeAccepted() public ElementId? GetNextPriorityQuestThatCanBeAccepted()
{ {
// all priority quests assume we're able to teleport to the beginning (and for e.g. class quests, the end)
// ideally without having to wait 15m for Return.
if (!_aetheryteFunctions.IsTeleportUnlocked())
return null;
// ideally, we'd also be able to afford *some* teleports
// this implicitly makes sure we're not starting one of the lv1 class quests if we can't afford to teleport back
//
// Of course, they can still be accepted manually.
InventoryManager* inventoryManager = InventoryManager.Instance();
if (inventoryManager->GetItemCountInContainer(1, InventoryType.Currency) < 2000)
return null;
return GetPriorityQuestsThatCanBeAccepted() return GetPriorityQuestsThatCanBeAccepted()
.FirstOrDefault(x => .Where(x =>
{ {
if (!_questRegistry.TryGetQuest(x, out Quest? quest)) if (!_questRegistry.TryGetQuest(x, out Quest? quest))
return false; return false;
@ -251,8 +264,7 @@ internal sealed unsafe class QuestFunctions
if (firstStep == null) if (firstStep == null)
return false; return false;
if (firstStep.AetheryteShortcut is { } aetheryteShortcut && if (firstStep.AetheryteShortcut != null)
_aetheryteFunctions.IsAetheryteUnlocked(aetheryteShortcut))
return true; return true;
if (firstStep is if (firstStep is
@ -260,6 +272,25 @@ internal sealed unsafe class QuestFunctions
return true; return true;
return false; return false;
})
.FirstOrDefault(x =>
{
if (!_questRegistry.TryGetQuest(x, out Quest? quest))
return false;
return quest.AllSteps().All(x =>
{
if (x.Step.AetheryteShortcut is { } aetheryteShortcut &&
_aetheryteFunctions.IsAetheryteUnlocked(aetheryteShortcut))
return false;
if (x.Step.AethernetShortcut is { } aethernetShortcut &&
(!_aetheryteFunctions.IsAetheryteUnlocked(aethernetShortcut.From) ||
!_aetheryteFunctions.IsAetheryteUnlocked(aethernetShortcut.To)))
return false;
return true;
});
}); });
} }
@ -415,6 +446,9 @@ internal sealed unsafe class QuestFunctions
if (questInfo.GrandCompany != GrandCompany.None && questInfo.GrandCompany != GetGrandCompany()) if (questInfo.GrandCompany != GrandCompany.None && questInfo.GrandCompany != GetGrandCompany())
return true; return true;
if (_questData.GetLockedClassQuests().Contains(questId))
return true;
return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo); return !HasCompletedPreviousQuests(questInfo, extraCompletedQuest) || !HasCompletedPreviousInstances(questInfo);
} }

View File

@ -1,16 +1,12 @@
using System; using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using ImGuiNET; using ImGuiNET;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Data; using Questionable.Data;
using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
namespace Questionable.Windows; namespace Questionable.Windows;