TeamTravelSystem.cs 76 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. /// <summary>
  6. /// New comprehensive travel system that handles movement costs, pathfinding, and visual planning.
  7. /// Integrates with SimpleTeamPlacement and MapMaker2 for seamless map-based travel.
  8. /// </summary>
  9. public class TeamTravelSystem : MonoBehaviour
  10. {
  11. [Header("Movement Costs")]
  12. [Range(0.1f, 5f)]
  13. public float plainsMovementCost = 1f;
  14. [Range(0.1f, 5f)]
  15. public float forestMovementCost = 2f;
  16. [Range(0.1f, 5f)]
  17. public float mountainMovementCost = 4f;
  18. [Range(0.1f, 2f)]
  19. public float roadMovementCost = 0.5f;
  20. [Range(0.1f, 2f)]
  21. public float townMovementCost = 0.1f; // Almost instant travel inside towns
  22. [Range(0.1f, 2f)]
  23. public float bridgeMovementCost = 0.6f;
  24. [Header("Special Travel Costs")]
  25. [Range(1, 100)]
  26. public int ferryBaseCost = 25; // Gold cost for ferry
  27. [Range(0.1f, 2f)]
  28. public float ferryMovementCost = 0.7f; // Faster than walking but costs money
  29. [Range(1, 50)]
  30. public int tunnelTorchCost = 10; // Torch cost for tunnel travel
  31. [Range(0.1f, 2f)]
  32. public float tunnelWithTorchCost = 0.8f; // Fast with torch
  33. [Range(1f, 5f)]
  34. public float tunnelWithoutTorchCost = 3f; // Slow and dangerous without torch
  35. [Range(1, 20)]
  36. public int bridgeTollCost = 5; // Gold cost for major bridge crossings
  37. [Range(1, 30)]
  38. public int mountainPassCost = 15; // Guide cost for dangerous mountain passes
  39. [Header("River Crossing")]
  40. [Range(2f, 10f)]
  41. public float riverCrossingCost = 2f; // Reduced from 6f - more reasonable river crossing
  42. [Range(1f, 8f)]
  43. public float lakeCrossingCost = 2.5f; // Reduced from 4f - more reasonable lake crossing
  44. [Range(5f, 15f)]
  45. public float oceanCrossingCost = 8f; // Reduced from 10f but still expensive
  46. [Header("Visual Path Settings")]
  47. public Material pathLineMaterial;
  48. public Color standardPathColor = Color.blue;
  49. public Color expensivePathColor = Color.red;
  50. public Color alternativePathColor = Color.yellow;
  51. [Range(0.1f, 5f)]
  52. public float pathLineWidth = 2.5f; // Increased for much better visibility
  53. [Range(-10f, 10f)]
  54. public float pathLineHeight = 1f; // Height above map tiles for path lines
  55. [Header("Debug")]
  56. public bool showDebugLogs = false;
  57. public bool showMovementCosts = false;
  58. // References
  59. private SimpleTeamPlacement teamPlacement;
  60. private MapMaker2 mapMaker;
  61. private Camera mapCamera;
  62. private TravelUI travelUI;
  63. private CombatEventPopupUXML combatEventPopup;
  64. // Travel state
  65. private Vector2Int? plannedDestination;
  66. private List<TravelRoute> availableRoutes = new List<TravelRoute>();
  67. private List<GameObject> currentPathVisualizations = new List<GameObject>(); // Store multiple route lines
  68. private bool isTraveling = false;
  69. private int currentRouteIndex = 0; // For cycling through route visualizations
  70. // Journey cancellation
  71. private bool shouldCancelJourney = false;
  72. private Coroutine currentTravelCoroutine = null;
  73. private TravelRoute currentRoute = null;
  74. // Time skip functionality
  75. [Header("Time Controls")]
  76. public float timeMultiplier = 1f; // Current time speed multiplier
  77. public float[] timeSpeedOptions = { 1f, 2f, 4f, 8f }; // Available speed options
  78. private int currentTimeSpeedIndex = 0;
  79. // Static instance
  80. public static TeamTravelSystem Instance { get; private set; }
  81. void Awake()
  82. {
  83. if (Instance == null)
  84. {
  85. Instance = this;
  86. }
  87. else
  88. {
  89. Debug.LogWarning("Multiple TeamTravelSystem instances found. Destroying this one.");
  90. Destroy(gameObject);
  91. return;
  92. }
  93. }
  94. void Start()
  95. {
  96. // Find required components
  97. teamPlacement = FindFirstObjectByType<SimpleTeamPlacement>();
  98. mapMaker = FindFirstObjectByType<MapMaker2>();
  99. mapCamera = Camera.main;
  100. travelUI = FindFirstObjectByType<TravelUI>();
  101. combatEventPopup = FindFirstObjectByType<CombatEventPopupUXML>();
  102. // Initialize pathLineMaterial if not assigned
  103. if (pathLineMaterial == null)
  104. {
  105. pathLineMaterial = CreateDefaultPathMaterial();
  106. }
  107. if (teamPlacement == null)
  108. {
  109. Debug.LogError("SimpleTeamPlacement not found! TeamTravelSystem requires it.");
  110. }
  111. if (mapMaker == null)
  112. {
  113. Debug.LogError("MapMaker2 not found! TeamTravelSystem requires it.");
  114. }
  115. if (travelUI == null)
  116. {
  117. Debug.LogWarning("TravelUI not found! UI features will be limited.");
  118. }
  119. }
  120. void Update()
  121. {
  122. HandleInput();
  123. }
  124. private void HandleInput()
  125. {
  126. // Time speed controls (available during travel)
  127. if (isTraveling)
  128. {
  129. if (Input.GetKeyDown(KeyCode.Period) || Input.GetKeyDown(KeyCode.RightArrow)) // > key or right arrow
  130. {
  131. IncreaseTimeSpeed();
  132. }
  133. else if (Input.GetKeyDown(KeyCode.Comma) || Input.GetKeyDown(KeyCode.LeftArrow)) // < key or left arrow
  134. {
  135. DecreaseTimeSpeed();
  136. }
  137. else if (Input.GetKeyDown(KeyCode.Space)) // Space to reset speed
  138. {
  139. ResetTimeSpeed();
  140. }
  141. else if (Input.GetKeyDown(KeyCode.Escape) || Input.GetKeyDown(KeyCode.C)) // ESC or C to cancel journey
  142. {
  143. CancelJourney();
  144. }
  145. }
  146. // Handle map clicking for travel planning (only if click is outside UI panel)
  147. if (Input.GetMouseButtonDown(0) && !isTraveling)
  148. {
  149. // Check if click is within the UI panel bounds
  150. if (travelUI != null && travelUI.IsVisible && travelUI.IsPointWithinUI(Input.mousePosition))
  151. {
  152. return; // Block map interaction if clicking within UI panel
  153. }
  154. // Check if click is within the combat popup bounds
  155. if (combatEventPopup != null && combatEventPopup.IsVisible && combatEventPopup.IsPointWithinUI(Input.mousePosition))
  156. {
  157. return; // Block map interaction if clicking within combat popup
  158. }
  159. Vector3 mouseWorldPos = GetMouseWorldPosition();
  160. if (mouseWorldPos != Vector3.zero)
  161. {
  162. Vector2Int clickedTile = WorldToTilePosition(mouseWorldPos);
  163. PlanTravelTo(clickedTile);
  164. }
  165. }
  166. // Check if TravelUI is blocking other input (non-map clicks)
  167. if (IsTravelUIBlocking())
  168. {
  169. return; // Block other inputs but allow map clicks above
  170. }
  171. // Cancel travel planning
  172. if (Input.GetKeyDown(KeyCode.Escape))
  173. {
  174. CancelTravelPlanning();
  175. }
  176. // Cycle through route visualizations
  177. if (Input.GetKeyDown(KeyCode.Tab) && availableRoutes.Count > 1)
  178. {
  179. currentRouteIndex = (currentRouteIndex + 1) % availableRoutes.Count;
  180. var selectedRoute = availableRoutes[currentRouteIndex];
  181. // Highlight the selected route without clearing others
  182. HighlightSpecificRoute(selectedRoute);
  183. // Update the UI to show the new selected route
  184. if (travelUI != null)
  185. {
  186. travelUI.UpdateUIForSelectedRoute(selectedRoute);
  187. }
  188. if (showDebugLogs)
  189. {
  190. Debug.Log($"Switched to {selectedRoute.routeType} route (Time: {selectedRoute.estimatedTime:F1}h)");
  191. }
  192. }
  193. // Debug key to show movement costs
  194. if (Input.GetKeyDown(KeyCode.C) && showMovementCosts)
  195. {
  196. ShowMovementCostInfo();
  197. }
  198. // Toggle debug logs
  199. if (Input.GetKeyDown(KeyCode.F8))
  200. {
  201. showDebugLogs = !showDebugLogs;
  202. Debug.Log($"Travel System Debug Logs: {(showDebugLogs ? "ENABLED" : "DISABLED")}");
  203. }
  204. }
  205. /// <summary>
  206. /// Plans travel to the specified destination and shows available routes
  207. /// </summary>
  208. public void PlanTravelTo(Vector2Int destination)
  209. {
  210. if (!teamPlacement.IsTeamPlaced())
  211. {
  212. Debug.LogWarning("❌ Cannot plan travel: Team not placed yet");
  213. return;
  214. }
  215. Vector2Int currentPosition = teamPlacement.GetTeamPosition();
  216. if (currentPosition == destination)
  217. {
  218. Debug.Log("Already at destination!");
  219. return;
  220. }
  221. plannedDestination = destination;
  222. currentRouteIndex = 0; // Reset route index for new destination
  223. if (showDebugLogs)
  224. {
  225. Debug.Log($"🗺️ Planning travel from {currentPosition} to {destination}");
  226. }
  227. // Calculate different route options
  228. CalculateRoutes(currentPosition, destination);
  229. // If UI is already visible, refresh it with new routes
  230. if (travelUI != null && travelUI.IsVisible)
  231. {
  232. Debug.Log("🔄 TeamTravelSystem: UI is visible, refreshing with new routes");
  233. travelUI.RefreshWithNewRoutes(availableRoutes);
  234. }
  235. // Show all available routes visually with different colors
  236. if (availableRoutes.Count > 0)
  237. {
  238. // Clear any existing visualizations first
  239. ClearPathVisualization();
  240. // Select the fastest route (shortest estimated time) by default
  241. var routesByTime = availableRoutes.OrderBy(r => r.estimatedTime).ToList();
  242. TravelRoute selectedRoute = routesByTime.First();
  243. // Update currentRouteIndex to match the selected route in the original list
  244. currentRouteIndex = availableRoutes.IndexOf(selectedRoute);
  245. // Visualize all routes with the fastest one highlighted on top
  246. VisualizeAllRoutes(availableRoutes);
  247. if (showDebugLogs)
  248. {
  249. Debug.Log($"🔧 About to show UI for {selectedRoute.routeType} route with {selectedRoute.path.Count} waypoints");
  250. }
  251. // Show travel UI if available
  252. ShowTravelUIIfAvailable(destination, availableRoutes);
  253. if (showDebugLogs)
  254. {
  255. Debug.Log($"Found {availableRoutes.Count} route options. Selected {selectedRoute.routeType} route: {selectedRoute.estimatedTime:F1}h time");
  256. Debug.Log("Use TAB to cycle through different route options");
  257. }
  258. }
  259. }
  260. /// <summary>
  261. /// Calculates different route options including fastest, shortest, and cheapest routes
  262. /// </summary>
  263. private void CalculateRoutes(Vector2Int start, Vector2Int destination)
  264. {
  265. availableRoutes.Clear();
  266. var mapData = mapMaker.GetMapData();
  267. if (mapData == null) return;
  268. if (showDebugLogs)
  269. {
  270. Debug.Log($"🔍 Calculating routes from {start} to {destination}");
  271. }
  272. // Fastest route (heavily prefers roads and fast terrain)
  273. var fastestRoute = FindPath(start, destination, RouteType.RoadPreferred);
  274. if (fastestRoute != null)
  275. {
  276. availableRoutes.Add(fastestRoute);
  277. if (showDebugLogs)
  278. {
  279. Debug.Log($"Fastest route: {fastestRoute.estimatedTime:F1}h time, {fastestRoute.path.Count} waypoints");
  280. }
  281. }
  282. // Shortest route (direct path, minimal waypoints)
  283. var shortestRoute = FindPath(start, destination, RouteType.Standard);
  284. if (shortestRoute != null)
  285. {
  286. bool similar = RoutesSimilar(fastestRoute, shortestRoute);
  287. if (showDebugLogs)
  288. {
  289. Debug.Log($"Shortest route: {shortestRoute.estimatedTime:F1}h time, {shortestRoute.path.Count} waypoints, similar to fastest: {similar}");
  290. }
  291. if (!similar)
  292. {
  293. availableRoutes.Add(shortestRoute);
  294. }
  295. }
  296. // Cheapest route (minimizes resource costs first, then travel time)
  297. var cheapestRoute = FindCheapestPath(start, destination);
  298. if (cheapestRoute != null)
  299. {
  300. bool similarToFastest = RoutesSimilar(fastestRoute, cheapestRoute);
  301. bool similarToShortest = RoutesSimilar(shortestRoute, cheapestRoute);
  302. if (showDebugLogs)
  303. {
  304. Debug.Log($"Cheapest route: {cheapestRoute.estimatedTime:F1}h time, {GetTotalResourceCost(cheapestRoute)} resource cost, {cheapestRoute.path.Count} waypoints");
  305. }
  306. if (!similarToFastest && !similarToShortest)
  307. {
  308. availableRoutes.Add(cheapestRoute);
  309. }
  310. }
  311. // If we ended up with only one route, try to generate alternatives with different parameters
  312. if (availableRoutes.Count == 1)
  313. {
  314. if (showDebugLogs)
  315. {
  316. Debug.Log("Only one unique route found, generating alternatives...");
  317. }
  318. // Try a more direct route that ignores some terrain penalties
  319. var directRoute = FindDirectPath(start, destination);
  320. if (directRoute != null && !RoutesSimilar(availableRoutes[0], directRoute))
  321. {
  322. availableRoutes.Add(directRoute);
  323. if (showDebugLogs)
  324. {
  325. Debug.Log($"Direct route added: {directRoute.estimatedTime:F1}h time");
  326. }
  327. }
  328. }
  329. if (showDebugLogs)
  330. {
  331. Debug.Log($"📊 Total unique routes calculated: {availableRoutes.Count}");
  332. }
  333. }
  334. /// <summary>
  335. /// Finds the best path between two points using A* pathfinding with different route preferences
  336. /// </summary>
  337. private TravelRoute FindPath(Vector2Int start, Vector2Int destination, RouteType routeType)
  338. {
  339. var mapData = mapMaker.GetMapData();
  340. Debug.Log($"🗺️ Finding {routeType} path from {start} to {destination}");
  341. var openSet = new List<PathNode>();
  342. var closedSet = new HashSet<Vector2Int>();
  343. var allNodes = new Dictionary<Vector2Int, PathNode>();
  344. var startNode = new PathNode
  345. {
  346. position = start,
  347. gCost = 0,
  348. hCost = CalculateHeuristic(start, destination, routeType),
  349. parent = null
  350. };
  351. openSet.Add(startNode);
  352. allNodes[start] = startNode;
  353. int iterations = 0;
  354. const int maxIterations = 10000; // Prevent infinite loops
  355. while (openSet.Count > 0 && iterations < maxIterations)
  356. {
  357. iterations++;
  358. // Find node with lowest fCost
  359. var currentNode = openSet.OrderBy(n => n.fCost).ThenBy(n => n.hCost).First();
  360. openSet.Remove(currentNode);
  361. closedSet.Add(currentNode.position);
  362. // Reached destination
  363. if (currentNode.position == destination)
  364. {
  365. var route = ReconstructRoute(currentNode, routeType);
  366. if (showDebugLogs)
  367. {
  368. Debug.Log($"{routeType} path found in {iterations} iterations, final cost: {currentNode.gCost:F1}");
  369. }
  370. // Analyze the route composition
  371. if (showDebugLogs)
  372. {
  373. AnalyzeRouteComposition(route);
  374. }
  375. return route;
  376. }
  377. // Check neighbors
  378. var neighbors = GetNeighbors(currentNode.position);
  379. foreach (var neighbor in neighbors)
  380. {
  381. if (closedSet.Contains(neighbor) || !mapData.IsValidPosition(neighbor.x, neighbor.y))
  382. continue;
  383. float movementCost = GetMovementCost(currentNode.position, neighbor, routeType);
  384. if (movementCost < 0) continue; // Impassable terrain
  385. float tentativeGCost = currentNode.gCost + movementCost;
  386. if (!allNodes.ContainsKey(neighbor))
  387. {
  388. allNodes[neighbor] = new PathNode
  389. {
  390. position = neighbor,
  391. gCost = float.MaxValue,
  392. hCost = CalculateHeuristic(neighbor, destination, routeType),
  393. parent = null
  394. };
  395. }
  396. var neighborNode = allNodes[neighbor];
  397. if (tentativeGCost < neighborNode.gCost)
  398. {
  399. neighborNode.parent = currentNode;
  400. neighborNode.gCost = tentativeGCost;
  401. if (!openSet.Contains(neighborNode))
  402. {
  403. openSet.Add(neighborNode);
  404. }
  405. }
  406. }
  407. }
  408. Debug.LogWarning($"❌ {routeType} path not found after {iterations} iterations (max: {maxIterations})");
  409. return null; // No path found
  410. }
  411. /// <summary>
  412. /// Gets movement cost for moving from one tile to another, considering route type preferences
  413. /// </summary>
  414. private float GetMovementCost(Vector2Int from, Vector2Int to, RouteType routeType)
  415. {
  416. var mapData = mapMaker.GetMapData();
  417. var toTile = mapData.GetTile(to.x, to.y);
  418. // Base movement cost by terrain
  419. float baseCost = GetTerrainMovementCost(toTile);
  420. // Feature modifications
  421. float featureCost = GetFeatureMovementCost(toTile, routeType);
  422. // Combine costs
  423. float totalCost = baseCost * featureCost;
  424. // Route type adjustments
  425. switch (routeType)
  426. {
  427. case RouteType.Standard:
  428. // Shortest route: minimize distance regardless of terrain difficulty
  429. // No terrain or road preferences - pure distance optimization
  430. break;
  431. case RouteType.RoadPreferred:
  432. // Fastest route: heavily favors roads and fast terrain, considers costly but fast options
  433. if (toTile.featureType == FeatureType.Road)
  434. {
  435. totalCost *= 0.3f; // Strong road preference (70% discount) - roads are much faster but realistic
  436. }
  437. else if (toTile.featureType == FeatureType.Tunnel)
  438. {
  439. // Tunnels are fast for fastest route (ignore resource cost for speed optimization)
  440. totalCost *= 0.4f; // Very fast travel through tunnels
  441. }
  442. else if (toTile.featureType == FeatureType.Ferry)
  443. {
  444. // Ferries are reasonably fast for fastest route
  445. totalCost *= 0.6f; // Fast water crossing
  446. }
  447. else
  448. {
  449. // Penalize non-road movement for road-preferred routes
  450. totalCost *= 1.5f; // 50% penalty for off-road movement
  451. }
  452. break;
  453. case RouteType.Special:
  454. // Cheapest route: avoid resource costs, but don't make travel time unrealistic
  455. // Moderate penalties for features that cost resources
  456. if (toTile.featureType == FeatureType.Ferry || toTile.featureType == FeatureType.Tunnel)
  457. totalCost *= 3.0f; // Significant penalty for resource-costing features (was 10x)
  458. // Moderate penalties for features that might trigger resource costs
  459. if (toTile.featureType == FeatureType.Bridge)
  460. totalCost *= 2.0f; // Penalty for bridges (potential tolls) (was 5x)
  461. // Light penalty for terrain that requires guides/special costs
  462. if (toTile.terrainType == TerrainType.Mountain)
  463. totalCost *= 2.0f; // Penalty for mountains (guide costs) (was 5x)
  464. // Still avoid slow terrain, but secondary to cost avoidance
  465. if (toTile.terrainType == TerrainType.River || toTile.terrainType == TerrainType.Lake)
  466. totalCost *= 1.5f; // Light penalty for water crossings (was 2x)
  467. // Prefer roads since they're free and fast
  468. if (toTile.featureType == FeatureType.Road)
  469. totalCost *= 0.5f; // Good road preference (50% discount)
  470. break;
  471. }
  472. return totalCost;
  473. }
  474. /// <summary>
  475. /// Gets base movement cost for different terrain types
  476. /// </summary>
  477. private float GetTerrainMovementCost(MapTile tile)
  478. {
  479. switch (tile.terrainType)
  480. {
  481. case TerrainType.Plains:
  482. return plainsMovementCost;
  483. case TerrainType.Forest:
  484. return forestMovementCost;
  485. case TerrainType.Mountain:
  486. return mountainMovementCost;
  487. case TerrainType.Ocean:
  488. return oceanCrossingCost; // Allow dangerous ocean crossing
  489. case TerrainType.Lake:
  490. return lakeCrossingCost; // Allow swimming across lakes
  491. case TerrainType.River:
  492. return riverCrossingCost; // Allow slow river crossing
  493. default:
  494. return plainsMovementCost;
  495. }
  496. }
  497. /// <summary>
  498. /// Gets movement cost modifications from features (roads, bridges, etc.)
  499. /// </summary>
  500. private float GetFeatureMovementCost(MapTile tile, RouteType routeType)
  501. {
  502. switch (tile.featureType)
  503. {
  504. case FeatureType.Road:
  505. return roadMovementCost; // Fast movement on roads
  506. case FeatureType.Bridge:
  507. return roadMovementCost; // Bridges are as fast as roads (crossing structures)
  508. case FeatureType.Town:
  509. case FeatureType.Village:
  510. return townMovementCost; // Very fast through settlements
  511. case FeatureType.Ferry:
  512. return ferryMovementCost; // Ferries are fast transportation (cost is in gold, not time)
  513. case FeatureType.Tunnel:
  514. return tunnelWithTorchCost; // Tunnels are fast with torch (cost is in torches, not time)
  515. default:
  516. return 1f; // No modification
  517. }
  518. }
  519. /// <summary>
  520. /// Calculates heuristic distance for A* pathfinding with route type considerations
  521. /// </summary>
  522. private float CalculateHeuristic(Vector2Int from, Vector2Int to, RouteType routeType = RouteType.Standard)
  523. {
  524. float baseHeuristic = Vector2Int.Distance(from, to) * plainsMovementCost;
  525. // For road-preferred routes, make the heuristic slightly more optimistic about finding roads
  526. if (routeType == RouteType.RoadPreferred)
  527. {
  528. // Assume we'll find roads for some of the journey
  529. baseHeuristic *= 0.8f; // Slightly more optimistic heuristic
  530. }
  531. return baseHeuristic;
  532. }
  533. /// <summary>
  534. /// Gets valid neighboring tiles for pathfinding
  535. /// </summary>
  536. private List<Vector2Int> GetNeighbors(Vector2Int position)
  537. {
  538. var neighbors = new List<Vector2Int>();
  539. // 8-directional movement
  540. for (int dx = -1; dx <= 1; dx++)
  541. {
  542. for (int dy = -1; dy <= 1; dy++)
  543. {
  544. if (dx == 0 && dy == 0) continue;
  545. Vector2Int neighbor = new Vector2Int(position.x + dx, position.y + dy);
  546. neighbors.Add(neighbor);
  547. }
  548. }
  549. return neighbors;
  550. }
  551. /// <summary>
  552. /// Reconstructs the final route from pathfinding results
  553. /// </summary>
  554. private TravelRoute ReconstructRoute(PathNode destinationNode, RouteType routeType)
  555. {
  556. var route = new TravelRoute
  557. {
  558. routeType = routeType,
  559. path = new List<Vector2Int>(),
  560. totalCost = destinationNode.gCost,
  561. totalDistance = 0f,
  562. estimatedTime = 0f,
  563. specialCosts = new Dictionary<string, int>()
  564. };
  565. var current = destinationNode;
  566. while (current != null)
  567. {
  568. route.path.Insert(0, current.position);
  569. current = current.parent;
  570. }
  571. // Calculate additional route information
  572. CalculateRouteDetails(route);
  573. return route;
  574. }
  575. /// <summary>
  576. /// Finds a more direct path that ignores some terrain penalties for route variety
  577. /// </summary>
  578. private TravelRoute FindDirectPath(Vector2Int start, Vector2Int destination)
  579. {
  580. var mapData = mapMaker.GetMapData();
  581. Debug.Log($"🎯 Finding direct path from {start} to {destination}");
  582. var openSet = new List<PathNode>();
  583. var closedSet = new HashSet<Vector2Int>();
  584. var allNodes = new Dictionary<Vector2Int, PathNode>();
  585. var startNode = new PathNode
  586. {
  587. position = start,
  588. gCost = 0,
  589. hCost = CalculateHeuristic(start, destination),
  590. parent = null
  591. };
  592. openSet.Add(startNode);
  593. allNodes[start] = startNode;
  594. int iterations = 0;
  595. const int maxIterations = 10000;
  596. while (openSet.Count > 0 && iterations < maxIterations)
  597. {
  598. iterations++;
  599. var currentNode = openSet.OrderBy(n => n.fCost).ThenBy(n => n.hCost).First();
  600. openSet.Remove(currentNode);
  601. closedSet.Add(currentNode.position);
  602. if (currentNode.position == destination)
  603. {
  604. var route = ReconstructRoute(currentNode, RouteType.Special);
  605. route.routeType = RouteType.Special; // Mark as direct route
  606. Debug.Log($"✅ Direct path found in {iterations} iterations, final cost: {currentNode.gCost:F1}");
  607. CalculateRouteDetails(route);
  608. return route;
  609. }
  610. var neighbors = GetNeighbors(currentNode.position);
  611. foreach (var neighbor in neighbors)
  612. {
  613. if (closedSet.Contains(neighbor) || !mapData.IsValidPosition(neighbor.x, neighbor.y))
  614. continue;
  615. // Use simplified movement cost for direct routes (less terrain penalty)
  616. float movementCost = GetDirectMovementCost(currentNode.position, neighbor);
  617. if (movementCost < 0) continue;
  618. float tentativeGCost = currentNode.gCost + movementCost;
  619. if (!allNodes.ContainsKey(neighbor))
  620. {
  621. allNodes[neighbor] = new PathNode
  622. {
  623. position = neighbor,
  624. gCost = float.MaxValue,
  625. hCost = CalculateHeuristic(neighbor, destination),
  626. parent = null
  627. };
  628. }
  629. var neighborNode = allNodes[neighbor];
  630. if (tentativeGCost < neighborNode.gCost)
  631. {
  632. neighborNode.parent = currentNode;
  633. neighborNode.gCost = tentativeGCost;
  634. if (!openSet.Contains(neighborNode))
  635. {
  636. openSet.Add(neighborNode);
  637. }
  638. }
  639. }
  640. }
  641. Debug.LogWarning($"❌ Direct path not found after {iterations} iterations");
  642. return null;
  643. }
  644. /// <summary>
  645. /// Simplified movement cost calculation for direct routes
  646. /// </summary>
  647. private float GetDirectMovementCost(Vector2Int from, Vector2Int to)
  648. {
  649. var mapData = mapMaker.GetMapData();
  650. var toTile = mapData.GetTile(to.x, to.y);
  651. // Simplified costs that emphasize directness over terrain optimization
  652. switch (toTile.terrainType)
  653. {
  654. case TerrainType.Plains:
  655. return 1.0f;
  656. case TerrainType.Forest:
  657. return 1.5f; // Less penalty than normal
  658. case TerrainType.Mountain:
  659. return 2.0f; // Much less penalty for directness
  660. case TerrainType.River:
  661. return 1.8f;
  662. case TerrainType.Lake:
  663. return 2.0f;
  664. case TerrainType.Ocean:
  665. return 4.0f; // Still expensive but possible
  666. default:
  667. return 1.0f;
  668. }
  669. }
  670. /// <summary>
  671. /// Finds the cheapest path that minimizes resource costs (gold) first, then travel time
  672. /// Uses the Special route type but should calculate actual travel time correctly
  673. /// </summary>
  674. private TravelRoute FindCheapestPath(Vector2Int start, Vector2Int destination)
  675. {
  676. // Use the standard A* pathfinding with Special route type (which avoids costly features)
  677. return FindPath(start, destination, RouteType.Special);
  678. }
  679. /// <summary>
  680. /// Gets the resource cost for entering a specific tile
  681. /// </summary>
  682. private float GetResourceCostForTile(Vector2Int position)
  683. {
  684. var mapData = mapMaker.GetMapData();
  685. var tile = mapData.GetTile(position.x, position.y);
  686. switch (tile.featureType)
  687. {
  688. case FeatureType.Ferry:
  689. return ferryBaseCost; // Direct gold cost
  690. case FeatureType.Tunnel:
  691. return tunnelTorchCost; // Torch cost (treated as gold equivalent)
  692. case FeatureType.Bridge:
  693. return 2.0f; // Potential bridge toll (small cost to prefer other routes)
  694. default:
  695. break;
  696. }
  697. // Terrain-based resource costs (for guide fees, etc.)
  698. switch (tile.terrainType)
  699. {
  700. case TerrainType.Mountain:
  701. return 3.0f; // Potential guide cost for dangerous areas
  702. default:
  703. return 0.0f; // No resource cost
  704. }
  705. }
  706. /// <summary>
  707. /// Calculates detailed information about the route (distance, time, special costs)
  708. /// </summary>
  709. private void CalculateRouteDetails(TravelRoute route)
  710. {
  711. var mapData = mapMaker.GetMapData();
  712. route.totalDistance = route.path.Count;
  713. route.estimatedTime = route.totalCost * 0.5f; // Rough time estimate
  714. // Check for special costs
  715. int bridgeCount = 0;
  716. int mountainTileCount = 0;
  717. foreach (var position in route.path)
  718. {
  719. var tile = mapData.GetTile(position.x, position.y);
  720. // Ferry costs
  721. if (tile.featureType == FeatureType.Ferry)
  722. {
  723. if (!route.specialCosts.ContainsKey("Ferry"))
  724. route.specialCosts["Ferry"] = ferryBaseCost;
  725. }
  726. // Tunnel costs
  727. else if (tile.featureType == FeatureType.Tunnel)
  728. {
  729. if (!route.specialCosts.ContainsKey("Tunnel Torch"))
  730. route.specialCosts["Tunnel Torch"] = tunnelTorchCost;
  731. }
  732. // Bridge tolls (for major bridges)
  733. else if (tile.featureType == FeatureType.Bridge)
  734. {
  735. bridgeCount++;
  736. }
  737. // Mountain passes (dangerous mountain areas need guides)
  738. else if (tile.terrainType == TerrainType.Mountain)
  739. {
  740. mountainTileCount++;
  741. }
  742. }
  743. // Add bridge toll costs (only for longer bridge crossings)
  744. if (bridgeCount >= 3) // Only charge for significant bridge crossings
  745. {
  746. route.specialCosts["Bridge Toll"] = bridgeTollCost;
  747. }
  748. // Add mountain guide costs (for extensive mountain travel)
  749. if (mountainTileCount >= 5) // Only charge for dangerous mountain passes
  750. {
  751. route.specialCosts["Mountain Guide"] = mountainPassCost;
  752. }
  753. }
  754. /// <summary>
  755. /// Calculates the total resource cost (gold equivalent) for a route
  756. /// </summary>
  757. private int GetTotalResourceCost(TravelRoute route)
  758. {
  759. if (route?.specialCosts == null) return 0;
  760. int totalCost = 0;
  761. foreach (var cost in route.specialCosts)
  762. {
  763. totalCost += cost.Value; // All costs are in gold or gold-equivalent
  764. }
  765. return totalCost;
  766. }
  767. /// <summary>
  768. /// Checks if two routes are essentially the same
  769. /// </summary>
  770. private bool RoutesSimilar(TravelRoute route1, TravelRoute route2)
  771. {
  772. if (route1 == null || route2 == null) return false;
  773. // Consider routes similar if they have >90% path overlap (more strict to allow more variations)
  774. var overlap = route1.path.Intersect(route2.path).Count();
  775. var maxLength = Mathf.Max(route1.path.Count, route2.path.Count);
  776. bool similar = (float)overlap / maxLength > 0.9f;
  777. Debug.Log($"🔍 Route similarity check: {overlap}/{maxLength} = {((float)overlap / maxLength):F2} > 0.9? {similar}");
  778. return similar;
  779. }
  780. /// <summary>
  781. /// Analyzes and logs the composition of a route for debugging
  782. /// </summary>
  783. private void AnalyzeRouteComposition(TravelRoute route)
  784. {
  785. if (route == null || route.path == null) return;
  786. var mapData = mapMaker.GetMapData();
  787. int plainsCount = 0, forestCount = 0, mountainCount = 0, roadCount = 0, riverCount = 0, lakeCount = 0;
  788. float totalMovementCost = 0;
  789. float roadBonusTotal = 0;
  790. foreach (var pos in route.path)
  791. {
  792. var tile = mapData.GetTile(pos.x, pos.y);
  793. float tileCost = GetMovementCost(pos, pos, route.routeType);
  794. totalMovementCost += tileCost;
  795. switch (tile.terrainType)
  796. {
  797. case TerrainType.Plains: plainsCount++; break;
  798. case TerrainType.Forest: forestCount++; break;
  799. case TerrainType.Mountain: mountainCount++; break;
  800. case TerrainType.River: riverCount++; break;
  801. case TerrainType.Lake: lakeCount++; break;
  802. }
  803. if (tile.featureType == FeatureType.Road)
  804. {
  805. roadCount++;
  806. // Calculate road bonus for this route type
  807. switch (route.routeType)
  808. {
  809. case RouteType.Standard:
  810. roadBonusTotal += GetTerrainMovementCost(tile) * (1f - 0.6f); // 40% discount
  811. break;
  812. case RouteType.RoadPreferred:
  813. roadBonusTotal += GetTerrainMovementCost(tile) * (1f - 0.2f); // 80% discount
  814. break;
  815. }
  816. }
  817. }
  818. float avgCostPerTile = totalMovementCost / route.path.Count;
  819. float roadPercentage = (float)roadCount / route.path.Count * 100f;
  820. Debug.Log($"📊 Route Analysis ({route.routeType}): {route.path.Count} tiles");
  821. Debug.Log($" Terrain: Plains:{plainsCount}, Forest:{forestCount}, Mountain:{mountainCount}, Rivers:{riverCount}, Lakes:{lakeCount}");
  822. Debug.Log($" Roads: {roadCount} tiles ({roadPercentage:F1}%), saved {roadBonusTotal:F1} cost");
  823. Debug.Log($" Average cost per tile: {avgCostPerTile:F2} (Total: {totalMovementCost:F1})");
  824. Debug.Log($" Time calculation: {route.estimatedTime:F1}h = {route.totalCost:F1} cost × 0.5 multiplier");
  825. // Explain why this route type might be different
  826. string explanation = "";
  827. switch (route.routeType)
  828. {
  829. case RouteType.Standard:
  830. explanation = "Shortest route minimizes distance and waypoints";
  831. break;
  832. case RouteType.RoadPreferred:
  833. explanation = $"Fastest route heavily favors roads (90% discount) - {roadPercentage:F1}% on roads";
  834. break;
  835. case RouteType.Special:
  836. explanation = "Cheapest/Direct route avoids special costs and takes direct paths";
  837. break;
  838. }
  839. Debug.Log($" Strategy: {explanation}");
  840. }
  841. /// <summary>
  842. /// Visualizes the selected route on the map
  843. /// </summary>
  844. private void VisualizeRoute(TravelRoute route)
  845. {
  846. if (showDebugLogs)
  847. {
  848. Debug.Log($"🎨 VisualizeRoute called with route: {route?.routeType} ({route?.path?.Count ?? 0} points)");
  849. }
  850. ClearPathVisualization();
  851. if (route == null || route.path.Count == 0)
  852. {
  853. if (showDebugLogs)
  854. {
  855. Debug.LogWarning("❌ Cannot visualize route: route is null or has no path points");
  856. }
  857. return;
  858. }
  859. // Create path visualization
  860. var pathObject = new GameObject("TravelPath");
  861. var lineRenderer = pathObject.AddComponent<LineRenderer>();
  862. // Configure line renderer with simple, reliable settings
  863. lineRenderer.material = pathLineMaterial ?? CreateDefaultPathMaterial();
  864. lineRenderer.material.color = GetRouteColor(route);
  865. // Force line width to 1.0 for consistent visibility
  866. lineRenderer.startWidth = 1.0f;
  867. lineRenderer.endWidth = 1.0f;
  868. lineRenderer.positionCount = route.path.Count;
  869. lineRenderer.useWorldSpace = true;
  870. // Simple LineRenderer settings for reliable visibility
  871. lineRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  872. lineRenderer.receiveShadows = false;
  873. lineRenderer.alignment = LineAlignment.View; // Face camera
  874. lineRenderer.textureMode = LineTextureMode.Tile;
  875. lineRenderer.sortingLayerName = "Default";
  876. lineRenderer.sortingOrder = 50; // High sorting order but not excessive
  877. // Debug the width setting
  878. if (showDebugLogs)
  879. {
  880. Debug.Log($"🔧 Setting LineRenderer width to: {pathLineWidth} (startWidth: {lineRenderer.startWidth}, endWidth: {lineRenderer.endWidth})");
  881. }
  882. // Set path points
  883. for (int i = 0; i < route.path.Count; i++)
  884. {
  885. Vector3 worldPos = TileToWorldPosition(route.path[i]);
  886. worldPos.y = pathLineHeight; // Place above map tiles
  887. lineRenderer.SetPosition(i, worldPos);
  888. }
  889. currentPathVisualizations.Add(pathObject);
  890. if (showDebugLogs)
  891. {
  892. Debug.Log($"📍 Visualized {route.routeType} route with {route.path.Count} waypoints");
  893. Debug.Log($"🔧 LineRenderer config: startWidth={lineRenderer.startWidth}, endWidth={lineRenderer.endWidth}, material={lineRenderer.material?.name}, color={lineRenderer.material?.color}");
  894. Debug.Log($"🎯 GameObject created: {pathObject.name} at position {pathObject.transform.position}");
  895. }
  896. }
  897. /// <summary>
  898. /// Visualizes all available routes with different colors and proper layering
  899. /// </summary>
  900. private void VisualizeAllRoutes(List<TravelRoute> routes)
  901. {
  902. ClearPathVisualization();
  903. // Render all routes with the first one (fastest by time) selected by default
  904. var routesByTime = routes.OrderBy(r => r.estimatedTime).ToList();
  905. var defaultSelected = routesByTime.FirstOrDefault();
  906. for (int i = 0; i < routes.Count; i++)
  907. {
  908. var route = routes[i];
  909. if (route == null || route.path.Count == 0) continue;
  910. bool isSelected = (route == defaultSelected);
  911. RenderSingleRoute(route, i, isSelected);
  912. }
  913. if (showDebugLogs)
  914. {
  915. string selectedName = defaultSelected?.routeType == RouteType.RoadPreferred ? "Fastest" :
  916. defaultSelected?.routeType == RouteType.Standard ? "Shortest" : "Cheapest";
  917. Debug.Log($"🎨 Visualized {routes.Count} routes with {selectedName} as default selected (on top)");
  918. }
  919. } /// <summary>
  920. /// Gets the appropriate color for a route based on its type (GPS-style)
  921. /// </summary>
  922. private Color GetRouteColor(TravelRoute route)
  923. {
  924. // GPS-style color coding
  925. switch (route.routeType)
  926. {
  927. case RouteType.RoadPreferred: // Fastest route
  928. if (showDebugLogs) Debug.Log($"🎨 Route color: GREEN (fastest route)");
  929. return Color.green; // Green for fastest (like GPS)
  930. case RouteType.Standard: // Shortest route
  931. if (showDebugLogs) Debug.Log($"🎨 Route color: BLUE (shortest route)");
  932. return standardPathColor; // Blue for shortest
  933. case RouteType.Special: // Cheapest/Direct route
  934. if (showDebugLogs) Debug.Log($"🎨 Route color: YELLOW (cheapest/direct route)");
  935. return alternativePathColor; // Yellow for cheapest/alternative
  936. default:
  937. if (showDebugLogs) Debug.Log($"🎨 Route color: BLUE (default)");
  938. return standardPathColor;
  939. }
  940. }
  941. /// <summary>
  942. /// Creates a default material for path visualization
  943. /// </summary>
  944. private Material CreateDefaultPathMaterial()
  945. {
  946. // Use simple Sprites/Default shader for reliable rendering
  947. Shader shader = Shader.Find("Sprites/Default");
  948. if (shader == null)
  949. shader = Shader.Find("Legacy Shaders/Particles/Alpha Blended Premultiply");
  950. if (shader == null)
  951. shader = Shader.Find("Unlit/Color");
  952. if (shader == null)
  953. shader = Shader.Find("Standard");
  954. var material = new Material(shader ?? Shader.Find("Diffuse"));
  955. material.color = standardPathColor;
  956. if (showDebugLogs)
  957. {
  958. Debug.Log($"🎨 Created path material with shader: {shader?.name ?? "Unknown"}");
  959. }
  960. return material;
  961. }
  962. /// <summary>
  963. /// Clears any existing path visualization
  964. /// </summary>
  965. private void ClearPathVisualization()
  966. {
  967. foreach (var pathViz in currentPathVisualizations)
  968. {
  969. if (pathViz != null)
  970. {
  971. DestroyImmediate(pathViz);
  972. }
  973. }
  974. currentPathVisualizations.Clear();
  975. }
  976. /// <summary>
  977. /// Shows only the selected route with transparency for better marker visibility during travel
  978. /// </summary>
  979. private void ShowTravelRouteOnly(TravelRoute route)
  980. {
  981. if (route == null || route.path.Count == 0)
  982. {
  983. if (showDebugLogs)
  984. {
  985. Debug.LogWarning("❌ Cannot show travel route: route is null or has no path");
  986. }
  987. return;
  988. }
  989. // Clear all existing route visualizations
  990. ClearPathVisualization();
  991. if (showDebugLogs)
  992. {
  993. Debug.Log($"🎯 Showing only {route.routeType} route with transparency for travel");
  994. }
  995. // Create a semi-transparent visualization of the selected route
  996. var pathObject = new GameObject($"TravelPath_{route.routeType}_Traveling");
  997. var lineRenderer = pathObject.AddComponent<LineRenderer>();
  998. // Configure line renderer with semi-transparent material
  999. var baseMaterial = pathLineMaterial ?? CreateDefaultPathMaterial();
  1000. var routeMaterial = new Material(baseMaterial);
  1001. var routeColor = GetRouteColor(route);
  1002. // Set to 50% transparency for better marker visibility
  1003. routeColor.a = 0.5f;
  1004. routeMaterial.color = routeColor;
  1005. lineRenderer.material = routeMaterial;
  1006. // Use a thinner line during travel to be less obtrusive
  1007. lineRenderer.startWidth = 1.2f;
  1008. lineRenderer.endWidth = 1.2f;
  1009. lineRenderer.positionCount = route.path.Count;
  1010. // Set sorting order to be below the team marker
  1011. lineRenderer.sortingOrder = 45; // Lower than team marker
  1012. // Set path points
  1013. for (int i = 0; i < route.path.Count; i++)
  1014. {
  1015. Vector3 worldPos = TileToWorldPosition(route.path[i]);
  1016. worldPos.y = pathLineHeight; // Keep consistent height
  1017. lineRenderer.SetPosition(i, worldPos);
  1018. }
  1019. currentPathVisualizations.Add(pathObject);
  1020. if (showDebugLogs)
  1021. {
  1022. Debug.Log($"✅ Travel route visualization created: {route.routeType} with 50% transparency");
  1023. }
  1024. }
  1025. /// <summary>
  1026. /// Cancels current travel planning
  1027. /// </summary>
  1028. public void CancelTravelPlanning()
  1029. {
  1030. plannedDestination = null;
  1031. availableRoutes.Clear();
  1032. ClearPathVisualization();
  1033. HideTravelUI();
  1034. if (showDebugLogs)
  1035. {
  1036. Debug.Log("❌ Travel planning cancelled");
  1037. }
  1038. }
  1039. /// <summary>
  1040. /// Starts travel using the specified route
  1041. /// </summary>
  1042. public void StartTravel(TravelRoute route)
  1043. {
  1044. if (route == null || isTraveling)
  1045. {
  1046. Debug.LogWarning("Cannot start travel: invalid route or already traveling");
  1047. return;
  1048. }
  1049. // Synchronize coordinates before starting travel
  1050. SynchronizeWithExplorationSystem();
  1051. // Reset cancellation flag and store current route
  1052. shouldCancelJourney = false;
  1053. currentRoute = route;
  1054. // Show only the selected route with transparency for better marker visibility
  1055. ShowTravelRouteOnly(route);
  1056. // Start the travel coroutine and store reference
  1057. currentTravelCoroutine = StartCoroutine(ExecuteTravel(route));
  1058. }
  1059. /// <summary>
  1060. /// Cancels the current journey if one is in progress
  1061. /// </summary>
  1062. public void CancelJourney()
  1063. {
  1064. if (!isTraveling)
  1065. {
  1066. if (showDebugLogs) Debug.Log("No journey to cancel");
  1067. return;
  1068. }
  1069. if (showDebugLogs)
  1070. {
  1071. Debug.Log("Journey cancellation requested by player");
  1072. }
  1073. // Set the cancellation flag
  1074. shouldCancelJourney = true;
  1075. // The actual cleanup will happen in the ExecuteTravel coroutine
  1076. }
  1077. /// <summary>
  1078. /// Executes the actual travel along the route
  1079. /// </summary>
  1080. private IEnumerator ExecuteTravel(TravelRoute route)
  1081. {
  1082. isTraveling = true;
  1083. if (showDebugLogs)
  1084. {
  1085. Debug.Log($"🚶 Starting travel along {route.routeType} route");
  1086. Debug.Log($"📍 Route has {route.path.Count} waypoints, estimated time: {route.estimatedTime:F1} hours");
  1087. }
  1088. // Ensure team marker is visible during travel (stop blinking)
  1089. EnsureTeamMarkerVisible(true);
  1090. // Debug: Check initial team marker state
  1091. var initialMarker = teamPlacement.GetTeamMarker();
  1092. if (initialMarker != null)
  1093. {
  1094. Debug.Log($"🔍 Initial team marker: Position={initialMarker.transform.position}, Active={initialMarker.activeInHierarchy}, WorldPos={initialMarker.transform.position}");
  1095. var initialRenderer = initialMarker.GetComponent<Renderer>();
  1096. if (initialRenderer != null)
  1097. {
  1098. Debug.Log($"🔍 Initial renderer: Enabled={initialRenderer.enabled}, Material={initialRenderer.material?.name}");
  1099. }
  1100. }
  1101. else
  1102. {
  1103. Debug.LogError("❌ No team marker found at start of travel!");
  1104. }
  1105. // Deduct special costs before travel
  1106. DeductTravelCosts(route);
  1107. // Move team marker along the path with smooth animation
  1108. for (int i = 1; i < route.path.Count; i++)
  1109. {
  1110. // Check for cancellation at the start of each step
  1111. if (shouldCancelJourney)
  1112. {
  1113. if (showDebugLogs)
  1114. {
  1115. Debug.Log($"🛑 Journey cancelled at step {i}/{route.path.Count - 1}. Stopping at {route.path[i - 1]}");
  1116. }
  1117. break; // Exit the movement loop
  1118. }
  1119. Vector2Int nextPosition = route.path[i];
  1120. Vector2Int currentPos = route.path[i - 1];
  1121. if (showDebugLogs && i % 3 == 0) // Log every 3rd step
  1122. {
  1123. Debug.Log($"🚶 Traveling step {i}/{route.path.Count - 1} to {nextPosition} ({((float)i / (route.path.Count - 1) * 100):F0}% complete)");
  1124. }
  1125. // Get current and target world positions for smooth movement
  1126. Vector3 currentWorldPos = TileToWorldPosition(currentPos);
  1127. Vector3 targetWorldPos = TileToWorldPosition(nextPosition);
  1128. // Animate the team marker moving smoothly between positions
  1129. float moveTime = 0.8f; // Base time per step (slower movement)
  1130. float stepCost = GetMovementCost(currentPos, nextPosition, route.routeType);
  1131. // Adjust move time based on terrain difficulty
  1132. moveTime *= Mathf.Clamp(stepCost * 0.3f, 0.5f, 3f); // Slower for difficult terrain
  1133. // Smooth movement animation
  1134. float elapsed = 0f;
  1135. while (elapsed < moveTime)
  1136. {
  1137. // Check for cancellation during movement animation
  1138. if (shouldCancelJourney)
  1139. {
  1140. if (showDebugLogs)
  1141. {
  1142. Debug.Log($"🛑 Journey cancelled during movement animation. Stopping at current position.");
  1143. }
  1144. break; // Exit the animation loop
  1145. }
  1146. elapsed += Time.deltaTime;
  1147. float t = Mathf.SmoothStep(0f, 1f, elapsed / moveTime);
  1148. // Interpolate between current and target position
  1149. Vector3 lerpedWorldPos = Vector3.Lerp(currentWorldPos, targetWorldPos, t);
  1150. Vector2Int lerpedTilePos = WorldToTilePosition(lerpedWorldPos);
  1151. // Update team position smoothly (but only if it's a valid position)
  1152. if (mapMaker.GetMapData().IsValidPosition(lerpedTilePos.x, lerpedTilePos.y))
  1153. {
  1154. teamPlacement.PlaceTeamAt(lerpedTilePos);
  1155. // Ensure marker stays visible during travel
  1156. var teamMarker = teamPlacement.GetTeamMarker();
  1157. if (teamMarker != null)
  1158. {
  1159. teamMarker.SetActive(true);
  1160. var renderer = teamMarker.GetComponent<Renderer>();
  1161. if (renderer != null && !renderer.enabled)
  1162. {
  1163. renderer.enabled = true;
  1164. if (showDebugLogs)
  1165. {
  1166. Debug.Log($"🔧 Re-enabled team marker renderer at {teamMarker.transform.position}");
  1167. }
  1168. }
  1169. }
  1170. }
  1171. yield return null; // Wait one frame
  1172. }
  1173. // Break out of the inner animation loop if cancelled
  1174. if (shouldCancelJourney)
  1175. {
  1176. break; // Exit the for loop as well
  1177. }
  1178. // Ensure we end up exactly at the target position (if not cancelled)
  1179. if (!shouldCancelJourney)
  1180. {
  1181. teamPlacement.PlaceTeamAt(nextPosition);
  1182. }
  1183. }
  1184. // Journey cleanup - whether completed or cancelled
  1185. isTraveling = false;
  1186. ResetTimeSpeed(); // Reset time scale to normal when travel ends
  1187. EnsureTeamMarkerVisible(false); // Restore blinking
  1188. ClearPathVisualization();
  1189. HideTravelUI();
  1190. // Synchronize coordinate systems after travel
  1191. SynchronizeWithExplorationSystem();
  1192. // Clear travel state
  1193. currentTravelCoroutine = null;
  1194. currentRoute = null;
  1195. if (shouldCancelJourney)
  1196. {
  1197. if (showDebugLogs)
  1198. {
  1199. Debug.Log($"🛑 Journey cancelled! Team stopped at {teamPlacement.GetTeamPosition()}");
  1200. }
  1201. shouldCancelJourney = false; // Reset the flag
  1202. }
  1203. else
  1204. {
  1205. if (showDebugLogs)
  1206. {
  1207. Debug.Log($"✅ Travel completed! Arrived at {route.path.Last()}");
  1208. }
  1209. }
  1210. }
  1211. /// <summary>
  1212. /// Synchronize travel system coordinates with exploration system
  1213. /// </summary>
  1214. private void SynchronizeWithExplorationSystem()
  1215. {
  1216. if (mapMaker != null && mapMaker.useExplorationSystem)
  1217. {
  1218. mapMaker.SyncTeamPosition();
  1219. if (showDebugLogs)
  1220. {
  1221. Debug.Log("🔄 Synchronized team position with exploration system");
  1222. }
  1223. }
  1224. }
  1225. /// <summary>
  1226. /// Deducts costs for travel (gold, items, etc.)
  1227. /// </summary>
  1228. private void DeductTravelCosts(TravelRoute route)
  1229. {
  1230. if (route.specialCosts.Count == 0) return;
  1231. if (showDebugLogs)
  1232. {
  1233. Debug.Log($"💰 Deducting travel costs: {string.Join(", ", route.specialCosts.Select(kvp => $"{kvp.Key}: {kvp.Value}"))}");
  1234. }
  1235. // TODO: Implement actual resource deduction
  1236. // This would integrate with your inventory/currency system
  1237. foreach (var cost in route.specialCosts)
  1238. {
  1239. switch (cost.Key)
  1240. {
  1241. case "Ferry":
  1242. // DeductGold(cost.Value);
  1243. Debug.Log($"💰 Would deduct {cost.Value} gold for ferry");
  1244. break;
  1245. case "Tunnel Torch":
  1246. // DeductTorches(cost.Value);
  1247. Debug.Log($"🔥 Would deduct {cost.Value} torches for tunnel");
  1248. break;
  1249. }
  1250. }
  1251. }
  1252. /// <summary>
  1253. /// Ensures the team marker is visible during travel by controlling its blinking behavior
  1254. /// </summary>
  1255. private void EnsureTeamMarkerVisible(bool forceVisible)
  1256. {
  1257. if (teamPlacement == null)
  1258. {
  1259. Debug.LogWarning("❌ TeamPlacement is null in EnsureTeamMarkerVisible");
  1260. return;
  1261. }
  1262. var teamMarker = teamPlacement.GetTeamMarker();
  1263. if (teamMarker == null)
  1264. {
  1265. Debug.LogWarning("❌ Team marker is null - creating new marker");
  1266. // Force creation of a new marker if it doesn't exist
  1267. teamPlacement.PlaceTeamAt(teamPlacement.GetTeamPosition());
  1268. teamMarker = teamPlacement.GetTeamMarker();
  1269. if (teamMarker == null)
  1270. {
  1271. Debug.LogError("❌ Failed to create team marker!");
  1272. return;
  1273. }
  1274. }
  1275. var renderer = teamMarker.GetComponent<Renderer>();
  1276. if (renderer == null)
  1277. {
  1278. Debug.LogWarning("❌ Team marker has no Renderer component");
  1279. return;
  1280. }
  1281. Debug.Log($"🔧 Team marker status: Position={teamMarker.transform.position}, Active={teamMarker.activeInHierarchy}, RendererEnabled={renderer.enabled}");
  1282. if (forceVisible)
  1283. {
  1284. // Force marker to be visible and stop blinking during travel
  1285. teamMarker.SetActive(true);
  1286. renderer.enabled = true;
  1287. // Make the marker more visible by adjusting its properties
  1288. teamMarker.transform.localScale = Vector3.one * 2f; // Make it bigger
  1289. // Try to change material color to bright red for visibility
  1290. var material = renderer.material;
  1291. if (material != null)
  1292. {
  1293. material.color = Color.red;
  1294. if (material.HasProperty("_BaseColor"))
  1295. material.SetColor("_BaseColor", Color.red);
  1296. if (material.HasProperty("_Color"))
  1297. material.SetColor("_Color", Color.red);
  1298. Debug.Log($"🔴 Set team marker to bright red for visibility");
  1299. }
  1300. // Force position to be visible above map (adjust Y for top-down view)
  1301. var pos = teamMarker.transform.position;
  1302. teamMarker.transform.position = new Vector3(pos.x, 2f, pos.z); // Raise above map for visibility
  1303. // Stop any blinking coroutines in SimpleTeamPlacement using reflection
  1304. try
  1305. {
  1306. var placementType = teamPlacement.GetType();
  1307. var stopCooroutinesMethod = placementType.GetMethod("StopAllCoroutines");
  1308. if (stopCooroutinesMethod != null)
  1309. {
  1310. stopCooroutinesMethod.Invoke(teamPlacement, null);
  1311. Debug.Log("✅ Stopped blinking coroutines");
  1312. }
  1313. // Set isBlinking to false using reflection
  1314. var isBlinkingField = placementType.GetField("isBlinking", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
  1315. if (isBlinkingField != null)
  1316. {
  1317. isBlinkingField.SetValue(teamPlacement, false);
  1318. Debug.Log("✅ Set isBlinking to false");
  1319. }
  1320. }
  1321. catch (System.Exception e)
  1322. {
  1323. Debug.LogWarning($"⚠️ Reflection failed: {e.Message}");
  1324. }
  1325. if (showDebugLogs)
  1326. {
  1327. Debug.Log($"👁️ Team marker forced visible for travel at {teamMarker.transform.position}");
  1328. }
  1329. }
  1330. else
  1331. {
  1332. // Restore normal blinking behavior after travel
  1333. try
  1334. {
  1335. var placementType = teamPlacement.GetType();
  1336. var enableBlinkingField = placementType.GetField("enableBlinking", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
  1337. if (enableBlinkingField != null && (bool)enableBlinkingField.GetValue(teamPlacement))
  1338. {
  1339. // Restart blinking using reflection
  1340. var startBlinkingMethod = placementType.GetMethod("StartBlinking", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
  1341. if (startBlinkingMethod != null)
  1342. {
  1343. startBlinkingMethod.Invoke(teamPlacement, null);
  1344. if (showDebugLogs)
  1345. {
  1346. Debug.Log("✨ Team marker blinking restored after travel");
  1347. }
  1348. }
  1349. }
  1350. }
  1351. catch (System.Exception e)
  1352. {
  1353. Debug.LogWarning($"⚠️ Reflection failed during restore: {e.Message}");
  1354. }
  1355. }
  1356. }
  1357. /// <summary>
  1358. /// Utility methods for coordinate conversion
  1359. /// </summary>
  1360. private Vector3 GetMouseWorldPosition()
  1361. {
  1362. if (mapCamera == null)
  1363. {
  1364. if (showDebugLogs) Debug.LogWarning("❌ Map camera is null, trying to find Camera.main");
  1365. mapCamera = Camera.main;
  1366. if (mapCamera == null) return Vector3.zero;
  1367. }
  1368. Ray ray = mapCamera.ScreenPointToRay(Input.mousePosition);
  1369. if (showDebugLogs) Debug.Log($"📐 Raycast from camera: {ray.origin} direction: {ray.direction}");
  1370. // Try raycast first (if there are colliders)
  1371. if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
  1372. {
  1373. if (showDebugLogs) Debug.Log($"🎯 Raycast hit: {hit.point} on {hit.collider.name}");
  1374. return hit.point;
  1375. }
  1376. // Fallback: use camera plane projection at Y=0 (map level) for top-down view
  1377. Plane mapPlane = new Plane(Vector3.up, Vector3.zero); // Y=0 plane facing up for top-down camera
  1378. if (mapPlane.Raycast(ray, out float distance))
  1379. {
  1380. Vector3 point = ray.GetPoint(distance);
  1381. if (showDebugLogs) Debug.Log($"📍 Plane intersection: {point}");
  1382. return point;
  1383. }
  1384. if (showDebugLogs) Debug.LogWarning("❌ No raycast hit and no plane intersection");
  1385. return Vector3.zero;
  1386. }
  1387. private Vector2Int WorldToTilePosition(Vector3 worldPos)
  1388. {
  1389. float tileSize = mapMaker?.mapVisualizer?.tileSize ?? 1f;
  1390. return new Vector2Int(
  1391. Mathf.RoundToInt(worldPos.x / tileSize),
  1392. Mathf.RoundToInt(worldPos.z / tileSize) // Use Z coordinate for map Y in top-down view
  1393. );
  1394. }
  1395. private Vector3 TileToWorldPosition(Vector2Int tilePos)
  1396. {
  1397. float tileSize = mapMaker?.mapVisualizer?.tileSize ?? 1f;
  1398. return new Vector3(
  1399. tilePos.x * tileSize,
  1400. 0f, // Y=0 for tiles at ground level
  1401. tilePos.y * tileSize // Map Y coordinate becomes world Z coordinate for top-down view
  1402. );
  1403. }
  1404. /// <summary>
  1405. /// Debug method to show movement cost information
  1406. /// </summary>
  1407. private void ShowMovementCostInfo()
  1408. {
  1409. Debug.Log("=== MOVEMENT COST INFO ===");
  1410. Debug.Log("TERRAIN COSTS:");
  1411. Debug.Log($" Plains: {plainsMovementCost}x");
  1412. Debug.Log($" Forest: {forestMovementCost}x");
  1413. Debug.Log($" Mountain: {mountainMovementCost}x");
  1414. Debug.Log($" River Crossing: {riverCrossingCost}x (slow crossing)");
  1415. Debug.Log($" Lake Swimming: {lakeCrossingCost}x (swimming)");
  1416. Debug.Log($" Ocean Crossing: {oceanCrossingCost}x (very dangerous)");
  1417. Debug.Log("FEATURE COSTS:");
  1418. Debug.Log($" Road: {roadMovementCost}x");
  1419. Debug.Log($" Town/Village: {townMovementCost}x");
  1420. Debug.Log($" Bridge: {bridgeMovementCost}x");
  1421. Debug.Log($" Ferry: {ferryMovementCost}x (+ {ferryBaseCost} gold)");
  1422. Debug.Log($" Tunnel (with torch): {tunnelWithTorchCost}x (+ {tunnelTorchCost} torches)");
  1423. Debug.Log($" Tunnel (without torch): {tunnelWithoutTorchCost}x");
  1424. Debug.Log("CONTROLS:");
  1425. Debug.Log(" TAB: Cycle through route options");
  1426. Debug.Log(" ESC: Cancel travel planning (or cancel journey if traveling)");
  1427. Debug.Log(" C: Cancel journey while traveling");
  1428. Debug.Log(" F8: Toggle debug logs");
  1429. Debug.Log("TRAVEL CONTROLS (while traveling):");
  1430. Debug.Log(" >,→: Increase travel speed");
  1431. Debug.Log(" <,←: Decrease travel speed");
  1432. Debug.Log(" Space: Reset travel speed to normal");
  1433. Debug.Log("========================");
  1434. }
  1435. /// <summary>
  1436. /// Public getters for external systems
  1437. /// </summary>
  1438. public List<TravelRoute> GetAvailableRoutes() => availableRoutes;
  1439. public Vector2Int? GetPlannedDestination() => plannedDestination;
  1440. public bool IsTraveling() => isTraveling;
  1441. public bool CanCancelJourney() => isTraveling && currentTravelCoroutine != null;
  1442. public TravelRoute GetCurrentRoute() => currentRoute;
  1443. /// <summary>
  1444. /// Increases time speed for faster travel
  1445. /// </summary>
  1446. public void IncreaseTimeSpeed()
  1447. {
  1448. if (currentTimeSpeedIndex < timeSpeedOptions.Length - 1)
  1449. {
  1450. currentTimeSpeedIndex++;
  1451. timeMultiplier = timeSpeedOptions[currentTimeSpeedIndex];
  1452. Time.timeScale = timeMultiplier;
  1453. Debug.Log($"⏩ Time speed increased to {timeMultiplier}x");
  1454. }
  1455. }
  1456. /// <summary>
  1457. /// Decreases time speed
  1458. /// </summary>
  1459. public void DecreaseTimeSpeed()
  1460. {
  1461. if (currentTimeSpeedIndex > 0)
  1462. {
  1463. currentTimeSpeedIndex--;
  1464. timeMultiplier = timeSpeedOptions[currentTimeSpeedIndex];
  1465. Time.timeScale = timeMultiplier;
  1466. Debug.Log($"⏪ Time speed decreased to {timeMultiplier}x");
  1467. }
  1468. }
  1469. /// <summary>
  1470. /// Resets time speed to normal
  1471. /// </summary>
  1472. public void ResetTimeSpeed()
  1473. {
  1474. currentTimeSpeedIndex = 0;
  1475. timeMultiplier = timeSpeedOptions[0];
  1476. Time.timeScale = timeMultiplier;
  1477. Debug.Log($"⏸️ Time speed reset to {timeMultiplier}x");
  1478. }
  1479. /// <summary>
  1480. /// Gets current time speed multiplier
  1481. /// </summary>
  1482. public float GetTimeSpeed() => timeMultiplier;
  1483. /// <summary>
  1484. /// Methods for UI integration
  1485. /// </summary>
  1486. private bool IsTravelUIBlocking()
  1487. {
  1488. // Check both travel UI and combat popup for blocking input
  1489. bool travelUIBlocking = travelUI != null && travelUI.IsUIBlocking();
  1490. bool combatPopupBlocking = combatEventPopup != null && combatEventPopup.IsVisible;
  1491. return travelUIBlocking || combatPopupBlocking;
  1492. }
  1493. private void ShowTravelUIIfAvailable(Vector2Int destination, List<TravelRoute> routes)
  1494. {
  1495. // Direct call instead of reflection since TravelUI is now available
  1496. if (travelUI != null)
  1497. {
  1498. travelUI.ShowTravelUI(destination, routes);
  1499. }
  1500. else
  1501. {
  1502. Debug.LogWarning("⚠️ TravelUI not found, cannot show travel interface");
  1503. }
  1504. }
  1505. public void PlanTravelWithPreferences(Vector2Int destination, bool avoidFerries, bool avoidMountainPasses, bool preferRoads)
  1506. {
  1507. // Store preferences and recalculate routes
  1508. if (showDebugLogs)
  1509. {
  1510. Debug.Log($"🔄 Recalculating routes with preferences: AvoidFerries={avoidFerries}, AvoidMountains={avoidMountainPasses}, PreferRoads={preferRoads}");
  1511. }
  1512. // For now, just recalculate the same routes
  1513. // In a full implementation, these preferences would modify the pathfinding algorithm
  1514. PlanTravelTo(destination);
  1515. }
  1516. /// <summary>
  1517. /// Forces recalculation of routes and refreshes the UI - useful for debugging or preference changes
  1518. /// </summary>
  1519. [ContextMenu("DEBUG: Recalculate Routes")]
  1520. public void ForceRecalculateRoutes()
  1521. {
  1522. if (plannedDestination.HasValue)
  1523. {
  1524. Debug.Log($"🔄 TeamTravelSystem: Force recalculating routes to {plannedDestination.Value}");
  1525. Vector2Int currentPosition = teamPlacement.GetTeamPosition();
  1526. // Recalculate routes
  1527. CalculateRoutes(currentPosition, plannedDestination.Value);
  1528. // Refresh visualization
  1529. if (availableRoutes.Count > 0)
  1530. {
  1531. VisualizeAllRoutes(availableRoutes);
  1532. }
  1533. // Refresh UI if visible
  1534. if (travelUI != null && travelUI.IsVisible)
  1535. {
  1536. Debug.Log("🔄 TeamTravelSystem: Refreshing UI with recalculated routes");
  1537. travelUI.RefreshWithNewRoutes(availableRoutes);
  1538. }
  1539. Debug.Log($"✅ TeamTravelSystem: Routes recalculated - {availableRoutes.Count} routes available");
  1540. }
  1541. else
  1542. {
  1543. Debug.LogWarning("⚠️ TeamTravelSystem: No planned destination to recalculate routes for");
  1544. }
  1545. }
  1546. public void VisualizeSpecificRoute(TravelRoute route)
  1547. {
  1548. var callId = System.Guid.NewGuid().ToString("N")[..8]; // 8-character unique ID
  1549. if (showDebugLogs)
  1550. {
  1551. Debug.Log($"🎯 [{callId}] VisualizeSpecificRoute called with route: {route?.routeType}, availableRoutes count: {availableRoutes?.Count ?? 0}");
  1552. }
  1553. if (route == null)
  1554. {
  1555. Debug.LogError($"❌ [{callId}] Cannot visualize null route!");
  1556. return;
  1557. }
  1558. // If availableRoutes is empty, this might be because routes were cleared during UI interaction
  1559. // In this case, we can still visualize the single route that was passed to us
  1560. if (availableRoutes == null || availableRoutes.Count == 0)
  1561. {
  1562. Debug.LogWarning($"⚠️ [{callId}] availableRoutes is empty, visualizing single route {route.routeType}");
  1563. // Temporarily add this route to the list so the visualization system can work
  1564. if (availableRoutes == null) availableRoutes = new List<TravelRoute>();
  1565. availableRoutes.Add(route);
  1566. // Visualize just this route
  1567. ClearPathVisualization();
  1568. RenderSingleRoute(route, 0, true); // Render as selected
  1569. if (showDebugLogs)
  1570. {
  1571. Debug.Log($"🎨 [{callId}] Visualized single {route.routeType} route");
  1572. }
  1573. return;
  1574. }
  1575. // Normal path when we have available routes
  1576. // Verify the route is actually in our available routes
  1577. bool routeExists = availableRoutes.Contains(route);
  1578. if (!routeExists)
  1579. {
  1580. Debug.LogWarning($"⚠️ [{callId}] Selected route {route.routeType} is not in availableRoutes list!");
  1581. // Try to find a matching route by type
  1582. var matchingRoute = availableRoutes.FirstOrDefault(r => r?.routeType == route.routeType);
  1583. if (matchingRoute != null)
  1584. {
  1585. Debug.Log($"🔄 [{callId}] Found matching route by type, using that instead");
  1586. route = matchingRoute;
  1587. }
  1588. else
  1589. {
  1590. Debug.LogError($"❌ [{callId}] No matching route found, cannot visualize!");
  1591. return;
  1592. }
  1593. }
  1594. // Instead of clearing all routes, just highlight the selected one
  1595. HighlightSpecificRoute(route);
  1596. if (showDebugLogs)
  1597. {
  1598. Debug.Log($"🎨 [{callId}] Highlighted {route.routeType} route while keeping all routes visible");
  1599. }
  1600. }
  1601. /// <summary>
  1602. /// Highlights a specific route while keeping all routes visible (selected route on top)
  1603. /// </summary>
  1604. private void HighlightSpecificRoute(TravelRoute selectedRoute)
  1605. {
  1606. if (showDebugLogs)
  1607. {
  1608. Debug.Log($"🔍 HighlightSpecificRoute called with {selectedRoute?.routeType}, availableRoutes count: {availableRoutes.Count}");
  1609. for (int i = 0; i < availableRoutes.Count; i++)
  1610. {
  1611. Debug.Log($" Route {i}: {availableRoutes[i]?.routeType} with {availableRoutes[i]?.path?.Count ?? 0} waypoints");
  1612. }
  1613. }
  1614. // Validate that we have routes to work with
  1615. if (availableRoutes == null || availableRoutes.Count == 0)
  1616. {
  1617. Debug.LogError("❌ HighlightSpecificRoute: No available routes to render!");
  1618. return;
  1619. }
  1620. if (selectedRoute == null)
  1621. {
  1622. Debug.LogError("❌ HighlightSpecificRoute: Selected route is null!");
  1623. return;
  1624. }
  1625. // Clear and re-render all routes with proper transparency and layering
  1626. ClearPathVisualization();
  1627. // Create a copy of routes to avoid any modification issues
  1628. var routesToRender = new List<TravelRoute>(availableRoutes);
  1629. if (showDebugLogs)
  1630. {
  1631. Debug.Log($"🎨 About to render {routesToRender.Count} routes");
  1632. }
  1633. // First render non-selected routes with transparency
  1634. for (int i = 0; i < routesToRender.Count; i++)
  1635. {
  1636. var route = routesToRender[i];
  1637. if (route == null)
  1638. {
  1639. Debug.LogWarning($"⚠️ Route {i} is null, skipping");
  1640. continue;
  1641. }
  1642. if (route.path == null || route.path.Count == 0)
  1643. {
  1644. Debug.LogWarning($"⚠️ Route {i} ({route.routeType}) has no path, skipping");
  1645. continue;
  1646. }
  1647. bool isSelected = (route == selectedRoute);
  1648. if (showDebugLogs)
  1649. {
  1650. Debug.Log($"🎨 Rendering route {i}: {route.routeType}, selected: {isSelected}, pathCount: {route.path.Count}");
  1651. }
  1652. // All routes are rendered, but with different emphasis
  1653. RenderSingleRoute(route, i, isSelected);
  1654. }
  1655. if (showDebugLogs)
  1656. {
  1657. string routeName = selectedRoute?.routeType == RouteType.RoadPreferred ? "Fastest" :
  1658. selectedRoute?.routeType == RouteType.Standard ? "Shortest" : "Cheapest";
  1659. Debug.Log($"🔝 Highlighted {routeName} route - rendered {currentPathVisualizations.Count} route visualizations");
  1660. }
  1661. }
  1662. /// <summary>
  1663. /// Renders a single route with specified layering
  1664. /// </summary>
  1665. private void RenderSingleRoute(TravelRoute route, int layerIndex, bool isSelected)
  1666. {
  1667. if (route == null || route.path.Count == 0)
  1668. {
  1669. if (showDebugLogs)
  1670. {
  1671. Debug.LogWarning($"⚠️ RenderSingleRoute: Cannot render route - route is null: {route == null}, path count: {route?.path?.Count ?? 0}");
  1672. }
  1673. return;
  1674. }
  1675. if (showDebugLogs)
  1676. {
  1677. Debug.Log($"🎨 RenderSingleRoute: Creating visualization for {route.routeType}, selected: {isSelected}, layerIndex: {layerIndex}");
  1678. }
  1679. // Create path visualization
  1680. var pathObject = new GameObject($"TravelPath_{route.routeType}_{(isSelected ? "Selected" : "Normal")}");
  1681. var lineRenderer = pathObject.AddComponent<LineRenderer>();
  1682. if (showDebugLogs)
  1683. {
  1684. Debug.Log($"🔧 Created GameObject: {pathObject.name} with LineRenderer");
  1685. }
  1686. // Configure line renderer with a new material instance
  1687. var baseMaterial = pathLineMaterial ?? CreateDefaultPathMaterial();
  1688. var routeMaterial = new Material(baseMaterial);
  1689. var routeColor = GetRouteColor(route);
  1690. // Selected route gets full opacity, non-selected get good visibility
  1691. if (isSelected)
  1692. {
  1693. routeColor.a = 1.0f; // Full opacity for selected
  1694. }
  1695. else
  1696. {
  1697. routeColor.a = 0.6f; // Good visibility for non-selected (was 0.8f)
  1698. }
  1699. routeMaterial.color = routeColor;
  1700. lineRenderer.material = routeMaterial;
  1701. // Selected route gets thicker line, non-selected get thinner
  1702. float lineWidth = 1.0f;
  1703. int sortingOrder = 50;
  1704. switch (route.routeType)
  1705. {
  1706. case RouteType.RoadPreferred: // Fastest
  1707. lineWidth = isSelected ? 2.2f : 1.4f; // More dramatic difference
  1708. sortingOrder = isSelected ? 60 : 53;
  1709. break;
  1710. case RouteType.Standard: // Shortest
  1711. lineWidth = isSelected ? 2.0f : 1.2f; // More dramatic difference
  1712. sortingOrder = isSelected ? 59 : 52;
  1713. break;
  1714. case RouteType.Special: // Cheapest
  1715. lineWidth = isSelected ? 1.8f : 1.0f; // More dramatic difference
  1716. sortingOrder = isSelected ? 58 : 51;
  1717. break;
  1718. }
  1719. lineRenderer.startWidth = lineWidth;
  1720. lineRenderer.endWidth = lineWidth;
  1721. lineRenderer.positionCount = route.path.Count;
  1722. lineRenderer.useWorldSpace = true;
  1723. // Enhanced settings with proper layering
  1724. lineRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  1725. lineRenderer.receiveShadows = false;
  1726. lineRenderer.alignment = LineAlignment.View;
  1727. lineRenderer.sortingLayerName = "Default";
  1728. lineRenderer.sortingOrder = sortingOrder;
  1729. lineRenderer.textureMode = LineTextureMode.Tile;
  1730. // Selected route gets priority Y-offset (higher above map)
  1731. float zOffset = pathLineHeight + (isSelected ? 1.0f : layerIndex * 0.2f);
  1732. // Set path points
  1733. for (int j = 0; j < route.path.Count; j++)
  1734. {
  1735. // Convert map coordinates to world coordinates properly for top-down view
  1736. Vector3 worldPos = TileToWorldPosition(route.path[j]);
  1737. // Add slight offset for non-selected routes to prevent exact overlap
  1738. if (!isSelected)
  1739. {
  1740. float offsetAmount = layerIndex * 0.02f;
  1741. worldPos.x += Mathf.Sin(j + layerIndex) * offsetAmount;
  1742. worldPos.z += Mathf.Cos(j + layerIndex) * offsetAmount; // Use Z for map Y offset
  1743. }
  1744. worldPos.y = zOffset; // Use Y for height/depth positioning
  1745. lineRenderer.SetPosition(j, worldPos);
  1746. }
  1747. currentPathVisualizations.Add(pathObject);
  1748. if (showDebugLogs)
  1749. {
  1750. string routeName = route.routeType == RouteType.RoadPreferred ? "Fastest" :
  1751. route.routeType == RouteType.Standard ? "Shortest" : "Cheapest";
  1752. Debug.Log($"✅ Successfully rendered {routeName} route: selected={isSelected}, width={lineWidth}, sorting={sortingOrder}, z-offset={zOffset}");
  1753. Debug.Log($"📊 Total visualizations now: {currentPathVisualizations.Count}");
  1754. }
  1755. }
  1756. public void HideTravelUI()
  1757. {
  1758. // Direct call instead of reflection since TravelUI is now available
  1759. if (travelUI != null)
  1760. {
  1761. travelUI.HideTravelPanel();
  1762. }
  1763. }
  1764. }
  1765. /// <summary>
  1766. /// Represents different types of routes with GPS-style optimization goals
  1767. /// </summary>
  1768. public enum RouteType
  1769. {
  1770. Standard, // Shortest path (minimal distance/waypoints)
  1771. RoadPreferred, // Fastest path (heavily prefers roads and fast terrain)
  1772. Special // Cheapest/Direct path (avoids special costs, takes more direct routes)
  1773. }
  1774. /// <summary>
  1775. /// Represents a complete travel route with all associated costs and information
  1776. /// </summary>
  1777. [System.Serializable]
  1778. public class TravelRoute
  1779. {
  1780. public RouteType routeType;
  1781. public List<Vector2Int> path;
  1782. public float totalCost;
  1783. public float totalDistance;
  1784. public float estimatedTime;
  1785. public Dictionary<string, int> specialCosts; // Special costs like ferry fees, torch usage
  1786. }
  1787. /// <summary>
  1788. /// Node used for A* pathfinding
  1789. /// </summary>
  1790. public class PathNode
  1791. {
  1792. public Vector2Int position;
  1793. public float gCost; // Distance from starting node
  1794. public float hCost; // Heuristic distance to target
  1795. public float fCost => gCost + hCost;
  1796. public PathNode parent;
  1797. }