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 particles = new List(); private int selectedTab; private int selectedDCFilter; private bool charactersExpanded = true; private bool menuExpanded = true; private bool isMinimized; private List registeredCharacters = new List(); private Dictionary characterSelection = new Dictionary(); private Dictionary characterProgressCache = new Dictionary(); 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> charactersByDataCenter = new Dictionary>(); private List availableDataCenters = new List { "All", "EU", "NA", "JP", "OCE" }; private bool showSelectWorldDialog; private bool showDeselectWorldDialog; private string selectedWorldForBulkAction = ""; private List availableWorlds = new List(); 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 resolvedPrerequisites = new List(); private int eventQuestViewMode; private DateTime lastEventQuestRefresh = DateTime.MinValue; private readonly Dictionary> dataCenterWorlds = new Dictionary> { { "Aether", new List { "Adamantoise", "Cactuar", "Faerie", "Gilgamesh", "Jenova", "Midgardsormr", "Sargatanas", "Siren" } }, { "Primal", new List { "Behemoth", "Excalibur", "Exodus", "Hyperion", "Lamia", "Leviathan", "Ultros" } }, { "Crystal", new List { "Balmung", "Brynhildr", "Coeurl", "Diabolos", "Goblin", "Malboro", "Mateus", "Zalera" } }, { "Dynamis", new List { "Halicarnassus", "Maduin", "Marilith", "Seraph" } }, { "Chaos", new List { "Cerberus", "Louisoix", "Moogle", "Omega", "Phantom", "Ragnarok", "Sagittarius", "Spriggan" } }, { "Light", new List { "Alpha", "Lich", "Odin", "Phoenix", "Raiden", "Shiva", "Twintania", "Zodiark" } }, { "Materia", new List { "Bismarck", "Ravana", "Sephirot", "Sophia", "Zurvan" } }, { "Elemental", new List { "Aegis", "Atomos", "Carbuncle", "Garuda", "Gungnir", "Kujata", "Tonberry", "Typhon" } }, { "Gaia", new List { "Alexander", "Bahamut", "Durandal", "Fenrir", "Ifrit", "Ridill", "Tiamat", "Ultima" } }, { "Mana", new List { "Anima", "Asura", "Chocobo", "Hades", "Ixion", "Masamune", "Pandaemonium", "Titan" } }, { "Meteor", new List { "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 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 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 worlds = dataCenterWorlds.GetValueOrDefault(selectedDataCenter) ?? new List(); 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 worlds2 = dataCenterWorlds.GetValueOrDefault(selectedDataCenter) ?? new List(); 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 availableWorlds = dataCenterWorlds.GetValueOrDefault(selectedDataCenter) ?? new List(); 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 worlds3 = dataCenterWorlds.GetValueOrDefault(selectedDataCenter) ?? new List(); 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 list = new List { "Auto (First Available)" }; foreach (var item5 in availableHelpers) { string item = item5.Item1; ushort item2 = item5.Item2; ExcelSheet excelSheet = Plugin.DataManager.GetExcelSheet(); 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 excelSheet2 = Plugin.DataManager.GetExcelSheet(); 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 list2 = new List(); 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)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 excelSheet = Plugin.DataManager.GetExcelSheet(); 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 list = (Plugin.Instance?.GetChauffeurMode())?.GetDiscoveredQuesters() ?? new List(); 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 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 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 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 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 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 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 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 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 characters) { List 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 completedQuests = questRotationService.GetCompletedQuestsByCharacter(character); int currentExpansionIndex = (int)MSQExpansionData.GetCurrentExpansion(completedQuests); Dictionary 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 GetWorldsForCurrentDatacenter() { if (selectedDCFilter == 0) { return availableWorlds; } string dcName = selectedDCFilter switch { 1 => "EU", 2 => "NA", 3 => "JP", 4 => "OCE", _ => "All", }; List charactersForDataCenter = dataCenterService.GetCharactersForDataCenter(registeredCharacters, dcName, charactersByDataCenter); HashSet worlds = new HashSet(); foreach (string item in charactersForDataCenter) { string[] parts = item.Split('@'); if (parts.Length > 1) { worlds.Add(parts[1]); } } return worlds.ToList(); } private List GetFilteredCharacters() { List 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 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 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 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 selectedCharacters = (from keyValuePair in characterSelection where keyValuePair.Value select keyValuePair.Key).ToList(); if ((DateTime.Now - lastEventQuestRefresh).TotalSeconds > 5.0 || availableEventQuests.Count == 0) { List 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 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 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> 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> kvp in completionData) { string questId4 = kvp.Key; List 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); } }