Compare commits

..

7 Commits

56 changed files with 360 additions and 48 deletions

View File

@ -1,5 +1,5 @@
<Project> <Project>
<PropertyGroup Condition="$(MSBuildProjectName) != 'GatheringPathRenderer'"> <PropertyGroup Condition="$(MSBuildProjectName) != 'GatheringPathRenderer'">
<Version>4.19</Version> <Version>4.20</Version>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -21,8 +21,7 @@ internal static class SinglePlayerDutyOptionsExtensions
Assignment(nameof(SinglePlayerDutyOptions.Enabled), Assignment(nameof(SinglePlayerDutyOptions.Enabled),
dutyOptions.Enabled, emptyOptions.Enabled) dutyOptions.Enabled, emptyOptions.Enabled)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(SinglePlayerDutyOptions.Notes), AssignmentList(nameof(SinglePlayerDutyOptions.Notes), dutyOptions.Notes)
dutyOptions.Notes, emptyOptions.Notes)
.AsSyntaxNodeOrToken(), .AsSyntaxNodeOrToken(),
Assignment(nameof(SinglePlayerDutyOptions.Index), Assignment(nameof(SinglePlayerDutyOptions.Index),
dutyOptions.Index, emptyOptions.Index) dutyOptions.Index, emptyOptions.Index)

View File

@ -141,7 +141,8 @@
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
}, },
"Fly": true "Fly": true
} }

View File

@ -36,7 +36,8 @@
"TerritoryId": 137, "TerritoryId": 137,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
}, },
"AetheryteShortcut": "Eastern La Noscea - Wineport", "AetheryteShortcut": "Eastern La Noscea - Wineport",
"Fly": true "Fly": true

View File

@ -117,7 +117,8 @@
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
}, },
"Fly": true "Fly": true
} }

View File

@ -119,7 +119,8 @@
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -251,7 +251,8 @@
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
}, },
"Fly": true "Fly": true
} }

View File

@ -140,7 +140,8 @@
"TerritoryId": 148, "TerritoryId": 148,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -114,6 +114,7 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true, "Enabled": true,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"Healer NPC is only killed after the boss dies; all NPCs need to be killed for the duty to complete" "Healer NPC is only killed after the boss dies; all NPCs need to be killed for the duty to complete"
] ]

View File

@ -31,6 +31,7 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": false, "Enabled": false,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"AI doesn't automatically target newly spawning adds until after the boss died, and dies (tested on CNJ)" "AI doesn't automatically target newly spawning adds until after the boss died, and dies (tested on CNJ)"
] ]

View File

@ -79,6 +79,7 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true, "Enabled": true,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"(Phase 1) Healer NPCs are only killed after the boss dies - allied NPCs will kill them eventually; all NPCs need to be killed for the duty to complete" "(Phase 1) Healer NPCs are only killed after the boss dies - allied NPCs will kill them eventually; all NPCs need to be killed for the duty to complete"
] ]

View File

@ -71,6 +71,7 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": false, "Enabled": false,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"(Phase 1, second enemy group) Stuck with enemy being out of sight -- but still able to attack you (tested on ACN)" "(Phase 1, second enemy group) Stuck with enemy being out of sight -- but still able to attack you (tested on ACN)"
] ]

View File

@ -75,7 +75,8 @@
"TerritoryId": 134, "TerritoryId": 134,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -31,6 +31,7 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": false, "Enabled": false,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"AI doesn't automatically target newly spawning adds until after the boss died (requires healing luck on ACN)" "AI doesn't automatically target newly spawning adds until after the boss died (requires healing luck on ACN)"
] ]

View File

@ -60,6 +60,7 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true, "Enabled": true,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"(Phase 1) Kills PGL NPCs and then the boss - allied NPCs will kill most other NPCs eventually; all NPCs need to be killed for the duty to complete" "(Phase 1) Kills PGL NPCs and then the boss - allied NPCs will kill most other NPCs eventually; all NPCs need to be killed for the duty to complete"
] ]

View File

