ExpansionManager.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. public class ExpansionManager
  4. {
  5. private MapMaker2 mapMaker;
  6. private NoiseGenerator noiseGenerator;
  7. private System.Random random;
  8. public ExpansionManager(MapMaker2 mapMaker)
  9. {
  10. this.mapMaker = mapMaker;
  11. this.noiseGenerator = new NoiseGenerator();
  12. this.random = new System.Random(mapMaker.seed);
  13. }
  14. public bool ShouldExpand(Vector2 playerPosition, MapData mapData)
  15. {
  16. float distanceThreshold = mapMaker.playerDistanceThreshold;
  17. // Check distance to each edge
  18. float distToLeft = playerPosition.x;
  19. float distToRight = mapData.Width - playerPosition.x;
  20. float distToTop = mapData.Height - playerPosition.y;
  21. float distToBottom = playerPosition.y;
  22. bool shouldExpand = distToLeft < distanceThreshold ||
  23. distToRight < distanceThreshold ||
  24. distToTop < distanceThreshold ||
  25. distToBottom < distanceThreshold;
  26. if (shouldExpand)
  27. {
  28. Debug.Log($"Expansion needed! Threshold: {distanceThreshold}");
  29. Debug.Log($"Distances - Left: {distToLeft}, Right: {distToRight}, Top: {distToTop}, Bottom: {distToBottom}");
  30. }
  31. return shouldExpand;
  32. }
  33. public void ExpandMap(MapData mapData, Vector2 playerPosition, int expansionSize)
  34. {
  35. float distanceThreshold = mapMaker.playerDistanceThreshold;
  36. // Determine expansion directions
  37. bool expandLeft = playerPosition.x < distanceThreshold;
  38. bool expandRight = mapData.Width - playerPosition.x < distanceThreshold;
  39. bool expandUp = mapData.Height - playerPosition.y < distanceThreshold;
  40. bool expandDown = playerPosition.y < distanceThreshold;
  41. // Calculate new dimensions
  42. int leftExpansion = expandLeft ? expansionSize : 0;
  43. int rightExpansion = expandRight ? expansionSize : 0;
  44. int upExpansion = expandUp ? expansionSize : 0;
  45. int downExpansion = expandDown ? expansionSize : 0;
  46. int newWidth = mapData.Width + leftExpansion + rightExpansion;
  47. int newHeight = mapData.Height + upExpansion + downExpansion;
  48. // Store old dimensions and expansion info
  49. ExpansionInfo expansionInfo = new ExpansionInfo
  50. {
  51. oldWidth = mapData.Width,
  52. oldHeight = mapData.Height,
  53. newWidth = newWidth,
  54. newHeight = newHeight,
  55. leftExpansion = leftExpansion,
  56. rightExpansion = rightExpansion,
  57. upExpansion = upExpansion,
  58. downExpansion = downExpansion
  59. };
  60. // Expand the map with offset handling
  61. ExpandMapWithOffset(mapData, expansionInfo);
  62. // Generate logical terrain and features for new areas using enhanced system
  63. GenerateEnhancedExpansion(mapData, expansionInfo);
  64. Debug.Log($"Map expanded from {expansionInfo.oldWidth}x{expansionInfo.oldHeight} to {newWidth}x{newHeight}");
  65. Debug.Log($"Expansion directions - Left: {leftExpansion}, Right: {rightExpansion}, Up: {upExpansion}, Down: {downExpansion}");
  66. }
  67. private struct ExpansionInfo
  68. {
  69. public int oldWidth, oldHeight;
  70. public int newWidth, newHeight;
  71. public int leftExpansion, rightExpansion;
  72. public int upExpansion, downExpansion;
  73. }
  74. private void ExpandMapWithOffset(MapData mapData, ExpansionInfo info)
  75. {
  76. mapData.ExpandMapWithOffset(info.newWidth, info.newHeight, info.leftExpansion, info.downExpansion);
  77. }
  78. private void GenerateEnhancedExpansion(MapData mapData, ExpansionInfo info)
  79. {
  80. // Enhanced multi-pass terrain generation for natural-looking results
  81. GenerateNaturalTerrain(mapData, info);
  82. SmoothTerrainTransitions(mapData, info);
  83. GenerateNaturalFeatures(mapData, info);
  84. GenerateSettlementsAndRoads(mapData, info);
  85. }
  86. private void GenerateNaturalTerrain(MapData mapData, ExpansionInfo info)
  87. {
  88. // Generate each expansion region with proper biome continuity
  89. if (info.leftExpansion > 0)
  90. GenerateTerrainRegion(mapData, 0, 0, info.leftExpansion, info.newHeight, ExpansionDirection.Left, info);
  91. if (info.rightExpansion > 0)
  92. {
  93. int startX = info.leftExpansion + info.oldWidth;
  94. GenerateTerrainRegion(mapData, startX, 0, info.rightExpansion, info.newHeight, ExpansionDirection.Right, info);
  95. }
  96. if (info.downExpansion > 0)
  97. GenerateTerrainRegion(mapData, info.leftExpansion, 0, info.oldWidth, info.downExpansion, ExpansionDirection.Down, info);
  98. if (info.upExpansion > 0)
  99. {
  100. int startY = info.downExpansion + info.oldHeight;
  101. GenerateTerrainRegion(mapData, info.leftExpansion, startY, info.oldWidth, info.upExpansion, ExpansionDirection.Up, info);
  102. }
  103. }
  104. private enum ExpansionDirection { Left, Right, Up, Down }
  105. private void GenerateTerrainRegion(MapData mapData, int startX, int startY, int width, int height, ExpansionDirection direction, ExpansionInfo info)
  106. {
  107. for (int x = startX; x < startX + width; x++)
  108. {
  109. for (int y = startY; y < startY + height; y++)
  110. {
  111. GenerateEnhancedTerrain(mapData, x, y, direction, info);
  112. }
  113. }
  114. }
  115. private void GenerateEnhancedTerrain(MapData mapData, int x, int y, ExpansionDirection direction, ExpansionInfo info)
  116. {
  117. MapTile tile = mapData.GetTile(x, y);
  118. if (tile == null) return;
  119. // Get reference context from existing terrain
  120. List<MapTile> referenceTiles = GetExtendedReferenceTiles(mapData, x, y, direction, info);
  121. // Generate realistic height with proper continuity
  122. float height = GenerateRealisticHeight(x, y, referenceTiles, direction, info);
  123. tile.height = height;
  124. // Determine terrain with natural biome rules
  125. TerrainType terrainType = DetermineRealisticTerrain(referenceTiles, height, x, y, direction, info);
  126. tile.terrainType = terrainType;
  127. tile.isWalkable = terrainType != TerrainType.Mountain && terrainType != TerrainType.Ocean;
  128. }
  129. private List<MapTile> GetExtendedReferenceTiles(MapData mapData, int x, int y, ExpansionDirection direction, ExpansionInfo info)
  130. {
  131. List<MapTile> referenceTiles = new List<MapTile>();
  132. int range = 4; // Wider reference for better continuity
  133. switch (direction)
  134. {
  135. case ExpansionDirection.Left:
  136. for (int refY = y - range; refY <= y + range; refY++)
  137. {
  138. int refX = info.leftExpansion;
  139. if (mapData.IsValidPosition(refX, refY))
  140. referenceTiles.Add(mapData.GetTile(refX, refY));
  141. }
  142. break;
  143. case ExpansionDirection.Right:
  144. for (int refY = y - range; refY <= y + range; refY++)
  145. {
  146. int refX = info.leftExpansion + info.oldWidth - 1;
  147. if (mapData.IsValidPosition(refX, refY))
  148. referenceTiles.Add(mapData.GetTile(refX, refY));
  149. }
  150. break;
  151. case ExpansionDirection.Up:
  152. for (int refX = x - range; refX <= x + range; refX++)
  153. {
  154. int refY = info.downExpansion + info.oldHeight - 1;
  155. if (mapData.IsValidPosition(refX, refY))
  156. referenceTiles.Add(mapData.GetTile(refX, refY));
  157. }
  158. break;
  159. case ExpansionDirection.Down:
  160. for (int refX = x - range; refX <= x + range; refX++)
  161. {
  162. int refY = info.downExpansion;
  163. if (mapData.IsValidPosition(refX, refY))
  164. referenceTiles.Add(mapData.GetTile(refX, refY));
  165. }
  166. break;
  167. }
  168. return referenceTiles;
  169. }
  170. private float GenerateRealisticHeight(int x, int y, List<MapTile> referenceTiles, ExpansionDirection direction, ExpansionInfo info)
  171. {
  172. float distanceFromBoundary = GetDistanceFromBoundary(x, y, direction, info);
  173. // For very close tiles, copy heights directly
  174. if (distanceFromBoundary < 6f)
  175. {
  176. Vector2Int sourcePos = GetCorrespondingOriginalTile(x, y, direction, info);
  177. if (IsInOriginalArea(sourcePos.x, sourcePos.y, info))
  178. {
  179. MapTile sourceTile = mapMaker.GetMapData().GetTile(sourcePos.x, sourcePos.y);
  180. if (sourceTile != null)
  181. {
  182. // Add very small noise to avoid perfect repetition
  183. float noise = noiseGenerator.GetNoise(x, y, 0.1f) * 0.05f;
  184. return sourceTile.height + noise;
  185. }
  186. }
  187. }
  188. // For farther tiles, use blended approach
  189. float height = 0f;
  190. height += noiseGenerator.GetNoise(x, y, 0.02f) * 0.6f; // Large landforms
  191. height += noiseGenerator.GetNoise(x, y, 0.06f) * 0.25f; // Medium features
  192. height += noiseGenerator.GetNoise(x, y, 0.12f) * 0.1f; // Small details
  193. height += noiseGenerator.GetNoise(x, y, 0.25f) * 0.05f; // Fine noise
  194. // Strong blending with reference heights
  195. if (referenceTiles.Count > 0)
  196. {
  197. float refAverage = 0f;
  198. foreach (var tile in referenceTiles)
  199. refAverage += tile.height;
  200. refAverage /= referenceTiles.Count;
  201. float blendFactor = Mathf.Lerp(0.8f, 0.2f, Mathf.Clamp01(distanceFromBoundary / 15f));
  202. height = Mathf.Lerp(height, refAverage, blendFactor);
  203. }
  204. return height;
  205. }
  206. private float GetDistanceFromBoundary(int x, int y, ExpansionDirection direction, ExpansionInfo info)
  207. {
  208. switch (direction)
  209. {
  210. case ExpansionDirection.Left:
  211. return x;
  212. case ExpansionDirection.Right:
  213. return (info.newWidth - 1) - x;
  214. case ExpansionDirection.Down:
  215. return y;
  216. case ExpansionDirection.Up:
  217. return (info.newHeight - 1) - y;
  218. default:
  219. return 10f;
  220. }
  221. }
  222. private TerrainType DetermineRealisticTerrain(List<MapTile> referenceTiles, float height, int x, int y, ExpansionDirection direction, ExpansionInfo info)
  223. {
  224. // Calculate distance from boundary for continuity strength
  225. float distanceFromBoundary = GetDistanceFromBoundary(x, y, direction, info);
  226. // For the first 8 tiles from boundary, use direct terrain copying
  227. if (distanceFromBoundary < 8f)
  228. {
  229. return CopyNearestTerrain(x, y, direction, info);
  230. }
  231. else
  232. {
  233. // For further tiles, still use heavy continuity
  234. Dictionary<TerrainType, float> biomeInfluence = AnalyzeBiomeInfluence(referenceTiles);
  235. TerrainType dominantBiome = GetDominantBiome(biomeInfluence);
  236. return GenerateContinuousTerrain(dominantBiome, height, x, y);
  237. }
  238. }
  239. private TerrainType CopyNearestTerrain(int x, int y, ExpansionDirection direction, ExpansionInfo info)
  240. {
  241. // Find the exact corresponding tile in the original map and copy its terrain
  242. Vector2Int sourcePos = GetCorrespondingOriginalTile(x, y, direction, info);
  243. if (IsInOriginalArea(sourcePos.x, sourcePos.y, info))
  244. {
  245. MapTile sourceTile = mapMaker.GetMapData().GetTile(sourcePos.x, sourcePos.y);
  246. if (sourceTile != null)
  247. {
  248. return sourceTile.terrainType;
  249. }
  250. }
  251. // Fallback to nearest boundary tile
  252. return GetNearestBoundaryTerrain(x, y, direction, info);
  253. }
  254. private Vector2Int GetCorrespondingOriginalTile(int x, int y, ExpansionDirection direction, ExpansionInfo info)
  255. {
  256. switch (direction)
  257. {
  258. case ExpansionDirection.Left:
  259. // Mirror from the left boundary
  260. int offsetFromLeft = x;
  261. return new Vector2Int(info.leftExpansion + offsetFromLeft, y);
  262. case ExpansionDirection.Right:
  263. // Mirror from the right boundary
  264. int offsetFromRight = x - (info.leftExpansion + info.oldWidth);
  265. return new Vector2Int(info.leftExpansion + info.oldWidth - 1 - offsetFromRight, y);
  266. case ExpansionDirection.Down:
  267. // Mirror from the bottom boundary
  268. int offsetFromBottom = y;
  269. return new Vector2Int(x, info.downExpansion + offsetFromBottom);
  270. case ExpansionDirection.Up:
  271. // Mirror from the top boundary
  272. int offsetFromTop = y - (info.downExpansion + info.oldHeight);
  273. return new Vector2Int(x, info.downExpansion + info.oldHeight - 1 - offsetFromTop);
  274. default:
  275. return new Vector2Int(x, y);
  276. }
  277. }
  278. private TerrainType GetNearestBoundaryTerrain(int x, int y, ExpansionDirection direction, ExpansionInfo info)
  279. {
  280. Vector2Int boundaryPos = Vector2Int.zero;
  281. switch (direction)
  282. {
  283. case ExpansionDirection.Left:
  284. boundaryPos = new Vector2Int(info.leftExpansion, y);
  285. break;
  286. case ExpansionDirection.Right:
  287. boundaryPos = new Vector2Int(info.leftExpansion + info.oldWidth - 1, y);
  288. break;
  289. case ExpansionDirection.Down:
  290. boundaryPos = new Vector2Int(x, info.downExpansion);
  291. break;
  292. case ExpansionDirection.Up:
  293. boundaryPos = new Vector2Int(x, info.downExpansion + info.oldHeight - 1);
  294. break;
  295. }
  296. if (IsInOriginalArea(boundaryPos.x, boundaryPos.y, info))
  297. {
  298. MapTile boundaryTile = mapMaker.GetMapData().GetTile(boundaryPos.x, boundaryPos.y);
  299. if (boundaryTile != null)
  300. return boundaryTile.terrainType;
  301. }
  302. return TerrainType.Plains;
  303. }
  304. private Dictionary<TerrainType, float> AnalyzeBiomeInfluence(List<MapTile> referenceTiles)
  305. {
  306. Dictionary<TerrainType, float> influence = new Dictionary<TerrainType, float>();
  307. foreach (var tile in referenceTiles)
  308. {
  309. float weight = GetBiomeWeight(tile.terrainType);
  310. if (influence.ContainsKey(tile.terrainType))
  311. influence[tile.terrainType] += weight;
  312. else
  313. influence[tile.terrainType] = weight;
  314. }
  315. return influence;
  316. }
  317. private float GetBiomeWeight(TerrainType terrain)
  318. {
  319. // Some terrain types are more "influential" for expansion
  320. switch (terrain)
  321. {
  322. case TerrainType.Ocean: return 4.0f; // Much stronger ocean influence
  323. case TerrainType.Mountain: return 2.5f;
  324. case TerrainType.Lake: return 2.0f; // Stronger water influence
  325. case TerrainType.Forest: return 1.5f;
  326. case TerrainType.Shore: return 1.5f; // Stronger shore influence
  327. case TerrainType.River: return 1.2f;
  328. default: return 0.8f;
  329. }
  330. }
  331. private TerrainType GetDominantBiome(Dictionary<TerrainType, float> influence)
  332. {
  333. if (influence.Count == 0) return TerrainType.Plains;
  334. TerrainType dominant = TerrainType.Plains;
  335. float maxInfluence = 0f;
  336. foreach (var kvp in influence)
  337. {
  338. if (kvp.Value > maxInfluence)
  339. {
  340. maxInfluence = kvp.Value;
  341. dominant = kvp.Key;
  342. }
  343. }
  344. return dominant;
  345. }
  346. private TerrainType GenerateContinuousTerrain(TerrainType dominantBiome, float height, int x, int y)
  347. {
  348. switch (dominantBiome)
  349. {
  350. case TerrainType.Mountain:
  351. return TransitionMountain(height, x, y);
  352. case TerrainType.Forest:
  353. return TransitionForest(height, x, y);
  354. case TerrainType.Ocean:
  355. return TransitionOcean(height, x, y);
  356. case TerrainType.Lake:
  357. return TransitionLake(height, x, y);
  358. case TerrainType.Shore:
  359. return TransitionShore(height, x, y);
  360. case TerrainType.River:
  361. return TransitionRiver(height, x, y);
  362. default:
  363. return TerrainType.Plains;
  364. }
  365. }
  366. private TerrainType TransitionMountain(float height, int x, int y)
  367. {
  368. // Mountains are very conservative
  369. if (height > 0.3f) // Lower threshold for mountain continuation
  370. return TerrainType.Mountain;
  371. else if (height > 0.0f) // Wide foothill zone
  372. return TerrainType.Forest; // Mountain foothills
  373. else
  374. return TerrainType.Plains;
  375. }
  376. private TerrainType TransitionForest(float height, int x, int y)
  377. {
  378. // Conservative forest generation
  379. if (height > 0.2f)
  380. return TerrainType.Mountain; // Higher areas become mountains
  381. else if (height > -0.1f) // Wide forest zone
  382. return TerrainType.Forest;
  383. else
  384. return TerrainType.Plains;
  385. }
  386. private TerrainType TransitionOcean(float height, int x, int y)
  387. {
  388. // Ocean is extremely conservative - almost always continues as ocean
  389. if (height < 0.0f) // Much more lenient for ocean continuation
  390. return TerrainType.Ocean;
  391. else if (height < 0.2f) // Very wide shore zone
  392. return TerrainType.Shore;
  393. else if (height < 0.4f)
  394. return TerrainType.Plains; // Coastal plains
  395. else
  396. return TerrainType.Forest;
  397. }
  398. private TerrainType TransitionLake(float height, int x, int y)
  399. {
  400. // Lakes are very conservative
  401. if (height < 0.0f) // Much more lenient for lake continuation
  402. return TerrainType.Lake;
  403. else if (height < 0.2f) // Wide shore zone
  404. return TerrainType.Shore;
  405. else
  406. return TerrainType.Plains;
  407. }
  408. private TerrainType TransitionShore(float height, int x, int y)
  409. {
  410. if (height < -0.1f) // More lenient transition to water
  411. return TerrainType.Ocean;
  412. else if (height < 0.4f) // Much wider shore zone
  413. return TerrainType.Shore;
  414. else
  415. return TerrainType.Plains;
  416. }
  417. private TerrainType TransitionRiver(float height, int x, int y)
  418. {
  419. // Rivers are conservative
  420. if (height < 0.0f)
  421. return TerrainType.River;
  422. else if (height < 0.1f)
  423. return TerrainType.Plains; // River plains
  424. else
  425. return TerrainType.Forest;
  426. }
  427. private TerrainType GenerateNaturalTerrainFromHeight(float height, int x, int y)
  428. {
  429. // Pure height-based generation without noise variation
  430. if (height > 0.6f) return TerrainType.Mountain;
  431. else if (height > 0.2f) return TerrainType.Forest;
  432. else if (height < -0.5f) return TerrainType.Ocean;
  433. else if (height < -0.2f) return TerrainType.Lake;
  434. else if (height < 0.0f) return TerrainType.Shore;
  435. else return TerrainType.Plains;
  436. }
  437. private void SmoothTerrainTransitions(MapData mapData, ExpansionInfo info)
  438. {
  439. // Much more aggressive smoothing for natural blending
  440. int smoothRadius = 8; // Increased from 5
  441. // Process each expansion boundary multiple times
  442. for (int pass = 0; pass < 3; pass++) // Multiple smoothing passes
  443. {
  444. if (info.leftExpansion > 0)
  445. SmoothBoundaryRegion(mapData, info.leftExpansion - smoothRadius, 0, smoothRadius * 2, info.newHeight);
  446. if (info.rightExpansion > 0)
  447. SmoothBoundaryRegion(mapData, info.leftExpansion + info.oldWidth - smoothRadius, 0, smoothRadius * 2, info.newHeight);
  448. if (info.upExpansion > 0)
  449. SmoothBoundaryRegion(mapData, 0, info.downExpansion + info.oldHeight - smoothRadius, info.newWidth, smoothRadius * 2);
  450. if (info.downExpansion > 0)
  451. SmoothBoundaryRegion(mapData, 0, info.downExpansion - smoothRadius, info.newWidth, smoothRadius * 2);
  452. }
  453. }
  454. private void SmoothBoundaryRegion(MapData mapData, int startX, int startY, int width, int height)
  455. {
  456. for (int x = startX; x < startX + width; x++)
  457. {
  458. for (int y = startY; y < startY + height; y++)
  459. {
  460. if (!mapData.IsValidPosition(x, y)) continue;
  461. SmoothTileWithNeighbors(mapData, x, y);
  462. }
  463. }
  464. }
  465. private void SmoothTileWithNeighbors(MapData mapData, int x, int y)
  466. {
  467. MapTile centerTile = mapData.GetTile(x, y);
  468. Dictionary<TerrainType, int> neighborTerrain = GetNeighborTerrain(mapData, x, y, 3); // Larger radius
  469. // If this tile is very isolated, consider changing it
  470. if (!neighborTerrain.ContainsKey(centerTile.terrainType) ||
  471. neighborTerrain[centerTile.terrainType] < 3) // Stricter isolation check
  472. {
  473. TerrainType newTerrain = GetMostCommonNeighborTerrain(neighborTerrain);
  474. if (IsValidTerrainTransition(centerTile.terrainType, newTerrain))
  475. {
  476. centerTile.terrainType = newTerrain;
  477. // Also smooth the height towards neighbors
  478. float avgHeight = GetAverageNeighborHeight(mapData, x, y, 2);
  479. centerTile.height = Mathf.Lerp(centerTile.height, avgHeight, 0.5f);
  480. }
  481. }
  482. }
  483. private Dictionary<TerrainType, int> GetNeighborTerrain(MapData mapData, int centerX, int centerY, int radius)
  484. {
  485. Dictionary<TerrainType, int> terrain = new Dictionary<TerrainType, int>();
  486. for (int x = centerX - radius; x <= centerX + radius; x++)
  487. {
  488. for (int y = centerY - radius; y <= centerY + radius; y++)
  489. {
  490. if (x == centerX && y == centerY) continue;
  491. if (!mapData.IsValidPosition(x, y)) continue;
  492. TerrainType terrainType = mapData.GetTile(x, y).terrainType;
  493. terrain[terrainType] = terrain.ContainsKey(terrainType) ? terrain[terrainType] + 1 : 1;
  494. }
  495. }
  496. return terrain;
  497. }
  498. private float GetAverageNeighborHeight(MapData mapData, int centerX, int centerY, int radius)
  499. {
  500. float totalHeight = 0f;
  501. int count = 0;
  502. for (int x = centerX - radius; x <= centerX + radius; x++)
  503. {
  504. for (int y = centerY - radius; y <= centerY + radius; y++)
  505. {
  506. if (x == centerX && y == centerY) continue;
  507. if (!mapData.IsValidPosition(x, y)) continue;
  508. totalHeight += mapData.GetTile(x, y).height;
  509. count++;
  510. }
  511. }
  512. return count > 0 ? totalHeight / count : 0f;
  513. }
  514. private TerrainType GetMostCommonNeighborTerrain(Dictionary<TerrainType, int> terrain)
  515. {
  516. TerrainType mostCommon = TerrainType.Plains;
  517. int maxCount = 0;
  518. foreach (var kvp in terrain)
  519. {
  520. if (kvp.Value > maxCount)
  521. {
  522. maxCount = kvp.Value;
  523. mostCommon = kvp.Key;
  524. }
  525. }
  526. return mostCommon;
  527. }
  528. private bool IsValidTerrainTransition(TerrainType from, TerrainType to)
  529. {
  530. // Some terrain transitions don't make sense (e.g., ocean to mountain directly)
  531. if (from == TerrainType.Ocean && to == TerrainType.Mountain) return false;
  532. if (from == TerrainType.Mountain && to == TerrainType.Ocean) return false;
  533. return true;
  534. }
  535. private void GenerateNaturalFeatures(MapData mapData, ExpansionInfo info)
  536. {
  537. // Extend rivers naturally
  538. ExtendRiversIntoExpansion(mapData, info);
  539. // Create natural water bodies
  540. GenerateNaturalLakes(mapData, info);
  541. }
  542. private void ExtendRiversIntoExpansion(MapData mapData, ExpansionInfo info)
  543. {
  544. // Find rivers at boundaries and extend them naturally
  545. List<Vector2Int> riverEnds = FindRiversAtBoundaries(mapData, info);
  546. foreach (var riverEnd in riverEnds)
  547. {
  548. ExtendRiverNaturally(mapData, riverEnd, info);
  549. }
  550. }
  551. private List<Vector2Int> FindRiversAtBoundaries(MapData mapData, ExpansionInfo info)
  552. {
  553. List<Vector2Int> riverPoints = new List<Vector2Int>();
  554. // Check boundaries for rivers
  555. CheckBoundaryForRivers(mapData, info.leftExpansion, 0, 1, info.newHeight, riverPoints);
  556. CheckBoundaryForRivers(mapData, info.leftExpansion + info.oldWidth - 1, 0, 1, info.newHeight, riverPoints);
  557. return riverPoints;
  558. }
  559. private void CheckBoundaryForRivers(MapData mapData, int startX, int startY, int width, int height, List<Vector2Int> riverPoints)
  560. {
  561. for (int x = startX; x < startX + width; x++)
  562. {
  563. for (int y = startY; y < startY + height; y++)
  564. {
  565. if (mapData.IsValidPosition(x, y))
  566. {
  567. MapTile tile = mapData.GetTile(x, y);
  568. if (tile.terrainType == TerrainType.River)
  569. {
  570. riverPoints.Add(new Vector2Int(x, y));
  571. }
  572. }
  573. }
  574. }
  575. }
  576. private void ExtendRiverNaturally(MapData mapData, Vector2Int start, ExpansionInfo info)
  577. {
  578. Vector2Int current = start;
  579. int maxLength = 25;
  580. for (int i = 0; i < maxLength; i++)
  581. {
  582. Vector2Int next = FindBestRiverDirection(mapData, current);
  583. if (next == current) break;
  584. MapTile nextTile = mapData.GetTile(next.x, next.y);
  585. if (nextTile.terrainType == TerrainType.Plains || nextTile.terrainType == TerrainType.Shore)
  586. {
  587. nextTile.terrainType = TerrainType.River;
  588. current = next;
  589. }
  590. else
  591. {
  592. break;
  593. }
  594. }
  595. }
  596. private Vector2Int FindBestRiverDirection(MapData mapData, Vector2Int current)
  597. {
  598. Vector2Int[] directions = {
  599. new Vector2Int(0, 1), new Vector2Int(1, 0), new Vector2Int(0, -1), new Vector2Int(-1, 0),
  600. new Vector2Int(1, 1), new Vector2Int(-1, 1), new Vector2Int(1, -1), new Vector2Int(-1, -1)
  601. };
  602. Vector2Int best = current;
  603. float bestScore = float.MinValue;
  604. foreach (var dir in directions)
  605. {
  606. Vector2Int candidate = current + dir;
  607. if (mapData.IsValidPosition(candidate.x, candidate.y))
  608. {
  609. float score = EvaluateRiverDirection(mapData, candidate);
  610. if (score > bestScore)
  611. {
  612. bestScore = score;
  613. best = candidate;
  614. }
  615. }
  616. }
  617. return best;
  618. }
  619. private float EvaluateRiverDirection(MapData mapData, Vector2Int position)
  620. {
  621. MapTile tile = mapData.GetTile(position.x, position.y);
  622. // Rivers prefer lower elevations and suitable terrain
  623. float score = -tile.height; // Lower is better
  624. if (tile.terrainType == TerrainType.Plains) score += 2f;
  625. else if (tile.terrainType == TerrainType.Shore) score += 1f;
  626. else if (tile.terrainType == TerrainType.Forest) score += 0.5f;
  627. else score -= 10f; // Avoid mountains, existing water, etc.
  628. return score;
  629. }
  630. private void GenerateNaturalLakes(MapData mapData, ExpansionInfo info)
  631. {
  632. // Create 1-2 small lakes in suitable locations
  633. for (int i = 0; i < 2; i++)
  634. {
  635. Vector2Int lakeCenter = FindSuitableLakeLocation(mapData, info);
  636. if (lakeCenter != Vector2Int.zero)
  637. {
  638. CreateNaturalLake(mapData, lakeCenter);
  639. }
  640. }
  641. }
  642. private Vector2Int FindSuitableLakeLocation(MapData mapData, ExpansionInfo info)
  643. {
  644. for (int attempts = 0; attempts < 30; attempts++)
  645. {
  646. int x = random.Next(0, info.newWidth);
  647. int y = random.Next(0, info.newHeight);
  648. // Only in expansion areas
  649. if (IsInOriginalArea(x, y, info)) continue;
  650. if (mapData.IsValidPosition(x, y))
  651. {
  652. MapTile tile = mapData.GetTile(x, y);
  653. if (tile.height < -0.1f && tile.terrainType == TerrainType.Plains)
  654. {
  655. return new Vector2Int(x, y);
  656. }
  657. }
  658. }
  659. return Vector2Int.zero;
  660. }
  661. private bool IsInOriginalArea(int x, int y, ExpansionInfo info)
  662. {
  663. return x >= info.leftExpansion && x < info.leftExpansion + info.oldWidth &&
  664. y >= info.downExpansion && y < info.downExpansion + info.oldHeight;
  665. }
  666. private void CreateNaturalLake(MapData mapData, Vector2Int center)
  667. {
  668. int radius = random.Next(3, 6);
  669. for (int x = center.x - radius; x <= center.x + radius; x++)
  670. {
  671. for (int y = center.y - radius; y <= center.y + radius; y++)
  672. {
  673. if (mapData.IsValidPosition(x, y))
  674. {
  675. float distance = Vector2Int.Distance(center, new Vector2Int(x, y));
  676. if (distance <= radius)
  677. {
  678. MapTile tile = mapData.GetTile(x, y);
  679. if (distance < radius - 1)
  680. {
  681. tile.terrainType = TerrainType.Lake;
  682. }
  683. else if (distance < radius)
  684. {
  685. tile.terrainType = TerrainType.Shore;
  686. }
  687. }
  688. }
  689. }
  690. }
  691. }
  692. private void GenerateSettlementsAndRoads(MapData mapData, ExpansionInfo info)
  693. {
  694. GenerateExpansionSettlements(mapData, info);
  695. ExtendRoadsToExpansion(mapData, info);
  696. GenerateExpansionHarbours(mapData, info);
  697. }
  698. // Settlement and road generation methods (enhanced versions)
  699. private void GenerateExpansionSettlements(MapData mapData, ExpansionInfo info)
  700. {
  701. int totalNewArea = (info.newWidth * info.newHeight) - (info.oldWidth * info.oldHeight);
  702. int newSettlementCount = Mathf.Max(1, totalNewArea / 3000); // Fewer, better placed settlements
  703. Debug.Log($"Generating {newSettlementCount} new settlements for {totalNewArea} new tiles");
  704. for (int i = 0; i < newSettlementCount; i++)
  705. {
  706. Vector2Int position = FindSuitableSettlementPosition(mapData, info);
  707. if (position != Vector2Int.zero)
  708. {
  709. CreateNewSettlement(mapData, position);
  710. Debug.Log($"Created new settlement at {position}");
  711. }
  712. }
  713. }
  714. private Vector2Int FindSuitableSettlementPosition(MapData mapData, ExpansionInfo info)
  715. {
  716. for (int attempts = 0; attempts < 100; attempts++)
  717. {
  718. int x = random.Next(0, info.newWidth);
  719. int y = random.Next(0, info.newHeight);
  720. if (IsInOriginalArea(x, y, info)) continue;
  721. MapTile tile = mapData.GetTile(x, y);
  722. if (tile != null && tile.terrainType == TerrainType.Plains && tile.featureType == FeatureType.None)
  723. {
  724. // Check distance from other settlements
  725. bool validLocation = true;
  726. var existingSettlements = mapData.GetTowns();
  727. existingSettlements.AddRange(mapData.GetVillages());
  728. foreach (var settlement in existingSettlements)
  729. {
  730. float distance = Vector2Int.Distance(new Vector2Int(x, y), settlement.position);
  731. if (distance < 25) // Increased minimum distance for better distribution
  732. {
  733. validLocation = false;
  734. break;
  735. }
  736. }
  737. if (validLocation)
  738. return new Vector2Int(x, y);
  739. }
  740. }
  741. return Vector2Int.zero;
  742. }
  743. private void CreateNewSettlement(MapData mapData, Vector2Int position)
  744. {
  745. MapTile tile = mapData.GetTile(position.x, position.y);
  746. SettlementType type = random.NextDouble() < 0.25 ? SettlementType.Town : SettlementType.Village;
  747. tile.featureType = type == SettlementType.Town ? FeatureType.Town : FeatureType.Village;
  748. string settlementName = GenerateSettlementName(type);
  749. tile.name = settlementName;
  750. Settlement settlement = new Settlement(settlementName, type, position);
  751. mapData.AddSettlement(settlement);
  752. }
  753. private string GenerateSettlementName(SettlementType type)
  754. {
  755. string[] townNames = { "Newborough", "Westhold", "Eastport", "Northgate", "Southhaven", "Riverside", "Hillcrest", "Stoneburg" };
  756. string[] villageNames = { "Millbrook", "Fairfield", "Greendale", "Oakwood", "Rosehaven", "Brightwater", "Meadowbrook", "Willowbend" };
  757. string[] names = type == SettlementType.Town ? townNames : villageNames;
  758. return names[random.Next(names.Length)];
  759. }
  760. private void ExtendRoadsToExpansion(MapData mapData, ExpansionInfo info)
  761. {
  762. var settlements = mapData.GetTowns();
  763. settlements.AddRange(mapData.GetVillages());
  764. foreach (var settlement in settlements)
  765. {
  766. bool nearExpansionEdge = IsNearExpansionEdge(settlement.position, info);
  767. if (nearExpansionEdge)
  768. {
  769. var nearestNewSettlement = FindNearestNewSettlement(mapData, settlement.position, info);
  770. if (nearestNewSettlement != Vector2Int.zero)
  771. {
  772. CreateRoadBetween(mapData, settlement.position, nearestNewSettlement);
  773. }
  774. }
  775. }
  776. }
  777. private bool IsNearExpansionEdge(Vector2Int position, ExpansionInfo info)
  778. {
  779. int edgeDistance = 20;
  780. bool nearLeft = info.leftExpansion > 0 && position.x < info.leftExpansion + edgeDistance;
  781. bool nearRight = info.rightExpansion > 0 && position.x > info.leftExpansion + info.oldWidth - edgeDistance;
  782. bool nearBottom = info.downExpansion > 0 && position.y < info.downExpansion + edgeDistance;
  783. bool nearTop = info.upExpansion > 0 && position.y > info.downExpansion + info.oldHeight - edgeDistance;
  784. return nearLeft || nearRight || nearBottom || nearTop;
  785. }
  786. private Vector2Int FindNearestNewSettlement(MapData mapData, Vector2Int fromPosition, ExpansionInfo info)
  787. {
  788. Vector2Int nearest = Vector2Int.zero;
  789. float nearestDistance = float.MaxValue;
  790. var settlements = mapData.GetTowns();
  791. settlements.AddRange(mapData.GetVillages());
  792. foreach (var settlement in settlements)
  793. {
  794. bool inNewArea = !IsInOriginalArea(settlement.position.x, settlement.position.y, info);
  795. if (inNewArea)
  796. {
  797. float distance = Vector2Int.Distance(fromPosition, settlement.position);
  798. if (distance < nearestDistance)
  799. {
  800. nearestDistance = distance;
  801. nearest = settlement.position;
  802. }
  803. }
  804. }
  805. return nearest;
  806. }
  807. private void CreateRoadBetween(MapData mapData, Vector2Int start, Vector2Int end)
  808. {
  809. Vector2Int current = start;
  810. while (current != end)
  811. {
  812. if (current.x < end.x) current.x++;
  813. else if (current.x > end.x) current.x--;
  814. else if (current.y < end.y) current.y++;
  815. else if (current.y > end.y) current.y--;
  816. if (mapData.IsValidPosition(current.x, current.y))
  817. {
  818. MapTile tile = mapData.GetTile(current.x, current.y);
  819. if (tile.featureType == FeatureType.None && tile.isWalkable)
  820. {
  821. tile.featureType = FeatureType.Road;
  822. }
  823. }
  824. }
  825. }
  826. private void GenerateExpansionHarbours(MapData mapData, ExpansionInfo info)
  827. {
  828. var settlements = mapData.GetTowns();
  829. settlements.AddRange(mapData.GetVillages());
  830. foreach (var settlement in settlements)
  831. {
  832. if (!IsInOriginalArea(settlement.position.x, settlement.position.y, info))
  833. {
  834. if (IsNearWater(mapData, settlement.position.x, settlement.position.y, 5))
  835. {
  836. Vector2Int harbourPos = FindHarbourPosition(mapData, settlement.position);
  837. if (harbourPos != Vector2Int.zero)
  838. {
  839. mapData.GetTile(harbourPos.x, harbourPos.y).featureType = FeatureType.Harbour;
  840. }
  841. }
  842. }
  843. }
  844. }
  845. private bool IsNearWater(MapData mapData, int x, int y, int range)
  846. {
  847. for (int dx = -range; dx <= range; dx++)
  848. {
  849. for (int dy = -range; dy <= range; dy++)
  850. {
  851. int checkX = x + dx;
  852. int checkY = y + dy;
  853. if (mapData.IsValidPosition(checkX, checkY))
  854. {
  855. MapTile tile = mapData.GetTile(checkX, checkY);
  856. if (tile.IsWater())
  857. return true;
  858. }
  859. }
  860. }
  861. return false;
  862. }
  863. private Vector2Int FindHarbourPosition(MapData mapData, Vector2Int settlementPos)
  864. {
  865. for (int range = 1; range <= 5; range++)
  866. {
  867. for (int dx = -range; dx <= range; dx++)
  868. {
  869. for (int dy = -range; dy <= range; dy++)
  870. {
  871. int x = settlementPos.x + dx;
  872. int y = settlementPos.y + dy;
  873. if (mapData.IsValidPosition(x, y))
  874. {
  875. MapTile tile = mapData.GetTile(x, y);
  876. if (tile.terrainType == TerrainType.Shore && tile.featureType == FeatureType.None)
  877. {
  878. return new Vector2Int(x, y);
  879. }
  880. }
  881. }
  882. }
  883. }
  884. return Vector2Int.zero;
  885. }
  886. }