TeamTravelSystem.cs 82 KB

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