ActiveQuestUI.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  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. void OnEnable()
  46. {
  47. ClickBlockingHelper.RegisterWithClickManager(this);
  48. // Refresh quest list when returning from scenes (like settlements)
  49. // Use a coroutine to ensure other systems are ready
  50. StartCoroutine(RefreshOnEnable());
  51. }
  52. void OnDisable() => ClickBlockingHelper.UnregisterWithClickManager(this);
  53. private IEnumerator RefreshOnEnable()
  54. {
  55. // Wait a frame to ensure other systems are initialized
  56. yield return new WaitForEndOfFrame();
  57. // Check if we need to refresh the quest list
  58. if (QuestManager.Instance != null)
  59. {
  60. RefreshQuestList();
  61. }
  62. }
  63. /// <summary>
  64. /// Sets a temporary flag to block travel system clicks when UI handles a click event.
  65. /// This is much more reliable than trying to calculate coordinates manually.
  66. /// </summary>
  67. private void SetClickFlag()
  68. {
  69. ClickBlockingHelper.SetClickFlag("ActiveQuestUI", this, (flag) => recentlyHandledClick = flag);
  70. }
  71. private void Start()
  72. {
  73. InitializeUI();
  74. SubscribeToEvents();
  75. // Delay quest refresh to ensure QuestManager is fully initialized
  76. StartCoroutine(DelayedQuestRefresh());
  77. }
  78. private IEnumerator DelayedQuestRefresh()
  79. {
  80. yield return new WaitForEndOfFrame();
  81. RefreshQuestList();
  82. }
  83. private void InitializeUI()
  84. {
  85. if (uiDocument == null)
  86. uiDocument = GetComponent<UIDocument>();
  87. var root = uiDocument.rootVisualElement;
  88. if (root == null)
  89. {
  90. return;
  91. }
  92. // Ensure root doesn't block other UI - set to ignore picking
  93. root.pickingMode = PickingMode.Ignore;
  94. // Ensure this UI has proper sorting order to be above map but below modals
  95. if (uiDocument.sortingOrder == 0)
  96. {
  97. uiDocument.sortingOrder = 10; // Above map, below modals like TravelUI
  98. }
  99. // Get main elements
  100. questTracker = root.Q<VisualElement>("quest-tracker");
  101. trackerTitle = root.Q<Label>("tracker-title");
  102. questLogButton = root.Q<Button>("quest-log-button");
  103. toggleTrackerButton = root.Q<Button>("toggle-tracker-button");
  104. activeQuestsList = root.Q<ScrollView>("active-quests-list");
  105. noQuestsMessage = root.Q<Label>("no-quests-message");
  106. quickActions = root.Q<VisualElement>("quick-actions");
  107. showAllButton = root.Q<Button>("show-all-button");
  108. trackLocationButton = root.Q<Button>("track-location-button");
  109. abandonQuestButton = root.Q<Button>("abandon-quest-button");
  110. // Get popup elements
  111. questDetailsPopup = root.Q<VisualElement>("quest-details-popup");
  112. popupContent = root.Q<VisualElement>("popup-content");
  113. popupTitle = root.Q<Label>("popup-title");
  114. closePopupButton = root.Q<Button>("close-popup-button");
  115. popupDetails = root.Q<VisualElement>("popup-details");
  116. questInfo = root.Q<VisualElement>("quest-info");
  117. objectivesList = root.Q<VisualElement>("objectives-list");
  118. rewardsList = root.Q<VisualElement>("rewards-list");
  119. popupActions = root.Q<VisualElement>("popup-actions");
  120. trackButton = root.Q<Button>("track-button");
  121. navigateButton = root.Q<Button>("navigate-button");
  122. // Get notification elements
  123. completionNotification = root.Q<VisualElement>("completion-notification");
  124. progressNotification = root.Q<VisualElement>("progress-notification");
  125. // Set up event handlers
  126. questLogButton.clicked += () => OpenFullQuestLog();
  127. toggleTrackerButton.clicked += () => ToggleQuestTracker();
  128. showAllButton.clicked += () => OpenFullQuestLog();
  129. trackLocationButton.clicked += () => TrackSelectedQuest();
  130. abandonQuestButton.clicked += () => AbandonSelectedQuest();
  131. closePopupButton.clicked += () => CloseQuestDetails();
  132. trackButton.clicked += () => TrackSelectedQuest();
  133. navigateButton.clicked += () => NavigateToQuest();
  134. // Initially hide popup and notifications
  135. if (questDetailsPopup != null)
  136. questDetailsPopup.style.display = DisplayStyle.None;
  137. if (completionNotification != null)
  138. completionNotification.style.display = DisplayStyle.None;
  139. if (progressNotification != null)
  140. progressNotification.style.display = DisplayStyle.None;
  141. // Update tracker title with count
  142. UpdateTrackerTitle();
  143. isUIVisible = true;
  144. }
  145. private bool isTrackerMinimized = false;
  146. private void ToggleQuestTracker()
  147. {
  148. isTrackerMinimized = !isTrackerMinimized;
  149. if (isTrackerMinimized)
  150. {
  151. // Hide the quest list and actions, only show header
  152. if (activeQuestsList != null)
  153. activeQuestsList.style.display = DisplayStyle.None;
  154. if (quickActions != null)
  155. quickActions.style.display = DisplayStyle.None;
  156. if (noQuestsMessage != null)
  157. noQuestsMessage.style.display = DisplayStyle.None;
  158. // Change button text to show expand option
  159. if (toggleTrackerButton != null)
  160. toggleTrackerButton.text = "+";
  161. // Make tracker smaller
  162. if (questTracker != null)
  163. {
  164. questTracker.style.height = 40;
  165. questTracker.style.maxHeight = 40;
  166. }
  167. }
  168. else
  169. {
  170. // Show everything again
  171. if (activeQuestsList != null)
  172. activeQuestsList.style.display = DisplayStyle.Flex;
  173. if (quickActions != null)
  174. quickActions.style.display = DisplayStyle.Flex;
  175. // Show no quests message if needed
  176. var activeQuests = QuestManager.Instance?.GetActiveQuests();
  177. if (activeQuests == null || activeQuests.Count == 0)
  178. {
  179. if (noQuestsMessage != null)
  180. noQuestsMessage.style.display = DisplayStyle.Flex;
  181. }
  182. // Change button text back
  183. if (toggleTrackerButton != null)
  184. toggleTrackerButton.text = "−";
  185. // Restore tracker size
  186. if (questTracker != null)
  187. {
  188. questTracker.style.height = StyleKeyword.Auto;
  189. questTracker.style.maxHeight = 350;
  190. }
  191. }
  192. }
  193. private void SubscribeToEvents()
  194. {
  195. if (QuestManager.Instance != null)
  196. {
  197. QuestManager.Instance.OnQuestAccepted += HandleQuestAccepted;
  198. QuestManager.Instance.OnQuestCompleted += HandleQuestCompleted;
  199. QuestManager.Instance.OnQuestAbandoned += HandleQuestAbandoned;
  200. QuestManager.Instance.OnQuestFailed += HandleQuestFailed;
  201. }
  202. }
  203. private void OnDestroy()
  204. {
  205. // Unregister from ClickManager
  206. ClickBlockingHelper.UnregisterWithClickManager(this);
  207. // Unregister from QuestManager events
  208. if (QuestManager.Instance != null)
  209. {
  210. QuestManager.Instance.OnQuestAccepted -= HandleQuestAccepted;
  211. QuestManager.Instance.OnQuestCompleted -= HandleQuestCompleted;
  212. QuestManager.Instance.OnQuestAbandoned -= HandleQuestAbandoned;
  213. QuestManager.Instance.OnQuestFailed -= HandleQuestFailed;
  214. }
  215. }
  216. #region Quest List Management
  217. private void RefreshQuestList()
  218. {
  219. ClearQuestList();
  220. if (QuestManager.Instance == null)
  221. {
  222. Debug.LogWarning("ActiveQuestUI: QuestManager.Instance is null");
  223. ShowNoQuestsMessage();
  224. return;
  225. }
  226. var activeQuests = QuestManager.Instance.GetActiveQuests();
  227. if (activeQuests.Count == 0)
  228. {
  229. ShowNoQuestsMessage();
  230. return;
  231. }
  232. HideNoQuestsMessage();
  233. foreach (var quest in activeQuests)
  234. {
  235. CreateQuestEntry(quest);
  236. }
  237. UpdateTrackerTitle();
  238. UpdateQuickActions();
  239. }
  240. private void ClearQuestList()
  241. {
  242. questEntries.Clear();
  243. activeQuestsList?.Clear();
  244. }
  245. private void ShowNoQuestsMessage()
  246. {
  247. if (noQuestsMessage != null)
  248. noQuestsMessage.style.display = DisplayStyle.Flex;
  249. if (quickActions != null)
  250. quickActions.style.display = DisplayStyle.None;
  251. }
  252. private void HideNoQuestsMessage()
  253. {
  254. if (noQuestsMessage != null)
  255. noQuestsMessage.style.display = DisplayStyle.None;
  256. if (quickActions != null)
  257. quickActions.style.display = DisplayStyle.Flex;
  258. }
  259. private void CreateQuestEntry(ActiveQuest quest)
  260. {
  261. var entry = new VisualElement();
  262. entry.AddToClassList("quest-entry-compact");
  263. // Header with title and urgency
  264. var header = new VisualElement();
  265. header.AddToClassList("quest-compact-header");
  266. var title = new Label(quest.questData.questTitle);
  267. title.AddToClassList("quest-compact-title");
  268. var urgency = new VisualElement();
  269. urgency.AddToClassList("quest-compact-urgency");
  270. urgency.AddToClassList(GetUrgencyClass(quest.GetUrgencyLevel()));
  271. header.Add(title);
  272. header.Add(urgency);
  273. // Info row with progress and time
  274. var info = new VisualElement();
  275. info.AddToClassList("quest-compact-info");
  276. var progress = new Label($"{quest.GetCompletedObjectivesCount()}/{quest.questData.goals.Count} objectives");
  277. progress.AddToClassList("quest-compact-progress"); var timeRemaining = new Label(GetTimeRemainingText(quest));
  278. timeRemaining.AddToClassList("quest-compact-time");
  279. info.Add(progress);
  280. info.Add(timeRemaining);
  281. entry.Add(header);
  282. entry.Add(info);
  283. // Click handler
  284. entry.RegisterCallback<ClickEvent>(evt => SelectQuest(quest, entry));
  285. activeQuestsList.Add(entry);
  286. questEntries.Add(entry);
  287. }
  288. private string GetUrgencyClass(QuestUrgency urgency)
  289. {
  290. switch (urgency)
  291. {
  292. case QuestUrgency.Low: return "urgency-low";
  293. case QuestUrgency.Medium: return "urgency-medium";
  294. case QuestUrgency.High: return "urgency-high";
  295. case QuestUrgency.Critical: return "urgency-critical";
  296. default: return "urgency-low";
  297. }
  298. }
  299. private string GetTimeRemainingText(ActiveQuest quest)
  300. {
  301. if (quest.questData.timeLimit <= 0)
  302. return "No time limit";
  303. float remaining = quest.GetTimeRemaining();
  304. if (remaining <= 0)
  305. return "EXPIRED";
  306. if (remaining < 1f)
  307. return "< 1 hour";
  308. if (remaining < 24f)
  309. return $"{Mathf.CeilToInt(remaining)}h";
  310. return $"{Mathf.CeilToInt(remaining / 24f)}d";
  311. }
  312. private void UpdateTrackerTitle()
  313. {
  314. if (trackerTitle == null)
  315. {
  316. Debug.LogWarning("trackerTitle is null!");
  317. return;
  318. }
  319. int activeCount = QuestManager.Instance?.GetActiveQuests().Count ?? 0;
  320. string newTitle = $"Active Quests ({activeCount})";
  321. trackerTitle.text = newTitle;
  322. // Force update the display
  323. if (questTracker != null)
  324. {
  325. questTracker.style.display = DisplayStyle.Flex;
  326. }
  327. }
  328. #endregion
  329. #region Selection and Actions
  330. private void SelectQuest(ActiveQuest quest, VisualElement entry)
  331. {
  332. // Deselect previous
  333. foreach (var questEntry in questEntries)
  334. {
  335. questEntry.RemoveFromClassList("selected");
  336. }
  337. // Select new
  338. entry.AddToClassList("selected");
  339. selectedQuest = quest;
  340. UpdateQuickActions();
  341. }
  342. private void UpdateQuickActions()
  343. {
  344. if (quickActions == null) return;
  345. bool hasSelection = selectedQuest != null;
  346. trackLocationButton?.SetEnabled(hasSelection);
  347. abandonQuestButton?.SetEnabled(hasSelection);
  348. }
  349. private void TrackSelectedQuest()
  350. {
  351. if (selectedQuest == null) return;
  352. // Focus camera on quest location
  353. var questMapMarkerManager = FindFirstObjectByType<QuestMapMarkerManager>();
  354. if (questMapMarkerManager != null)
  355. {
  356. questMapMarkerManager.FocusOnQuest(selectedQuest);
  357. }
  358. // Show quest details popup
  359. ShowQuestDetails(selectedQuest);
  360. }
  361. private void AbandonSelectedQuest()
  362. {
  363. if (selectedQuest == null) return;
  364. // Show confirmation dialog (simplified for now)
  365. if (QuestManager.Instance != null)
  366. {
  367. QuestManager.Instance.AbandonQuest(selectedQuest.questId);
  368. }
  369. }
  370. private void OpenFullQuestLog()
  371. {
  372. if (selectedQuest != null)
  373. {
  374. ShowQuestDetails(selectedQuest);
  375. }
  376. }
  377. private void NavigateToQuest()
  378. {
  379. if (selectedQuest == null) return;
  380. Vector2 questLocation = new Vector2(selectedQuest.questData.targetLocationX, selectedQuest.questData.targetLocationY);
  381. }
  382. #endregion
  383. #region Quest Details Popup
  384. private void ShowQuestDetails(ActiveQuest quest)
  385. {
  386. if (questDetailsPopup == null) return;
  387. // Populate quest info
  388. PopulateQuestDetails(quest);
  389. // Show popup
  390. if (questDetailsPopup != null)
  391. questDetailsPopup.style.display = DisplayStyle.Flex;
  392. }
  393. private void CloseQuestDetails()
  394. {
  395. if (questDetailsPopup != null)
  396. questDetailsPopup.style.display = DisplayStyle.None;
  397. }
  398. private void PopulateQuestDetails(ActiveQuest quest)
  399. {
  400. if (popupTitle != null)
  401. popupTitle.text = quest.questData.questTitle;
  402. if (questInfo == null) return;
  403. questInfo.Clear();
  404. // Title and difficulty
  405. var title = new Label(quest.questData.questTitle);
  406. title.AddToClassList("quest-title");
  407. questInfo.Add(title);
  408. var difficulty = new Label(quest.questData.difficulty.ToString().ToUpper());
  409. difficulty.AddToClassList("quest-difficulty");
  410. difficulty.AddToClassList($"difficulty-{quest.questData.difficulty.ToString().ToLower()}");
  411. questInfo.Add(difficulty);
  412. // Status bar
  413. var status = new VisualElement();
  414. status.AddToClassList("quest-status");
  415. var timeLabel = new Label(GetDetailedTimeText(quest));
  416. timeLabel.AddToClassList("time-remaining");
  417. var locationLabel = new Label($"Location: {quest.questData.targetLocationName}");
  418. locationLabel.AddToClassList("quest-location");
  419. status.Add(timeLabel);
  420. status.Add(locationLabel);
  421. questInfo.Add(status);
  422. // Description
  423. var descSection = new VisualElement();
  424. descSection.AddToClassList("section");
  425. var descHeader = new Label("Description");
  426. descHeader.AddToClassList("section-header");
  427. descSection.Add(descHeader);
  428. var description = new Label(quest.questData.description);
  429. description.AddToClassList("quest-description");
  430. descSection.Add(description);
  431. questInfo.Add(descSection);
  432. // Objectives
  433. PopulateObjectives(quest);
  434. // Rewards
  435. PopulateRewards(quest);
  436. }
  437. private void PopulateObjectives(ActiveQuest quest)
  438. {
  439. if (objectivesList == null) return;
  440. objectivesList.Clear();
  441. var section = new VisualElement();
  442. section.AddToClassList("section");
  443. var header = new Label("Objectives");
  444. header.AddToClassList("section-header");
  445. section.Add(header);
  446. // Progress bar
  447. var progressContainer = new VisualElement();
  448. progressContainer.AddToClassList("progress-container");
  449. var progressBar = new VisualElement();
  450. progressBar.AddToClassList("progress-bar");
  451. var progressFill = new VisualElement();
  452. progressFill.AddToClassList("progress-fill");
  453. int completed = quest.GetCompletedObjectivesCount();
  454. int total = quest.questData.objectives.Count;
  455. float percentage = total > 0 ? (float)completed / total * 100f : 0f;
  456. progressFill.style.width = new Length(percentage, LengthUnit.Percent);
  457. progressBar.Add(progressFill);
  458. var progressText = new Label($"{completed}/{total}");
  459. progressText.AddToClassList("progress-text");
  460. progressContainer.Add(progressBar);
  461. progressContainer.Add(progressText);
  462. section.Add(progressContainer);
  463. // Objective list
  464. var objList = new VisualElement();
  465. objList.AddToClassList("objectives-list");
  466. for (int i = 0; i < quest.questData.objectives.Count; i++)
  467. {
  468. var objective = quest.questData.objectives[i];
  469. bool isCompleted = quest.GetObjectiveProgress(i) >= objective.targetAmount;
  470. var objItem = new VisualElement();
  471. objItem.AddToClassList("objective-item-popup");
  472. var status = new VisualElement();
  473. status.AddToClassList("objective-status-popup");
  474. if (isCompleted)
  475. status.AddToClassList("objective-completed-popup");
  476. var text = new Label(objective.description);
  477. text.AddToClassList("objective-text-popup");
  478. var progress = new Label($"{quest.GetObjectiveProgress(i)}/{objective.targetAmount}");
  479. progress.AddToClassList("objective-progress-popup");
  480. objItem.Add(status);
  481. objItem.Add(text);
  482. objItem.Add(progress);
  483. objList.Add(objItem);
  484. }
  485. section.Add(objList);
  486. objectivesList.Add(section);
  487. }
  488. private void PopulateRewards(ActiveQuest quest)
  489. {
  490. if (rewardsList == null) return;
  491. rewardsList.Clear();
  492. var section = new VisualElement();
  493. section.AddToClassList("section");
  494. var header = new Label("Rewards");
  495. header.AddToClassList("section-header");
  496. section.Add(header);
  497. var rewards = new VisualElement();
  498. rewards.AddToClassList("rewards-list");
  499. if (quest.questData.goldReward > 0)
  500. {
  501. var goldReward = new Label($"🪙 {quest.questData.goldReward} Gold");
  502. goldReward.AddToClassList("reward-item");
  503. rewards.Add(goldReward);
  504. }
  505. if (quest.questData.renownReward > 0)
  506. {
  507. var renownReward = new Label($"⭐ {quest.questData.renownReward} Renown");
  508. renownReward.AddToClassList("reward-item");
  509. rewards.Add(renownReward);
  510. }
  511. foreach (var item in quest.questData.itemRewards)
  512. {
  513. if (item != null)
  514. {
  515. var itemReward = new Label($"📦 {item}");
  516. itemReward.AddToClassList("reward-item");
  517. rewards.Add(itemReward);
  518. }
  519. }
  520. section.Add(rewards);
  521. rewardsList.Add(section);
  522. }
  523. private string GetDetailedTimeText(ActiveQuest quest)
  524. {
  525. if (quest.questData.timeLimit <= 0)
  526. return "No time limit";
  527. float remaining = quest.GetTimeRemaining();
  528. if (remaining <= 0)
  529. return "QUEST EXPIRED";
  530. if (remaining < 1f)
  531. return $"{Mathf.RoundToInt(remaining * 60f)} minutes remaining";
  532. if (remaining < 24f)
  533. return $"{Mathf.RoundToInt(remaining)} hours remaining";
  534. int days = Mathf.FloorToInt(remaining / 24f);
  535. int hours = Mathf.RoundToInt(remaining % 24f);
  536. return $"{days}d {hours}h remaining";
  537. }
  538. #endregion
  539. #region Event Handlers
  540. private void HandleQuestAccepted(ActiveQuest quest)
  541. {
  542. RefreshQuestList();
  543. }
  544. private void HandleQuestCompleted(ActiveQuest quest, List<QuestReward> rewards)
  545. {
  546. ShowCompletionNotification(quest, rewards);
  547. RefreshQuestList();
  548. }
  549. private void HandleQuestAbandoned(ActiveQuest quest)
  550. {
  551. if (selectedQuest?.questData == quest.questData)
  552. selectedQuest = null;
  553. RefreshQuestList();
  554. }
  555. private void HandleQuestFailed(ActiveQuest quest)
  556. {
  557. if (selectedQuest?.questData == quest.questData)
  558. selectedQuest = null;
  559. RefreshQuestList();
  560. }
  561. private void HandleQuestProgress(ActiveQuest quest, int objectiveIndex, int newProgress)
  562. {
  563. // Update the specific quest entry
  564. RefreshQuestList();
  565. // Show progress notification if objective completed
  566. if (quest.questData.objectives[objectiveIndex].targetAmount <= newProgress)
  567. {
  568. ShowProgressNotification(quest, objectiveIndex);
  569. }
  570. }
  571. #endregion
  572. #region Notifications
  573. private void ShowCompletionNotification(ActiveQuest quest, List<QuestReward> rewards)
  574. {
  575. if (completionNotification == null) return;
  576. var content = completionNotification.Q<VisualElement>("notification-content");
  577. if (content == null) return;
  578. content.Clear();
  579. var icon = new Label("✅");
  580. icon.AddToClassList("completion-icon");
  581. content.Add(icon);
  582. var title = new Label("Quest Completed!");
  583. title.AddToClassList("completion-title");
  584. content.Add(title);
  585. var questTitle = new Label(quest.questData.questTitle);
  586. questTitle.AddToClassList("completed-quest-title");
  587. content.Add(questTitle);
  588. var rewardsText = new Label("Rewards: " + GetRewardsText(rewards));
  589. rewardsText.AddToClassList("completion-rewards");
  590. content.Add(rewardsText);
  591. if (completionNotification != null)
  592. completionNotification.style.display = DisplayStyle.Flex;
  593. // Auto-hide after 5 seconds
  594. StartCoroutine(HideNotificationAfterDelay(completionNotification, 5f));
  595. }
  596. private void ShowProgressNotification(ActiveQuest quest, int objectiveIndex)
  597. {
  598. if (progressNotification == null) return;
  599. var content = progressNotification.Q<VisualElement>("progress-notification-content");
  600. if (content == null) return;
  601. content.Clear();
  602. var icon = new Label("🎯");
  603. icon.AddToClassList("progress-icon");
  604. content.Add(icon);
  605. var title = new Label("Objective Complete!");
  606. title.AddToClassList("progress-title");
  607. content.Add(title);
  608. var objective = new Label(quest.questData.objectives[objectiveIndex].description);
  609. objective.AddToClassList("completed-objective");
  610. content.Add(objective);
  611. if (progressNotification != null)
  612. progressNotification.style.display = DisplayStyle.Flex;
  613. // Auto-hide after 3 seconds
  614. StartCoroutine(HideNotificationAfterDelay(progressNotification, 3f));
  615. }
  616. private IEnumerator HideNotificationAfterDelay(VisualElement notification, float delay)
  617. {
  618. yield return new WaitForSeconds(delay);
  619. if (notification != null)
  620. notification.style.display = DisplayStyle.None;
  621. }
  622. private string GetRewardsText(List<QuestReward> rewards)
  623. {
  624. var rewardTexts = new List<string>();
  625. foreach (var reward in rewards)
  626. {
  627. switch (reward.type)
  628. {
  629. case QuestRewardType.Gold:
  630. rewardTexts.Add($"{reward.amount} Gold");
  631. break;
  632. case QuestRewardType.Renown:
  633. rewardTexts.Add($"{reward.amount} Renown");
  634. break;
  635. case QuestRewardType.Item:
  636. if (reward.item != null)
  637. rewardTexts.Add(reward.item.name);
  638. break;
  639. }
  640. }
  641. return string.Join(", ", rewardTexts);
  642. }
  643. #endregion
  644. public bool IsBlockingClick(Vector2 screenPosition)
  645. {
  646. // Primary method: Check if we recently handled a UI click
  647. if (recentlyHandledClick)
  648. {
  649. return true;
  650. }
  651. // If UI is not visible, don't block anything
  652. if (!isUIVisible)
  653. {
  654. return false;
  655. }
  656. // Dynamic coordinate-based approach: Block clicks in the quest tracker area
  657. // This needs to account for the expanded quest panel when it shows more options
  658. float questPanelWidth = 320f; // Approximate width of quest panel
  659. float questPanelHeight = 250f; // Larger height to cover expanded panel (was 120f)
  660. float questPanelX = 20f; // Left edge
  661. float questPanelY = Screen.height - questPanelHeight - 20f; // Top edge (converted to screen coords)
  662. // Alternative: Try to get actual bounds from the quest tracker if available
  663. if (questTracker != null)
  664. {
  665. try
  666. {
  667. // Try to use the actual UI element bounds if possible
  668. var bounds = questTracker.worldBound;
  669. if (bounds.width > 0 && bounds.height > 0)
  670. {
  671. // Convert UI bounds to screen coordinates
  672. questPanelX = bounds.x;
  673. questPanelY = Screen.height - bounds.y - bounds.height;
  674. questPanelWidth = bounds.width;
  675. questPanelHeight = bounds.height;
  676. }
  677. }
  678. catch (System.Exception e)
  679. {
  680. Debug.LogWarning($"Failed to get UI bounds, using fallback: {e.Message}");
  681. }
  682. }
  683. bool inQuestArea = screenPosition.x >= questPanelX &&
  684. screenPosition.x <= questPanelX + questPanelWidth &&
  685. screenPosition.y >= questPanelY &&
  686. screenPosition.y <= questPanelY + questPanelHeight;
  687. if (inQuestArea)
  688. {
  689. return true;
  690. }
  691. // Don't block clicks outside the quest area
  692. return false;
  693. }
  694. }