TravelUI.cs 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  1. using UnityEngine;
  2. using UnityEngine.UIElements;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. /// <summary>
  6. /// UI Controller for the Travel System with route selection and comparison
  7. /// </summary>
  8. public class TravelUI : MonoBehaviour, IClickBlocker
  9. {
  10. [Header("UI Document")]
  11. public UIDocument uiDocument;
  12. [Header("UI Files")]
  13. public VisualTreeAsset travelUIAsset;
  14. public StyleSheet travelUIStyleSheet;
  15. // UI Elements
  16. private VisualElement travelContainer;
  17. private VisualElement travelPanel;
  18. private VisualElement clickBlocker;
  19. private Label distanceLabel;
  20. private Label timeLabel;
  21. private Label specialCostsLabel;
  22. private Button startTravelButton;
  23. private Button cancelTravelButton;
  24. private VisualElement routeList;
  25. // Travel data
  26. private Vector2Int currentDestination;
  27. private List<TravelRoute> availableRoutes = new List<TravelRoute>();
  28. private TravelRoute selectedRoute;
  29. // State
  30. private bool isUIVisible = false;
  31. void Awake()
  32. {
  33. // Find UI Document if not assigned
  34. if (uiDocument == null)
  35. {
  36. uiDocument = GetComponent<UIDocument>();
  37. }
  38. // Load UXML asset from Resources if not assigned
  39. if (travelUIAsset == null)
  40. {
  41. travelUIAsset = Resources.Load<VisualTreeAsset>("UI/Map/TravelUI");
  42. if (travelUIAsset == null)
  43. {
  44. // Try alternative path
  45. travelUIAsset = Resources.Load<VisualTreeAsset>("TravelUI");
  46. }
  47. }
  48. // Load USS style sheet if not assigned
  49. if (travelUIStyleSheet == null)
  50. {
  51. travelUIStyleSheet = Resources.Load<StyleSheet>("UI/Map/TravelUI");
  52. if (travelUIStyleSheet == null)
  53. {
  54. // Try alternative path
  55. travelUIStyleSheet = Resources.Load<StyleSheet>("TravelUI");
  56. }
  57. }
  58. }
  59. void Start()
  60. {
  61. SetupUI();
  62. }
  63. void OnEnable()
  64. {
  65. // Register with ClickManager when this component is enabled
  66. if (ClickManager.Instance != null)
  67. {
  68. ClickManager.Instance.RegisterClickBlocker(this);
  69. }
  70. }
  71. void OnDisable()
  72. {
  73. // Unregister from ClickManager when this component is disabled
  74. if (ClickManager.Instance != null)
  75. {
  76. ClickManager.Instance.UnregisterClickBlocker(this);
  77. }
  78. }
  79. void OnDestroy()
  80. {
  81. // Ensure we're unregistered when destroyed
  82. if (ClickManager.Instance != null)
  83. {
  84. ClickManager.Instance.UnregisterClickBlocker(this);
  85. }
  86. }
  87. void SetupUI()
  88. {
  89. if (uiDocument == null)
  90. {
  91. Debug.LogError("TravelUI: UIDocument not found!");
  92. return;
  93. }
  94. var root = uiDocument.rootVisualElement;
  95. // Load and instantiate the UXML if we have the asset
  96. if (travelUIAsset != null)
  97. {
  98. // Clear existing content
  99. root.Clear();
  100. // Instantiate from UXML
  101. travelUIAsset.CloneTree(root);
  102. // Add style sheet
  103. if (travelUIStyleSheet != null)
  104. {
  105. root.styleSheets.Add(travelUIStyleSheet);
  106. }
  107. else
  108. {
  109. Debug.LogWarning("TravelUI: No style sheet found");
  110. }
  111. }
  112. else
  113. {
  114. Debug.LogWarning("TravelUI: No UXML asset found");
  115. }
  116. // Find UI elements
  117. travelContainer = root.Q<VisualElement>("TravelContainer");
  118. travelPanel = root.Q<VisualElement>("TravelPanel");
  119. clickBlocker = root.Q<VisualElement>("ClickBlocker");
  120. distanceLabel = root.Q<Label>("DistanceLabel");
  121. timeLabel = root.Q<Label>("TimeLabel");
  122. specialCostsLabel = root.Q<Label>("SpecialCostsLabel");
  123. startTravelButton = root.Q<Button>("StartTravelButton");
  124. cancelTravelButton = root.Q<Button>("CancelTravelButton");
  125. // Find draggable header and close button
  126. var travelHeader = root.Q<VisualElement>("TravelHeader");
  127. var closeButton = root.Q<Button>("CloseButton");
  128. // Find or create route list container
  129. routeList = root.Q<VisualElement>("RouteList");
  130. if (routeList == null)
  131. {
  132. Debug.Log("🔨 TravelUI: RouteList not found, creating it...");
  133. // Create route list if it doesn't exist
  134. var routeOptions = root.Q<VisualElement>("RouteOptions");
  135. if (routeOptions != null)
  136. {
  137. routeList = new VisualElement();
  138. routeList.name = "RouteList";
  139. routeList.AddToClassList("route-list");
  140. routeOptions.Add(routeList);
  141. Debug.Log("✅ TravelUI: RouteList created and added to RouteOptions");
  142. }
  143. else
  144. {
  145. Debug.LogWarning("⚠️ TravelUI: RouteOptions container not found, cannot create RouteList");
  146. }
  147. }
  148. else
  149. {
  150. Debug.Log("RouteList found");
  151. }
  152. // Setup dragging functionality
  153. if (travelHeader != null && travelPanel != null)
  154. {
  155. SetupDragFunctionality(travelHeader, travelPanel);
  156. }
  157. // Setup close button
  158. if (closeButton != null)
  159. {
  160. closeButton.clicked += OnCancelTravelClicked;
  161. }
  162. // Setup button callbacks
  163. if (startTravelButton != null)
  164. {
  165. startTravelButton.clicked += OnStartTravelClicked;
  166. }
  167. if (cancelTravelButton != null)
  168. {
  169. cancelTravelButton.clicked += OnCancelTravelClicked;
  170. }
  171. // Setup click blocking on the invisible overlay
  172. if (clickBlocker != null)
  173. {
  174. clickBlocker.RegisterCallback<ClickEvent>(evt =>
  175. {
  176. Debug.Log("�️ TravelUI: Click blocked by overlay - stopping propagation");
  177. evt.StopPropagation(); // Prevent clicks from passing through to the map
  178. });
  179. }
  180. else
  181. {
  182. Debug.LogWarning("⚠️ TravelUI: ClickBlocker element not found!");
  183. }
  184. // Hide UI initially
  185. HideTravelPanel();
  186. Debug.Log("TravelUI initialized successfully");
  187. }
  188. /// <summary>
  189. /// DEBUG: Force show the TravelUI for testing
  190. /// </summary>
  191. [ContextMenu("DEBUG: Force Show TravelUI")]
  192. public void DebugForceShowTravelUI()
  193. {
  194. Debug.Log("🧪 TravelUI: DEBUG Force showing TravelUI...");
  195. // CHECK FOR DUPLICATE TEAM MARKERS
  196. var allTeamMarkers = GameObject.FindObjectsByType<GameObject>(FindObjectsSortMode.None).Where(g => g.name == "TeamMarker").ToArray();
  197. Debug.Log($"🔍 Found {allTeamMarkers.Length} TeamMarker objects:");
  198. for (int i = 0; i < allTeamMarkers.Length; i++)
  199. {
  200. var marker = allTeamMarkers[i];
  201. bool hasSphere = marker.GetComponentInChildren<Transform>().name.Contains("Sphere");
  202. Debug.Log($" {i + 1}. {marker.name} at {marker.transform.position} - Has Sphere: {hasSphere}");
  203. }
  204. // Prevent multiple instances
  205. if (isUIVisible)
  206. {
  207. Debug.Log("⚠️ TravelUI: Already visible, ignoring duplicate show request");
  208. return;
  209. }
  210. // Create some dummy routes for testing with proper initialization
  211. var dummyRoutes = new List<TravelRoute>
  212. {
  213. new TravelRoute
  214. {
  215. routeType = RouteType.RoadPreferred,
  216. estimatedTime = 10.5f,
  217. totalDistance = 25.0f,
  218. totalCost = 50,
  219. specialCosts = new Dictionary<string, int> { { "Ferry", 10 } },
  220. path = new List<Vector2Int> { new Vector2Int(50, 50), new Vector2Int(75, 75), new Vector2Int(100, 100) }
  221. },
  222. new TravelRoute
  223. {
  224. routeType = RouteType.Standard,
  225. estimatedTime = 12.0f,
  226. totalDistance = 22.0f,
  227. totalCost = 45,
  228. specialCosts = new Dictionary<string, int>(),
  229. path = new List<Vector2Int> { new Vector2Int(50, 50), new Vector2Int(100, 100) }
  230. },
  231. new TravelRoute
  232. {
  233. routeType = RouteType.Special,
  234. estimatedTime = 8.0f,
  235. totalDistance = 30.0f,
  236. totalCost = 35,
  237. specialCosts = new Dictionary<string, int> { { "Tunnel Torch", 2 }, { "Mountain Guide", 15 } },
  238. path = new List<Vector2Int> { new Vector2Int(50, 50), new Vector2Int(60, 80), new Vector2Int(100, 100) }
  239. }
  240. };
  241. ShowTravelUI(new Vector2Int(100, 100), dummyRoutes);
  242. }
  243. /// <summary>
  244. /// Shows the travel UI with route options - UPDATED
  245. /// </summary>
  246. public void ShowTravelUI(Vector2Int destination, List<TravelRoute> routes)
  247. {
  248. Debug.Log($"🚀 TravelUI: ShowTravelUI called with destination {destination} and {routes.Count} routes");
  249. // If UI is already visible, update with new routes instead of ignoring
  250. if (isUIVisible)
  251. {
  252. Debug.Log("🔄 TravelUI: Already visible, updating with new routes");
  253. UpdateRoutesAndRefreshUI(destination, routes);
  254. return;
  255. }
  256. if (travelContainer == null)
  257. {
  258. Debug.LogWarning("⚠️ TravelUI: Container not found, attempting to setup UI");
  259. SetupUI();
  260. if (travelContainer == null)
  261. {
  262. Debug.LogError("❌ TravelUI: Failed to setup UI - travelContainer still null");
  263. return;
  264. }
  265. Debug.Log("✅ TravelUI: Successfully setup UI after retry");
  266. }
  267. currentDestination = destination;
  268. availableRoutes = new List<TravelRoute>(routes);
  269. // Select the fastest route by default
  270. if (availableRoutes.Count > 0)
  271. {
  272. selectedRoute = availableRoutes.OrderBy(r => r.estimatedTime).First();
  273. }
  274. else
  275. {
  276. Debug.LogWarning("TravelUI: No routes available!");
  277. }
  278. // Update UI content
  279. UpdateTravelInfo();
  280. PopulateRouteList();
  281. // Show the UI
  282. travelContainer.style.display = DisplayStyle.Flex;
  283. isUIVisible = true;
  284. // Ensure travel UI doesn't overlap with team overview panel
  285. EnsureNoTeamOverviewOverlap();
  286. // Force a layout update to ensure everything is positioned correctly
  287. travelContainer.MarkDirtyRepaint();
  288. }
  289. /// <summary>
  290. /// Hides the travel panel - UPDATED
  291. /// </summary>
  292. public void HideTravelPanel()
  293. {
  294. if (travelContainer != null)
  295. {
  296. travelContainer.style.display = DisplayStyle.None;
  297. }
  298. isUIVisible = false;
  299. // Clear route visualization when hiding
  300. var travelSystem = FindFirstObjectByType<TeamTravelSystem>();
  301. if (travelSystem != null)
  302. {
  303. try
  304. {
  305. // Try to clear route visualization
  306. var clearMethod = travelSystem.GetType().GetMethod("ClearRouteVisualization");
  307. if (clearMethod != null)
  308. {
  309. clearMethod.Invoke(travelSystem, null);
  310. Debug.Log("✅ TravelUI: Cleared route visualization");
  311. }
  312. }
  313. catch (System.Exception e)
  314. {
  315. Debug.LogWarning($"⚠️ TravelUI: Failed to clear route visualization: {e.Message}");
  316. }
  317. }
  318. Debug.Log("👻 TravelUI: Panel hidden");
  319. Debug.Log($"🔓 TravelUI: Map clicks now enabled outside panel - isUIVisible: {isUIVisible}");
  320. }
  321. /// <summary>
  322. /// Updates the UI with new routes and forces a complete refresh
  323. /// </summary>
  324. public void UpdateRoutesAndRefreshUI(Vector2Int destination, List<TravelRoute> routes)
  325. {
  326. Debug.Log($"🔄 TravelUI: UpdateRoutesAndRefreshUI called with {routes.Count} routes");
  327. // Update the route data
  328. currentDestination = destination;
  329. availableRoutes = new List<TravelRoute>(routes);
  330. // Select the fastest route by default
  331. if (availableRoutes.Count > 0)
  332. {
  333. selectedRoute = availableRoutes.OrderBy(r => r.estimatedTime).First();
  334. Debug.Log($"🎯 TravelUI: Updated to new default route: {selectedRoute.routeType}");
  335. }
  336. else
  337. {
  338. Debug.LogWarning("⚠️ TravelUI: No routes available in update!");
  339. selectedRoute = null;
  340. }
  341. // Force a complete UI refresh
  342. Debug.Log("🔄 TravelUI: Updating travel info after route refresh...");
  343. UpdateTravelInfo();
  344. Debug.Log("📋 TravelUI: Repopulating route list...");
  345. PopulateRouteList();
  346. // Force the entire container to repaint
  347. if (travelContainer != null)
  348. {
  349. travelContainer.MarkDirtyRepaint();
  350. Debug.Log("🎨 TravelUI: Forced container repaint after route update");
  351. }
  352. Debug.Log($"✅ TravelUI: UI refreshed with {routes.Count} updated routes");
  353. }
  354. /// <summary>
  355. /// DEBUG: Force hide the TravelUI for testing
  356. /// </summary>
  357. [ContextMenu("DEBUG: Force Hide TravelUI")]
  358. public void DebugForceHideTravelUI()
  359. {
  360. Debug.Log("🧪 TravelUI: DEBUG Force hiding TravelUI...");
  361. // Hide UI
  362. HideTravelPanel();
  363. }
  364. /// <summary>
  365. /// DEBUG: Test manual label updates
  366. /// </summary>
  367. [ContextMenu("DEBUG: Test Label Updates")]
  368. public void DebugTestLabelUpdates()
  369. {
  370. Debug.Log("🧪 TravelUI: DEBUG Testing label updates...");
  371. if (distanceLabel != null)
  372. {
  373. distanceLabel.text = $"Distance: {UnityEngine.Random.Range(10f, 100f):F1} leagues [DEBUG]";
  374. distanceLabel.MarkDirtyRepaint(); // Force UI refresh
  375. Debug.Log($"✅ TravelUI: Distance updated to: {distanceLabel.text}");
  376. }
  377. else
  378. {
  379. Debug.LogError("❌ TravelUI: distanceLabel is null!");
  380. }
  381. if (timeLabel != null)
  382. {
  383. timeLabel.text = $"Travel Time: {UnityEngine.Random.Range(5f, 50f):F1} hours [DEBUG]";
  384. timeLabel.MarkDirtyRepaint(); // Force UI refresh
  385. Debug.Log($"✅ TravelUI: Time updated to: {timeLabel.text}");
  386. }
  387. else
  388. {
  389. Debug.LogError("❌ TravelUI: timeLabel is null!");
  390. }
  391. if (specialCostsLabel != null)
  392. {
  393. specialCostsLabel.text = $"Special Costs: {UnityEngine.Random.Range(0, 100)} gold [DEBUG]";
  394. specialCostsLabel.MarkDirtyRepaint(); // Force UI refresh
  395. Debug.Log($"✅ TravelUI: Special costs updated to: {specialCostsLabel.text}");
  396. }
  397. else
  398. {
  399. Debug.LogError("❌ TravelUI: specialCostsLabel is null!");
  400. }
  401. // Force the entire container to refresh
  402. if (travelContainer != null)
  403. {
  404. travelContainer.MarkDirtyRepaint();
  405. }
  406. }
  407. /// <summary>
  408. /// DEBUG: Test route selection programmatically
  409. /// </summary>
  410. [ContextMenu("DEBUG: Test Route Selection")]
  411. public void DebugTestRouteSelection()
  412. {
  413. Debug.Log("🧪 TravelUI: DEBUG Testing route selection...");
  414. if (availableRoutes == null || availableRoutes.Count == 0)
  415. {
  416. Debug.LogError("❌ TravelUI: No available routes for testing!");
  417. return;
  418. }
  419. // Try to select each route programmatically
  420. for (int i = 0; i < availableRoutes.Count; i++)
  421. {
  422. var route = availableRoutes[i];
  423. Debug.Log($"🔄 TravelUI: Testing route {i + 1}: {route.routeType}");
  424. // Find the corresponding UI element
  425. var routeOptions = routeList.Children().ToList();
  426. if (i < routeOptions.Count)
  427. {
  428. var routeElement = routeOptions[i];
  429. Debug.Log($"🎯 TravelUI: Simulating click on route element {i}");
  430. SelectRoute(route, routeElement);
  431. // Wait a bit and show results
  432. Debug.Log($"📊 TravelUI: After selecting {route.routeType}:");
  433. Debug.Log($" Distance: {distanceLabel?.text ?? "NULL"}");
  434. Debug.Log($" Time: {timeLabel?.text ?? "NULL"}");
  435. Debug.Log($" Special: {specialCostsLabel?.text ?? "NULL"}");
  436. }
  437. else
  438. {
  439. Debug.LogError($"❌ TravelUI: Route element {i} not found in UI!");
  440. }
  441. }
  442. }
  443. /// <summary>
  444. /// Updates travel information display
  445. /// </summary>
  446. private void UpdateTravelInfo()
  447. {
  448. Debug.Log($"🔄 TravelUI.UpdateTravelInfo called - selectedRoute: {selectedRoute?.routeType}");
  449. if (selectedRoute == null)
  450. {
  451. Debug.LogWarning("⚠️ TravelUI.UpdateTravelInfo: selectedRoute is null");
  452. return;
  453. }
  454. Debug.Log($"📊 TravelUI.UpdateTravelInfo: Updating with route {selectedRoute.routeType} - Time: {selectedRoute.estimatedTime:F1}h, Distance: {selectedRoute.totalDistance:F1}, SpecialCosts: {selectedRoute.specialCosts?.Count ?? 0}");
  455. if (distanceLabel != null)
  456. {
  457. distanceLabel.text = $"Distance: {selectedRoute.totalDistance:F1} leagues";
  458. distanceLabel.MarkDirtyRepaint(); // Force UI refresh
  459. Debug.Log($"✅ TravelUI: Updated distance to: {distanceLabel.text}");
  460. }
  461. else
  462. {
  463. Debug.LogWarning("⚠️ TravelUI.UpdateTravelInfo: distanceLabel is null");
  464. }
  465. if (timeLabel != null)
  466. {
  467. timeLabel.text = $"Travel Time: {selectedRoute.estimatedTime:F1} hours";
  468. timeLabel.MarkDirtyRepaint(); // Force UI refresh
  469. Debug.Log($"✅ TravelUI: Updated time to: {timeLabel.text}");
  470. }
  471. else
  472. {
  473. Debug.LogWarning("⚠️ TravelUI.UpdateTravelInfo: timeLabel is null");
  474. }
  475. if (specialCostsLabel != null)
  476. {
  477. if (selectedRoute.specialCosts != null && selectedRoute.specialCosts.Count > 0)
  478. {
  479. var costs = selectedRoute.specialCosts.Select(kvp => $"{kvp.Key}: {kvp.Value}");
  480. specialCostsLabel.text = $"Special Costs: {string.Join(", ", costs)}";
  481. specialCostsLabel.style.display = DisplayStyle.Flex;
  482. specialCostsLabel.MarkDirtyRepaint(); // Force UI refresh
  483. Debug.Log($"✅ TravelUI: Updated special costs to: {specialCostsLabel.text}");
  484. }
  485. else
  486. {
  487. specialCostsLabel.text = "Special Costs: None";
  488. specialCostsLabel.style.display = DisplayStyle.Flex;
  489. specialCostsLabel.MarkDirtyRepaint(); // Force UI refresh
  490. Debug.Log($"✅ TravelUI: Updated special costs to: None");
  491. }
  492. }
  493. else
  494. {
  495. Debug.LogWarning("⚠️ TravelUI.UpdateTravelInfo: specialCostsLabel is null");
  496. }
  497. // Force the entire container to refresh
  498. if (travelContainer != null)
  499. {
  500. travelContainer.MarkDirtyRepaint();
  501. }
  502. Debug.Log("✅ TravelUI.UpdateTravelInfo: Complete");
  503. }
  504. /// <summary>
  505. /// Populates the route selection list
  506. /// </summary>
  507. private void PopulateRouteList()
  508. {
  509. if (routeList == null) return;
  510. // Clear existing route options
  511. routeList.Clear();
  512. // Sort routes by estimated time (fastest first)
  513. var sortedRoutes = availableRoutes.OrderBy(r => r.estimatedTime).ToList();
  514. for (int i = 0; i < sortedRoutes.Count; i++)
  515. {
  516. var route = sortedRoutes[i];
  517. var routeElement = CreateRouteOptionElement(route, i == 0); // First route is selected by default
  518. routeList.Add(routeElement);
  519. }
  520. Debug.Log($"📊 TravelUI: Populated route list with {sortedRoutes.Count} options");
  521. }
  522. /// <summary>
  523. /// Creates a clickable route option element
  524. /// </summary>
  525. private VisualElement CreateRouteOptionElement(TravelRoute route, bool isSelected)
  526. {
  527. var routeOption = new VisualElement();
  528. routeOption.AddToClassList("route-option");
  529. if (isSelected)
  530. {
  531. routeOption.AddToClassList("route-selected");
  532. selectedRoute = route;
  533. }
  534. // Color indicator
  535. var colorIndicator = new VisualElement();
  536. colorIndicator.AddToClassList("route-color-indicator");
  537. // Set color based on route type
  538. Color routeColor = GetRouteDisplayColor(route);
  539. colorIndicator.style.backgroundColor = routeColor;
  540. routeOption.Add(colorIndicator);
  541. // Route details container
  542. var routeDetails = new VisualElement();
  543. routeDetails.AddToClassList("route-details");
  544. // Route type and main info
  545. var routeHeader = new Label($"{GetRouteDisplayName(route)}");
  546. routeHeader.AddToClassList("route-header");
  547. routeDetails.Add(routeHeader);
  548. // Time info
  549. var routeTime = new Label($"Time: {route.estimatedTime:F1}h");
  550. routeTime.AddToClassList("route-time");
  551. routeDetails.Add(routeTime);
  552. // Resource costs (gold, torches, etc.)
  553. var resourceCosts = GetResourceCosts(route);
  554. if (resourceCosts.Count > 0)
  555. {
  556. var costText = string.Join(", ", resourceCosts.Select(kvp => $"{kvp.Value} {kvp.Key}"));
  557. var routeCost = new Label($"Resources: {costText}");
  558. routeCost.AddToClassList("route-cost");
  559. routeDetails.Add(routeCost);
  560. }
  561. else
  562. {
  563. var routeCost = new Label("Resources: None");
  564. routeCost.AddToClassList("route-cost");
  565. routeDetails.Add(routeCost);
  566. }
  567. // Special costs if any
  568. if (route.specialCosts.Count > 0)
  569. {
  570. var specialText = string.Join(", ", route.specialCosts.Select(kvp => $"{kvp.Key}: {kvp.Value}"));
  571. var specialLabel = new Label($"Special: {specialText}");
  572. specialLabel.AddToClassList("route-special");
  573. routeDetails.Add(specialLabel);
  574. }
  575. // Add debug information showing path composition
  576. var debugInfo = GetRouteDebugInfo(route);
  577. if (!string.IsNullOrEmpty(debugInfo))
  578. {
  579. var debugLabel = new Label(debugInfo);
  580. debugLabel.AddToClassList("route-debug");
  581. routeDetails.Add(debugLabel);
  582. }
  583. routeOption.Add(routeDetails);
  584. // Click handler with enhanced debugging
  585. routeOption.RegisterCallback<ClickEvent>(evt =>
  586. {
  587. Debug.Log($"🖱️ TravelUI: Route clicked! Event target: {evt.target}, Route: {route.routeType}");
  588. SelectRoute(route, routeOption);
  589. evt.StopPropagation(); // Prevent event bubbling
  590. });
  591. // Also add mouse events for debugging
  592. routeOption.RegisterCallback<MouseEnterEvent>(evt =>
  593. {
  594. Debug.Log($"🐭 TravelUI: Mouse entered route: {route.routeType}");
  595. });
  596. return routeOption;
  597. }
  598. /// <summary>
  599. /// Handles route selection
  600. /// </summary>
  601. private void SelectRoute(TravelRoute route, VisualElement clickedElement)
  602. {
  603. Debug.Log($"🎯 TravelUI: SelectRoute called for {route.routeType} - Time: {route.estimatedTime:F1}h, Distance: {route.totalDistance:F1}");
  604. Debug.Log($"🔍 TravelUI: SelectRoute - Previous selectedRoute: {selectedRoute?.routeType}");
  605. selectedRoute = route;
  606. // Update visual selection
  607. var allRouteOptions = routeList.Children();
  608. Debug.Log($"📋 TravelUI: SelectRoute - Found {allRouteOptions.Count()} route options in list");
  609. foreach (var option in allRouteOptions)
  610. {
  611. option.RemoveFromClassList("route-selected");
  612. }
  613. clickedElement.AddToClassList("route-selected");
  614. Debug.Log($"✅ TravelUI: SelectRoute - Visual selection updated for {route.routeType}");
  615. Debug.Log($"🔄 TravelUI: About to call UpdateTravelInfo for {route.routeType}");
  616. // Update travel info
  617. UpdateTravelInfo();
  618. // Notify the travel system to highlight this route - FIX THIS CALL
  619. var travelSystem = FindFirstObjectByType<TeamTravelSystem>();
  620. if (travelSystem != null)
  621. {
  622. // Use the correct method name from your travel system
  623. try
  624. {
  625. travelSystem.VisualizeSpecificRoute(route);
  626. Debug.Log($"✅ TravelUI: Successfully called VisualizeSpecificRoute for {route.routeType}");
  627. }
  628. catch (System.Exception e)
  629. {
  630. Debug.LogWarning($"⚠️ TravelUI: Failed to visualize route: {e.Message}");
  631. // Try alternative method names
  632. var method = travelSystem.GetType().GetMethod("HighlightRoute");
  633. if (method != null)
  634. {
  635. method.Invoke(travelSystem, new object[] { route });
  636. Debug.Log($"✅ TravelUI: Used HighlightRoute instead");
  637. }
  638. }
  639. }
  640. else
  641. {
  642. Debug.LogWarning("⚠️ TravelUI: TeamTravelSystem not found for route visualization");
  643. }
  644. Debug.Log($"🎯 TravelUI: Selected {route.routeType} route - SelectRoute complete");
  645. }
  646. /// <summary>
  647. /// Gets display color for route based on type (GPS-style)
  648. /// </summary>
  649. private Color GetRouteDisplayColor(TravelRoute route)
  650. {
  651. // Match colors from TeamTravelSystem GPS-style
  652. switch (route.routeType)
  653. {
  654. case RouteType.Standard:
  655. return Color.blue; // Shortest route
  656. case RouteType.RoadPreferred:
  657. return Color.green; // Fastest route
  658. case RouteType.Special:
  659. return Color.yellow; // Cheapest route
  660. default:
  661. return Color.blue;
  662. }
  663. }
  664. /// <summary>
  665. /// Gets GPS-style display name for route type
  666. /// </summary>
  667. private string GetRouteDisplayName(TravelRoute route)
  668. {
  669. switch (route.routeType)
  670. {
  671. case RouteType.Standard:
  672. return "Shortest Route";
  673. case RouteType.RoadPreferred:
  674. return "Fastest Route";
  675. case RouteType.Special:
  676. return "Cheapest Route";
  677. default:
  678. return "Unknown Route";
  679. }
  680. }
  681. /// <summary>
  682. /// Handles start travel button click
  683. /// </summary>
  684. private void OnStartTravelClicked()
  685. {
  686. if (selectedRoute == null)
  687. {
  688. Debug.LogWarning("⚠️ TravelUI: No route selected for travel");
  689. return;
  690. }
  691. Debug.Log($"🚶 TravelUI: Starting travel with {selectedRoute.routeType} route");
  692. // Start travel through the travel system
  693. var travelSystem = TeamTravelSystem.Instance;
  694. if (travelSystem != null)
  695. {
  696. travelSystem.StartTravel(selectedRoute);
  697. }
  698. // Hide UI
  699. HideTravelPanel();
  700. }
  701. /// <summary>
  702. /// Handles cancel button click
  703. /// </summary>
  704. private void OnCancelTravelClicked()
  705. {
  706. Debug.Log("❌ TravelUI: Travel cancelled by user");
  707. // Cancel travel planning
  708. var travelSystem = TeamTravelSystem.Instance;
  709. if (travelSystem != null)
  710. {
  711. travelSystem.CancelTravelPlanning();
  712. }
  713. // Hide UI
  714. HideTravelPanel();
  715. }
  716. /// <summary>
  717. /// Updates UI for a selected route (called by TeamTravelSystem via reflection)
  718. /// </summary>
  719. public void UpdateUIForSelectedRoute(TravelRoute route)
  720. {
  721. if (!isUIVisible)
  722. {
  723. return;
  724. }
  725. selectedRoute = route;
  726. UpdateTravelInfo();
  727. // Update visual selection in route list
  728. if (routeList != null)
  729. {
  730. var routeOptions = routeList.Children().ToList();
  731. for (int i = 0; i < routeOptions.Count && i < availableRoutes.Count; i++)
  732. {
  733. var routeOption = routeOptions[i];
  734. var routeData = availableRoutes.OrderBy(r => r.estimatedTime).ToList()[i];
  735. if (routeData == route)
  736. {
  737. routeOption.AddToClassList("route-selected");
  738. Debug.Log($"🎯 TravelUI.UpdateUIForSelectedRoute: Visual selection updated for route {i}");
  739. }
  740. else
  741. {
  742. routeOption.RemoveFromClassList("route-selected");
  743. }
  744. }
  745. }
  746. else
  747. {
  748. Debug.LogWarning("⚠️ TravelUI.UpdateUIForSelectedRoute: routeList is null");
  749. }
  750. Debug.Log($"🔄 TravelUI: Updated UI for {route.routeType} route");
  751. }
  752. /// <summary>
  753. /// Checks if UI is blocking input (called by TeamTravelSystem via reflection)
  754. /// </summary>
  755. public bool IsUIBlocking()
  756. {
  757. return isUIVisible;
  758. }
  759. /// <summary>
  760. /// <summary>
  761. /// IClickBlocker implementation: Check if this UI should block clicks at the given position
  762. /// </summary>
  763. public bool IsBlockingClick(Vector2 screenPosition)
  764. {
  765. if (!isUIVisible || travelPanel == null)
  766. {
  767. return false;
  768. }
  769. // Convert screen position to panel-relative position
  770. Vector2 panelPosition = RuntimePanelUtils.ScreenToPanel(travelPanel.panel, screenPosition);
  771. // Check if the position is within the panel bounds
  772. bool withinBounds = travelPanel.worldBound.Contains(panelPosition);
  773. return withinBounds;
  774. }
  775. /// <summary>
  776. /// Legacy method for backward compatibility - now delegates to IsBlockingClick
  777. /// Check if a screen position (in Unity's screen coordinates) is within the UI panel
  778. /// </summary>
  779. public bool IsPointWithinUI(Vector2 screenPosition)
  780. {
  781. return IsBlockingClick(screenPosition);
  782. }
  783. /// <summary>
  784. /// Public getters for external access
  785. /// </summary>
  786. public bool IsVisible => isUIVisible;
  787. public TravelRoute GetSelectedRoute() => selectedRoute;
  788. /// <summary>
  789. /// Forces the UI to refresh with new route data - called externally when routes are recalculated
  790. /// </summary>
  791. public void RefreshWithNewRoutes(List<TravelRoute> routes)
  792. {
  793. Debug.Log($"🔄 TravelUI: RefreshWithNewRoutes called externally with {routes.Count} routes");
  794. if (!isUIVisible)
  795. {
  796. Debug.LogWarning("⚠️ TravelUI: RefreshWithNewRoutes called but UI is not visible");
  797. return;
  798. }
  799. UpdateRoutesAndRefreshUI(currentDestination, routes);
  800. }
  801. /// <summary>
  802. /// Sets up drag functionality for the travel panel
  803. /// </summary>
  804. private void SetupDragFunctionality(VisualElement dragHandle, VisualElement panelToDrag)
  805. {
  806. bool isDragging = false;
  807. Vector2 startPosition = Vector2.zero;
  808. Vector2 startMousePosition = Vector2.zero;
  809. dragHandle.RegisterCallback<MouseDownEvent>(evt =>
  810. {
  811. if (evt.button == 0) // Left mouse button
  812. {
  813. isDragging = true;
  814. startPosition = new Vector2(panelToDrag.style.left.value.value, panelToDrag.style.top.value.value);
  815. startMousePosition = evt.mousePosition;
  816. dragHandle.CaptureMouse();
  817. evt.StopPropagation();
  818. }
  819. });
  820. dragHandle.RegisterCallback<MouseMoveEvent>(evt =>
  821. {
  822. if (isDragging)
  823. {
  824. Vector2 delta = evt.mousePosition - startMousePosition;
  825. panelToDrag.style.left = startPosition.x + delta.x;
  826. panelToDrag.style.top = startPosition.y + delta.y;
  827. evt.StopPropagation();
  828. }
  829. });
  830. dragHandle.RegisterCallback<MouseUpEvent>(evt =>
  831. {
  832. if (isDragging)
  833. {
  834. isDragging = false;
  835. dragHandle.ReleaseMouse();
  836. evt.StopPropagation();
  837. }
  838. });
  839. }
  840. /// <summary>
  841. /// Gets debug information about route composition
  842. /// </summary>
  843. private string GetRouteDebugInfo(TravelRoute route)
  844. {
  845. if (route?.path == null || route.path.Count == 0) return "";
  846. // Get terrain analysis from TeamTravelSystem
  847. var travelSystem = TeamTravelSystem.Instance;
  848. if (travelSystem == null) return "";
  849. // Count different terrain types in the route
  850. var terrainCounts = new Dictionary<string, int>();
  851. // This is a simplified analysis - in a full implementation,
  852. // you'd access the actual map data through the travel system
  853. float avgCostPerTile = route.totalCost / route.path.Count;
  854. string preference = "";
  855. switch (route.routeType)
  856. {
  857. case RouteType.RoadPreferred:
  858. preference = "Prefers roads";
  859. break;
  860. case RouteType.Special:
  861. preference = "Uses ferries/tunnels";
  862. break;
  863. default:
  864. preference = "Cheapest path";
  865. break;
  866. }
  867. return $"{preference} • Avg: {avgCostPerTile:F1}/tile";
  868. }
  869. /// <summary>
  870. /// Gets resource costs for a route (gold, torches, etc.)
  871. /// </summary>
  872. private Dictionary<string, int> GetResourceCosts(TravelRoute route)
  873. {
  874. var costs = new Dictionary<string, int>();
  875. // Add special costs from the route (with null safety)
  876. if (route?.specialCosts != null)
  877. {
  878. foreach (var specialCost in route.specialCosts)
  879. {
  880. switch (specialCost.Key)
  881. {
  882. case "Ferry":
  883. case "Bridge Toll":
  884. costs["Gold"] = costs.GetValueOrDefault("Gold", 0) + specialCost.Value;
  885. break;
  886. case "Tunnel Torch":
  887. costs["Torches"] = costs.GetValueOrDefault("Torches", 0) + specialCost.Value;
  888. break;
  889. case "Mountain Guide":
  890. costs["Gold"] = costs.GetValueOrDefault("Gold", 0) + specialCost.Value;
  891. break;
  892. default:
  893. costs[specialCost.Key] = specialCost.Value;
  894. break;
  895. }
  896. }
  897. }
  898. return costs;
  899. }
  900. /// <summary>
  901. /// Ensures the travel UI doesn't overlap with the team overview panel on the right side
  902. /// </summary>
  903. private void EnsureNoTeamOverviewOverlap()
  904. {
  905. if (travelContainer == null) return;
  906. // Check if team overview exists
  907. var teamOverview = FindFirstObjectByType<TeamOverviewController>();
  908. if (teamOverview != null)
  909. {
  910. // The CSS already centers the travel UI, but we can add extra margin if needed
  911. // This is a safeguard to ensure proper spacing
  912. var panel = travelContainer.Q("TravelPanel");
  913. if (panel != null)
  914. {
  915. // Add some extra margin to avoid any potential overlap
  916. panel.style.marginRight = 320; // Team overview width + padding
  917. }
  918. }
  919. }
  920. }