using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Questionable.Controller; using Questionable.Data; using Questionable.Functions; using Questionable.Model; using Questionable.Model.Questing; using Questionable.Windows.QuestComponents; using Questionable.Windows.Utils; namespace Questionable.Windows; internal sealed class FateSelectionWindow : ThemedWindow { private readonly FateController _fateController; private readonly FateDefinitionRegistry _fateDefinitionRegistry; private readonly QuestController _questController; private readonly SeasonalDutyController _seasonalDutyController; private readonly CustomDeliveryController _customDeliveryController; private readonly AttunementController _attunementController; private readonly MovementController _movementController; private readonly QuestFunctions _questFunctions; private readonly QuestData _questData; private readonly TerritoryData _territoryData; private int _cycleLimit; private string _searchText = string.Empty; public FateSelectionWindow(FateController fateController, FateDefinitionRegistry fateDefinitionRegistry, QuestController questController, SeasonalDutyController seasonalDutyController, CustomDeliveryController customDeliveryController, AttunementController attunementController, MovementController movementController, QuestFunctions questFunctions, QuestData questData, TerritoryData territoryData) : base("FATE Farming###QuestionableFateFarming") { _fateController = fateController; _fateDefinitionRegistry = fateDefinitionRegistry; _questController = questController; _seasonalDutyController = seasonalDutyController; _customDeliveryController = customDeliveryController; _attunementController = attunementController; _movementController = movementController; _questFunctions = questFunctions; _questData = questData; _territoryData = territoryData; base.Size = new Vector2(600f, 400f); base.SizeCondition = ImGuiCond.FirstUseEver; base.SizeConstraints = new WindowSizeConstraints { MinimumSize = new Vector2(500f, 300f), MaximumSize = new Vector2(900f, 700f) }; } public override void DrawContent() { if (_fateController.IsRunning) { if (CycleSelectionUi.DrawRunningBanner(BuildRunningStatusText(), UiThemeUtils.StatusActive, FontAwesomeIcon.Play)) { _fateController.Stop("UI stop"); _movementController.Stop(); } } else { var (contentStartPos, availableWidth, drawList) = UiThemeUtils.BeginCard(); UiThemeUtils.SectionHeader("Cycle Configuration"); DrawControlsStrip(); UiThemeUtils.EndCard(contentStartPos, availableWidth, drawList); } if (_fateDefinitionRegistry.Definitions.Count == 0) { UiThemeUtils.EmptyState(FontAwesomeIcon.Star, "No FATE Definitions", "Add FATE definitions to the FatePaths folder to get started."); return; } UiThemeUtils.SectionSpacing(); var (contentStartPos2, availableWidth2, drawList2) = UiThemeUtils.BeginCard(); UiThemeUtils.SectionHeader("FATE List"); UiThemeUtils.SearchInput("##FateSearch", ref _searchText, -13f, "Search FATEs..."); ImGui.Spacing(); DrawFateTable(); UiThemeUtils.EndCard(contentStartPos2, availableWidth2, drawList2); } private string BuildRunningStatusText() { string value = (_fateController.CycleLimit.HasValue ? $"Cycle {_fateController.CompletedCycles + 1} / {_fateController.CycleLimit}" : $"Cycle {_fateController.CompletedCycles + 1}"); string value2 = FormatElapsed(_fateController.ElapsedTime); IList remainingTaskNames = _fateController.GetRemainingTaskNames(); string text = ((remainingTaskNames.Count > 0) ? remainingTaskNames[0] : ""); string name = _fateController.CurrentFate.Name; if (text.Length > 0) { return $"{name} · {value} · {value2} · {text}"; } return $"{name} · {value} · {value2}"; } private void DrawControlsStrip() { using (ImRaii.Disabled(_fateController.IsRunning)) { ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted("Cycles:"); ImGui.SameLine(); ImGui.SetNextItemWidth(100f * ImGui.GetIO().FontGlobalScale); ImGui.InputInt("##CycleLimit", ref _cycleLimit, 1, 5); if (_cycleLimit < 0) { _cycleLimit = 0; } ImGui.SameLine(); ImGui.TextColored(in UiThemeUtils.MutedTextColor, (_cycleLimit == 0) ? "(unlimited)" : ""); } ImGui.Spacing(); } private void DrawFateTable() { IEnumerable source = _fateDefinitionRegistry.Definitions.Values; if (!string.IsNullOrWhiteSpace(_searchText)) { source = source.Where((FateDefinition f) => f.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase) || (_territoryData.GetName(f.TerritoryId) ?? f.Description).Contains(_searchText, StringComparison.OrdinalIgnoreCase)); } List list = source.OrderBy(delegate(FateDefinition f) { DateTime? eventExpiry = f.EventExpiry; if (eventExpiry.HasValue) { DateTime valueOrDefault = eventExpiry.GetValueOrDefault(); if (EventInfoComponent.NormalizeExpiry(valueOrDefault) - DateTime.UtcNow > TimeSpan.Zero) { return 0; } } return 1; }).ThenBy(delegate(FateDefinition f) { DateTime? eventExpiry = f.EventExpiry; if (eventExpiry.HasValue) { DateTime valueOrDefault = eventExpiry.GetValueOrDefault(); TimeSpan timeSpan = EventInfoComponent.NormalizeExpiry(valueOrDefault) - DateTime.UtcNow; if (timeSpan > TimeSpan.Zero) { return timeSpan.TotalSeconds; } } return double.MaxValue; }).ThenBy((FateDefinition f) => f.Name, StringComparer.OrdinalIgnoreCase) .ToList(); if (list.Count == 0) { UiThemeUtils.EmptyState(FontAwesomeIcon.Search, "No FATEs match your search."); return; } using ImRaii.TableDisposable tableDisposable = UiThemeUtils.BeginThemedTable("FateTable", 4, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.ScrollY); if (!tableDisposable) { return; } ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.None, 3f); ImGui.TableSetupColumn("Zone", ImGuiTableColumnFlags.None, 2f); ImGui.TableSetupColumn("Expiry", ImGuiTableColumnFlags.WidthFixed, 80f); ImGui.TableSetupColumn("##Actions", ImGuiTableColumnFlags.WidthFixed, 30f); ImGui.TableHeadersRow(); foreach (FateDefinition item in list) { ImGui.TableNextRow(); bool flag = _fateController.IsRunning && _fateController.CurrentFate == item; if (flag) { ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.ColorConvertFloat4ToU32(UiThemeUtils.AccentDimColor)); } ImGui.TableNextColumn(); DrawFateRowName(item, flag); ImGui.TableNextColumn(); ImGui.TextUnformatted(_territoryData.GetName(item.TerritoryId) ?? item.Description); ImGui.TableNextColumn(); DrawFateRowExpiry(item); ImGui.TableNextColumn(); bool disabled = _fateController.IsRunning || _seasonalDutyController.IsRunning || _customDeliveryController.IsRunning || _attunementController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual; DrawFateRowActions(item, disabled); } } private void DrawFateRowName(FateDefinition fate, bool isActive) { if (isActive) { ImGui.TextColored(in UiThemeUtils.AccentColor, fate.Name); } else { ImGui.TextUnformatted(fate.Name); } if (!ImGui.IsItemHovered()) { return; } using (ImRaii.Tooltip()) { ImGui.TextColored(in UiThemeUtils.PrimaryTextColor, fate.Name); ImGui.Separator(); ImGui.PushTextWrapPos(300f); ImGui.TextUnformatted(fate.Description); ImGui.PopTextWrapPos(); ImGui.Spacing(); string nameAndId = _territoryData.GetNameAndId(fate.TerritoryId); ImU8String text = new ImU8String(6, 1); text.AppendLiteral("Zone: "); text.AppendFormatted(nameAndId); ImGui.TextColored(in UiThemeUtils.DimTextColor, text); ImU8String text2 = new ImU8String(9, 1); text2.AppendLiteral("Targets: "); text2.AppendFormatted(fate.Targets.Count); ImGui.TextColored(in UiThemeUtils.DimTextColor, text2); if (fate.RequiredStatusId.HasValue) { ImGui.TextColored(in UiThemeUtils.StatusActive, "Requires transformation"); } DateTime? eventExpiry = fate.EventExpiry; if (eventExpiry.HasValue) { DateTime valueOrDefault = eventExpiry.GetValueOrDefault(); TimeSpan timeSpan = EventInfoComponent.NormalizeExpiry(valueOrDefault) - DateTime.UtcNow; if (timeSpan > TimeSpan.Zero) { string text3 = EventInfoComponent.FormatRemainingFull(timeSpan); ImGui.TextColored(in UiThemeUtils.StatusActive, text3); } } } } private static void DrawFateRowExpiry(FateDefinition fate) { DateTime? eventExpiry = fate.EventExpiry; if (eventExpiry.HasValue) { DateTime valueOrDefault = eventExpiry.GetValueOrDefault(); TimeSpan timeSpan = EventInfoComponent.NormalizeExpiry(valueOrDefault) - DateTime.UtcNow; if (timeSpan > TimeSpan.Zero) { string text = EventInfoComponent.FormatRemainingDays(timeSpan); ImGui.TextColored((timeSpan.TotalDays < 3.0) ? UiThemeUtils.StatusActive : UiThemeUtils.DimTextColor, text); } else { ImGui.TextColored(in UiThemeUtils.MutedTextColor, "Expired"); } } else { ImGui.TextColored(in UiThemeUtils.MutedTextColor, "--"); } } private void DrawFateRowActions(FateDefinition fate, bool disabled) { uint? num = fate.RequiredQuestId; object obj; if (num.HasValue) { uint valueOrDefault = num.GetValueOrDefault(); if (valueOrDefault <= 65535) { obj = new QuestId((ushort)valueOrDefault); goto IL_004d; } } obj = null; goto IL_004d; IL_004d: QuestId questId = (QuestId)obj; bool flag = questId != null && !_questFunctions.IsQuestComplete(questId); ImU8String id = new ImU8String(5, 1); id.AppendLiteral("fate_"); id.AppendFormatted(fate.Name); using (ImRaii.PushId(id)) { if (flag) { using (ImRaii.PushFont(UiBuilder.IconFont)) { ImGui.TextColored(in UiThemeUtils.StatusLocked, FontAwesomeIcon.Times.ToIconString()); } if (ImGui.IsItemHovered()) { IQuestInfo questInfo; string value = (_questData.TryGetQuestInfo(new QuestId(fate.RequiredQuestId.Value), out questInfo) ? questInfo.Name : (num?.ToString(CultureInfo.InvariantCulture) ?? "Unknown")); 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)) { using (ImRaii.PushColor(ImGuiCol.Button, UiThemeUtils.PlayButtonColor)) { using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UiThemeUtils.PlayButtonHover)) { using (ImRaii.PushColor(ImGuiCol.ButtonActive, UiThemeUtils.PlayButtonActive)) { using (ImRaii.PushColor(ImGuiCol.Text, UiThemeUtils.PlayButtonText)) { if (UiThemeUtils.IconButton(FontAwesomeIcon.Play, ImGui.GetFrameHeight())) { _movementController.Stop(); _questController.Stop("FATE farming start"); _seasonalDutyController.Stop("FATE farming start"); _customDeliveryController.Stop("FATE farming start"); _attunementController.Stop("FATE farming start"); int? cycleLimit = ((_cycleLimit > 0) ? new int?(_cycleLimit) : ((int?)null)); _fateController.Start(fate, cycleLimit); } } } } } } if (disabled && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { if (_fateController.IsRunning) { ImGui.SetTooltip("Already farming a FATE"); } else if (_seasonalDutyController.IsRunning) { ImGui.SetTooltip("Stop seasonal duty farming first"); } else if (_customDeliveryController.IsRunning) { ImGui.SetTooltip("Stop custom delivery first"); } else if (_attunementController.IsRunning) { ImGui.SetTooltip("Stop attunement first"); } else { ImGui.SetTooltip("Stop quest automation first"); } } } } private static string FormatElapsed(TimeSpan elapsed) { IFormatProvider invariantCulture; if (elapsed.TotalHours >= 1.0) { invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider = invariantCulture; DefaultInterpolatedStringHandler handler = new DefaultInterpolatedStringHandler(5, 3, invariantCulture); handler.AppendFormatted((int)elapsed.TotalHours); handler.AppendLiteral("h "); handler.AppendFormatted(elapsed.Minutes, "D2"); handler.AppendLiteral("m "); handler.AppendFormatted(elapsed.Seconds, "D2"); handler.AppendLiteral("s"); return string.Create(provider, ref handler); } if (elapsed.TotalMinutes >= 1.0) { invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider2 = invariantCulture; DefaultInterpolatedStringHandler handler2 = new DefaultInterpolatedStringHandler(3, 2, invariantCulture); handler2.AppendFormatted((int)elapsed.TotalMinutes); handler2.AppendLiteral("m "); handler2.AppendFormatted(elapsed.Seconds, "D2"); handler2.AppendLiteral("s"); return string.Create(provider2, ref handler2); } invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider3 = invariantCulture; DefaultInterpolatedStringHandler handler3 = new DefaultInterpolatedStringHandler(1, 1, invariantCulture); handler3.AppendFormatted(elapsed.Seconds); handler3.AppendLiteral("s"); return string.Create(provider3, ref handler3); } }