1
0
Fork 0
forked from aly/qstbak

muffin v7.4.6

This commit is contained in:
alydev 2025-12-24 05:01:16 +10:00
parent bb09805213
commit 1cc65e495d
13 changed files with 1291 additions and 949 deletions

View file

@ -52,13 +52,16 @@ internal sealed class CreditsController : IDisposable
private readonly IAddonLifecycle _addonLifecycle;
private readonly Configuration _configuration;
private readonly ILogger<CreditsController> _logger;
private static readonly object _lock = new object();
public CreditsController(IAddonLifecycle addonLifecycle, ILogger<CreditsController> logger)
public CreditsController(IAddonLifecycle addonLifecycle, Configuration configuration, ILogger<CreditsController> logger)
{
_addonLifecycle = addonLifecycle;
_configuration = configuration;
_logger = logger;
lock (_lock)
{
@ -105,6 +108,11 @@ internal sealed class CreditsController : IDisposable
private void HandleCutscene(Action nativeAction, AddonArgs args)
{
if (_configuration.General.CinemaMode)
{
_logger.LogDebug("HandleCutscene: Cinema Mode enabled, not skipping cutscene.");
return;
}
long num = ((args.Addon.Address == IntPtr.Zero) ? 0 : ((IntPtr)args.Addon.Address).ToInt64());
if (num == 0L)
{

View file

@ -4,24 +4,32 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
using LLib.GameData;
using Microsoft.Extensions.Logging;
using Questionable.Data;
using Questionable.Model.Questing;
namespace Questionable.Controller.Utils;
internal sealed class PartyWatchDog : IDisposable
internal sealed class PartyWatchdog : IDisposable
{
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly TerritoryData _territoryData;
private readonly IClientState _clientState;
private readonly IChatGui _chatGui;
private readonly ILogger<PartyWatchDog> _logger;
private readonly ILogger<PartyWatchdog> _logger;
private ushort? _uncheckedTeritoryId;
public PartyWatchDog(QuestController questController, IClientState clientState, IChatGui chatGui, ILogger<PartyWatchDog> logger)
public PartyWatchdog(QuestController questController, Configuration configuration, TerritoryData territoryData, IClientState clientState, IChatGui chatGui, ILogger<PartyWatchdog> logger)
{
_questController = questController;
_configuration = configuration;
_territoryData = territoryData;
_clientState = clientState;
_chatGui = chatGui;
_logger = logger;
@ -30,92 +38,116 @@ internal sealed class PartyWatchDog : IDisposable
private unsafe void TerritoryChanged(ushort newTerritoryId)
{
switch ((ETerritoryIntendedUse)GameMain.Instance()->CurrentTerritoryIntendedUseId)
if (!_configuration.Advanced.DisablePartyWatchdog)
{
case ETerritoryIntendedUse.Gaol:
case ETerritoryIntendedUse.Frontline:
case ETerritoryIntendedUse.LordOfVerminion:
case ETerritoryIntendedUse.Diadem:
case ETerritoryIntendedUse.CrystallineConflict:
case ETerritoryIntendedUse.DeepDungeon:
case ETerritoryIntendedUse.TreasureMapDuty:
case ETerritoryIntendedUse.Battlehall:
case ETerritoryIntendedUse.CrystallineConflict2:
case ETerritoryIntendedUse.Diadem2:
case ETerritoryIntendedUse.RivalWings:
case ETerritoryIntendedUse.Eureka:
case ETerritoryIntendedUse.LeapOfFaith:
case ETerritoryIntendedUse.OceanFishing:
case ETerritoryIntendedUse.Diadem3:
case ETerritoryIntendedUse.Bozja:
case ETerritoryIntendedUse.Battlehall2:
case ETerritoryIntendedUse.Battlehall3:
case ETerritoryIntendedUse.LargeScaleRaid:
case ETerritoryIntendedUse.LargeScaleSavageRaid:
case ETerritoryIntendedUse.Blunderville:
StopIfRunning($"Unsupported Area entered ({newTerritoryId})");
break;
case ETerritoryIntendedUse.Dungeon:
case ETerritoryIntendedUse.VariantDungeon:
case ETerritoryIntendedUse.AllianceRaid:
case ETerritoryIntendedUse.Trial:
case ETerritoryIntendedUse.Raid:
case ETerritoryIntendedUse.Raid2:
case ETerritoryIntendedUse.SeasonalEvent:
case ETerritoryIntendedUse.SeasonalEvent2:
case ETerritoryIntendedUse.CriterionDuty:
case ETerritoryIntendedUse.CriterionSavageDuty:
_uncheckedTeritoryId = newTerritoryId;
_logger.LogInformation("Will check territory {TerritoryId} after loading", newTerritoryId);
break;
case ETerritoryIntendedUse.StartingArea:
case ETerritoryIntendedUse.QuestArea:
case ETerritoryIntendedUse.QuestBattle:
case (ETerritoryIntendedUse)11:
case ETerritoryIntendedUse.QuestArea2:
case ETerritoryIntendedUse.ResidentialArea:
case ETerritoryIntendedUse.HousingInstances:
case ETerritoryIntendedUse.QuestArea3:
case (ETerritoryIntendedUse)19:
case ETerritoryIntendedUse.ChocoboSquare:
case ETerritoryIntendedUse.RestorationEvent:
case ETerritoryIntendedUse.Sanctum:
case ETerritoryIntendedUse.GoldSaucer:
case (ETerritoryIntendedUse)24:
case ETerritoryIntendedUse.HallOfTheNovice:
case ETerritoryIntendedUse.QuestBattle2:
case ETerritoryIntendedUse.Barracks:
case ETerritoryIntendedUse.SeasonalEventDuty:
case (ETerritoryIntendedUse)36:
case ETerritoryIntendedUse.Unknown1:
case (ETerritoryIntendedUse)42:
case ETerritoryIntendedUse.MaskedCarnivale:
case ETerritoryIntendedUse.IslandSanctuary:
case ETerritoryIntendedUse.QuestArea4:
case (ETerritoryIntendedUse)55:
case ETerritoryIntendedUse.TribalInstance:
break;
switch ((ETerritoryIntendedUse)GameMain.Instance()->CurrentTerritoryIntendedUseId)
{
case ETerritoryIntendedUse.Gaol:
case ETerritoryIntendedUse.Frontline:
case ETerritoryIntendedUse.LordOfVerminion:
case ETerritoryIntendedUse.Diadem:
case ETerritoryIntendedUse.CrystallineConflict:
case ETerritoryIntendedUse.DeepDungeon:
case ETerritoryIntendedUse.TreasureMapDuty:
case ETerritoryIntendedUse.Battlehall:
case ETerritoryIntendedUse.CrystallineConflict2:
case ETerritoryIntendedUse.Diadem2:
case ETerritoryIntendedUse.RivalWings:
case ETerritoryIntendedUse.Eureka:
case ETerritoryIntendedUse.LeapOfFaith:
case ETerritoryIntendedUse.OceanFishing:
case ETerritoryIntendedUse.Diadem3:
case ETerritoryIntendedUse.Bozja:
case ETerritoryIntendedUse.Battlehall2:
case ETerritoryIntendedUse.Battlehall3:
case ETerritoryIntendedUse.LargeScaleRaid:
case ETerritoryIntendedUse.LargeScaleSavageRaid:
case ETerritoryIntendedUse.Blunderville:
StopIfRunning($"Unsupported Area entered ({newTerritoryId})");
break;
case ETerritoryIntendedUse.Dungeon:
case ETerritoryIntendedUse.VariantDungeon:
case ETerritoryIntendedUse.AllianceRaid:
case ETerritoryIntendedUse.Trial:
case ETerritoryIntendedUse.Raid:
case ETerritoryIntendedUse.Raid2:
case ETerritoryIntendedUse.SeasonalEvent:
case ETerritoryIntendedUse.SeasonalEvent2:
case ETerritoryIntendedUse.CriterionDuty:
case ETerritoryIntendedUse.CriterionSavageDuty:
_uncheckedTeritoryId = newTerritoryId;
_logger.LogInformation("Will check territory {TerritoryId} after loading", newTerritoryId);
break;
case ETerritoryIntendedUse.StartingArea:
case ETerritoryIntendedUse.QuestArea:
case ETerritoryIntendedUse.QuestBattle:
case (ETerritoryIntendedUse)11:
case ETerritoryIntendedUse.QuestArea2:
case ETerritoryIntendedUse.ResidentialArea:
case ETerritoryIntendedUse.HousingInstances:
case ETerritoryIntendedUse.QuestArea3:
case (ETerritoryIntendedUse)19:
case ETerritoryIntendedUse.ChocoboSquare:
case ETerritoryIntendedUse.RestorationEvent:
case ETerritoryIntendedUse.Sanctum:
case ETerritoryIntendedUse.GoldSaucer:
case (ETerritoryIntendedUse)24:
case ETerritoryIntendedUse.HallOfTheNovice:
case ETerritoryIntendedUse.QuestBattle2:
case ETerritoryIntendedUse.Barracks:
case ETerritoryIntendedUse.SeasonalEventDuty:
case (ETerritoryIntendedUse)36:
case ETerritoryIntendedUse.Unknown1:
case (ETerritoryIntendedUse)42:
case ETerritoryIntendedUse.MaskedCarnivale:
case ETerritoryIntendedUse.IslandSanctuary:
case ETerritoryIntendedUse.QuestArea4:
case (ETerritoryIntendedUse)55:
case ETerritoryIntendedUse.TribalInstance:
break;
}
}
}
public unsafe void Update()
{
if (_uncheckedTeritoryId != _clientState.TerritoryType || GameMain.Instance()->TerritoryLoadState != 2)
if (_configuration.Advanced.DisablePartyWatchdog || _uncheckedTeritoryId != _clientState.TerritoryType || GameMain.Instance()->TerritoryLoadState != 2)
{
return;
}
GroupManager* ptr = GroupManager.Instance();
if (ptr != null)
if (ptr == null)
{
byte memberCount = ptr->MainGroup.MemberCount;
bool isAlliance = ptr->MainGroup.IsAlliance;
_logger.LogDebug("Territory {TerritoryId} with {MemberCount} members, alliance: {IsInAlliance}", _uncheckedTeritoryId, memberCount, isAlliance);
if (memberCount > 1 || isAlliance)
return;
}
byte memberCount = ptr->MainGroup.MemberCount;
bool isAlliance = ptr->MainGroup.IsAlliance;
_logger.LogDebug("Territory {TerritoryId} with {MemberCount} members, alliance: {IsInAlliance}", _uncheckedTeritoryId, memberCount, isAlliance);
if (memberCount > 1 || isAlliance)
{
if (!IsDutyConfiguredForParty(_uncheckedTeritoryId.Value))
{
StopIfRunning("Other party members present");
}
_uncheckedTeritoryId = null;
else
{
_logger.LogInformation("Party detected but duty is configured for Unsync (Party) mode, continuing");
}
}
_uncheckedTeritoryId = null;
}
private bool IsDutyConfiguredForParty(ushort territoryId)
{
if (!_territoryData.TryGetContentFinderConditionIdForTerritory(territoryId, out var cfcId))
{
return false;
}
if (_configuration.Duties.DutyModeOverrides.TryGetValue(cfcId, out var value))
{
return value == EDutyMode.UnsyncParty;
}
return _configuration.Duties.DefaultDutyMode == EDutyMode.UnsyncParty;
}
private void StopIfRunning(string reason)

View file

@ -361,6 +361,13 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (_configuration.Stop.Enabled && _startedQuest != null)
{
string text = _startedQuest.Quest.Id.ToString();
if (_configuration.Stop.LevelToStopAfter && IsRunning && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel)
{
_logger.LogInformation("Reached level stop condition (current: {CurrentLevel}, target: {TargetLevel})", playerCharacter.Level, _configuration.Stop.TargetLevel);
_chatGui.Print($"Character level {playerCharacter.Level} reached target level {_configuration.Stop.TargetLevel}.", "Questionable", 576);
Stop($"Level stop condition reached [{playerCharacter.Level}]");
return;
}
if (_configuration.Stop.QuestSequences.TryGetValue(text, out var value) && value.HasValue)
{
int sequence = _startedQuest.Sequence;
@ -395,17 +402,17 @@ internal sealed class QuestController : MiniTaskController<QuestController>
if (step == 0 || step == 255)
{
flag2 = true;
goto IL_054c;
goto IL_0691;
}
}
flag2 = false;
goto IL_054c;
goto IL_0691;
}
goto IL_0550;
IL_054c:
goto IL_0695;
IL_0691:
flag = flag2;
goto IL_0550;
IL_0550:
goto IL_0695;
IL_0695:
if (flag && DateTime.Now >= CurrentQuest.StepProgress.StartedAt.AddSeconds(15.0))
{
lock (_progressLock)
@ -958,8 +965,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
using (_logger.BeginScope("Q/" + label))
{
AutomationType = EAutomationType.Automatic;
ExecuteNextStep();
if (!CheckAndBlockForStopConditions())
{
AutomationType = EAutomationType.Automatic;
ExecuteNextStep();
}
}
}
@ -967,8 +977,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
using (_logger.BeginScope("GQ/" + label))
{
AutomationType = EAutomationType.GatheringOnly;
ExecuteNextStep();
if (!CheckAndBlockForStopConditions())
{
AutomationType = EAutomationType.GatheringOnly;
ExecuteNextStep();
}
}
}
@ -976,8 +989,11 @@ internal sealed class QuestController : MiniTaskController<QuestController>
{
using (_logger.BeginScope("SQ/" + label))
{
AutomationType = EAutomationType.SingleQuestA;
ExecuteNextStep();
if (!CheckAndBlockForStopConditions())
{
AutomationType = EAutomationType.SingleQuestA;
ExecuteNextStep();
}
}
}
@ -990,6 +1006,65 @@ internal sealed class QuestController : MiniTaskController<QuestController>
}
}
private bool CheckAndBlockForStopConditions()
{
if (!_configuration.Stop.Enabled)
{
return false;
}
if (_configuration.Stop.LevelToStopAfter && _objectTable[0] is IPlayerCharacter playerCharacter && playerCharacter.Level >= _configuration.Stop.TargetLevel)
{
_logger.LogInformation("Blocking start: Level stop condition already met (current: {CurrentLevel}, target: {TargetLevel})", playerCharacter.Level, _configuration.Stop.TargetLevel);
_chatGui.Print($"Cannot start: Character level {playerCharacter.Level} has reached target level {_configuration.Stop.TargetLevel}.", "Questionable", 576);
return true;
}
foreach (ElementId item in _configuration.Stop.QuestsToStopAfter)
{
string key = item.ToString();
if (_configuration.Stop.QuestSequences.TryGetValue(key, out var value) && value.HasValue)
{
if (!_questFunctions.IsQuestAccepted(item))
{
continue;
}
QuestProgressInfo questProgressInfo = _questFunctions.GetQuestProgressInfo(item);
if (questProgressInfo != null && questProgressInfo.Sequence >= value.Value)
{
if (_questRegistry.TryGetQuest(item, out Quest quest))
{
_logger.LogInformation("Blocking start: Quest '{QuestName}' is at sequence {CurrentSequence}, stop sequence is {StopSequence}", quest.Info.Name, questProgressInfo.Sequence, value.Value);
_chatGui.Print($"Cannot start: Quest '{quest.Info.Name}' is at sequence {questProgressInfo.Sequence}, configured stop sequence is {value.Value}.", "Questionable", 576);
}
return true;
}
}
else if (_questFunctions.IsQuestComplete(item))
{
if (_questRegistry.TryGetQuest(item, out Quest quest2))
{
_logger.LogInformation("Blocking start: Quest '{QuestName}' is already complete and configured as a stop condition", quest2.Info.Name);
_chatGui.Print("Cannot start: Quest '" + quest2.Info.Name + "' is complete and configured as a stopping point.", "Questionable", 576);
}
return true;
}
}
if (_configuration.Stop.SequenceToStopAfter && _startedQuest != null)
{
string key2 = _startedQuest.Quest.Id.ToString();
if (!_configuration.Stop.QuestSequences.ContainsKey(key2))
{
int sequence = _startedQuest.Sequence;
if (sequence >= _configuration.Stop.TargetSequence)
{
_logger.LogInformation("Blocking start: Global sequence stop condition already met (current: {CurrentSequence}, target: {TargetSequence})", sequence, _configuration.Stop.TargetSequence);
_chatGui.Print($"Cannot start: Quest sequence {sequence} has reached target sequence {_configuration.Stop.TargetSequence}.", "Questionable", 576);
return true;
}
}
}
return false;
}
private void ExecuteNextStep()
{
ClearTasksInternal();

File diff suppressed because it is too large Load diff

View file

@ -33,6 +33,8 @@ internal sealed class TerritoryData
private readonly ImmutableDictionary<uint, ContentFinderConditionData> _contentFinderConditions;
private readonly ImmutableDictionary<uint, uint> _territoryToContentFinderCondition;
private readonly ImmutableDictionary<(ElementId QuestId, byte Index), uint> _questBattlesToContentFinderCondition;
public TerritoryData(IDataManager dataManager)
@ -67,6 +69,9 @@ internal sealed class TerritoryData
return flag && x.ContentType.RowId != 6;
})
select new ContentFinderConditionData(x, dataManager.Language)).ToImmutableDictionary((ContentFinderConditionData x) => x.ContentFinderConditionId, (ContentFinderConditionData x) => x);
_territoryToContentFinderCondition = (from x in dataManager.GetExcelSheet<ContentFinderCondition>()
where x.RowId != 0 && x.TerritoryType.RowId != 0
group x by x.TerritoryType.RowId).ToImmutableDictionary((IGrouping<uint, ContentFinderCondition> g) => g.Key, (IGrouping<uint, ContentFinderCondition> g) => g.First().RowId);
_questBattlesToContentFinderCondition = (from x in (from x in dataManager.GetExcelSheet<Quest>()
where x.RowId != 0 && x.IssuerLocation.RowId != 0
select x).SelectMany(GetQuestBattles)
@ -138,6 +143,11 @@ internal sealed class TerritoryData
return false;
}
public bool TryGetContentFinderConditionIdForTerritory(ushort territoryId, out uint cfcId)
{
return _territoryToContentFinderCondition.TryGetValue(territoryId, out cfcId);
}
public IEnumerable<(ElementId QuestId, byte Index, ContentFinderConditionData Data)> GetAllQuestsWithQuestBattles()
{
return _questBattlesToContentFinderCondition.Select<KeyValuePair<(ElementId, byte), uint>, (ElementId, byte, ContentFinderConditionData)>((KeyValuePair<(ElementId QuestId, byte Index), uint> x) => (QuestId: x.Key.QuestId, Index: x.Key.Index, _contentFinderConditions[x.Value]));

View file

@ -1,4 +1,5 @@
using System;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Dalamud.Plugin.Services;
@ -10,31 +11,37 @@ internal sealed class TextAdvanceIpc : IDisposable
{
public sealed class ExternalTerritoryConfig
{
public bool? EnableQuestAccept = true;
public bool? EnableQuestAccept;
public bool? EnableQuestComplete = true;
public bool? EnableQuestComplete;
public bool? EnableRewardPick = true;
public bool? EnableRewardPick;
public bool? EnableRequestHandin = true;
public bool? EnableRequestHandin;
public bool? EnableCutsceneEsc = true;
public bool? EnableCutsceneEsc;
public bool? EnableCutsceneSkipConfirm = true;
public bool? EnableCutsceneSkipConfirm;
public bool? EnableTalkSkip = true;
public bool? EnableTalkSkip;
public bool? EnableRequestFill = true;
public bool? EnableRequestFill;
public bool? EnableAutoInteract = false;
public bool? EnableAutoInteract;
}
private bool _isExternalControlActivated;
private bool _lastCinemaModeState;
private bool _lastInCutsceneState;
private readonly QuestController _questController;
private readonly Configuration _configuration;
private readonly ICondition _condition;
private readonly IFramework _framework;
private readonly ICallGateSubscriber<bool> _isInExternalControl;
@ -45,13 +52,12 @@ internal sealed class TextAdvanceIpc : IDisposable
private readonly string _pluginName;
private readonly ExternalTerritoryConfig _externalTerritoryConfig = new ExternalTerritoryConfig();
public TextAdvanceIpc(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, Configuration configuration)
public TextAdvanceIpc(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, Configuration configuration, ICondition condition)
{
_framework = framework;
_questController = questController;
_configuration = configuration;
_condition = condition;
_isInExternalControl = pluginInterface.GetIpcSubscriber<bool>("TextAdvance.IsInExternalControl");
_enableExternalControl = pluginInterface.GetIpcSubscriber<string, ExternalTerritoryConfig, bool>("TextAdvance.EnableExternalControl");
_disableExternalControl = pluginInterface.GetIpcSubscriber<string, bool>("TextAdvance.DisableExternalControl");
@ -71,16 +77,53 @@ internal sealed class TextAdvanceIpc : IDisposable
private void OnUpdate(IFramework framework)
{
bool flag = _questController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual;
if (_configuration.General.ConfigureTextAdvance && flag)
if (!_configuration.General.ConfigureTextAdvance || !flag)
{
if (!_isInExternalControl.InvokeFunc() && _enableExternalControl.InvokeFunc(_pluginName, _externalTerritoryConfig))
if (_isExternalControlActivated && (_disableExternalControl.InvokeFunc(_pluginName) || !_isInExternalControl.InvokeFunc()))
{
_isExternalControlActivated = true;
_isExternalControlActivated = false;
}
return;
}
bool cinemaMode = _configuration.General.CinemaMode;
bool flag2 = _condition[ConditionFlag.OccupiedInCutSceneEvent] || _condition[ConditionFlag.WatchingCutscene] || _condition[ConditionFlag.WatchingCutscene78];
bool flag3 = cinemaMode != _lastCinemaModeState || (cinemaMode && flag2 != _lastInCutsceneState);
if (!_isExternalControlActivated)
{
if (!_isInExternalControl.InvokeFunc())
{
ExternalTerritoryConfig arg = CreateExternalTerritoryConfig(cinemaMode, flag2);
if (_enableExternalControl.InvokeFunc(_pluginName, arg))
{
_isExternalControlActivated = true;
_lastCinemaModeState = cinemaMode;
_lastInCutsceneState = flag2;
}
}
}
else if (_isExternalControlActivated && (_disableExternalControl.InvokeFunc(_pluginName) || !_isInExternalControl.InvokeFunc()))
else if (flag3)
{
_isExternalControlActivated = false;
ExternalTerritoryConfig arg2 = CreateExternalTerritoryConfig(cinemaMode, flag2);
_enableExternalControl.InvokeFunc(_pluginName, arg2);
_lastCinemaModeState = cinemaMode;
_lastInCutsceneState = flag2;
}
}
private static ExternalTerritoryConfig CreateExternalTerritoryConfig(bool cinemaMode, bool inCutscene)
{
bool flag = cinemaMode && inCutscene;
return new ExternalTerritoryConfig
{
EnableQuestAccept = true,
EnableQuestComplete = true,
EnableRewardPick = true,
EnableRequestHandin = true,
EnableCutsceneEsc = !cinemaMode,
EnableCutsceneSkipConfirm = !cinemaMode,
EnableTalkSkip = !flag,
EnableRequestFill = true,
EnableAutoInteract = false
};
}
}

View file

@ -52,15 +52,23 @@ internal sealed class DebugConfigComponent : ConfigComponent
base.Configuration.Advanced.AdditionalStatusInformation = v4;
Save();
}
bool v5 = base.Configuration.Advanced.DisablePartyWatchdog;
if (ImGui.Checkbox("Disable Party Watchdog", ref v5))
{
base.Configuration.Advanced.DisablePartyWatchdog = v5;
Save();
}
ImGui.SameLine();
ImGuiComponents.HelpMarker("The Party Watchdog stops Questionable when entering certain zones with other party members, or when entering unsupported content. Disabling this allows Questionable to continue working while in a party, but may cause unexpected behavior in group content.");
ImGui.Separator();
ImGui.Text("AutoDuty Settings");
using (ImRaii.PushIndent())
{
ImGui.AlignTextToFramePadding();
bool v5 = base.Configuration.Advanced.DisableAutoDutyBareMode;
if (ImGui.Checkbox("Use Pre-Loop/Loop/Post-Loop settings", ref v5))
bool v6 = base.Configuration.Advanced.DisableAutoDutyBareMode;
if (ImGui.Checkbox("Use Pre-Loop/Loop/Post-Loop settings", ref v6))
{
base.Configuration.Advanced.DisableAutoDutyBareMode = v5;
base.Configuration.Advanced.DisableAutoDutyBareMode = v6;
Save();
}
ImGui.SameLine();
@ -70,42 +78,42 @@ internal sealed class DebugConfigComponent : ConfigComponent
ImGui.Text("Quest/Interaction Skips");
using (ImRaii.PushIndent())
{
bool v6 = base.Configuration.Advanced.SkipAetherCurrents;
if (ImGui.Checkbox("Don't pick up aether currents/aether current quests", ref v6))
bool v7 = base.Configuration.Advanced.SkipAetherCurrents;
if (ImGui.Checkbox("Don't pick up aether currents/aether current quests", ref v7))
{
base.Configuration.Advanced.SkipAetherCurrents = v6;
base.Configuration.Advanced.SkipAetherCurrents = v7;
Save();
}
ImGui.SameLine();
ImGuiComponents.HelpMarker("If not done during the MSQ by Questionable, you have to manually pick up any missed aether currents/quests. There is no way to automatically pick up all missing aether currents.");
bool v7 = base.Configuration.Advanced.SkipClassJobQuests;
if (ImGui.Checkbox("Don't pick up class/job/role quests", ref v7))
bool v8 = base.Configuration.Advanced.SkipClassJobQuests;
if (ImGui.Checkbox("Don't pick up class/job/role quests", ref v8))
{
base.Configuration.Advanced.SkipClassJobQuests = v7;
base.Configuration.Advanced.SkipClassJobQuests = v8;
Save();
}
ImGui.SameLine();
ImGuiComponents.HelpMarker("Class and job skills for A Realm Reborn, Heavensward and (for the Lv70 skills) Stormblood are locked behind quests. Not recommended if you plan on queueing for instances with duty finder/party finder.\n\nNote: This setting is ignored for the first class/job quest if your character is not high enough level to start the level 4 MSQ.");
bool v8 = base.Configuration.Advanced.SkipARealmRebornHardModePrimals;
if (ImGui.Checkbox("Don't pick up ARR hard mode primal quests", ref v8))
bool v9 = base.Configuration.Advanced.SkipARealmRebornHardModePrimals;
if (ImGui.Checkbox("Don't pick up ARR hard mode primal quests", ref v9))
{
base.Configuration.Advanced.SkipARealmRebornHardModePrimals = v8;
base.Configuration.Advanced.SkipARealmRebornHardModePrimals = v9;
Save();
}
ImGui.SameLine();
ImGuiComponents.HelpMarker("Hard mode Ifrit/Garuda/Titan are required for the Patch 2.5 quest 'Good Intentions' and to start Heavensward.");
bool v9 = base.Configuration.Advanced.SkipCrystalTowerRaids;
if (ImGui.Checkbox("Don't pick up Crystal Tower quests", ref v9))
bool v10 = base.Configuration.Advanced.SkipCrystalTowerRaids;
if (ImGui.Checkbox("Don't pick up Crystal Tower quests", ref v10))
{
base.Configuration.Advanced.SkipCrystalTowerRaids = v9;
base.Configuration.Advanced.SkipCrystalTowerRaids = v10;
Save();
}
ImGui.SameLine();
ImGuiComponents.HelpMarker("Crystal Tower raids are required for the Patch 2.55 quest 'A Time to Every Purpose' and to start Heavensward.");
bool v10 = base.Configuration.Advanced.PreventQuestCompletion;
if (ImGui.Checkbox("Prevent quest completion", ref v10))
bool v11 = base.Configuration.Advanced.PreventQuestCompletion;
if (ImGui.Checkbox("Prevent quest completion", ref v11))
{
base.Configuration.Advanced.PreventQuestCompletion = v10;
base.Configuration.Advanced.PreventQuestCompletion = v11;
Save();
}
ImGui.SameLine();

View file

@ -43,6 +43,10 @@ internal sealed class GeneralConfigComponent : ConfigComponent
private bool _mountComboJustOpened;
private string _classJobSearchText = string.Empty;
private bool _classJobComboJustOpened;
public GeneralConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration, IDataManager dataManager, ClassJobUtils classJobUtils, QuestRegistry questRegistry, TerritoryData territoryData)
: base(pluginInterface, configuration)
{
@ -155,17 +159,54 @@ internal sealed class GeneralConfigComponent : ConfigComponent
base.Configuration.General.GrandCompany = (FFXIVClientStructs.FFXIV.Client.UI.Agent.GrandCompany)currentItem2;
Save();
}
int currentItem3 = Array.IndexOf(_classJobIds, base.Configuration.General.CombatJob);
if (currentItem3 == -1)
int num3 = Array.IndexOf(_classJobIds, base.Configuration.General.CombatJob);
if (num3 == -1)
{
base.Configuration.General.CombatJob = EClassJob.Adventurer;
Save();
currentItem3 = 0;
num3 = 0;
}
if (ImGui.Combo((ImU8String)"Preferred Combat Job", ref currentItem3, (ReadOnlySpan<string>)_classJobNames, _classJobNames.Length))
string text2 = ((num3 >= 0 && num3 < _classJobNames.Length) ? _classJobNames[num3] : "Unknown");
if (ImGui.BeginCombo("Preferred Combat Job", text2, ImGuiComboFlags.HeightLarge))
{
base.Configuration.General.CombatJob = _classJobIds[currentItem3];
Save();
if (!_classJobComboJustOpened)
{
ImGui.SetKeyboardFocusHere();
_classJobComboJustOpened = true;
}
ImGui.SetNextItemWidth(-1f);
ImGui.InputTextWithHint("##CombatJobSearch", "Search combat jobs...", ref _classJobSearchText, 256);
ImGui.Separator();
using (ImRaii.IEndObject endObject3 = ImRaii.Child("##CombatJobScrollArea", new Vector2(0f, 300f), border: false))
{
if (endObject3)
{
string value2 = _classJobSearchText.ToUpperInvariant();
for (int num4 = 0; num4 < _classJobNames.Length; num4++)
{
if (string.IsNullOrEmpty(_classJobSearchText) || _classJobNames[num4].ToUpperInvariant().Contains(value2, StringComparison.Ordinal))
{
bool flag2 = num4 == num3;
if (ImGui.Selectable(_classJobNames[num4], flag2))
{
base.Configuration.General.CombatJob = _classJobIds[num4];
Save();
_classJobSearchText = string.Empty;
ImGui.CloseCurrentPopup();
}
if (flag2)
{
ImGui.SetItemDefaultFocus();
}
}
}
}
}
ImGui.EndCombo();
}
else
{
_classJobComboJustOpened = false;
}
ImGui.Separator();
ImGui.Text("UI");
@ -212,16 +253,38 @@ internal sealed class GeneralConfigComponent : ConfigComponent
ImGui.Text("Questing");
using (ImRaii.PushIndent())
{
bool v7 = base.Configuration.General.ConfigureTextAdvance;
if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v7))
bool v7 = base.Configuration.General.CinemaMode;
if (ImGui.Checkbox("Cinema Mode (watch cutscenes)", ref v7))
{
base.Configuration.General.ConfigureTextAdvance = v7;
base.Configuration.General.CinemaMode = v7;
Save();
}
bool v8 = base.Configuration.General.SkipLowPriorityDuties;
if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v8))
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
{
base.Configuration.General.SkipLowPriorityDuties = v8;
ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString());
}
if (ImGui.IsItemHovered())
{
using (ImRaii.Tooltip())
{
ImGui.Text("When enabled, cutscenes will NOT be automatically skipped.");
ImGui.Text("This allows you to experience the story while Questionable");
ImGui.Text("handles navigation, combat, and other gameplay automation.");
ImGui.Spacing();
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.7f, 1f), "Recommended for first-time story playthroughs.");
}
}
bool v8 = base.Configuration.General.ConfigureTextAdvance;
if (ImGui.Checkbox("Automatically configure TextAdvance with the recommended settings", ref v8))
{
base.Configuration.General.ConfigureTextAdvance = v8;
Save();
}
bool v9 = base.Configuration.General.SkipLowPriorityDuties;
if (ImGui.Checkbox("Unlock certain optional dungeons and raids (instead of waiting for completion)", ref v9))
{
base.Configuration.General.SkipLowPriorityDuties = v9;
Save();
}
ImGui.SameLine();
@ -241,18 +304,18 @@ internal sealed class GeneralConfigComponent : ConfigComponent
{
if (_territoryData.TryGetContentFinderCondition(lowPriorityContentFinderConditionQuest.ContentFinderConditionId, out TerritoryData.ContentFinderConditionData contentFinderConditionData))
{
ImU8String text2 = new ImU8String(0, 1);
text2.AppendFormatted(contentFinderConditionData.Name);
ImGui.BulletText(text2);
ImU8String text3 = new ImU8String(0, 1);
text3.AppendFormatted(contentFinderConditionData.Name);
ImGui.BulletText(text3);
}
}
}
}
ImGui.Spacing();
bool v9 = base.Configuration.General.AutoStepRefreshEnabled;
if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v9))
bool v10 = base.Configuration.General.AutoStepRefreshEnabled;
if (ImGui.Checkbox("Automatically refresh quest steps when stuck", ref v10))
{
base.Configuration.General.AutoStepRefreshEnabled = v9;
base.Configuration.General.AutoStepRefreshEnabled = v10;
Save();
}
ImGui.SameLine();
@ -268,37 +331,37 @@ internal sealed class GeneralConfigComponent : ConfigComponent
ImGui.Text("This helps resume automated quest completion when interruptions occur.");
}
}
using (ImRaii.Disabled(!v9))
using (ImRaii.Disabled(!v10))
{
ImGui.Indent();
int v10 = base.Configuration.General.AutoStepRefreshDelaySeconds;
int v11 = base.Configuration.General.AutoStepRefreshDelaySeconds;
ImGui.SetNextItemWidth(150f);
if (ImGui.SliderInt("Refresh delay (seconds)", ref v10, 10, 180))
if (ImGui.SliderInt("Refresh delay (seconds)", ref v11, 10, 180))
{
base.Configuration.General.AutoStepRefreshDelaySeconds = v10;
base.Configuration.General.AutoStepRefreshDelaySeconds = v11;
Save();
}
Vector4 col = new Vector4(0.7f, 0.7f, 0.7f, 1f);
ImU8String text3 = new ImU8String(77, 1);
text3.AppendLiteral("Quest steps will refresh automatically after ");
text3.AppendFormatted(v10);
text3.AppendLiteral(" seconds if no progress is made.");
ImGui.TextColored(in col, text3);
ImU8String text4 = new ImU8String(77, 1);
text4.AppendLiteral("Quest steps will refresh automatically after ");
text4.AppendFormatted(v11);
text4.AppendLiteral(" seconds if no progress is made.");
ImGui.TextColored(in col, text4);
ImGui.Unindent();
}
ImGui.Spacing();
ImGui.Separator();
ImGui.Text("Priority Quest Management");
bool v11 = base.Configuration.General.ClearPriorityQuestsOnLogout;
if (ImGui.Checkbox("Clear priority quests on character logout", ref v11))
bool v12 = base.Configuration.General.ClearPriorityQuestsOnLogout;
if (ImGui.Checkbox("Clear priority quests on character logout", ref v12))
{
base.Configuration.General.ClearPriorityQuestsOnLogout = v11;
base.Configuration.General.ClearPriorityQuestsOnLogout = v12;
Save();
}
bool v12 = base.Configuration.General.ClearPriorityQuestsOnCompletion;
if (ImGui.Checkbox("Remove priority quests when completed", ref v12))
bool v13 = base.Configuration.General.ClearPriorityQuestsOnCompletion;
if (ImGui.Checkbox("Remove priority quests when completed", ref v13))
{
base.Configuration.General.ClearPriorityQuestsOnCompletion = v12;
base.Configuration.General.ClearPriorityQuestsOnCompletion = v13;
Save();
}
ImGui.SameLine();

