ActiveQuestUI.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152
  1. using UnityEngine;
  2. using UnityEngine.UIElements;
  3. using System.Collections.Generic;
  4. using System.Collections;
  5. using Unity.VisualScripting;
  6. using System;
  7. public class ActiveQuestUI : MonoBehaviour, IClickBlocker
  8. {
  9. [Header("UI References")]
  10. public UIDocument uiDocument;
  11. // Main UI elements
  12. private VisualElement questTracker;
  13. private Label trackerTitle;
  14. private Button questLogButton;
  15. private Button toggleTrackerButton;
  16. private ScrollView activeQuestsList;
  17. private Label noQuestsMessage;
  18. private VisualElement quickActions;
  19. private Button showAllButton;
  20. private Button trackLocationButton;
  21. private Button abandonQuestButton;
  22. // Popup elements
  23. private VisualElement questDetailsPopup;
  24. private VisualElement popupContent;
  25. private Label popupTitle;
  26. private Button closePopupButton;
  27. private VisualElement popupDetails;
  28. private VisualElement questInfo;
  29. private VisualElement objectivesList;
  30. private VisualElement rewardsList;
  31. private VisualElement popupActions;
  32. private Button trackButton;
  33. private Button navigateButton;
  34. // Notification elements
  35. private VisualElement completionNotification;
  36. private VisualElement progressNotification;
  37. // Exposed flag other systems can check to know UI is capturing pointer
  38. // State
  39. private ActiveQuest selectedQuest;
  40. private bool isUIVisible = false;
  41. private List<VisualElement> questEntries = new List<VisualElement>();
  42. [System.NonSerialized]
  43. private bool recentlyHandledClick = false; // Flag to prevent map clicks when UI handles them
  44. public bool IsVisible => isUIVisible;
  45. public bool IsBlockingClick(Vector2 screenPosition)
  46. {
  47. // Primary method: Check if we recently handled a UI click
  48. if (recentlyHandledClick)
  49. {
  50. Debug.Log("🚫 ActiveQuestUI: Blocking click due to recent UI interaction");
  51. return true;
  52. }
  53. // If UI is not visible, don't block anything
  54. if (!isUIVisible)
  55. {
  56. return false;
  57. }
  58. // Dynamic coordinate-based approach: Block clicks in the quest tracker area
  59. // This needs to account for the expanded quest panel when it shows more options
  60. float questPanelWidth = 320f; // Approximate width of quest panel
  61. float questPanelHeight = 250f; // Larger height to cover expanded panel (was 120f)
  62. float questPanelX = 20f; // Left edge
  63. float questPanelY = Screen.height - questPanelHeight - 20f; // Top edge (converted to screen coords)
  64. // Alternative: Try to get actual bounds from the quest tracker if available
  65. if (questTracker != null)
  66. {
  67. try
  68. {
  69. // Try to use the actual UI element bounds if possible
  70. var bounds = questTracker.worldBound;
  71. if (bounds.width > 0 && bounds.height > 0)
  72. {
  73. // Convert UI bounds to screen coordinates
  74. questPanelX = bounds.x;
  75. questPanelY = Screen.height - bounds.y - bounds.height;
  76. questPanelWidth = bounds.width;
  77. questPanelHeight = bounds.height;
  78. Debug.Log($"🔍 Using actual UI bounds: x={questPanelX}, y={questPanelY}, w={questPanelWidth}, h={questPanelHeight}");
  79. }
  80. }
  81. catch (System.Exception e)
  82. {
  83. Debug.LogWarning($"Failed to get UI bounds, using fallback: {e.Message}");
  84. }
  85. }
  86. bool inQuestArea = screenPosition.x >= questPanelX &&
  87. screenPosition.x <= questPanelX + questPanelWidth &&
  88. screenPosition.y >= questPanelY &&
  89. screenPosition.y <= questPanelY + questPanelHeight;
  90. if (inQuestArea)
  91. {
  92. Debug.Log($"🛡️ ActiveQuestUI: Blocking click in quest area at {screenPosition} (quest area: {questPanelX}-{questPanelX + questPanelWidth}, {questPanelY}-{questPanelY + questPanelHeight})");
  93. return true;
  94. }
  95. // Don't block clicks outside the quest area
  96. return false;
  97. }
  98. void OnEnable() => ClickBlockingHelper.RegisterWithClickManager(this);
  99. void OnDisable() => ClickBlockingHelper.UnregisterWithClickManager(this);
  100. /// <summary>
  101. /// Sets a temporary flag to block travel system clicks when UI handles a click event.
  102. /// This is much more reliable than trying to calculate coordinates manually.
  103. /// </summary>
  104. private void SetClickFlag()
  105. {
  106. ClickBlockingHelper.SetClickFlag("ActiveQuestUI", this, (flag) => recentlyHandledClick = flag);
  107. }
  108. private void Start()
  109. {
  110. Debug.Log("ActiveQuestUI: Start called");
  111. InitializeUI();
  112. SubscribeToEvents();
  113. // Delay quest refresh to ensure QuestManager is fully initialized
  114. StartCoroutine(DelayedQuestRefresh());
  115. }
  116. private IEnumerator DelayedQuestRefresh()
  117. {
  118. yield return new WaitForEndOfFrame();
  119. Debug.Log("ActiveQuestUI: Performing delayed quest refresh");
  120. RefreshQuestList();
  121. }
  122. private void InitializeUI()
  123. {
  124. if (uiDocument == null)
  125. uiDocument = GetComponent<UIDocument>();
  126. var root = uiDocument.rootVisualElement;
  127. if (root == null)
  128. {
  129. return;
  130. }
  131. // Ensure root doesn't block other UI - set to ignore picking
  132. root.pickingMode = PickingMode.Ignore;
  133. // Ensure this UI has proper sorting order to be above map but below modals
  134. if (uiDocument.sortingOrder == 0)
  135. {
  136. uiDocument.sortingOrder = 10; // Above map, below modals like TravelUI
  137. }
  138. // Get main elements
  139. questTracker = root.Q<VisualElement>("quest-tracker");
  140. trackerTitle = root.Q<Label>("tracker-title");
  141. questLogButton = root.Q<Button>("quest-log-button");
  142. toggleTrackerButton = root.Q<Button>("toggle-tracker-button");
  143. activeQuestsList = root.Q<ScrollView>("active-quests-list");
  144. noQuestsMessage = root.Q<Label>("no-quests-message");
  145. quickActions = root.Q<VisualElement>("quick-actions");
  146. showAllButton = root.Q<Button>("show-all-button");
  147. trackLocationButton = root.Q<Button>("track-location-button");
  148. abandonQuestButton = root.Q<Button>("abandon-quest-button");
  149. // Get popup elements
  150. questDetailsPopup = root.Q<VisualElement>("quest-details-popup");
  151. popupContent = root.Q<VisualElement>("popup-content");
  152. popupTitle = root.Q<Label>("popup-title");
  153. closePopupButton = root.Q<Button>("close-popup-button");
  154. popupDetails = root.Q<VisualElement>("popup-details");
  155. questInfo = root.Q<VisualElement>("quest-info");
  156. objectivesList = root.Q<VisualElement>("objectives-list");
  157. rewardsList = root.Q<VisualElement>("rewards-list");
  158. popupActions = root.Q<VisualElement>("popup-actions");
  159. trackButton = root.Q<Button>("track-button");
  160. navigateButton = root.Q<Button>("navigate-button");
  161. // Get notification elements
  162. completionNotification = root.Q<VisualElement>("completion-notification");
  163. progressNotification = root.Q<VisualElement>("progress-notification");
  164. // Set up event handlers
  165. questLogButton.clicked += () => OpenFullQuestLog();
  166. toggleTrackerButton.clicked += () => ToggleQuestTracker();
  167. showAllButton.clicked += () => OpenFullQuestLog();
  168. trackLocationButton.clicked += () => TrackSelectedQuest();
  169. abandonQuestButton.clicked += () => AbandonSelectedQuest();
  170. closePopupButton.clicked += () => CloseQuestDetails();
  171. trackButton.clicked += () => TrackSelectedQuest();
  172. navigateButton.clicked += () => NavigateToQuest();
  173. // Initially hide popup and notifications
  174. if (questDetailsPopup != null)
  175. questDetailsPopup.style.display = DisplayStyle.None;
  176. if (completionNotification != null)
  177. completionNotification.style.display = DisplayStyle.None;
  178. if (progressNotification != null)
  179. progressNotification.style.display = DisplayStyle.None;
  180. // Update tracker title with count
  181. UpdateTrackerTitle();
  182. // Add TravelUI-style click blocking
  183. AddClickBlockingOverlay(root);
  184. // Add mouse event blocking to prevent clicks from going through
  185. if (questTracker != null)
  186. {
  187. // Ensure the quest tracker can receive mouse events and has higher priority
  188. questTracker.pickingMode = PickingMode.Position;
  189. questTracker.RegisterCallback<MouseDownEvent>(evt =>
  190. {
  191. Debug.Log("QuestTracker MouseDownEvent - stopping propagation");
  192. SetClickFlag(); // Set flag to block travel system
  193. evt.StopPropagation();
  194. });
  195. questTracker.RegisterCallback<MouseUpEvent>(evt =>
  196. {
  197. Debug.Log("QuestTracker MouseUpEvent - stopping propagation");
  198. evt.StopPropagation();
  199. });
  200. questTracker.RegisterCallback<ClickEvent>(evt =>
  201. {
  202. Debug.Log("QuestTracker ClickEvent - stopping propagation");
  203. SetClickFlag(); // Set flag to block travel system
  204. evt.StopPropagation();
  205. });
  206. // Also block pointer events for UI Toolkit compatibility
  207. questTracker.RegisterCallback<PointerDownEvent>(evt =>
  208. {
  209. Debug.Log("QuestTracker PointerDownEvent - stopping propagation");
  210. SetClickFlag(); // Set flag to block travel system
  211. evt.StopPropagation();
  212. });
  213. questTracker.RegisterCallback<PointerUpEvent>(evt =>
  214. {
  215. Debug.Log("QuestTracker PointerUpEvent - stopping propagation");
  216. evt.StopPropagation();
  217. });
  218. } // Also block events on popup
  219. if (questDetailsPopup != null)
  220. {
  221. questDetailsPopup.RegisterCallback<MouseDownEvent>(evt =>
  222. {
  223. SetClickFlag(); // Set flag to block travel system
  224. evt.StopPropagation();
  225. });
  226. questDetailsPopup.RegisterCallback<MouseUpEvent>(evt =>
  227. {
  228. evt.StopPropagation();
  229. });
  230. questDetailsPopup.RegisterCallback<ClickEvent>(evt =>
  231. {
  232. SetClickFlag(); // Set flag to block travel system
  233. evt.StopPropagation();
  234. });
  235. }
  236. isUIVisible = true;
  237. }
  238. #region Context Menu Debug Methods
  239. [ContextMenu("Refresh Quest List")]
  240. private void DebugRefreshQuestList()
  241. {
  242. RefreshQuestList();
  243. }
  244. [ContextMenu("Debug Quest System")]
  245. private void DebugQuestSystem()
  246. {
  247. Debug.Log("=== QUEST SYSTEM DEBUG ===");
  248. if (QuestManager.Instance == null)
  249. {
  250. Debug.LogError("QuestManager.Instance is NULL!");
  251. return;
  252. }
  253. Debug.Log("QuestManager.Instance exists");
  254. var activeQuests = QuestManager.Instance.GetActiveQuests();
  255. Debug.Log($"Active quests count: {activeQuests.Count}");
  256. foreach (var quest in activeQuests)
  257. {
  258. Debug.Log($"Active Quest: {quest.questData.questTitle} - Status: {quest.status}");
  259. }
  260. // Check UI elements
  261. Debug.Log($"questTracker: {(questTracker != null ? "EXISTS" : "NULL")}");
  262. Debug.Log($"activeQuestsList: {(activeQuestsList != null ? "EXISTS" : "NULL")}");
  263. Debug.Log($"trackerTitle: {(trackerTitle != null ? "EXISTS" : "NULL")}");
  264. if (trackerTitle != null)
  265. {
  266. Debug.Log($"Tracker title text: '{trackerTitle.text}'");
  267. }
  268. }
  269. [ContextMenu("Force Load Quest Data")]
  270. private void DebugForceLoadQuestData()
  271. {
  272. if (QuestManager.Instance != null)
  273. {
  274. Debug.Log("Forcing QuestManager to load quest data...");
  275. QuestManager.Instance.LoadQuestData();
  276. RefreshQuestList();
  277. }
  278. else
  279. {
  280. Debug.LogError("QuestManager.Instance is null!");
  281. }
  282. }
  283. [ContextMenu("Check PlayerPrefs Quest Data")]
  284. private void DebugCheckPlayerPrefsQuestData()
  285. {
  286. Debug.Log("=== CHECKING QUEST PERSISTENCE ===");
  287. if (PlayerPrefs.HasKey("QuestSaveData"))
  288. {
  289. string questSaveData = PlayerPrefs.GetString("QuestSaveData");
  290. Debug.Log($"Found QuestSaveData in PlayerPrefs: {questSaveData}");
  291. if (string.IsNullOrEmpty(questSaveData))
  292. {
  293. Debug.LogWarning("QuestSaveData exists but is empty!");
  294. }
  295. }
  296. else
  297. {
  298. Debug.LogWarning("No QuestSaveData found in PlayerPrefs!");
  299. }
  300. }
  301. [ContextMenu("Test Quest Entry")]
  302. private void DebugTestQuestEntry()
  303. {
  304. if (QuestManager.Instance != null)
  305. {
  306. var activeQuests = QuestManager.Instance.GetActiveQuests();
  307. Debug.Log($"Active quests found: {activeQuests.Count}");
  308. foreach (var quest in activeQuests)
  309. {
  310. Debug.Log($"- {quest.questData.questTitle}");
  311. }
  312. }
  313. else
  314. {
  315. Debug.LogWarning("No QuestManager found!");
  316. }
  317. }
  318. [ContextMenu("Force Show Popup")]
  319. private void DebugForceShowPopup()
  320. {
  321. if (questDetailsPopup != null)
  322. {
  323. questDetailsPopup.style.display = DisplayStyle.Flex;
  324. isUIVisible = true;
  325. Debug.Log("Forced popup to show");
  326. }
  327. }
  328. [ContextMenu("Force Hide Popup")]
  329. private void DebugForceHidePopup()
  330. {
  331. if (questDetailsPopup != null)
  332. {
  333. questDetailsPopup.style.display = DisplayStyle.None;
  334. Debug.Log("Forced popup to hide");
  335. isUIVisible = false;
  336. }
  337. }
  338. [ContextMenu("Toggle Quest Tracker Visibility")]
  339. private void DebugToggleQuestTracker()
  340. {
  341. if (questTracker != null)
  342. {
  343. bool isVisible = questTracker.style.display != DisplayStyle.None;
  344. questTracker.style.display = isVisible ? DisplayStyle.None : DisplayStyle.Flex;
  345. Debug.Log($"Quest tracker {(isVisible ? "hidden" : "shown")}");
  346. isUIVisible = isVisible;
  347. }
  348. }
  349. private bool isTrackerMinimized = false;
  350. private void ToggleQuestTracker()
  351. {
  352. isTrackerMinimized = !isTrackerMinimized;
  353. if (isTrackerMinimized)
  354. {
  355. // Hide the quest list and actions, only show header
  356. if (activeQuestsList != null)
  357. activeQuestsList.style.display = DisplayStyle.None;
  358. if (quickActions != null)
  359. quickActions.style.display = DisplayStyle.None;
  360. if (noQuestsMessage != null)
  361. noQuestsMessage.style.display = DisplayStyle.None;
  362. // Change button text to show expand option
  363. if (toggleTrackerButton != null)
  364. toggleTrackerButton.text = "+";
  365. // Make tracker smaller
  366. if (questTracker != null)
  367. {
  368. questTracker.style.height = 40;
  369. questTracker.style.maxHeight = 40;
  370. }
  371. }
  372. else
  373. {
  374. // Show everything again
  375. if (activeQuestsList != null)
  376. activeQuestsList.style.display = DisplayStyle.Flex;
  377. if (quickActions != null)
  378. quickActions.style.display = DisplayStyle.Flex;
  379. // Show no quests message if needed
  380. var activeQuests = QuestManager.Instance?.GetActiveQuests();
  381. if (activeQuests == null || activeQuests.Count == 0)
  382. {
  383. if (noQuestsMessage != null)
  384. noQuestsMessage.style.display = DisplayStyle.Flex;
  385. }
  386. // Change button text back
  387. if (toggleTrackerButton != null)
  388. toggleTrackerButton.text = "−";
  389. // Restore tracker size
  390. if (questTracker != null)
  391. {
  392. questTracker.style.height = StyleKeyword.Auto;
  393. questTracker.style.maxHeight = 350;
  394. }
  395. }
  396. }
  397. [ContextMenu("Reset Quest Tracker Position")]
  398. private void DebugResetQuestTrackerPosition()
  399. {
  400. if (questTracker != null)
  401. {
  402. questTracker.style.position = Position.Absolute;
  403. questTracker.style.top = 20;
  404. questTracker.style.left = 20;
  405. questTracker.style.right = StyleKeyword.Auto;
  406. questTracker.style.width = 250;
  407. Debug.Log("Reset quest tracker to left side position");
  408. }
  409. }
  410. #endregion
  411. private void SubscribeToEvents()
  412. {
  413. Debug.Log("ActiveQuestUI: SubscribeToEvents called");
  414. if (QuestManager.Instance != null)
  415. {
  416. Debug.Log("ActiveQuestUI: QuestManager found, subscribing to events");
  417. QuestManager.Instance.OnQuestAccepted += HandleQuestAccepted;
  418. QuestManager.Instance.OnQuestCompleted += HandleQuestCompleted;
  419. QuestManager.Instance.OnQuestAbandoned += HandleQuestAbandoned;
  420. QuestManager.Instance.OnQuestFailed += HandleQuestFailed;
  421. }
  422. else
  423. {
  424. Debug.LogWarning("ActiveQuestUI: QuestManager.Instance is null during SubscribeToEvents!");
  425. }
  426. }
  427. private void OnDestroy()
  428. {
  429. // Unregister from ClickManager
  430. ClickBlockingHelper.UnregisterWithClickManager(this);
  431. // Unregister from QuestManager events
  432. if (QuestManager.Instance != null)
  433. {
  434. QuestManager.Instance.OnQuestAccepted -= HandleQuestAccepted;
  435. QuestManager.Instance.OnQuestCompleted -= HandleQuestCompleted;
  436. QuestManager.Instance.OnQuestAbandoned -= HandleQuestAbandoned;
  437. QuestManager.Instance.OnQuestFailed -= HandleQuestFailed;
  438. }
  439. }
  440. #region Quest List Management
  441. private void RefreshQuestList()
  442. {
  443. Debug.Log("ActiveQuestUI: RefreshQuestList called");
  444. ClearQuestList();
  445. if (QuestManager.Instance == null)
  446. {
  447. Debug.LogWarning("ActiveQuestUI: QuestManager.Instance is null");
  448. ShowNoQuestsMessage();
  449. return;
  450. }
  451. var activeQuests = QuestManager.Instance.GetActiveQuests();
  452. Debug.Log($"ActiveQuestUI: Found {activeQuests.Count} active quests");
  453. if (activeQuests.Count == 0)
  454. {
  455. Debug.Log("ActiveQuestUI: No active quests found, showing no quests message");
  456. ShowNoQuestsMessage();
  457. return;
  458. }
  459. Debug.Log("ActiveQuestUI: Creating quest entries");
  460. HideNoQuestsMessage();
  461. foreach (var quest in activeQuests)
  462. {
  463. Debug.Log($"ActiveQuestUI: Creating entry for quest: {quest.questData.questTitle}");
  464. CreateQuestEntry(quest);
  465. }
  466. UpdateTrackerTitle();
  467. UpdateQuickActions();
  468. }
  469. private void ClearQuestList()
  470. {
  471. questEntries.Clear();
  472. activeQuestsList?.Clear();
  473. }
  474. private void ShowNoQuestsMessage()
  475. {
  476. if (noQuestsMessage != null)
  477. noQuestsMessage.style.display = DisplayStyle.Flex;
  478. if (quickActions != null)
  479. quickActions.style.display = DisplayStyle.None;
  480. }
  481. private void HideNoQuestsMessage()
  482. {
  483. if (noQuestsMessage != null)
  484. noQuestsMessage.style.display = DisplayStyle.None;
  485. if (quickActions != null)
  486. quickActions.style.display = DisplayStyle.Flex;
  487. }
  488. private void CreateQuestEntry(ActiveQuest quest)
  489. {
  490. var entry = new VisualElement();
  491. entry.AddToClassList("quest-entry-compact");
  492. // Header with title and urgency
  493. var header = new VisualElement();
  494. header.AddToClassList("quest-compact-header");
  495. var title = new Label(quest.questData.questTitle);
  496. title.AddToClassList("quest-compact-title");
  497. var urgency = new VisualElement();
  498. urgency.AddToClassList("quest-compact-urgency");
  499. urgency.AddToClassList(GetUrgencyClass(quest.GetUrgencyLevel()));
  500. header.Add(title);
  501. header.Add(urgency);
  502. // Info row with progress and time
  503. var info = new VisualElement();
  504. info.AddToClassList("quest-compact-info");
  505. var progress = new Label($"{quest.GetCompletedObjectivesCount()}/{quest.questData.goals.Count} objectives");
  506. progress.AddToClassList("quest-compact-progress"); var timeRemaining = new Label(GetTimeRemainingText(quest));
  507. timeRemaining.AddToClassList("quest-compact-time");
  508. info.Add(progress);
  509. info.Add(timeRemaining);
  510. entry.Add(header);
  511. entry.Add(info);
  512. // Click handler
  513. entry.RegisterCallback<ClickEvent>(evt => SelectQuest(quest, entry));
  514. activeQuestsList.Add(entry);
  515. questEntries.Add(entry);
  516. }
  517. private string GetUrgencyClass(QuestUrgency urgency)
  518. {
  519. switch (urgency)
  520. {
  521. case QuestUrgency.Low: return "urgency-low";
  522. case QuestUrgency.Medium: return "urgency-medium";
  523. case QuestUrgency.High: return "urgency-high";
  524. case QuestUrgency.Critical: return "urgency-critical";
  525. default: return "urgency-low";
  526. }
  527. }
  528. private string GetTimeRemainingText(ActiveQuest quest)
  529. {
  530. if (quest.questData.timeLimit <= 0)
  531. return "No time limit";
  532. float remaining = quest.GetTimeRemaining();
  533. if (remaining <= 0)
  534. return "EXPIRED";
  535. if (remaining < 1f)
  536. return "< 1 hour";
  537. if (remaining < 24f)
  538. return $"{Mathf.CeilToInt(remaining)}h";
  539. return $"{Mathf.CeilToInt(remaining / 24f)}d";
  540. }
  541. private void AddClickBlockingOverlay(VisualElement root)
  542. {
  543. // Create a click blocker similar to TravelUI
  544. var clickBlocker = new VisualElement();
  545. clickBlocker.name = "ClickBlocker";
  546. clickBlocker.AddToClassList("click-blocker");
  547. // Position as first child (behind everything else)
  548. root.Insert(0, clickBlocker);
  549. // Block all mouse events
  550. clickBlocker.RegisterCallback<ClickEvent>(evt =>
  551. {
  552. evt.StopPropagation();
  553. });
  554. clickBlocker.RegisterCallback<MouseDownEvent>(evt =>
  555. {
  556. evt.StopPropagation();
  557. });
  558. clickBlocker.RegisterCallback<MouseUpEvent>(evt =>
  559. {
  560. evt.StopPropagation();
  561. });
  562. }
  563. private void UpdateTrackerTitle()
  564. {
  565. Debug.Log("UpdateTrackerTitle called");
  566. if (trackerTitle == null)
  567. {
  568. Debug.LogWarning("trackerTitle is null!");
  569. return;
  570. }
  571. int activeCount = QuestManager.Instance?.GetActiveQuests().Count ?? 0;
  572. string newTitle = $"Active Quests ({activeCount})";
  573. Debug.Log($"Setting tracker title to: '{newTitle}'");
  574. trackerTitle.text = newTitle;
  575. // Force update the display
  576. if (questTracker != null)
  577. {
  578. questTracker.style.display = DisplayStyle.Flex;
  579. Debug.Log("Ensured questTracker is visible");
  580. }
  581. }
  582. #endregion
  583. #region Selection and Actions
  584. private void SelectQuest(ActiveQuest quest, VisualElement entry)
  585. {
  586. // Deselect previous
  587. foreach (var questEntry in questEntries)
  588. {
  589. questEntry.RemoveFromClassList("selected");
  590. }
  591. // Select new
  592. entry.AddToClassList("selected");
  593. selectedQuest = quest;
  594. UpdateQuickActions();
  595. }
  596. private void UpdateQuickActions()
  597. {
  598. if (quickActions == null) return;
  599. bool hasSelection = selectedQuest != null;
  600. trackLocationButton?.SetEnabled(hasSelection);
  601. abandonQuestButton?.SetEnabled(hasSelection);
  602. }
  603. private void TrackSelectedQuest()
  604. {
  605. if (selectedQuest == null) return;
  606. // Focus camera on quest location
  607. var questMapMarkerManager = FindFirstObjectByType<QuestMapMarkerManager>();
  608. if (questMapMarkerManager != null)
  609. {
  610. questMapMarkerManager.FocusOnQuest(selectedQuest);
  611. Debug.Log($"Tracking quest: {selectedQuest.questData.questTitle}");
  612. }
  613. // Show quest details popup
  614. ShowQuestDetails(selectedQuest);
  615. }
  616. private void AbandonSelectedQuest()
  617. {
  618. if (selectedQuest == null) return;
  619. // Show confirmation dialog (simplified for now)
  620. if (QuestManager.Instance != null)
  621. {
  622. QuestManager.Instance.AbandonQuest(selectedQuest.questId);
  623. }
  624. }
  625. private void OpenFullQuestLog()
  626. {
  627. if (selectedQuest != null)
  628. {
  629. ShowQuestDetails(selectedQuest);
  630. }
  631. else
  632. {
  633. // Open the Adventures Guild UI or a quest log scene
  634. Debug.Log("Opening full quest log...");
  635. }
  636. }
  637. private void NavigateToQuest()
  638. {
  639. if (selectedQuest == null) return;
  640. // Set travel destination to quest location
  641. // var travelSystem = FindFirstObjectByType<TravelSystem>();
  642. // if (travelSystem != null)
  643. // {
  644. // Vector2 questLocation = new Vector2(selectedQuest.questData.targetLocationX, selectedQuest.questData.targetLocationY);
  645. // travelSystem.SetDestination(questLocation);
  646. // Debug.Log($"Navigating to quest at {questLocation}");
  647. // }
  648. Vector2 questLocation = new Vector2(selectedQuest.questData.targetLocationX, selectedQuest.questData.targetLocationY);
  649. Debug.Log($"Navigating to quest at {questLocation}");
  650. }
  651. #endregion
  652. #region Quest Details Popup
  653. private void ShowQuestDetails(ActiveQuest quest)
  654. {
  655. if (questDetailsPopup == null) return;
  656. // Populate quest info
  657. PopulateQuestDetails(quest);
  658. // Show popup
  659. if (questDetailsPopup != null)
  660. questDetailsPopup.style.display = DisplayStyle.Flex;
  661. }
  662. private void CloseQuestDetails()
  663. {
  664. if (questDetailsPopup != null)
  665. questDetailsPopup.style.display = DisplayStyle.None;
  666. }
  667. private void PopulateQuestDetails(ActiveQuest quest)
  668. {
  669. if (popupTitle != null)
  670. popupTitle.text = quest.questData.questTitle;
  671. if (questInfo == null) return;
  672. questInfo.Clear();
  673. // Title and difficulty
  674. var title = new Label(quest.questData.questTitle);
  675. title.AddToClassList("quest-title");
  676. questInfo.Add(title);
  677. var difficulty = new Label(quest.questData.difficulty.ToString().ToUpper());
  678. difficulty.AddToClassList("quest-difficulty");
  679. difficulty.AddToClassList($"difficulty-{quest.questData.difficulty.ToString().ToLower()}");
  680. questInfo.Add(difficulty);
  681. // Status bar
  682. var status = new VisualElement();
  683. status.AddToClassList("quest-status");
  684. var timeLabel = new Label(GetDetailedTimeText(quest));
  685. timeLabel.AddToClassList("time-remaining");
  686. var locationLabel = new Label($"Location: {quest.questData.targetLocationName}");
  687. locationLabel.AddToClassList("quest-location");
  688. status.Add(timeLabel);
  689. status.Add(locationLabel);
  690. questInfo.Add(status);
  691. // Description
  692. var descSection = new VisualElement();
  693. descSection.AddToClassList("section");
  694. var descHeader = new Label("Description");
  695. descHeader.AddToClassList("section-header");
  696. descSection.Add(descHeader);
  697. var description = new Label(quest.questData.description);
  698. description.AddToClassList("quest-description");
  699. descSection.Add(description);
  700. questInfo.Add(descSection);
  701. // Objectives
  702. PopulateObjectives(quest);
  703. // Rewards
  704. PopulateRewards(quest);
  705. }
  706. private void PopulateObjectives(ActiveQuest quest)
  707. {
  708. if (objectivesList == null) return;
  709. objectivesList.Clear();
  710. var section = new VisualElement();
  711. section.AddToClassList("section");
  712. var header = new Label("Objectives");
  713. header.AddToClassList("section-header");
  714. section.Add(header);
  715. // Progress bar
  716. var progressContainer = new VisualElement();
  717. progressContainer.AddToClassList("progress-container");
  718. var progressBar = new VisualElement();
  719. progressBar.AddToClassList("progress-bar");
  720. var progressFill = new VisualElement();
  721. progressFill.AddToClassList("progress-fill");
  722. int completed = quest.GetCompletedObjectivesCount();
  723. int total = quest.questData.objectives.Count;
  724. float percentage = total > 0 ? (float)completed / total * 100f : 0f;
  725. progressFill.style.width = new Length(percentage, LengthUnit.Percent);
  726. progressBar.Add(progressFill);
  727. var progressText = new Label($"{completed}/{total}");
  728. progressText.AddToClassList("progress-text");
  729. progressContainer.Add(progressBar);
  730. progressContainer.Add(progressText);
  731. section.Add(progressContainer);
  732. // Objective list
  733. var objList = new VisualElement();
  734. objList.AddToClassList("objectives-list");
  735. for (int i = 0; i < quest.questData.objectives.Count; i++)
  736. {
  737. var objective = quest.questData.objectives[i];
  738. bool isCompleted = quest.GetObjectiveProgress(i) >= objective.targetAmount;
  739. var objItem = new VisualElement();
  740. objItem.AddToClassList("objective-item-popup");
  741. var status = new VisualElement();
  742. status.AddToClassList("objective-status-popup");
  743. if (isCompleted)
  744. status.AddToClassList("objective-completed-popup");
  745. var text = new Label(objective.description);
  746. text.AddToClassList("objective-text-popup");
  747. var progress = new Label($"{quest.GetObjectiveProgress(i)}/{objective.targetAmount}");
  748. progress.AddToClassList("objective-progress-popup");
  749. objItem.Add(status);
  750. objItem.Add(text);
  751. objItem.Add(progress);
  752. objList.Add(objItem);
  753. }
  754. section.Add(objList);
  755. objectivesList.Add(section);
  756. }
  757. private void PopulateRewards(ActiveQuest quest)
  758. {
  759. if (rewardsList == null) return;
  760. rewardsList.Clear();
  761. var section = new VisualElement();
  762. section.AddToClassList("section");
  763. var header = new Label("Rewards");
  764. header.AddToClassList("section-header");
  765. section.Add(header);
  766. var rewards = new VisualElement();
  767. rewards.AddToClassList("rewards-list");
  768. if (quest.questData.goldReward > 0)
  769. {
  770. var goldReward = new Label($"🪙 {quest.questData.goldReward} Gold");
  771. goldReward.AddToClassList("reward-item");
  772. rewards.Add(goldReward);
  773. }
  774. if (quest.questData.renownReward > 0)
  775. {
  776. var renownReward = new Label($"⭐ {quest.questData.renownReward} Renown");
  777. renownReward.AddToClassList("reward-item");
  778. rewards.Add(renownReward);
  779. }
  780. foreach (var item in quest.questData.itemRewards)
  781. {
  782. if (item != null)
  783. {
  784. var itemReward = new Label($"📦 {item}");
  785. itemReward.AddToClassList("reward-item");
  786. rewards.Add(itemReward);
  787. }
  788. }
  789. section.Add(rewards);
  790. rewardsList.Add(section);
  791. }
  792. private string GetDetailedTimeText(ActiveQuest quest)
  793. {
  794. if (quest.questData.timeLimit <= 0)
  795. return "No time limit";
  796. float remaining = quest.GetTimeRemaining();
  797. if (remaining <= 0)
  798. return "QUEST EXPIRED";
  799. if (remaining < 1f)
  800. return $"{Mathf.RoundToInt(remaining * 60f)} minutes remaining";
  801. if (remaining < 24f)
  802. return $"{Mathf.RoundToInt(remaining)} hours remaining";
  803. int days = Mathf.FloorToInt(remaining / 24f);
  804. int hours = Mathf.RoundToInt(remaining % 24f);
  805. return $"{days}d {hours}h remaining";
  806. }
  807. #endregion
  808. #region Event Handlers
  809. private void HandleQuestAccepted(ActiveQuest quest)
  810. {
  811. Debug.Log($"ActiveQuestUI: HandleQuestAccepted called for quest {quest.questData.questTitle}");
  812. RefreshQuestList();
  813. }
  814. private void HandleQuestCompleted(ActiveQuest quest, List<QuestReward> rewards)
  815. {
  816. Debug.Log($"ActiveQuestUI: HandleQuestCompleted called for quest {quest.questData.questTitle}");
  817. ShowCompletionNotification(quest, rewards);
  818. RefreshQuestList();
  819. }
  820. private void HandleQuestAbandoned(ActiveQuest quest)
  821. {
  822. Debug.Log($"ActiveQuestUI: HandleQuestAbandoned called for quest {quest.questData.questTitle}");
  823. if (selectedQuest?.questData == quest.questData)
  824. selectedQuest = null;
  825. RefreshQuestList();
  826. }
  827. private void HandleQuestFailed(ActiveQuest quest)
  828. {
  829. Debug.Log($"ActiveQuestUI: HandleQuestFailed called for quest {quest.questData.questTitle}");
  830. if (selectedQuest?.questData == quest.questData)
  831. selectedQuest = null;
  832. RefreshQuestList();
  833. }
  834. private void HandleQuestProgress(ActiveQuest quest, int objectiveIndex, int newProgress)
  835. {
  836. // Update the specific quest entry
  837. RefreshQuestList();
  838. // Show progress notification if objective completed
  839. if (quest.questData.objectives[objectiveIndex].targetAmount <= newProgress)
  840. {
  841. ShowProgressNotification(quest, objectiveIndex);
  842. }
  843. }
  844. #endregion
  845. #region Notifications
  846. private void ShowCompletionNotification(ActiveQuest quest, List<QuestReward> rewards)
  847. {
  848. if (completionNotification == null) return;
  849. var content = completionNotification.Q<VisualElement>("notification-content");
  850. if (content == null) return;
  851. content.Clear();
  852. var icon = new Label("✅");
  853. icon.AddToClassList("completion-icon");
  854. content.Add(icon);
  855. var title = new Label("Quest Completed!");
  856. title.AddToClassList("completion-title");
  857. content.Add(title);
  858. var questTitle = new Label(quest.questData.questTitle);
  859. questTitle.AddToClassList("completed-quest-title");
  860. content.Add(questTitle);
  861. var rewardsText = new Label("Rewards: " + GetRewardsText(rewards));
  862. rewardsText.AddToClassList("completion-rewards");
  863. content.Add(rewardsText);
  864. if (completionNotification != null)
  865. completionNotification.style.display = DisplayStyle.Flex;
  866. // Auto-hide after 5 seconds
  867. StartCoroutine(HideNotificationAfterDelay(completionNotification, 5f));
  868. }
  869. private void ShowProgressNotification(ActiveQuest quest, int objectiveIndex)
  870. {
  871. if (progressNotification == null) return;
  872. var content = progressNotification.Q<VisualElement>("progress-notification-content");
  873. if (content == null) return;
  874. content.Clear();
  875. var icon = new Label("🎯");
  876. icon.AddToClassList("progress-icon");
  877. content.Add(icon);
  878. var title = new Label("Objective Complete!");
  879. title.AddToClassList("progress-title");
  880. content.Add(title);
  881. var objective = new Label(quest.questData.objectives[objectiveIndex].description);
  882. objective.AddToClassList("completed-objective");
  883. content.Add(objective);
  884. if (progressNotification != null)
  885. progressNotification.style.display = DisplayStyle.Flex;
  886. // Auto-hide after 3 seconds
  887. StartCoroutine(HideNotificationAfterDelay(progressNotification, 3f));
  888. }
  889. private IEnumerator HideNotificationAfterDelay(VisualElement notification, float delay)
  890. {
  891. yield return new WaitForSeconds(delay);
  892. if (notification != null)
  893. notification.style.display = DisplayStyle.None;
  894. }
  895. private string GetRewardsText(List<QuestReward> rewards)
  896. {
  897. var rewardTexts = new List<string>();
  898. foreach (var reward in rewards)
  899. {
  900. switch (reward.type)
  901. {
  902. case QuestRewardType.Gold:
  903. rewardTexts.Add($"{reward.amount} Gold");
  904. break;
  905. case QuestRewardType.Renown:
  906. rewardTexts.Add($"{reward.amount} Renown");
  907. break;
  908. case QuestRewardType.Item:
  909. if (reward.item != null)
  910. rewardTexts.Add(reward.item.name);
  911. break;
  912. }
  913. }
  914. return string.Join(", ", rewardTexts);
  915. }
  916. internal bool IsPointWithinUI(Vector2 screenPosition)
  917. {
  918. if (questTracker == null)
  919. {
  920. return false;
  921. }
  922. // Convert screen position to panel-relative position
  923. Vector2 panelPosition = RuntimePanelUtils.ScreenToPanel(questTracker.panel, screenPosition);
  924. // Check if the position is within the panel bounds
  925. bool withinBounds = questTracker.worldBound.Contains(panelPosition);
  926. return withinBounds;
  927. }
  928. #endregion
  929. }