muffin v7.4.15

This commit is contained in:
alydev 2026-03-01 00:29:23 +10:00
parent 9bf3dbdf69
commit 0b1b2d38c7
14 changed files with 1215 additions and 1057 deletions

View file

@ -52,6 +52,11 @@
}, },
"minItems": 1 "minItems": 1
}, },
"RequiredQuestId": {
"description": "Quest ID that must be completed before this FATE can be farmed",
"type": "integer",
"minimum": 1
},
"RequiredStatusId": { "RequiredStatusId": {
"description": "Status effect required to participate in the FATE", "description": "Status effect required to participate in the FATE",
"type": "string" "type": "string"
@ -65,6 +70,10 @@
"description": "Position of the transform NPC", "description": "Position of the transform NPC",
"$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json" "$ref": "https://github.com/WigglyMuffin/Questionable/raw/refs/heads/main/Questionable.Model/common-vector3.json"
}, },
"StopAction": {
"description": "Action to use when stopping FATE farming (e.g. to remove a transformation debuff). Only used if the player has RequiredStatusId.",
"type": "string"
},
"EventExpiry": { "EventExpiry": {
"description": "If this is a seasonal/event FATE, the date and time (in UTC) when it is no longer available. Date-only values are treated as ending at daily reset (14:59:59 UTC).", "description": "If this is a seasonal/event FATE, the date and time (in UTC) when it is no longer available. Date-only values are treated as ending at daily reset (14:59:59 UTC).",
"format": "date-time", "format": "date-time",

View file

@ -478824,10 +478824,7 @@ public static class AssemblyQuestLoader
reference212 = obj228; reference212 = obj228;
index2++; index2++;
ref QuestStep reference213 = ref span297[index2]; ref QuestStep reference213 = ref span297[index2];
QuestStep obj229 = new QuestStep(EInteractionType.FateAction, null, new Vector3(-38.322124f, 3.9999998f, -144.23155f), 130) QuestStep questStep20 = new QuestStep(EInteractionType.FateAction, null, new Vector3(-38.322124f, 3.9999998f, -144.23155f), 130);
{
Comment = "Use correct Cheer Rhythm color on each randomly-appearing performer NPC"
};
index3 = 4; index3 = 4;
List<FateActionTarget> list299 = new List<FateActionTarget>(index3); List<FateActionTarget> list299 = new List<FateActionTarget>(index3);
CollectionsMarshal.SetCount(list299, index3); CollectionsMarshal.SetCount(list299, index3);
@ -478856,13 +478853,13 @@ public static class AssemblyQuestLoader
DataId = 18859u, DataId = 18859u,
Action = EAction.CheerRhythmRed Action = EAction.CheerRhythmRed
}; };
obj229.FateActionTargets = list299; questStep20.FateActionTargets = list299;
reference213 = obj229; reference213 = questStep20;
obj227.Steps = list297; obj227.Steps = list297;
reference211 = obj227; reference211 = obj227;
num++; num++;
ref QuestSequence reference214 = ref span287[num]; ref QuestSequence reference214 = ref span287[num];
QuestSequence obj230 = new QuestSequence QuestSequence obj229 = new QuestSequence
{ {
Sequence = byte.MaxValue Sequence = byte.MaxValue
}; };
@ -478875,8 +478872,8 @@ public static class AssemblyQuestLoader
{ {
NextQuestId = new QuestId(5445) NextQuestId = new QuestId(5445)
}; };
obj230.Steps = list300; obj229.Steps = list300;
reference214 = obj230; reference214 = obj229;
questRoot25.QuestSequence = list287; questRoot25.QuestSequence = list287;
AddQuest(questId25, questRoot25); AddQuest(questId25, questRoot25);
QuestId questId26 = new QuestId(5445); QuestId questId26 = new QuestId(5445);
@ -478896,7 +478893,7 @@ public static class AssemblyQuestLoader
Span<QuestSequence> span302 = CollectionsMarshal.AsSpan(list302); Span<QuestSequence> span302 = CollectionsMarshal.AsSpan(list302);
num = 0; num = 0;
ref QuestSequence reference215 = ref span302[num]; ref QuestSequence reference215 = ref span302[num];
QuestSequence obj231 = new QuestSequence QuestSequence obj230 = new QuestSequence
{ {
Sequence = 0 Sequence = 0
}; };
@ -478916,11 +478913,11 @@ public static class AssemblyQuestLoader
} }
} }
}; };
obj231.Steps = list303; obj230.Steps = list303;
reference215 = obj231; reference215 = obj230;
num++; num++;
ref QuestSequence reference216 = ref span302[num]; ref QuestSequence reference216 = ref span302[num];
QuestSequence obj232 = new QuestSequence QuestSequence obj231 = new QuestSequence
{ {
Sequence = 1 Sequence = 1
}; };
@ -478930,11 +478927,11 @@ public static class AssemblyQuestLoader
Span<QuestStep> span304 = CollectionsMarshal.AsSpan(list304); Span<QuestStep> span304 = CollectionsMarshal.AsSpan(list304);
num2 = 0; num2 = 0;
span304[num2] = new QuestStep(EInteractionType.Interact, 1056476u, new Vector3(-35.1416f, 5.000001f, -130.38837f), 130); span304[num2] = new QuestStep(EInteractionType.Interact, 1056476u, new Vector3(-35.1416f, 5.000001f, -130.38837f), 130);
obj232.Steps = list304; obj231.Steps = list304;
reference216 = obj232; reference216 = obj231;
num++; num++;
ref QuestSequence reference217 = ref span302[num]; ref QuestSequence reference217 = ref span302[num];
QuestSequence obj233 = new QuestSequence QuestSequence obj232 = new QuestSequence
{ {
Sequence = byte.MaxValue Sequence = byte.MaxValue
}; };
@ -478944,8 +478941,8 @@ public static class AssemblyQuestLoader
Span<QuestStep> span305 = CollectionsMarshal.AsSpan(list305); Span<QuestStep> span305 = CollectionsMarshal.AsSpan(list305);
index2 = 0; index2 = 0;
span305[index2] = new QuestStep(EInteractionType.CompleteQuest, 1056476u, new Vector3(-35.1416f, 5.000001f, -130.38837f), 130); span305[index2] = new QuestStep(EInteractionType.CompleteQuest, 1056476u, new Vector3(-35.1416f, 5.000001f, -130.38837f), 130);
obj233.Steps = list305; obj232.Steps = list305;
reference217 = obj233; reference217 = obj232;
questRoot26.QuestSequence = list302; questRoot26.QuestSequence = list302;
AddQuest(questId26, questRoot26); AddQuest(questId26, questRoot26);
} }

