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

pull/17/head
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.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using LLib.GameData;
using Lumina.Excel.GeneratedSheets;
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> PhysicalRangedRoleQuests = [138, 157, 181];
public static readonly IReadOnlyList<ushort> CasterRoleQuests = [139, 158, 182];
public static readonly IReadOnlyList<IReadOnlyList<ushort>> AllRoleQuestChapters =
[
TankRoleQuests,
@ -59,7 +61,7 @@ internal sealed class QuestData
_quests = quests.ToDictionary(x => x.QuestId, x => x);
// 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));
}
@ -180,4 +182,32 @@ internal sealed class QuestData
.Where(x => chapterIds.Contains(x.NewGamePlusChapter))
.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.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Model.Common;
@ -10,13 +13,19 @@ namespace Questionable.Functions;
internal sealed unsafe class AetheryteFunctions
{
private const uint TeleportAction = 5;
private const uint ReturnAction = 8;
private readonly IServiceProvider _serviceProvider;
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;
_logger = logger;
_dataManager = dataManager;
}
public DateTime ReturnRequestedAt { get; set; } = DateTime.MinValue;
@ -39,10 +48,18 @@ internal sealed unsafe class AetheryteFunctions
public bool CanTeleport(EAetheryteLocation aetheryteLocation)
{
if ((ushort)aetheryteLocation == PlayerState.Instance()->HomeAetheryteId &&
ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 8) == 0)
ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, ReturnAction) == 0)
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)
@ -51,17 +68,17 @@ internal sealed unsafe class AetheryteFunctions
if (IsAetheryteUnlocked(aetheryteId, out var subIndex))
{
if (aetheryteId == PlayerState.Instance()->HomeAetheryteId &&
ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 8) == 0)
ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, ReturnAction) == 0)
{
ReturnRequestedAt = DateTime.Now;
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, 8))
if (ActionManager.Instance()->UseAction(ActionType.GeneralAction, ReturnAction))
{
_logger.LogInformation("Using 'return' for home aetheryte");
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
_logger.LogInformation("Teleporting to aetheryte {AetheryteId}", aetheryteId);

View File

@ -241,8 +241,21 @@ internal sealed unsafe class QuestFunctions
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()
.FirstOrDefault(x =>
.Where(x =>
{
if (!_questRegistry.TryGetQuest(x, out Quest? quest))
return false;
@ -251,8 +264,7 @@ internal sealed unsafe class QuestFunctions
if (firstStep == null)
return false;
if (firstStep.AetheryteShortcut is { } aetheryteShortcut &&
_aetheryteFunctions.IsAetheryteUnlocked(aetheryteShortcut))
if (firstStep.AetheryteShortcut != null)
return true;
if (firstStep is
@ -260,6 +272,25 @@ internal sealed unsafe class QuestFunctions
return true;
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())
return true;
if (_questData.GetLockedClassQuests().Contains(questId))
return true;
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.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
using ImGuiNET;
using Questionable.Controller;
using Questionable.Data;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Windows;