RoadGenerator.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. public class RoadGenerator
  5. {
  6. public void GenerateRoads(MapData mapData)
  7. {
  8. var settlements = mapData.GetTowns();
  9. settlements.AddRange(mapData.GetVillages());
  10. // Connect settlements with roads
  11. ConnectSettlements(mapData, settlements);
  12. // Connect to map edges
  13. ConnectToMapEdges(mapData, settlements);
  14. // Add standalone harbours on lakes
  15. GenerateLakeHarbours(mapData);
  16. }
  17. private void ConnectSettlements(MapData mapData, List<Settlement> settlements)
  18. {
  19. var towns = settlements.Where(s => s.Type == SettlementType.Town).ToList();
  20. var villages = settlements.Where(s => s.Type == SettlementType.Village).ToList();
  21. var mapMaker = Object.FindFirstObjectByType<MapMaker2>();
  22. bool debugMode = mapMaker != null && mapMaker.debugMode;
  23. if (debugMode) Debug.Log($"Connecting {towns.Count} towns and {villages.Count} villages");
  24. // Connect towns to each other (main highway network)
  25. List<List<Vector2Int>> mainRoads = new List<List<Vector2Int>>();
  26. int townConnections = 0;
  27. int maxTownConnections = towns.Count * (towns.Count - 1) / 2; // Limit total connections
  28. for (int i = 0; i < towns.Count; i++)
  29. {
  30. for (int j = i + 1; j < towns.Count; j++)
  31. {
  32. townConnections++;
  33. if (townConnections > maxTownConnections)
  34. {
  35. Debug.LogWarning($"Limiting town connections to prevent performance issues ({townConnections}/{maxTownConnections})");
  36. break;
  37. }
  38. var roadPath = CreateRoad(mapData, towns[i].position, towns[j].position);
  39. mainRoads.Add(roadPath);
  40. }
  41. if (townConnections > maxTownConnections) break;
  42. }
  43. // Create village chains - connect villages to nearest settlements (including other villages)
  44. var unconnectedVillages = new List<Settlement>(villages);
  45. var connectedSettlements = new List<Settlement>(towns); // Towns are already connected
  46. int villageConnections = 0;
  47. int maxVillageConnections = villages.Count + 10; // Safety limit
  48. while (unconnectedVillages.Count > 0 && villageConnections < maxVillageConnections)
  49. {
  50. villageConnections++;
  51. Settlement nearestVillage = null;
  52. Settlement nearestTarget = null;
  53. float shortestDistance = float.MaxValue;
  54. // Find the closest unconnected village to any connected settlement
  55. foreach (var village in unconnectedVillages)
  56. {
  57. foreach (var connectedSettlement in connectedSettlements)
  58. {
  59. float distance = Vector2.Distance(
  60. new Vector2(village.position.x, village.position.y),
  61. new Vector2(connectedSettlement.position.x, connectedSettlement.position.y));
  62. if (distance < shortestDistance)
  63. {
  64. shortestDistance = distance;
  65. nearestVillage = village;
  66. nearestTarget = connectedSettlement;
  67. }
  68. }
  69. }
  70. // Connect the nearest village and add it to connected settlements
  71. if (nearestVillage != null && nearestTarget != null)
  72. {
  73. Debug.Log($"Connecting village {villageConnections}/{villages.Count}");
  74. CreateRoad(mapData, nearestVillage.position, nearestTarget.position);
  75. connectedSettlements.Add(nearestVillage);
  76. unconnectedVillages.Remove(nearestVillage);
  77. }
  78. else
  79. {
  80. Debug.LogWarning("Could not find village connection, breaking loop");
  81. break; // Safety break
  82. }
  83. }
  84. if (villageConnections >= maxVillageConnections)
  85. {
  86. Debug.LogWarning($"Village connection limit reached ({maxVillageConnections}), some villages may remain unconnected");
  87. }
  88. }
  89. private Vector2Int FindNearestRoadConnection(MapData mapData, Vector2Int villagePos, List<List<Vector2Int>> mainRoads, List<Settlement> towns)
  90. {
  91. Vector2Int nearestPoint = villagePos;
  92. float nearestDistance = float.MaxValue;
  93. // Check all main road points
  94. foreach (var road in mainRoads)
  95. {
  96. foreach (var roadPoint in road)
  97. {
  98. float distance = Vector2.Distance(new Vector2(villagePos.x, villagePos.y), new Vector2(roadPoint.x, roadPoint.y));
  99. if (distance < nearestDistance && distance > 5) // Don't connect too close to avoid overlaps
  100. {
  101. nearestDistance = distance;
  102. nearestPoint = roadPoint;
  103. }
  104. }
  105. }
  106. // If no good road connection found, connect to nearest town
  107. if (nearestDistance > 50f || nearestPoint == villagePos)
  108. {
  109. foreach (var town in towns)
  110. {
  111. float distance = Vector2.Distance(new Vector2(villagePos.x, villagePos.y), new Vector2(town.position.x, town.position.y));
  112. if (distance < nearestDistance)
  113. {
  114. nearestDistance = distance;
  115. nearestPoint = town.position;
  116. }
  117. }
  118. }
  119. return nearestPoint;
  120. }
  121. private void ConnectToMapEdges(MapData mapData, List<Settlement> settlements)
  122. {
  123. var towns = settlements.Where(s => s.Type == SettlementType.Town).ToList();
  124. // Only connect 1-2 towns to map edges (trade routes)
  125. int edgeConnections = Mathf.Min(2, towns.Count);
  126. for (int i = 0; i < edgeConnections; i++)
  127. {
  128. var town = towns[i];
  129. Vector2Int edgePoint = FindNearestEdgePoint(mapData, town.position);
  130. CreateRoad(mapData, town.position, edgePoint);
  131. }
  132. }
  133. private Vector2Int FindNearestEdgePoint(MapData mapData, Vector2Int position)
  134. {
  135. int distToLeft = position.x;
  136. int distToRight = mapData.Width - 1 - position.x;
  137. int distToTop = mapData.Height - 1 - position.y;
  138. int distToBottom = position.y;
  139. int minDist = Mathf.Min(distToLeft, distToRight, distToTop, distToBottom);
  140. if (minDist == distToLeft)
  141. return new Vector2Int(0, position.y);
  142. else if (minDist == distToRight)
  143. return new Vector2Int(mapData.Width - 1, position.y);
  144. else if (minDist == distToTop)
  145. return new Vector2Int(position.x, mapData.Height - 1);
  146. else
  147. return new Vector2Int(position.x, 0);
  148. }
  149. private List<Vector2Int> CreateRoad(MapData mapData, Vector2Int start, Vector2Int end)
  150. {
  151. var path = FindPath(mapData, start, end);
  152. // Process the path to handle water crossings and mountain tunnels
  153. path = ProcessRoadPath(mapData, path);
  154. foreach (var point in path)
  155. {
  156. if (mapData.IsValidPosition(point.x, point.y))
  157. {
  158. MapTile tile = mapData.GetTile(point.x, point.y);
  159. if (tile.featureType == FeatureType.None)
  160. {
  161. // Determine road type based on terrain
  162. if (tile.IsWater())
  163. {
  164. tile.featureType = FeatureType.Bridge;
  165. }
  166. else if (tile.terrainType == TerrainType.Mountain)
  167. {
  168. tile.featureType = FeatureType.Tunnel;
  169. }
  170. else
  171. {
  172. tile.featureType = FeatureType.Road;
  173. }
  174. tile.isWalkable = true;
  175. }
  176. }
  177. }
  178. return path;
  179. }
  180. private List<Vector2Int> ProcessRoadPath(MapData mapData, List<Vector2Int> originalPath)
  181. {
  182. List<Vector2Int> processedPath = new List<Vector2Int>();
  183. for (int i = 0; i < originalPath.Count; i++)
  184. {
  185. Vector2Int current = originalPath[i];
  186. processedPath.Add(current);
  187. // Check if we're about to cross water and need a bridge
  188. if (i < originalPath.Count - 1)
  189. {
  190. Vector2Int next = originalPath[i + 1];
  191. if (mapData.IsValidPosition(next.x, next.y) && mapData.GetTile(next.x, next.y).IsWater())
  192. {
  193. // We're about to enter water, check bridge length
  194. List<Vector2Int> waterCrossing = GetWaterCrossingSegment(mapData, originalPath, i + 1);
  195. if (waterCrossing.Count > 5) // Bridge too long
  196. {
  197. // Check if this is an ocean crossing - perfect for harbours and ferry
  198. if (IsOceanCrossing(mapData, waterCrossing))
  199. {
  200. CreateHarbourFerryConnection(mapData, current, waterCrossing, originalPath, i);
  201. // Continue with the road after the water crossing
  202. i += waterCrossing.Count; // Skip over the water tiles
  203. continue;
  204. }
  205. // Check if it's a large lake - also suitable for harbours
  206. else if (IsLargeLakeCrossing(mapData, waterCrossing))
  207. {
  208. CreateHarbourFerryConnection(mapData, current, waterCrossing, originalPath, i);
  209. // Continue with the road after the water crossing
  210. i += waterCrossing.Count; // Skip over the water tiles
  211. continue;
  212. }
  213. else
  214. {
  215. // It's a river or small lake - try to find alternative path or skip this connection
  216. Debug.LogWarning($"Bridge too long ({waterCrossing.Count} tiles), skipping water crossing - not suitable for harbours");
  217. break; // Stop building this road segment
  218. }
  219. }
  220. }
  221. }
  222. }
  223. return processedPath;
  224. }
  225. private List<Vector2Int> GetWaterCrossingSegment(MapData mapData, List<Vector2Int> path, int startIndex)
  226. {
  227. List<Vector2Int> waterSegment = new List<Vector2Int>();
  228. for (int i = startIndex; i < path.Count; i++)
  229. {
  230. Vector2Int point = path[i];
  231. if (mapData.IsValidPosition(point.x, point.y) && mapData.GetTile(point.x, point.y).IsWater())
  232. {
  233. waterSegment.Add(point);
  234. }
  235. else
  236. {
  237. break; // End of water crossing
  238. }
  239. }
  240. return waterSegment;
  241. }
  242. private bool IsOceanCrossing(MapData mapData, List<Vector2Int> waterCrossing)
  243. {
  244. // Check if any of the water tiles are ocean tiles
  245. int oceanTiles = 0;
  246. int totalWaterTiles = 0;
  247. foreach (var waterTile in waterCrossing)
  248. {
  249. if (mapData.IsValidPosition(waterTile.x, waterTile.y))
  250. {
  251. MapTile tile = mapData.GetTile(waterTile.x, waterTile.y);
  252. totalWaterTiles++;
  253. if (tile.terrainType == TerrainType.Ocean)
  254. {
  255. oceanTiles++;
  256. }
  257. }
  258. }
  259. // Consider it an ocean crossing if more than 30% of the water is ocean
  260. return oceanTiles > 0 && (float)oceanTiles / totalWaterTiles > 0.3f;
  261. }
  262. private bool IsLargeLakeCrossing(MapData mapData, List<Vector2Int> waterCrossing)
  263. {
  264. // Check if any of the water tiles are lake tiles and it's a substantial crossing
  265. int lakeTiles = 0;
  266. int totalWaterTiles = 0;
  267. foreach (var waterTile in waterCrossing)
  268. {
  269. if (mapData.IsValidPosition(waterTile.x, waterTile.y))
  270. {
  271. MapTile tile = mapData.GetTile(waterTile.x, waterTile.y);
  272. totalWaterTiles++;
  273. if (tile.terrainType == TerrainType.Lake)
  274. {
  275. lakeTiles++;
  276. }
  277. }
  278. }
  279. // Consider it a large lake crossing if:
  280. // 1. More than 50% of the water is lake
  281. // 2. AND the crossing is at least 8 tiles long (larger than normal bridge)
  282. return lakeTiles > 0 &&
  283. (float)lakeTiles / totalWaterTiles > 0.5f &&
  284. waterCrossing.Count >= 8;
  285. }
  286. private void CreateHarbourFerryConnection(MapData mapData, Vector2Int roadStart, List<Vector2Int> waterCrossing, List<Vector2Int> originalPath, int pathIndex)
  287. {
  288. // Find the end of the water crossing to place the destination harbour
  289. Vector2Int roadEnd = Vector2Int.zero;
  290. int endIndex = pathIndex + 1 + waterCrossing.Count;
  291. if (endIndex < originalPath.Count)
  292. {
  293. roadEnd = originalPath[endIndex];
  294. }
  295. else if (waterCrossing.Count > 0)
  296. {
  297. // Use the last water tile and find adjacent land
  298. Vector2Int lastWaterTile = waterCrossing[waterCrossing.Count - 1];
  299. roadEnd = FindNearestLandTile(mapData, lastWaterTile);
  300. }
  301. if (roadEnd != Vector2Int.zero)
  302. {
  303. // Create harbour at the start of water crossing
  304. Vector2Int startHarbour = FindHarbourPosition(mapData, roadStart);
  305. if (startHarbour != Vector2Int.zero && !HasNearbyHarbour(mapData, startHarbour, 8))
  306. {
  307. mapData.GetTile(startHarbour.x, startHarbour.y).featureType = FeatureType.Harbour;
  308. }
  309. else if (startHarbour != Vector2Int.zero)
  310. {
  311. // Still try to find the existing nearby harbour for ferry connection
  312. startHarbour = FindNearestHarbour(mapData, startHarbour, 8);
  313. }
  314. // Create harbour at the end of water crossing
  315. Vector2Int endHarbour = FindHarbourPosition(mapData, roadEnd);
  316. if (endHarbour != Vector2Int.zero && !HasNearbyHarbour(mapData, endHarbour, 8))
  317. {
  318. mapData.GetTile(endHarbour.x, endHarbour.y).featureType = FeatureType.Harbour;
  319. }
  320. else if (endHarbour != Vector2Int.zero)
  321. {
  322. // Still try to find the existing nearby harbour for ferry connection
  323. endHarbour = FindNearestHarbour(mapData, endHarbour, 8);
  324. }
  325. // Create ferry route between harbours (visual connection)
  326. if (startHarbour != Vector2Int.zero && endHarbour != Vector2Int.zero)
  327. {
  328. CreateFerryRoute(mapData, startHarbour, endHarbour);
  329. }
  330. }
  331. }
  332. private Vector2Int FindHarbourPosition(MapData mapData, Vector2Int nearPosition)
  333. {
  334. // Look for shore tiles near the given position that can become harbours
  335. for (int range = 1; range <= 5; range++)
  336. {
  337. for (int x = nearPosition.x - range; x <= nearPosition.x + range; x++)
  338. {
  339. for (int y = nearPosition.y - range; y <= nearPosition.y + range; y++)
  340. {
  341. if (mapData.IsValidPosition(x, y))
  342. {
  343. MapTile tile = mapData.GetTile(x, y);
  344. // Perfect spot: shore tile adjacent to water
  345. if (tile.terrainType == TerrainType.Shore && IsAdjacentToWater(mapData, x, y))
  346. {
  347. return new Vector2Int(x, y);
  348. }
  349. // Alternative: land tile adjacent to water that we can convert to shore/harbour
  350. if ((tile.terrainType == TerrainType.Plains || tile.terrainType == TerrainType.Forest)
  351. && IsAdjacentToWater(mapData, x, y) && tile.featureType == FeatureType.None)
  352. {
  353. // Convert to shore first, then harbour
  354. tile.terrainType = TerrainType.Shore;
  355. return new Vector2Int(x, y);
  356. }
  357. }
  358. }
  359. }
  360. }
  361. return Vector2Int.zero;
  362. }
  363. private Vector2Int FindNearestLandTile(MapData mapData, Vector2Int waterPosition)
  364. {
  365. // Find the nearest non-water tile from the given water position
  366. for (int range = 1; range <= 10; range++)
  367. {
  368. for (int x = waterPosition.x - range; x <= waterPosition.x + range; x++)
  369. {
  370. for (int y = waterPosition.y - range; y <= waterPosition.y + range; y++)
  371. {
  372. if (mapData.IsValidPosition(x, y))
  373. {
  374. MapTile tile = mapData.GetTile(x, y);
  375. if (!tile.IsWater())
  376. {
  377. return new Vector2Int(x, y);
  378. }
  379. }
  380. }
  381. }
  382. }
  383. return Vector2Int.zero;
  384. }
  385. private bool IsAdjacentToWater(MapData mapData, int x, int y)
  386. {
  387. // Check if the tile is adjacent to any water tile
  388. Vector2Int[] directions = {
  389. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  390. new Vector2Int(0, 1), new Vector2Int(0, -1)
  391. };
  392. foreach (var dir in directions)
  393. {
  394. int checkX = x + dir.x;
  395. int checkY = y + dir.y;
  396. if (mapData.IsValidPosition(checkX, checkY))
  397. {
  398. MapTile tile = mapData.GetTile(checkX, checkY);
  399. if (tile.IsWater())
  400. {
  401. return true;
  402. }
  403. }
  404. }
  405. return false;
  406. }
  407. private void CreateFerryRoute(MapData mapData, Vector2Int startHarbour, Vector2Int endHarbour)
  408. {
  409. // Create a simple straight line ferry route for visualization
  410. // This could be enhanced with ferry scheduling, costs, etc.
  411. Vector2Int current = startHarbour;
  412. Vector2Int direction = new Vector2Int(
  413. endHarbour.x > startHarbour.x ? 1 : (endHarbour.x < startHarbour.x ? -1 : 0),
  414. endHarbour.y > startHarbour.y ? 1 : (endHarbour.y < startHarbour.y ? -1 : 0)
  415. );
  416. int steps = 0;
  417. int maxSteps = Mathf.Max(Mathf.Abs(endHarbour.x - startHarbour.x), Mathf.Abs(endHarbour.y - startHarbour.y));
  418. while (current != endHarbour && steps < maxSteps * 2)
  419. {
  420. steps++;
  421. current += direction;
  422. if (mapData.IsValidPosition(current.x, current.y))
  423. {
  424. MapTile tile = mapData.GetTile(current.x, current.y);
  425. // Mark water tiles as ferry route
  426. if (tile.IsWater() && tile.featureType == FeatureType.None)
  427. {
  428. tile.featureType = FeatureType.Ferry;
  429. tile.isWalkable = true; // Ferry routes are walkable (for a cost)
  430. }
  431. }
  432. // Adjust direction to reach target
  433. if (current.x != endHarbour.x)
  434. direction.x = endHarbour.x > current.x ? 1 : -1;
  435. if (current.y != endHarbour.y)
  436. direction.y = endHarbour.y > current.y ? 1 : -1;
  437. }
  438. }
  439. private void GenerateLakeHarbours(MapData mapData)
  440. {
  441. // Find lakes and add small harbours to them
  442. HashSet<Vector2Int> processedAreas = new HashSet<Vector2Int>();
  443. int harboursCreated = 0;
  444. int maxHarbours = 3; // Reduced from 6 to prevent too many harbours
  445. for (int x = 0; x < mapData.Width && harboursCreated < maxHarbours; x++)
  446. {
  447. for (int y = 0; y < mapData.Height && harboursCreated < maxHarbours; y++)
  448. {
  449. if (processedAreas.Contains(new Vector2Int(x, y)))
  450. continue;
  451. MapTile tile = mapData.GetTile(x, y);
  452. if (tile.terrainType == TerrainType.Lake)
  453. {
  454. // Check if this lake is large enough for a harbour
  455. int lakeSize = MeasureLakeSize(mapData, x, y, processedAreas);
  456. if (lakeSize >= 25) // Increased from 15 - only add harbours to larger lakes
  457. {
  458. Vector2Int harbourPos = FindLakeHarbourPosition(mapData, x, y);
  459. if (harbourPos != Vector2Int.zero && !HasNearbyHarbour(mapData, harbourPos, 12))
  460. {
  461. mapData.GetTile(harbourPos.x, harbourPos.y).featureType = FeatureType.Harbour;
  462. harboursCreated++;
  463. }
  464. }
  465. }
  466. }
  467. }
  468. }
  469. private int MeasureLakeSize(MapData mapData, int startX, int startY, HashSet<Vector2Int> processedAreas)
  470. {
  471. // Simple flood fill to measure lake size
  472. Queue<Vector2Int> toCheck = new Queue<Vector2Int>();
  473. HashSet<Vector2Int> lakeArea = new HashSet<Vector2Int>();
  474. toCheck.Enqueue(new Vector2Int(startX, startY));
  475. while (toCheck.Count > 0 && lakeArea.Count < 100) // Limit search to prevent performance issues
  476. {
  477. Vector2Int current = toCheck.Dequeue();
  478. if (lakeArea.Contains(current) || !mapData.IsValidPosition(current.x, current.y))
  479. continue;
  480. MapTile tile = mapData.GetTile(current.x, current.y);
  481. if (tile.terrainType != TerrainType.Lake)
  482. continue;
  483. lakeArea.Add(current);
  484. processedAreas.Add(current);
  485. // Check adjacent tiles
  486. Vector2Int[] directions = {
  487. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  488. new Vector2Int(0, 1), new Vector2Int(0, -1)
  489. };
  490. foreach (var dir in directions)
  491. {
  492. Vector2Int next = current + dir;
  493. if (!lakeArea.Contains(next))
  494. {
  495. toCheck.Enqueue(next);
  496. }
  497. }
  498. }
  499. return lakeArea.Count;
  500. }
  501. private Vector2Int FindLakeHarbourPosition(MapData mapData, int lakeX, int lakeY)
  502. {
  503. // Find a good position for a harbour near this lake
  504. for (int range = 1; range <= 8; range++)
  505. {
  506. for (int x = lakeX - range; x <= lakeX + range; x++)
  507. {
  508. for (int y = lakeY - range; y <= lakeY + range; y++)
  509. {
  510. if (mapData.IsValidPosition(x, y))
  511. {
  512. MapTile tile = mapData.GetTile(x, y);
  513. // Look for shore tiles or land tiles adjacent to the lake
  514. if ((tile.terrainType == TerrainType.Shore ||
  515. tile.terrainType == TerrainType.Plains ||
  516. tile.terrainType == TerrainType.Forest) &&
  517. tile.featureType == FeatureType.None &&
  518. IsAdjacentToLake(mapData, x, y))
  519. {
  520. // Convert to shore if it isn't already
  521. if (tile.terrainType != TerrainType.Shore)
  522. {
  523. tile.terrainType = TerrainType.Shore;
  524. }
  525. return new Vector2Int(x, y);
  526. }
  527. }
  528. }
  529. }
  530. }
  531. return Vector2Int.zero;
  532. }
  533. private bool HasNearbyHarbour(MapData mapData, Vector2Int position, int checkRadius)
  534. {
  535. // Check if there's already a harbour within the specified radius
  536. for (int x = position.x - checkRadius; x <= position.x + checkRadius; x++)
  537. {
  538. for (int y = position.y - checkRadius; y <= position.y + checkRadius; y++)
  539. {
  540. if (mapData.IsValidPosition(x, y))
  541. {
  542. MapTile tile = mapData.GetTile(x, y);
  543. if (tile.featureType == FeatureType.Harbour)
  544. {
  545. float distance = Vector2.Distance(
  546. new Vector2(position.x, position.y),
  547. new Vector2(x, y)
  548. );
  549. if (distance <= checkRadius)
  550. {
  551. return true;
  552. }
  553. }
  554. }
  555. }
  556. }
  557. return false;
  558. }
  559. private Vector2Int FindNearestHarbour(MapData mapData, Vector2Int position, int searchRadius)
  560. {
  561. // Find the nearest existing harbour within the search radius
  562. Vector2Int nearestHarbour = Vector2Int.zero;
  563. float nearestDistance = float.MaxValue;
  564. for (int x = position.x - searchRadius; x <= position.x + searchRadius; x++)
  565. {
  566. for (int y = position.y - searchRadius; y <= position.y + searchRadius; y++)
  567. {
  568. if (mapData.IsValidPosition(x, y))
  569. {
  570. MapTile tile = mapData.GetTile(x, y);
  571. if (tile.featureType == FeatureType.Harbour)
  572. {
  573. float distance = Vector2.Distance(
  574. new Vector2(position.x, position.y),
  575. new Vector2(x, y)
  576. );
  577. if (distance <= searchRadius && distance < nearestDistance)
  578. {
  579. nearestDistance = distance;
  580. nearestHarbour = new Vector2Int(x, y);
  581. }
  582. }
  583. }
  584. }
  585. }
  586. return nearestHarbour;
  587. }
  588. private bool IsAdjacentToLake(MapData mapData, int x, int y)
  589. {
  590. Vector2Int[] directions = {
  591. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  592. new Vector2Int(0, 1), new Vector2Int(0, -1)
  593. };
  594. foreach (var dir in directions)
  595. {
  596. int checkX = x + dir.x;
  597. int checkY = y + dir.y;
  598. if (mapData.IsValidPosition(checkX, checkY))
  599. {
  600. MapTile tile = mapData.GetTile(checkX, checkY);
  601. if (tile.terrainType == TerrainType.Lake)
  602. {
  603. return true;
  604. }
  605. }
  606. }
  607. return false;
  608. }
  609. private List<Vector2Int> FindPath(MapData mapData, Vector2Int start, Vector2Int end)
  610. {
  611. // Simple pathfinding with some randomness for variation and water avoidance
  612. List<Vector2Int> path = new List<Vector2Int>();
  613. Vector2Int current = start;
  614. HashSet<Vector2Int> visitedPositions = new HashSet<Vector2Int>();
  615. int maxIterations = mapData.Width * mapData.Height; // Prevent infinite loops
  616. int iterations = 0;
  617. while (current != end && iterations < maxIterations)
  618. {
  619. iterations++;
  620. // Check if we're stuck in a loop
  621. if (visitedPositions.Contains(current))
  622. {
  623. Debug.LogWarning($"Pathfinding detected loop at {current}, breaking to prevent infinite loop");
  624. break;
  625. }
  626. visitedPositions.Add(current);
  627. path.Add(current);
  628. Vector2Int direction = new Vector2Int(
  629. end.x > current.x ? 1 : (end.x < current.x ? -1 : 0),
  630. end.y > current.y ? 1 : (end.y < current.y ? -1 : 0)
  631. );
  632. // Add some randomness to avoid perfectly straight roads
  633. if (Random.value < 0.3f)
  634. {
  635. if (Random.value < 0.5f)
  636. direction.x = 0;
  637. else
  638. direction.y = 0;
  639. }
  640. Vector2Int nextPos = current + direction;
  641. // Check if next position would be water and try to avoid long water crossings
  642. if (mapData.IsValidPosition(nextPos.x, nextPos.y))
  643. {
  644. MapTile nextTile = mapData.GetTile(nextPos.x, nextPos.y);
  645. // If next tile is water, try to find alternative route first
  646. if (nextTile.IsWater())
  647. {
  648. Vector2Int alternativePos = FindAlternativeRoute(mapData, current, end, visitedPositions);
  649. if (alternativePos != current && !visitedPositions.Contains(alternativePos))
  650. {
  651. nextPos = alternativePos;
  652. }
  653. }
  654. }
  655. // Ensure we're making progress and not going to visited positions
  656. if (!mapData.IsValidPosition(nextPos.x, nextPos.y) || visitedPositions.Contains(nextPos))
  657. {
  658. // Find any valid adjacent position that moves toward target
  659. Vector2Int fallbackPos = FindFallbackMove(mapData, current, end, visitedPositions);
  660. if (fallbackPos != current)
  661. {
  662. nextPos = fallbackPos;
  663. }
  664. else
  665. {
  666. Debug.LogWarning($"Pathfinding stuck at {current}, unable to continue to {end}");
  667. break;
  668. }
  669. }
  670. current = nextPos;
  671. }
  672. if (iterations >= maxIterations)
  673. {
  674. Debug.LogError($"Pathfinding exceeded maximum iterations ({maxIterations}), terminating path from {start} to {end}");
  675. }
  676. // Only add end if we actually reached it
  677. if (current == end)
  678. {
  679. path.Add(end);
  680. }
  681. return path;
  682. }
  683. private Vector2Int FindAlternativeRoute(MapData mapData, Vector2Int current, Vector2Int end, HashSet<Vector2Int> visitedPositions = null)
  684. {
  685. // Try to find a non-water adjacent tile that still moves toward the destination
  686. Vector2Int[] directions = {
  687. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  688. new Vector2Int(0, 1), new Vector2Int(0, -1),
  689. new Vector2Int(1, 1), new Vector2Int(-1, -1),
  690. new Vector2Int(1, -1), new Vector2Int(-1, 1)
  691. };
  692. Vector2Int bestDirection = Vector2Int.zero;
  693. float bestScore = float.MaxValue;
  694. foreach (var dir in directions)
  695. {
  696. Vector2Int candidate = current + dir;
  697. if (mapData.IsValidPosition(candidate.x, candidate.y))
  698. {
  699. // Skip if we've already visited this position
  700. if (visitedPositions != null && visitedPositions.Contains(candidate))
  701. continue;
  702. MapTile candidateTile = mapData.GetTile(candidate.x, candidate.y);
  703. // Prefer non-water tiles
  704. if (!candidateTile.IsWater())
  705. {
  706. // Calculate distance to end and prefer directions that move toward target
  707. float distanceToEnd = Vector2.Distance(new Vector2(candidate.x, candidate.y), new Vector2(end.x, end.y));
  708. float currentDistanceToEnd = Vector2.Distance(new Vector2(current.x, current.y), new Vector2(end.x, end.y));
  709. // Only consider if it moves us closer or maintains distance
  710. if (distanceToEnd <= currentDistanceToEnd + 1f && distanceToEnd < bestScore)
  711. {
  712. bestScore = distanceToEnd;
  713. bestDirection = dir;
  714. }
  715. }
  716. }
  717. }
  718. return bestDirection != Vector2Int.zero ? current + bestDirection : current;
  719. }
  720. private Vector2Int FindFallbackMove(MapData mapData, Vector2Int current, Vector2Int end, HashSet<Vector2Int> visitedPositions)
  721. {
  722. // Find any valid adjacent position that hasn't been visited and moves toward target
  723. Vector2Int[] directions = {
  724. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  725. new Vector2Int(0, 1), new Vector2Int(0, -1),
  726. new Vector2Int(1, 1), new Vector2Int(-1, -1),
  727. new Vector2Int(1, -1), new Vector2Int(-1, 1)
  728. };
  729. Vector2Int bestMove = current;
  730. float bestDistance = float.MaxValue;
  731. foreach (var dir in directions)
  732. {
  733. Vector2Int candidate = current + dir;
  734. if (mapData.IsValidPosition(candidate.x, candidate.y) && !visitedPositions.Contains(candidate))
  735. {
  736. float distanceToEnd = Vector2.Distance(new Vector2(candidate.x, candidate.y), new Vector2(end.x, end.y));
  737. if (distanceToEnd < bestDistance)
  738. {
  739. bestDistance = distanceToEnd;
  740. bestMove = candidate;
  741. }
  742. }
  743. }
  744. return bestMove;
  745. }
  746. }