919b72051f
Co-authored-by: Cursor <cursoragent@cursor.com>
166 lines
6.5 KiB
C#
166 lines
6.5 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Numerics;
|
||
using Dalamud.Bindings.ImGui;
|
||
using Dalamud.Interface.Windowing;
|
||
using Lumina.Excel.Sheets;
|
||
using CfcType = FFXIVClientStructs.FFXIV.Client.Game.InstanceContent.InstanceContentType;
|
||
|
||
namespace MSQProgress;
|
||
|
||
public class ProgressWindow : Window
|
||
{
|
||
private readonly MSQDataService _dataService;
|
||
private int _drawCount;
|
||
|
||
public ProgressWindow() : base("MSQ Progress", ImGuiWindowFlags.AlwaysAutoResize)
|
||
{
|
||
_dataService = new MSQDataService(Service.DataManager);
|
||
}
|
||
|
||
public override void Draw()
|
||
{
|
||
if (!Service.ClientState.IsLoggedIn)
|
||
{
|
||
ImGui.TextColored(new Vector4(1, 0.8f, 0.4f, 1), "Log in to see your MSQ progress.");
|
||
return;
|
||
}
|
||
|
||
_drawCount++;
|
||
if (_drawCount % 30 == 0)
|
||
_dataService.Refresh();
|
||
|
||
if (ImGui.Button("Refresh"))
|
||
_dataService.Refresh();
|
||
|
||
ImGui.SameLine();
|
||
ImGui.TextDisabled("(Progress updates automatically; use Refresh if needed)");
|
||
|
||
ImGui.Spacing();
|
||
ImGui.Separator();
|
||
ImGui.Spacing();
|
||
|
||
ImGui.Text("Progress by expansion");
|
||
ImGui.TextDisabled("(Each expansion = base + all patch MSQ, e.g. ARR = 2.0 + 2.1–2.55)");
|
||
ImGui.Spacing();
|
||
|
||
var byExpansion = _dataService.ByExpansion;
|
||
if (byExpansion.Count == 0)
|
||
{
|
||
ImGui.TextColored(new Vector4(0.8f, 0.6f, 0.2f, 1), "No MSQ data loaded. Try refreshing after opening the MSQ journal once.");
|
||
return;
|
||
}
|
||
|
||
if (ImGui.BeginTable("msq_expansions", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg))
|
||
{
|
||
ImGui.TableSetupColumn("Expansion", ImGuiTableColumnFlags.WidthStretch);
|
||
ImGui.TableSetupColumn("Completed", ImGuiTableColumnFlags.WidthFixed, 80);
|
||
ImGui.TableSetupColumn("Total", ImGuiTableColumnFlags.WidthFixed, 60);
|
||
ImGui.TableSetupColumn("Remaining", ImGuiTableColumnFlags.WidthFixed, 80);
|
||
ImGui.TableHeadersRow();
|
||
|
||
foreach (var kv in byExpansion.OrderBy(x => x.Key))
|
||
{
|
||
var prog = kv.Value;
|
||
ImGui.TableNextRow();
|
||
ImGui.TableSetColumnIndex(0);
|
||
var isCurrent = kv.Key == _dataService.CurrentExpansionRowId;
|
||
if (isCurrent)
|
||
ImGui.TextColored(new Vector4(0.4f, 0.9f, 0.5f, 1), $"{prog.Name} (current)");
|
||
else
|
||
ImGui.Text(prog.Name);
|
||
ImGui.TableSetColumnIndex(1);
|
||
ImGui.Text(prog.CompletedQuests.ToString());
|
||
ImGui.TableSetColumnIndex(2);
|
||
ImGui.Text(prog.TotalQuests.ToString());
|
||
ImGui.TableSetColumnIndex(3);
|
||
ImGui.Text(prog.RemainingQuests.ToString());
|
||
}
|
||
ImGui.EndTable();
|
||
}
|
||
|
||
ImGui.Spacing();
|
||
ImGui.Separator();
|
||
ImGui.Spacing();
|
||
|
||
var cfcSheet = Service.DataManager.GetExcelSheet<ContentFinderCondition>();
|
||
if (cfcSheet != null)
|
||
{
|
||
var (until, fromStart) = GetQuestsUntilNextInstanceUnlock(cfcSheet);
|
||
var currentIndex = _dataService.CurrentGlobalIndex;
|
||
if (until >= 0)
|
||
ImGui.TextColored(new Vector4(0.6f, 0.85f, 1f, 1), $"{until} Quests remaining until next instance unlock");
|
||
else if (fromStart >= 0)
|
||
ImGui.TextColored(new Vector4(0.6f, 0.85f, 1f, 1), $"At least {fromStart} quests until next instance unlock (open MSQ journal + Refresh for exact count)");
|
||
else if (currentIndex >= 0)
|
||
ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.6f, 1), "Next instance unlock unknown (open MSQ journal and Refresh).");
|
||
else
|
||
ImGui.TextColored(new Vector4(0.5f, 0.7f, 0.5f, 1), "No instance unlocks remaining (or open MSQ journal and Refresh).");
|
||
}
|
||
else
|
||
ImGui.TextDisabled("Duty data unavailable.");
|
||
}
|
||
|
||
/// <summary>Returns (quests until next instance, from-start fallback). Uses MSQ order only (ignores game unlock state) so we always show the next instance in the story.</summary>
|
||
private (int until, int fromStart) GetQuestsUntilNextInstanceUnlock(IEnumerable<ContentFinderCondition> cfcSheet)
|
||
{
|
||
int minUntil = -1;
|
||
int minFromStart = -1;
|
||
var currentIndex = _dataService.CurrentGlobalIndex;
|
||
foreach (var cfc in GetDutiesForDisplay(cfcSheet))
|
||
{
|
||
if (!DutyUnlockMap.TryGetUnlockQuest(cfc.RowId, out var unlockQuestId, Service.DataManager))
|
||
unlockQuestId = DutyUnlockMap.GetUnlockQuestFromRow(cfc);
|
||
if (unlockQuestId == 0)
|
||
continue;
|
||
var unlockStepIndex = _dataService.GetStepIndexForQuest(unlockQuestId);
|
||
// Skip if we're past this unlock (we've completed that step)
|
||
if (currentIndex >= 0 && unlockStepIndex >= 0 && unlockStepIndex < currentIndex)
|
||
continue;
|
||
|
||
int until;
|
||
if (currentIndex >= 0 && unlockStepIndex >= 0 && unlockStepIndex == currentIndex)
|
||
until = 0; // we're on the unlock quest right now
|
||
else
|
||
until = _dataService.QuestsUntilQuest(unlockQuestId);
|
||
|
||
var fromStart = _dataService.QuestsFromStartToQuest(unlockQuestId);
|
||
if (until >= 0 && (minUntil < 0 || until < minUntil))
|
||
minUntil = until;
|
||
if (fromStart >= 0 && (minFromStart < 0 || fromStart < minFromStart))
|
||
minFromStart = fromStart;
|
||
}
|
||
return (minUntil, minFromStart);
|
||
}
|
||
|
||
private static IEnumerable<ContentFinderCondition> GetDutiesForDisplay(IEnumerable<ContentFinderCondition> sheet)
|
||
{
|
||
var dungeon = (uint)CfcType.Dungeon;
|
||
var trial = (uint)CfcType.Trial;
|
||
var raid = (uint)CfcType.Raid;
|
||
foreach (var row in sheet)
|
||
{
|
||
if (!row.ContentType.IsValid) continue;
|
||
var typeId = row.ContentType.RowId;
|
||
if (typeId == dungeon || typeId == trial || typeId == raid)
|
||
yield return row;
|
||
}
|
||
}
|
||
|
||
private static string GetContentTypeLabel(ContentFinderCondition cfc)
|
||
{
|
||
if (!cfc.ContentType.IsValid)
|
||
return "?";
|
||
var typeId = cfc.ContentType.RowId;
|
||
return typeId switch
|
||
{
|
||
(uint)CfcType.Dungeon => "Dungeon",
|
||
(uint)CfcType.Trial => "Trial",
|
||
(uint)CfcType.Raid => "Raid",
|
||
(uint)CfcType.GuildOrder => "Guildhest",
|
||
_ => "?"
|
||
};
|
||
}
|
||
}
|