From: Liza Carvelli Date: Sun, 6 Apr 2025 01:46:22 +0000 (+0200) Subject: Add Patch 7.2 Fantasia as pseudo-event X-Git-Tag: v5.6~5 X-Git-Url: https://git.jacobcasper.com/?a=commitdiff_plain;h=6796ef5a6e9ab0be9ca7e6a4b69e1726f0614809;p=Questionable.git Add Patch 7.2 Fantasia as pseudo-event --- diff --git a/QuestPaths/7.x - Dawntrail/Unlocks/Misc/U506_Patch 7 2 Fantasia.json b/QuestPaths/7.x - Dawntrail/Unlocks/Misc/U506_Patch 7 2 Fantasia.json new file mode 100644 index 00000000..b48a33f0 --- /dev/null +++ b/QuestPaths/7.x - Dawntrail/Unlocks/Misc/U506_Patch 7 2 Fantasia.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1052475, + "Position": { + "X": -22.354492, + "Y": 10.13581, + "Z": -241.41296 + }, + "TerritoryId": 133, + "InteractionType": "AcceptQuest", + "AetheryteShortcut": "Gridania", + "AethernetShortcut": [ + "[Gridania] Aetheryte Plaza", + "[Gridania] Mih Khetto's Amphitheatre" + ], + "SkipConditions": { + "AetheryteShortcutIf": { + "InSameTerritory": true, + "InTerritory": [ + 133 + ] + } + } + } + ] + } + ] +} diff --git a/Questionable.IpcTest/IpcTestPlugin.cs b/Questionable.IpcTest/IpcTestPlugin.cs index 6121feba..2dcc932b 100644 --- a/Questionable.IpcTest/IpcTestPlugin.cs +++ b/Questionable.IpcTest/IpcTestPlugin.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using Dalamud.Game.Command; @@ -43,6 +44,15 @@ public sealed class IpcTestPlugin : IDalamudPlugin .AddUiForeground(stepData?.TerritoryId.ToString() ?? "?", 61) .Build()); } + else if (arguments == "events") + { + var eventQuests = _pluginInterface.GetIpcSubscriber>("Questionable.GetCurrentlyActiveEventQuests").InvokeFunc(); + _chatGui.Print(new SeStringBuilder() + .AddUiForeground("[IPC]", 576) + .AddText(": Quests: ") + .AddUiForeground(string.Join(", ", eventQuests), 61) + .Build()); + } else _chatGui.PrintError("Unknown subcommand"); } diff --git a/Questionable.Model/Questing/Converter/SkipConditionConverter.cs b/Questionable.Model/Questing/Converter/SkipConditionConverter.cs index 8833655a..b49a1cfb 100644 --- a/Questionable.Model/Questing/Converter/SkipConditionConverter.cs +++ b/Questionable.Model/Questing/Converter/SkipConditionConverter.cs @@ -13,6 +13,5 @@ public sealed class SkipConditionConverter() : EnumConverter DockStorehouse, - - SkipFreeFantasia, } diff --git a/Questionable.Model/Questing/ElementId.cs b/Questionable.Model/Questing/ElementId.cs index 51db2289..f87a7a6b 100644 --- a/Questionable.Model/Questing/ElementId.cs +++ b/Questionable.Model/Questing/ElementId.cs @@ -54,6 +54,8 @@ public abstract class ElementId : IComparable, IEquatable { if (value.StartsWith("S")) return new SatisfactionSupplyNpcId(ushort.Parse(value.Substring(1), CultureInfo.InvariantCulture)); + else if (value.StartsWith("U")) + return new UnlockLinkId(ushort.Parse(value.Substring(1), CultureInfo.InvariantCulture)); else if (value.StartsWith("A")) { value = value.Substring(1); @@ -85,6 +87,8 @@ public abstract class ElementId : IComparable, IEquatable throw; } } + + public abstract override string ToString(); } public sealed class QuestId(ushort value) : ElementId(value) @@ -105,6 +109,14 @@ public sealed class SatisfactionSupplyNpcId(ushort value) : ElementId(value) } } +public sealed class UnlockLinkId(ushort value) : ElementId(value) +{ + public override string ToString() + { + return "U" + Value.ToString(CultureInfo.InvariantCulture); + } +} + public sealed class AlliedSocietyDailyId(byte alliedSociety, byte rank = 0) : ElementId((ushort)(alliedSociety * 10 + rank)) { public byte AlliedSociety { get; } = alliedSociety; diff --git a/Questionable/Configuration.cs b/Questionable/Configuration.cs index 7bf14f68..952cf2bb 100644 --- a/Questionable/Configuration.cs +++ b/Questionable/Configuration.cs @@ -37,9 +37,6 @@ internal sealed class Configuration : IPluginConfiguration public bool UseEscToCancelQuesting { get; set; } = true; public bool ShowIncompleteSeasonalEvents { get; set; } = true; public bool ConfigureTextAdvance { get; set; } = true; - - // TODO Temporary setting, 7.2 adds another fantasia - public bool PickUpFreeFantasia { get; set; } = true; } internal sealed class DutyConfiguration diff --git a/Questionable/Controller/Steps/Shared/ExtraConditionUtils.cs b/Questionable/Controller/Steps/Shared/ExtraConditionUtils.cs index 0c72f2ad..1572f4e4 100644 --- a/Questionable/Controller/Steps/Shared/ExtraConditionUtils.cs +++ b/Questionable/Controller/Steps/Shared/ExtraConditionUtils.cs @@ -32,7 +32,7 @@ internal sealed class ExtraConditionUtils MatchesExtraCondition(skipCondition, position.Value, _clientState.TerritoryType); } - public bool MatchesExtraCondition(EExtraSkipCondition skipCondition, Vector3 position, ushort territoryType) + public static bool MatchesExtraCondition(EExtraSkipCondition skipCondition, Vector3 position, ushort territoryType) { return skipCondition switch { @@ -42,42 +42,7 @@ internal sealed class ExtraConditionUtils EExtraSkipCondition.RoguesGuild => territoryType == 129 && position.Y <= -115, EExtraSkipCondition.NotRoguesGuild => territoryType == 129 && position.Y > -115, EExtraSkipCondition.DockStorehouse => territoryType == 137 && position.Y <= -20, - EExtraSkipCondition.SkipFreeFantasia => ShouldSkipFreeFantasia(), _ => throw new ArgumentOutOfRangeException(nameof(skipCondition), skipCondition, null) }; } - - private unsafe bool ShouldSkipFreeFantasia() - { - if (!_configuration.General.PickUpFreeFantasia) - { - _logger.LogInformation("Skipping fantasia step, as free fantasia is disabled in the configuration"); - return true; - } - - bool foundFestival = false; - for (int i = 0; i < GameMain.Instance()->ActiveFestivals.Length; ++i) - { - if (GameMain.Instance()->ActiveFestivals[i].Id == 160) - { - foundFestival = true; - break; - } - } - - if (!foundFestival) - { - _logger.LogInformation("Skipping fantasia step, as free fantasia moogle is not available"); - return true; - } - - UIState* uiState = UIState.Instance(); - if (uiState != null && uiState->IsUnlockLinkUnlocked(505)) - { - _logger.LogInformation("Already picked up free fantasia"); - return true; - } - - return false; - } } diff --git a/Questionable/Data/QuestData.cs b/Questionable/Data/QuestData.cs index c724642f..c8c41ffd 100644 --- a/Questionable/Data/QuestData.cs +++ b/Questionable/Data/QuestData.cs @@ -95,6 +95,8 @@ internal sealed class QuestData } })); + quests.Add(new UnlockLinkQuestInfo(new UnlockLinkId(506), "Patch 7.2 Fantasia", 1052475)); + _quests = quests.ToDictionary(x => x.QuestId, x => x); // workaround because the game doesn't require completion of the CT questline through normal means diff --git a/Questionable/Functions/QuestFunctions.cs b/Questionable/Functions/QuestFunctions.cs index 78055d63..fcb107bc 100644 --- a/Questionable/Functions/QuestFunctions.cs +++ b/Questionable/Functions/QuestFunctions.cs @@ -533,6 +533,8 @@ internal sealed unsafe class QuestFunctions return false; else if (elementId is AlliedSocietyDailyId) return false; + else if (elementId is UnlockLinkId) + return false; else throw new ArgumentOutOfRangeException(nameof(elementId)); } @@ -551,6 +553,8 @@ internal sealed unsafe class QuestFunctions return false; else if (elementId is AlliedSocietyDailyId) return false; + else if (elementId is UnlockLinkId unlockLinkId) + return IsQuestComplete(unlockLinkId); else throw new ArgumentOutOfRangeException(nameof(elementId)); } @@ -561,6 +565,11 @@ internal sealed unsafe class QuestFunctions return QuestManager.IsQuestComplete(questId.Value); } + public bool IsQuestComplete(UnlockLinkId unlockLinkId) + { + return UIState.Instance()->IsUnlockLinkUnlocked(unlockLinkId.Value); + } + public bool IsQuestLocked(ElementId elementId, ElementId? extraCompletedQuest = null) { if (elementId is QuestId questId) @@ -569,6 +578,8 @@ internal sealed unsafe class QuestFunctions return IsQuestLocked(satisfactionSupplyNpcId); else if (elementId is AlliedSocietyDailyId alliedSocietyDailyId) return IsQuestLocked(alliedSocietyDailyId); + else if (elementId is UnlockLinkId unlockLinkId) + return IsQuestLocked(unlockLinkId); else throw new ArgumentOutOfRangeException(nameof(elementId)); } @@ -613,6 +624,11 @@ internal sealed unsafe class QuestFunctions return currentRank == 0 || currentRank < alliedSocietyDailyId.Rank; } + private static bool IsQuestLocked(UnlockLinkId unlockLinkId) + { + return IsQuestUnobtainable(unlockLinkId); + } + public bool IsDailyAlliedSocietyQuest(QuestId questId) { var questInfo = (QuestInfo)_questData.GetQuestInfo(questId); @@ -632,6 +648,8 @@ internal sealed unsafe class QuestFunctions { if (elementId is QuestId questId) return IsQuestUnobtainable(questId, extraCompletedQuest); + else if (elementId is UnlockLinkId unlockLinkId) + return IsQuestUnobtainable(unlockLinkId); else return false; } @@ -696,6 +714,29 @@ internal sealed unsafe class QuestFunctions return false; } + /// + /// All unlock links (presumably) have unique conditions, be that quests or otherwise. + /// + private static bool IsQuestUnobtainable(UnlockLinkId unlockLinkId) + { + if (unlockLinkId.Value == 506) + return !IsFestivalActive(160, 2); + else + return true; + } + + private static bool IsFestivalActive(ushort id, ushort? phase = null) + { + for (int i = 0; i < GameMain.Instance()->ActiveFestivals.Length; ++i) + { + var festival = GameMain.Instance()->ActiveFestivals[i]; + if (festival.Id == id) + return phase == null || festival.Phase == phase; + } + + return false; + } + public bool IsQuestRemoved(ElementId elementId) { if (elementId is QuestId questId) diff --git a/Questionable/Model/UnlockLinkQuestInfo.cs b/Questionable/Model/UnlockLinkQuestInfo.cs new file mode 100644 index 00000000..6e08f796 --- /dev/null +++ b/Questionable/Model/UnlockLinkQuestInfo.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using LLib.GameData; +using Questionable.Model.Questing; + +namespace Questionable.Model; + +internal sealed class UnlockLinkQuestInfo : IQuestInfo +{ + public UnlockLinkQuestInfo(UnlockLinkId unlockLinkId, string name, uint issuerDataId) + { + QuestId = unlockLinkId; + Name = name; + IssuerDataId = issuerDataId; + } + + public ElementId QuestId { get; } + public string Name { get; } + public uint IssuerDataId { get; } + public bool IsRepeatable => false; + public ImmutableList PreviousQuests => []; + public EQuestJoin PreviousQuestJoin => EQuestJoin.All; + public ushort Level => 1; + public EAlliedSociety AlliedSociety => EAlliedSociety.None; + public uint? JournalGenre => null; + public ushort SortKey => 0; + public bool IsMainScenarioQuest => false; + public IReadOnlyList ClassJobs => []; + public EExpansionVersion Expansion => EExpansionVersion.ARealmReborn; +} diff --git a/Questionable/Windows/QuestComponents/EventInfoComponent.cs b/Questionable/Windows/QuestComponents/EventInfoComponent.cs index c10658ac..b86a7955 100644 --- a/Questionable/Windows/QuestComponents/EventInfoComponent.cs +++ b/Questionable/Windows/QuestComponents/EventInfoComponent.cs @@ -22,6 +22,7 @@ internal sealed class EventInfoComponent [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] private readonly List _eventQuests = [ + new EventQuest("Limited Time Items", [new UnlockLinkId(506)], DateTime.MaxValue), ]; private readonly QuestData _questData; @@ -66,12 +67,17 @@ internal sealed class EventInfoComponent private void DrawEventQuest(EventQuest eventQuest) { - string time = (eventQuest.EndsAtUtc - DateTime.UtcNow).Humanize( - precision: 1, - culture: CultureInfo.InvariantCulture, - minUnit: TimeUnit.Minute, - maxUnit: TimeUnit.Day); - ImGui.Text($"{eventQuest.Name} ({time})"); + if (eventQuest.EndsAtUtc != DateTime.MaxValue) + { + string time = (eventQuest.EndsAtUtc - DateTime.UtcNow).Humanize( + precision: 1, + culture: CultureInfo.InvariantCulture, + minUnit: TimeUnit.Minute, + maxUnit: TimeUnit.Day); + ImGui.Text($"{eventQuest.Name} ({time})"); + } + else + ImGui.Text(eventQuest.Name); float width; using (var _ = _pluginInterface.UiBuilder.IconFontHandle.Push()) @@ -80,7 +86,7 @@ internal sealed class EventInfoComponent using (var _ = _pluginInterface.UiBuilder.IconFontFixedWidthHandle.Push()) width -= ImGui.CalcTextSize(FontAwesomeIcon.Check.ToIconString()).X; - List startableQuests = eventQuest.QuestIds.Where(x => + List startableQuests = eventQuest.QuestIds.Where(x => _questRegistry.IsKnownQuest(x) && _questFunctions.IsReadyToAcceptQuest(x) && x != _questController.StartedQuest?.Quest.Id && @@ -132,15 +138,19 @@ internal sealed class EventInfoComponent if (eventQuest.EndsAtUtc <= DateTime.UtcNow) return false; - return !eventQuest.QuestIds.All(x => _questFunctions.IsQuestComplete(x)); + return eventQuest.QuestIds.Any(ShouldShowQuest); } - public IEnumerable GetCurrentlyActiveEventQuests() + public IEnumerable GetCurrentlyActiveEventQuests() { return _eventQuests .Where(x => x.EndsAtUtc >= DateTime.UtcNow) - .SelectMany(x => x.QuestIds); + .SelectMany(x => x.QuestIds) + .Where(ShouldShowQuest); } - private sealed record EventQuest(string Name, List QuestIds, DateTime EndsAtUtc); + private bool ShouldShowQuest(ElementId elementId) => !_questFunctions.IsQuestComplete(elementId) && + !_questFunctions.IsQuestUnobtainable(elementId); + + private sealed record EventQuest(string Name, List QuestIds, DateTime EndsAtUtc); }