forked from aly/qstbak
374 lines
13 KiB
C#
374 lines
13 KiB
C#
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 SeasonalDutySelectionWindow : ThemedWindow
|
|
{
|
|
private readonly SeasonalDutyController _seasonalDutyController;
|
|
|
|
private readonly SeasonalDutyDefinitionRegistry _seasonalDutyDefinitionRegistry;
|
|
|
|
private readonly QuestController _questController;
|
|
|
|
private readonly FateController _fateController;
|
|
|
|
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 SeasonalDutySelectionWindow(SeasonalDutyController seasonalDutyController, SeasonalDutyDefinitionRegistry seasonalDutyDefinitionRegistry, QuestController questController, FateController fateController, CustomDeliveryController customDeliveryController, AttunementController attunementController, MovementController movementController, QuestFunctions questFunctions, QuestData questData, TerritoryData territoryData)
|
|
: base("Seasonal Duty Farming###QuestionableSeasonalDutyFarming")
|
|
{
|
|
_seasonalDutyController = seasonalDutyController;
|
|
_seasonalDutyDefinitionRegistry = seasonalDutyDefinitionRegistry;
|
|
_questController = questController;
|
|
_fateController = fateController;
|
|
_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 (_seasonalDutyController.IsRunning)
|
|
{
|
|
if (CycleSelectionUi.DrawRunningBanner(BuildRunningStatusText(), UiThemeUtils.StatusComplete, FontAwesomeIcon.Play))
|
|
{
|
|
_seasonalDutyController.Stop("UI stop");
|
|
_movementController.Stop();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var (contentStartPos, availableWidth, drawList) = UiThemeUtils.BeginCard();
|
|
UiThemeUtils.SectionHeader("Cycle Configuration");
|
|
DrawControlsStrip();
|
|
UiThemeUtils.EndCard(contentStartPos, availableWidth, drawList);
|
|
}
|
|
if (_seasonalDutyDefinitionRegistry.Definitions.Count == 0)
|
|
{
|
|
UiThemeUtils.EmptyState(FontAwesomeIcon.Leaf, "No Seasonal Duty Definitions", "Add definitions to the SeasonalDutyPaths folder to get started.");
|
|
return;
|
|
}
|
|
UiThemeUtils.SectionSpacing();
|
|
var (contentStartPos2, availableWidth2, drawList2) = UiThemeUtils.BeginCard();
|
|
UiThemeUtils.SectionHeader("Duty List");
|
|
UiThemeUtils.SearchInput("##SeasonalDutySearch", ref _searchText, -13f, "Search duties...");
|
|
ImGui.Spacing();
|
|
DrawDutyTable();
|
|
UiThemeUtils.EndCard(contentStartPos2, availableWidth2, drawList2);
|
|
}
|
|
|
|
private string BuildRunningStatusText()
|
|
{
|
|
string value = (_seasonalDutyController.CycleLimit.HasValue ? $"Cycle {_seasonalDutyController.CompletedCycles + 1} / {_seasonalDutyController.CycleLimit}" : $"Cycle {_seasonalDutyController.CompletedCycles + 1}");
|
|
string value2 = FormatElapsed(_seasonalDutyController.ElapsedTime);
|
|
IList<string> remainingTaskNames = _seasonalDutyController.GetRemainingTaskNames();
|
|
string text = ((remainingTaskNames.Count > 0) ? remainingTaskNames[0] : "");
|
|
string name = _seasonalDutyController.CurrentDuty.Name;
|
|
if (text.Length > 0)
|
|
{
|
|
return $"{name} · {value} · {value2} · {text}";
|
|
}
|
|
return $"{name} · {value} · {value2}";
|
|
}
|
|
|
|
private void DrawControlsStrip()
|
|
{
|
|
using (ImRaii.Disabled(_seasonalDutyController.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 DrawDutyTable()
|
|
{
|
|
IEnumerable<SeasonalDutyDefinition> source = _seasonalDutyDefinitionRegistry.Definitions.Values;
|
|
if (!string.IsNullOrWhiteSpace(_searchText))
|
|
{
|
|
source = source.Where((SeasonalDutyDefinition d) => d.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase) || (_territoryData.GetName(d.TerritoryId) ?? d.Name).Contains(_searchText, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
List<SeasonalDutyDefinition> list = source.OrderBy(delegate(SeasonalDutyDefinition d)
|
|
{
|
|
DateTime? eventExpiry = d.EventExpiry;
|
|
if (eventExpiry.HasValue)
|
|
{
|
|
DateTime valueOrDefault = eventExpiry.GetValueOrDefault();
|
|
if (EventInfoComponent.NormalizeExpiry(valueOrDefault) - DateTime.UtcNow > TimeSpan.Zero)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}).ThenBy(delegate(SeasonalDutyDefinition d)
|
|
{
|
|
DateTime? eventExpiry = d.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<SeasonalDutyDefinition, string>((SeasonalDutyDefinition d) => d.Name, StringComparer.OrdinalIgnoreCase)
|
|
.ToList();
|
|
if (list.Count == 0)
|
|
{
|
|
UiThemeUtils.EmptyState(FontAwesomeIcon.Search, "No duties match your search.");
|
|
return;
|
|
}
|
|
using ImRaii.TableDisposable tableDisposable = UiThemeUtils.BeginThemedTable("SeasonalDutyTable", 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 (SeasonalDutyDefinition item in list)
|
|
{
|
|
ImGui.TableNextRow();
|
|
bool flag = _seasonalDutyController.IsRunning && _seasonalDutyController.CurrentDuty == item;
|
|
if (flag)
|
|
{
|
|
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.ColorConvertFloat4ToU32(UiThemeUtils.AccentDimColor));
|
|
}
|
|
ImGui.TableNextColumn();
|
|
DrawDutyRowName(item, flag);
|
|
ImGui.TableNextColumn();
|
|
ImGui.TextUnformatted(_territoryData.GetName(item.TerritoryId) ?? item.Name);
|
|
ImGui.TableNextColumn();
|
|
DrawDutyRowExpiry(item);
|
|
ImGui.TableNextColumn();
|
|
bool disabled = _seasonalDutyController.IsRunning || _fateController.IsRunning || _customDeliveryController.IsRunning || _attunementController.IsRunning || _questController.AutomationType != QuestController.EAutomationType.Manual;
|
|
DrawDutyRowActions(item, disabled);
|
|
}
|
|
}
|
|
|
|
private void DrawDutyRowName(SeasonalDutyDefinition duty, bool isActive)
|
|
{
|
|
if (isActive)
|
|
{
|
|
ImGui.TextColored(in UiThemeUtils.AccentColor, duty.Name);
|
|
}
|
|
else
|
|
{
|
|
ImGui.TextUnformatted(duty.Name);
|
|
}
|
|
if (!ImGui.IsItemHovered())
|
|
{
|
|
return;
|
|
}
|
|
using (ImRaii.Tooltip())
|
|
{
|
|
ImGui.TextColored(in UiThemeUtils.PrimaryTextColor, duty.Name);
|
|
ImGui.Separator();
|
|
ImGui.Spacing();
|
|
string nameAndId = _territoryData.GetNameAndId(duty.TerritoryId);
|
|
ImU8String text = new ImU8String(6, 1);
|
|
text.AppendLiteral("Zone: ");
|
|
text.AppendFormatted(nameAndId);
|
|
ImGui.TextColored(in UiThemeUtils.DimTextColor, text);
|
|
DateTime? eventExpiry = duty.EventExpiry;
|
|
if (eventExpiry.HasValue)
|
|
{
|
|
DateTime valueOrDefault = eventExpiry.GetValueOrDefault();
|
|
TimeSpan timeSpan = EventInfoComponent.NormalizeExpiry(valueOrDefault) - DateTime.UtcNow;
|
|
if (timeSpan > TimeSpan.Zero)
|
|
{
|
|
string text2 = EventInfoComponent.FormatRemainingFull(timeSpan);
|
|
ImGui.TextColored(in UiThemeUtils.StatusActive, text2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void DrawDutyRowExpiry(SeasonalDutyDefinition duty)
|
|
{
|
|
DateTime? eventExpiry = duty.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 DrawDutyRowActions(SeasonalDutyDefinition duty, bool disabled)
|
|
{
|
|
bool flag = duty.RequiredQuestId.HasValue && !_questFunctions.IsQuestComplete(new QuestId(duty.RequiredQuestId.Value));
|
|
ImU8String id = new ImU8String(5, 1);
|
|
id.AppendLiteral("duty_");
|
|
id.AppendFormatted(duty.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(duty.RequiredQuestId.Value), out questInfo) ? questInfo.Name : duty.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))
|
|
{
|
|
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("Seasonal duty farming start");
|
|
_fateController.Stop("Seasonal duty farming start");
|
|
_customDeliveryController.Stop("Seasonal duty farming start");
|
|
_attunementController.Stop("Seasonal duty farming start");
|
|
int? cycleLimit = ((_cycleLimit > 0) ? new int?(_cycleLimit) : ((int?)null));
|
|
_seasonalDutyController.Start(duty, cycleLimit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (disabled && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
|
{
|
|
if (_seasonalDutyController.IsRunning)
|
|
{
|
|
ImGui.SetTooltip("Already farming a seasonal duty");
|
|
}
|
|
else if (_fateController.IsRunning)
|
|
{
|
|
ImGui.SetTooltip("Stop FATE 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);
|
|
}
|
|
}
|