@ -46,7 +46,8 @@
"TerritoryId": 145, "TerritoryId": 145,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -80,7 +80,8 @@
"TerritoryId": 130, "TerritoryId": 130,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
}, },
"AetheryteShortcut": "Ul'dah", "AetheryteShortcut": "Ul'dah",
"AethernetShortcut": [ "AethernetShortcut": [

View File

@ -90,7 +90,8 @@
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
}, },
"AetheryteShortcut": "East Shroud - Hawthorne Hut", "AetheryteShortcut": "East Shroud - Hawthorne Hut",
"SkipConditions": { "SkipConditions": {

View File

@ -65,7 +65,8 @@
"TerritoryId": 135, "TerritoryId": 135,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
}, },
"AethernetShortcut": [ "AethernetShortcut": [
"[Limsa Lominsa] The Aftcastle", "[Limsa Lominsa] The Aftcastle",

View File

@ -60,7 +60,8 @@
"TerritoryId": 140, "TerritoryId": 140,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
}, },
"AetheryteShortcut": "Western Thanalan - Horizon" "AetheryteShortcut": "Western Thanalan - Horizon"
} }

View File

@ -160,7 +160,8 @@
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -39,7 +39,8 @@
"TerritoryId": 140, "TerritoryId": 140,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -30,7 +30,8 @@
"TerritoryId": 141, "TerritoryId": 141,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -67,6 +67,7 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true, "Enabled": true,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"(Phase 1) Healer NPCs are only killed after the boss dies - allied NPCs will kill them eventually; all NPCs need to be killed for the duty to complete" "(Phase 1) Healer NPCs are only killed after the boss dies - allied NPCs will kill them eventually; all NPCs need to be killed for the duty to complete"
] ]

View File

@ -66,7 +66,8 @@
"TerritoryId": 152, "TerritoryId": 152,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -86,6 +86,7 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true, "Enabled": true,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"AI will kill initial adds before the boss, but not switch target whenever new enemies spawn; all NPCs need to be killed for the duty to complete" "AI will kill initial adds before the boss, but not switch target whenever new enemies spawn; all NPCs need to be killed for the duty to complete"
] ]

View File

@ -159,7 +159,11 @@
"Z": -805.478 "Z": -805.478
}, },
"TerritoryId": 140, "TerritoryId": 140,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

View File

@ -105,7 +105,8 @@
"TerritoryId": 1053, "TerritoryId": 1053,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -68,6 +68,15 @@
{ {
"Sequence": 3, "Sequence": 3,
"Steps": [ "Steps": [
{
"Position": {
"X": -561.9863,
"Y": 9.919454,
"Z": 66.29564
},
"TerritoryId": 152,
"InteractionType": "WalkTo"
},
{ {
"DataId": 1008276, "DataId": 1008276,
"Position": { "Position": {

View File

@ -78,6 +78,10 @@
"StopDistance": 1, "StopDistance": 1,
"TerritoryId": 156, "TerritoryId": 156,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"Fly": true "Fly": true
} }
] ]

View File

@ -100,6 +100,28 @@
2 2
] ]
}, },
{
"Position": {
"X": 86.662384,
"Y": 28.34813,
"Z": -627.5218
},
"TerritoryId": 156,
"InteractionType": "WalkTo",
"Fly": true,
"SkipConditions": {
"StepIf": {
"CompletionQuestVariablesFlags": [
null,
null,
null,
null,
null,
32
]
}
}
},
{ {
"DataId": 1009143, "DataId": 1009143,
"Position": { "Position": {
@ -109,7 +131,6 @@
}, },
"TerritoryId": 156, "TerritoryId": 156,
"InteractionType": "Interact", "InteractionType": "Interact",
"Fly": true,
"$": "1 112 0 0 0 2 -> 2 96 0 0 0 34", "$": "1 112 0 0 0 2 -> 2 96 0 0 0 34",
"CompletionQuestVariablesFlags": [ "CompletionQuestVariablesFlags": [
null, null,

View File

@ -71,6 +71,14 @@
}, },
"TerritoryId": 147, "TerritoryId": 147,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292,
"Notes": [
"Will target Eline first (other NPCs later), and move to some -other- group of NPCs; only re-targets once they're at 1 HP (for Eline) or die",
"If the target isn't in melee range but other NPCs are, whether any AOEs are used for nearby enemies seems random"
]
},
"Fly": true, "Fly": true,
"AetheryteShortcut": "Northern Thanalan - Ceruleum Processing Plant" "AetheryteShortcut": "Northern Thanalan - Ceruleum Processing Plant"
} }

