TravelUI.cs 35 KB

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