View file

@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Text;
using Dalamud.Interface.Utility.Raii;
@ -10,9 +11,21 @@ namespace Questionable.Windows.ConfigComponents;
internal sealed class NotificationConfigComponent : ConfigComponent
{
private readonly XivChatType[] _xivChatTypes;
private readonly string[] _chatTypeNames;
private string _chatChannelSearchText = string.Empty;
private bool _chatChannelComboJustOpened;
public NotificationConfigComponent(IDalamudPluginInterface pluginInterface, Configuration configuration)
: base(pluginInterface, configuration)
{
_xivChatTypes = (from x in Enum.GetValues<XivChatType>()
where x != XivChatType.StandardEmote
select x).ToArray();
_chatTypeNames = _xivChatTypes.Select((XivChatType t) => t.GetAttribute<XivChatTypeInfoAttribute>()?.FancyName ?? t.ToString()).ToArray();
}
public override void DrawTab()
@ -32,15 +45,52 @@ internal sealed class NotificationConfigComponent : ConfigComponent
{
using (ImRaii.PushIndent())
{
XivChatType[] array = (from x in Enum.GetValues<XivChatType>()
where x != XivChatType.StandardEmote
select x).ToArray();
int currentItem = Array.IndexOf(array, base.Configuration.Notifications.ChatType);
string[] array2 = array.Select((XivChatType t) => t.GetAttribute<XivChatTypeInfoAttribute>()?.FancyName ?? t.ToString()).ToArray();
if (ImGui.Combo((ImU8String)"Chat channel", ref currentItem, (ReadOnlySpan<string>)array2, array2.Length))
int num = Array.IndexOf(_xivChatTypes, base.Configuration.Notifications.ChatType);
if (num == -1)
{
base.Configuration.Notifications.ChatType = array[currentItem];
Save();
num = 0;
}
string text = ((num >= 0 && num < _chatTypeNames.Length) ? _chatTypeNames[num] : "Unknown");
if (ImGui.BeginCombo("Chat channel", text, ImGuiComboFlags.HeightLarge))
{
if (!_chatChannelComboJustOpened)
{
ImGui.SetKeyboardFocusHere();
_chatChannelComboJustOpened = true;
}
ImGui.SetNextItemWidth(-1f);
ImGui.InputTextWithHint("##ChatChannelSearch", "Search chat channels...", ref _chatChannelSearchText, 256);
ImGui.Separator();
using (ImRaii.IEndObject endObject2 = ImRaii.Child("##ChatChannelScrollArea", new Vector2(0f, 300f), border: false))
{
if (endObject2)
{
string value = _chatChannelSearchText.ToUpperInvariant();
for (int i = 0; i < _chatTypeNames.Length; i++)
{
if (string.IsNullOrEmpty(_chatChannelSearchText) || _chatTypeNames[i].ToUpperInvariant().Contains(value, StringComparison.Ordinal))
{
bool flag = i == num;
if (ImGui.Selectable(_chatTypeNames[i], flag))
{
base.Configuration.Notifications.ChatType = _xivChatTypes[i];
Save();
_chatChannelSearchText = string.Empty;
ImGui.CloseCurrentPopup();
}
if (flag)
{
ImGui.SetItemDefaultFocus();
}
}
}
}
}
ImGui.EndCombo();
}
else
{
_chatChannelComboJustOpened = false;
}
}
}

View file

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using Questionable.Controller;
using Questionable.Model;
using Questionable.Model.Questing;
@ -12,6 +14,8 @@ internal sealed class QuestSelector(QuestRegistry questRegistry)
{
private string _searchString = string.Empty;
private bool _comboJustOpened;
public Predicate<Quest>? SuggestionPredicate { private get; set; }
public Predicate<Quest>? DefaultPredicate { private get; set; }
@ -27,12 +31,18 @@ internal sealed class QuestSelector(QuestRegistry questRegistry)
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
if (ImGui.BeginCombo("##QuestSelection", "Add Quest...", ImGuiComboFlags.HeightLarge))
{
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
bool flag = ImGui.InputTextWithHint("", "Filter...", ref _searchString, 256, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue);
if (!_comboJustOpened)
{
ImGui.SetKeyboardFocusHere();
_comboJustOpened = true;
}
ImGui.SetNextItemWidth(-1f);
bool flag = ImGui.InputTextWithHint("##QuestSearch", "Search quests...", ref _searchString, 256, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.EnterReturnsTrue);
ImGui.Separator();
IEnumerable<Quest> enumerable;
if (!string.IsNullOrEmpty(_searchString))
{
enumerable = Enumerable.Where(predicate: (!ElementId.TryFromString(_searchString, out ElementId elementId)) ? new Func<Quest, bool>(DefaultPredicate) : ((Func<Quest, bool>)((Quest x) => DefaultPredicate(x) || x.Id == elementId)), source: questRegistry.AllQuests.Where(delegate(Quest x)
enumerable = Enumerable.Where(predicate: (!ElementId.TryFromString(_searchString, out ElementId elementId)) ? new Func<Quest, bool>(SearchPredicate) : ((Func<Quest, bool>)((Quest x) => SearchPredicate(x) || x.Id == elementId)), source: questRegistry.AllQuests.Where(delegate(Quest x)
{
ElementId id = x.Id;
return !(id is SatisfactionSupplyNpcId) && !(id is AlliedSocietyDailyId);
@ -40,24 +50,35 @@ internal sealed class QuestSelector(QuestRegistry questRegistry)
}
else
{
enumerable = questRegistry.AllQuests.Where((Quest x) => this.DefaultPredicate?.Invoke(x) ?? true);
enumerable = questRegistry.AllQuests.Where((Quest x) => DefaultPredicate?.Invoke(x) ?? true);
}
foreach (Quest item in enumerable)
using (ImRaii.IEndObject endObject = ImRaii.Child("##QuestScrollArea", new Vector2(0f, 300f), border: false))
{
if ((SuggestionPredicate == null || SuggestionPredicate(item)) && (ImGui.Selectable(item.Info.Name) || flag))
if (endObject)
{
QuestSelected(item);
if (flag)
foreach (Quest item in enumerable)
{
ImGui.CloseCurrentPopup();
flag = false;
if ((SuggestionPredicate == null || SuggestionPredicate(item)) && (ImGui.Selectable(item.Info.Name) || flag))
{
QuestSelected(item);
_searchString = string.Empty;
if (flag)
{
ImGui.CloseCurrentPopup();
flag = false;
}
}
}
}
}
ImGui.EndCombo();
}
else
{
_comboJustOpened = false;
}
ImGui.Spacing();
bool DefaultPredicate(Quest x)
bool SearchPredicate(Quest x)
{
return x.Info.Name.Contains(_searchString, StringComparison.CurrentCultureIgnoreCase);
}

View file

@ -32,6 +32,8 @@ internal sealed class Configuration : IPluginConfiguration
public bool ConfigureTextAdvance { get; set; } = true;
public bool CinemaMode { get; set; }
public bool AutoStepRefreshEnabled { get; set; } = true;
public int AutoStepRefreshDelaySeconds { get; set; } = 60;
@ -110,6 +112,8 @@ internal sealed class Configuration : IPluginConfiguration
public bool AdditionalStatusInformation { get; set; }
public bool DisablePartyWatchdog { get; set; }
public bool DisableAutoDutyBareMode { get; set; }
public bool SkipAetherCurrents { get; set; }

View file

@ -33,11 +33,11 @@ internal sealed class DalamudInitializer : IDisposable
private readonly Configuration _configuration;
private readonly PartyWatchDog _partyWatchDog;
private readonly PartyWatchdog _partyWatchdog;
private readonly ILogger<DalamudInitializer> _logger;
public DalamudInitializer(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, MovementController movementController, WindowSystem windowSystem, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, DebugOverlay debugOverlay, ConfigWindow configWindow, QuestSelectionWindow questSelectionWindow, QuestSequenceWindow questSequenceWindow, QuestValidationWindow questValidationWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, ChangelogWindow changelogWindow, IToastGui toastGui, Configuration configuration, PartyWatchDog partyWatchDog, ILogger<DalamudInitializer> logger)
public DalamudInitializer(IDalamudPluginInterface pluginInterface, IFramework framework, QuestController questController, MovementController movementController, WindowSystem windowSystem, OneTimeSetupWindow oneTimeSetupWindow, QuestWindow questWindow, DebugOverlay debugOverlay, ConfigWindow configWindow, QuestSelectionWindow questSelectionWindow, QuestSequenceWindow questSequenceWindow, QuestValidationWindow questValidationWindow, JournalProgressWindow journalProgressWindow, PriorityWindow priorityWindow, ChangelogWindow changelogWindow, IToastGui toastGui, Configuration configuration, PartyWatchdog partyWatchdog, ILogger<DalamudInitializer> logger)
{
_pluginInterface = pluginInterface;
_framework = framework;
@ -49,7 +49,7 @@ internal sealed class DalamudInitializer : IDisposable
_configWindow = configWindow;
_toastGui = toastGui;
_configuration = configuration;
_partyWatchDog = partyWatchDog;
_partyWatchdog = partyWatchdog;
_logger = logger;
_windowSystem.AddWindow(oneTimeSetupWindow);
_windowSystem.AddWindow(questWindow);
@ -72,7 +72,7 @@ internal sealed class DalamudInitializer : IDisposable
private void FrameworkUpdate(IFramework framework)
{
_partyWatchDog.Update();
_partyWatchdog.Update();
_questController.Update();
try
{

View file

@ -213,7 +213,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin, IDisposable
serviceCollection.AddSingleton<ContextMenuController>();
serviceCollection.AddSingleton<ShopController>();
serviceCollection.AddSingleton<InterruptHandler>();
serviceCollection.AddSingleton<PartyWatchDog>();
serviceCollection.AddSingleton<PartyWatchdog>();
serviceCollection.AddSingleton<CraftworksSupplyController>();
serviceCollection.AddSingleton<CreditsController>();
serviceCollection.AddSingleton<HelpUiController>();