View File

@ -28,7 +28,16 @@
"Z": -328.66406 "Z": -328.66406
}, },
"TerritoryId": 155, "TerritoryId": 155,
"InteractionType": "SinglePlayerDuty" "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": false,
"TestedBossModVersion": 292,
"Notes": [
"WIP: Needs to be re-tested",
"AI doesn't move after starting the instance, so enemies won't be triggered",
"(First Barrier) If the player is too far south, after being stunned by Vishap's roar, AI doesn't move out of the AOE and dies to the Cauterize"
]
}
} }
] ]
}, },

View File

@ -46,6 +46,10 @@
}, },
"TerritoryId": 155, "TerritoryId": 155,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
},
"Fly": true "Fly": true
} }
] ]

View File

@ -97,7 +97,8 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"Fly": true, "Fly": true,
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -30,7 +30,11 @@
}, },
"TerritoryId": 397, "TerritoryId": 397,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"Comment": "Walk straight to Gorgagne Mills basement, ignore footprints" "Comment": "Walk straight to Gorgagne Mills basement, ignore footprints",
"SinglePlayerDutyOptions": {
"Enabled": true,
"TestedBossModVersion": 292
}
} }
] ]
}, },

View File

@ -60,7 +60,8 @@
"TerritoryId": 401, "TerritoryId": 401,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -80,7 +80,8 @@
"[Ishgard] The Tribunal" "[Ishgard] The Tribunal"
], ],
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -30,7 +30,11 @@
"TerritoryId": 145, "TerritoryId": 145,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292,
"Notes": [
"Will not move into melee range to kill the gate; Alphinaud will kill it after a while"
]
} }
} }
] ]

View File

@ -80,7 +80,8 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"DisableNavmesh": true, "DisableNavmesh": true,
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -76,7 +76,8 @@
"TerritoryId": 418, "TerritoryId": 418,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -58,7 +58,8 @@
"Emote": "lookout", "Emote": "lookout",
"StopDistance": 0.25, "StopDistance": 0.25,
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -49,7 +49,8 @@
"[Idyllshire] Epilogue Gate (Eastern Hinterlands)" "[Idyllshire] Epilogue Gate (Eastern Hinterlands)"
], ],
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -70,7 +70,8 @@
"TerritoryId": 402, "TerritoryId": 402,
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": true "Enabled": true,
"TestedBossModVersion": 292
} }
} }
] ]

View File

@ -87,6 +87,7 @@
"Comment": "Estinien vs. Arch Ultima", "Comment": "Estinien vs. Arch Ultima",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": false, "Enabled": false,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"AI doesn't move automatically for the first boss", "AI doesn't move automatically for the first boss",
"AI doesn't move automatically for the dialogue with gaius on the bridge", "AI doesn't move automatically for the dialogue with gaius on the bridge",

View File

@ -48,6 +48,7 @@
"InteractionType": "SinglePlayerDuty", "InteractionType": "SinglePlayerDuty",
"SinglePlayerDutyOptions": { "SinglePlayerDutyOptions": {
"Enabled": false, "Enabled": false,
"TestedBossModVersion": 292,
"Notes": [ "Notes": [
"Doesn't walk to the teleporter to finish the duty" "Doesn't walk to the teleporter to finish the duty"
] ]

View File

@ -1285,6 +1285,10 @@
"maximum": 1, "maximum": 1,
"description": "If a quest has multiple solo instances (which affects 5 quests total), indicates which one this is" "description": "If a quest has multiple solo instances (which affects 5 quests total), indicates which one this is"
}, },
"TestedBossModVersion": {
"type": "number",
"minimum": 292
},
"$": { "$": {
"type": "string" "type": "string"
} }

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Dalamud.Configuration; using Dalamud.Configuration;
using Dalamud.Game.Text; using Dalamud.Game.Text;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
@ -45,7 +46,10 @@ internal sealed class Configuration : IPluginConfiguration
internal sealed class SinglePlayerDutyConfiguration internal sealed class SinglePlayerDutyConfiguration
{ {
public bool RunSoloInstancesWithBossMod { get; set; } public bool RunSoloInstancesWithBossMod { get; set; }
public byte RetryDifficulty { get; set; } = 2;
[SuppressMessage("Performance", "CA1822", Justification = "Will be fixed when no longer WIP")]
public byte RetryDifficulty => 0;
public HashSet<uint> WhitelistedSinglePlayerDutyCfcIds { get; set; } = []; public HashSet<uint> WhitelistedSinglePlayerDutyCfcIds { get; set; } = [];
public HashSet<uint> BlacklistedSinglePlayerDutyCfcIds { get; set; } = []; public HashSet<uint> BlacklistedSinglePlayerDutyCfcIds { get; set; } = [];
} }

