diff --git a/QuestPaths/3.x - Heavensward/Allied Societies/Vanu Vanu/A6_Vanu Vanu (all).json b/QuestPaths/3.x - Heavensward/Allied Societies/Vanu Vanu/A6_Vanu Vanu (all).json new file mode 100644 index 00000000..036c2982 --- /dev/null +++ b/QuestPaths/3.x - Heavensward/Allied Societies/Vanu Vanu/A6_Vanu Vanu (all).json @@ -0,0 +1,29 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "DataId": 1016089, + "Position": { + "X": -799.46594, + "Y": -133.2695, + "Z": -404.1352 + }, + "StopDistance": 3, + "TerritoryId": 401, + "InteractionType": "WalkTo", + "AetheryteShortcut": "The Sea of Clouds - Ok' Zundu", + "Fly": true, + "SkipConditions": { + "AetheryteShortcutIf": { + "InSameTerritory": true + } + } + } + ] + } + ] +} diff --git a/QuestPaths/3.x - Heavensward/Allied Societies/Vanu Vanu/Dailies/2172_Pussyfooting About.json b/QuestPaths/3.x - Heavensward/Allied Societies/Vanu Vanu/Dailies/2172_Pussyfooting About.json index fab39d1f..e12ff08c 100644 --- a/QuestPaths/3.x - Heavensward/Allied Societies/Vanu Vanu/Dailies/2172_Pussyfooting About.json +++ b/QuestPaths/3.x - Heavensward/Allied Societies/Vanu Vanu/Dailies/2172_Pussyfooting About.json @@ -58,6 +58,17 @@ { "Sequence": 2, "Steps": [ + { + "Position": { + "X": -799.46594, + "Y": -133.2695, + "Z": -404.1352 + }, + "TerritoryId": 401, + "InteractionType": "WalkTo", + "AetheryteShortcut": "The Sea of Clouds - Ok' Zundu", + "Fly": true + }, { "DataId": 1016089, "Position": { @@ -67,8 +78,7 @@ }, "TerritoryId": 401, "InteractionType": "Interact", - "AetheryteShortcut": "The Sea of Clouds - Ok' Zundu", - "Fly": true + "Mount": false } ] }, diff --git a/QuestPaths/3.x - Heavensward/Allied Societies/Vath/A7_Vath.json b/QuestPaths/3.x - Heavensward/Allied Societies/Vath/A7_Vath.json new file mode 100644 index 00000000..6ffe879d --- /dev/null +++ b/QuestPaths/3.x - Heavensward/Allied Societies/Vath/A7_Vath.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://git.carvel.li/liza/Questionable/raw/branch/master/QuestPaths/quest-v1.json", + "Author": "liza", + "QuestSequence": [ + { + "Sequence": 0, + "Steps": [ + { + "Position": { + "X": 58.39701, + "Y": -48.000008, + "Z": -172.36507 + }, + "TerritoryId": 398, + "InteractionType": "WalkTo", + "AetheryteShortcut": "The Dravanian Forelands - Anyx Trine", + "Fly": true, + "SkipConditions": { + "AetheryteShortcutIf": { + "InSameTerritory": true + } + } + } + ] + } + ] +} diff --git a/Questionable.Model/Questing/ElementId.cs b/Questionable.Model/Questing/ElementId.cs index 396c3524..a553ff0d 100644 --- a/Questionable.Model/Questing/ElementId.cs +++ b/Questionable.Model/Questing/ElementId.cs @@ -56,6 +56,19 @@ public abstract class ElementId : IComparable, IEquatable return new LeveId(ushort.Parse(value.Substring(1), CultureInfo.InvariantCulture)); else if (value.StartsWith("S")) return new SatisfactionSupplyNpcId(ushort.Parse(value.Substring(1), CultureInfo.InvariantCulture)); + else if (value.StartsWith("A")) + { + value = value.Substring(1); + string[] parts = value.Split('x'); + if (parts.Length == 2) + { + return new AlliedSocietyDailyId( + byte.Parse(parts[0], CultureInfo.InvariantCulture), + byte.Parse(parts[1], CultureInfo.InvariantCulture)); + } + else + return new AlliedSocietyDailyId(byte.Parse(value, CultureInfo.InvariantCulture)); + } else return new QuestId(ushort.Parse(value, CultureInfo.InvariantCulture)); } @@ -70,7 +83,8 @@ public abstract class ElementId : IComparable, IEquatable catch (Exception) { elementId = null; - return false; + //return false; + throw; } } } @@ -98,3 +112,14 @@ public sealed class SatisfactionSupplyNpcId(ushort value) : ElementId(value) return "S" + Value.ToString(CultureInfo.InvariantCulture); } } + +public sealed class AlliedSocietyDailyId(byte alliedSociety, byte rank = 0) : ElementId((ushort)(alliedSociety * 10 + rank)) +{ + public byte AlliedSociety { get; } = alliedSociety; + public byte Rank { get; } = rank; + + public override string ToString() + { + return "A" + AlliedSociety + "x" + Rank; + } +} diff --git a/Questionable/Controller/CommandHandler.cs b/Questionable/Controller/CommandHandler.cs index c6085a18..1c2bed2b 100644 --- a/Questionable/Controller/CommandHandler.cs +++ b/Questionable/Controller/CommandHandler.cs @@ -4,6 +4,7 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Game.Command; using Dalamud.Plugin.Services; using Lumina.Excel.Sheets; +using Microsoft.Extensions.Logging; using Questionable.Functions; using Questionable.Model.Questing; using Questionable.Windows; diff --git a/Questionable/Data/QuestData.cs b/Questionable/Data/QuestData.cs index 5d7b7022..3cb2f295 100644 --- a/Questionable/Data/QuestData.cs +++ b/Questionable/Data/QuestData.cs @@ -68,6 +68,29 @@ internal sealed class QuestData .Where(x => x.LevelLevemete.RowId != 0) .Select(x => new LeveInfo(x)), ]; + + quests.AddRange( + dataManager.GetExcelSheet() + .Where(x => x.RowId > 0 && !x.Name.IsEmpty) + .SelectMany(x => + { + if (x.RowId < 5) + { + return ((IEnumerable) + [ + 0, + ..quests.Where(y => y.AlliedSociety == (EAlliedSociety)x.RowId && y.IsRepeatable) + .Cast() + .Select(y => (byte)y.AlliedSocietyRank).Distinct() + ]) + .Select(rank => new AlliedSocietyDailyInfo(x, rank)); + } + else + { + return [new AlliedSocietyDailyInfo(x, 0)]; + } + })); + _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 bb206a1b..fc5d7935 100644 --- a/Questionable/Functions/QuestFunctions.cs +++ b/Questionable/Functions/QuestFunctions.cs @@ -14,7 +14,6 @@ using LLib.GameData; using LLib.GameUI; using Lumina.Excel.Sheets; using Questionable.Controller; -using Questionable.Controller.Steps.Interactions; using Questionable.Data; using Questionable.Model; using Questionable.Model.Questing; @@ -487,6 +486,8 @@ internal sealed unsafe class QuestFunctions return IsQuestAccepted(leveId); else if (elementId is SatisfactionSupplyNpcId) return false; + else if (elementId is AlliedSocietyDailyId) + return false; else throw new ArgumentOutOfRangeException(nameof(elementId)); } @@ -517,6 +518,8 @@ internal sealed unsafe class QuestFunctions return IsQuestComplete(leveId); else if (elementId is SatisfactionSupplyNpcId) return false; + else if (elementId is AlliedSocietyDailyId) + return false; else throw new ArgumentOutOfRangeException(nameof(elementId)); } @@ -540,6 +543,8 @@ internal sealed unsafe class QuestFunctions return IsQuestLocked(leveId); else if (elementId is SatisfactionSupplyNpcId satisfactionSupplyNpcId) return IsQuestLocked(satisfactionSupplyNpcId); + else if (elementId is AlliedSocietyDailyId alliedSocietyDailyId) + return IsQuestLocked(alliedSocietyDailyId); else throw new ArgumentOutOfRangeException(nameof(elementId)); } @@ -579,6 +584,13 @@ internal sealed unsafe class QuestFunctions return !HasCompletedPreviousQuests(questInfo, null); } + private bool IsQuestLocked(AlliedSocietyDailyId alliedSocietyDailyId) + { + PlayerState* playerState = PlayerState.Instance(); + byte currentRank = playerState->GetBeastTribeRank(alliedSocietyDailyId.AlliedSociety); + return currentRank == 0 || currentRank < alliedSocietyDailyId.Rank; + } + public bool IsDailyAlliedSocietyQuest(QuestId questId) { var questInfo = (QuestInfo)_questData.GetQuestInfo(questId); diff --git a/Questionable/Model/AlliedSocietyDailyInfo.cs b/Questionable/Model/AlliedSocietyDailyInfo.cs new file mode 100644 index 00000000..0f203609 --- /dev/null +++ b/Questionable/Model/AlliedSocietyDailyInfo.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using LLib.GameData; +using Lumina.Excel.Sheets; +using Questionable.Data; +using Questionable.Model.Questing; + +namespace Questionable.Model; + +internal sealed class AlliedSocietyDailyInfo : IQuestInfo +{ + public AlliedSocietyDailyInfo(BeastTribe beastTribe, byte rank) + { + QuestId = new AlliedSocietyDailyId((byte)beastTribe.RowId, rank); + Name = beastTribe.Name.ToString(); + ClassJobs = (EAlliedSociety)beastTribe.RowId switch + { + EAlliedSociety.Amaljaa or EAlliedSociety.Sylphs or EAlliedSociety.Kobolds or EAlliedSociety.Sahagin or + EAlliedSociety.VanuVanu or EAlliedSociety.Vath or + EAlliedSociety.Kojin or EAlliedSociety.Ananta or + EAlliedSociety.Pixies or + EAlliedSociety.Arkasodara or + EAlliedSociety.Pelupelu => + [ + ..ClassJobUtils.AsIndividualJobs(EExtendedClassJob.DoW), + ..ClassJobUtils.AsIndividualJobs(EExtendedClassJob.DoM) + ], + EAlliedSociety.Ixal or EAlliedSociety.Moogles or EAlliedSociety.Dwarves or EAlliedSociety.Loporrits => + ClassJobUtils.AsIndividualJobs(EExtendedClassJob.DoH).ToList(), + + EAlliedSociety.Qitari or EAlliedSociety.Omicrons => + ClassJobUtils.AsIndividualJobs(EExtendedClassJob.DoL).ToList(), + + EAlliedSociety.Namazu => + [ + ..ClassJobUtils.AsIndividualJobs(EExtendedClassJob.DoH), + ..ClassJobUtils.AsIndividualJobs(EExtendedClassJob.DoL) + ], + + _ => throw new ArgumentOutOfRangeException(nameof(beastTribe)) + }; + Expansion = (EExpansionVersion)beastTribe.Expansion.RowId; + } + + public ElementId QuestId { get; } + public string Name { get; } + public uint IssuerDataId => 0; + public ImmutableList PreviousQuests { get; } = []; + public EQuestJoin PreviousQuestJoin => EQuestJoin.All; + public bool IsRepeatable => true; + public ushort Level => 1; + public EAlliedSociety AlliedSociety => EAlliedSociety.None; + public uint? JournalGenre => null; + public ushort SortKey => 0; + public bool IsMainScenarioQuest => false; + public IReadOnlyList ClassJobs { get; } + public EExpansionVersion Expansion { get; } +} diff --git a/Questionable/Windows/PriorityWindow.cs b/Questionable/Windows/PriorityWindow.cs index 08bd35e5..8b4d5202 100644 --- a/Questionable/Windows/PriorityWindow.cs +++ b/Questionable/Windows/PriorityWindow.cs @@ -106,7 +106,7 @@ internal sealed class PriorityWindow : LWindow if (!string.IsNullOrEmpty(_searchString)) { foundQuests = _questRegistry.AllQuests - .Where(x => x.Id is not SatisfactionSupplyNpcId) + .Where(x => x.Id is not SatisfactionSupplyNpcId and not AlliedSocietyDailyId) .Where(x => x.Info.Name.Contains(_searchString, StringComparison.CurrentCultureIgnoreCase)) .Where(x => !_questFunctions.IsQuestUnobtainable(x.Id)); }