TeamTravelSystem.cs 73 KB

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