View File

@ -1,4 +1,6 @@
using Microsoft.Extensions.Logging; using System.Linq;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Functions; using Questionable.Functions;
using Questionable.Model; using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
@ -21,7 +23,7 @@ internal static class NextQuest
return null; return null;
// probably irrelevant, since pick up is handled elsewhere (and, in particular, checks for aetherytes and stuff) // probably irrelevant, since pick up is handled elsewhere (and, in particular, checks for aetherytes and stuff)
if (questFunctions.GetPriorityQuests().Contains(step.NextQuestId)) if (questFunctions.GetPriorityQuests(onlyClassAndRoleQuests: true).Contains(step.NextQuestId))
return null; return null;
return new SetQuestTask(step.NextQuestId, quest.Id); return new SetQuestTask(step.NextQuestId, quest.Id);

View File

@ -96,6 +96,12 @@ internal static class Interact
private EInteractionState _interactionState = EInteractionState.None; private EInteractionState _interactionState = EInteractionState.None;
private DateTime _continueAt = DateTime.MinValue; private DateTime _continueAt = DateTime.MinValue;
/// <summary>
/// A slight delay when we think an interaction has ended, to make sure that we're processing "Action cancelled"
/// prior to the next step (in case we're attacked).
/// </summary>
private bool delayedFinalCheck;
public Quest? Quest => Task.Quest; public Quest? Quest => Task.Quest;
public EInteractionType InteractionType { get; set; } public EInteractionType InteractionType { get; set; }
@ -179,7 +185,14 @@ internal static class Interact
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
else if (ProgressContext.WasSuccessful() || else if (ProgressContext.WasSuccessful() ||
_interactionState == EInteractionState.InteractionConfirmed) _interactionState == EInteractionState.InteractionConfirmed)
return ETaskResult.TaskComplete; {
if (delayedFinalCheck)
return ETaskResult.TaskComplete;
_continueAt = DateTime.Now.AddSeconds(0.2);
delayedFinalCheck = true;
return ETaskResult.StillRunning;
}
} }
IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId); IGameObject? gameObject = gameFunctions.FindObjectByDataId(Task.DataId);

View File

