TravelUI.cs 35 KB

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