RoadGenerator.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  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. Debug.Log($"Connecting {towns.Count} towns and {villages.Count} villages");
  22. // Connect towns to each other (main highway network)
  23. List<List<Vector2Int>> mainRoads = new List<List<Vector2Int>>();
  24. int townConnections = 0;
  25. int maxTownConnections = towns.Count * (towns.Count - 1) / 2; // Limit total connections
  26. for (int i = 0; i < towns.Count; i++)
  27. {
  28. for (int j = i + 1; j < towns.Count; j++)
  29. {
  30. townConnections++;
  31. if (townConnections > maxTownConnections)
  32. {
  33. Debug.LogWarning($"Limiting town connections to prevent performance issues ({townConnections}/{maxTownConnections})");
  34. break;
  35. }
  36. Debug.Log($"Creating road between towns {i} and {j}");
  37. var roadPath = CreateRoad(mapData, towns[i].position, towns[j].position);
  38. mainRoads.Add(roadPath);
  39. }
  40. if (townConnections > maxTownConnections) break;
  41. }
  42. // Create village chains - connect villages to nearest settlements (including other villages)
  43. var unconnectedVillages = new List<Settlement>(villages);
  44. var connectedSettlements = new List<Settlement>(towns); // Towns are already connected
  45. int villageConnections = 0;
  46. int maxVillageConnections = villages.Count + 10; // Safety limit
  47. Debug.Log($"Connecting {unconnectedVillages.Count} villages to settlement network");
  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. Debug.Log($"Water crossing detected: {waterCrossing.Count} tiles");
  196. if (waterCrossing.Count > 5) // Bridge too long
  197. {
  198. // Check if this is an ocean crossing - perfect for harbours and ferry
  199. if (IsOceanCrossing(mapData, waterCrossing))
  200. {
  201. Debug.Log($"Ocean crossing detected ({waterCrossing.Count} tiles) - creating harbour and ferry connection");
  202. CreateHarbourFerryConnection(mapData, current, waterCrossing, originalPath, i);
  203. // Continue with the road after the water crossing
  204. i += waterCrossing.Count; // Skip over the water tiles
  205. continue;
  206. }
  207. // Check if it's a large lake - also suitable for harbours
  208. else if (IsLargeLakeCrossing(mapData, waterCrossing))
  209. {
  210. Debug.Log($"Large lake crossing detected ({waterCrossing.Count} tiles) - creating harbour connection");
  211. CreateHarbourFerryConnection(mapData, current, waterCrossing, originalPath, i);
  212. // Continue with the road after the water crossing
  213. i += waterCrossing.Count; // Skip over the water tiles
  214. continue;
  215. }
  216. else
  217. {
  218. // It's a river or small lake - try to find alternative path or skip this connection
  219. Debug.LogWarning($"Bridge too long ({waterCrossing.Count} tiles), skipping water crossing - not suitable for harbours");
  220. break; // Stop building this road segment
  221. }
  222. }
  223. else
  224. {
  225. Debug.Log($"Water crossing short enough for bridge ({waterCrossing.Count} tiles)");
  226. }
  227. }
  228. }
  229. }
  230. return processedPath;
  231. }
  232. private List<Vector2Int> GetWaterCrossingSegment(MapData mapData, List<Vector2Int> path, int startIndex)
  233. {
  234. List<Vector2Int> waterSegment = new List<Vector2Int>();
  235. for (int i = startIndex; i < path.Count; i++)
  236. {
  237. Vector2Int point = path[i];
  238. if (mapData.IsValidPosition(point.x, point.y) && mapData.GetTile(point.x, point.y).IsWater())
  239. {
  240. waterSegment.Add(point);
  241. }
  242. else
  243. {
  244. break; // End of water crossing
  245. }
  246. }
  247. return waterSegment;
  248. }
  249. private bool IsOceanCrossing(MapData mapData, List<Vector2Int> waterCrossing)
  250. {
  251. // Check if any of the water tiles are ocean tiles
  252. int oceanTiles = 0;
  253. int totalWaterTiles = 0;
  254. foreach (var waterTile in waterCrossing)
  255. {
  256. if (mapData.IsValidPosition(waterTile.x, waterTile.y))
  257. {
  258. MapTile tile = mapData.GetTile(waterTile.x, waterTile.y);
  259. totalWaterTiles++;
  260. if (tile.terrainType == TerrainType.Ocean)
  261. {
  262. oceanTiles++;
  263. }
  264. }
  265. }
  266. Debug.Log($"Water crossing analysis: {oceanTiles} ocean tiles out of {totalWaterTiles} total water tiles");
  267. // Consider it an ocean crossing if more than 30% of the water is ocean
  268. return oceanTiles > 0 && (float)oceanTiles / totalWaterTiles > 0.3f;
  269. }
  270. private bool IsLargeLakeCrossing(MapData mapData, List<Vector2Int> waterCrossing)
  271. {
  272. // Check if any of the water tiles are lake tiles and it's a substantial crossing
  273. int lakeTiles = 0;
  274. int totalWaterTiles = 0;
  275. foreach (var waterTile in waterCrossing)
  276. {
  277. if (mapData.IsValidPosition(waterTile.x, waterTile.y))
  278. {
  279. MapTile tile = mapData.GetTile(waterTile.x, waterTile.y);
  280. totalWaterTiles++;
  281. if (tile.terrainType == TerrainType.Lake)
  282. {
  283. lakeTiles++;
  284. }
  285. }
  286. }
  287. Debug.Log($"Lake crossing analysis: {lakeTiles} lake tiles out of {totalWaterTiles} total water tiles");
  288. // Consider it a large lake crossing if:
  289. // 1. More than 50% of the water is lake
  290. // 2. AND the crossing is at least 8 tiles long (larger than normal bridge)
  291. return lakeTiles > 0 &&
  292. (float)lakeTiles / totalWaterTiles > 0.5f &&
  293. waterCrossing.Count >= 8;
  294. }
  295. private void CreateHarbourFerryConnection(MapData mapData, Vector2Int roadStart, List<Vector2Int> waterCrossing, List<Vector2Int> originalPath, int pathIndex)
  296. {
  297. // Find the end of the water crossing to place the destination harbour
  298. Vector2Int roadEnd = Vector2Int.zero;
  299. int endIndex = pathIndex + 1 + waterCrossing.Count;
  300. if (endIndex < originalPath.Count)
  301. {
  302. roadEnd = originalPath[endIndex];
  303. }
  304. else if (waterCrossing.Count > 0)
  305. {
  306. // Use the last water tile and find adjacent land
  307. Vector2Int lastWaterTile = waterCrossing[waterCrossing.Count - 1];
  308. roadEnd = FindNearestLandTile(mapData, lastWaterTile);
  309. }
  310. if (roadEnd != Vector2Int.zero)
  311. {
  312. // Create harbour at the start of water crossing
  313. Vector2Int startHarbour = FindHarbourPosition(mapData, roadStart);
  314. if (startHarbour != Vector2Int.zero && !HasNearbyHarbour(mapData, startHarbour, 8))
  315. {
  316. mapData.GetTile(startHarbour.x, startHarbour.y).featureType = FeatureType.Harbour;
  317. Debug.Log($"Created start harbour at {startHarbour}");
  318. }
  319. else if (startHarbour != Vector2Int.zero)
  320. {
  321. Debug.Log($"Skipped start harbour at {startHarbour} - too close to existing harbour");
  322. // Still try to find the existing nearby harbour for ferry connection
  323. startHarbour = FindNearestHarbour(mapData, startHarbour, 8);
  324. }
  325. // Create harbour at the end of water crossing
  326. Vector2Int endHarbour = FindHarbourPosition(mapData, roadEnd);
  327. if (endHarbour != Vector2Int.zero && !HasNearbyHarbour(mapData, endHarbour, 8))
  328. {
  329. mapData.GetTile(endHarbour.x, endHarbour.y).featureType = FeatureType.Harbour;
  330. Debug.Log($"Created end harbour at {endHarbour}");
  331. }
  332. else if (endHarbour != Vector2Int.zero)
  333. {
  334. Debug.Log($"Skipped end harbour at {endHarbour} - too close to existing harbour");
  335. // Still try to find the existing nearby harbour for ferry connection
  336. endHarbour = FindNearestHarbour(mapData, endHarbour, 8);
  337. }
  338. // Create ferry route between harbours (visual connection)
  339. if (startHarbour != Vector2Int.zero && endHarbour != Vector2Int.zero)
  340. {
  341. CreateFerryRoute(mapData, startHarbour, endHarbour);
  342. }
  343. }
  344. }
  345. private Vector2Int FindHarbourPosition(MapData mapData, Vector2Int nearPosition)
  346. {
  347. // Look for shore tiles near the given position that can become harbours
  348. for (int range = 1; range <= 5; range++)
  349. {
  350. for (int x = nearPosition.x - range; x <= nearPosition.x + range; x++)
  351. {
  352. for (int y = nearPosition.y - range; y <= nearPosition.y + range; y++)
  353. {
  354. if (mapData.IsValidPosition(x, y))
  355. {
  356. MapTile tile = mapData.GetTile(x, y);
  357. // Perfect spot: shore tile adjacent to water
  358. if (tile.terrainType == TerrainType.Shore && IsAdjacentToWater(mapData, x, y))
  359. {
  360. return new Vector2Int(x, y);
  361. }
  362. // Alternative: land tile adjacent to water that we can convert to shore/harbour
  363. if ((tile.terrainType == TerrainType.Plains || tile.terrainType == TerrainType.Forest)
  364. && IsAdjacentToWater(mapData, x, y) && tile.featureType == FeatureType.None)
  365. {
  366. // Convert to shore first, then harbour
  367. tile.terrainType = TerrainType.Shore;
  368. return new Vector2Int(x, y);
  369. }
  370. }
  371. }
  372. }
  373. }
  374. return Vector2Int.zero;
  375. }
  376. private Vector2Int FindNearestLandTile(MapData mapData, Vector2Int waterPosition)
  377. {
  378. // Find the nearest non-water tile from the given water position
  379. for (int range = 1; range <= 10; range++)
  380. {
  381. for (int x = waterPosition.x - range; x <= waterPosition.x + range; x++)
  382. {
  383. for (int y = waterPosition.y - range; y <= waterPosition.y + range; y++)
  384. {
  385. if (mapData.IsValidPosition(x, y))
  386. {
  387. MapTile tile = mapData.GetTile(x, y);
  388. if (!tile.IsWater())
  389. {
  390. return new Vector2Int(x, y);
  391. }
  392. }
  393. }
  394. }
  395. }
  396. return Vector2Int.zero;
  397. }
  398. private bool IsAdjacentToWater(MapData mapData, int x, int y)
  399. {
  400. // Check if the tile is adjacent to any water tile
  401. Vector2Int[] directions = {
  402. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  403. new Vector2Int(0, 1), new Vector2Int(0, -1)
  404. };
  405. foreach (var dir in directions)
  406. {
  407. int checkX = x + dir.x;
  408. int checkY = y + dir.y;
  409. if (mapData.IsValidPosition(checkX, checkY))
  410. {
  411. MapTile tile = mapData.GetTile(checkX, checkY);
  412. if (tile.IsWater())
  413. {
  414. return true;
  415. }
  416. }
  417. }
  418. return false;
  419. }
  420. private void CreateFerryRoute(MapData mapData, Vector2Int startHarbour, Vector2Int endHarbour)
  421. {
  422. // Create a simple straight line ferry route for visualization
  423. // This could be enhanced with ferry scheduling, costs, etc.
  424. Vector2Int current = startHarbour;
  425. Vector2Int direction = new Vector2Int(
  426. endHarbour.x > startHarbour.x ? 1 : (endHarbour.x < startHarbour.x ? -1 : 0),
  427. endHarbour.y > startHarbour.y ? 1 : (endHarbour.y < startHarbour.y ? -1 : 0)
  428. );
  429. int steps = 0;
  430. int maxSteps = Mathf.Max(Mathf.Abs(endHarbour.x - startHarbour.x), Mathf.Abs(endHarbour.y - startHarbour.y));
  431. while (current != endHarbour && steps < maxSteps * 2)
  432. {
  433. steps++;
  434. current += direction;
  435. if (mapData.IsValidPosition(current.x, current.y))
  436. {
  437. MapTile tile = mapData.GetTile(current.x, current.y);
  438. // Mark water tiles as ferry route
  439. if (tile.IsWater() && tile.featureType == FeatureType.None)
  440. {
  441. tile.featureType = FeatureType.Ferry;
  442. tile.isWalkable = true; // Ferry routes are walkable (for a cost)
  443. }
  444. }
  445. // Adjust direction to reach target
  446. if (current.x != endHarbour.x)
  447. direction.x = endHarbour.x > current.x ? 1 : -1;
  448. if (current.y != endHarbour.y)
  449. direction.y = endHarbour.y > current.y ? 1 : -1;
  450. }
  451. Debug.Log($"Created ferry route from {startHarbour} to {endHarbour}");
  452. }
  453. private void GenerateLakeHarbours(MapData mapData)
  454. {
  455. // Find lakes and add small harbours to them
  456. HashSet<Vector2Int> processedAreas = new HashSet<Vector2Int>();
  457. int harboursCreated = 0;
  458. int maxHarbours = 3; // Reduced from 6 to prevent too many harbours
  459. for (int x = 0; x < mapData.Width && harboursCreated < maxHarbours; x++)
  460. {
  461. for (int y = 0; y < mapData.Height && harboursCreated < maxHarbours; y++)
  462. {
  463. if (processedAreas.Contains(new Vector2Int(x, y)))
  464. continue;
  465. MapTile tile = mapData.GetTile(x, y);
  466. if (tile.terrainType == TerrainType.Lake)
  467. {
  468. // Check if this lake is large enough for a harbour
  469. int lakeSize = MeasureLakeSize(mapData, x, y, processedAreas);
  470. if (lakeSize >= 25) // Increased from 15 - only add harbours to larger lakes
  471. {
  472. Vector2Int harbourPos = FindLakeHarbourPosition(mapData, x, y);
  473. if (harbourPos != Vector2Int.zero && !HasNearbyHarbour(mapData, harbourPos, 12))
  474. {
  475. mapData.GetTile(harbourPos.x, harbourPos.y).featureType = FeatureType.Harbour;
  476. harboursCreated++;
  477. Debug.Log($"Created lake harbour at {harbourPos} (lake size: {lakeSize})");
  478. }
  479. else if (harbourPos != Vector2Int.zero)
  480. {
  481. Debug.Log($"Skipped lake harbour at {harbourPos} - too close to existing harbour");
  482. }
  483. }
  484. }
  485. }
  486. }
  487. Debug.Log($"Generated {harboursCreated} lake harbours");
  488. }
  489. private int MeasureLakeSize(MapData mapData, int startX, int startY, HashSet<Vector2Int> processedAreas)
  490. {
  491. // Simple flood fill to measure lake size
  492. Queue<Vector2Int> toCheck = new Queue<Vector2Int>();
  493. HashSet<Vector2Int> lakeArea = new HashSet<Vector2Int>();
  494. toCheck.Enqueue(new Vector2Int(startX, startY));
  495. while (toCheck.Count > 0 && lakeArea.Count < 100) // Limit search to prevent performance issues
  496. {
  497. Vector2Int current = toCheck.Dequeue();
  498. if (lakeArea.Contains(current) || !mapData.IsValidPosition(current.x, current.y))
  499. continue;
  500. MapTile tile = mapData.GetTile(current.x, current.y);
  501. if (tile.terrainType != TerrainType.Lake)
  502. continue;
  503. lakeArea.Add(current);
  504. processedAreas.Add(current);
  505. // Check adjacent tiles
  506. Vector2Int[] directions = {
  507. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  508. new Vector2Int(0, 1), new Vector2Int(0, -1)
  509. };
  510. foreach (var dir in directions)
  511. {
  512. Vector2Int next = current + dir;
  513. if (!lakeArea.Contains(next))
  514. {
  515. toCheck.Enqueue(next);
  516. }
  517. }
  518. }
  519. return lakeArea.Count;
  520. }
  521. private Vector2Int FindLakeHarbourPosition(MapData mapData, int lakeX, int lakeY)
  522. {
  523. // Find a good position for a harbour near this lake
  524. for (int range = 1; range <= 8; range++)
  525. {
  526. for (int x = lakeX - range; x <= lakeX + range; x++)
  527. {
  528. for (int y = lakeY - range; y <= lakeY + range; y++)
  529. {
  530. if (mapData.IsValidPosition(x, y))
  531. {
  532. MapTile tile = mapData.GetTile(x, y);
  533. // Look for shore tiles or land tiles adjacent to the lake
  534. if ((tile.terrainType == TerrainType.Shore ||
  535. tile.terrainType == TerrainType.Plains ||
  536. tile.terrainType == TerrainType.Forest) &&
  537. tile.featureType == FeatureType.None &&
  538. IsAdjacentToLake(mapData, x, y))
  539. {
  540. // Convert to shore if it isn't already
  541. if (tile.terrainType != TerrainType.Shore)
  542. {
  543. tile.terrainType = TerrainType.Shore;
  544. }
  545. return new Vector2Int(x, y);
  546. }
  547. }
  548. }
  549. }
  550. }
  551. return Vector2Int.zero;
  552. }
  553. private bool HasNearbyHarbour(MapData mapData, Vector2Int position, int checkRadius)
  554. {
  555. // Check if there's already a harbour within the specified radius
  556. for (int x = position.x - checkRadius; x <= position.x + checkRadius; x++)
  557. {
  558. for (int y = position.y - checkRadius; y <= position.y + checkRadius; y++)
  559. {
  560. if (mapData.IsValidPosition(x, y))
  561. {
  562. MapTile tile = mapData.GetTile(x, y);
  563. if (tile.featureType == FeatureType.Harbour)
  564. {
  565. float distance = Vector2.Distance(
  566. new Vector2(position.x, position.y),
  567. new Vector2(x, y)
  568. );
  569. if (distance <= checkRadius)
  570. {
  571. return true;
  572. }
  573. }
  574. }
  575. }
  576. }
  577. return false;
  578. }
  579. private Vector2Int FindNearestHarbour(MapData mapData, Vector2Int position, int searchRadius)
  580. {
  581. // Find the nearest existing harbour within the search radius
  582. Vector2Int nearestHarbour = Vector2Int.zero;
  583. float nearestDistance = float.MaxValue;
  584. for (int x = position.x - searchRadius; x <= position.x + searchRadius; x++)
  585. {
  586. for (int y = position.y - searchRadius; y <= position.y + searchRadius; y++)
  587. {
  588. if (mapData.IsValidPosition(x, y))
  589. {
  590. MapTile tile = mapData.GetTile(x, y);
  591. if (tile.featureType == FeatureType.Harbour)
  592. {
  593. float distance = Vector2.Distance(
  594. new Vector2(position.x, position.y),
  595. new Vector2(x, y)
  596. );
  597. if (distance <= searchRadius && distance < nearestDistance)
  598. {
  599. nearestDistance = distance;
  600. nearestHarbour = new Vector2Int(x, y);
  601. }
  602. }
  603. }
  604. }
  605. }
  606. return nearestHarbour;
  607. }
  608. private bool IsAdjacentToLake(MapData mapData, int x, int y)
  609. {
  610. Vector2Int[] directions = {
  611. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  612. new Vector2Int(0, 1), new Vector2Int(0, -1)
  613. };
  614. foreach (var dir in directions)
  615. {
  616. int checkX = x + dir.x;
  617. int checkY = y + dir.y;
  618. if (mapData.IsValidPosition(checkX, checkY))
  619. {
  620. MapTile tile = mapData.GetTile(checkX, checkY);
  621. if (tile.terrainType == TerrainType.Lake)
  622. {
  623. return true;
  624. }
  625. }
  626. }
  627. return false;
  628. }
  629. private List<Vector2Int> FindPath(MapData mapData, Vector2Int start, Vector2Int end)
  630. {
  631. // Simple pathfinding with some randomness for variation and water avoidance
  632. List<Vector2Int> path = new List<Vector2Int>();
  633. Vector2Int current = start;
  634. HashSet<Vector2Int> visitedPositions = new HashSet<Vector2Int>();
  635. int maxIterations = mapData.Width * mapData.Height; // Prevent infinite loops
  636. int iterations = 0;
  637. while (current != end && iterations < maxIterations)
  638. {
  639. iterations++;
  640. // Check if we're stuck in a loop
  641. if (visitedPositions.Contains(current))
  642. {
  643. Debug.LogWarning($"Pathfinding detected loop at {current}, breaking to prevent infinite loop");
  644. break;
  645. }
  646. visitedPositions.Add(current);
  647. path.Add(current);
  648. Vector2Int direction = new Vector2Int(
  649. end.x > current.x ? 1 : (end.x < current.x ? -1 : 0),
  650. end.y > current.y ? 1 : (end.y < current.y ? -1 : 0)
  651. );
  652. // Add some randomness to avoid perfectly straight roads
  653. if (Random.value < 0.3f)
  654. {
  655. if (Random.value < 0.5f)
  656. direction.x = 0;
  657. else
  658. direction.y = 0;
  659. }
  660. Vector2Int nextPos = current + direction;
  661. // Check if next position would be water and try to avoid long water crossings
  662. if (mapData.IsValidPosition(nextPos.x, nextPos.y))
  663. {
  664. MapTile nextTile = mapData.GetTile(nextPos.x, nextPos.y);
  665. // If next tile is water, try to find alternative route first
  666. if (nextTile.IsWater())
  667. {
  668. Vector2Int alternativePos = FindAlternativeRoute(mapData, current, end, visitedPositions);
  669. if (alternativePos != current && !visitedPositions.Contains(alternativePos))
  670. {
  671. nextPos = alternativePos;
  672. }
  673. }
  674. }
  675. // Ensure we're making progress and not going to visited positions
  676. if (!mapData.IsValidPosition(nextPos.x, nextPos.y) || visitedPositions.Contains(nextPos))
  677. {
  678. // Find any valid adjacent position that moves toward target
  679. Vector2Int fallbackPos = FindFallbackMove(mapData, current, end, visitedPositions);
  680. if (fallbackPos != current)
  681. {
  682. nextPos = fallbackPos;
  683. }
  684. else
  685. {
  686. Debug.LogWarning($"Pathfinding stuck at {current}, unable to continue to {end}");
  687. break;
  688. }
  689. }
  690. current = nextPos;
  691. }
  692. if (iterations >= maxIterations)
  693. {
  694. Debug.LogError($"Pathfinding exceeded maximum iterations ({maxIterations}), terminating path from {start} to {end}");
  695. }
  696. // Only add end if we actually reached it
  697. if (current == end)
  698. {
  699. path.Add(end);
  700. }
  701. return path;
  702. }
  703. private Vector2Int FindAlternativeRoute(MapData mapData, Vector2Int current, Vector2Int end, HashSet<Vector2Int> visitedPositions = null)
  704. {
  705. // Try to find a non-water adjacent tile that still moves toward the destination
  706. Vector2Int[] directions = {
  707. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  708. new Vector2Int(0, 1), new Vector2Int(0, -1),
  709. new Vector2Int(1, 1), new Vector2Int(-1, -1),
  710. new Vector2Int(1, -1), new Vector2Int(-1, 1)
  711. };
  712. Vector2Int bestDirection = Vector2Int.zero;
  713. float bestScore = float.MaxValue;
  714. foreach (var dir in directions)
  715. {
  716. Vector2Int candidate = current + dir;
  717. if (mapData.IsValidPosition(candidate.x, candidate.y))
  718. {
  719. // Skip if we've already visited this position
  720. if (visitedPositions != null && visitedPositions.Contains(candidate))
  721. continue;
  722. MapTile candidateTile = mapData.GetTile(candidate.x, candidate.y);
  723. // Prefer non-water tiles
  724. if (!candidateTile.IsWater())
  725. {
  726. // Calculate distance to end and prefer directions that move toward target
  727. float distanceToEnd = Vector2.Distance(new Vector2(candidate.x, candidate.y), new Vector2(end.x, end.y));
  728. float currentDistanceToEnd = Vector2.Distance(new Vector2(current.x, current.y), new Vector2(end.x, end.y));
  729. // Only consider if it moves us closer or maintains distance
  730. if (distanceToEnd <= currentDistanceToEnd + 1f && distanceToEnd < bestScore)
  731. {
  732. bestScore = distanceToEnd;
  733. bestDirection = dir;
  734. }
  735. }
  736. }
  737. }
  738. return bestDirection != Vector2Int.zero ? current + bestDirection : current;
  739. }
  740. private Vector2Int FindFallbackMove(MapData mapData, Vector2Int current, Vector2Int end, HashSet<Vector2Int> visitedPositions)
  741. {
  742. // Find any valid adjacent position that hasn't been visited and moves toward target
  743. Vector2Int[] directions = {
  744. new Vector2Int(1, 0), new Vector2Int(-1, 0),
  745. new Vector2Int(0, 1), new Vector2Int(0, -1),
  746. new Vector2Int(1, 1), new Vector2Int(-1, -1),
  747. new Vector2Int(1, -1), new Vector2Int(-1, 1)
  748. };
  749. Vector2Int bestMove = current;
  750. float bestDistance = float.MaxValue;
  751. foreach (var dir in directions)
  752. {
  753. Vector2Int candidate = current + dir;
  754. if (mapData.IsValidPosition(candidate.x, candidate.y) && !visitedPositions.Contains(candidate))
  755. {
  756. float distanceToEnd = Vector2.Distance(new Vector2(candidate.x, candidate.y), new Vector2(end.x, end.y));
  757. if (distanceToEnd < bestDistance)
  758. {
  759. bestDistance = distanceToEnd;
  760. bestMove = candidate;
  761. }
  762. }
  763. }
  764. return bestMove;
  765. }
  766. }