TravelUI.cs 35 KB

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