qstbak/QuestionableCompanion/QuestionableCompanion.Windows/NewMainWindow.cs
2025-12-04 04:40:03 +10:00

3368 lines
123 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using QuestionableCompanion.Data;
using QuestionableCompanion.Models;
using QuestionableCompanion.Services;
namespace QuestionableCompanion.Windows;
public class NewMainWindow : Window, IDisposable
{
private class Particle
{
public Vector2 Position;
public Vector2 Velocity;
public float Size;
public float Alpha;
public Vector4 Color;
}
private readonly Plugin plugin;
private readonly AutoRetainerIPC autoRetainerIpc;
private readonly QuestTrackingService questTrackingService;
private readonly QuestRotationExecutionService questRotationService;
private readonly EventQuestExecutionService eventQuestService;
private readonly AlliedSocietyRotationService alliedSocietyRotationService;
private readonly AlliedSocietyPriorityWindow alliedSocietyPriorityWindow;
private readonly DataCenterService dataCenterService;
private readonly MSQProgressionService msqProgressionService;
private readonly IPluginLog log;
private readonly IUiBuilder uiBuilder;
private readonly IDataManager dataManager;
private readonly Vector4 colorPrimary = new Vector4(0.478f, 0.686f, 0.878f, 1f);
private readonly Vector4 colorSecondary = new Vector4(0.949f, 0.769f, 0.388f, 1f);
private readonly Vector4 colorAccent = new Vector4(0.729f, 0.294f, 0.184f, 1f);
private readonly Vector4 colorDarkBg = new Vector4(0.12f, 0.12f, 0.12f, 1f);
private readonly Vector4 colorSidebarBg = new Vector4(0.08f, 0.08f, 0.08f, 1f);
private readonly Vector4 colorHover = new Vector4(0.2f, 0.2f, 0.2f, 1f);
private float animTime;
private float glowPulse;
private float particleTime;
private List<Particle> particles = new List<Particle>();
private int selectedTab;
private int selectedDCFilter;
private bool charactersExpanded = true;
private bool menuExpanded = true;
private bool isMinimized;
private List<string> registeredCharacters = new List<string>();
private Dictionary<string, bool> characterSelection = new Dictionary<string, bool>();
private Dictionary<string, CharacterProgressInfo> characterProgressCache = new Dictionary<string, CharacterProgressInfo>();
private DateTime lastCharacterRefresh = DateTime.MinValue;
private bool initialCharacterLoadComplete;
private DateTime initialLoadStartTime = DateTime.MinValue;
private int characterLoadAttempts;
private const int MaxCharacterLoadAttempts = 5;
private readonly int[] retryDelaysSeconds = new int[5] { 1, 2, 4, 8, 15 };
private bool warningMenuRetryCycleComplete;
private DateTime warningMenuRetryStartTime = DateTime.MinValue;
private int warningMenuRetryAttempts;
private const int MaxWarningMenuRetryAttempts = 4;
private readonly int[] warningMenuRetryDelaysSeconds = new int[4] { 1, 2, 4, 8 };
private Dictionary<string, List<string>> charactersByDataCenter = new Dictionary<string, List<string>>();
private List<string> availableDataCenters = new List<string> { "All", "EU", "NA", "JP", "OCE" };
private bool showSelectWorldDialog;
private bool showDeselectWorldDialog;
private string selectedWorldForBulkAction = "";
private List<string> availableWorlds = new List<string>();
private string selectedWorldFilter = "All";
private bool showWorldActionDropdown;
private uint inputStopQuestId;
private int inputStopSequence = -1;
private string selectedEventQuestId = "";
private List<(string QuestId, string QuestName)> availableEventQuests = new List<(string, string)>();
private List<string> resolvedPrerequisites = new List<string>();
private int eventQuestViewMode;
private DateTime lastEventQuestRefresh = DateTime.MinValue;
private readonly Dictionary<string, List<string>> dataCenterWorlds = new Dictionary<string, List<string>>
{
{
"Aether",
new List<string> { "Adamantoise", "Cactuar", "Faerie", "Gilgamesh", "Jenova", "Midgardsormr", "Sargatanas", "Siren" }
},
{
"Primal",
new List<string> { "Behemoth", "Excalibur", "Exodus", "Hyperion", "Lamia", "Leviathan", "Ultros" }
},
{
"Crystal",
new List<string> { "Balmung", "Brynhildr", "Coeurl", "Diabolos", "Goblin", "Malboro", "Mateus", "Zalera" }
},
{
"Dynamis",
new List<string> { "Halicarnassus", "Maduin", "Marilith", "Seraph" }
},
{
"Chaos",
new List<string> { "Cerberus", "Louisoix", "Moogle", "Omega", "Phantom", "Ragnarok", "Sagittarius", "Spriggan" }
},
{
"Light",
new List<string> { "Alpha", "Lich", "Odin", "Phoenix", "Raiden", "Shiva", "Twintania", "Zodiark" }
},
{
"Materia",
new List<string> { "Bismarck", "Ravana", "Sephirot", "Sophia", "Zurvan" }
},
{
"Elemental",
new List<string> { "Aegis", "Atomos", "Carbuncle", "Garuda", "Gungnir", "Kujata", "Tonberry", "Typhon" }
},
{
"Gaia",
new List<string> { "Alexander", "Bahamut", "Durandal", "Fenrir", "Ifrit", "Ridill", "Tiamat", "Ultima" }
},
{
"Mana",
new List<string> { "Anima", "Asura", "Chocobo", "Hades", "Ixion", "Masamune", "Pandaemonium", "Titan" }
},
{
"Meteor",
new List<string> { "Belias", "Mandragora", "Ramuh", "Shinryu", "Unicorn", "Valefor", "Yojimbo", "Zeromus" }
}
};
private string selectedDataCenter = "";
private string selectedWorld = "";
private void DrawAlliedSocietyTab()
{
ImGui.TextColored(in colorSecondary, "Allied Society Rotation");
ImGui.Separator();
if (ImGui.Button("Configure Priorities"))
{
alliedSocietyPriorityWindow.IsOpen = true;
}
ImGui.SameLine();
ImGui.TextDisabled("(Use Up/Down buttons to reorder)");
ImGui.Spacing();
ImGui.Separator();
ImGui.TextColored(in colorSecondary, "Quest Selection Mode");
AlliedSocietyConfiguration config = plugin.Configuration.AlliedSociety.RotationConfig;
bool modeChanged = false;
if (ImGui.RadioButton("Only 3 Quests per Society", config.QuestMode == AlliedSocietyQuestMode.OnlyThreePerSociety))
{
config.QuestMode = AlliedSocietyQuestMode.OnlyThreePerSociety;
modeChanged = true;
}
if (ImGui.RadioButton("All Available Quests (until 0 allowances)", config.QuestMode == AlliedSocietyQuestMode.AllAvailableQuests))
{
config.QuestMode = AlliedSocietyQuestMode.AllAvailableQuests;
modeChanged = true;
}
if (modeChanged)
{
plugin.Configuration.Save();
}
ImGui.Spacing();
ImGui.Separator();
List<string> selectedCharacters = (from kvp in characterSelection
where kvp.Value
select kvp.Key).ToList();
ImGui.TextColored(in colorSecondary, "Rotation Control");
if (alliedSocietyRotationService.IsRotationActive)
{
if (ImGui.Button("Stop Rotation", new Vector2(150f, 30f)))
{
alliedSocietyRotationService.StopRotation();
}
ImGui.SameLine();
Vector4 col = ImGuiColors.DalamudYellow;
ImU8String text = new ImU8String(18, 1);
text.AppendLiteral("Running... Phase: ");
text.AppendFormatted(alliedSocietyRotationService.CurrentPhase);
ImGui.TextColored(in col, text);
ImU8String text2 = new ImU8String(19, 1);
text2.AppendLiteral("Current Character: ");
text2.AppendFormatted(alliedSocietyRotationService.CurrentCharacterId);
ImGui.Text(text2);
}
else
{
if (ImGui.Button("Start Rotation", new Vector2(150f, 30f)))
{
if (selectedCharacters.Count == 0)
{
ImGui.OpenPopup("NoCharactersSelected");
}
else
{
alliedSocietyRotationService.StartRotation(selectedCharacters);
}
}
if (ImGui.BeginPopup("NoCharactersSelected"))
{
ImGui.Text("Please select at least one character from the Characters tab.");
if (ImGui.Button("OK", new Vector2(120f, 0f)))
{
ImGui.CloseCurrentPopup();
}
ImGui.EndPopup();
}
}
ImGui.Spacing();
ImGui.Separator();
ref readonly Vector4 col2 = ref colorSecondary;
ImU8String text3 = new ImU8String(28, 1);
text3.AppendLiteral("Character Status (");
text3.AppendFormatted(selectedCharacters.Count);
text3.AppendLiteral(" selected)");
ImGui.TextColored(in col2, text3);
if (selectedCharacters.Count > 0 && ImGui.BeginTable("AlliedSocietyStatusTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg))
{
ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthFixed, 150f);
ImGui.TableSetupColumn("Allowances", ImGuiTableColumnFlags.WidthFixed, 100f);
ImGui.TableHeadersRow();
foreach (string character in selectedCharacters)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
if (character == alliedSocietyRotationService.CurrentCharacterId)
{
ImGui.TextColored(ImGuiColors.DalamudYellow, character);
}
else
{
ImGui.Text(character);
}
ImGui.TableNextColumn();
AlliedSocietyCharacterStatus status = (plugin.Configuration.AlliedSociety.CharacterStatuses.ContainsKey(character) ? plugin.Configuration.AlliedSociety.CharacterStatuses[character] : new AlliedSocietyCharacterStatus
{
CharacterId = character
});
ImGui.TextColored((status.Status == AlliedSocietyRotationStatus.Complete) ? ImGuiColors.HealerGreen : ImGuiColors.DalamudWhite, status.Status.ToString());
ImGui.TableNextColumn();
ImGui.Text("-");
}
ImGui.EndTable();
}
else if (selectedCharacters.Count == 0)
{
ImGui.TextColored(ImGuiColors.DalamudGrey, "No characters selected. Please select characters in the Characters tab.");
}
}
public NewMainWindow(Plugin plugin, AutoRetainerIPC autoRetainerIpc, QuestTrackingService questTrackingService, QuestRotationExecutionService questRotationService, EventQuestExecutionService eventQuestService, AlliedSocietyRotationService alliedSocietyRotationService, AlliedSocietyPriorityWindow alliedSocietyPriorityWindow, DataCenterService dataCenterService, MSQProgressionService msqProgressionService, IPluginLog log, IUiBuilder uiBuilder, IDataManager dataManager)
: base("Questionable Companion##NewMainWindow", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoBackground)
{
this.plugin = plugin;
this.autoRetainerIpc = autoRetainerIpc;
this.questTrackingService = questTrackingService;
this.questRotationService = questRotationService;
this.eventQuestService = eventQuestService;
this.alliedSocietyRotationService = alliedSocietyRotationService;
this.alliedSocietyPriorityWindow = alliedSocietyPriorityWindow;
this.dataCenterService = dataCenterService;
this.msqProgressionService = msqProgressionService;
this.log = log;
this.uiBuilder = uiBuilder;
this.dataManager = dataManager;
base.SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(900f, 600f),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
};
base.BgAlpha = 0f;
try
{
dataCenterService.InitializeWorldMapping();
}
catch (Exception ex)
{
log.Error("[NewMainWindow] Failed to initialize data center mapping: " + ex.Message);
}
initialLoadStartTime = DateTime.Now;
log.Information("[NewMainWindow] Delayed character loading started (will retry with exponential backoff)");
Random random = new Random();
for (int i = 0; i < 80; i++)
{
Vector4 particleColor = random.Next(3) switch
{
0 => colorPrimary,
1 => colorSecondary,
_ => colorAccent,
};
particles.Add(new Particle
{
Position = new Vector2(random.Next(0, 900), random.Next(0, 600)),
Velocity = new Vector2((float)(random.NextDouble() - 0.5) * 25f, (float)(random.NextDouble() - 0.5) * 25f),
Size = (float)random.NextDouble() * 3f + 1f,
Alpha = (float)random.NextDouble() * 0.5f + 0.2f,
Color = particleColor
});
}
}
public void Dispose()
{
}
public override void Draw()
{
float deltaTime = ImGui.GetIO().DeltaTime;
animTime += deltaTime;
particleTime += deltaTime * 0.5f;
glowPulse = (MathF.Sin(animTime * 2f) + 1f) * 0.5f;
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
Vector2 windowSize = ImGui.GetWindowSize();
Vector2 windowPos = ImGui.GetWindowPos();
float sidebarWidth = 200f;
float titleBarHeight = 30f;
if (isMinimized)
{
float minHeight = titleBarHeight + 5f;
base.SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(400f, minHeight),
MaximumSize = new Vector2(float.MaxValue, minHeight)
};
DrawCustomTitleBar(drawList, windowPos, windowSize, titleBarHeight);
ImGui.SetWindowSize(new Vector2(windowSize.X, minHeight));
return;
}
base.SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(900f, 600f),
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
};
TryInitialCharacterLoad();
DrawGradientBackground();
DrawAnimatedParticles(drawList, windowPos, windowSize, deltaTime);
DrawScanningLine(drawList, windowPos, windowSize);
DrawCustomTitleBar(drawList, windowPos, windowSize, titleBarHeight);
DrawSemiTransparentBackgrounds(windowPos + new Vector2(0f, titleBarHeight), windowSize - new Vector2(0f, titleBarHeight), sidebarWidth);
ImGui.SetCursorPos(new Vector2(0f, titleBarHeight));
DrawSidebar(sidebarWidth, windowSize.Y - titleBarHeight);
ImGui.SameLine();
DrawContentArea(windowSize.X - sidebarWidth - 20f, windowSize.Y - titleBarHeight);
DrawWorldSelectionDialogs();
}
private void RefreshCharacterList(bool forceIpcCheck = false)
{
try
{
log.Debug($"[NewMainWindow] RefreshCharacterList called (forceIpcCheck: {forceIpcCheck})");
if (forceIpcCheck)
{
log.Debug("[NewMainWindow] Forcing IPC availability check...");
autoRetainerIpc.TryReinitialize();
}
if (!autoRetainerIpc.IsAvailable)
{
log.Warning("[NewMainWindow] AutoRetainer IPC not available during character refresh");
return;
}
registeredCharacters = autoRetainerIpc.GetRegisteredCharacters();
lastCharacterRefresh = DateTime.Now;
log.Information($"[NewMainWindow] Loaded {registeredCharacters.Count} characters from AutoRetainer");
if (registeredCharacters.Count > 0)
{
initialCharacterLoadComplete = true;
log.Information("[NewMainWindow] ✅ Initial character load successful");
}
charactersByDataCenter = dataCenterService.GroupCharactersByDataCenter(registeredCharacters);
availableWorlds = (from w in (from c in registeredCharacters
select c.Split('@') into parts
where parts.Length > 1
select parts[1]).Distinct()
orderby w
select w).ToList();
foreach (string character in registeredCharacters)
{
if (!characterSelection.ContainsKey(character))
{
characterSelection[character] = false;
}
}
}
catch (Exception ex)
{
log.Error("[NewMainWindow] RefreshCharacterList failed: " + ex.Message);
log.Error("[NewMainWindow] Stack trace: " + ex.StackTrace);
}
}
private void TryInitialCharacterLoad()
{
if (initialCharacterLoadComplete || characterLoadAttempts >= 5)
{
return;
}
double elapsedSeconds = (DateTime.Now - initialLoadStartTime).TotalSeconds;
int requiredDelay = ((characterLoadAttempts < retryDelaysSeconds.Length) ? retryDelaysSeconds[characterLoadAttempts] : retryDelaysSeconds[^1]);
if (!(elapsedSeconds < (double)requiredDelay))
{
characterLoadAttempts++;
log.Information($"[NewMainWindow] Character load attempt {characterLoadAttempts}/{5} (after {elapsedSeconds:F1}s)");
RefreshCharacterList(forceIpcCheck: true);
if (!initialCharacterLoadComplete)
{
initialLoadStartTime = DateTime.Now;
log.Debug($"[NewMainWindow] Next retry in {((characterLoadAttempts < retryDelaysSeconds.Length) ? retryDelaysSeconds[characterLoadAttempts] : retryDelaysSeconds[^1])}s");
}
}
}
private void TryWarningMenuQuestionableCheck()
{
if (warningMenuRetryCycleComplete)
{
return;
}
if (plugin.QuestionableIPC.TryEnsureAvailableSilent() && plugin.QuestionableIPC.ValidateFeatureCompatibility())
{
warningMenuRetryCycleComplete = true;
selectedTab = 5;
log.Information("[NewMainWindow] Warning Menu retry cycle complete - Questionable IPC is now available");
}
else if (warningMenuRetryAttempts < 4)
{
if (warningMenuRetryAttempts == 0)
{
warningMenuRetryStartTime = DateTime.Now;
}
double elapsedSeconds = (DateTime.Now - warningMenuRetryStartTime).TotalSeconds;
int requiredDelay = ((warningMenuRetryAttempts < warningMenuRetryDelaysSeconds.Length) ? warningMenuRetryDelaysSeconds[warningMenuRetryAttempts] : warningMenuRetryDelaysSeconds[^1]);
if (!(elapsedSeconds < (double)requiredDelay))
{
warningMenuRetryAttempts++;
log.Debug($"[NewMainWindow] Warning Menu retry attempt {warningMenuRetryAttempts}/{4} (after {elapsedSeconds:F1}s)");
warningMenuRetryStartTime = DateTime.Now;
}
}
}
private void DrawSidebar(float width, float height)
{
using ImRaii.IEndObject child = ImRaii.Child("Sidebar", new Vector2(width, height - 10f), border: false);
if (!child.Success)
{
return;
}
DrawSidebarCategory("CHARACTERS", ref charactersExpanded, delegate
{
DrawSidebarItem("All", 0, registeredCharacters.Count);
DrawSidebarItem("EU", 1, GetCharacterCountForDC("EU"));
DrawSidebarItem("NA", 2, GetCharacterCountForDC("NA"));
DrawSidebarItem("JP", 3, GetCharacterCountForDC("JP"));
DrawSidebarItem("OCE", 4, GetCharacterCountForDC("OCE"));
});
ImGuiHelpers.ScaledDummy(5f);
DrawSidebarCategory("MENU", ref menuExpanded, delegate
{
if (plugin.QuestionableIPC.ValidateFeatureCompatibility())
{
DrawSidebarItem("Stop Points", 5, 0);
DrawSidebarItem("Allied Society", 10, 0);
DrawSidebarItem("Event Quest", 6, 0);
DrawSidebarItem("MSQ Progression", 7, 0);
DrawSidebarItem("Data Center Travel", 8, 0);
DrawSidebarItem("Settings", 9, 0);
}
else
{
DrawSidebarItem("Warning", 11, 0);
}
});
}
private void DrawSidebarCategory(string name, ref bool expanded, System.Action drawItems)
{
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float itemWidth = ImGui.GetContentRegionAvail().X - 10f;
float itemHeight = 30f;
Vector4 headerBg = new Vector4(colorPrimary.X * 0.25f, colorPrimary.Y * 0.25f, colorPrimary.Z * 0.25f, 0.7f);
uint borderColor = ImGui.ColorConvertFloat4ToU32(new Vector4(colorPrimary.X, colorPrimary.Y, colorPrimary.Z, 0.6f));
Vector2 boxStart = cursorScreenPos + new Vector2(5f, 0f);
drawList.AddRectFilled(boxStart, boxStart + new Vector2(itemWidth, itemHeight), ImGui.ColorConvertFloat4ToU32(headerBg), 6f);
drawList.AddRect(boxStart, boxStart + new Vector2(itemWidth, itemHeight), borderColor, 6f, ImDrawFlags.None, 1.5f);
string obj = (expanded ? "v" : ">");
ImGui.SetCursorScreenPos(boxStart + new Vector2(10f, 8f));
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.Text(obj);
ImGui.PopStyleColor();
ImGui.SameLine();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 1f);
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.Text(name);
ImGui.PopStyleColor();
if (ImGui.IsMouseHoveringRect(boxStart, boxStart + new Vector2(itemWidth, itemHeight)) && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
{
expanded = !expanded;
}
ImGui.Dummy(new Vector2(itemWidth + 10f, itemHeight));
ImGui.Spacing();
if (expanded)
{
ImGui.Indent(10f);
drawItems();
ImGui.Unindent(10f);
ImGui.Spacing();
}
}
private void DrawSidebarItem(string label, int tabIndex, int count)
{
bool isSelected = selectedTab == tabIndex;
string obj = ((count > 0) ? $"{label} ({count})" : label);
if (isSelected)
{
ImGui.PushStyleColor(ImGuiCol.Header, colorPrimary);
}
else
{
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, colorHover);
}
if (ImGui.Selectable(obj, isSelected, ImGuiSelectableFlags.None, new Vector2(0f, 22f)))
{
selectedTab = tabIndex;
if (tabIndex <= 4)
{
selectedDCFilter = tabIndex;
}
}
ImGui.PopStyleColor();
}
private int GetCharacterCountForDC(string dcName)
{
if (charactersByDataCenter.TryGetValue(dcName, out List<string> chars))
{
return chars.Count;
}
return 0;
}
private void DrawCustomTitleBar(ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, float height)
{
uint leftColor = ImGui.ColorConvertFloat4ToU32(new Vector4(colorPrimary.X * 0.4f, colorPrimary.Y * 0.4f, colorPrimary.Z * 0.4f, 1f));
uint rightColor = ImGui.ColorConvertFloat4ToU32(new Vector4(colorSecondary.X * 0.3f, colorSecondary.Y * 0.3f, colorSecondary.Z * 0.3f, 1f));
drawList.AddRectFilledMultiColor(windowPos, windowPos + new Vector2(windowSize.X, height), leftColor, rightColor, rightColor, leftColor);
Vector2 titlePos = windowPos + new Vector2(10f, 7f);
drawList.AddText(titlePos, ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 0.9f)), "Questionable Companion V.1.0.3");
Vector2 minimizeButtonPos = windowPos + new Vector2(windowSize.X - 60f, 3f);
Vector2 minimizeButtonSize = new Vector2(24f, 24f);
if (ImGui.IsMouseHoveringRect(minimizeButtonPos, minimizeButtonPos + minimizeButtonSize))
{
drawList.AddRectFilled(minimizeButtonPos, minimizeButtonPos + minimizeButtonSize, ImGui.ColorConvertFloat4ToU32(new Vector4(0.4f, 0.4f, 0.4f, 0.8f)), 4f);
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
{
isMinimized = !isMinimized;
}
}
drawList.AddText(minimizeButtonPos + new Vector2(8f, 2f), ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 1f)), "_");
Vector2 closeButtonPos = windowPos + new Vector2(windowSize.X - 30f, 3f);
Vector2 closeButtonSize = new Vector2(24f, 24f);
if (ImGui.IsMouseHoveringRect(closeButtonPos, closeButtonPos + closeButtonSize))
{
drawList.AddRectFilled(closeButtonPos, closeButtonPos + closeButtonSize, ImGui.ColorConvertFloat4ToU32(new Vector4(0.8f, 0.2f, 0.2f, 0.8f)), 4f);
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
{
base.IsOpen = false;
}
}
drawList.AddText(closeButtonPos + new Vector2(7f, 2f), ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 1f)), "X");
}
private void DrawSemiTransparentBackgrounds(Vector2 windowPos, Vector2 windowSize, float sidebarWidth)
{
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
drawList.AddRectFilled(col: ImGui.ColorConvertFloat4ToU32(new Vector4(0.12f, 0.12f, 0.15f, 0.85f)), pMin: windowPos, pMax: windowPos + new Vector2(sidebarWidth, windowSize.Y));
float gradientWidth = 20f;
for (int i = 0; i < 20; i++)
{
float alpha = (float)i / 20f;
drawList.AddRectFilled(col: ImGui.ColorConvertFloat4ToU32(new Vector4(0.12f + 0.030000009f * alpha, 0.12f + 0.030000009f * alpha, 0.15f + 0.030000001f * alpha, 0.85f - 0.05f * alpha)), pMin: windowPos + new Vector2(sidebarWidth + (float)i, 0f), pMax: windowPos + new Vector2(sidebarWidth + (float)i + 1f, windowSize.Y));
}
drawList.AddRectFilled(col: ImGui.ColorConvertFloat4ToU32(new Vector4(0.15f, 0.15f, 0.18f, 0.8f)), pMin: windowPos + new Vector2(sidebarWidth + gradientWidth, 0f), pMax: windowPos + windowSize);
}
private void DrawGradientBackground()
{
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
Vector2 windowPos = ImGui.GetWindowPos();
Vector2 windowSize = ImGui.GetWindowSize();
float extend = 2f;
float colorShift = MathF.Sin(animTime * 0.3f) * 0.15f;
float colorShift2 = MathF.Cos(animTime * 0.25f) * 0.12f;
uint blue = ImGui.ColorConvertFloat4ToU32(new Vector4(0.47843137f + colorShift, 35f / 51f + colorShift2, 0.8784314f - colorShift * 0.5f, 1f));
uint gold = ImGui.ColorConvertFloat4ToU32(new Vector4(0.9490196f - colorShift2 * 0.5f, 0.76862746f + colorShift, 33f / 85f + colorShift2, 1f));
uint brown = ImGui.ColorConvertFloat4ToU32(new Vector4(62f / 85f + colorShift2, 0.29411766f - colorShift, 0.18431373f + colorShift * 0.8f, 1f));
drawList.AddRectFilledMultiColor(windowPos - new Vector2(extend, extend), windowPos + windowSize + new Vector2(extend, extend), blue, gold, brown, blue);
}
private void DrawContentArea(float width, float height)
{
using ImRaii.IEndObject child = ImRaii.Child("ContentArea", new Vector2(width, height - 10f), border: false);
if (child.Success)
{
switch (selectedTab)
{
case 0:
case 1:
case 2:
case 3:
case 4:
DrawCharactersTab();
break;
case 5:
DrawStopPointsTab();
break;
case 6:
DrawEventQuestTab();
break;
case 7:
DrawMSQProgressionTab();
break;
case 8:
DrawDCTravelTab();
break;
case 9:
DrawSettingsTab();
break;
case 10:
DrawAlliedSocietyTab();
break;
case 11:
DrawWarningTab();
break;
}
}
}
private void DrawAnimatedParticles(ImDrawListPtr drawList, Vector2 pos, Vector2 size, float deltaTime)
{
foreach (Particle p in particles)
{
if (p.Position.X < pos.X)
{
p.Position.X = pos.X + size.X;
}
if (p.Position.X > pos.X + size.X)
{
p.Position.X = pos.X;
}
if (p.Position.Y < pos.Y)
{
p.Position.Y = pos.Y + size.Y;
}
if (p.Position.Y > pos.Y + size.Y)
{
p.Position.Y = pos.Y;
}
float colorPulse = 0.8f + glowPulse * 0.2f;
float alpha = p.Alpha * (0.6f + glowPulse * 0.4f);
uint color = ImGui.ColorConvertFloat4ToU32(new Vector4(p.Color.X * colorPulse, p.Color.Y * colorPulse, p.Color.Z * colorPulse, alpha));
float glowAlpha = alpha * 0.3f;
uint glowColor = ImGui.ColorConvertFloat4ToU32(new Vector4(p.Color.X * colorPulse, p.Color.Y * colorPulse, p.Color.Z * colorPulse, glowAlpha));
drawList.AddCircleFilled(p.Position, p.Size * 2f, glowColor, 12);
drawList.AddCircleFilled(p.Position, p.Size, color, 8);
}
}
private void DrawScanningLine(ImDrawListPtr drawList, Vector2 pos, Vector2 size)
{
float scan1Y = pos.Y + animTime * 0.3f % 1f * size.Y;
float scan2Y = pos.Y + (animTime * 0.25f + 0.33f) % 1f * size.Y;
float scan3Y = pos.Y + (animTime * 0.2f + 0.66f) % 1f * size.Y;
uint blueColor = ImGui.ColorConvertFloat4ToU32(new Vector4(colorPrimary.X, colorPrimary.Y, colorPrimary.Z, 0.15f * glowPulse));
uint goldColor = ImGui.ColorConvertFloat4ToU32(new Vector4(colorSecondary.X, colorSecondary.Y, colorSecondary.Z, 0.15f * glowPulse));
uint redColor = ImGui.ColorConvertFloat4ToU32(new Vector4(colorAccent.X, colorAccent.Y, colorAccent.Z, 0.15f * glowPulse));
drawList.AddLine(new Vector2(pos.X, scan1Y), new Vector2(pos.X + size.X, scan1Y), blueColor, 2f);
drawList.AddLine(new Vector2(pos.X, scan2Y), new Vector2(pos.X + size.X, scan2Y), goldColor, 2f);
drawList.AddLine(new Vector2(pos.X, scan3Y), new Vector2(pos.X + size.X, scan3Y), redColor, 2f);
}
private void DrawDCTravelTab()
{
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Data Center Travel Configuration");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
Configuration config = plugin.Configuration;
if (string.IsNullOrEmpty(selectedDataCenter) && !string.IsNullOrEmpty(config.DCTravelDataCenter))
{
selectedDataCenter = config.DCTravelDataCenter;
}
if (string.IsNullOrEmpty(selectedWorld) && !string.IsNullOrEmpty(config.DCTravelTargetWorld))
{
selectedWorld = config.DCTravelTargetWorld;
}
if (string.IsNullOrEmpty(selectedDataCenter))
{
selectedDataCenter = dataCenterWorlds.Keys.First();
}
if (string.IsNullOrEmpty(selectedWorld))
{
List<string> worlds = dataCenterWorlds.GetValueOrDefault(selectedDataCenter) ?? new List<string>();
if (worlds.Count > 0)
{
selectedWorld = worlds[0];
}
}
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextWrapped("Configure automatic Data Center travel for quest rotation. The plugin will travel to the specified Data Center and World before starting quests.");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(15f);
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Select Data Center:");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
ImGui.SetNextItemWidth(350f);
ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(0.15f, 0.15f, 0.18f, 0.9f));
ImGui.PushStyleColor(ImGuiCol.FrameBgHovered, new Vector4(colorPrimary.X * 0.3f, colorPrimary.Y * 0.3f, colorPrimary.Z * 0.3f, 0.9f));
if (ImGui.BeginCombo("##DataCenterCombo", selectedDataCenter))
{
foreach (string dc in dataCenterWorlds.Keys.OrderBy((string k) => k))
{
bool isSelected = selectedDataCenter == dc;
if (ImGui.Selectable(dc, isSelected))
{
selectedDataCenter = dc;
List<string> worlds2 = dataCenterWorlds.GetValueOrDefault(selectedDataCenter) ?? new List<string>();
if (worlds2.Count > 0)
{
selectedWorld = worlds2[0];
}
}
if (isSelected)
{
ImGui.SetItemDefaultFocus();
}
}
ImGui.EndCombo();
}
ImGuiHelpers.ScaledDummy(15f);
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Select World:");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
List<string> availableWorlds = dataCenterWorlds.GetValueOrDefault(selectedDataCenter) ?? new List<string>();
ImGui.SetNextItemWidth(350f);
ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(0.15f, 0.15f, 0.18f, 0.9f));
ImGui.PushStyleColor(ImGuiCol.FrameBgHovered, new Vector4(colorPrimary.X * 0.3f, colorPrimary.Y * 0.3f, colorPrimary.Z * 0.3f, 0.9f));
if (ImGui.BeginCombo("##WorldCombo", selectedWorld))
{
foreach (string world in availableWorlds)
{
bool isSelected2 = selectedWorld == world;
if (ImGui.Selectable(world, isSelected2))
{
selectedWorld = world;
}
if (isSelected2)
{
ImGui.SetItemDefaultFocus();
}
}
ImGui.EndCombo();
}
ImGui.PopStyleColor(2);
ImGuiHelpers.ScaledDummy(15f);
LifestreamIPC lifestreamIPC = Plugin.Instance?.LifestreamIPC;
if (lifestreamIPC != null && !lifestreamIPC.IsAvailable)
{
lifestreamIPC.ForceCheckAvailability();
}
int num;
if (lifestreamIPC == null)
{
num = 0;
}
else
{
num = (lifestreamIPC.IsAvailable ? 1 : 0);
if (num != 0)
{
goto IL_0421;
}
}
ImGui.BeginDisabled();
goto IL_0421;
IL_0421:
bool enableDCTravel = config.EnableDCTravel;
if (ImGui.Checkbox("Enable Data Center Travel", ref enableDCTravel))
{
config.EnableDCTravel = enableDCTravel;
config.Save();
}
if (num == 0)
{
ImGui.EndDisabled();
}
ImGui.SameLine();
DrawInfoIcon("Automatically travels to the specified Data Center and World before starting quest rotation.\nRequires Lifestream plugin to be installed and configured.\nImpact: Characters will travel to target DC/World at rotation start.");
if (num == 0)
{
ImGuiHelpers.ScaledDummy(5f);
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.5f, 0f, 1f));
ImGui.TextWrapped("Lifestream plugin is not available! DC Travel requires Lifestream to be installed and enabled.");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
if (ImGui.Button("Check Lifestream Again") && lifestreamIPC != null)
{
bool result = lifestreamIPC.ForceCheckAvailability();
log.Information($"[DCTravel UI] Manual Lifestream check result: {result}");
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Manually check if Lifestream is available.\nCheck the logs for detailed information.");
}
}
ImGuiHelpers.ScaledDummy(20f);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(15f);
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Current Configuration:");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
ImGui.Indent(10f);
ImU8String text = new ImU8String(13, 0);
text.AppendLiteral("Data Center: ");
ImGui.TextUnformatted(text);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted((config.DCTravelDataCenter.Length > 0) ? config.DCTravelDataCenter : "Not Set");
ImGui.PopStyleColor();
ImU8String text2 = new ImU8String(14, 0);
text2.AppendLiteral("Target World: ");
ImGui.TextUnformatted(text2);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted((config.DCTravelTargetWorld.Length > 0) ? config.DCTravelTargetWorld : "Not Set");
ImGui.PopStyleColor();
ImU8String text3 = new ImU8String(8, 0);
text3.AppendLiteral("Status: ");
ImGui.TextUnformatted(text3);
ImGui.SameLine();
if (config.EnableDCTravel)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Enabled");
ImGui.PopStyleColor();
}
else
{
ImGui.PushStyleColor(ImGuiCol.Text, colorAccent);
ImGui.TextUnformatted("Disabled");
ImGui.PopStyleColor();
}
ImGui.Unindent(10f);
ImGuiHelpers.ScaledDummy(20f);
ImGui.PushStyleColor(ImGuiCol.Button, colorPrimary);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorSecondary);
if (ImGui.Button("Apply", new Vector2(120f, 30f)))
{
config.DCTravelDataCenter = selectedDataCenter;
config.DCTravelTargetWorld = selectedWorld;
config.Save();
log.Information("[DCTravel] Configuration saved: " + selectedDataCenter + " -> " + selectedWorld);
}
ImGui.PopStyleColor(2);
ImGui.SameLine();
if (ImGui.Button("Cancel", new Vector2(120f, 30f)))
{
selectedDataCenter = config.DCTravelDataCenter;
selectedWorld = config.DCTravelTargetWorld;
if (string.IsNullOrEmpty(selectedDataCenter))
{
selectedDataCenter = dataCenterWorlds.Keys.First();
}
if (string.IsNullOrEmpty(selectedWorld))
{
List<string> worlds3 = dataCenterWorlds.GetValueOrDefault(selectedDataCenter) ?? new List<string>();
if (worlds3.Count > 0)
{
selectedWorld = worlds3[0];
}
}
}
ImGuiHelpers.ScaledDummy(10f);
if (!config.EnableDCTravel)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextWrapped("Note: Data Center Travel is currently disabled. Enable it above to use this feature.");
ImGui.PopStyleColor();
}
}
private void DrawSettingsTabFull()
{
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Plugin Settings");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
using ImRaii.IEndObject child = ImRaii.Child("SettingsScrollArea", new Vector2(0f, 0f), border: false, ImGuiWindowFlags.None);
if (!child.Success)
{
return;
}
Configuration config = plugin.Configuration;
DrawSettingSection("Submarine Management", delegate
{
config.EnableSubmarineCheck = DrawSettingWithInfo("Enable Submarine Monitoring", config.EnableSubmarineCheck, "Automatically monitors submarines and pauses quest rotation when submarines are ready.\nPrevents quest progression while submarines need attention.\nImpact: Rotation will pause when submarines are detected.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
if (config.EnableSubmarineCheck)
{
ImGui.Indent();
int v = config.SubmarineCheckInterval;
if (ImGui.SliderInt("Check Interval (seconds)", ref v, 30, 300))
{
config.SubmarineCheckInterval = v;
config.Save();
}
DrawInfoIcon("How often to check for submarine status.\nLower values = more frequent checks but higher CPU usage.");
int v2 = config.SubmarineReloginCooldown;
if (ImGui.SliderInt("Cooldown after Relog (seconds)", ref v2, 60, 300))
{
config.SubmarineReloginCooldown = v2;
config.Save();
}
DrawInfoIcon("Time to wait after character switch before checking submarines again.");
int v3 = config.SubmarineWaitTime;
if (ImGui.SliderInt("Wait time before submarine (seconds)", ref v3, 10, 120))
{
config.SubmarineWaitTime = v3;
config.Save();
}
DrawInfoIcon("Delay before starting submarine operations after detection.");
ImGui.Unindent();
}
}, config.EnableSubmarineCheck);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("AutoRetainer Post Process Event Quests", delegate
{
config.RunEventQuestsOnARPostProcess = DrawSettingWithInfo("Run Event Quests on AR Post Process", config.RunEventQuestsOnARPostProcess, "AUTO-DETECTION: Automatically detects and runs active Event Quests when AutoRetainer completes a character.\nEvent Quests are detected via Questionable IPC (same as manual Event Quest tab).\nAll prerequisites will be automatically resolved and executed.\nAutoRetainer will wait until all Event Quests are completed before proceeding.\nImpact: Extends AR post-process time but ensures Event Quests are completed.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
if (config.RunEventQuestsOnARPostProcess)
{
ImGui.Indent();
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.4f, 0.8f, 0.4f, 1f));
ImGui.TextUnformatted("Auto-Detection Enabled");
ImGui.PopStyleColor();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextWrapped("Event Quests will be automatically detected from Questionable when AR Post Process starts. No manual configuration needed - just enable this setting and the plugin will handle the rest!");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
int v = config.EventQuestPostProcessTimeoutMinutes;
if (ImGui.SliderInt("Timeout (minutes)", ref v, 10, 60))
{
config.EventQuestPostProcessTimeoutMinutes = v;
config.Save();
}
DrawInfoIcon("Maximum time to wait for Event Quests to complete.\nAfter timeout, AR will proceed with next character.");
ImGui.Unindent();
}
}, config.RunEventQuestsOnARPostProcess);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Dungeon Automation", delegate
{
bool enableAutoDutyUnsynced = config.EnableAutoDutyUnsynced;
config.EnableAutoDutyUnsynced = DrawSettingWithInfo("Enable Auto Duty (Unsynced)", config.EnableAutoDutyUnsynced, "Automatically handles dungeon entries and party formation.\nUses AutoDuty plugin for unsynced dungeon runs.\nImpact: Dungeons will be automated during quest rotation.");
if (config.EnableAutoDutyUnsynced != enableAutoDutyUnsynced)
{
config.Save();
plugin.GetDungeonAutomation()?.SetDutyModeBasedOnConfig();
}
if (config.EnableAutoDutyUnsynced)
{
ImGui.Indent();
int v = config.AutoDutyPartySize;
if (ImGui.SliderInt("Minimum Party Size", ref v, 1, 4))
{
config.AutoDutyPartySize = v;
config.Save();
}
DrawInfoIcon("Minimum number of party members required before entering dungeon.");
int v2 = config.AutoDutyMaxWaitForParty;
if (ImGui.SliderInt("Max Wait for Party (seconds)", ref v2, 10, 120))
{
config.AutoDutyMaxWaitForParty = v2;
config.Save();
}
DrawInfoIcon("Maximum time to wait for party members before timing out.");
int v3 = config.AutoDutyReInviteInterval;
if (ImGui.SliderInt("Re-Invite Interval (seconds)", ref v3, 5, 60))
{
config.AutoDutyReInviteInterval = v3;
config.Save();
}
DrawInfoIcon("How often to re-send party invites if members don't join.");
ImGui.Unindent();
}
}, config.EnableAutoDutyUnsynced);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Multi-Client Role", delegate
{
ImGui.TextWrapped("Select your role for multi-client features (party management, chauffeur mode):");
ImGuiHelpers.ScaledDummy(5f);
int num = 0;
if (config.IsQuester)
{
num = 1;
}
else if (config.IsHighLevelHelper)
{
num = 2;
}
if (ImGui.RadioButton("None", num == 0))
{
config.IsQuester = false;
config.IsHighLevelHelper = false;
config.Save();
}
ImGui.SameLine();
DrawInfoIcon("No multi-client features enabled");
if (ImGui.RadioButton("Quester", num == 1))
{
config.IsQuester = true;
config.IsHighLevelHelper = false;
config.Save();
}
ImGui.SameLine();
DrawInfoIcon("This client will quest and invite helpers for dungeons");
if (ImGui.RadioButton("High-Level Helper", num == 2))
{
config.IsQuester = false;
config.IsHighLevelHelper = true;
Plugin.Framework.RunOnFrameworkThread(delegate
{
Plugin.Instance?.GetHelperManager()?.AnnounceIfHelper();
});
config.Save();
}
ImGui.SameLine();
DrawInfoIcon("This client will help with dungeons.\nAutoDuty starts/stops automatically on duty enter/leave");
ImGuiHelpers.ScaledDummy(10f);
if (config.IsQuester)
{
ImGui.Separator();
ImGuiHelpers.ScaledDummy(5f);
ImGui.TextColored(in colorPrimary, "Auto-Discovered Helpers");
ImGui.TextWrapped("Helpers are automatically discovered via IPC when they have 'I'm a High-Level Helper' enabled:");
ImGuiHelpers.ScaledDummy(5f);
List<(string, ushort)> availableHelpers = plugin.GetAvailableHelpers();
if (availableHelpers.Count != 0)
{
Vector4 col = new Vector4(0.2f, 1f, 0.2f, 1f);
ImU8String text = new ImU8String(21, 1);
text.AppendFormatted(availableHelpers.Count);
text.AppendLiteral(" helper(s) available:");
ImGui.TextColored(in col, text);
ImGuiHelpers.ScaledDummy(5f);
ImGui.TextUnformatted("Preferred Helper for Chauffeur:");
ImGui.SetNextItemWidth(250f);
List<string> list = new List<string> { "Auto (First Available)" };
foreach (var item5 in availableHelpers)
{
string item = item5.Item1;
ushort item2 = item5.Item2;
ExcelSheet<World> excelSheet = Plugin.DataManager.GetExcelSheet<World>();
string text2 = "Unknown";
if (excelSheet != null)
{
foreach (World current2 in excelSheet)
{
if (current2.RowId == item2)
{
text2 = current2.Name.ExtractText();
break;
}
}
}
list.Add(item + "@" + text2);
}
string text3 = (string.IsNullOrEmpty(config.PreferredHelper) ? "Auto (First Available)" : config.PreferredHelper);
if (ImGui.BeginCombo("##PreferredHelper", text3))
{
foreach (string current3 in list)
{
bool flag = text3 == current3;
if (ImGui.Selectable(current3, flag))
{
config.PreferredHelper = ((current3 == "Auto (First Available)") ? "" : current3);
config.Save();
}
if (flag)
{
ImGui.SetItemDefaultFocus();
}
}
ImGui.EndCombo();
}
ImGui.SameLine();
DrawInfoIcon("Select which helper to use for Chauffeur Mode.\n'Auto' will use the first available helper.");
if (!string.IsNullOrEmpty(config.PreferredHelper))
{
ImGuiHelpers.ScaledDummy(3f);
string text4 = (Plugin.Instance?.GetChauffeurMode())?.GetHelperStatus(config.PreferredHelper);
Vector4 col2;
ImU8String text5;
switch (text4)
{
case "Available":
col = new Vector4(0.2f, 1f, 0.2f, 1f);
goto IL_04f7;
case "Transporting":
col = new Vector4(1f, 0.8f, 0f, 1f);
goto IL_04f7;
case "InDungeon":
col = new Vector4(1f, 0.3f, 0.3f, 1f);
goto IL_04f7;
default:
col = colorSecondary;
goto IL_04f7;
case null:
{
ImGui.TextColored(in colorSecondary, "Helper Status: Unknown (waiting for update...)");
break;
}
IL_04f7:
col2 = col;
text5 = new ImU8String(15, 1);
text5.AppendLiteral("Helper Status: ");
text5.AppendFormatted(text4);
ImGui.TextColored(in col2, text5);
break;
}
}
ImGuiHelpers.ScaledDummy(5f);
ImGui.TextUnformatted("Available Helpers:");
ChauffeurModeService chauffeurModeService = Plugin.Instance?.GetChauffeurMode();
{
foreach (var item6 in availableHelpers)
{
string item3 = item6.Item1;
ushort item4 = item6.Item2;
ExcelSheet<World> excelSheet2 = Plugin.DataManager.GetExcelSheet<World>();
string text6 = "Unknown";
if (excelSheet2 != null)
{
foreach (World current5 in excelSheet2)
{
if (current5.RowId == item4)
{
text6 = current5.Name.ExtractText();
break;
}
}
}
string text7 = item3 + "@" + text6;
string text8 = chauffeurModeService?.GetHelperStatus(text7);
ImU8String text9 = new ImU8String(4, 1);
text9.AppendLiteral(" • ");
text9.AppendFormatted(text7);
ImGui.TextUnformatted(text9);
if (text8 != null)
{
ImGui.SameLine();
Vector4 col3 = text8 switch
{
"Available" => new Vector4(0.2f, 1f, 0.2f, 1f),
"Transporting" => new Vector4(1f, 0.8f, 0f, 1f),
"InDungeon" => new Vector4(1f, 0.3f, 0.3f, 1f),
_ => colorSecondary,
};
ImU8String text10 = new ImU8String(2, 1);
text10.AppendLiteral("[");
text10.AppendFormatted(text8);
text10.AppendLiteral("]");
ImGui.TextColored(in col3, text10);
}
}
return;
}
}
ImGui.TextColored(new Vector4(1f, 0.8f, 0.2f, 1f), "No helpers discovered yet");
ImGui.TextWrapped("Make sure helper clients are running with 'I'm a High-Level Helper' enabled.");
}
}, config.IsQuester || config.IsHighLevelHelper);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Chauffeur Mode", delegate
{
ImGui.TextWrapped("Multi-character transport system. Helper transports Quester to quest objectives using multi-seater mounts.");
ImGuiHelpers.ScaledDummy(5f);
if (!config.IsQuester && !config.IsHighLevelHelper)
{
ImGui.TextColored(new Vector4(1f, 0.8f, 0.2f, 1f), "Please select a role above to configure Chauffeur Mode");
}
else
{
bool v = config.ChauffeurModeEnabled;
if (ImGui.Checkbox("Enable Chauffeur Mode", ref v))
{
config.ChauffeurModeEnabled = v;
config.Save();
}
DrawInfoIcon("Enable automatic helper summoning for long-distance travel in non-flying zones");
if (config.ChauffeurModeEnabled)
{
ImGuiHelpers.ScaledDummy(5f);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(5f);
if (config.IsQuester)
{
ImGui.TextColored(in colorPrimary, "Quester Settings");
ImGuiHelpers.ScaledDummy(3f);
float v2 = config.ChauffeurDistanceThreshold;
ImGui.SetNextItemWidth(200f);
if (ImGui.SliderFloat("Distance Threshold (yalms)", ref v2, 50f, 300f, "%.0f"))
{
config.ChauffeurDistanceThreshold = v2;
config.Save();
}
DrawInfoIcon("Helper will be summoned when task is further than this distance\nand flying is not available in the zone");
ImU8String text = new ImU8String(15, 1);
text.AppendLiteral("Current: ");
text.AppendFormatted(config.ChauffeurDistanceThreshold, "F0");
text.AppendLiteral(" yalms");
ImGui.TextWrapped(text);
}
if (config.IsHighLevelHelper)
{
ImGui.TextColored(in colorPrimary, "Helper Settings");
ImGuiHelpers.ScaledDummy(3f);
Vector4 col = config.CurrentHelperStatus switch
{
HelperStatus.Available => new Vector4(0.2f, 1f, 0.2f, 1f),
HelperStatus.Transporting => new Vector4(1f, 0.8f, 0f, 1f),
HelperStatus.InDungeon => new Vector4(1f, 0.3f, 0.3f, 1f),
_ => colorSecondary,
};
string value = config.CurrentHelperStatus switch
{
HelperStatus.Available => "Available",
HelperStatus.Transporting => "Transporting",
HelperStatus.InDungeon => "In Dungeon",
_ => "Unknown",
};
ImU8String text2 = new ImU8String(8, 1);
text2.AppendLiteral("Status: ");
text2.AppendFormatted(value);
ImGui.TextColored(in col, text2);
ImGuiHelpers.ScaledDummy(3f);
if (!string.IsNullOrEmpty(config.AssignedQuester))
{
Vector4 col2 = new Vector4(0.2f, 1f, 0.2f, 1f);
ImU8String text3 = new ImU8String(18, 1);
text3.AppendLiteral("Assigned Quester: ");
text3.AppendFormatted(config.AssignedQuester);
ImGui.TextColored(in col2, text3);
ImGuiHelpers.ScaledDummy(3f);
}
else
{
ImGui.TextColored(in colorSecondary, "Assigned Quester: None");
ImGuiHelpers.ScaledDummy(3f);
}
float v3 = config.ChauffeurStopDistance;
ImGui.SetNextItemWidth(200f);
if (ImGui.SliderFloat("Stop Distance (yalms)", ref v3, 2f, 15f, "%.1f"))
{
config.ChauffeurStopDistance = v3;
config.Save();
}
DrawInfoIcon("How close you bring the quester to their destination\n(2-15 yalms, default: 5)");
ImU8String text4 = new ImU8String(15, 1);
text4.AppendLiteral("Current: ");
text4.AppendFormatted(config.ChauffeurStopDistance, "F1");
text4.AppendLiteral(" yalms");
ImGui.TextWrapped(text4);
ImGuiHelpers.ScaledDummy(5f);
List<(uint, string, byte)> list = (Plugin.Instance?.GetChauffeurMode())?.GetMultiSeaterMounts() ?? new List<(uint, string, byte)>();
if (list.Count == 0)
{
ImGui.TextColored(new Vector4(1f, 0.3f, 0.3f, 1f), "No multi-seater mounts found!");
ImGui.TextWrapped("Make sure you have unlocked at least one multi-seater mount.");
}
else
{
ImGui.TextWrapped("Select Multi-Seater Mount:");
ImGuiHelpers.ScaledDummy(3f);
int num = 0;
List<string> list2 = new List<string>();
for (int i = 0; i < list.Count; i++)
{
var (num2, value2, value3) = list[i];
list2.Add($"{value2} (Passengers: {value3})");
if (num2 == config.ChauffeurMountId)
{
num = i;
}
}
list2.Insert(0, "-- Not Selected --");
num = ((config.ChauffeurMountId != 0) ? (num + 1) : 0);
ImGui.SetNextItemWidth(300f);
if (ImGui.Combo((ImU8String)"##MountSelect", ref num, (ReadOnlySpan<string>)list2.ToArray(), list2.Count))
{
if (num == 0)
{
config.ChauffeurMountId = 0u;
}
else
{
(uint, string, byte) tuple2 = list[num - 1];
config.ChauffeurMountId = tuple2.Item1;
}
config.Save();
}
DrawInfoIcon("This mount will be used to transport the Quester");
if (config.ChauffeurMountId == 0)
{
ImGuiHelpers.ScaledDummy(3f);
ImGui.TextColored(new Vector4(1f, 0.8f, 0.2f, 1f), "Please select a mount to enable Chauffeur Mode");
}
}
}
}
}
}, config.ChauffeurModeEnabled);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Helper Following", delegate
{
ImGui.TextWrapped("Helper passively follows Quester and maintains a configurable distance. Automatically navigates when too far away.");
ImGuiHelpers.ScaledDummy(5f);
if (config.IsQuester)
{
ImGui.TextColored(new Vector4(0.7f, 0.9f, 1f, 1f), "Quester Settings:");
ImGui.TextWrapped("Select which Helper should follow you. Your position will be broadcasted to this Helper.");
ImGuiHelpers.ScaledDummy(3f);
string assignedHelperForFollowing = config.AssignedHelperForFollowing;
ImGui.Text("Assigned Helper:");
ImGui.SameLine();
if (string.IsNullOrEmpty(assignedHelperForFollowing))
{
ImGui.TextColored(new Vector4(1f, 0.5f, 0f, 1f), "None");
}
else
{
ImGui.TextColored(new Vector4(0.2f, 1f, 0.2f, 1f), assignedHelperForFollowing);
}
ImGuiHelpers.ScaledDummy(3f);
ImGui.Text("Auto-Discovered Helpers:");
ImGui.SetNextItemWidth(300f);
List<(string, ushort)> availableHelpers = plugin.GetAvailableHelpers();
if (ImGui.BeginCombo("##HelperDropdown", string.IsNullOrEmpty(assignedHelperForFollowing) ? "Select Helper..." : assignedHelperForFollowing))
{
if (availableHelpers.Count == 0)
{
ImGui.TextColored(new Vector4(1f, 0.5f, 0f, 1f), "No helpers discovered yet");
ImGui.TextWrapped("Helpers are auto-discovered via IPC when they have 'I'm a High-Level Helper' enabled.");
}
else
{
foreach (var item7 in availableHelpers)
{
string item = item7.Item1;
ushort item2 = item7.Item2;
ExcelSheet<World> excelSheet = Plugin.DataManager.GetExcelSheet<World>();
string text = "Unknown";
if (excelSheet != null)
{
foreach (World current2 in excelSheet)
{
if (current2.RowId == item2)
{
text = current2.Name.ToString();
break;
}
}
}
string text2 = item + "@" + text;
bool selected = assignedHelperForFollowing == text2;
if (ImGui.Selectable(text2, selected))
{
config.AssignedHelperForFollowing = text2;
config.Save();
}
}
}
ImGui.EndCombo();
}
DrawInfoIcon("Select the Helper from auto-discovered helpers.\nHelpers are discovered via IPC when they broadcast their status.");
ImGuiHelpers.ScaledDummy(5f);
bool v = config.EnableHelperFollowing;
if (string.IsNullOrEmpty(config.AssignedHelperForFollowing))
{
ImGui.BeginDisabled();
}
if (ImGui.Checkbox("Enable Position Broadcasting", ref v))
{
config.EnableHelperFollowing = v;
config.Save();
}
if (string.IsNullOrEmpty(config.AssignedHelperForFollowing))
{
ImGui.EndDisabled();
}
DrawInfoIcon("Enable to broadcast your position to the assigned Helper.\nThe Helper can then follow you automatically.");
ImGuiHelpers.ScaledDummy(3f);
if (config.EnableHelperFollowing && !string.IsNullOrEmpty(config.AssignedHelperForFollowing))
{
ImGui.TextColored(new Vector4(0.2f, 1f, 0.2f, 1f), "✓ Broadcasting position to Helper");
}
else if (!string.IsNullOrEmpty(config.AssignedHelperForFollowing))
{
ImGui.TextColored(new Vector4(1f, 0.5f, 0f, 1f), "⚠ Enable broadcasting to start");
}
else
{
ImGui.TextColored(new Vector4(1f, 0.5f, 0f, 1f), "⚠ Select a Helper first");
}
}
else if (!config.IsHighLevelHelper)
{
ImGui.TextColored(new Vector4(1f, 0.8f, 0.2f, 1f), "Please select a role (Quester or Helper) above");
}
else
{
ImGui.TextColored(new Vector4(0.7f, 0.9f, 1f, 1f), "Helper Settings:");
ImGui.TextWrapped("Select which Quester to follow. You will only follow this specific Quester.");
ImGuiHelpers.ScaledDummy(3f);
string assignedQuesterForFollowing = config.AssignedQuesterForFollowing;
ImGui.Text("Assigned Quester:");
ImGui.SameLine();
if (string.IsNullOrEmpty(assignedQuesterForFollowing))
{
ImGui.TextColored(new Vector4(1f, 0.3f, 0.3f, 1f), "⚠ None - Helper Following disabled!");
}
else
{
ImGui.TextColored(new Vector4(0.2f, 1f, 0.2f, 1f), assignedQuesterForFollowing);
}
ImGuiHelpers.ScaledDummy(3f);
ImGui.Text("Auto-Discovered Questers:");
ImGui.SetNextItemWidth(300f);
List<string> list = (Plugin.Instance?.GetChauffeurMode())?.GetDiscoveredQuesters() ?? new List<string>();
if (ImGui.BeginCombo("##QuesterDropdown", string.IsNullOrEmpty(assignedQuesterForFollowing) ? "Select Quester..." : assignedQuesterForFollowing))
{
if (list.Count == 0)
{
ImGui.TextColored(new Vector4(1f, 0.5f, 0f, 1f), "No questers discovered yet");
ImGui.TextWrapped("Questers are auto-discovered when they broadcast position.");
ImGui.TextWrapped("Make sure the Quester has Helper Following enabled and has assigned you as Helper.");
}
else
{
foreach (string current3 in list)
{
bool selected2 = assignedQuesterForFollowing == current3;
if (ImGui.Selectable(current3, selected2))
{
config.AssignedQuesterForFollowing = current3;
config.Save();
}
}
}
ImGui.EndCombo();
}
DrawInfoIcon("Questers are automatically discovered via IPC when they broadcast position.\nSelect the Quester you want to follow from the list.");
ImGuiHelpers.ScaledDummy(5f);
bool v2 = config.EnableHelperFollowing;
if (string.IsNullOrEmpty(config.AssignedQuesterForFollowing))
{
ImGui.BeginDisabled();
}
if (ImGui.Checkbox("Enable Helper Following", ref v2))
{
config.EnableHelperFollowing = v2;
config.Save();
}
if (string.IsNullOrEmpty(config.AssignedQuesterForFollowing))
{
ImGui.EndDisabled();
}
DrawInfoIcon("Helper will automatically follow the assigned Quester and maintain distance.\nStops in restricted zones (Main Cities) and when Chauffeur Mode is active.");
if (config.EnableHelperFollowing)
{
ImGuiHelpers.ScaledDummy(5f);
ImGui.Indent();
float v3 = config.HelperFollowDistance;
if (ImGui.SliderFloat("Follow Distance (yalms)", ref v3, 50f, 200f, "%.0f"))
{
config.HelperFollowDistance = v3;
config.Save();
}
DrawInfoIcon("Distance to maintain from Quester.\nHelper will navigate when further than this distance.\nRecommended: 80-120 yalms");
ImGuiHelpers.ScaledDummy(3f);
int v4 = config.HelperFollowCheckInterval;
if (ImGui.SliderInt("Check Interval (seconds)", ref v4, 3, 15))
{
config.HelperFollowCheckInterval = v4;
config.Save();
}
DrawInfoIcon("How often to check distance to Quester.\nLower values = more responsive but more CPU usage.\nRecommended: 5 seconds");
ImGui.Unindent();
ImGuiHelpers.ScaledDummy(5f);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(3f);
ImGui.TextColored(new Vector4(0.7f, 0.9f, 1f, 1f), "Status:");
if (Plugin.Instance?.GetChauffeurMode() != null)
{
ImU8String text3 = new ImU8String(23, 1);
text3.AppendLiteral("Follow Distance: ");
text3.AppendFormatted(config.HelperFollowDistance, "F0");
text3.AppendLiteral(" yalms");
ImGui.TextWrapped(text3);
ImU8String text4 = new ImU8String(17, 1);
text4.AppendLiteral("Check Interval: ");
text4.AppendFormatted(config.HelperFollowCheckInterval);
text4.AppendLiteral("s");
ImGui.TextWrapped(text4);
}
ImGuiHelpers.ScaledDummy(3f);
ImGui.TextColored(new Vector4(0.8f, 0.8f, 0.8f, 1f), "Note: Helper Following automatically stops when:");
ImGui.BulletText("Entering restricted zones (Main Cities)");
ImGui.BulletText("Chauffeur Mode summon is active");
ImGui.BulletText("Quester leaves party");
ImGui.BulletText("Quester changes to different zone");
}
}
}, config.EnableHelperFollowing);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Movement Monitor", delegate
{
config.EnableMovementMonitor = DrawSettingWithInfo("Enable Movement Monitor", config.EnableMovementMonitor, "Automatically detects if player is stuck and sends /qst reload.\nMonitors player position and detects lack of movement.\nImpact: Quest will auto-reload if stuck for too long.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
if (config.EnableMovementMonitor)
{
ImGui.Indent();
int v = config.MovementCheckInterval;
if (ImGui.SliderInt("Check Interval (seconds)", ref v, 3, 30))
{
config.MovementCheckInterval = v;
config.Save();
}
DrawInfoIcon("How often to check player position.\nLower values = faster stuck detection.");
int v2 = config.MovementStuckThreshold;
if (ImGui.SliderInt("Stuck Threshold (seconds)", ref v2, 15, 120))
{
config.MovementStuckThreshold = v2;
config.Save();
}
DrawInfoIcon("Time without movement before considering player stuck.\nHigher values = less false positives.");
ImGui.Unindent();
}
}, config.EnableMovementMonitor);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Combat Handling", delegate
{
config.EnableCombatHandling = DrawSettingWithInfo("Enable Combat Handling", config.EnableCombatHandling, "Automatically enables combat automation when HP drops below threshold.\nActivates RSR/VBMAI/BMRAI during dangerous situations.\nImpact: Combat plugins will auto-enable when HP is low.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
if (config.EnableCombatHandling)
{
ImGui.Indent();
int v = config.CombatHPThreshold;
if (ImGui.SliderInt("HP Threshold (%)", ref v, 1, 99))
{
config.CombatHPThreshold = v;
config.Save();
}
DrawInfoIcon("Enable combat automation when HP drops below this percentage.\nCommands: /rsr manual, /vbmai on, /bmrai on");
ImGui.Unindent();
}
}, config.EnableCombatHandling);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Death Handling", delegate
{
config.EnableDeathHandling = DrawSettingWithInfo("Enable Death Handling", config.EnableDeathHandling, "Automatically respawns and teleports back to death location.\nSaves position before death and returns after respawn.\nImpact: Deaths will be handled automatically during rotation.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
if (config.EnableDeathHandling)
{
ImGui.Indent();
int v = config.DeathRespawnDelay;
if (ImGui.SliderInt("Teleport Delay (seconds)", ref v, 1, 30))
{
config.DeathRespawnDelay = v;
config.Save();
}
DrawInfoIcon("Time to wait after respawn before teleporting back to death location.\nAllows time for loading and stabilization.");
ImGui.Unindent();
}
}, config.EnableDeathHandling);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Quest Automation", delegate
{
config.EnableQSTReloadTracking = DrawSettingWithInfo("Enable QST Reload Tracking", config.EnableQSTReloadTracking, "Tracks how many times /qst reload is called.\nSwitches character if reload count exceeds threshold.\nImpact: Prevents infinite reload loops by switching characters.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
if (config.EnableQSTReloadTracking)
{
ImGui.Indent();
int v = config.MaxQSTReloadsBeforeSwitch;
if (ImGui.SliderInt("Max Reloads Before Switch", ref v, 3, 20))
{
config.MaxQSTReloadsBeforeSwitch = v;
config.Save();
}
DrawInfoIcon("Maximum number of /qst reload commands before switching to next character.\nPrevents getting stuck on problematic quests.");
ImGui.Unindent();
}
}, config.EnableQSTReloadTracking);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Character Management", delegate
{
config.EnableMultiModeAfterRotation = DrawSettingWithInfo("Enable Multi-Mode After Rotation", config.EnableMultiModeAfterRotation, "Automatically enables AutoRetainer multi-mode after rotation completes.\nAllows retainer/submarine management after quest rotation.\nImpact: Multi-mode will activate when all quests are done.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
config.ReturnToHomeworldOnStopQuest = DrawSettingWithInfo("Return to Homeworld on Stop Quest", config.ReturnToHomeworldOnStopQuest, "Automatically returns character to home world when rotation stops.\nUses /li command to return home.\nImpact: Characters will be sent home after completing their quests.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
}, config.EnableMultiModeAfterRotation);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Safe Wait Settings", delegate
{
config.EnableSafeWaitBeforeCharacterSwitch = DrawSettingWithInfo("Enable Safe Wait Before Character Switch", config.EnableSafeWaitBeforeCharacterSwitch, "Waits for safe conditions before switching characters.\nChecks for combat, cutscenes, and loading screens.\nImpact: Character switches will be delayed until safe.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
config.EnableSafeWaitAfterCharacterSwitch = DrawSettingWithInfo("Enable Safe Wait After Character Switch", config.EnableSafeWaitAfterCharacterSwitch, "Waits for safe conditions after logging in new character.\nEnsures character is fully loaded before starting quests.\nImpact: Quest start will be delayed until character is ready.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
}, config.EnableSafeWaitBeforeCharacterSwitch);
ImGuiHelpers.ScaledDummy(10f);
DrawSettingSection("Quest Pre-Check", delegate
{
config.EnableQuestPreCheck = DrawSettingWithInfo("Enable Quest Pre-Check", config.EnableQuestPreCheck, "Scans completed quests before starting rotation.\nSkips characters who already completed target quests.\nImpact: Saves time by not processing already-completed quests.");
if (ImGui.IsItemDeactivatedAfterEdit())
{
config.Save();
}
}, config.EnableQuestPreCheck);
ImGuiHelpers.ScaledDummy(20f);
}
private void DrawSettingSection(string title, System.Action drawContent, bool isEnabled = false)
{
Vector2 cursorScreenPos = ImGui.GetCursorScreenPos();
float availWidth = ImGui.GetContentRegionAvail().X;
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
ImGui.ColorConvertFloat4ToU32(new Vector4(0.12f, 0.12f, 0.15f, 0.8f));
Vector4 borderColor;
if (isEnabled)
{
float pulse = (MathF.Sin((float)ImGui.GetTime() * 2f) + 1f) / 2f;
borderColor = new Vector4(0.47f, 0.69f, 0.88f, 0.5f + pulse * 0.5f);
}
else
{
borderColor = new Vector4(colorPrimary.X * 0.5f, colorPrimary.Y * 0.5f, colorPrimary.Z * 0.5f, 0.6f);
}
uint borderColorU32 = ImGui.ColorConvertFloat4ToU32(borderColor);
Vector2 boxStart = cursorScreenPos;
ImGui.PushStyleColor(ImGuiCol.Header, new Vector4(colorPrimary.X * 0.3f, colorPrimary.Y * 0.3f, colorPrimary.Z * 0.3f, 0.5f));
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, new Vector4(colorPrimary.X * 0.4f, colorPrimary.Y * 0.4f, colorPrimary.Z * 0.4f, 0.6f));
ImGui.PushStyleColor(ImGuiCol.HeaderActive, new Vector4(colorPrimary.X * 0.5f, colorPrimary.Y * 0.5f, colorPrimary.Z * 0.5f, 0.7f));
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
bool num = ImGui.CollapsingHeader(title, ImGuiTreeNodeFlags.DefaultOpen);
ImGui.PopStyleColor(4);
if (num)
{
ImGui.Indent(10f);
drawContent();
ImGui.Unindent(10f);
ImGuiHelpers.ScaledDummy(5f);
}
Vector2 boxEnd = ImGui.GetCursorScreenPos();
drawList.AddRect(boxStart, boxEnd + new Vector2(availWidth, 0f), borderColorU32, 4f, ImDrawFlags.None, isEnabled ? 2.5f : 1.5f);
}
private bool DrawSettingWithInfo(string label, bool value, string infoText)
{
ImGui.Checkbox(label, ref value);
ImGui.SameLine();
DrawInfoIcon(infoText);
return value;
}
private void DrawInfoIcon(string tooltipText)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("[i]");
ImGui.PopStyleColor();
if (ImGui.IsItemHovered())
{
ImGui.PushStyleColor(ImGuiCol.PopupBg, new Vector4(0.1f, 0.1f, 0.1f, 0.95f));
ImGui.PushStyleColor(ImGuiCol.Border, colorPrimary);
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(400f);
ImGui.TextUnformatted(tooltipText);
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
ImGui.PopStyleColor(2);
}
}
private void DrawCharactersTab()
{
object obj = selectedDCFilter switch
{
0 => "All Characters",
1 => "EU Characters",
2 => "NA Characters",
3 => "JP Characters",
4 => "OCE Characters",
_ => "Characters",
};
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted((string?)obj);
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
List<string> filteredChars = GetFilteredCharacters();
if (!initialCharacterLoadComplete)
{
if (characterLoadAttempts < 5)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
double elapsedSeconds = (DateTime.Now - initialLoadStartTime).TotalSeconds;
int nextRetryIn = ((characterLoadAttempts < retryDelaysSeconds.Length) ? (retryDelaysSeconds[characterLoadAttempts] - (int)elapsedSeconds) : 0);
if (nextRetryIn > 0)
{
ImU8String text = new ImU8String(36, 3);
text.AppendLiteral("Loading characters... (Retry ");
text.AppendFormatted(characterLoadAttempts);
text.AppendLiteral("/");
text.AppendFormatted(5);
text.AppendLiteral(" in ");
text.AppendFormatted(nextRetryIn);
text.AppendLiteral("s)");
ImGui.TextUnformatted(text);
}
else
{
ImU8String text2 = new ImU8String(33, 2);
text2.AppendLiteral("Loading characters... (Attempt ");
text2.AppendFormatted(characterLoadAttempts + 1);
text2.AppendLiteral("/");
text2.AppendFormatted(5);
text2.AppendLiteral(")");
ImGui.TextUnformatted(text2);
}
ImGui.PopStyleColor();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Waiting for AutoRetainer to initialize...");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
if (ImGui.Button("Retry Now"))
{
characterLoadAttempts = 0;
initialLoadStartTime = DateTime.MinValue;
autoRetainerIpc.TryReinitialize();
RefreshCharacterList();
}
}
else
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.3f, 0.3f, 1f));
ImGui.TextUnformatted("AutoRetainer not available");
ImGui.PopStyleColor();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Please ensure AutoRetainer plugin is installed and enabled.");
ImU8String text3 = new ImU8String(29, 1);
text3.AppendLiteral("Tried ");
text3.AppendFormatted(5);
text3.AppendLiteral(" times without success.");
ImGui.TextUnformatted(text3);
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
if (ImGui.Button("Retry Connection"))
{
characterLoadAttempts = 0;
initialLoadStartTime = DateTime.Now;
initialCharacterLoadComplete = false;
autoRetainerIpc.TryReinitialize();
RefreshCharacterList();
}
}
return;
}
if (!autoRetainerIpc.IsAvailable)
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.8f, 0f, 1f));
ImGui.TextUnformatted("AutoRetainer connection lost");
ImGui.PopStyleColor();
ImGui.TextUnformatted("The connection to AutoRetainer was lost.");
if (ImGui.Button("Reconnect"))
{
autoRetainerIpc.TryReinitialize();
RefreshCharacterList();
}
return;
}
if (filteredChars.Count == 0)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("No characters found");
ImGui.PopStyleColor();
return;
}
ImGui.PushStyleColor(ImGuiCol.Button, colorPrimary);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorSecondary);
if (ImGui.Button("Refresh"))
{
RefreshCharacterList();
characterProgressCache.Clear();
}
ImGui.PopStyleColor(2);
ImGui.SameLine();
if (ImGui.Button("Select All"))
{
foreach (string character in filteredChars)
{
characterSelection[character] = true;
}
}
ImGui.SameLine();
if (ImGui.Button("Deselect All"))
{
foreach (string character2 in filteredChars)
{
characterSelection[character2] = false;
}
}
ImGui.SameLine();
ImGui.SetNextItemWidth(150f);
List<string> currentDCWorlds = GetWorldsForCurrentDatacenter();
if (ImGui.BeginCombo("##WorldFilter", selectedWorldFilter))
{
if (ImGui.Selectable("All", selectedWorldFilter == "All"))
{
selectedWorldFilter = "All";
}
foreach (string world in currentDCWorlds.OrderBy((string w) => w))
{
if (ImGui.Selectable(world, selectedWorldFilter == world))
{
selectedWorldFilter = world;
}
}
ImGui.EndCombo();
}
ImGuiHelpers.ScaledDummy(10f);
float cardWidth = (ImGui.GetContentRegionAvail().X - 20f) / 2f;
using ImRaii.IEndObject child = ImRaii.Child("CharacterCards", new Vector2(0f, 0f), border: false);
if (!child.Success)
{
return;
}
int cardIndex = 0;
foreach (string character3 in filteredChars)
{
string[] parts = character3.Split('@');
string charName = ((parts.Length != 0) ? parts[0] : character3);
string world2 = ((parts.Length > 1) ? parts[1] : "Unknown");
if (cardIndex % 2 == 1)
{
ImGui.SameLine();
}
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
Vector2 cursorPos = ImGui.GetCursorScreenPos();
float cardHeight = 90f;
Vector4 cardBg = new Vector4(0.15f, 0.15f, 0.18f, 0.9f);
uint borderColor = (characterSelection.GetValueOrDefault(character3, defaultValue: false) ? ImGui.ColorConvertFloat4ToU32(new Vector4(colorPrimary.X, colorPrimary.Y, colorPrimary.Z, 0.8f)) : ImGui.ColorConvertFloat4ToU32(new Vector4(colorPrimary.X * 0.3f, colorPrimary.Y * 0.3f, colorPrimary.Z * 0.3f, 0.5f)));
drawList.AddRectFilled(cursorPos, cursorPos + new Vector2(cardWidth, cardHeight), ImGui.ColorConvertFloat4ToU32(cardBg), 6f);
drawList.AddRect(cursorPos, cursorPos + new Vector2(cardWidth, cardHeight), borderColor, 6f, ImDrawFlags.None, 2f);
ImGui.SetCursorScreenPos(cursorPos + new Vector2(10f, 10f));
using (ImRaii.PushId(character3))
{
bool isSelected = characterSelection.GetValueOrDefault(character3, defaultValue: false);
if (ImGui.Checkbox("##Select", ref isSelected))
{
characterSelection[character3] = isSelected;
}
}
ImGui.SetCursorScreenPos(cursorPos + new Vector2(40f, 8f));
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.Text(charName);
ImGui.PopStyleColor();
ImGui.SetCursorScreenPos(cursorPos + new Vector2(40f, 26f));
ImGui.PushStyleColor(ImGuiCol.Text, colorAccent);
ImGui.Text(world2);
ImGui.PopStyleColor();
if (characterProgressCache.TryGetValue(character3, out CharacterProgressInfo progressInfo))
{
ImGui.SetCursorScreenPos(cursorPos + new Vector2(10f, 50f));
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImU8String text4 = new ImU8String(7, 1);
text4.AppendFormatted(progressInfo.CompletedQuestCount);
text4.AppendLiteral(" Quests");
ImGui.Text(text4);
ImGui.PopStyleColor();
ImGui.SetCursorScreenPos(cursorPos + new Vector2(10f, 68f));
float barWidth = cardWidth - 20f;
float progress = progressInfo.MSQCompletionPercentage / 100f;
uint barBg = ImGui.ColorConvertFloat4ToU32(new Vector4(0.2f, 0.2f, 0.2f, 0.8f));
uint barFg = ImGui.ColorConvertFloat4ToU32(new Vector4(colorSecondary.X, colorSecondary.Y, colorSecondary.Z, 0.9f));
drawList.AddRectFilled(cursorPos + new Vector2(10f, 68f), cursorPos + new Vector2(10f + barWidth, 82f), barBg, 3f);
drawList.AddRectFilled(cursorPos + new Vector2(10f, 68f), cursorPos + new Vector2(10f + barWidth * progress, 82f), barFg, 3f);
string progressText = $"{progressInfo.MSQCompletionPercentage:F0}%";
Vector2 textSize = ImGui.CalcTextSize(progressText);
drawList.AddText(cursorPos + new Vector2(10f + barWidth / 2f - textSize.X / 2f, 69f), ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 1f)), progressText);
}
else
{
ImGui.SetCursorScreenPos(cursorPos + new Vector2(10f, 50f));
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.Text("Loading...");
ImGui.PopStyleColor();
GetCharacterProgress(character3);
}
ImGui.SetCursorScreenPos(cursorPos);
ImGui.Dummy(new Vector2(cardWidth, cardHeight));
cardIndex++;
}
}
private void DrawStopPointsTab()
{
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Quest Rotation System");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
List<string> selectedCharacters = (from kvp in characterSelection
where kvp.Value
select kvp.Key).ToList();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextWrapped("Rotates your selected Characters depending on the Stop Configurations you have enabled in Questionable. Please Configure a Quest and / or Level for a Rotation to be able to start.");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
ImGui.PushStyleColor(ImGuiCol.Button, colorAccent);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorPrimary);
if (ImGui.Button("Import from Questionable"))
{
questRotationService.ImportStopPointsFromQuestionable();
log.Information("[StopPoints] Imported stop points from Questionable");
}
ImGui.PopStyleColor(2);
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Pull stop quests and sequences from Questionable configuration");
}
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Button, colorPrimary);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorSecondary);
if (ImGui.Button("Open Questionable Settings"))
{
Plugin.CommandManager.ProcessCommand("/qst config");
log.Information("[StopPoints] Opened Questionable settings");
}
ImGui.PopStyleColor(2);
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Open Questionable plugin settings window");
}
ImGuiHelpers.ScaledDummy(10f);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(10f);
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Level Stop Condition:");
ImGui.PopStyleColor();
StopConditionData levelStopCondition = plugin.QuestionableIPC.GetLevelStopCondition();
if (levelStopCondition != null && levelStopCondition.Enabled)
{
ImGui.SetWindowFontScale(1.2f);
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImU8String text = new ImU8String(15, 1);
text.AppendLiteral("Stop at Level: ");
text.AppendFormatted(levelStopCondition.TargetValue);
ImGui.TextUnformatted(text);
ImGui.PopStyleColor();
ImGui.SetWindowFontScale(1f);
}
else
{
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.6f, 0.6f, 0.6f, 1f));
ImGui.TextUnformatted("Not configured");
ImGui.PopStyleColor();
}
ImGuiHelpers.ScaledDummy(10f);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(10f);
ImGui.TextUnformatted("Active Stop Points:");
ImGuiHelpers.ScaledDummy(5f);
List<StopPoint> stopPoints = questRotationService.GetAllStopPoints();
if (stopPoints.Count == 0)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("No stop points configured.");
ImGui.PopStyleColor();
}
else
{
using ImRaii.IEndObject table = ImRaii.Table("StopPointsTable", 5, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg);
if (table.Success)
{
ImGui.TableSetupColumn("Stop Point", ImGuiTableColumnFlags.WidthFixed, 150f);
ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthFixed, 80f);
ImGui.TableSetupColumn("Remaining", ImGuiTableColumnFlags.WidthFixed, 100f);
ImGui.TableSetupColumn("Progress", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 60f);
ImGui.TableHeadersRow();
for (int i = 0; i < stopPoints.Count; i++)
{
StopPoint stopPoint = stopPoints[i];
(int completed, int total) rotationProgress = questRotationService.GetRotationProgress(stopPoint.QuestId);
int completed = rotationProgress.completed;
int total = rotationProgress.total;
int num = total - completed;
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TextUnformatted(stopPoint.DisplayName);
ImGui.TableNextColumn();
if (stopPoint.IsActive)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Active");
ImGui.PopStyleColor();
}
else
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Queued");
ImGui.PopStyleColor();
}
ImGui.TableNextColumn();
Vector4 remainingColor = ((num == 0) ? colorPrimary : colorSecondary);
ImGui.PushStyleColor(ImGuiCol.Text, remainingColor);
ImU8String text2 = new ImU8String(1, 2);
text2.AppendFormatted(completed);
text2.AppendLiteral("/");
text2.AppendFormatted(total);
ImGui.TextUnformatted(text2);
ImGui.PopStyleColor();
ImGui.TableNextColumn();
float progress = ((total > 0) ? ((float)completed / (float)total) : 0f);
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
Vector2 cursorPos = ImGui.GetCursorScreenPos();
float barWidth = ImGui.GetContentRegionAvail().X;
float barHeight = 20f;
uint barBg = ImGui.ColorConvertFloat4ToU32(new Vector4(0.2f, 0.2f, 0.2f, 0.8f));
drawList.AddRectFilled(cursorPos, cursorPos + new Vector2(barWidth, barHeight), barBg, 4f);
Vector4 barColor = ((progress >= 0.85f) ? colorPrimary : ((progress >= 0.5f) ? colorSecondary : colorAccent));
uint barFg = ImGui.ColorConvertFloat4ToU32(new Vector4(barColor.X, barColor.Y, barColor.Z, 0.9f));
drawList.AddRectFilled(cursorPos, cursorPos + new Vector2(barWidth * progress, barHeight), barFg, 4f);
string progressText = $"{(int)(progress * 100f)}%";
Vector2 textSize = ImGui.CalcTextSize(progressText);
drawList.AddText(cursorPos + new Vector2(barWidth / 2f - textSize.X / 2f, 2f), ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 1f)), progressText);
ImGui.Dummy(new Vector2(barWidth, barHeight));
ImGui.TableNextColumn();
using (ImRaii.PushId(i))
{
ImGui.PushStyleColor(ImGuiCol.Button, colorAccent);
if (ImGui.Button("X"))
{
questRotationService.RemoveStopPoint(stopPoint.QuestId);
log.Information($"[StopPoints] Removed stop quest {stopPoint.QuestId}");
}
ImGui.PopStyleColor();
}
}
}
}
ImGuiHelpers.ScaledDummy(15f);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(10f);
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Current Status:");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
RotationState state = questRotationService.GetCurrentState();
ImU8String text3 = new ImU8String(7, 0);
text3.AppendLiteral("Phase: ");
ImGui.TextUnformatted(text3);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, state.Phase switch
{
RotationPhase.Idle => colorSecondary,
RotationPhase.Questing => colorPrimary,
RotationPhase.QuestActive => colorPrimary,
RotationPhase.InCombat => new Vector4(1f, 0.5f, 0.2f, 1f),
RotationPhase.InDungeon => new Vector4(0.8f, 0.4f, 1f, 1f),
RotationPhase.HandlingSubmarines => new Vector4(0.2f, 0.8f, 1f, 1f),
RotationPhase.WaitingForChauffeur => new Vector4(1f, 1f, 0.4f, 1f),
RotationPhase.TravellingWithChauffeur => new Vector4(0.4f, 1f, 0.4f, 1f),
RotationPhase.DCTraveling => new Vector4(0.5f, 0.5f, 1f, 1f),
RotationPhase.Completed => colorPrimary,
RotationPhase.Error => colorAccent,
_ => colorSecondary,
});
ImGui.TextUnformatted(state.Phase.ToString());
ImGui.PopStyleColor();
if (!string.IsNullOrEmpty(state.CurrentCharacter))
{
ImU8String text4 = new ImU8String(11, 0);
text4.AppendLiteral("Logged In: ");
ImGui.TextUnformatted(text4);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted(state.CurrentCharacter);
ImGui.PopStyleColor();
}
if (state.CurrentStopQuestId != 0)
{
ImU8String text5 = new ImU8String(14, 0);
text5.AppendLiteral("Target Quest: ");
ImGui.TextUnformatted(text5);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted(state.CurrentStopQuestId.ToString());
ImGui.PopStyleColor();
}
if (!string.IsNullOrEmpty(state.NextCharacter))
{
ImU8String text6 = new ImU8String(16, 0);
text6.AppendLiteral("Next Character: ");
ImGui.TextUnformatted(text6);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted(state.NextCharacter);
ImGui.PopStyleColor();
}
if (state.RemainingCharacters.Count > 0)
{
ImU8String text7 = new ImU8String(11, 1);
text7.AppendLiteral("Remaining: ");
text7.AppendFormatted(string.Join(", ", state.RemainingCharacters));
ImGui.TextUnformatted(text7);
}
if (state.Phase == RotationPhase.Error && !string.IsNullOrEmpty(state.ErrorMessage))
{
ImGui.PushStyleColor(ImGuiCol.Text, colorAccent);
ImU8String text8 = new ImU8String(7, 1);
text8.AppendLiteral("Error: ");
text8.AppendFormatted(state.ErrorMessage);
ImGui.TextUnformatted(text8);
ImGui.PopStyleColor();
}
if (state.SelectedCharacters.Count > 0)
{
ImGuiHelpers.ScaledDummy(5f);
float fraction = (float)state.CompletedCharacters.Count / (float)state.SelectedCharacters.Count;
Vector2 sizeArg = new Vector2(-1f, 0f);
ImU8String overlay = new ImU8String(11, 2);
overlay.AppendFormatted(state.CompletedCharacters.Count);
overlay.AppendLiteral("/");
overlay.AppendFormatted(state.SelectedCharacters.Count);
overlay.AppendLiteral(" completed");
ImGui.ProgressBar(fraction, sizeArg, overlay);
}
ImGuiHelpers.ScaledDummy(10f);
if (selectedCharacters.Count == 0)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Select characters in the Characters tab to start rotation");
ImGui.PopStyleColor();
}
else if (stopPoints.Count == 0 && (levelStopCondition == null || !levelStopCondition.Enabled))
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Configure a Quest or Level stop condition above to start rotation");
ImGui.PopStyleColor();
}
else if (state.Phase == RotationPhase.Idle || state.Phase == RotationPhase.Completed || state.Phase == RotationPhase.Error)
{
ImGui.PushStyleColor(ImGuiCol.Button, colorPrimary);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorSecondary);
if (ImGui.Button("Start Rotation", new Vector2(200f, 30f)))
{
log.Information("[StopPoints] Start Rotation button clicked!");
log.Information($"[StopPoints] Selected characters: {selectedCharacters.Count}");
log.Information($"[StopPoints] Stop points: {stopPoints.Count}");
if (stopPoints.Count > 0)
{
foreach (StopPoint item in stopPoints)
{
item.IsActive = false;
}
bool foundValidStopPoint = false;
for (int i2 = 0; i2 < stopPoints.Count; i2++)
{
StopPoint stopPoint2 = stopPoints[i2];
var (completed2, total2) = questRotationService.GetRotationProgress(stopPoint2.QuestId, selectedCharacters);
if (completed2 < total2)
{
log.Information("[StopPoints] Starting rotation with: " + stopPoint2.DisplayName);
log.Information($"[StopPoints] Progress: {completed2}/{total2} completed");
log.Information($"[StopPoints] Total stop points in queue: {stopPoints.Count - i2}");
for (int j = i2; j < stopPoints.Count; j++)
{
stopPoints[j].IsActive = j == i2;
}
if (questRotationService.StartRotation(stopPoint2.QuestId, selectedCharacters))
{
log.Information("[StopPoints] Rotation started successfully!");
foundValidStopPoint = true;
}
else
{
log.Error("[StopPoints] Failed to start rotation");
}
break;
}
log.Information($"[StopPoints] Skipping {stopPoint2.DisplayName} - all characters completed ({completed2}/{total2})");
}
if (!foundValidStopPoint)
{
log.Warning("[StopPoints] All stop points already completed by all characters!");
}
}
else if (levelStopCondition != null && levelStopCondition.Enabled)
{
log.Information($"[StopPoints] Starting level-only rotation (target level: {levelStopCondition.TargetValue})");
if (questRotationService.StartRotationLevelOnly(selectedCharacters))
{
log.Information("[StopPoints] Level-only rotation started successfully!");
}
else
{
log.Error("[StopPoints] Failed to start level-only rotation");
}
}
else
{
log.Warning("[StopPoints] No stop points or level condition configured!");
}
}
ImGui.PopStyleColor(2);
}
else
{
ImGui.PushStyleColor(ImGuiCol.Button, colorAccent);
if (ImGui.Button("Stop Rotation", new Vector2(200f, 30f)))
{
questRotationService.AbortRotation();
log.Information("[StopPoints] Stopped rotation");
}
ImGui.PopStyleColor();
}
}
private void DrawMSQProgressionTab()
{
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImGui.TextUnformatted("Main Scenario Quest Progression");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
List<string> filteredChars = GetFilteredCharacters();
Configuration config = plugin.Configuration;
if (filteredChars.Count == 0)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("No characters to display");
ImGui.PopStyleColor();
return;
}
ImGui.PushStyleColor(ImGuiCol.Button, colorPrimary);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorSecondary);
if (ImGui.Button("Refresh Progress"))
{
characterProgressCache.Clear();
log.Information("[MSQProgression] Manual progress refresh requested");
}
ImGui.PopStyleColor(2);
ImGui.SameLine();
RotationState state = questRotationService.GetCurrentState();
if (state.Phase == RotationPhase.Idle || state.Phase == RotationPhase.Completed || state.Phase == RotationPhase.Error || state.CurrentStopQuestId != 0)
{
ImGui.PushStyleColor(ImGuiCol.Button, colorSecondary);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorPrimary);
if (ImGui.Button("First Time Sync"))
{
log.Information("[MSQProgression] First Time Sync requested");
if (questRotationService.StartSyncRotation(filteredChars))
{
log.Information("[MSQProgression] Sync rotation started - will go through all characters without data");
}
else
{
log.Information("[MSQProgression] No characters need sync or failed to start");
}
}
ImGui.PopStyleColor(2);
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Automatically sync MSQ data for all characters without existing data");
}
}
else
{
ImGui.PushStyleColor(ImGuiCol.Button, colorAccent);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorPrimary);
if (ImGui.Button("Stop Syncing"))
{
log.Information("[MSQProgression] Stop Syncing requested");
questRotationService.AbortRotation();
characterProgressCache.Clear();
}
ImGui.PopStyleColor(2);
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip("Stop the sync rotation");
}
}
ImGui.SameLine();
ImGui.TextUnformatted("Display Mode:");
ImGui.SameLine();
int displayMode = (int)config.MSQDisplayMode;
ImGui.SetNextItemWidth(200f);
if (ImGui.Combo("##MSQDisplayMode", ref displayMode, "Current Expansion\0Overall Progress\0Expansion Breakdown\0"))
{
config.MSQDisplayMode = (MSQDisplayMode)displayMode;
config.Save();
}
ImGuiHelpers.ScaledDummy(10f);
switch (config.MSQDisplayMode)
{
case MSQDisplayMode.CurrentExpansion:
DrawMSQCurrentExpansion(filteredChars);
break;
case MSQDisplayMode.Overall:
DrawMSQOverall(filteredChars);
break;
case MSQDisplayMode.ExpansionBreakdown:
DrawMSQExpansionBreakdown(filteredChars);
break;
}
}
private void DrawMSQCurrentExpansion(List<string> characters)
{
using ImRaii.IEndObject table = ImRaii.Table("MSQCurrentExpTable", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY);
if (!table.Success)
{
return;
}
ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Current Expansion", ImGuiTableColumnFlags.WidthFixed, 150f);
ImGui.TableSetupColumn("Progress", ImGuiTableColumnFlags.WidthFixed, 100f);
ImGui.TableSetupColumn("Completion", ImGuiTableColumnFlags.WidthFixed, 120f);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
foreach (string character in characters)
{
if (!characterProgressCache.TryGetValue(character, out CharacterProgressInfo _))
{
GetCharacterProgress(character);
continue;
}
List<uint> completedQuestsList = questRotationService.GetCompletedQuestsByCharacter(character);
MSQExpansionData.Expansion currentExpansion = MSQExpansionData.GetCurrentExpansion(completedQuestsList);
ExpansionInfo currentExp = new ExpansionInfo
{
Name = MSQExpansionData.GetExpansionName(currentExpansion),
ShortName = MSQExpansionData.GetExpansionShortName(currentExpansion),
MinQuestId = 0u,
MaxQuestId = 0u,
ExpectedQuestCount = MSQExpansionData.GetExpectedQuestCount(currentExpansion)
};
ImGui.TableNextRow();
ImGui.TableNextColumn();
string[] parts = character.Split('@');
ImGui.TextUnformatted((parts.Length != 0) ? parts[0] : character);
ImGui.TableNextColumn();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted(currentExp?.Name ?? "A Realm Reborn");
ImGui.PopStyleColor();
ImGui.TableNextColumn();
if (currentExp != null)
{
(int completed, int total) valueOrDefault = msqProgressionService.GetExpansionProgressForCharacter(completedQuestsList).GetValueOrDefault(currentExp.ShortName, (0, 0));
int completed = valueOrDefault.completed;
int total = valueOrDefault.total;
ImU8String text = new ImU8String(1, 2);
text.AppendFormatted(completed);
text.AppendLiteral("/");
text.AppendFormatted(total);
ImGui.TextUnformatted(text);
}
else
{
ImGui.TextUnformatted("0/0");
}
ImGui.TableNextColumn();
if (currentExp != null)
{
(int completed, int total) valueOrDefault2 = msqProgressionService.GetExpansionProgressForCharacter(completedQuestsList).GetValueOrDefault(currentExp.ShortName, (0, 0));
int completed2 = valueOrDefault2.completed;
int total2 = valueOrDefault2.total;
float percentage = ((total2 > 0) ? ((float)completed2 / (float)total2) : 0f);
Vector2 sizeArg = new Vector2(-1f, 0f);
ImU8String overlay = new ImU8String(1, 1);
overlay.AppendFormatted((int)(percentage * 100f));
overlay.AppendLiteral("%");
ImGui.ProgressBar(percentage, sizeArg, overlay);
}
else
{
ImGui.ProgressBar(0f, new Vector2(-1f, 0f), "0%");
}
}
}
private void DrawMSQOverall(List<string> characters)
{
using ImRaii.IEndObject table = ImRaii.Table("MSQOverallTable", 4, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY);
if (!table.Success)
{
return;
}
ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("MSQ Progress", ImGuiTableColumnFlags.WidthFixed, 120f);
ImGui.TableSetupColumn("Current MSQ", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Completion %", ImGuiTableColumnFlags.WidthFixed, 100f);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
foreach (string character in characters)
{
if (!characterProgressCache.TryGetValue(character, out CharacterProgressInfo progressInfo))
{
GetCharacterProgress(character);
continue;
}
ImGui.TableNextRow();
ImGui.TableNextColumn();
string[] parts = character.Split('@');
ImGui.TextUnformatted((parts.Length != 0) ? parts[0] : character);
ImGui.TableNextColumn();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImU8String text = new ImU8String(3, 2);
text.AppendFormatted(progressInfo.CompletedMSQCount);
text.AppendLiteral(" / ");
text.AppendFormatted(msqProgressionService.GetTotalMSQCount());
ImGui.TextUnformatted(text);
ImGui.PopStyleColor();
ImGui.TableNextColumn();
ImGui.TextUnformatted(progressInfo.LastCompletedMSQName);
ImGui.TableNextColumn();
float percentage = progressInfo.MSQCompletionPercentage;
float fraction = percentage / 100f;
Vector2 sizeArg = new Vector2(-1f, 0f);
ImU8String overlay = new ImU8String(1, 1);
overlay.AppendFormatted(percentage, "F1");
overlay.AppendLiteral("%");
ImGui.ProgressBar(fraction, sizeArg, overlay);
}
}
private void DrawMSQExpansionBreakdown(List<string> characters)
{
List<ExpansionInfo> expansions = msqProgressionService.GetExpansions();
foreach (string character in characters)
{
string[] parts = character.Split('@');
string obj = ((parts.Length != 0) ? parts[0] : character);
string worldName = ((parts.Length > 1) ? parts[1] : "Unknown");
string displayName = obj + " @ " + worldName;
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImU8String label = new ImU8String(15, 2);
label.AppendFormatted(displayName);
label.AppendLiteral("##MSQBreakdown_");
label.AppendFormatted(character);
if (ImGui.CollapsingHeader(label))
{
ImGui.PopStyleColor();
ImGui.Indent(15f);
List<uint> completedQuests = questRotationService.GetCompletedQuestsByCharacter(character);
int currentExpansionIndex = (int)MSQExpansionData.GetCurrentExpansion(completedQuests);
Dictionary<string, (int, int)> expProgress = msqProgressionService.GetExpansionProgressForCharacter(completedQuests);
int totalCompleted = 0;
int totalQuests = 0;
foreach (ExpansionInfo exp in expansions)
{
var (completed, total) = expProgress.GetValueOrDefault(exp.ShortName, (0, 0));
if ((int)MSQExpansionData.GetAllExpansions().FirstOrDefault((MSQExpansionData.Expansion e) => MSQExpansionData.GetExpansionShortName(e) == exp.ShortName) < currentExpansionIndex)
{
completed = total;
}
totalCompleted += completed;
totalQuests += total;
}
ImU8String text = new ImU8String(13, 0);
text.AppendLiteral("Overall MSQ: ");
ImGui.TextUnformatted(text);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImU8String text2 = new ImU8String(1, 2);
text2.AppendFormatted(totalCompleted);
text2.AppendLiteral("/");
text2.AppendFormatted(totalQuests);
ImGui.TextUnformatted(text2);
ImGui.PopStyleColor();
float overallPercentage = ((totalQuests > 0) ? ((float)totalCompleted / (float)totalQuests) : 0f);
Vector2 sizeArg = new Vector2(-1f, 0f);
ImU8String overlay = new ImU8String(1, 1);
overlay.AppendFormatted((int)(overallPercentage * 100f));
overlay.AppendLiteral("%");
ImGui.ProgressBar(overallPercentage, sizeArg, overlay);
ImGuiHelpers.ScaledDummy(10f);
ImGui.TextUnformatted("Expansion Breakdown:");
ImGuiHelpers.ScaledDummy(5f);
foreach (ExpansionInfo exp2 in expansions)
{
var (completed2, total2) = expProgress.GetValueOrDefault(exp2.ShortName, (0, 0));
if ((int)MSQExpansionData.GetAllExpansions().FirstOrDefault((MSQExpansionData.Expansion e) => MSQExpansionData.GetExpansionShortName(e) == exp2.ShortName) < currentExpansionIndex)
{
completed2 = total2;
}
float percentage = ((total2 > 0) ? ((float)completed2 / (float)total2) : 0f);
bool num = completed2 == total2 && total2 > 0;
ImU8String text3 = new ImU8String(6, 2);
text3.AppendLiteral(" ");
text3.AppendFormatted(exp2.Name);
text3.AppendLiteral(" (");
text3.AppendFormatted(exp2.ShortName);
text3.AppendLiteral("):");
ImGui.TextUnformatted(text3);
ImGui.SameLine();
if (num)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
ImU8String text4 = new ImU8String(10, 2);
text4.AppendFormatted(completed2);
text4.AppendLiteral("/");
text4.AppendFormatted(total2);
text4.AppendLiteral(" Complete");
ImGui.TextUnformatted(text4);
ImGui.PopStyleColor();
}
else if (completed2 == 0)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorAccent);
ImU8String text5 = new ImU8String(13, 2);
text5.AppendFormatted(completed2);
text5.AppendLiteral("/");
text5.AppendFormatted(total2);
text5.AppendLiteral(" Not Started");
ImGui.TextUnformatted(text5);
ImGui.PopStyleColor();
}
else
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImU8String text6 = new ImU8String(1, 2);
text6.AppendFormatted(completed2);
text6.AppendLiteral("/");
text6.AppendFormatted(total2);
ImGui.TextUnformatted(text6);
ImGui.PopStyleColor();
}
ImGui.Indent(20f);
Vector2 sizeArg2 = new Vector2(-1f, 0f);
ImU8String overlay2 = new ImU8String(1, 1);
overlay2.AppendFormatted((int)(percentage * 100f));
overlay2.AppendLiteral("%");
ImGui.ProgressBar(percentage, sizeArg2, overlay2);
ImGui.Unindent(20f);
}
ImGui.Unindent(15f);
}
else
{
ImGui.PopStyleColor();
}
ImGuiHelpers.ScaledDummy(5f);
}
}
private void DrawSettingsTab()
{
DrawSettingsTabFull();
}
private List<string> GetWorldsForCurrentDatacenter()
{
if (selectedDCFilter == 0)
{
return availableWorlds;
}
string dcName = selectedDCFilter switch
{
1 => "EU",
2 => "NA",
3 => "JP",
4 => "OCE",
_ => "All",
};
List<string> charactersForDataCenter = dataCenterService.GetCharactersForDataCenter(registeredCharacters, dcName, charactersByDataCenter);
HashSet<string> worlds = new HashSet<string>();
foreach (string item in charactersForDataCenter)
{
string[] parts = item.Split('@');
if (parts.Length > 1)
{
worlds.Add(parts[1]);
}
}
return worlds.ToList();
}
private List<string> GetFilteredCharacters()
{
List<string> dcFiltered;
if (selectedDCFilter == 0)
{
dcFiltered = registeredCharacters;
}
else
{
string dcName = selectedDCFilter switch
{
1 => "EU",
2 => "NA",
3 => "JP",
4 => "OCE",
_ => "All",
};
dcFiltered = dataCenterService.GetCharactersForDataCenter(registeredCharacters, dcName, charactersByDataCenter);
}
if (selectedWorldFilter != "All")
{
return dcFiltered.Where((string c) => c.EndsWith("@" + selectedWorldFilter)).ToList();
}
return dcFiltered;
}
private CharacterProgressInfo GetCharacterProgress(string characterName)
{
if (characterProgressCache.TryGetValue(characterName, out CharacterProgressInfo cached) && (DateTime.Now - cached.LastUpdatedUtc).TotalSeconds < 300.0)
{
return cached;
}
string[] parts = characterName.Split('@');
string world = ((parts.Length > 1) ? parts[1] : "Unknown");
List<uint> completedQuests = questRotationService.GetCompletedQuestsByCharacter(characterName);
uint lastQuestId = 0u;
string lastQuestName = "—";
if (completedQuests.Count > 0)
{
lastQuestId = completedQuests.Max();
lastQuestName = msqProgressionService.GetQuestName(lastQuestId);
if (lastQuestName == "Unknown Quest")
{
lastQuestName = $"Quest {lastQuestId}";
}
}
List<uint> completedMSQs = completedQuests.Where((uint q) => msqProgressionService.IsMSQ(q)).ToList();
uint lastMSQId = 0u;
string lastMSQName = "—";
MSQExpansionData.Expansion currentExpansion = MSQExpansionData.GetCurrentExpansion(completedQuests);
string currentExpansionName = MSQExpansionData.GetExpansionName(currentExpansion);
List<uint> completedMSQsInCurrentExpansion = completedMSQs.Where((uint q) => MSQExpansionData.GetExpansionForQuest(q) == currentExpansion).ToList();
if (completedMSQsInCurrentExpansion.Count > 0)
{
lastMSQId = completedMSQsInCurrentExpansion.Max();
lastMSQName = msqProgressionService.GetQuestName(lastMSQId);
if (lastMSQName == "Unknown Quest")
{
lastMSQName = $"Quest {lastMSQId}";
}
lastMSQName = "[" + currentExpansionName + "] " + lastMSQName;
}
else if (completedMSQs.Count > 0)
{
lastMSQId = completedMSQs.Max();
lastMSQName = msqProgressionService.GetQuestName(lastMSQId);
if (lastMSQName == "Unknown Quest")
{
lastMSQName = $"Quest {lastMSQId}";
}
}
int totalMSQCount = msqProgressionService.GetTotalMSQCount();
float msqPercentage = ((totalMSQCount > 0) ? ((float)completedMSQs.Count / (float)totalMSQCount * 100f) : 0f);
CharacterProgressInfo progressInfo = new CharacterProgressInfo
{
World = world,
CompletedQuestCount = completedQuests.Count,
LastQuestId = lastQuestId,
LastQuestName = lastQuestName,
LastCompletedMSQId = lastMSQId,
LastCompletedMSQName = lastMSQName,
CompletedMSQCount = completedMSQs.Count,
MSQCompletionPercentage = msqPercentage,
LastUpdatedUtc = DateTime.UtcNow
};
characterProgressCache[characterName] = progressInfo;
return progressInfo;
}
public void DrawWorldSelectionDialogs()
{
if (showSelectWorldDialog)
{
ImGui.OpenPopup("Select World##SelectWorldDialog");
}
if (ImGui.BeginPopupModal("Select World##SelectWorldDialog", ref showSelectWorldDialog, ImGuiWindowFlags.AlwaysAutoResize))
{
ImGui.TextUnformatted("Select a world to check all characters:");
ImGuiHelpers.ScaledDummy(10f);
ImGui.SetNextItemWidth(200f);
if (ImGui.BeginCombo("##WorldSelect", selectedWorldForBulkAction))
{
foreach (string world in availableWorlds)
{
bool isSelected = selectedWorldForBulkAction == world;
if (ImGui.Selectable(world, isSelected))
{
selectedWorldForBulkAction = world;
}
if (isSelected)
{
ImGui.SetItemDefaultFocus();
}
}
ImGui.EndCombo();
}
ImGuiHelpers.ScaledDummy(10f);
if (ImGui.Button("Cancel", new Vector2(100f, 0f)))
{
showSelectWorldDialog = false;
}
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Button, colorPrimary);
if (ImGui.Button("Select", new Vector2(100f, 0f)))
{
foreach (string character in registeredCharacters)
{
if (character.EndsWith("@" + selectedWorldForBulkAction))
{
characterSelection[character] = true;
}
}
showSelectWorldDialog = false;
log.Information("[NewMainWindow] Selected all characters from " + selectedWorldForBulkAction);
}
ImGui.PopStyleColor();
ImGui.EndPopup();
}
if (showDeselectWorldDialog)
{
ImGui.OpenPopup("Deselect World##DeselectWorldDialog");
}
if (!ImGui.BeginPopupModal("Deselect World##DeselectWorldDialog", ref showDeselectWorldDialog, ImGuiWindowFlags.AlwaysAutoResize))
{
return;
}
ImGui.TextUnformatted("Select a world to uncheck all characters:");
ImGuiHelpers.ScaledDummy(10f);
ImGui.SetNextItemWidth(200f);
if (ImGui.BeginCombo("##WorldDeselect", selectedWorldForBulkAction))
{
foreach (string world2 in availableWorlds)
{
bool isSelected2 = selectedWorldForBulkAction == world2;
if (ImGui.Selectable(world2, isSelected2))
{
selectedWorldForBulkAction = world2;
}
if (isSelected2)
{
ImGui.SetItemDefaultFocus();
}
}
ImGui.EndCombo();
}
ImGuiHelpers.ScaledDummy(10f);
if (ImGui.Button("Cancel", new Vector2(100f, 0f)))
{
showDeselectWorldDialog = false;
}
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Button, colorAccent);
if (ImGui.Button("Deselect", new Vector2(100f, 0f)))
{
foreach (string character2 in registeredCharacters)
{
if (character2.EndsWith("@" + selectedWorldForBulkAction))
{
characterSelection[character2] = false;
}
}
showDeselectWorldDialog = false;
log.Information("[NewMainWindow] Deselected all characters from " + selectedWorldForBulkAction);
}
ImGui.PopStyleColor();
ImGui.EndPopup();
}
private void DrawEventQuestTab()
{
Vector4 eventQuestColor = new Vector4(0.949f, 0.769f, 0.388f, 1f);
Vector4 eventQuestAccent = new Vector4(1f, 0.6f, 0.2f, 1f);
ImGui.PushStyleColor(ImGuiCol.Text, eventQuestColor);
ImGui.TextUnformatted("Event Quest System");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
List<string> selectedCharacters = (from keyValuePair in characterSelection
where keyValuePair.Value
select keyValuePair.Key).ToList();
if ((DateTime.Now - lastEventQuestRefresh).TotalSeconds > 5.0 || availableEventQuests.Count == 0)
{
List<string> currentlyActiveEventQuests = plugin.QuestionableIPC.GetCurrentlyActiveEventQuests();
EventQuestResolver resolver = new EventQuestResolver(dataManager, log);
availableEventQuests.Clear();
foreach (string questId in currentlyActiveEventQuests)
{
log.Information($"[EventQuest] Questionable returned quest ID: '{questId}' (Length: {questId.Length})");
string questName = resolver.GetQuestName(questId);
availableEventQuests.Add((questId, questName));
}
lastEventQuestRefresh = DateTime.Now;
log.Debug($"[EventQuest] Loaded {availableEventQuests.Count} active event quests from Questionable");
}
ImGui.TextUnformatted("Active Event Quests:");
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Select an active Event Quest. Prerequisites will be automatically resolved.");
ImU8String text = new ImU8String(54, 1);
text.AppendLiteral("Currently ");
text.AppendFormatted(availableEventQuests.Count);
text.AppendLiteral(" event quest(s) available from Questionable.");
ImGui.TextUnformatted(text);
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
ImGui.TextUnformatted("Event Quest:");
ImGui.SameLine();
ImGui.SetNextItemWidth(400f);
string currentQuestDisplay = (string.IsNullOrEmpty(selectedEventQuestId) ? "Select Event Quest..." : (availableEventQuests.FirstOrDefault<(string, string)>(((string QuestId, string QuestName) q) => q.QuestId == selectedEventQuestId).Item2 ?? selectedEventQuestId));
if (ImGui.BeginCombo("##EventQuestCombo", currentQuestDisplay))
{
foreach (var availableEventQuest in availableEventQuests)
{
string questId2 = availableEventQuest.QuestId;
string questName2 = availableEventQuest.QuestName;
bool isSelected = selectedEventQuestId == questId2;
ImU8String label = new ImU8String(3, 2);
label.AppendFormatted(questName2);
label.AppendLiteral(" (");
label.AppendFormatted(questId2);
label.AppendLiteral(")");
if (ImGui.Selectable(label, isSelected))
{
selectedEventQuestId = questId2;
EventQuestResolver resolver2 = new EventQuestResolver(dataManager, log);
resolvedPrerequisites = resolver2.ResolveEventQuestDependencies(questId2);
log.Information($"[EventQuest] Selected quest {questId2}, resolved {resolvedPrerequisites.Count} prerequisites");
}
if (isSelected)
{
ImGui.SetItemDefaultFocus();
}
}
ImGui.EndCombo();
}
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Button, eventQuestColor);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, eventQuestAccent);
if (ImGui.Button("Refresh from Questionable"))
{
List<string> currentlyActiveEventQuests2 = plugin.QuestionableIPC.GetCurrentlyActiveEventQuests();
EventQuestResolver resolver3 = new EventQuestResolver(dataManager, log);
availableEventQuests.Clear();
foreach (string questId3 in currentlyActiveEventQuests2)
{
string questName3 = resolver3.GetQuestName(questId3);
availableEventQuests.Add((questId3, questName3));
}
lastEventQuestRefresh = DateTime.Now;
log.Information($"[EventQuest] Refreshed event quest list from Questionable: {availableEventQuests.Count} quests found");
}
ImGui.PopStyleColor(2);
ImGuiHelpers.ScaledDummy(10f);
if (!string.IsNullOrEmpty(selectedEventQuestId))
{
string questName4 = availableEventQuests.FirstOrDefault<(string, string)>(((string QuestId, string QuestName) q) => q.QuestId == selectedEventQuestId).Item2 ?? "Unknown";
ImGui.PushStyleColor(ImGuiCol.Text, eventQuestColor);
ImU8String text2 = new ImU8String(16, 1);
text2.AppendLiteral("Selected Quest: ");
text2.AppendFormatted(questName4);
ImGui.TextUnformatted(text2);
ImGui.PopStyleColor();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImU8String text3 = new ImU8String(10, 1);
text3.AppendLiteral("Quest ID: ");
text3.AppendFormatted(selectedEventQuestId);
ImGui.TextUnformatted(text3);
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
}
if (!string.IsNullOrEmpty(selectedEventQuestId) && resolvedPrerequisites.Count > 0)
{
ImGui.Separator();
ImGuiHelpers.ScaledDummy(5f);
ImGui.PushStyleColor(ImGuiCol.Text, eventQuestAccent);
ImU8String text4 = new ImU8String(17, 1);
text4.AppendLiteral("Prerequisites (");
text4.AppendFormatted(resolvedPrerequisites.Count);
text4.AppendLiteral("):");
ImGui.TextUnformatted(text4);
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(3f);
EventQuestResolver resolver4 = new EventQuestResolver(dataManager, log);
using (ImRaii.IEndObject table = ImRaii.Table("PrerequisitesTable", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg))
{
if (table.Success)
{
ImGui.TableSetupColumn("Quest Name", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Quest ID", ImGuiTableColumnFlags.WidthFixed, 80f);
ImGui.TableHeadersRow();
foreach (string prereqId in resolvedPrerequisites)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TextUnformatted(resolver4.GetQuestName(prereqId));
ImGui.TableNextColumn();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted(prereqId);
ImGui.PopStyleColor();
}
}
}
ImGuiHelpers.ScaledDummy(5f);
}
ImGuiHelpers.ScaledDummy(10f);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(10f);
ImGui.PushStyleColor(ImGuiCol.Text, eventQuestColor);
ImGui.TextUnformatted("Current Status:");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
EventQuestState state = eventQuestService.GetCurrentState();
ImU8String text5 = new ImU8String(7, 0);
text5.AppendLiteral("Phase: ");
ImGui.TextUnformatted(text5);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, state.Phase switch
{
EventQuestPhase.Idle => colorSecondary,
EventQuestPhase.QuestActive => eventQuestColor,
EventQuestPhase.Completed => colorPrimary,
EventQuestPhase.Error => colorAccent,
_ => colorSecondary,
});
ImGui.TextUnformatted(state.Phase.ToString());
ImGui.PopStyleColor();
if (!string.IsNullOrEmpty(state.CurrentCharacter))
{
ImU8String text6 = new ImU8String(19, 0);
text6.AppendLiteral("Current Character: ");
ImGui.TextUnformatted(text6);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, eventQuestColor);
ImGui.TextUnformatted(state.CurrentCharacter);
ImGui.PopStyleColor();
}
if (!string.IsNullOrEmpty(state.EventQuestName))
{
ImU8String text7 = new ImU8String(13, 0);
text7.AppendLiteral("Event Quest: ");
ImGui.TextUnformatted(text7);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, eventQuestColor);
ImU8String text8 = new ImU8String(3, 2);
text8.AppendFormatted(state.EventQuestName);
text8.AppendLiteral(" (");
text8.AppendFormatted(state.EventQuestId);
text8.AppendLiteral(")");
ImGui.TextUnformatted(text8);
ImGui.PopStyleColor();
}
if (state.DependencyQuests.Count > 0)
{
ImU8String text9 = new ImU8String(15, 2);
text9.AppendLiteral("Dependencies: ");
text9.AppendFormatted(state.DependencyIndex + 1);
text9.AppendLiteral("/");
text9.AppendFormatted(state.DependencyQuests.Count);
ImGui.TextUnformatted(text9);
}
if (!string.IsNullOrEmpty(state.NextCharacter))
{
ImU8String text10 = new ImU8String(16, 0);
text10.AppendLiteral("Next Character: ");
ImGui.TextUnformatted(text10);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted(state.NextCharacter);
ImGui.PopStyleColor();
}
if (state.Phase == EventQuestPhase.Error && !string.IsNullOrEmpty(state.ErrorMessage))
{
ImGui.PushStyleColor(ImGuiCol.Text, colorAccent);
ImU8String text11 = new ImU8String(7, 1);
text11.AppendLiteral("Error: ");
text11.AppendFormatted(state.ErrorMessage);
ImGui.TextUnformatted(text11);
ImGui.PopStyleColor();
}
if (state.SelectedCharacters.Count > 0)
{
ImGuiHelpers.ScaledDummy(5f);
float progress = (float)state.CompletedCharacters.Count / (float)state.SelectedCharacters.Count;
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
Vector2 cursorPos = ImGui.GetCursorScreenPos();
float barWidth = ImGui.GetContentRegionAvail().X;
float barHeight = 25f;
uint barBg = ImGui.ColorConvertFloat4ToU32(new Vector4(0.2f, 0.2f, 0.2f, 0.8f));
drawList.AddRectFilled(cursorPos, cursorPos + new Vector2(barWidth, barHeight), barBg, 4f);
uint barFg = ImGui.ColorConvertFloat4ToU32(new Vector4(eventQuestColor.X, eventQuestColor.Y, eventQuestColor.Z, 0.9f));
drawList.AddRectFilled(cursorPos, cursorPos + new Vector2(barWidth * progress, barHeight), barFg, 4f);
string progressText = $"{state.CompletedCharacters.Count}/{state.SelectedCharacters.Count} completed";
Vector2 textSize = ImGui.CalcTextSize(progressText);
drawList.AddText(cursorPos + new Vector2(barWidth / 2f - textSize.X / 2f, 4f), ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, 1f)), progressText);
ImGui.Dummy(new Vector2(barWidth, barHeight));
}
ImGuiHelpers.ScaledDummy(10f);
if (state.SelectedCharacters.Count > 0)
{
ImGui.Separator();
ImGuiHelpers.ScaledDummy(5f);
ImGui.PushStyleColor(ImGuiCol.Button, (eventQuestViewMode == 0) ? eventQuestColor : colorDarkBg);
ImU8String label2 = new ImU8String(12, 1);
label2.AppendLiteral("Remaining (");
label2.AppendFormatted(state.RemainingCharacters.Count);
label2.AppendLiteral(")");
if (ImGui.Button(label2))
{
eventQuestViewMode = 0;
}
ImGui.PopStyleColor();
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Button, (eventQuestViewMode == 1) ? eventQuestColor : colorDarkBg);
ImU8String label3 = new ImU8String(12, 1);
label3.AppendLiteral("Completed (");
label3.AppendFormatted(state.CompletedCharacters.Count);
label3.AppendLiteral(")");
if (ImGui.Button(label3))
{
eventQuestViewMode = 1;
}
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
List<string> displayList = ((eventQuestViewMode == 0) ? state.RemainingCharacters : state.CompletedCharacters);
if (displayList.Count > 0)
{
using ImRaii.IEndObject child = ImRaii.Child("CharacterList", new Vector2(0f, 150f), border: true);
if (child.Success)
{
foreach (string character in displayList)
{
string[] parts = character.Split('@');
string charName = ((parts.Length != 0) ? parts[0] : character);
string world = ((parts.Length > 1) ? parts[1] : "Unknown");
ImGui.PushStyleColor(ImGuiCol.Text, (eventQuestViewMode == 0) ? colorSecondary : eventQuestColor);
ImU8String text12 = new ImU8String(2, 1);
text12.AppendLiteral("• ");
text12.AppendFormatted(charName);
ImGui.TextUnformatted(text12);
ImGui.PopStyleColor();
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImU8String text13 = new ImU8String(2, 1);
text13.AppendLiteral("@ ");
text13.AppendFormatted(world);
ImGui.TextUnformatted(text13);
ImGui.PopStyleColor();
}
}
}
else
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted((eventQuestViewMode == 0) ? "No remaining characters" : "No completed characters");
ImGui.PopStyleColor();
}
ImGuiHelpers.ScaledDummy(10f);
}
ImGui.Separator();
ImGuiHelpers.ScaledDummy(10f);
if (selectedCharacters.Count == 0)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Select characters in the Characters tab to start Event Quest rotation");
ImGui.PopStyleColor();
}
else if (string.IsNullOrEmpty(selectedEventQuestId))
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Select an Event Quest from the dropdown above to start");
ImGui.PopStyleColor();
}
else if (!eventQuestService.IsRotationActive)
{
ImGui.PushStyleColor(ImGuiCol.Button, eventQuestColor);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, eventQuestAccent);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, eventQuestAccent);
if (ImGui.Button("▶ Start Event Quest Rotation", new Vector2(250f, 35f)))
{
log.Information("[EventQuest] Start button clicked!");
log.Information("[EventQuest] Event Quest ID: " + selectedEventQuestId);
log.Information($"[EventQuest] Selected characters: {selectedCharacters.Count}");
log.Information($"[EventQuest] Prerequisites: {resolvedPrerequisites.Count}");
if (eventQuestService.StartEventQuestRotation(selectedEventQuestId, selectedCharacters))
{
log.Information("[EventQuest] Rotation started successfully!");
}
else
{
log.Error("[EventQuest] Failed to start rotation");
}
}
ImGui.PopStyleColor(3);
}
else
{
ImGui.PushStyleColor(ImGuiCol.Button, colorAccent);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorAccent);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, colorAccent);
if (ImGui.Button("⏹ Abort Rotation", new Vector2(200f, 30f)))
{
eventQuestService.AbortRotation();
log.Information("[EventQuest] Rotation aborted");
}
ImGui.PopStyleColor(3);
}
ImGuiHelpers.ScaledDummy(5f);
ImGui.PushStyleColor(ImGuiCol.Button, colorPrimary);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorSecondary);
if (ImGui.Button("Refresh Characters"))
{
RefreshCharacterList();
log.Information("[EventQuest] Character list refreshed");
}
ImGui.PopStyleColor(2);
ImGuiHelpers.ScaledDummy(15f);
ImGui.Separator();
ImGuiHelpers.ScaledDummy(10f);
ImGui.PushStyleColor(ImGuiCol.Text, eventQuestColor);
ImGui.TextUnformatted("Completion Data:");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(5f);
Dictionary<string, List<string>> completionData = plugin.Configuration.EventQuestCompletionByCharacter;
if (completionData.Count == 0)
{
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("No completion data recorded yet.");
ImGui.PopStyleColor();
}
else
{
using ImRaii.IEndObject table2 = ImRaii.Table("CompletionDataTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0f, 150f));
if (table2.Success)
{
ImGui.TableSetupColumn("Event Quest ID", ImGuiTableColumnFlags.WidthFixed, 120f);
ImGui.TableSetupColumn("Completed", ImGuiTableColumnFlags.WidthFixed, 100f);
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
EventQuestResolver resolver5 = new EventQuestResolver(dataManager, log);
foreach (KeyValuePair<string, List<string>> kvp in completionData)
{
string questId4 = kvp.Key;
List<string> completedChars = kvp.Value;
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TextUnformatted(resolver5.GetQuestName(questId4));
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImU8String text14 = new ImU8String(4, 1);
text14.AppendLiteral("ID: ");
text14.AppendFormatted(questId4);
ImGui.TextUnformatted(text14);
ImGui.PopStyleColor();
ImGui.TableNextColumn();
ImGui.PushStyleColor(ImGuiCol.Text, eventQuestColor);
ImU8String text15 = new ImU8String(0, 1);
text15.AppendFormatted(completedChars.Count);
ImGui.TextUnformatted(text15);
ImGui.PopStyleColor();
ImGui.TableNextColumn();
using (ImRaii.PushId(questId4))
{
ImGui.PushStyleColor(ImGuiCol.Button, colorAccent);
if (ImGui.Button("Clear"))
{
completionData.Remove(questId4);
plugin.Configuration.Save();
log.Information("[EventQuest] Cleared completion data for quest " + questId4);
}
ImGui.PopStyleColor();
}
}
}
}
ImGuiHelpers.ScaledDummy(10f);
if (completionData.Count > 0)
{
ImGui.PushStyleColor(ImGuiCol.Button, colorAccent);
if (ImGui.Button("\ud83d\uddd1 Clear All Completion Data"))
{
completionData.Clear();
plugin.Configuration.Save();
log.Information("[EventQuest] Cleared all completion data");
}
ImGui.PopStyleColor();
}
}
private void DrawWarningTab()
{
TryWarningMenuQuestionableCheck();
ImGuiHelpers.ScaledDummy(50f);
float windowWidth = ImGui.GetContentRegionAvail().X;
string text2 = "Please ensure you are using Questionable from WigglyMuffin!";
ImGui.SetWindowFontScale(2f);
Vector2 textSize = ImGui.CalcTextSize("Incorrect Questionable Version.");
Vector2 textSize2 = ImGui.CalcTextSize(text2);
ImGui.SetCursorPosX((windowWidth - textSize.X) * 0.5f);
ImGui.PushStyleColor(ImGuiCol.Text, colorAccent);
ImGui.TextUnformatted("Incorrect Questionable Version.");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(10f);
float flashAlpha = (MathF.Sin((float)ImGui.GetTime() * 5f) + 1f) * 0.5f;
Vector4 flashColor = new Vector4(1f, 0f, 0f, flashAlpha);
ImGui.SetCursorPosX((windowWidth - textSize2.X) * 0.5f);
ImGui.TextColored(in flashColor, text2);
ImGuiHelpers.ScaledDummy(30f);
ImGui.SetWindowFontScale(1f);
if (warningMenuRetryAttempts < 4)
{
double elapsedSeconds = (DateTime.Now - warningMenuRetryStartTime).TotalSeconds;
int nextRetryIn = ((warningMenuRetryAttempts < warningMenuRetryDelaysSeconds.Length) ? (warningMenuRetryDelaysSeconds[warningMenuRetryAttempts] - (int)elapsedSeconds) : 0);
ImGui.PushStyleColor(ImGuiCol.Text, colorPrimary);
if (nextRetryIn > 0)
{
string obj = $"Checking for Questionable... (Retry {warningMenuRetryAttempts}/{4} in {nextRetryIn}s)";
ImGui.SetCursorPosX((windowWidth - ImGui.CalcTextSize(obj).X) * 0.5f);
ImGui.TextUnformatted(obj);
}
else
{
string obj2 = $"Checking for Questionable... (Attempt {warningMenuRetryAttempts + 1}/{4})";
ImGui.SetCursorPosX((windowWidth - ImGui.CalcTextSize(obj2).X) * 0.5f);
ImGui.TextUnformatted(obj2);
}
ImGui.PopStyleColor();
ImGui.SetCursorPosX((windowWidth - ImGui.CalcTextSize("Waiting for Questionable IPC to become available...").X) * 0.5f);
ImGui.PushStyleColor(ImGuiCol.Text, colorSecondary);
ImGui.TextUnformatted("Waiting for Questionable IPC to become available...");
ImGui.PopStyleColor();
return;
}
float buttonWidth = 120f;
float buttonHeight = 40f;
ImGui.SetCursorPosX((windowWidth - buttonWidth) * 0.5f);
ImGui.PushStyleColor(ImGuiCol.Button, colorPrimary);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, colorSecondary);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, colorSecondary);
if (ImGui.Button("Refresh", new Vector2(buttonWidth, buttonHeight)))
{
if (plugin.QuestionableIPC.TryEnsureAvailableSilent() && plugin.QuestionableIPC.ValidateFeatureCompatibility())
{
selectedTab = 5;
}
else
{
warningMenuRetryAttempts = 0;
warningMenuRetryStartTime = DateTime.MinValue;
warningMenuRetryCycleComplete = false;
}
}
ImGui.PopStyleColor(3);
}
}