View file

@ -218,6 +218,10 @@ public sealed class ActionConverter : EnumConverter<EAction>
{ {
EAction.CheerRhythmYellow, EAction.CheerRhythmYellow,
"Cheer Rhythm: Yellow" "Cheer Rhythm: Yellow"
},
{
EAction.CurtainCall,
"Curtain Call"
} }
}; };

View file

@ -66,6 +66,7 @@ public enum EAction
CheerRhythmBlue = 44502, CheerRhythmBlue = 44502,
CheerRhythmGreen = 44503, CheerRhythmGreen = 44503,
CheerRhythmYellow = 44504, CheerRhythmYellow = 44504,
CurtainCall = 11063,
Prospect = 227, Prospect = 227,
CollectMiner = 240, CollectMiner = 240,
LuckOfTheMountaineer = 4081, LuckOfTheMountaineer = 4081,

View file

@ -31,5 +31,9 @@ public sealed class FateDefinition
public List<DialogueChoice>? TransformDialogueChoices { get; set; } public List<DialogueChoice>? TransformDialogueChoices { get; set; }
public ushort? RequiredQuestId { get; set; }
public EAction? StopAction { get; set; }
public DateTime? EventExpiry { get; set; } public DateTime? EventExpiry { get; set; }
} }

View file

@ -63,7 +63,7 @@ internal static class FateAction
} }
foreach (FateActionTarget target in base.Task.Targets) foreach (FateActionTarget target in base.Task.Targets)
{ {
IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId); IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId, null, warnIfMissing: false);
if (gameObject != null && gameObject.IsTargetable) if (gameObject != null && gameObject.IsTargetable)
{ {
bool flag = gameFunctions.UseAction(gameObject, target.Action); bool flag = gameFunctions.UseAction(gameObject, target.Action);

View file

@ -21,9 +21,12 @@ internal static class FateFarming
{ {
private DateTime _nextPollAt = DateTime.MinValue; private DateTime _nextPollAt = DateTime.MinValue;
private bool _loggedWaitingForFate;
protected override bool Start() protected override bool Start()
{ {
logger.LogInformation("Waiting for FATE targets to appear ({Count} targets)", base.Task.Targets.Count); logger.LogInformation("Waiting for FATE targets to appear ({Count} targets)", base.Task.Targets.Count);
_loggedWaitingForFate = false;
return true; return true;
} }
@ -33,12 +36,24 @@ internal static class FateFarming
{ {
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
} }
ushort currentFateId = gameFunctions.GetCurrentFateId();
if (currentFateId == 0)
{
if (!_loggedWaitingForFate)
{
logger.LogInformation("No active FATE yet, waiting for FATE to start before checking targets");
_loggedWaitingForFate = true;
}
_nextPollAt = DateTime.Now.AddSeconds(1.0);
return ETaskResult.StillRunning;
}
_loggedWaitingForFate = false;
foreach (FateActionTarget target in base.Task.Targets) foreach (FateActionTarget target in base.Task.Targets)
{ {
IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId); IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId, null, warnIfMissing: false);
if (gameObject != null && gameObject.IsTargetable) if (gameObject != null && gameObject.IsTargetable)
{ {
logger.LogInformation("FATE target {DataId} is now targetable", target.DataId); logger.LogInformation("FATE {FateId} active and target {DataId} is targetable", currentFateId, target.DataId);
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
} }
@ -86,7 +101,7 @@ internal static class FateFarming
} }
} }
internal sealed record FateActionLoop(IReadOnlyList<FateActionTarget> Targets) : ITask internal sealed record FateActionLoop(IReadOnlyList<FateActionTarget> Targets, EStatus? RequiredStatusId = null) : ITask
{ {
public bool ShouldRedoOnInterrupt() public bool ShouldRedoOnInterrupt()
{ {
@ -103,12 +118,12 @@ internal static class FateFarming
{ {
private DateTime _nextActionAt = DateTime.MinValue; private DateTime _nextActionAt = DateTime.MinValue;
private bool _fateWasActive; private ushort _trackedFateId;
protected override bool Start() protected override bool Start()
{ {
logger.LogInformation("Starting FATE action loop with {Count} targets", base.Task.Targets.Count); _trackedFateId = gameFunctions.GetCurrentFateId();
_fateWasActive = gameFunctions.GetCurrentFateId() != 0; logger.LogInformation("Starting FATE action loop with {Count} targets, tracking FATE {FateId}", base.Task.Targets.Count, _trackedFateId);
return true; return true;
} }
@ -118,33 +133,31 @@ internal static class FateFarming
{ {
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
} }
bool flag = gameFunctions.GetCurrentFateId() != 0; if (base.Task.RequiredStatusId.HasValue && !gameFunctions.HasStatus(base.Task.RequiredStatusId.Value))
if (_fateWasActive && !flag)
{ {
bool flag2 = false; logger.LogInformation("Required status {StatusId} lost during FATE action loop, ending cycle to re-apply", base.Task.RequiredStatusId.Value);
foreach (FateActionTarget target in base.Task.Targets)
{
IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId);
if (gameObject != null && gameObject.IsTargetable)
{
flag2 = true;
break;
}
}
if (!flag2)
{
logger.LogInformation("FATE completed (was active, now inactive, no targetable NPCs)");
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;
} }
if (_trackedFateId == 0)
{
_trackedFateId = gameFunctions.GetCurrentFateId();
if (_trackedFateId != 0)
{
logger.LogInformation("Now tracking FATE {FateId}", _trackedFateId);
} }
_fateWasActive = flag; }
foreach (FateActionTarget target2 in base.Task.Targets) if (_trackedFateId != 0 && !gameFunctions.IsFateStillActive(_trackedFateId))
{ {
IGameObject gameObject2 = gameFunctions.FindObjectByDataId(target2.DataId); logger.LogInformation("FATE {FateId} is no longer running, cycle complete", _trackedFateId);
if (gameObject2 != null && gameObject2.IsTargetable) return ETaskResult.TaskComplete;
}
foreach (FateActionTarget target in base.Task.Targets)
{ {
bool flag3 = gameFunctions.UseAction(gameObject2, target2.Action); IGameObject gameObject = gameFunctions.FindObjectByDataId(target.DataId, null, warnIfMissing: false);
_nextActionAt = (flag3 ? DateTime.Now.AddSeconds(2.5) : DateTime.Now.AddSeconds(0.5)); if (gameObject != null && gameObject.IsTargetable)
{
bool flag = gameFunctions.UseAction(gameObject, target.Action);
_nextActionAt = (flag ? DateTime.Now.AddSeconds(2.5) : DateTime.Now.AddSeconds(0.5));
return ETaskResult.StillRunning; return ETaskResult.StillRunning;
} }
} }

View file

@ -107,13 +107,13 @@ internal static class Interact
public SkipStepConditions? SkipConditions { get; init; } public SkipStepConditions? SkipConditions { get; init; }
public EStatus? RequiredStatusId { get; init; } public EStatus? CompletionStatusId { get; init; }
public List<QuestWorkValue?> CompletionQuestVariablesFlags { get; } public List<QuestWorkValue?> CompletionQuestVariablesFlags { get; }
public bool HasCompletionQuestVariablesFlags { get; } public bool HasCompletionQuestVariablesFlags { get; }
public Task(uint DataId, Quest? Quest, EInteractionType InteractionType, bool SkipMarkerCheck = false, uint? PickUpItemId = null, byte? TaxiStandId = null, SkipStepConditions? SkipConditions = null, List<QuestWorkValue?>? CompletionQuestVariablesFlags = null, EStatus? RequiredStatusId = null) public Task(uint DataId, Quest? Quest, EInteractionType InteractionType, bool SkipMarkerCheck = false, uint? PickUpItemId = null, byte? TaxiStandId = null, SkipStepConditions? SkipConditions = null, List<QuestWorkValue?>? CompletionQuestVariablesFlags = null, EStatus? CompletionStatusId = null)
{ {
this.DataId = DataId; this.DataId = DataId;
this.Quest = Quest; this.Quest = Quest;
@ -122,7 +122,7 @@ internal static class Interact
this.PickUpItemId = PickUpItemId; this.PickUpItemId = PickUpItemId;
this.TaxiStandId = TaxiStandId; this.TaxiStandId = TaxiStandId;
this.SkipConditions = SkipConditions; this.SkipConditions = SkipConditions;
this.RequiredStatusId = RequiredStatusId; this.CompletionStatusId = CompletionStatusId;
this.CompletionQuestVariablesFlags = CompletionQuestVariablesFlags ?? new List<QuestWorkValue>(); this.CompletionQuestVariablesFlags = CompletionQuestVariablesFlags ?? new List<QuestWorkValue>();
HasCompletionQuestVariablesFlags = Quest != null && CompletionQuestVariablesFlags != null && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags); HasCompletionQuestVariablesFlags = Quest != null && CompletionQuestVariablesFlags != null && QuestWorkUtils.HasCompletionFlags(CompletionQuestVariablesFlags);
base._002Ector(); base._002Ector();
@ -139,7 +139,7 @@ internal static class Interact
} }
[CompilerGenerated] [CompilerGenerated]
public void Deconstruct(out uint DataId, out Quest? Quest, out EInteractionType InteractionType, out bool SkipMarkerCheck, out uint? PickUpItemId, out byte? TaxiStandId, out SkipStepConditions? SkipConditions, out List<QuestWorkValue?>? CompletionQuestVariablesFlags, out EStatus? RequiredStatusId) public void Deconstruct(out uint DataId, out Quest? Quest, out EInteractionType InteractionType, out bool SkipMarkerCheck, out uint? PickUpItemId, out byte? TaxiStandId, out SkipStepConditions? SkipConditions, out List<QuestWorkValue?>? CompletionQuestVariablesFlags, out EStatus? CompletionStatusId)
{ {
DataId = this.DataId; DataId = this.DataId;
Quest = this.Quest; Quest = this.Quest;
@ -149,7 +149,7 @@ internal static class Interact
TaxiStandId = this.TaxiStandId; TaxiStandId = this.TaxiStandId;
SkipConditions = this.SkipConditions; SkipConditions = this.SkipConditions;
CompletionQuestVariablesFlags = this.CompletionQuestVariablesFlags; CompletionQuestVariablesFlags = this.CompletionQuestVariablesFlags;
RequiredStatusId = this.RequiredStatusId; CompletionStatusId = this.CompletionStatusId;
} }
} }
@ -228,10 +228,10 @@ internal static class Interact
} }
_needsUnmount = false; _needsUnmount = false;
} }
EStatus? requiredStatusId = base.Task.RequiredStatusId; EStatus? completionStatusId = base.Task.CompletionStatusId;
if (requiredStatusId.HasValue) if (completionStatusId.HasValue)
{ {
EStatus valueOrDefault = requiredStatusId.GetValueOrDefault(); EStatus valueOrDefault = completionStatusId.GetValueOrDefault();
if (gameFunctions.HasStatus(valueOrDefault)) if (gameFunctions.HasStatus(valueOrDefault))
{ {
return ETaskResult.TaskComplete; return ETaskResult.TaskComplete;

View file

@ -102,16 +102,12 @@ internal sealed class FateController : MiniTaskController<FateController>
} }
if (_currentFate.RequiredStatusId.HasValue && _currentFate.TransformNpcDataId.HasValue && _currentFate.TransformNpcPosition.HasValue && !_gameFunctions.HasStatus(_currentFate.RequiredStatusId.Value)) if (_currentFate.RequiredStatusId.HasValue && _currentFate.TransformNpcDataId.HasValue && _currentFate.TransformNpcPosition.HasValue && !_gameFunctions.HasStatus(_currentFate.RequiredStatusId.Value))
{ {
ILogger<FateController> logger = _logger; _logger.LogInformation("Player missing required status {StatusId}, enqueuing transform", _currentFate.RequiredStatusId.Value);
object[] array = new object[1];
EStatus? requiredStatusId = _currentFate.RequiredStatusId;
array[0] = requiredStatusId.Value;
logger.LogInformation("Player missing required status {StatusId}, enqueuing transform", array);
_taskQueue.Enqueue(new MoveTask(_currentFate.TerritoryId, _currentFate.TransformNpcPosition.Value, null, 3f)); _taskQueue.Enqueue(new MoveTask(_currentFate.TerritoryId, _currentFate.TransformNpcPosition.Value, null, 3f));
_taskQueue.Enqueue(new Mount.UnmountTask()); _taskQueue.Enqueue(new Mount.UnmountTask());
TaskQueue taskQueue = _taskQueue; TaskQueue taskQueue = _taskQueue;
uint value = _currentFate.TransformNpcDataId.Value; uint value = _currentFate.TransformNpcDataId.Value;
requiredStatusId = _currentFate.RequiredStatusId.Value; EStatus? requiredStatusId = _currentFate.RequiredStatusId;
taskQueue.Enqueue(new Interact.Task(value, null, EInteractionType.Interact, SkipMarkerCheck: true, null, null, null, null, requiredStatusId)); taskQueue.Enqueue(new Interact.Task(value, null, EInteractionType.Interact, SkipMarkerCheck: true, null, null, null, null, requiredStatusId));
_taskQueue.Enqueue(new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(2L))); _taskQueue.Enqueue(new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(2L)));
} }
@ -119,7 +115,7 @@ internal sealed class FateController : MiniTaskController<FateController>
_taskQueue.Enqueue(new Mount.UnmountTask()); _taskQueue.Enqueue(new Mount.UnmountTask());
_taskQueue.Enqueue(new FateFarming.WaitForFateTargets(_currentFate.Targets)); _taskQueue.Enqueue(new FateFarming.WaitForFateTargets(_currentFate.Targets));
_taskQueue.Enqueue(new FateFarming.SyncFateLevel()); _taskQueue.Enqueue(new FateFarming.SyncFateLevel());
_taskQueue.Enqueue(new FateFarming.FateActionLoop(_currentFate.Targets)); _taskQueue.Enqueue(new FateFarming.FateActionLoop(_currentFate.Targets, _currentFate.RequiredStatusId));
_taskQueue.Enqueue(new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(3L))); _taskQueue.Enqueue(new WaitAtEnd.WaitDelay(TimeSpan.FromSeconds(3L)));
} }
} }
@ -129,6 +125,11 @@ internal sealed class FateController : MiniTaskController<FateController>
if (_currentFate != null) if (_currentFate != null)
{ {
_logger.LogInformation("Stopping FATE farming: {Label} (completed {Cycles} cycles)", label, _completedCycles); _logger.LogInformation("Stopping FATE farming: {Label} (completed {Cycles} cycles)", label, _completedCycles);
if (_currentFate.StopAction.HasValue && _currentFate.RequiredStatusId.HasValue && _gameFunctions.HasStatus(_currentFate.RequiredStatusId.Value))
{
_logger.LogInformation("Using stop action {Action} to remove transformation", _currentFate.StopAction.Value);
_gameFunctions.UseAction(_currentFate.StopAction.Value);
}
_currentFate = null; _currentFate = null;
_completedCycles = 0; _completedCycles = 0;
_cycleLimit = null; _cycleLimit = null;

File diff suppressed because it is too large Load diff

View file

@ -121,7 +121,7 @@ internal sealed class GameFunctions
return false; return false;
} }
public IGameObject? FindObjectByDataId(uint dataId, Dalamud.Game.ClientState.Objects.Enums.ObjectKind? kind = null) public IGameObject? FindObjectByDataId(uint dataId, Dalamud.Game.ClientState.Objects.Enums.ObjectKind? kind = null, bool warnIfMissing = true)
{ {
foreach (IGameObject item in _objectTable) foreach (IGameObject item in _objectTable)
{ {
@ -132,7 +132,10 @@ internal sealed class GameFunctions
return item; return item;
} }
} }
if (warnIfMissing)
{
_logger.LogWarning("Could not find GameObject with dataId {DataId}", dataId); _logger.LogWarning("Could not find GameObject with dataId {DataId}", dataId);
}
return null; return null;
} }
@ -581,6 +584,25 @@ internal sealed class GameFunctions
return ptr->CurrentFate->FateId; return ptr->CurrentFate->FateId;
} }
public unsafe bool IsFateStillActive(ushort fateId)
{
if (fateId == 0)
{
return false;
}
FateManager* ptr = FateManager.Instance();
if (ptr == null)
{
return false;
}
FateContext* fateById = ptr->GetFateById(fateId);
if (fateById == null)
{
return false;
}
return fateById->State == FateState.Running;
}
public uint GetItemLevel(uint itemId) public uint GetItemLevel(uint itemId)
{ {
return (_dataManager.GetExcelSheet<Item>()?.GetRowOrDefault(itemId))?.LevelItem.RowId ?? 0; return (_dataManager.GetExcelSheet<Item>()?.GetRowOrDefault(itemId))?.LevelItem.RowId ?? 0;

View file

@ -87,6 +87,11 @@ internal sealed class ActiveQuestComponent
public void Draw(bool isMinimized) public void Draw(bool isMinimized)
{ {
if (_fateController.IsRunning)
{
DrawFateActive(isMinimized);
return;
}
(QuestController.QuestProgress, QuestController.ECurrentQuestType)? currentQuestDetails = _questController.CurrentQuestDetails; (QuestController.QuestProgress, QuestController.ECurrentQuestType)? currentQuestDetails = _questController.CurrentQuestDetails;
QuestController.QuestProgress questProgress = currentQuestDetails?.Item1; QuestController.QuestProgress questProgress = currentQuestDetails?.Item1;
QuestController.ECurrentQuestType? currentQuestType = currentQuestDetails?.Item2; QuestController.ECurrentQuestType? currentQuestType = currentQuestDetails?.Item2;
@ -131,12 +136,12 @@ internal sealed class ActiveQuestComponent
if (interactionType == EInteractionType.WaitForManualProgress || interactionType == EInteractionType.Snipe || interactionType == EInteractionType.Instruction) if (interactionType == EInteractionType.WaitForManualProgress || interactionType == EInteractionType.Snipe || interactionType == EInteractionType.Instruction)
{ {
flag = true; flag = true;
goto IL_0154; goto IL_0169;
} }
} }
flag = false; flag = false;
goto IL_0154; goto IL_0169;
IL_0154: IL_0169:
if (flag) if (flag)
{ {
color.Push(ImGuiCol.Text, ImGuiColors.DalamudOrange); color.Push(ImGuiCol.Text, ImGuiColors.DalamudOrange);
@ -178,7 +183,7 @@ internal sealed class ActiveQuestComponent
text2.AppendFormatted((item2 == 1) ? string.Empty : "s"); text2.AppendFormatted((item2 == 1) ? string.Empty : "s");
text2.AppendLiteral(" - Leveling mode will start automatically"); text2.AppendLiteral(" - Leveling mode will start automatically");
ImGui.TextColored(in col, text2); ImGui.TextColored(in col, text2);
using (ImRaii.Disabled(_questController.IsRunning || !_autoDutyIpc.IsStopped())) using (ImRaii.Disabled(_questController.IsRunning || _fateController.IsRunning || !_autoDutyIpc.IsStopped()))
{ {
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
{ {
@ -247,6 +252,32 @@ internal sealed class ActiveQuestComponent
} }
} }
private void DrawFateActive(bool isMinimized)
{
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.ParsedGold))
{
ImU8String text = new ImU8String(6, 1);
text.AppendLiteral("FATE: ");
text.AppendFormatted(Shorten(_fateController.CurrentFate.Name));
ImGui.TextUnformatted(text);
}
IList<string> remainingTaskNames = _fateController.GetRemainingTaskNames();
if (remainingTaskNames.Count > 0)
{
ImGui.TextColored(ImGuiColors.DalamudGrey, remainingTaskNames[0]);
}
if (!isMinimized)
{
string text2 = (_fateController.CycleLimit.HasValue ? $"Cycle {_fateController.CompletedCycles + 1} / {_fateController.CycleLimit}" : $"Cycle {_fateController.CompletedCycles + 1}");
ImGui.TextColored(ImGuiColors.DalamudGrey3, text2);
}
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
{
_fateController.Stop("UI stop");
_movementController.Stop();
}
}
private void DrawQuestNames(QuestController.QuestProgress currentQuest, QuestController.ECurrentQuestType? currentQuestType) private void DrawQuestNames(QuestController.QuestProgress currentQuest, QuestController.ECurrentQuestType? currentQuestType)
{ {
if (currentQuestType == QuestController.ECurrentQuestType.Simulated) if (currentQuestType == QuestController.ECurrentQuestType.Simulated)
@ -563,7 +594,7 @@ internal sealed class ActiveQuestComponent
private void DrawQuestButtons(QuestController.QuestProgress currentQuest, QuestStep? currentStep, QuestProgressInfo? questProgressInfo, bool isMinimized) private void DrawQuestButtons(QuestController.QuestProgress currentQuest, QuestStep? currentStep, QuestProgressInfo? questProgressInfo, bool isMinimized)
{ {
using (ImRaii.Disabled(_questController.IsRunning)) using (ImRaii.Disabled(_questController.IsRunning || _fateController.IsRunning))
{ {
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
{ {

View file

@ -13,6 +13,8 @@ using Dalamud.Interface.Windowing;
using LLib.ImGui; using LLib.ImGui;
using Questionable.Controller; using Questionable.Controller;
using Questionable.Data; using Questionable.Data;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Questing; using Questionable.Model.Questing;
using Questionable.Windows.QuestComponents; using Questionable.Windows.QuestComponents;
@ -28,17 +30,23 @@ internal sealed class FateSelectionWindow : LWindow
private readonly MovementController _movementController; private readonly MovementController _movementController;
private readonly QuestFunctions _questFunctions;
private readonly QuestData _questData;
private readonly TerritoryData _territoryData; private readonly TerritoryData _territoryData;
private int _cycleLimit; private int _cycleLimit;
public FateSelectionWindow(FateController fateController, FateDefinitionRegistry fateDefinitionRegistry, QuestController questController, MovementController movementController, TerritoryData territoryData) public FateSelectionWindow(FateController fateController, FateDefinitionRegistry fateDefinitionRegistry, QuestController questController, MovementController movementController, QuestFunctions questFunctions, QuestData questData, TerritoryData territoryData)
: base("FATE Farming###QuestionableFateFarming") : base("FATE Farming###QuestionableFateFarming")
{ {
_fateController = fateController; _fateController = fateController;
_fateDefinitionRegistry = fateDefinitionRegistry; _fateDefinitionRegistry = fateDefinitionRegistry;
_questController = questController; _questController = questController;
_movementController = movementController; _movementController = movementController;
_questFunctions = questFunctions;
_questData = questData;
_territoryData = territoryData; _territoryData = territoryData;
base.Size = new Vector2(600f, 400f); base.Size = new Vector2(600f, 400f);
base.SizeCondition = ImGuiCond.FirstUseEver; base.SizeCondition = ImGuiCond.FirstUseEver;
@ -335,12 +343,31 @@ internal sealed class FateSelectionWindow : LWindow
private void DrawFateRowActions(FateDefinition fate, bool disabled) private void DrawFateRowActions(FateDefinition fate, bool disabled)
{ {
using (ImRaii.Disabled(disabled)) bool flag = fate.RequiredQuestId.HasValue && !_questFunctions.IsQuestComplete(new QuestId(fate.RequiredQuestId.Value));
{
ImU8String id = new ImU8String(5, 1); ImU8String id = new ImU8String(5, 1);
id.AppendLiteral("fate_"); id.AppendLiteral("fate_");
id.AppendFormatted(fate.Name); id.AppendFormatted(fate.Name);
using (ImRaii.PushId(id)) using (ImRaii.PushId(id))
{
if (flag)
{
using (ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TextColored(ImGuiColors.DalamudRed, FontAwesomeIcon.Times.ToIconString());
}
if (ImGui.IsItemHovered())
{
IQuestInfo questInfo;
string value = (_questData.TryGetQuestInfo(new QuestId(fate.RequiredQuestId.Value), out questInfo) ? questInfo.Name : fate.RequiredQuestId.Value.ToString(CultureInfo.InvariantCulture));
ImU8String tooltip = new ImU8String(33, 1);
tooltip.AppendLiteral("Requires \"");
tooltip.AppendFormatted(value);
tooltip.AppendLiteral("\" to be completed first");
ImGui.SetTooltip(tooltip);
}
return;
}
using (ImRaii.Disabled(disabled))
{ {
if (ImGuiComponents.IconButton(FontAwesomeIcon.Play)) if (ImGuiComponents.IconButton(FontAwesomeIcon.Play))
{ {
@ -350,7 +377,6 @@ internal sealed class FateSelectionWindow : LWindow
_fateController.Start(fate, cycleLimit); _fateController.Start(fate, cycleLimit);
} }
} }
}
if (disabled && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) if (disabled && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{ {
if (_fateController.IsRunning) if (_fateController.IsRunning)
@ -363,6 +389,7 @@ internal sealed class FateSelectionWindow : LWindow
} }
} }
} }
}
private static string FormatElapsed(TimeSpan elapsed) private static string FormatElapsed(TimeSpan elapsed)
{ {

View file

@ -18,6 +18,8 @@ internal sealed class QuestWindow : LWindow, IPersistableWindowConfig
{ {
private static readonly Version PluginVersion = typeof(QuestionablePlugin).Assembly.GetName().Version; private static readonly Version PluginVersion = typeof(QuestionablePlugin).Assembly.GetName().Version;
private const string TestingSuffix = "";
private readonly IDalamudPluginInterface _pluginInterface; private readonly IDalamudPluginInterface _pluginInterface;
private readonly QuestController _questController; private readonly QuestController _questController;