@ -0,0 +1,169 @@
using System;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
namespace Questionable.Controller.Utils;
internal sealed class PartyWatchDog : IDisposable
{
private readonly QuestController _questController;
private readonly IClientState _clientState;
private readonly IChatGui _chatGui;
private readonly ILogger<PartyWatchDog> _logger;
private ushort? _uncheckedTeritoryId;
public PartyWatchDog(QuestController questController, IClientState clientState, IChatGui chatGui,
ILogger<PartyWatchDog> logger)
{
_questController = questController;
_clientState = clientState;
_chatGui = chatGui;
_logger = logger;
_clientState.TerritoryChanged += TerritoryChanged;
}
private unsafe void TerritoryChanged(ushort newTerritoryId)
{
var intendedUse = (ETerritoryIntendedUseEnum)GameMain.Instance()->CurrentTerritoryIntendedUseId;
switch (intendedUse)
{
case ETerritoryIntendedUseEnum.Gaol:
case ETerritoryIntendedUseEnum.Frontline:
case ETerritoryIntendedUseEnum.LordOfVerminion:
case ETerritoryIntendedUseEnum.Diadem:
case ETerritoryIntendedUseEnum.CrystallineConflict:
case ETerritoryIntendedUseEnum.Battlehall:
case ETerritoryIntendedUseEnum.CrystallineConflict2:
case ETerritoryIntendedUseEnum.DeepDungeon:
case ETerritoryIntendedUseEnum.TreasureMapDuty:
case ETerritoryIntendedUseEnum.Diadem2:
case ETerritoryIntendedUseEnum.RivalWings:
case ETerritoryIntendedUseEnum.Eureka:
case ETerritoryIntendedUseEnum.LeapOfFaith:
case ETerritoryIntendedUseEnum.OceanFishing:
case ETerritoryIntendedUseEnum.Diadem3:
case ETerritoryIntendedUseEnum.Bozja:
case ETerritoryIntendedUseEnum.Battlehall2:
case ETerritoryIntendedUseEnum.Battlehall3:
case ETerritoryIntendedUseEnum.LargeScaleRaid:
case ETerritoryIntendedUseEnum.LargeScaleSavageRaid:
case ETerritoryIntendedUseEnum.Blunderville:
StopIfRunning($"Unsupported Area entered ({newTerritoryId})");
break;
case ETerritoryIntendedUseEnum.Dungeon:
case ETerritoryIntendedUseEnum.VariantDungeon:
case ETerritoryIntendedUseEnum.AllianceRaid:
case ETerritoryIntendedUseEnum.Trial:
case ETerritoryIntendedUseEnum.Raid:
case ETerritoryIntendedUseEnum.Raid2:
case ETerritoryIntendedUseEnum.SeasonalEvent:
case ETerritoryIntendedUseEnum.SeasonalEvent2:
case ETerritoryIntendedUseEnum.CriterionDuty:
case ETerritoryIntendedUseEnum.CriterionSavageDuty:
_uncheckedTeritoryId = newTerritoryId;
_logger.LogInformation("Will check territory {TerritoryId} after loading", newTerritoryId);
break;
}
}
public unsafe void Update()
{
if (_uncheckedTeritoryId == _clientState.TerritoryType && GameMain.Instance()->TerritoryLoadState == 2)
{
var groupManager = GroupManager.Instance();
if (groupManager == null)
return;
byte memberCount = groupManager->MainGroup.MemberCount;
bool isInAlliance = groupManager->MainGroup.IsAlliance;
_logger.LogDebug("Territory {TerritoryId} with {MemberCount} members, alliance: {IsInAlliance}",
_uncheckedTeritoryId, memberCount, isInAlliance);
if (memberCount > 1 || isInAlliance)
StopIfRunning("Other party members present");
_uncheckedTeritoryId = null;
}
}
private void StopIfRunning(string reason)
{
if (_questController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual)
{
_chatGui.PrintError(
$"Stopping Questionable: {reason}. If you believe this to be correct, please restart Questionable manually.",
CommandHandler.MessageTag, CommandHandler.TagColor);
_questController.Stop(reason);
}
}
public void Dispose()
{
_clientState.TerritoryChanged -= TerritoryChanged;
}
// from https://github.com/NightmareXIV/ECommons/blob/f69e460e95134c72592654059843b138b4c01a9e/ECommons/ExcelServices/TerritoryIntendedUseEnum.cs#L5
[UsedImplicitly(ImplicitUseTargetFlags.Members, Reason = "game data")]
private enum ETerritoryIntendedUseEnum : byte
{
CityArea = 0,
OpenWorld = 1,
Inn = 2,
Dungeon = 3,
VariantDungeon = 4,
Gaol = 5,
StartingArea = 6,
QuestArea = 7,
AllianceRaid = 8,
QuestBattle = 9,
Trial = 10,
QuestArea2 = 12,
ResidentialArea = 13,
HousingInstances = 14,
QuestArea3 = 15,
Raid = 16,
Raid2 = 17,
Frontline = 18,
ChocoboSquare = 20,
RestorationEvent = 21,
Sanctum = 22,
GoldSaucer = 23,
LordOfVerminion = 25,
Diadem = 26,
HallOfTheNovice = 27,
CrystallineConflict = 28,
QuestBattle2 = 29,
Barracks = 30,
DeepDungeon = 31,
SeasonalEvent = 32,
TreasureMapDuty = 33,
SeasonalEventDuty = 34,
Battlehall = 35,
CrystallineConflict2 = 37,
Diadem2 = 38,
RivalWings = 39,
Unknown1 = 40,
Eureka = 41,
SeasonalEvent2 = 43,
LeapOfFaith = 44,
MaskedCarnivale = 45,
OceanFishing = 46,
Diadem3 = 47,
Bozja = 48,
IslandSanctuary = 49,
Battlehall2 = 50,
Battlehall3 = 51,
LargeScaleRaid = 52,
LargeScaleSavageRaid = 53,
QuestArea4 = 54,
TribalInstance = 56,
CriterionDuty = 57,
CriterionSavageDuty = 58,
Blunderville = 59,
}
}

