Consider alternative Lv1 class quests locked; don't do priority quests unless we can teleport + aren't broke
This commit is contained in:
parent
f9368ae809
commit
052c366ea0
@ -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),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user