{
"TerritoryId": 153,
"InteractionType": "EquipItem",
- "ItemId": 4546
+ "ItemId": 4546,
+ "AetheryteShortcut": "South Shroud - Quarrymill",
+ "Fly": true,
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
},
{
"TerritoryId": 153,
"Z": -7.3396606
},
"TerritoryId": 153,
- "InteractionType": "AcceptQuest",
- "AetheryteShortcut": "South Shroud - Quarrymill",
- "Fly": true,
- "SkipConditions": {
- "AetheryteShortcutIf": {
- "InSameTerritory": true
- }
- }
+ "InteractionType": "AcceptQuest"
}
]
},
"Z": -142.93127\r
},\r
"TerritoryId": 133,\r
- "InteractionType": "AcceptQuest"\r
+ "InteractionType": "AcceptQuest",\r
+ "AetheryteShortcut": "Gridania",\r
+ "AethernetShortcut": [\r
+ "[Gridania] Aetheryte Plaza",\r
+ "[Gridania] Botanists' Guild"\r
+ ],\r
+ "SkipConditions": {\r
+ "AetheryteShortcutIf": {\r
+ "InSameTerritory": true,\r
+ "InTerritory": [\r
+ 133\r
+ ]\r
+ }\r
+ }\r
}\r
]\r
},\r
},
"TerritoryId": 131,
"InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ul'dah",
+ "AethernetShortcut": [
+ "[Ul'dah] Aetheryte Plaza",
+ "[Ul'dah] Miners' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 131
+ ]
+ }
+ },
"DialogueChoices": [
{
"Type": "YesNo",
"Z": 153.2157
},
"TerritoryId": 131,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ul'dah",
+ "AethernetShortcut": [
+ "[Ul'dah] Aetheryte Plaza",
+ "[Ul'dah] Miners' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 131
+ ]
+ }
+ }
}
]
},
"Z": 157.42725
},
"TerritoryId": 131,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ul'dah",
+ "AethernetShortcut": [
+ "[Ul'dah] Aetheryte Plaza",
+ "[Ul'dah] Miners' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 131
+ ]
+ }
+ }
}
]
},
"Z": 157.42725
},
"TerritoryId": 131,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ul'dah",
+ "AethernetShortcut": [
+ "[Ul'dah] Aetheryte Plaza",
+ "[Ul'dah] Miners' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 131
+ ]
+ }
+ }
}
]
},
"Z": -51.163513\r
},\r
"TerritoryId": 130,\r
- "InteractionType": "AcceptQuest"\r
+ "InteractionType": "AcceptQuest",\r
+ "AetheryteShortcut": "Ul'dah",\r
+ "SkipConditions": {\r
+ "AetheryteShortcutIf": {\r
+ "InSameTerritory": true\r
+ }\r
+ }\r
}\r
]\r
},\r
"Z": -51.163513
},
"TerritoryId": 130,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ul'dah",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
}
},\r
"TerritoryId": 131,\r
"InteractionType": "AcceptQuest",\r
+ "AetheryteShortcut": "Ul'dah",\r
"AethernetShortcut": [\r
"[Ul'dah] Aetheryte Plaza",\r
"[Ul'dah] Gladiators' Guild"\r
"Z": 39.81079
},
"TerritoryId": 131,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ul'dah",
+ "AethernetShortcut": [
+ "[Ul'dah] Aetheryte Plaza",
+ "[Ul'dah] Gladiators' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 131
+ ]
+ }
+ }
}
]
},
"Z": 4.017052
},
"TerritoryId": 129,
- "InteractionType": "WalkTo"
+ "InteractionType": "WalkTo",
+ "AetheryteShortcut": "Limsa Lominsa",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
},
{
"DataId": 1000895,
{
"TerritoryId": 128,
"InteractionType": "EquipItem",
- "ItemId": 4550
+ "ItemId": 4550,
+ "AetheryteShortcut": "Limsa Lominsa",
+ "AethernetShortcut": [
+ "[Limsa Lominsa] Aetheryte Plaza",
+ "[Limsa Lominsa] Marauders' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 128
+ ]
+ }
+ }
},
{
"DataId": 1006757,
"Z": -250.56848
},
"TerritoryId": 128,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Limsa Lominsa",
+ "AethernetShortcut": [
+ "[Limsa Lominsa] Aetheryte Plaza",
+ "[Limsa Lominsa] Marauders' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 128
+ ]
+ }
+ }
}
]
},
"Z": -250.56848
},
"TerritoryId": 128,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Limsa Lominsa",
+ "AethernetShortcut": [
+ "[Limsa Lominsa] Aetheryte Plaza",
+ "[Limsa Lominsa] Marauders' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 128
+ ]
+ }
+ }
}
]
},
"Z": -250.56848
},
"TerritoryId": 128,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Limsa Lominsa",
+ "AethernetShortcut": [
+ "[Limsa Lominsa] Aetheryte Plaza",
+ "[Limsa Lominsa] Marauders' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 128
+ ]
+ }
+ }
}
]
},
"Z": -250.56848
},
"TerritoryId": 128,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Limsa Lominsa",
+ "AethernetShortcut": [
+ "[Limsa Lominsa] Aetheryte Plaza",
+ "[Limsa Lominsa] Marauders' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 128
+ ]
+ }
+ }
}
]
},
"Y": 8.712891,\r
"Z": 281.69678\r
},\r
- "MaximumDistance": 3\r
+ "MaximumDistance": 3,\r
+ "TerritoryId": 153\r
}\r
}\r
}\r
"Y": 8.712891,\r
"Z": 281.69678\r
},\r
- "MaximumDistance": 3\r
+ "MaximumDistance": 3,\r
+ "TerritoryId": 153\r
}\r
}\r
}\r
"Z": 195.94104
},
"TerritoryId": 129,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Limsa Lominsa",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": 201.9226
},
"TerritoryId": 819,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Crystarium",
+ "AethernetShortcut": [
+ "[Crystarium] Aetheryte Plaza",
+ "[Crystarium] Musica Universalis Markets"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": 251.75854
},
"TerritoryId": 819,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Crystarium",
+ "AethernetShortcut": [
+ "[Crystarium] Aetheryte Plaza",
+ "[Crystarium] Musica Universalis Markets"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
}
"Prompt": "TEXT_KINGBB201_04854_Q1_000_000",
"Answer": "TEXT_KINGBB201_04854_A1_000_002"
}
- ]
+ ],
+ "AetheryteShortcut": "Gridania",
+ "AethernetShortcut": [
+ "[Gridania] Aetheryte Plaza",
+ "[Gridania] Conjurers' Guild"
+ ],
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 133
+ ]
+ }
+ }
}
]
},
"TerritoryId": 132,
"InteractionType": "UseItem",
"ItemId": 43538,
+ "AetheryteShortcut": "Gridania",
"SkipConditions": {
"StepIf": {
"Item": {
"NotInInventory": true
}
+ },
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
}
}
},
"Z": -99.321045
},
"TerritoryId": 130,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ul'dah",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"TerritoryId": 131,
"InteractionType": "UseItem",
"ItemId": 43537,
+ "AetheryteShortcut": "Ul'dah",
+ "AethernetShortcut": [
+ "[Ul'dah] Aetheryte Plaza",
+ "[Ul'dah] Weavers' Guild"
+ ],
"SkipConditions": {
"StepIf": {
"Item": {
"NotInInventory": true
}
+ },
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true,
+ "InTerritory": [
+ 131
+ ]
}
}
},
{
- "TerritoryId": 131,
+ "TerritoryId": 131,
"InteractionType": "EquipItem",
"ItemId": 41808,
"SkipConditions": {
"Z": 194.72034
},
"TerritoryId": 1185,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Tuliyollal",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": 628.3817
},
"TerritoryId": 957,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Thavnair - Yedlihmad",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": 628.3817
},
"TerritoryId": 957,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Thavnair - Yedlihmad",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
},
"TerritoryId": 957,
"InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Thavnair - Yedlihmad",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ },
"DialogueChoices": [
{
"Type": "List",
"Z": 628.3817
},
"TerritoryId": 957,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Thavnair - Yedlihmad",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": 628.3817
},
"TerritoryId": 957,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Thavnair - Yedlihmad",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": -3.6469727
},
"TerritoryId": 621,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Lochs - Porta Praetoria",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": 739.4979
},
"TerritoryId": 620,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Peaks - Ala Ghiri",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": 739.4979
},
"TerritoryId": 620,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Peaks - Ala Ghiri",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": 618.09717
},
"TerritoryId": 621,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Lochs - Ala Mhigan Quarter",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": 203.14331
},
"TerritoryId": 1185,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Tuliyollal",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": -48.05072
},
"TerritoryId": 418,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ishgard",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
},
"TerritoryId": 418,
"InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ishgard",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ },
"DialogueChoices": [
{
"Type": "List",
"Z": -1.7243042
},
"TerritoryId": 418,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ishgard",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
},
"StopDistance": 5,
"TerritoryId": 418,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ishgard",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
"Z": -48.17273
},
"TerritoryId": 418,
- "InteractionType": "AcceptQuest"
+ "InteractionType": "AcceptQuest",
+ "AetheryteShortcut": "Ishgard",
+ "SkipConditions": {
+ "AetheryteShortcutIf": {
+ "InSameTerritory": true
+ }
+ }
}
]
},
public sealed class QuestStep
{
public const float DefaultStopDistance = 3f;
+ public const int VesperBayAetheryteTicket = 30362;
public uint? DataId { get; set; }
else
return StopDistance ?? DefaultStopDistance;
}
+
+ /// <summary>
+ /// Only relevant for the step 0 in sequence 0: Whether this step is valid for teleporting to it.
+ /// </summary>
+ /// <returns></returns>
+ public bool IsTeleportableForPriorityQuests()
+ {
+ if (AetheryteShortcut != null)
+ return true;
+
+ if (InteractionType == EInteractionType.UseItem && ItemId == VesperBayAetheryteTicket)
+ return true;
+
+ return false;
+ }
}
internal static class UseItem
{
- public const int VesperBayAetheryteTicket = 30362;
-
internal sealed class Factory(
Mount.Factory mountFactory,
MoveTo.Factory moveFactory,
ArgumentNullException.ThrowIfNull(step.ItemId);
- if (step.ItemId == VesperBayAetheryteTicket)
+ if (step.ItemId == QuestStep.VesperBayAetheryteTicket)
{
unsafe
{
if (StartingCombat && condition[ConditionFlag.InCombat])
return ETaskResult.TaskComplete;
- if (ItemId == VesperBayAetheryteTicket && _usedItem)
+ if (ItemId == QuestStep.VesperBayAetheryteTicket && _usedItem)
{
InventoryManager* inventoryManager = InventoryManager.Instance();
if (inventoryManager == null)
private TimeSpan GetRetryDelay()
{
- if (ItemId == VesperBayAetheryteTicket)
+ if (ItemId == QuestStep.VesperBayAetheryteTicket)
return TimeSpan.FromSeconds(11);
else
return TimeSpan.FromSeconds(5);
if (skipConditions.NearPosition is { } nearPosition &&
- clientState.TerritoryType == step.TerritoryId)
+ clientState.TerritoryType == nearPosition.TerritoryId)
{
if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
nearPosition.MaximumDistance)
}
}
- if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == step.TerritoryId)
+ if (skipConditions.NearPosition is { } nearPosition && clientState.TerritoryType == nearPosition.TerritoryId)
{
if (Vector3.Distance(nearPosition.Position, clientState.LocalPlayer!.Position) <=
nearPosition.MaximumDistance)
if (firstStep == null)
return false;
- if (firstStep.AetheryteShortcut != null)
- return true;
-
- if (firstStep is
- { InteractionType: EInteractionType.UseItem, ItemId: UseItem.VesperBayAetheryteTicket })
- return true;
-
- return false;
+ return firstStep.IsTeleportableForPriorityQuests();
})
.FirstOrDefault(x =>
{
serviceCollection.AddSingleton<IQuestValidator, CompletionFlagsValidator>();
serviceCollection.AddSingleton<IQuestValidator, AethernetShortcutValidator>();
serviceCollection.AddSingleton<IQuestValidator, DialogueChoiceValidator>();
+ serviceCollection.AddSingleton<IQuestValidator, ClassQuestShouldHaveShortcutValidator>();
serviceCollection.AddSingleton<JsonSchemaValidator>();
serviceCollection.AddSingleton<IQuestValidator>(sp => sp.GetRequiredService<JsonSchemaValidator>());
}
UnexpectedCompleteQuestStep,
InvalidAethernetShortcut,
InvalidExcelRef,
+ ClassQuestWithoutAetheryteShortcut,
}
-namespace Questionable.Validation.Validators;
+using System.Collections.Generic;
+using LLib.GameData;
+using Questionable.Data;
+using Questionable.Model;
+using Questionable.Model.Questing;
-public class ClassQuestShouldHaveShortcut
+namespace Questionable.Validation.Validators;
+
+internal sealed class ClassQuestShouldHaveShortcutValidator : IQuestValidator
{
-
+ private readonly HashSet<ElementId> _classJobQuests = [];
+
+ public ClassQuestShouldHaveShortcutValidator(QuestData questData)
+ {
+ foreach (EClassJob classJob in typeof(EClassJob).GetEnumValues())
+ {
+ if (classJob == EClassJob.Adventurer)
+ continue;
+
+ foreach (var questInfo in questData.GetClassJobQuests(classJob))
+ {
+ // TODO maybe remove the level check
+ if (questInfo.Level > 1)
+ _classJobQuests.Add(questInfo.QuestId);
+ }
+ }
+ }
+
+ public IEnumerable<ValidationIssue> Validate(Quest quest)
+ {
+ if (!_classJobQuests.Contains(quest.Id))
+ yield break;
+
+ var firstStep = quest.FindSequence(0)?.FindStep(0);
+ if (firstStep == null)
+ yield break;
+
+ if (firstStep.IsTeleportableForPriorityQuests())
+ yield break;
+
+ yield return new ValidationIssue
+ {
+ ElementId = quest.Id,
+ Sequence = 0,
+ Step = 0,
+ Type = EIssueType.ClassQuestWithoutAetheryteShortcut,
+ Severity = EIssueSeverity.Error,
+ Description = "Class quest should have an aetheryte shortcut to be done automatically",
+ };
+ }
}