View File

@ -7,6 +7,7 @@ using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Controller.GameUi; using Questionable.Controller.GameUi;
using Questionable.Controller.Utils;
using Questionable.Windows; using Questionable.Windows;
namespace Questionable; namespace Questionable;
@ -23,6 +24,7 @@ internal sealed class DalamudInitializer : IDisposable
private readonly ConfigWindow _configWindow; private readonly ConfigWindow _configWindow;
private readonly IToastGui _toastGui; private readonly IToastGui _toastGui;
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly PartyWatchDog _partyWatchDog;
private readonly ILogger<DalamudInitializer> _logger; private readonly ILogger<DalamudInitializer> _logger;
public DalamudInitializer( public DalamudInitializer(
@ -42,6 +44,7 @@ internal sealed class DalamudInitializer : IDisposable
PriorityWindow priorityWindow, PriorityWindow priorityWindow,
IToastGui toastGui, IToastGui toastGui,
Configuration configuration, Configuration configuration,
PartyWatchDog partyWatchDog,
ILogger<DalamudInitializer> logger) ILogger<DalamudInitializer> logger)
{ {
_pluginInterface = pluginInterface; _pluginInterface = pluginInterface;
@ -54,6 +57,7 @@ internal sealed class DalamudInitializer : IDisposable
_configWindow = configWindow; _configWindow = configWindow;
_toastGui = toastGui; _toastGui = toastGui;
_configuration = configuration; _configuration = configuration;
_partyWatchDog = partyWatchDog;
_logger = logger; _logger = logger;
_windowSystem.AddWindow(oneTimeSetupWindow); _windowSystem.AddWindow(oneTimeSetupWindow);
@ -77,6 +81,7 @@ internal sealed class DalamudInitializer : IDisposable
private void FrameworkUpdate(IFramework framework) private void FrameworkUpdate(IFramework framework)
{ {
_partyWatchDog.Update();
_questController.Update(); _questController.Update();
try try

View File

@ -401,14 +401,15 @@ internal sealed unsafe class QuestFunctions
return 1000 * quest.AllSteps().Count(x => x.Step.AetheryteShortcut != null); return 1000 * quest.AllSteps().Count(x => x.Step.AetheryteShortcut != null);
} }
public List<ElementId> GetPriorityQuests() public List<ElementId> GetPriorityQuests(bool onlyClassAndRoleQuests = false)
{ {
List<ElementId> priorityQuests = List<ElementId> priorityQuests = [];
[ if (!onlyClassAndRoleQuests)
new QuestId(1157), // Garuda (Hard) {
new QuestId(1158), // Titan (Hard) priorityQuests.Add(new QuestId(1157)); // Garuda (Hard)
..QuestData.CrystalTowerQuests priorityQuests.Add(new QuestId(1158)); // Titan (Hard)
]; priorityQuests.AddRange(QuestData.CrystalTowerQuests);
}
EClassJob classJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer; EClassJob classJob = (EClassJob?)_clientState.LocalPlayer?.ClassJob.RowId ?? EClassJob.Adventurer;
uint[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select(x => x[0]).ToArray(); uint[] shadowbringersRoleQuestChapters = QuestData.AllRoleQuestChapters.Select(x => x[0]).ToArray();

View File

@ -20,6 +20,7 @@ using Questionable.Controller.Steps.Common;
using Questionable.Controller.Steps.Gathering; using Questionable.Controller.Steps.Gathering;
using Questionable.Controller.Steps.Interactions; using Questionable.Controller.Steps.Interactions;
using Questionable.Controller.Steps.Leves; using Questionable.Controller.Steps.Leves;
using Questionable.Controller.Utils;
using Questionable.Data; using Questionable.Data;
using Questionable.External; using Questionable.External;
using Questionable.Functions; using Questionable.Functions;
@ -260,6 +261,8 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton<ShopController>(); serviceCollection.AddSingleton<ShopController>();
serviceCollection.AddSingleton<InterruptHandler>(); serviceCollection.AddSingleton<InterruptHandler>();
serviceCollection.AddSingleton<PartyWatchDog>();
serviceCollection.AddSingleton<CraftworksSupplyController>(); serviceCollection.AddSingleton<CraftworksSupplyController>();
serviceCollection.AddSingleton<CreditsController>(); serviceCollection.AddSingleton<CreditsController>();
serviceCollection.AddSingleton<HelpUiController>(); serviceCollection.AddSingleton<HelpUiController>();

View File

@ -9,7 +9,7 @@ internal sealed class UniqueStartStopValidator : IQuestValidator
{ {
public IEnumerable<ValidationIssue> Validate(Quest quest) public IEnumerable<ValidationIssue> Validate(Quest quest)
{ {
if (quest.Id is SatisfactionSupplyNpcId) if (quest.Id is SatisfactionSupplyNpcId or AlliedSocietyDailyId)
yield break; yield break;
var questAccepts = var questAccepts =

View File

@ -34,7 +34,9 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
(EClassJob.BlackMage, "Magical Ranged Role Quests"), (EClassJob.BlackMage, "Magical Ranged Role Quests"),
]; ];
#if false
private readonly string[] _retryDifficulties = ["Normal", "Easy", "Very Easy"]; private readonly string[] _retryDifficulties = ["Normal", "Easy", "Very Easy"];
#endif
private readonly TerritoryData _territoryData; private readonly TerritoryData _territoryData;
private readonly QuestRegistry _questRegistry; private readonly QuestRegistry _questRegistry;
@ -263,12 +265,19 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
using (ImRaii.PushIndent(ImGui.GetFrameHeight() + ImGui.GetStyle().ItemInnerSpacing.X)) using (ImRaii.PushIndent(ImGui.GetFrameHeight() + ImGui.GetStyle().ItemInnerSpacing.X))
{ {
ImGui.AlignTextToFramePadding(); using (_ = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed))
ImGui.TextColored(ImGuiColors.DalamudRed, {
"Work in Progress: For now, this will always use BossMod for combat."); ImGui.TextUnformatted("Work in Progress:");
ImGui.BulletText("Will always use BossMod for combat (ignoring the configured combat module).");
ImGui.BulletText("Only a small subset of quest battles have been tested - most of which are in the MSQ.");
ImGui.BulletText("When retrying a failed battle, it will always start at 'Normal' difficulty.");
ImGui.BulletText("Please don't enable this option when using a BossMod fork (such as Reborn);\nwith the missing combat module configuration, it is unlikely to be compatible.");
}
#if false
using (ImRaii.Disabled(!runSoloInstancesWithBossMod)) using (ImRaii.Disabled(!runSoloInstancesWithBossMod))
{ {
ImGui.Spacing();
int retryDifficulty = Configuration.SinglePlayerDuties.RetryDifficulty; int retryDifficulty = Configuration.SinglePlayerDuties.RetryDifficulty;
if (ImGui.Combo("Difficulty when retrying a quest battle", ref retryDifficulty, _retryDifficulties, if (ImGui.Combo("Difficulty when retrying a quest battle", ref retryDifficulty, _retryDifficulties,
_retryDifficulties.Length)) _retryDifficulties.Length))
@ -277,6 +286,7 @@ internal sealed class SinglePlayerDutyConfigComponent : ConfigComponent
Save(); Save();
} }
} }
#endif
} }
ImGui.Separator(); ImGui.Separator();