Advertisement
Guest User

PerfectWorld3.lua

a guest
Jul 9th, 2013
119
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 99.09 KB | None | 0 0
  1. --------------------------------------------------------------------------------
  2. --PerfectWorld3.lua map script (c)2010 Rich Marinaccio
  3. --version 4
  4. --------------------------------------------------------------------------------
  5. --This map script uses various manipulations of Perlin noise to create
  6. --landforms, and generates climate based on a simplified model of geostrophic
  7. --and monsoon wind patterns. Rivers are generated along accurate drainage paths
  8. --governed by the elevation map used to create the landforms.
  9. --
  10. --Version History
  11. --4 - A working version of v3
  12. --
  13. --3 - Placed Atolls. Shrank the huge map size based on advice from Sirian.
  14. --
  15. --2 - Shrank the map sizes except for huge. Added a better way to adjust river
  16. --lengths. Used the continent art styles in a more diverse way. Cleaned up the
  17. --mountain ranges a bit.
  18. --
  19. --1 - initial release! 11/24/2010
  20.  
  21. include("MapGenerator");
  22. include("FeatureGenerator");
  23. include("TerrainGenerator");
  24.  
  25. MapConstants = {}
  26.  
  27. function MapConstants:New()
  28. local mconst = {}
  29. setmetatable(mconst, self)
  30. self.__index = self
  31.  
  32. --Percent of land tiles on the map.
  33. mconst.landPercent = 0.28
  34.  
  35. --Percent of dry land that is below the hill elevation deviance threshold.
  36. mconst.hillsPercent = 0.50
  37.  
  38. --Percent of dry land that is below the mountain elevation deviance
  39. --threshold.
  40. mconst.mountainsPercent = 0.85
  41.  
  42. --Percent of land that is below the desert rainfall threshold.
  43. mconst.desertPercent = 0.36
  44. --Coldest absolute temperature allowed to be desert, plains if colder.
  45. mconst.desertMinTemperature = 0.34
  46.  
  47. --Percent of land that is below the plains rainfall threshold.
  48. mconst.plainsPercent = 0.56
  49.  
  50. --Percent of land that is below the rainfall threshold where no trees
  51. --can appear.
  52. mconst.zeroTreesPercent = 0.30
  53. --Coldest absolute temperature where trees appear.
  54. mconst.treesMinTemperature = 0.27
  55.  
  56. --Percent of land below the jungle rainfall threshold.
  57. mconst.junglePercent = 0.75
  58. --Coldest absolute temperature allowed to be jungle, forest if colder.
  59. mconst.jungleMinTemperature = 0.70
  60.  
  61. --Percent of land below the marsh rainfall threshold.
  62. mconst.marshPercent = 0.92
  63.  
  64. --Absolute temperature below which is snow.
  65. mconst.snowTemperature = 0.25
  66.  
  67. --Absolute temperature below which is tundra.
  68. mconst.tundraTemperature = 0.30
  69.  
  70. --North and south ice latitude limits.
  71. mconst.iceNorthLatitudeLimit = 60
  72. mconst.iceSouthLatitudeLimit = -60
  73.  
  74. --North and south atoll latitude limits.
  75. mconst.atollNorthLatitudeLimit = 20
  76. mconst.atollSouthLatitudeLimit = -20
  77. mconst.atollMinDeepWaterNeighbors = 4
  78.  
  79. --percent of river junctions that are large enough to become rivers.
  80. mconst.riverPercent = 0.19
  81.  
  82. --This value is multiplied by each river step. Values greater than one favor
  83. --watershed size. Values less than one favor actual rain amount.
  84. mconst.riverRainCheatFactor = 1.6
  85.  
  86. --These attenuation factors lower the altitude of the map edges. This is
  87. --currently used to prevent large continents in the uninhabitable polar
  88. --regions. East/west attenuation is set to zero, but modded maps may
  89. --have need for them.
  90. mconst.northAttenuationFactor = 0.75
  91. mconst.northAttenuationRange = 0.15 --percent of the map height.
  92. mconst.southAttenuationFactor = 0.75
  93. mconst.southAttenuationRange = 0.15
  94.  
  95. --east west attenuation may be desired for flat maps.
  96. mconst.eastAttenuationFactor = 0.0
  97. mconst.eastAttenuationRange = 0.0 --percent of the map width.
  98. mconst.westAttenuationFactor = 0.0
  99. mconst.westAttenuationRange = 0.0
  100.  
  101. --These set the water temperature compression that creates the land/sea
  102. --seasonal temperature differences that cause monsoon winds.
  103. mconst.minWaterTemp = 0.10
  104. mconst.maxWaterTemp = 0.60
  105.  
  106. --Top and bottom map latitudes.
  107. mconst.topLatitude = 70
  108. mconst.bottomLatitude = -70
  109.  
  110. --Important latitude markers used for generating climate.
  111. mconst.polarFrontLatitude = 60
  112. mconst.tropicLatitudes = 23
  113. mconst.horseLatitudes = 28 -- I shrunk these a bit to emphasize temperate lattitudes
  114.  
  115. --Strength of geostrophic climate generation versus monsoon climate
  116. --generation.
  117. mconst.geostrophicFactor = 3.0
  118.  
  119. mconst.geostrophicLateralWindStrength = 0.6
  120.  
  121. --Fill in any lakes smaller than this. It looks bad to have large
  122. --river systems flowing into a tiny lake.
  123. mconst.minOceanSize = 50
  124.  
  125. --Weight of the mountain elevation map versus the coastline elevation map.
  126. mconst.mountainWeight = 0.8
  127.  
  128. --Crazy rain tweaking variables. I wouldn't touch these if I were you.
  129. mconst.minimumRainCost = 0.0001
  130. mconst.upLiftExponent = 4
  131. mconst.polarRainBoost = 0.00
  132.  
  133. --default frequencies for map of width 128. Adjusting these frequences
  134. --will generate larger or smaller map features.
  135. mconst.twistMinFreq = 0.02
  136. mconst.twistMaxFreq = 0.12
  137. mconst.twistVar = 0.042
  138. mconst.mountainFreq = 0.078
  139.  
  140. --mconst.useCivRands = true --not ready for this yet
  141.  
  142. -----------------------------------------------------------------------
  143. --Below are map constants that should not be altered.
  144.  
  145. --directions
  146. mconst.C = 0
  147. mconst.W = 1
  148. mconst.NW = 2
  149. mconst.NE = 3
  150. mconst.E = 4
  151. mconst.SE = 5
  152. mconst.SW = 6
  153.  
  154. --flow directions
  155. mconst.NOFLOW = 0
  156. mconst.WESTFLOW = 1
  157. mconst.EASTFLOW = 2
  158. mconst.VERTFLOW = 3
  159.  
  160. --wind zones
  161. mconst.NOZONE = -1
  162. mconst.NPOLAR = 0
  163. mconst.NTEMPERATE = 1
  164. mconst.NEQUATOR = 2
  165. mconst.SEQUATOR = 3
  166. mconst.STEMPERATE = 4
  167. mconst.SPOLAR = 5
  168.  
  169. --Hex maps are shorter in the y direction than they are
  170. --wide per unit by this much. We need to know this to sample the perlin
  171. --maps properly so they don't look squished.
  172. mconst.YtoXRatio = 1.5/(math.sqrt(0.75) * 2)
  173.  
  174. return mconst
  175. end
  176.  
  177. function MapConstants:GetOppositeDir(dir)
  178. return ((dir + 2) % 6) + 1
  179. end
  180.  
  181. --Returns a value along a bell curve from a 0 - 1 range
  182. function MapConstants:GetBellCurve(value)
  183. return math.sin(value * math.pi * 2 - math.pi * 0.5) * 0.5 + 0.5
  184. end
  185.  
  186. -----------------------------------------------------------------------------
  187. --Interpolation and Perlin functions
  188. -----------------------------------------------------------------------------
  189. function CubicInterpolate(v0,v1,v2,v3,mu)
  190. local mu2 = mu * mu
  191. local a0 = v3 - v2 - v0 + v1
  192. local a1 = v0 - v1 - a0
  193. local a2 = v2 - v0
  194. local a3 = v1
  195.  
  196. return (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3)
  197. end
  198.  
  199. function BicubicInterpolate(v,muX,muY)
  200. local a0 = CubicInterpolate(v[1],v[2],v[3],v[4],muX);
  201. local a1 = CubicInterpolate(v[5],v[6],v[7],v[8],muX);
  202. local a2 = CubicInterpolate(v[9],v[10],v[11],v[12],muX);
  203. local a3 = CubicInterpolate(v[13],v[14],v[15],v[16],muX);
  204.  
  205. return CubicInterpolate(a0,a1,a2,a3,muY)
  206. end
  207.  
  208. function CubicDerivative(v0,v1,v2,v3,mu)
  209. local mu2 = mu * mu
  210. local a0 = v3 - v2 - v0 + v1
  211. local a1 = v0 - v1 - a0
  212. local a2 = v2 - v0
  213. --local a3 = v1
  214.  
  215. return (3 * a0 * mu2 + 2 * a1 * mu + a2)
  216. end
  217.  
  218. function BicubicDerivative(v,muX,muY)
  219. local a0 = CubicInterpolate(v[1],v[2],v[3],v[4],muX);
  220. local a1 = CubicInterpolate(v[5],v[6],v[7],v[8],muX);
  221. local a2 = CubicInterpolate(v[9],v[10],v[11],v[12],muX);
  222. local a3 = CubicInterpolate(v[13],v[14],v[15],v[16],muX);
  223.  
  224. return CubicDerivative(a0,a1,a2,a3,muY)
  225. end
  226.  
  227. --This function gets a smoothly interpolated value from srcMap.
  228. --x and y are non-integer coordinates of where the value is to
  229. --be calculated, and wrap in both directions. srcMap is an object
  230. --of type FloatMap.
  231. function GetInterpolatedValue(X,Y,srcMap)
  232. local points = {}
  233. local fractionX = X - math.floor(X)
  234. local fractionY = Y - math.floor(Y)
  235.  
  236. --wrappedX and wrappedY are set to -1,-1 of the sampled area
  237. --so that the sample area is in the middle quad of the 4x4 grid
  238. local wrappedX = ((math.floor(X) - 1) % srcMap.rectWidth) + srcMap.rectX
  239. local wrappedY = ((math.floor(Y) - 1) % srcMap.rectHeight) + srcMap.rectY
  240.  
  241. local x
  242. local y
  243.  
  244. for pY = 0, 4-1,1 do
  245. y = pY + wrappedY
  246. for pX = 0,4-1,1 do
  247. x = pX + wrappedX
  248. local srcIndex = srcMap:GetRectIndex(x, y)
  249. points[(pY * 4 + pX) + 1] = srcMap.data[srcIndex]
  250. end
  251. end
  252.  
  253. local finalValue = BicubicInterpolate(points,fractionX,fractionY)
  254.  
  255. return finalValue
  256.  
  257. end
  258.  
  259. function GetDerivativeValue(X,Y,srcMap)
  260. local points = {}
  261. local fractionX = X - math.floor(X)
  262. local fractionY = Y - math.floor(Y)
  263.  
  264. --wrappedX and wrappedY are set to -1,-1 of the sampled area
  265. --so that the sample area is in the middle quad of the 4x4 grid
  266. local wrappedX = ((math.floor(X) - 1) % srcMap.rectWidth) + srcMap.rectX
  267. local wrappedY = ((math.floor(Y) - 1) % srcMap.rectHeight) + srcMap.rectY
  268.  
  269. local x
  270. local y
  271.  
  272. for pY = 0, 4-1,1 do
  273. y = pY + wrappedY
  274. for pX = 0,4-1,1 do
  275. x = pX + wrappedX
  276. local srcIndex = srcMap:GetRectIndex(x, y)
  277. points[(pY * 4 + pX) + 1] = srcMap.data[srcIndex]
  278. end
  279. end
  280.  
  281. local finalValue = BicubicDerivative(points,fractionX,fractionY)
  282.  
  283. return finalValue
  284.  
  285. end
  286.  
  287. --This function gets Perlin noise for the destination coordinates. Note
  288. --that in order for the noise to wrap, the area sampled on the noise map
  289. --must change to fit each octave.
  290. function GetPerlinNoise(x,y,destMapWidth,destMapHeight,initialFrequency,initialAmplitude,amplitudeChange,octaves,noiseMap)
  291. local finalValue = 0.0
  292. local frequency = initialFrequency
  293. local amplitude = initialAmplitude
  294. local frequencyX --slight adjustment for seamless wrapping
  295. local frequencyY --''
  296. for i = 1,octaves,1 do
  297. if noiseMap.wrapX then
  298. noiseMap.rectX = math.floor(noiseMap.width/2 - (destMapWidth * frequency)/2)
  299. noiseMap.rectWidth = math.max(math.floor(destMapWidth * frequency),1)
  300. frequencyX = noiseMap.rectWidth/destMapWidth
  301. else
  302. noiseMap.rectX = 0
  303. noiseMap.rectWidth = noiseMap.width
  304. frequencyX = frequency
  305. end
  306. if noiseMap.wrapY then
  307. noiseMap.rectY = math.floor(noiseMap.height/2 - (destMapHeight * frequency)/2)
  308. noiseMap.rectHeight = math.max(math.floor(destMapHeight * frequency),1)
  309. frequencyY = noiseMap.rectHeight/destMapHeight
  310. else
  311. noiseMap.rectY = 0
  312. noiseMap.rectHeight = noiseMap.height
  313. frequencyY = frequency
  314. end
  315.  
  316. finalValue = finalValue + GetInterpolatedValue(x * frequencyX, y * frequencyY, noiseMap) * amplitude
  317. frequency = frequency * 2.0
  318. amplitude = amplitude * amplitudeChange
  319. end
  320. finalValue = finalValue/octaves
  321. return finalValue
  322. end
  323.  
  324. function GetPerlinDerivative(x,y,destMapWidth,destMapHeight,initialFrequency,initialAmplitude,amplitudeChange,octaves,noiseMap)
  325. local finalValue = 0.0
  326. local frequency = initialFrequency
  327. local amplitude = initialAmplitude
  328. local frequencyX --slight adjustment for seamless wrapping
  329. local frequencyY --''
  330. for i = 1,octaves,1 do
  331. if noiseMap.wrapX then
  332. noiseMap.rectX = math.floor(noiseMap.width/2 - (destMapWidth * frequency)/2)
  333. noiseMap.rectWidth = math.floor(destMapWidth * frequency)
  334. frequencyX = noiseMap.rectWidth/destMapWidth
  335. else
  336. noiseMap.rectX = 0
  337. noiseMap.rectWidth = noiseMap.width
  338. frequencyX = frequency
  339. end
  340. if noiseMap.wrapY then
  341. noiseMap.rectY = math.floor(noiseMap.height/2 - (destMapHeight * frequency)/2)
  342. noiseMap.rectHeight = math.floor(destMapHeight * frequency)
  343. frequencyY = noiseMap.rectHeight/destMapHeight
  344. else
  345. noiseMap.rectY = 0
  346. noiseMap.rectHeight = noiseMap.height
  347. frequencyY = frequency
  348. end
  349.  
  350. finalValue = finalValue + GetDerivativeValue(x * frequencyX, y * frequencyY, noiseMap) * amplitude
  351. frequency = frequency * 2.0
  352. amplitude = amplitude * amplitudeChange
  353. end
  354. finalValue = finalValue/octaves
  355. return finalValue
  356. end
  357.  
  358. function Push(a,item)
  359. table.insert(a,item)
  360. end
  361.  
  362. function Pop(a)
  363. return table.remove(a)
  364. end
  365. ------------------------------------------------------------------------
  366. --inheritance mechanism from http://www.gamedev.net/community/forums/topic.asp?topic_id=561909
  367. ------------------------------------------------------------------------
  368. function inheritsFrom( baseClass )
  369.  
  370. local new_class = {}
  371. local class_mt = { __index = new_class }
  372.  
  373. function new_class:create()
  374. local newinst = {}
  375. setmetatable( newinst, class_mt )
  376. return newinst
  377. end
  378.  
  379. if nil ~= baseClass then
  380. setmetatable( new_class, { __index = baseClass } )
  381. end
  382.  
  383. -- Implementation of additional OO properties starts here --
  384.  
  385. -- Return the class object of the instance
  386. function new_class:class()
  387. return new_class;
  388. end
  389.  
  390. -- Return the super class object of the instance, optional base class of the given class (must be part of hiearchy)
  391. function new_class:baseClass(class)
  392. return new_class:_B(class);
  393. end
  394.  
  395. -- Return the super class object of the instance, optional base class of the given class (must be part of hiearchy)
  396. function new_class:_B(class)
  397. if (class==nil) or (new_class==class) then
  398. return baseClass;
  399. elseif(baseClass~=nil) then
  400. return baseClass:_B(class);
  401. end
  402. return nil;
  403. end
  404.  
  405. -- Return true if the caller is an instance of theClass
  406. function new_class:_ISA( theClass )
  407. local b_isa = false
  408.  
  409. local cur_class = new_class
  410.  
  411. while ( nil ~= cur_class ) and ( false == b_isa ) do
  412. if cur_class == theClass then
  413. b_isa = true
  414. else
  415. cur_class = cur_class:baseClass()
  416. end
  417. end
  418.  
  419. return b_isa
  420. end
  421.  
  422. return new_class
  423. end
  424.  
  425. -----------------------------------------------------------------------------
  426. -- Random functions will use lua rands for stand alone script running
  427. -- and Map.rand for in game.
  428. -----------------------------------------------------------------------------
  429. function PWRand()
  430. return math.random()
  431. end
  432.  
  433. function PWRandSeed(fixedseed)
  434. local seed
  435. if fixedseed == nil then
  436. seed = os.time()
  437. else
  438. seed = fixedseed
  439. end
  440. math.randomseed(seed)
  441. print("random seed for this map is " .. seed)
  442. end
  443.  
  444. --range is inclusive, low and high are possible results
  445. function PWRandint(low, high)
  446. return math.random(low, high)
  447. end
  448. -----------------------------------------------------------------------------
  449. -- FloatMap class
  450. -- This is for storing 2D map data. The 'data' field is a zero based, one
  451. -- dimensional array. To access map data by x and y coordinates, use the
  452. -- GetIndex method to obtain the 1D index, which will handle any needs for
  453. -- wrapping in the x and y directions.
  454. -----------------------------------------------------------------------------
  455. FloatMap = inheritsFrom(nil)
  456.  
  457. function FloatMap:New(width, height, wrapX, wrapY)
  458. local new_inst = {}
  459. setmetatable(new_inst, {__index = FloatMap}); --setup metatable
  460.  
  461. new_inst.width = width
  462. new_inst.height = height
  463. new_inst.wrapX = wrapX
  464. new_inst.wrapY = wrapY
  465. new_inst.length = width*height
  466.  
  467. --These fields are used to access only a subset of the map
  468. --with the GetRectIndex function. This is useful for
  469. --making Perlin noise wrap without generating separate
  470. --noise fields for each octave
  471. new_inst.rectX = 0
  472. new_inst.rectY = 0
  473. new_inst.rectWidth = width
  474. new_inst.rectHeight = height
  475.  
  476. new_inst.data = {}
  477. for i = 0,width*height - 1,1 do
  478. new_inst.data[i] = 0.0
  479. end
  480.  
  481. return new_inst
  482. end
  483.  
  484. function FloatMap:GetNeighbor(x,y,dir)
  485. local xx
  486. local yy
  487. local odd = y % 2
  488. if dir == mc.C then
  489. return x,y
  490. elseif dir == mc.W then
  491. xx = x - 1
  492. yy = y
  493. return xx,yy
  494. elseif dir == mc.NW then
  495. xx = x - 1 + odd
  496. yy = y + 1
  497. return xx,yy
  498. elseif dir == mc.NE then
  499. xx = x + odd
  500. yy = y + 1
  501. return xx,yy
  502. elseif dir == mc.E then
  503. xx = x + 1
  504. yy = y
  505. return xx,yy
  506. elseif dir == mc.SE then
  507. xx = x + odd
  508. yy = y - 1
  509. return xx,yy
  510. elseif dir == mc.SW then
  511. xx = x - 1 + odd
  512. yy = y - 1
  513. return xx,yy
  514. else
  515. error("Bad direction in FloatMap:GetNeighbor")
  516. end
  517. return -1,-1
  518. end
  519.  
  520. function FloatMap:GetIndex(x,y)
  521. local xx
  522. if self.wrapX then
  523. xx = x % self.width
  524. elseif x < 0 or x > self.width - 1 then
  525. return -1
  526. else
  527. xx = x
  528. end
  529.  
  530. if self.wrapY then
  531. yy = y % self.height
  532. elseif y < 0 or y > self.height - 1 then
  533. return -1
  534. else
  535. yy = y
  536. end
  537.  
  538. return yy * self.width + xx
  539. end
  540.  
  541. function FloatMap:GetXYFromIndex(i)
  542. local x = i % self.width
  543. local y = (i - x)/self.width
  544. return x,y
  545. end
  546.  
  547. --quadrants are labeled
  548. --A B
  549. --D C
  550. function FloatMap:GetQuadrant(x,y)
  551. if x < self.width/2 then
  552. if y < self.height/2 then
  553. return "A"
  554. else
  555. return "D"
  556. end
  557. else
  558. if y < self.height/2 then
  559. return "B"
  560. else
  561. return "C"
  562. end
  563. end
  564. end
  565.  
  566. --Gets an index for x and y based on the current
  567. --rect settings. x and y are local to the defined rect.
  568. --Wrapping is assumed in both directions
  569. function FloatMap:GetRectIndex(x,y)
  570. local xx = x % self.rectWidth
  571. local yy = y % self.rectHeight
  572.  
  573. xx = self.rectX + xx
  574. yy = self.rectY + yy
  575.  
  576. return self:GetIndex(xx,yy)
  577. end
  578.  
  579. function FloatMap:Normalize()
  580. --find highest and lowest values
  581. local maxAlt = -1000.0
  582. local minAlt = 1000.0
  583. for i = 0,self.length - 1,1 do
  584. local alt = self.data[i]
  585. if alt > maxAlt then
  586. maxAlt = alt
  587. end
  588. if alt < minAlt then
  589. minAlt = alt
  590. end
  591.  
  592. end
  593. --subtract minAlt from all values so that
  594. --all values are zero and above
  595. for i = 0, self.length - 1, 1 do
  596. self.data[i] = self.data[i] - minAlt
  597. end
  598.  
  599. --subract minAlt also from maxAlt
  600. maxAlt = maxAlt - minAlt
  601.  
  602. --determine and apply scaler to whole map
  603. local scaler
  604. if maxAlt == 0.0 then
  605. scaler = 0.0
  606. else
  607. scaler = 1.0/maxAlt
  608. end
  609.  
  610. for i = 0,self.length - 1,1 do
  611. self.data[i] = self.data[i] * scaler
  612. end
  613.  
  614. end
  615.  
  616. function FloatMap:GenerateNoise()
  617. for i = 0,self.length - 1,1 do
  618. self.data[i] = PWRand()
  619. end
  620.  
  621. end
  622.  
  623. function FloatMap:GenerateBinaryNoise()
  624. for i = 0,self.length - 1,1 do
  625. if PWRand() > 0.5 then
  626. self.data[i] = 1
  627. else
  628. self.data[i] = 0
  629. end
  630. end
  631.  
  632. end
  633.  
  634. function FloatMap:FindThresholdFromPercent(percent, greaterThan, excludeZeros)
  635. local mapList = {}
  636. local percentage = percent * 100
  637.  
  638. if greaterThan then
  639. percentage = 100 - percentage
  640. end
  641.  
  642. if percentage >= 100 then
  643. return 1.01 --whole map
  644. elseif percentage <= 0 then
  645. return -0.01 --none of the map
  646. end
  647.  
  648. for i = 0,self.length - 1,1 do
  649. if not (self.data[i] == 0.0 and excludeZeros) then
  650. table.insert(mapList,self.data[i])
  651. end
  652. end
  653.  
  654. table.sort(mapList, function (a,b) return a < b end)
  655. local threshIndex = math.floor((#mapList * percentage)/100)
  656.  
  657. return mapList[threshIndex - 1]
  658.  
  659. end
  660.  
  661. function FloatMap:GetLatitudeForY(y)
  662. local range = mc.topLatitude - mc.bottomLatitude
  663. return y / self.height * range + mc.bottomLatitude
  664. end
  665.  
  666. function FloatMap:GetYForLatitude(lat)
  667. local range = mc.topLatitude - mc.bottomLatitude
  668. return math.floor(((lat - mc.bottomLatitude) /range * self.height) + 0.5)
  669. end
  670.  
  671. function FloatMap:GetZone(y)
  672. local lat = self:GetLatitudeForY(y)
  673. if y < 0 or y >= self.height then
  674. return mc.NOZONE
  675. end
  676. if lat > mc.polarFrontLatitude then
  677. return mc.NPOLAR
  678. elseif lat >= mc.horseLatitudes then
  679. return mc.NTEMPERATE
  680. elseif lat >= 0.0 then
  681. return mc.NEQUATOR
  682. elseif lat > -mc.horseLatitudes then
  683. return mc.SEQUATOR
  684. elseif lat >= -mc.polarFrontLatitude then
  685. return mc.STEMPERATE
  686. else
  687. return mc.SPOLAR
  688. end
  689. end
  690.  
  691. function FloatMap:GetYFromZone(zone, bTop)
  692. if bTop then
  693. for y=self.height - 1,0,-1 do
  694. if zone == self:GetZone(y) then
  695. return y
  696. end
  697. end
  698. else
  699. for y=0,self.height - 1,1 do
  700. if zone == self:GetZone(y) then
  701. return y
  702. end
  703. end
  704. end
  705. return -1
  706. end
  707.  
  708. function FloatMap:GetGeostrophicWindDirections(zone)
  709.  
  710. if zone == mc.NPOLAR then
  711. return mc.SW,mc.W
  712. elseif zone == mc.NTEMPERATE then
  713. return mc.NE,mc.E
  714. elseif zone == mc.NEQUATOR then
  715. return mc.SW,mc.W
  716. elseif zone == mc.SEQUATOR then
  717. return mc.NW,mc.W
  718. elseif zone == mc.STEMPERATE then
  719. return mc.SE, mc.E
  720. else
  721. return mc.NW,mc.W
  722. end
  723. return -1,-1
  724. end
  725.  
  726. function FloatMap:GetGeostrophicPressure(lat)
  727. local latRange = nil
  728. local latPercent = nil
  729. local pressure = nil
  730. if lat > mc.polarFrontLatitude then
  731. latRange = 90.0 - mc.polarFrontLatitude
  732. latPercent = (lat - mc.polarFrontLatitude)/latRange
  733. pressure = 1.0 - latPercent
  734. elseif lat >= mc.horseLatitudes then
  735. latRange = mc.polarFrontLatitude - mc.horseLatitudes
  736. latPercent = (lat - mc.horseLatitudes)/latRange
  737. pressure = latPercent
  738. elseif lat >= 0.0 then
  739. latRange = mc.horseLatitudes - 0.0
  740. latPercent = (lat - 0.0)/latRange
  741. pressure = 1.0 - latPercent
  742. elseif lat > -mc.horseLatitudes then
  743. latRange = 0.0 + mc.horseLatitudes
  744. latPercent = (lat + mc.horseLatitudes)/latRange
  745. pressure = latPercent
  746. elseif lat >= -mc.polarFrontLatitude then
  747. latRange = -mc.horseLatitudes + mc.polarFrontLatitude
  748. latPercent = (lat + mc.polarFrontLatitude)/latRange
  749. pressure = 1.0 - latPercent
  750. else
  751. latRange = -mc.polarFrontLatitude + 90.0
  752. latPercent = (lat + 90)/latRange
  753. pressure = latPercent
  754. end
  755. --print(pressure)
  756. return pressure
  757. end
  758.  
  759. function FloatMap:ApplyFunction(func)
  760. for i = 0,self.length - 1,1 do
  761. self.data[i] = func(self.data[i])
  762. end
  763. end
  764.  
  765. function FloatMap:GetRadiusAroundHex(x,y,radius)
  766. local list = {}
  767. table.insert(list,{x,y})
  768. if radius == 0 then
  769. return list
  770. end
  771.  
  772. local hereX = x
  773. local hereY = y
  774.  
  775. --make a circle for each radius
  776. for r = 1,radius,1 do
  777. --start 1 to the west
  778. hereX,hereY = self:GetNeighbor(hereX,hereY,mc.W)
  779. if self:IsOnMap(hereX,hereY) then
  780. table.insert(list,{hereX,hereY})
  781. end
  782. --Go r times to the NE
  783. for z = 1,r,1 do
  784. hereX, hereY = self:GetNeighbor(hereX,hereY,mc.NE)
  785. if self:IsOnMap(hereX,hereY) then
  786. table.insert(list,{hereX,hereY})
  787. end
  788. end
  789. --Go r times to the E
  790. for z = 1,r,1 do
  791. hereX, hereY = self:GetNeighbor(hereX,hereY,mc.E)
  792. if self:IsOnMap(hereX,hereY) then
  793. table.insert(list,{hereX,hereY})
  794. end
  795. end
  796. --Go r times to the SE
  797. for z = 1,r,1 do
  798. hereX, hereY = self:GetNeighbor(hereX,hereY,mc.SE)
  799. if self:IsOnMap(hereX,hereY) then
  800. table.insert(list,{hereX,hereY})
  801. end
  802. end
  803. --Go r times to the SW
  804. for z = 1,r,1 do
  805. hereX, hereY = self:GetNeighbor(hereX,hereY,mc.SW)
  806. if self:IsOnMap(hereX,hereY) then
  807. table.insert(list,{hereX,hereY})
  808. end
  809. end
  810. --Go r times to the W
  811. for z = 1,r,1 do
  812. hereX, hereY = self:GetNeighbor(hereX,hereY,mc.W)
  813. if self:IsOnMap(hereX,hereY) then
  814. table.insert(list,{hereX,hereY})
  815. end
  816. end
  817. --Go r - 1 times to the NW!!!!!
  818. for z = 1,r - 1,1 do
  819. hereX, hereY = self:GetNeighbor(hereX,hereY,mc.NW)
  820. if self:IsOnMap(hereX,hereY) then
  821. table.insert(list,{hereX,hereY})
  822. end
  823. end
  824. --one extra NW to set up for next circle
  825. hereX, hereY = self:GetNeighbor(hereX,hereY,mc.NW)
  826. end
  827. return list
  828. end
  829.  
  830. function FloatMap:GetAverageInHex(x,y,radius)
  831. local list = self:GetRadiusAroundHex(x,y,radius)
  832. local avg = 0.0
  833. for n = 1,#list,1 do
  834. local hex = list[n]
  835. local xx = hex[1]
  836. local yy = hex[2]
  837. local i = self:GetIndex(xx,yy)
  838. avg = avg + self.data[i]
  839. end
  840. avg = avg/#list
  841.  
  842. return avg
  843. end
  844.  
  845. function FloatMap:GetStdDevInHex(x,y,radius)
  846. local list = self:GetRadiusAroundHex(x,y,radius)
  847. local avg = 0.0
  848. for n = 1,#list,1 do
  849. local hex = list[n]
  850. local xx = hex[1]
  851. local yy = hex[2]
  852. local i = self:GetIndex(xx,yy)
  853. avg = avg + self.data[i]
  854. end
  855. avg = avg/#list
  856.  
  857. local deviation = 0.0
  858. for n = 1,#list,1 do
  859. local hex = list[n]
  860. local xx = hex[1]
  861. local yy = hex[2]
  862. local i = self:GetIndex(xx,yy)
  863. local sqr = self.data[i] - avg
  864. deviation = deviation + (sqr * sqr)
  865. end
  866. deviation = math.sqrt(deviation/ #list)
  867. return deviation
  868. end
  869.  
  870. function FloatMap:Smooth(radius)
  871. local dataCopy = {}
  872. for y = 0,self.height - 1,1 do
  873. for x = 0, self.width - 1,1 do
  874. local i = self:GetIndex(x,y)
  875. dataCopy[i] = self:GetAverageInHex(x,y,radius)
  876. end
  877. end
  878. self.data = dataCopy
  879. end
  880.  
  881. function FloatMap:Deviate(radius)
  882. local dataCopy = {}
  883. for y = 0,self.height - 1,1 do
  884. for x = 0, self.width - 1,1 do
  885. local i = self:GetIndex(x,y)
  886. dataCopy[i] = self:GetStdDevInHex(x,y,radius)
  887. end
  888. end
  889. self.data = dataCopy
  890. end
  891.  
  892. function FloatMap:IsOnMap(x,y)
  893. local i = self:GetIndex(x,y)
  894. if i == -1 then
  895. return false
  896. end
  897. return true
  898. end
  899.  
  900. function FloatMap:Save(name)
  901. print("saving " .. name .. "...")
  902. local str = self.width .. "," .. self.height
  903. for i = 0,self.length - 1,1 do
  904. str = str .. "," .. self.data[i]
  905. end
  906. local file = io.open(name,"w+")
  907. file:write(str)
  908. file:close()
  909. print("bitmap saved as " .. name .. ".")
  910. end
  911. ------------------------------------------------------------------------
  912. --ElevationMap class
  913. ------------------------------------------------------------------------
  914. ElevationMap = inheritsFrom(FloatMap)
  915.  
  916. function ElevationMap:New(width, height, wrapX, wrapY)
  917. local new_inst = FloatMap:New(width,height,wrapX,wrapY)
  918. setmetatable(new_inst, {__index = ElevationMap}); --setup metatable
  919. return new_inst
  920. end
  921. function ElevationMap:IsBelowSeaLevel(x,y)
  922. local i = self:GetIndex(x,y)
  923. if self.data[i] < self.seaLevelThreshold then
  924. return true
  925. else
  926. return false
  927. end
  928. end
  929. -------------------------------------------------------------------------
  930. --AreaMap class
  931. -------------------------------------------------------------------------
  932. PWAreaMap = inheritsFrom(FloatMap)
  933.  
  934. function PWAreaMap:New(width,height,wrapX,wrapY)
  935. local new_inst = FloatMap:New(width,height,wrapX,wrapY)
  936. setmetatable(new_inst, {__index = PWAreaMap}); --setup metatable
  937.  
  938. new_inst.areaList = {}
  939. new_inst.segStack = {}
  940. return new_inst
  941. end
  942.  
  943. function PWAreaMap:DefineAreas(matchFunction)
  944. --zero map data
  945. for i = 0,self.width*self.height - 1,1 do
  946. self.data[i] = 0.0
  947. end
  948.  
  949. self.areaList = {}
  950. local currentAreaID = 0
  951. for y = 0, self.height - 1,1 do
  952. for x = 0, self.width - 1,1 do
  953. local i = self:GetIndex(x,y)
  954. if self.data[i] == 0 then
  955. currentAreaID = currentAreaID + 1
  956. local area = PWArea:New(currentAreaID,x,y,matchFunction(x,y))
  957. --str = string.format("Filling area %d, matchFunction(x = %d,y = %d) = %s",area.id,x,y,tostring(matchFunction(x,y)))
  958. --print(str)
  959. self:FillArea(x,y,area,matchFunction)
  960. table.insert(self.areaList, area)
  961.  
  962. end
  963. end
  964. end
  965. end
  966.  
  967. function PWAreaMap:FillArea(x,y,area,matchFunction)
  968. self.segStack = {}
  969. local seg = LineSeg:New(y,x,x,1)
  970. Push(self.segStack,seg)
  971. seg = LineSeg:New(y + 1,x,x,-1)
  972. Push(self.segStack,seg)
  973. while #self.segStack > 0 do
  974. seg = Pop(self.segStack)
  975. self:ScanAndFillLine(seg,area,matchFunction)
  976. end
  977. end
  978.  
  979. function PWAreaMap:ScanAndFillLine(seg,area,matchFunction)
  980.  
  981. --str = string.format("Processing line y = %d, xLeft = %d, xRight = %d, dy = %d -------",seg.y,seg.xLeft,seg.xRight,seg.dy)
  982. --print(str)
  983. if self:ValidateY(seg.y + seg.dy) == -1 then
  984. return
  985. end
  986.  
  987. local odd = (seg.y + seg.dy) % 2
  988. local notOdd = seg.y % 2
  989. --str = string.format("odd = %d, notOdd = %d",odd,notOdd)
  990. --print(str)
  991.  
  992. local lineFound = 0
  993. local xStop = nil
  994. if self.wrapX then
  995. xStop = 0 - (self.width * 30)
  996. else
  997. xStop = -1
  998. end
  999. local leftExtreme = nil
  1000. for leftExt = seg.xLeft - odd,xStop + 1,-1 do
  1001. leftExtreme = leftExt --need this saved
  1002. --str = string.format("leftExtreme = %d",leftExtreme)
  1003. --print(str)
  1004. local x = self:ValidateX(leftExtreme)
  1005. local y = self:ValidateY(seg.y + seg.dy)
  1006. local i = self:GetIndex(x,y)
  1007. --str = string.format("x = %d, y = %d, area.trueMatch = %s, matchFunction(x,y) = %s",x,y,tostring(area.trueMatch),tostring(matchFunction(x,y)))
  1008. --print(str)
  1009. if self.data[i] == 0 and area.trueMatch == matchFunction(x,y) then
  1010. self.data[i] = area.id
  1011. area.size = area.size + 1
  1012. --print("adding to area")
  1013. lineFound = 1
  1014. else
  1015. --if no line was found, then leftExtreme is fine, but if
  1016. --a line was found going left, then we need to increment
  1017. --xLeftExtreme to represent the inclusive end of the line
  1018. if lineFound == 1 then
  1019. leftExtreme = leftExtreme + 1
  1020. --print("line found, adding 1 to leftExtreme")
  1021. end
  1022. break
  1023. end
  1024. end
  1025. --str = string.format("leftExtreme = %d",leftExtreme)
  1026. --print(str)
  1027. local rightExtreme = nil
  1028. --now scan right to find extreme right, place each found segment on stack
  1029. if self.wrapX then
  1030. xStop = self.width * 20
  1031. else
  1032. xStop = self.width
  1033. end
  1034. for rightExt = seg.xLeft + lineFound - odd,xStop - 1,1 do
  1035. rightExtreme = rightExt --need this saved
  1036. --str = string.format("rightExtreme = %d",rightExtreme)
  1037. --print(str)
  1038. local x = self:ValidateX(rightExtreme)
  1039. local y = self:ValidateY(seg.y + seg.dy)
  1040. local i = self:GetIndex(x,y)
  1041. --str = string.format("x = %d, y = %d, area.trueMatch = %s, matchFunction(x,y) = %s",x,y,tostring(area.trueMatch),tostring(matchFunction(x,y)))
  1042. --print(str)
  1043. if self.data[i] == 0 and area.trueMatch == matchFunction(x,y) then
  1044. self.data[i] = area.id
  1045. area.size = area.size + 1
  1046. --print("adding to area")
  1047. if lineFound == 0 then
  1048. lineFound = 1 --starting new line
  1049. leftExtreme = rightExtreme
  1050. end
  1051. elseif lineFound == 1 then --found the right end of a line segment
  1052. --print("found right end of line")
  1053. lineFound = 0
  1054. --put same direction on stack
  1055. local newSeg = LineSeg:New(y,leftExtreme,rightExtreme - 1,seg.dy)
  1056. Push(self.segStack,newSeg)
  1057. --str = string.format(" pushing y = %d, xLeft = %d, xRight = %d, dy = %d",y,leftExtreme,rightExtreme - 1,seg.dy)
  1058. --print(str)
  1059. --determine if we must put reverse direction on stack
  1060. if leftExtreme < seg.xLeft - odd or rightExtreme >= seg.xRight + notOdd then
  1061. --out of shadow so put reverse direction on stack
  1062. newSeg = LineSeg:New(y,leftExtreme,rightExtreme - 1,-seg.dy)
  1063. Push(self.segStack,newSeg)
  1064. --str = string.format(" pushing y = %d, xLeft = %d, xRight = %d, dy = %d",y,leftExtreme,rightExtreme - 1,-seg.dy)
  1065. --print(str)
  1066. end
  1067. if(rightExtreme >= seg.xRight + notOdd) then
  1068. break
  1069. end
  1070. elseif lineFound == 0 and rightExtreme >= seg.xRight + notOdd then
  1071. break --past the end of the parent line and no line found
  1072. end
  1073. --continue finding segments
  1074. end
  1075. if lineFound == 1 then --still needing a line to be put on stack
  1076. print("still need line segments")
  1077. lineFound = 0
  1078. --put same direction on stack
  1079. local newSeg = LineSeg:New(seg.y + seg.dy,leftExtreme,rightExtreme - 1,seg.dy)
  1080. Push(self.segStack,newSeg)
  1081. str = string.format(" pushing y = %d, xLeft = %d, xRight = %d, dy = %d",seg.y + seg.dy,leftExtreme,rightExtreme - 1,seg.dy)
  1082. print(str)
  1083. --determine if we must put reverse direction on stack
  1084. if leftExtreme < seg.xLeft - odd or rightExtreme >= seg.xRight + notOdd then
  1085. --out of shadow so put reverse direction on stack
  1086. newSeg = LineSeg:New(seg.y + seg.dy,leftExtreme,rightExtreme - 1,-seg.dy)
  1087. Push(self.segStack,newSeg)
  1088. str = string.format(" pushing y = %d, xLeft = %d, xRight = %d, dy = %d",seg.y + seg.dy,leftExtreme,rightExtreme - 1,-seg.dy)
  1089. print(str)
  1090. end
  1091. end
  1092. end
  1093.  
  1094. function PWAreaMap:GetAreaByID(id)
  1095. for i = 1,#self.areaList,1 do
  1096. if self.areaList[i].id == id then
  1097. return self.areaList[i]
  1098. end
  1099. end
  1100. error("Can't find area id in AreaMap.areaList")
  1101. end
  1102.  
  1103. function PWAreaMap:ValidateY(y)
  1104. local yy = nil
  1105. if self.wrapY then
  1106. yy = y % self.height
  1107. elseif y < 0 or y >= self.height then
  1108. return -1
  1109. else
  1110. yy = y
  1111. end
  1112. return yy
  1113. end
  1114.  
  1115. function PWAreaMap:ValidateX(x)
  1116. local xx = nil
  1117. if self.wrapX then
  1118. xx = x % self.width
  1119. elseif x < 0 or x >= self.width then
  1120. return -1
  1121. else
  1122. xx = x
  1123. end
  1124. return xx
  1125. end
  1126.  
  1127. function PWAreaMap:PrintAreaList()
  1128. for i=1,#self.areaList,1 do
  1129. local id = self.areaList[i].id
  1130. local seedx = self.areaList[i].seedx
  1131. local seedy = self.areaList[i].seedy
  1132. local size = self.areaList[i].size
  1133. local trueMatch = self.areaList[i].trueMatch
  1134. local str = string.format("area id = %d, trueMatch = %s, size = %d, seedx = %d, seedy = %d",id,tostring(trueMatch),size,seedx,seedy)
  1135. print(str)
  1136. end
  1137. end
  1138. -------------------------------------------------------------------------
  1139. --Area class
  1140. -------------------------------------------------------------------------
  1141. PWArea = inheritsFrom(nil)
  1142.  
  1143. function PWArea:New(id,seedx,seedy,trueMatch)
  1144. local new_inst = {}
  1145. setmetatable(new_inst, {__index = PWArea}); --setup metatable
  1146.  
  1147. new_inst.id = id
  1148. new_inst.seedx = seedx
  1149. new_inst.seedy = seedy
  1150. new_inst.trueMatch = trueMatch
  1151. new_inst.size = 0
  1152.  
  1153. return new_inst
  1154. end
  1155. -------------------------------------------------------------------------
  1156. --LineSeg class
  1157. -------------------------------------------------------------------------
  1158. LineSeg = inheritsFrom(nil)
  1159.  
  1160. function LineSeg:New(y,xLeft,xRight,dy)
  1161. local new_inst = {}
  1162. setmetatable(new_inst, {__index = LineSeg}); --setup metatable
  1163.  
  1164. new_inst.y = y
  1165. new_inst.xLeft = xLeft
  1166. new_inst.xRight = xRight
  1167. new_inst.dy = dy
  1168.  
  1169. return new_inst
  1170. end
  1171.  
  1172. -------------------------------------------------------------------------
  1173. --RiverMap class
  1174. -------------------------------------------------------------------------
  1175. RiverMap = inheritsFrom(nil)
  1176.  
  1177. function RiverMap:New(elevationMap)
  1178. local new_inst = {}
  1179. setmetatable(new_inst, {__index = RiverMap});
  1180.  
  1181. new_inst.elevationMap = elevationMap
  1182. new_inst.riverData = {}
  1183. for y = 0,new_inst.elevationMap.height - 1,1 do
  1184. for x = 0,new_inst.elevationMap.width - 1,1 do
  1185. local i = new_inst.elevationMap:GetIndex(x,y)
  1186. new_inst.riverData[i] = RiverHex:New(x,y)
  1187. end
  1188. end
  1189.  
  1190. return new_inst
  1191. end
  1192.  
  1193. function RiverMap:GetJunction(x,y,isNorth)
  1194. local i = self.elevationMap:GetIndex(x,y)
  1195. if isNorth then
  1196. return self.riverData[i].northJunction
  1197. else
  1198. return self.riverData[i].southJunction
  1199. end
  1200. end
  1201.  
  1202. function RiverMap:GetJunctionNeighbor(direction,junction)
  1203. local xx = nil
  1204. local yy = nil
  1205. local ii = nil
  1206. local neighbor = nil
  1207. local odd = junction.y % 2
  1208. if direction == mc.NOFLOW then
  1209. error("can't get junction neighbor in direction NOFLOW")
  1210. elseif direction == mc.WESTFLOW then
  1211. xx = junction.x + odd - 1
  1212. if junction.isNorth then
  1213. yy = junction.y + 1
  1214. else
  1215. yy = junction.y - 1
  1216. end
  1217. ii = self.elevationMap:GetIndex(xx,yy)
  1218. if ii ~= -1 then
  1219. neighbor = self:GetJunction(xx,yy,not junction.isNorth)
  1220. return neighbor
  1221. end
  1222. elseif direction == mc.EASTFLOW then
  1223. xx = junction.x + odd
  1224. if junction.isNorth then
  1225. yy = junction.y + 1
  1226. else
  1227. yy = junction.y - 1
  1228. end
  1229. ii = self.elevationMap:GetIndex(xx,yy)
  1230. if ii ~= -1 then
  1231. neighbor = self:GetJunction(xx,yy,not junction.isNorth)
  1232. return neighbor
  1233. end
  1234. elseif direction == mc.VERTFLOW then
  1235. xx = junction.x
  1236. if junction.isNorth then
  1237. yy = junction.y + 2
  1238. else
  1239. yy = junction.y - 2
  1240. end
  1241. ii = self.elevationMap:GetIndex(xx,yy)
  1242. if ii ~= -1 then
  1243. neighbor = self:GetJunction(xx,yy,not junction.isNorth)
  1244. return neighbor
  1245. end
  1246. end
  1247.  
  1248. return nil --neighbor off map
  1249. end
  1250.  
  1251. --Get the west or east hex neighboring this junction
  1252. function RiverMap:GetRiverHexNeighbor(junction,westNeighbor)
  1253. local xx = nil
  1254. local yy = nil
  1255. local ii = nil
  1256. local odd = junction.y % 2
  1257. if junction.isNorth then
  1258. yy = junction.y + 1
  1259. else
  1260. yy = junction.y - 1
  1261. end
  1262. if westNeighbor then
  1263. xx = junction.x + odd - 1
  1264. else
  1265. xx = junction.x + odd
  1266. end
  1267.  
  1268. ii = self.elevationMap:GetIndex(xx,yy)
  1269. if ii ~= -1 then
  1270. return self.riverData[ii]
  1271. end
  1272.  
  1273. return nil
  1274. end
  1275.  
  1276. function RiverMap:SetJunctionAltitudes()
  1277. for y = 0,self.elevationMap.height - 1,1 do
  1278. for x = 0,self.elevationMap.width - 1,1 do
  1279. local i = self.elevationMap:GetIndex(x,y)
  1280. local vertAltitude = self.elevationMap.data[i]
  1281. local westAltitude = nil
  1282. local eastAltitude = nil
  1283. local vertNeighbor = self.riverData[i]
  1284. local westNeighbor = nil
  1285. local eastNeighbor = nil
  1286. local xx = nil
  1287. local yy = nil
  1288. local ii = nil
  1289.  
  1290. --first do north
  1291. westNeighbor = self:GetRiverHexNeighbor(vertNeighbor.northJunction,true)
  1292. eastNeighbor = self:GetRiverHexNeighbor(vertNeighbor.northJunction,false)
  1293.  
  1294. if westNeighbor ~= nil then
  1295. ii = self.elevationMap:GetIndex(westNeighbor.x,westNeighbor.y)
  1296. else
  1297. ii = -1
  1298. end
  1299.  
  1300. if ii ~= -1 then
  1301. westAltitude = self.elevationMap.data[ii]
  1302. else
  1303. westAltitude = vertAltitude
  1304. end
  1305.  
  1306. if eastNeighbor ~= nil then
  1307. ii = self.elevationMap:GetIndex(eastNeighbor.x, eastNeighbor.y)
  1308. else
  1309. ii = -1
  1310. end
  1311.  
  1312. if ii ~= -1 then
  1313. eastAltitude = self.elevationMap.data[ii]
  1314. else
  1315. eastAltitude = vertAltitude
  1316. end
  1317.  
  1318. vertNeighbor.northJunction.altitude = math.min(math.min(vertAltitude,westAltitude),eastAltitude)
  1319.  
  1320. --then south
  1321. westNeighbor = self:GetRiverHexNeighbor(vertNeighbor.southJunction,true)
  1322. eastNeighbor = self:GetRiverHexNeighbor(vertNeighbor.southJunction,false)
  1323.  
  1324. if westNeighbor ~= nil then
  1325. ii = self.elevationMap:GetIndex(westNeighbor.x,westNeighbor.y)
  1326. else
  1327. ii = -1
  1328. end
  1329.  
  1330. if ii ~= -1 then
  1331. westAltitude = self.elevationMap.data[ii]
  1332. else
  1333. westAltitude = vertAltitude
  1334. end
  1335.  
  1336. if eastNeighbor ~= nil then
  1337. ii = self.elevationMap:GetIndex(eastNeighbor.x, eastNeighbor.y)
  1338. else
  1339. ii = -1
  1340. end
  1341.  
  1342. if ii ~= -1 then
  1343. eastAltitude = self.elevationMap.data[ii]
  1344. else
  1345. eastAltitude = vertAltitude
  1346. end
  1347.  
  1348. vertNeighbor.southJunction.altitude = math.min(math.min(vertAltitude,westAltitude),eastAltitude)
  1349. end
  1350. end
  1351. end
  1352.  
  1353. function RiverMap:isLake(junction)
  1354.  
  1355. --first exclude the map edges that don't have neighbors
  1356. if junction.y == 0 and junction.isNorth == false then
  1357. return false
  1358. elseif junction.y == self.elevationMap.height - 1 and junction.isNorth == true then
  1359. return false
  1360. end
  1361.  
  1362. --exclude altitudes below sea level
  1363. if junction.altitude < self.elevationMap.seaLevelThreshold then
  1364. return false
  1365. end
  1366.  
  1367. --print(string.format("junction = (%d,%d) N = %s, alt = %f",junction.x,junction.y,tostring(junction.isNorth),junction.altitude))
  1368.  
  1369. local vertNeighbor = self:GetJunctionNeighbor(mc.VERTFLOW,junction)
  1370. local vertAltitude = nil
  1371. if vertNeighbor == nil then
  1372. vertAltitude = junction.altitude
  1373. --print("--vertNeighbor == nil")
  1374. else
  1375. vertAltitude = vertNeighbor.altitude
  1376. --print(string.format("--vertNeighbor = (%d,%d) N = %s, alt = %f",vertNeighbor.x,vertNeighbor.y,tostring(vertNeighbor.isNorth),vertNeighbor.altitude))
  1377. end
  1378.  
  1379. local westNeighbor = self:GetJunctionNeighbor(mc.WESTFLOW,junction)
  1380. local westAltitude = nil
  1381. if westNeighbor == nil then
  1382. westAltitude = junction.altitude
  1383. --print("--westNeighbor == nil")
  1384. else
  1385. westAltitude = westNeighbor.altitude
  1386. --print(string.format("--westNeighbor = (%d,%d) N = %s, alt = %f",westNeighbor.x,westNeighbor.y,tostring(westNeighbor.isNorth),westNeighbor.altitude))
  1387. end
  1388.  
  1389. local eastNeighbor = self:GetJunctionNeighbor(mc.EASTFLOW,junction)
  1390. local eastAltitude = nil
  1391. if eastNeighbor == nil then
  1392. eastAltitude = junction.altitude
  1393. --print("--eastNeighbor == nil")
  1394. else
  1395. eastAltitude = eastNeighbor.altitude
  1396. --print(string.format("--eastNeighbor = (%d,%d) N = %s, alt = %f",eastNeighbor.x,eastNeighbor.y,tostring(eastNeighbor.isNorth),eastNeighbor.altitude))
  1397. end
  1398.  
  1399. local lowest = math.min(vertAltitude,math.min(westAltitude,math.min(eastAltitude,junction.altitude)))
  1400.  
  1401. if lowest == junction.altitude then
  1402. --print("--is lake")
  1403. return true
  1404. end
  1405. --print("--is not lake")
  1406. return false
  1407. end
  1408.  
  1409. --get the average altitude of the two lowest neighbors that are higher than
  1410. --the junction altitude.
  1411. function RiverMap:GetLowerNeighborAverage(junction)
  1412. local vertNeighbor = self:GetJunctionNeighbor(mc.VERTFLOW,junction)
  1413. local vertAltitude = nil
  1414. if vertNeighbor == nil then
  1415. vertAltitude = junction.altitude
  1416. else
  1417. vertAltitude = vertNeighbor.altitude
  1418. end
  1419.  
  1420. local westNeighbor = self:GetJunctionNeighbor(mc.WESTFLOW,junction)
  1421. local westAltitude = nil
  1422. if westNeighbor == nil then
  1423. westAltitude = junction.altitude
  1424. else
  1425. westAltitude = westNeighbor.altitude
  1426. end
  1427.  
  1428. local eastNeighbor = self:GetJunctionNeighbor(mc.EASTFLOW,junction)
  1429. local eastAltitude = nil
  1430. if eastNeighbor == nil then
  1431. eastAltitude = junction.altitude
  1432. else
  1433. eastAltitude = eastNeighbor.altitude
  1434. end
  1435.  
  1436. local nList = {vertAltitude,westAltitude,eastAltitude}
  1437. table.sort(nList)
  1438. local avg = nil
  1439. if nList[1] > junction.altitude then
  1440. avg = (nList[1] + nList[2])/2.0
  1441. elseif nList[2] > junction.altitude then
  1442. avg = (nList[2] + nList[3])/2.0
  1443. elseif nList[3] > junction.altitude then
  1444. avg = (nList[3] + junction.altitude)/2.0
  1445. else
  1446. avg = junction.altitude --all neighbors are the same height. Dealt with later
  1447. end
  1448. return avg
  1449. end
  1450.  
  1451. --this function alters the drainage pattern
  1452. function RiverMap:SiltifyLakes()
  1453. local lakeList = {}
  1454. local onQueueMapNorth = {}
  1455. local onQueueMapSouth = {}
  1456. for y = 0,self.elevationMap.height - 1,1 do
  1457. for x = 0,self.elevationMap.width - 1,1 do
  1458. local i = self.elevationMap:GetIndex(x,y)
  1459. onQueueMapNorth[i] = false
  1460. onQueueMapSouth[i] = false
  1461. if self:isLake(self.riverData[i].northJunction) then
  1462. Push(lakeList,self.riverData[i].northJunction)
  1463. onQueueMapNorth[i] = true
  1464. end
  1465. if self:isLake(self.riverData[i].southJunction) then
  1466. Push(lakeList,self.riverData[i].southJunction)
  1467. onQueueMapSouth[i] = true
  1468. end
  1469. end
  1470. end
  1471.  
  1472. local longestLakeList = #lakeList
  1473. local shortestLakeList = #lakeList
  1474. local iterations = 0
  1475. local debugOn = false
  1476. --print(string.format("initial lake count = %d",longestLakeList))
  1477. while #lakeList > 0 do
  1478. --print(string.format("length of lakeList = %d",#lakeList))
  1479. iterations = iterations + 1
  1480. if #lakeList > longestLakeList then
  1481. longestLakeList = #lakeList
  1482. end
  1483.  
  1484. if #lakeList < shortestLakeList then
  1485. shortestLakeList = #lakeList
  1486. --print(string.format("shortest lake list = %d, iterations = %d",shortestLakeList,iterations))
  1487. iterations = 0
  1488. end
  1489.  
  1490. if iterations > 1000000 then
  1491. debugOn = true
  1492. end
  1493.  
  1494. if iterations > 1001000 then
  1495. error("endless loop in lake siltification. check logs")
  1496. end
  1497.  
  1498. local junction = Pop(lakeList)
  1499. local i = self.elevationMap:GetIndex(junction.x,junction.y)
  1500. if junction.isNorth then
  1501. onQueueMapNorth[i] = false
  1502. else
  1503. onQueueMapSouth[i] = false
  1504. end
  1505.  
  1506. if debugOn then
  1507. print(string.format("processing (%d,%d) N=%s alt=%f",junction.x,junction.y,tostring(junction.isNorth),junction.altitude))
  1508. end
  1509.  
  1510. local avgLowest = self:GetLowerNeighborAverage(junction)
  1511.  
  1512. if debugOn then
  1513. print(string.format("--avgLowest == %f",avgLowest))
  1514. end
  1515.  
  1516. if avgLowest < junction.altitude + 0.005 then --cant use == in fp comparison
  1517. junction.altitude = avgLowest + 0.005
  1518. if debugOn then
  1519. print("--adding 0.005 to avgLowest")
  1520. end
  1521. else
  1522. junction.altitude = avgLowest
  1523. end
  1524.  
  1525. if debugOn then
  1526. print(string.format("--changing altitude to %f",junction.altitude))
  1527. end
  1528.  
  1529. for dir = mc.WESTFLOW,mc.VERTFLOW,1 do
  1530. local neighbor = self:GetJunctionNeighbor(dir,junction)
  1531. if debugOn and neighbor == nil then
  1532. print(string.format("--nil neighbor at direction = %d",dir))
  1533. end
  1534. if neighbor ~= nil and self:isLake(neighbor) then
  1535. local i = self.elevationMap:GetIndex(neighbor.x,neighbor.y)
  1536. if neighbor.isNorth == true and onQueueMapNorth[i] == false then
  1537. Push(lakeList,neighbor)
  1538. onQueueMapNorth[i] = true
  1539. if debugOn then
  1540. print(string.format("--pushing (%d,%d) N=%s alt=%f",neighbor.x,neighbor.y,tostring(neighbor.isNorth),neighbor.altitude))
  1541. end
  1542. elseif neighbor.isNorth == false and onQueueMapSouth[i] == false then
  1543. Push(lakeList,neighbor)
  1544. onQueueMapSouth[i] = true
  1545. if debugOn then
  1546. print(string.format("--pushing (%d,%d) N=%s alt=%f",neighbor.x,neighbor.y,tostring(neighbor.isNorth),neighbor.altitude))
  1547. end
  1548. end
  1549. end
  1550. end
  1551. end
  1552. --print(string.format("longestLakeList = %d",longestLakeList))
  1553.  
  1554. --print(string.format("sea level = %f",self.elevationMap.seaLevelThreshold))
  1555.  
  1556. local belowSeaLevelCount = 0
  1557. local riverTest = FloatMap:New(self.elevationMap.width,self.elevationMap.height,self.elevationMap.xWrap,self.elevationMap.yWrap)
  1558. local lakesFound = false
  1559. for y = 0,self.elevationMap.height - 1,1 do
  1560. for x = 0,self.elevationMap.width - 1,1 do
  1561. local i = self.elevationMap:GetIndex(x,y)
  1562.  
  1563. local northAltitude = self.riverData[i].northJunction.altitude
  1564. local southAltitude = self.riverData[i].southJunction.altitude
  1565. if northAltitude < self.elevationMap.seaLevelThreshold then
  1566. belowSeaLevelCount = belowSeaLevelCount + 1
  1567. end
  1568. if southAltitude < self.elevationMap.seaLevelThreshold then
  1569. belowSeaLevelCount = belowSeaLevelCount + 1
  1570. end
  1571. riverTest.data[i] = (northAltitude + southAltitude)/2.0
  1572.  
  1573. if self:isLake(self.riverData[i].northJunction) then
  1574. local junction = self.riverData[i].northJunction
  1575. print(string.format("lake found at (%d, %d) isNorth = %s, altitude = %f!",junction.x,junction.y,tostring(junction.isNorth),junction.altitude))
  1576. riverTest.data[i] = 1.0
  1577. lakesFound = true
  1578. end
  1579. if self:isLake(self.riverData[i].southJunction) then
  1580. local junction = self.riverData[i].southJunction
  1581. print(string.format("lake found at (%d, %d) isNorth = %s, altitude = %f!",junction.x,junction.y,tostring(junction.isNorth),junction.altitude))
  1582. riverTest.data[i] = 1.0
  1583. lakesFound = true
  1584. end
  1585. end
  1586. end
  1587.  
  1588. if lakesFound then
  1589. error("Failed to siltify lakes. check logs")
  1590. end
  1591. --riverTest:Normalize()
  1592. -- riverTest:Save("riverTest.csv")
  1593. end
  1594.  
  1595. function RiverMap:SetFlowDestinations()
  1596. junctionList = {}
  1597. for y = 0,self.elevationMap.height - 1,1 do
  1598. for x = 0,self.elevationMap.width - 1,1 do
  1599. local i = self.elevationMap:GetIndex(x,y)
  1600. table.insert(junctionList,self.riverData[i].northJunction)
  1601. table.insert(junctionList,self.riverData[i].southJunction)
  1602. end
  1603. end
  1604.  
  1605. table.sort(junctionList,function (a,b) return a.altitude > b.altitude end)
  1606.  
  1607. for n=1,#junctionList do
  1608. local junction = junctionList[n]
  1609. local validList = self:GetValidFlows(junction)
  1610. if #validList > 0 then
  1611. local choice = PWRandint(1,#validList)
  1612. junction.flow = validList[choice]
  1613. else
  1614. junction.flow = mc.NOFLOW
  1615. end
  1616. end
  1617. end
  1618.  
  1619. function RiverMap:GetValidFlows(junction)
  1620. local validList = {}
  1621. for dir = mc.WESTFLOW,mc.VERTFLOW,1 do
  1622. neighbor = self:GetJunctionNeighbor(dir,junction)
  1623. if neighbor ~= nil and neighbor.altitude < junction.altitude then
  1624. table.insert(validList,dir)
  1625. end
  1626. end
  1627. return validList
  1628. end
  1629.  
  1630. function RiverMap:IsTouchingOcean(junction)
  1631.  
  1632. if elevationMap:IsBelowSeaLevel(junction.x,junction.y) then
  1633. return true
  1634. end
  1635. local westNeighbor = self:GetRiverHexNeighbor(junction,true)
  1636. local eastNeighbor = self:GetRiverHexNeighbor(junction,false)
  1637.  
  1638. if westNeighbor == nil or elevationMap:IsBelowSeaLevel(westNeighbor.x,westNeighbor.y) then
  1639. return true
  1640. end
  1641. if eastNeighbor == nil or elevationMap:IsBelowSeaLevel(eastNeighbor.x,eastNeighbor.y) then
  1642. return true
  1643. end
  1644. return false
  1645. end
  1646.  
  1647. function RiverMap:SetRiverSizes(rainfallMap)
  1648. local junctionList = {} --only include junctions not touching ocean in this list
  1649. for y = 0,self.elevationMap.height - 1,1 do
  1650. for x = 0,self.elevationMap.width - 1,1 do
  1651. local i = self.elevationMap:GetIndex(x,y)
  1652. if not self:IsTouchingOcean(self.riverData[i].northJunction) then
  1653. table.insert(junctionList,self.riverData[i].northJunction)
  1654. end
  1655. if not self:IsTouchingOcean(self.riverData[i].southJunction) then
  1656. table.insert(junctionList,self.riverData[i].southJunction)
  1657. end
  1658. end
  1659. end
  1660.  
  1661. table.sort(junctionList,function (a,b) return a.altitude > b.altitude end)
  1662.  
  1663. for n=1,#junctionList do
  1664. local junction = junctionList[n]
  1665. local nextJunction = junction
  1666. local i = self.elevationMap:GetIndex(junction.x,junction.y)
  1667. while true do
  1668. nextJunction.size = (nextJunction.size + rainfallMap.data[i]) * mc.riverRainCheatFactor
  1669. if nextJunction.flow == mc.NOFLOW or self:IsTouchingOcean(nextJunction) then
  1670. nextJunction.size = 0.0
  1671. break
  1672. end
  1673. nextJunction = self:GetJunctionNeighbor(nextJunction.flow,nextJunction)
  1674. end
  1675. end
  1676.  
  1677. --now sort by river size to find river threshold
  1678. table.sort(junctionList,function (a,b) return a.size > b.size end)
  1679. local riverIndex = math.floor(mc.riverPercent * #junctionList)
  1680. self.riverThreshold = junctionList[riverIndex].size
  1681. print(string.format("river threshold = %f",self.riverThreshold))
  1682.  
  1683. --~ local riverMap = FloatMap:New(self.elevationMap.width,self.elevationMap.height,self.elevationMap.xWrap,self.elevationMap.yWrap)
  1684. --~ for y = 0,self.elevationMap.height - 1,1 do
  1685. --~ for x = 0,self.elevationMap.width - 1,1 do
  1686. --~ local i = self.elevationMap:GetIndex(x,y)
  1687. --~ riverMap.data[i] = math.max(self.riverData[i].northJunction.size,self.riverData[i].southJunction.size)
  1688. --~ end
  1689. --~ end
  1690. --~ riverMap:Normalize()
  1691. --riverMap:Save("riverSizeMap.csv")
  1692. end
  1693.  
  1694. --This function returns the flow directions needed by civ
  1695. function RiverMap:GetFlowDirections(x,y)
  1696. --print(string.format("Get flow dirs for %d,%d",x,y))
  1697. local i = elevationMap:GetIndex(x,y)
  1698.  
  1699. local WOfRiver = FlowDirectionTypes.NO_FLOWDIRECTION
  1700. local xx,yy = elevationMap:GetNeighbor(x,y,mc.NE)
  1701. local ii = elevationMap:GetIndex(xx,yy)
  1702. if ii ~= -1 and self.riverData[ii].southJunction.flow == mc.VERTFLOW and self.riverData[ii].southJunction.size > self.riverThreshold then
  1703. --print(string.format("--NE(%d,%d) south flow=%d, size=%f",xx,yy,self.riverData[ii].southJunction.flow,self.riverData[ii].southJunction.size))
  1704. WOfRiver = FlowDirectionTypes.FLOWDIRECTION_SOUTH
  1705. end
  1706. xx,yy = elevationMap:GetNeighbor(x,y,mc.SE)
  1707. ii = elevationMap:GetIndex(xx,yy)
  1708. if ii ~= -1 and self.riverData[ii].northJunction.flow == mc.VERTFLOW and self.riverData[ii].northJunction.size > self.riverThreshold then
  1709. --print(string.format("--SE(%d,%d) north flow=%d, size=%f",xx,yy,self.riverData[ii].northJunction.flow,self.riverData[ii].northJunction.size))
  1710. WOfRiver = FlowDirectionTypes.FLOWDIRECTION_NORTH
  1711. end
  1712.  
  1713. local NWOfRiver = FlowDirectionTypes.NO_FLOWDIRECTION
  1714. xx,yy = elevationMap:GetNeighbor(x,y,mc.SE)
  1715. ii = elevationMap:GetIndex(xx,yy)
  1716. if ii ~= -1 and self.riverData[ii].northJunction.flow == mc.WESTFLOW and self.riverData[ii].northJunction.size > self.riverThreshold then
  1717. NWOfRiver = FlowDirectionTypes.FLOWDIRECTION_SOUTHWEST
  1718. end
  1719. if self.riverData[i].southJunction.flow == mc.EASTFLOW and self.riverData[i].southJunction.size > self.riverThreshold then
  1720. NWOfRiver = FlowDirectionTypes.FLOWDIRECTION_NORTHEAST
  1721. end
  1722.  
  1723. local NEOfRiver = FlowDirectionTypes.NO_FLOWDIRECTION
  1724. xx,yy = elevationMap:GetNeighbor(x,y,mc.SW)
  1725. ii = elevationMap:GetIndex(xx,yy)
  1726. if ii ~= -1 and self.riverData[ii].northJunction.flow == mc.EASTFLOW and self.riverData[ii].northJunction.size > self.riverThreshold then
  1727. NEOfRiver = FlowDirectionTypes.FLOWDIRECTION_SOUTHEAST
  1728. end
  1729. if self.riverData[i].southJunction.flow == mc.WESTFLOW and self.riverData[i].southJunction.size > self.riverThreshold then
  1730. NEOfRiver = FlowDirectionTypes.FLOWDIRECTION_NORTHWEST
  1731. end
  1732.  
  1733. return WOfRiver,NWOfRiver,NEOfRiver
  1734. end
  1735. -------------------------------------------------------------------------
  1736. --RiverHex class
  1737. -------------------------------------------------------------------------
  1738. RiverHex = inheritsFrom(nil)
  1739.  
  1740. function RiverHex:New(x, y)
  1741. local new_inst = {}
  1742. setmetatable(new_inst, {__index = RiverHex});
  1743.  
  1744. new_inst.x = x
  1745. new_inst.y = y
  1746. new_inst.northJunction = RiverJunction:New(x,y,true)
  1747. new_inst.southJunction = RiverJunction:New(x,y,false)
  1748.  
  1749. return new_inst
  1750. end
  1751.  
  1752. -------------------------------------------------------------------------
  1753. --RiverJunction class
  1754. -------------------------------------------------------------------------
  1755. RiverJunction = inheritsFrom(nil)
  1756.  
  1757. function RiverJunction:New(x,y,isNorth)
  1758. local new_inst = {}
  1759. setmetatable(new_inst, {__index = RiverJunction});
  1760.  
  1761. new_inst.x = x
  1762. new_inst.y = y
  1763. new_inst.isNorth = isNorth
  1764. new_inst.altitude = 0.0
  1765. new_inst.flow = mc.NOFLOW
  1766. new_inst.size = 0.0
  1767.  
  1768. return new_inst
  1769. end
  1770.  
  1771. ------------------------------------------------------------------------------
  1772. --Global functions
  1773. ------------------------------------------------------------------------------
  1774. function GenerateTwistedPerlinMap(width, height, xWrap, yWrap,minFreq,maxFreq,varFreq)
  1775. local inputNoise = FloatMap:New(width,height,xWrap,yWrap)
  1776. inputNoise:GenerateNoise()
  1777. inputNoise:Normalize()
  1778.  
  1779. local freqMap = FloatMap:New(width,height,xWrap,yWrap)
  1780. for y = 0, freqMap.height - 1,1 do
  1781. for x = 0,freqMap.width - 1,1 do
  1782. local i = freqMap:GetIndex(x,y)
  1783. local odd = y % 2
  1784. local xx = x + odd * 0.5
  1785. freqMap.data[i] = GetPerlinNoise(xx,y * mc.YtoXRatio,freqMap.width,freqMap.height * mc.YtoXRatio,varFreq,1.0,0.1,8,inputNoise)
  1786. end
  1787. end
  1788. freqMap:Normalize()
  1789. -- freqMap:Save("freqMap.csv")
  1790.  
  1791. local twistMap = FloatMap:New(width,height,xWrap,yWrap)
  1792. for y = 0, twistMap.height - 1,1 do
  1793. for x = 0,twistMap.width - 1,1 do
  1794. local i = twistMap:GetIndex(x,y)
  1795. local freq = freqMap.data[i] * (maxFreq - minFreq) + minFreq
  1796. local mid = (maxFreq - minFreq)/2 + minFreq
  1797. local coordScale = freq/mid
  1798. local offset = (1.0 - coordScale)/mid
  1799. --print("1-coordscale = " .. (1.0 - coordScale) .. ", offset = " .. offset)
  1800. local ampChange = 0.85 - freqMap.data[i] * 0.5
  1801. local odd = y % 2
  1802. local xx = x + odd * 0.5
  1803. twistMap.data[i] = GetPerlinNoise(xx + offset,(y + offset) * mc.YtoXRatio,twistMap.width,twistMap.height * mc.YtoXRatio,mid,1.0,ampChange,8,inputNoise)
  1804. end
  1805. end
  1806.  
  1807. twistMap:Normalize()
  1808. --twistMap:Save("twistMap.csv")
  1809. return twistMap
  1810. end
  1811.  
  1812. function ShuffleList(list)
  1813. local len = #list
  1814. for i=0,len - 1,1 do
  1815. local k = PWRandint(0,len-1)
  1816. list[i], list[k] = list[k], list[i]
  1817. end
  1818. end
  1819.  
  1820. function GenerateMountainMap(width,height,xWrap,yWrap,initFreq)
  1821. local inputNoise = FloatMap:New(width,height,xWrap,yWrap)
  1822. inputNoise:GenerateBinaryNoise()
  1823. inputNoise:Normalize()
  1824. local inputNoise2 = FloatMap:New(width,height,xWrap,yWrap)
  1825. inputNoise2:GenerateNoise()
  1826. inputNoise2:Normalize()
  1827.  
  1828. local mountainMap = FloatMap:New(width,height,xWrap,yWrap)
  1829. local stdDevMap = FloatMap:New(width,height,xWrap,yWrap)
  1830. local noiseMap = FloatMap:New(width,height,xWrap,yWrap)
  1831. for y = 0, mountainMap.height - 1,1 do
  1832. for x = 0,mountainMap.width - 1,1 do
  1833. local i = mountainMap:GetIndex(x,y)
  1834. local odd = y % 2
  1835. local xx = x + odd * 0.5
  1836. mountainMap.data[i] = GetPerlinNoise(xx,y * mc.YtoXRatio,mountainMap.width,mountainMap.height * mc.YtoXRatio,initFreq,1.0,0.4,8,inputNoise)
  1837. noiseMap.data[i] = GetPerlinNoise(xx,y * mc.YtoXRatio,mountainMap.width,mountainMap.height * mc.YtoXRatio,initFreq,1.0,0.4,8,inputNoise2)
  1838. stdDevMap.data[i] = mountainMap.data[i]
  1839. end
  1840. end
  1841. mountainMap:Normalize()
  1842. stdDevMap:Deviate(7)
  1843. stdDevMap:Normalize()
  1844. --stdDevMap:Save("stdDevMap.csv")
  1845. --mountainMap:Save("mountainCloud.csv")
  1846. noiseMap:Normalize()
  1847. --noiseMap:Save("noiseMap.csv")
  1848.  
  1849. local moundMap = FloatMap:New(width,height,xWrap,yWrap)
  1850. for y = 0, mountainMap.height - 1,1 do
  1851. for x = 0,mountainMap.width - 1,1 do
  1852. local i = mountainMap:GetIndex(x,y)
  1853. local val = mountainMap.data[i]
  1854. moundMap.data[i] = (math.sin(val*math.pi*2-math.pi*0.5)*0.5+0.5) * GetAttenuationFactor(mountainMap,x,y)
  1855. if val < 0.5 then
  1856. val = val^1 * 4
  1857. else
  1858. val = (1 - val)^1 * 4
  1859. end
  1860. --mountainMap.data[i] = val
  1861. mountainMap.data[i] = moundMap.data[i]
  1862. end
  1863. end
  1864. mountainMap:Normalize()
  1865. --mountainMap:Save("premountMap.csv")
  1866. --moundMap:Save("moundMap.csv")
  1867.  
  1868. for y = 0, mountainMap.height - 1,1 do
  1869. for x = 0,mountainMap.width - 1,1 do
  1870. local i = mountainMap:GetIndex(x,y)
  1871. local val = mountainMap.data[i]
  1872. --mountainMap.data[i] = (math.sin(val * 2 * math.pi + math.pi * 0.5)^8 * val) + moundMap.data[i] * 2 + noiseMap.data[i] * 0.6
  1873. mountainMap.data[i] = (math.sin(val * 3 * math.pi + math.pi * 0.5)^16 * val)^0.5
  1874. if mountainMap.data[i] > 0.2 then
  1875. mountainMap.data[i] = 1.0
  1876. else
  1877. mountainMap.data[i] = 0.0
  1878. end
  1879. end
  1880. end
  1881. --mountainMap:Save("premountMap.csv")
  1882.  
  1883. local stdDevThreshold = stdDevMap:FindThresholdFromPercent(mc.landPercent,true,false)
  1884.  
  1885. for y = 0, mountainMap.height - 1,1 do
  1886. for x = 0,mountainMap.width - 1,1 do
  1887. local i = mountainMap:GetIndex(x,y)
  1888. local val = mountainMap.data[i]
  1889. local dev = 2.0 * stdDevMap.data[i] - 2.0 * stdDevThreshold
  1890. --mountainMap.data[i] = (math.sin(val * 2 * math.pi + math.pi * 0.5)^8 * val) + moundMap.data[i] * 2 + noiseMap.data[i] * 0.6
  1891. mountainMap.data[i] = (val + moundMap.data[i]) * dev
  1892. end
  1893. end
  1894.  
  1895. mountainMap:Normalize()
  1896. --mountainMap:Save("mountainMap.csv")
  1897. return mountainMap
  1898. end
  1899.  
  1900. function waterMatch(x,y)
  1901. if elevationMap:IsBelowSeaLevel(x,y) then
  1902. return true
  1903. end
  1904. return false
  1905. end
  1906.  
  1907. function GetAttenuationFactor(map,x,y)
  1908. local southY = map.height * mc.southAttenuationRange
  1909. local southRange = map.height * mc.southAttenuationRange
  1910. local yAttenuation = 1.0
  1911. if y < southY then
  1912. yAttenuation = mc.southAttenuationFactor + (y/southRange) * (1.0 - mc.southAttenuationFactor)
  1913. end
  1914.  
  1915. local northY = map.height - (map.height * mc.northAttenuationRange)
  1916. local northRange = map.height * mc.northAttenuationRange
  1917. if y > northY then
  1918. yAttenuation = mc.northAttenuationFactor + ((map.height - y)/northRange) * (1.0 - mc.northAttenuationFactor)
  1919. end
  1920.  
  1921. local eastY = map.width - (map.width * mc.eastAttenuationRange)
  1922. local eastRange = map.width * mc.eastAttenuationRange
  1923. local xAttenuation = 1.0
  1924. if x > eastY then
  1925. xAttenuation = mc.eastAttenuationFactor + ((map.width - x)/eastRange) * (1.0 - mc.eastAttenuationFactor)
  1926. end
  1927.  
  1928. local westY = map.width * mc.westAttenuationRange
  1929. local westRange = map.width * mc.westAttenuationRange
  1930. if x < westY then
  1931. xAttenuation = mc.westAttenuationFactor + (x/westRange) * (1.0 - mc.westAttenuationFactor)
  1932. end
  1933.  
  1934. return yAttenuation * xAttenuation
  1935. end
  1936.  
  1937. function GenerateElevationMap(width,height,xWrap,yWrap)
  1938. local twistMinFreq = 128/width * mc.twistMinFreq --0.02/128
  1939. local twistMaxFreq = 128/width * mc.twistMaxFreq --0.12/128
  1940. local twistVar = 128/width * mc.twistVar --0.042/128
  1941. local mountainFreq = 128/width * mc.mountainFreq --0.05/128
  1942. local twistMap = GenerateTwistedPerlinMap(width,height,xWrap,yWrap,twistMinFreq,twistMaxFreq,twistVar)
  1943. local mountainMap = GenerateMountainMap(width,height,xWrap,yWrap,mountainFreq)
  1944. local elevationMap = ElevationMap:New(width,height,xWrap,yWrap)
  1945. for y = 0,height - 1,1 do
  1946. for x = 0,width - 1,1 do
  1947. local i = elevationMap:GetIndex(x,y)
  1948. local tVal = twistMap.data[i]
  1949. tVal = (math.sin(tVal*math.pi-math.pi*0.5)*0.5+0.5)^0.25 --this formula adds a curve flattening the extremes
  1950. elevationMap.data[i] = (tVal + ((mountainMap.data[i] * 2) - 1) * mc.mountainWeight)
  1951. end
  1952. end
  1953.  
  1954. elevationMap:Normalize()
  1955.  
  1956. --attentuation should not break normalization
  1957. for y = 0,height - 1,1 do
  1958. for x = 0,width - 1,1 do
  1959. local i = elevationMap:GetIndex(x,y)
  1960. local attenuationFactor = GetAttenuationFactor(elevationMap,x,y)
  1961. elevationMap.data[i] = elevationMap.data[i] * attenuationFactor
  1962. end
  1963. end
  1964.  
  1965. elevationMap.seaLevelThreshold = elevationMap:FindThresholdFromPercent(mc.landPercent,true,false)
  1966.  
  1967. return elevationMap
  1968. end
  1969.  
  1970. function FillInLakes()
  1971. local areaMap = PWAreaMap:New(elevationMap.width,elevationMap.height,elevationMap.wrapX,elevationMap.wrapY)
  1972. areaMap:DefineAreas(waterMatch)
  1973. for i=1,#areaMap.areaList,1 do
  1974. local area = areaMap.areaList[i]
  1975. if area.trueMatch and area.size < mc.minOceanSize then
  1976. for n = 0,areaMap.length,1 do
  1977. if areaMap.data[n] == area.id then
  1978. elevationMap.data[n] = elevationMap.seaLevelThreshold
  1979. end
  1980. end
  1981. end
  1982. end
  1983. end
  1984.  
  1985. function GenerateTempMaps(elevationMap)
  1986.  
  1987. local aboveSeaLevelMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  1988. for y = 0,elevationMap.height - 1,1 do
  1989. for x = 0,elevationMap.width - 1,1 do
  1990. local i = aboveSeaLevelMap:GetIndex(x,y)
  1991. if elevationMap:IsBelowSeaLevel(x,y) then
  1992. aboveSeaLevelMap.data[i] = 0.0
  1993. else
  1994. aboveSeaLevelMap.data[i] = elevationMap.data[i] - elevationMap.seaLevelThreshold
  1995. end
  1996. end
  1997. end
  1998. aboveSeaLevelMap:Normalize()
  1999. --aboveSeaLevelMap:Save("aboveSeaLevelMap.csv")
  2000.  
  2001. local summerMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2002. local zenith = mc.tropicLatitudes
  2003. local topTempLat = mc.topLatitude + zenith
  2004. local bottomTempLat = mc.bottomLatitude
  2005. local latRange = topTempLat - bottomTempLat
  2006. for y = 0,elevationMap.height - 1,1 do
  2007. for x = 0,elevationMap.width - 1,1 do
  2008. local i = summerMap:GetIndex(x,y)
  2009. local lat = summerMap:GetLatitudeForY(y)
  2010. --print("y=" .. y ..",lat=" .. lat)
  2011. local latPercent = (lat - bottomTempLat)/latRange
  2012. --print("latPercent=" .. latPercent)
  2013. local temp = (math.sin(latPercent * math.pi * 2 - math.pi * 0.5) * 0.5 + 0.5)
  2014. if elevationMap:IsBelowSeaLevel(x,y) then
  2015. temp = temp * mc.maxWaterTemp + mc.minWaterTemp
  2016. end
  2017. summerMap.data[i] = temp
  2018. end
  2019. end
  2020. summerMap:Smooth(math.floor(elevationMap.width/8))
  2021. summerMap:Normalize()
  2022.  
  2023. local winterMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2024. zenith = -mc.tropicLatitudes
  2025. topTempLat = mc.topLatitude
  2026. bottomTempLat = mc.bottomLatitude + zenith
  2027. latRange = topTempLat - bottomTempLat
  2028. for y = 0,elevationMap.height - 1,1 do
  2029. for x = 0,elevationMap.width - 1,1 do
  2030. local i = winterMap:GetIndex(x,y)
  2031. local lat = winterMap:GetLatitudeForY(y)
  2032. local latPercent = (lat - bottomTempLat)/latRange
  2033. local temp = math.sin(latPercent * math.pi * 2 - math.pi * 0.5) * 0.5 + 0.5
  2034. if elevationMap:IsBelowSeaLevel(x,y) then
  2035. temp = temp * mc.maxWaterTemp + mc.minWaterTemp
  2036. end
  2037. winterMap.data[i] = temp
  2038. end
  2039. end
  2040. winterMap:Smooth(math.floor(elevationMap.width/8))
  2041. winterMap:Normalize()
  2042.  
  2043. local temperatureMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2044. for y = 0,elevationMap.height - 1,1 do
  2045. for x = 0,elevationMap.width - 1,1 do
  2046. local i = temperatureMap:GetIndex(x,y)
  2047. temperatureMap.data[i] = (winterMap.data[i] + summerMap.data[i]) * (1.0 - aboveSeaLevelMap.data[i])
  2048. end
  2049. end
  2050. temperatureMap:Normalize()
  2051.  
  2052. return summerMap,winterMap,temperatureMap
  2053. end
  2054.  
  2055. function GenerateRainfallMap(elevationMap)
  2056. local summerMap,winterMap,temperatureMap = GenerateTempMaps(elevationMap)
  2057. --summerMap:Save("summerMap.csv")
  2058. --winterMap:Save("winterMap.csv")
  2059. --temperatureMap:Save("temperatureMap.csv")
  2060. local geoMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2061. for y = 0,elevationMap.height - 1,1 do
  2062. for x = 0,elevationMap.width - 1,1 do
  2063. local i = elevationMap:GetIndex(x,y)
  2064. local lat = elevationMap:GetLatitudeForY(y)
  2065. local pressure = elevationMap:GetGeostrophicPressure(lat)
  2066. geoMap.data[i] = pressure
  2067. end
  2068. end
  2069. geoMap:Normalize()
  2070. --geoMap:Save("geoMap.csv")
  2071.  
  2072. local sortedSummerMap = {}
  2073. local sortedWinterMap = {}
  2074. for y = 0,elevationMap.height - 1,1 do
  2075. for x = 0,elevationMap.width - 1,1 do
  2076. local i = elevationMap:GetIndex(x,y)
  2077. sortedSummerMap[i + 1] = {x,y,summerMap.data[i]}
  2078. sortedWinterMap[i + 1] = {x,y,winterMap.data[i]}
  2079. end
  2080. end
  2081. table.sort(sortedSummerMap, function (a,b) return a[3] < b[3] end)
  2082. table.sort(sortedWinterMap, function (a,b) return a[3] < b[3] end)
  2083.  
  2084. local sortedGeoMap = {}
  2085. local xStart = 0
  2086. local xStop = 0
  2087. local yStart = 0
  2088. local yStop = 0
  2089. local incX = 0
  2090. local incY = 0
  2091. local geoIndex = 1
  2092. local str = ""
  2093. for zone=0,5,1 do
  2094. local topY = elevationMap:GetYFromZone(zone,true)
  2095. local bottomY = elevationMap:GetYFromZone(zone,false)
  2096. if not (topY == -1 and bottomY == -1) then
  2097. if topY == -1 then
  2098. topY = elevationMap.height - 1
  2099. end
  2100. if bottomY == -1 then
  2101. bottomY = 0
  2102. end
  2103. --str = string.format("topY = %d, bottomY = %d",topY,bottomY)
  2104. --print(str)
  2105. local dir1,dir2 = elevationMap:GetGeostrophicWindDirections(zone)
  2106. --str = string.format("zone = %d, dir1 = %d",zone,dir1)
  2107. --print(str)
  2108. if (dir1 == mc.SW) or (dir1 == mc.SE) then
  2109. yStart = topY
  2110. yStop = bottomY --- 1
  2111. incY = -1
  2112. else
  2113. yStart = bottomY
  2114. yStop = topY --+ 1
  2115. incY = 1
  2116. end
  2117. if dir2 == mc.W then
  2118. xStart = elevationMap.width - 1
  2119. xStop = 0---1
  2120. incX = -1
  2121. else
  2122. xStart = 0
  2123. xStop = elevationMap.width
  2124. incX = 1
  2125. end
  2126. --str = string.format("yStart = %d, yStop = %d, incY = %d",yStart,yStop,incY)
  2127. --print(str)
  2128. --str = string.format("xStart = %d, xStop = %d, incX = %d",xStart,xStop,incX)
  2129. --print(str)
  2130.  
  2131. for y = yStart,yStop ,incY do
  2132. --str = string.format("y = %d",y)
  2133. --print(str)
  2134. --each line should start on water to avoid vast areas without rain
  2135. local xxStart = xStart
  2136. local xxStop = xStop
  2137. for xx = xStart,xStop - incX, incX do
  2138. local i = elevationMap:GetIndex(xx,y)
  2139. if elevationMap:IsBelowSeaLevel(xx,y) then
  2140. xxStart = xx
  2141. xxStop = xx + elevationMap.width * incX
  2142. break
  2143. end
  2144. end
  2145. for x = xxStart,xxStop - incX,incX do
  2146. local i = elevationMap:GetIndex(x,y)
  2147. sortedGeoMap[geoIndex] = {x,y,geoMap.data[i]}
  2148. geoIndex = geoIndex + 1
  2149. end
  2150. end
  2151. end
  2152. end
  2153. -- table.sort(sortedGeoMap, function (a,b) return a[3] < b[3] end)
  2154. --print(#sortedGeoMap)
  2155. --print(#geoMap.data)
  2156.  
  2157. local rainfallSummerMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2158. local moistureMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2159. for i = 1,#sortedSummerMap,1 do
  2160. local x = sortedSummerMap[i][1]
  2161. local y = sortedSummerMap[i][2]
  2162. local pressure = sortedSummerMap[i][3]
  2163. DistributeRain(x,y,elevationMap,temperatureMap,summerMap,rainfallSummerMap,moistureMap,false)
  2164. end
  2165.  
  2166. local rainfallWinterMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2167. local moistureMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2168. for i = 1,#sortedWinterMap,1 do
  2169. local x = sortedWinterMap[i][1]
  2170. local y = sortedWinterMap[i][2]
  2171. local pressure = sortedWinterMap[i][3]
  2172. DistributeRain(x,y,elevationMap,temperatureMap,winterMap,rainfallWinterMap,moistureMap,false)
  2173. end
  2174.  
  2175. local rainfallGeostrophicMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2176. moistureMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2177. --print("----------------------------------------------------------------------------------------")
  2178. --print("--GEOSTROPHIC---------------------------------------------------------------------------")
  2179. --print("----------------------------------------------------------------------------------------")
  2180. for i = 1,#sortedGeoMap,1 do
  2181. local x = sortedGeoMap[i][1]
  2182. local y = sortedGeoMap[i][2]
  2183. --~ if y == 35 or y == 40 then
  2184. --~ str = string.format("x = %d, y = %d",x,y)
  2185. --~ print(str)
  2186. --~ end
  2187. DistributeRain(x,y,elevationMap,temperatureMap,geoMap,rainfallGeostrophicMap,moistureMap,true)
  2188. end
  2189. --zero below sea level for proper percent threshold finding
  2190. for y = 0,elevationMap.height - 1,1 do
  2191. for x = 0,elevationMap.width - 1,1 do
  2192. local i = elevationMap:GetIndex(x,y)
  2193. if elevationMap:IsBelowSeaLevel(x,y) then
  2194. rainfallSummerMap.data[i] = 0.0
  2195. rainfallWinterMap.data[i] = 0.0
  2196. rainfallGeostrophicMap.data[i] = 0.0
  2197. end
  2198. end
  2199. end
  2200.  
  2201. rainfallSummerMap:Normalize()
  2202. --rainfallSummerMap:Save("rainFallSummerMap.csv")
  2203. rainfallWinterMap:Normalize()
  2204. --rainfallWinterMap:Save("rainFallWinterMap.csv")
  2205. rainfallGeostrophicMap:Normalize()
  2206. --rainfallGeostrophicMap:Save("rainfallGeostrophicMap.csv")
  2207.  
  2208. local rainfallMap = FloatMap:New(elevationMap.width,elevationMap.height,elevationMap.xWrap,elevationMap.yWrap)
  2209. for y = 0,elevationMap.height - 1,1 do
  2210. for x = 0,elevationMap.width - 1,1 do
  2211. local i = elevationMap:GetIndex(x,y)
  2212. rainfallMap.data[i] = rainfallSummerMap.data[i] + rainfallWinterMap.data[i] + (rainfallGeostrophicMap.data[i] * mc.geostrophicFactor)
  2213. end
  2214. end
  2215. rainfallMap:Normalize()
  2216.  
  2217. return rainfallMap, temperatureMap
  2218. end
  2219.  
  2220. function DistributeRain(x,y,elevationMap,temperatureMap,pressureMap,rainfallMap,moistureMap,boolGeostrophic)
  2221.  
  2222. local i = elevationMap:GetIndex(x,y)
  2223. local upLiftSource = math.max(math.pow(pressureMap.data[i],mc.upLiftExponent),1.0 - temperatureMap.data[i])
  2224. --local str = string.format("geo=%s,x=%d, y=%d, srcPressure uplift = %f, upliftSource = %f",tostring(boolGeostrophic),x,y,math.pow(pressureMap.data[i],mc.upLiftExponent),upLiftSource)
  2225. --print(str)
  2226. if elevationMap:IsBelowSeaLevel(x,y) then
  2227. moistureMap.data[i] = math.max(moistureMap.data[i], temperatureMap.data[i])
  2228. --print("water tile = true")
  2229. end
  2230. --print(string.format("moistureMap.data[i] = %f",moistureMap.data[i]))
  2231.  
  2232. --make list of neighbors
  2233. local nList = {}
  2234. if boolGeostrophic then
  2235. local zone = elevationMap:GetZone(y)
  2236. local dir1,dir2 = elevationMap:GetGeostrophicWindDirections(zone)
  2237. local x1,y1 = elevationMap:GetNeighbor(x,y,dir1)
  2238. local ii = elevationMap:GetIndex(x1,y1)
  2239. --neighbor must be on map and in same wind zone
  2240. if ii >= 0 and (elevationMap:GetZone(y1) == elevationMap:GetZone(y)) then
  2241. table.insert(nList,{x1,y1})
  2242. end
  2243. local x2,y2 = elevationMap:GetNeighbor(x,y,dir2)
  2244. ii = elevationMap:GetIndex(x2,y2)
  2245. if ii >= 0 then
  2246. table.insert(nList,{x2,y2})
  2247. end
  2248. else
  2249. for dir = 1,6,1 do
  2250. local xx,yy = elevationMap:GetNeighbor(x,y,dir)
  2251. local ii = elevationMap:GetIndex(xx,yy)
  2252. if ii >= 0 and pressureMap.data[i] <= pressureMap.data[ii] then
  2253. table.insert(nList,{xx,yy})
  2254. end
  2255. end
  2256. end
  2257. if #nList == 0 or boolGeostrophic and #nList == 1 then
  2258. local cost = moistureMap.data[i]
  2259. rainfallMap.data[i] = cost
  2260. return
  2261. end
  2262. local moisturePerNeighbor = moistureMap.data[i]/#nList
  2263. --drop rain and pass moisture to neighbors
  2264. for n = 1,#nList,1 do
  2265. local xx = nList[n][1]
  2266. local yy = nList[n][2]
  2267. local ii = elevationMap:GetIndex(xx,yy)
  2268. local upLiftDest = math.max(math.pow(pressureMap.data[ii],mc.upLiftExponent),1.0 - temperatureMap.data[ii])
  2269. local cost = GetRainCost(upLiftSource,upLiftDest)
  2270. local bonus = 0.0
  2271. if (elevationMap:GetZone(y) == mc.NPOLAR or elevationMap:GetZone(y) == mc.SPOLAR) then
  2272. bonus = mc.polarRainBoost
  2273. end
  2274. if boolGeostrophic and #nList == 2 then
  2275. if n == 1 then
  2276. moisturePerNeighbor = (1.0 - mc.geostrophicLateralWindStrength) * moistureMap.data[i]
  2277. else
  2278. moisturePerNeighbor = mc.geostrophicLateralWindStrength * moistureMap.data[i]
  2279. end
  2280. end
  2281. --print(string.format("---xx=%d, yy=%d, destPressure uplift = %f, upLiftDest = %f, cost = %f, moisturePerNeighbor = %f, bonus = %f",xx,yy,math.pow(pressureMap.data[ii],mc.upLiftExponent),upLiftDest,cost,moisturePerNeighbor,bonus))
  2282. rainfallMap.data[i] = rainfallMap.data[i] + cost * moisturePerNeighbor + bonus
  2283. --pass to neighbor.
  2284. --print(string.format("---moistureMap.data[ii] = %f",moistureMap.data[ii]))
  2285. moistureMap.data[ii] = moistureMap.data[ii] + moisturePerNeighbor - (cost * moisturePerNeighbor)
  2286. --print(string.format("---dropping %f rain",cost * moisturePerNeighbor + bonus))
  2287. --print(string.format("---passing on %f moisture",moisturePerNeighbor - (cost * moisturePerNeighbor)))
  2288. end
  2289.  
  2290. end
  2291.  
  2292. function GetRainCost(upLiftSource,upLiftDest)
  2293. local cost = mc.minimumRainCost
  2294. cost = math.max(mc.minimumRainCost, cost + upLiftDest - upLiftSource)
  2295. if cost < 0.0 then
  2296. cost = 0.0
  2297. end
  2298. return cost
  2299. end
  2300.  
  2301. function GetDifferenceAroundHex(x,y)
  2302. local avg = elevationMap:GetAverageInHex(x,y,1)
  2303. local i = elevationMap:GetIndex(x,y)
  2304. return elevationMap.data[i] - avg
  2305. --~ local nList = elevationMap:GetRadiusAroundHex(x,y,1)
  2306. --~ local i = elevationMap:GetIndex(x,y)
  2307. --~ local biggestDiff = 0.0
  2308. --~ for n=1,#nList do
  2309. --~ local xx = nList[n][1]
  2310. --~ local yy = nList[n][2]
  2311. --~ local ii = elevationMap:GetIndex(xx,yy)
  2312. --~ local diff = nil
  2313. --~ if elevationMap:IsBelowSeaLevel(x,y) then
  2314. --~ diff = elevationMap.data[i] - elevationMap.seaLevelThreshold
  2315. --~ else
  2316. --~ diff = elevationMap.data[i] - elevationMap.data[ii]
  2317. --~ end
  2318. --~ if diff > biggestDiff then
  2319. --~ biggestDiff = diff
  2320. --~ end
  2321. --~ end
  2322. --~ if biggestDiff < 0.0 then
  2323. --~ biggestDiff = 0.0
  2324. --~ end
  2325. --~ return biggestDiff
  2326. end
  2327.  
  2328. function PlacePossibleOasis(x,y)
  2329. local terrainDesert = GameInfoTypes["TERRAIN_DESERT"];
  2330. local featureOasis = FeatureTypes.FEATURE_OASIS
  2331. local tiles = elevationMap:GetRadiusAroundHex(x,y,1)
  2332. local plot = Map.GetPlot(x,y)
  2333. if not plot:IsHills() and not plot:IsMountain() and plot:GetTerrainType() == terrainDesert then
  2334. local canPlace = true
  2335. for n=1,#tiles do
  2336. local xx = tiles[n][1]
  2337. local yy = tiles[n][2]
  2338. local nPlot = Map.GetPlot(xx,yy)
  2339. if nPlot:GetTerrainType() ~= terrainDesert then
  2340. canPlace = false
  2341. break
  2342. elseif nPlot:GetFeatureType() ~= FeatureTypes.NO_FEATURE then
  2343. canPlace = false
  2344. break
  2345. end
  2346. end
  2347. if canPlace then
  2348. plot:SetFeatureType(featureOasis,-1)
  2349. end
  2350. end
  2351. end
  2352.  
  2353. function PlacePossibleIce(x,y)
  2354. local featureIce = FeatureTypes.FEATURE_ICE
  2355. local plot = Map.GetPlot(x,y)
  2356. local i = temperatureMap:GetIndex(x,y)
  2357. if plot:IsWater() then
  2358. local temp = temperatureMap.data[i]
  2359. local latitude = temperatureMap:GetLatitudeForY(y)
  2360. --local randval = PWRand() * (mc.iceMaxTemperature - mc.minWaterTemp) + mc.minWaterTemp * 2
  2361. local randvalNorth = PWRand() * (mc.iceNorthLatitudeLimit - mc.topLatitude) + mc.topLatitude - 2
  2362. local randvalSouth = PWRand() * (mc.bottomLatitude - mc.iceSouthLatitudeLimit) + mc.iceSouthLatitudeLimit
  2363. --print(string.format("lat = %f, randvalNorth = %f, randvalSouth = %f",latitude,randvalNorth,randvalSouth))
  2364. if latitude > randvalNorth or latitude < randvalSouth then
  2365. plot:SetFeatureType(featureIce,-1)
  2366. end
  2367. end
  2368. end
  2369.  
  2370. function PlacePossibleAtoll(x,y)
  2371. local shallowWater = GameDefines.SHALLOW_WATER_TERRAIN
  2372. local deepWater = GameDefines.DEEP_WATER_TERRAIN
  2373. local featureAtoll = nil
  2374. for thisFeature in GameInfo.Features() do
  2375. if thisFeature.Type == "FEATURE_ATOLL" then
  2376. featureAtoll = thisFeature.ID;
  2377. end
  2378. end
  2379. local plot = Map.GetPlot(x,y)
  2380. local i = temperatureMap:GetIndex(x,y)
  2381. if plot:GetTerrainType() == shallowWater then
  2382. local temp = temperatureMap.data[i]
  2383. local latitude = temperatureMap:GetLatitudeForY(y)
  2384. if latitude < mc.atollNorthLatitudeLimit and latitude > mc.atollSouthLatitudeLimit then
  2385. local tiles = elevationMap:GetRadiusAroundHex(x,y,1)
  2386. local deepCount = 0
  2387. for n=1,#tiles do
  2388. local xx = tiles[n][1]
  2389. local yy = tiles[n][2]
  2390. local nPlot = Map.GetPlot(xx,yy)
  2391. if nPlot:GetTerrainType() == deepWater then
  2392. deepCount = deepCount + 1
  2393. end
  2394. end
  2395. if deepCount >= mc.atollMinDeepWaterNeighbors then
  2396. plot:SetFeatureType(featureAtoll,-1)
  2397. end
  2398. end
  2399. end
  2400. end
  2401. -------------------------------------------------------------------------------
  2402. --functions that Civ needs
  2403. -------------------------------------------------------------------------------
  2404. function GetMapScriptInfo()
  2405. local world_age, temperature, rainfall, sea_level, resources = GetCoreMapOptions()
  2406. return {
  2407. Name = "PerfectWorld 3",
  2408. Description = "Simulated semi-psuedo-quasi-realistic climate",
  2409. IsAdvancedMap = 0,
  2410. SupportsMultiplayer = false,
  2411. IconIndex = 1,
  2412. SortIndex = 1,
  2413. CustomOptions = {
  2414. {
  2415. Name = "Start Placement",
  2416. Values = {
  2417. "Start Anywhere",
  2418. "Largest Continent"
  2419. },
  2420. DefaultValue = 1,
  2421. SortPriority = 1,
  2422. },
  2423. resources},
  2424. };
  2425. end
  2426.  
  2427. function GetMapInitData(worldSize)
  2428. local worldsizes = {
  2429. [GameInfo.Worlds.WORLDSIZE_DUEL.ID] = {42, 28},
  2430. [GameInfo.Worlds.WORLDSIZE_TINY.ID] = {50, 36},
  2431. [GameInfo.Worlds.WORLDSIZE_SMALL.ID] = {60, 42},
  2432. [GameInfo.Worlds.WORLDSIZE_STANDARD.ID] = {80, 56},
  2433. [GameInfo.Worlds.WORLDSIZE_LARGE.ID] = {100, 70},
  2434. [GameInfo.Worlds.WORLDSIZE_HUGE.ID] = {120, 84}
  2435. }
  2436. --~ local worldsizes = {
  2437. --~ [GameInfo.Worlds.WORLDSIZE_DUEL.ID] = {50, 36},
  2438. --~ [GameInfo.Worlds.WORLDSIZE_TINY.ID] = {60, 42},
  2439. --~ [GameInfo.Worlds.WORLDSIZE_SMALL.ID] = {80, 56},
  2440. --~ [GameInfo.Worlds.WORLDSIZE_STANDARD.ID] = {100, 70},
  2441. --~ [GameInfo.Worlds.WORLDSIZE_LARGE.ID] = {120, 84},
  2442. --~ [GameInfo.Worlds.WORLDSIZE_HUGE.ID] = {140, 98}
  2443. --~ }
  2444. local grid_size = worldsizes[worldSize];
  2445. --
  2446. local world = GameInfo.Worlds[worldSize];
  2447. if(world ~= nil) then
  2448. return {
  2449. Width = grid_size[1],
  2450. Height = grid_size[2],
  2451. WrapX = true,
  2452. };
  2453. end
  2454. end
  2455.  
  2456. -------------------------------------------------------------------------------------------
  2457. --ShiftMap Class
  2458. -------------------------------------------------------------------------------------------
  2459. function ShiftMaps()
  2460. --local stripRadius = self.stripRadius;
  2461. local shift_x = 0;
  2462. local shift_y = 0;
  2463.  
  2464. shift_x = DetermineXShift();
  2465.  
  2466. ShiftMapsBy(shift_x, shift_y);
  2467. end
  2468. -------------------------------------------------------------------------------------------
  2469. function ShiftMapsBy(xshift, yshift)
  2470. local W, H = Map.GetGridSize();
  2471. if(xshift > 0 or yshift > 0) then
  2472. local Shift = {}
  2473. local iDestI = 0
  2474. for iDestY = 0, H-1 do
  2475. for iDestX = 0, W-1 do
  2476. local iSourceX = (iDestX + xshift) % W;
  2477.  
  2478. --local iSourceY = (iDestY + yshift) % H; -- If using yshift, enable this and comment out the faster line below. - Bobert13
  2479. local iSourceY = iDestY
  2480.  
  2481. local iSourceI = W * iSourceY + iSourceX
  2482. Shift[iDestI] = elevationMap.data[iSourceI]
  2483. --print(string.format("Shift:%d, %f | eMap:%d, %f",iDestI,Shift[iDestI],iSourceI,elevationMap.data[iSourceI]))
  2484. iDestI = iDestI + 1
  2485. end
  2486. end
  2487. elevationMap.data = Shift --It's faster to do one large table operation here than it is to do thousands of small operations to set up a copy of the input table at the beginning. -Bobert13
  2488. end
  2489. return elevationMap
  2490. end
  2491. -------------------------------------------------------------------------------------------
  2492. function DetermineXShift()
  2493. --[[ This function will align the most water-heavy vertical portion of the map with the
  2494. vertical map edge. This is a form of centering the landmasses, but it emphasizes the
  2495. edge not the middle. If there are columns completely empty of land, these will tend to
  2496. be chosen as the new map edge, but it is possible for a narrow column between two large
  2497. continents to be passed over in favor of the thinnest section of a continent, because
  2498. the operation looks at a group of columns not just a single column, then picks the
  2499. center of the most water heavy group of columns to be the new vertical map edge. ]]--
  2500.  
  2501. -- First loop through the map columns and record land plots in each column.
  2502. local gridWidth, gridHeight = Map.GetGridSize();
  2503. local land_totals = {};
  2504. for x = 0, gridWidth - 1 do
  2505. local current_column = 0;
  2506. for y = 0, gridHeight - 1 do
  2507. local i = y * gridWidth + x + 1;
  2508. if not elevationMap:IsBelowSeaLevel(x,y) then
  2509. current_column = current_column + 1;
  2510. end
  2511. end
  2512. table.insert(land_totals, current_column);
  2513. end
  2514.  
  2515. -- Now evaluate column groups, each record applying to the center column of the group.
  2516. local column_groups = {};
  2517. -- Determine the group size in relation to map width.
  2518. local group_radius = 3;
  2519. -- Measure the groups.
  2520. for column_index = 1, gridWidth do
  2521. local current_group_total = 0;
  2522. --for current_column = column_index - group_radius, column_index + group_radius do
  2523. --Changed how group_radius works to get groups of four. -Bobert13
  2524. for current_column = column_index, column_index + group_radius do
  2525. local current_index = current_column % gridWidth;
  2526. if current_index == 0 then -- Modulo of the last column will be zero; this repairs the issue.
  2527. current_index = gridWidth;
  2528. end
  2529. current_group_total = current_group_total + land_totals[current_index];
  2530. end
  2531. table.insert(column_groups, current_group_total);
  2532. end
  2533.  
  2534. -- Identify the group with the least amount of land in it.
  2535. local best_value = gridHeight * (group_radius + 1); -- Set initial value to max possible.
  2536. local best_group = 1; -- Set initial best group as current map edge.
  2537. for column_index, group_land_plots in ipairs(column_groups) do
  2538. if group_land_plots < best_value then
  2539. best_value = group_land_plots;
  2540. best_group = column_index;
  2541. end
  2542. end
  2543.  
  2544. -- Determine X Shift
  2545. local x_shift = best_group + 2;
  2546.  
  2547. return x_shift;
  2548. end
  2549. ------------------------------------------------------------------------------
  2550. --DiffMap Class
  2551. ------------------------------------------------------------------------------
  2552. --Seperated this from GeneratePlotTypes() to use it in other functions. -Bobert13
  2553.  
  2554. DiffMap = inheritsFrom(FloatMap)
  2555.  
  2556. function GenerateDiffMap(width,height,xWrap,yWrap)
  2557. DiffMap = FloatMap:New(width,height,xWrap,yWrap)
  2558. local i = 0
  2559. for y = 0, height - 1,1 do
  2560. for x = 0,width - 1,1 do
  2561. if elevationMap:IsBelowSeaLevel(x,y) then
  2562. DiffMap.data[i] = 0.0
  2563. else
  2564. DiffMap.data[i] = GetDifferenceAroundHex(x,y)
  2565. end
  2566. i=i+1
  2567. end
  2568. end
  2569.  
  2570. DiffMap:Normalize()
  2571. i = 0
  2572. for y = 0, height - 1,1 do
  2573. for x = 0,width - 1,1 do
  2574. if elevationMap:IsBelowSeaLevel(x,y) then
  2575. DiffMap.data[i] = 0.0
  2576. else
  2577. DiffMap.data[i] = DiffMap.data[i] + elevationMap.data[i] * 1.1
  2578. end
  2579. i=i+1
  2580. end
  2581. end
  2582.  
  2583. DiffMap:Normalize()
  2584. return DiffMap
  2585. end
  2586. -------------------------------------------------------------------------------------------
  2587. function GeneratePlotTypes()
  2588. print("Creating initial map data - PerfectWorld3")
  2589. local gridWidth, gridHeight = Map.GetGridSize();
  2590. --first do all the preliminary calculations in this function
  2591. print(string.format("map size: width=%d, height=%d",gridWidth,gridHeight))
  2592. mc = MapConstants:New()
  2593. PWRandSeed()
  2594.  
  2595. elevationMap = GenerateElevationMap(gridWidth,gridHeight,true,false)
  2596. --elevationMap:Save("elevationMap.csv")
  2597. FillInLakes()
  2598.  
  2599. --now gen plot types
  2600. print("Generating plot types - PerfectWorld3")
  2601. ShiftMaps();
  2602.  
  2603. DiffMap = GenerateDiffMap(gridWidth,gridHeight,true,false);
  2604. rainfallMap, temperatureMap = GenerateRainfallMap(elevationMap)
  2605. --rainfallMap:Save("rainfallMap.csv")
  2606.  
  2607. riverMap = RiverMap:New(elevationMap)
  2608. riverMap:SetJunctionAltitudes()
  2609. riverMap:SiltifyLakes()
  2610. riverMap:SetFlowDestinations()
  2611. riverMap:SetRiverSizes(rainfallMap)
  2612.  
  2613. --find exact thresholds
  2614. local hillsThreshold = DiffMap:FindThresholdFromPercent(mc.hillsPercent,false,true)
  2615. local mountainsThreshold = DiffMap:FindThresholdFromPercent(mc.mountainsPercent,false,true)
  2616. local i = 0
  2617. for y = 0, gridHeight - 1,1 do
  2618. for x = 0,gridWidth - 1,1 do
  2619. local plot = Map.GetPlot(x,y);
  2620. if elevationMap:IsBelowSeaLevel(x,y) then
  2621. plot:SetPlotType(PlotTypes.PLOT_OCEAN, false, false)
  2622. elseif DiffMap.data[i] < hillsThreshold then
  2623. plot:SetPlotType(PlotTypes.PLOT_LAND,false,false)
  2624. --This code makes the game only ever plot flat land if it's within two tiles of
  2625. --the seam. This prevents issues with tiles that don't look like what they are.
  2626. elseif x == 0 or x == 1 or x == gridWidth - 1 or x == gridWidth -2 then
  2627. plot:SetPlotType(PlotTypes.PLOT_LAND,false,false)
  2628. -- Bobert13
  2629. elseif DiffMap.data[i] < mountainsThreshold then
  2630. plot:SetPlotType(PlotTypes.PLOT_HILLS,false,false)
  2631. else
  2632. plot:SetPlotType(PlotTypes.PLOT_MOUNTAIN,false,false)
  2633. end
  2634. i=i+1
  2635. end
  2636. end
  2637. GenerateCoasts();
  2638. end
  2639. ------------------------------------------------------------------------------
  2640.  
  2641. function GenerateTerrain()
  2642. print("Generating terrain - PerfectWorld3")
  2643. local terrainDesert = GameInfoTypes["TERRAIN_DESERT"];
  2644. local terrainPlains = GameInfoTypes["TERRAIN_PLAINS"];
  2645. local terrainSnow = GameInfoTypes["TERRAIN_SNOW"];
  2646. local terrainTundra = GameInfoTypes["TERRAIN_TUNDRA"];
  2647. local terrainGrass = GameInfoTypes["TERRAIN_GRASS"];
  2648.  
  2649. local gridWidth, gridHeight = Map.GetGridSize();
  2650. --first find minimum rain above sea level for a soft desert transition
  2651. local minRain = 100.0
  2652. for y = 0, gridHeight - 1,1 do
  2653. for x = 0,gridWidth - 1,1 do
  2654. local i = elevationMap:GetIndex(x,y)
  2655. if not elevationMap:IsBelowSeaLevel(x,y) then
  2656. if rainfallMap.data[i] < minRain then
  2657. minRain = rainfallMap.data[i]
  2658. end
  2659. end
  2660. end
  2661. end
  2662.  
  2663. --find exact thresholds
  2664. local desertThreshold = rainfallMap:FindThresholdFromPercent(mc.desertPercent,false,true)
  2665. local plainsThreshold = rainfallMap:FindThresholdFromPercent(mc.plainsPercent,false,true)
  2666. for y = 0, gridHeight - 1,1 do
  2667. for x = 0,gridWidth - 1,1 do
  2668. local i = elevationMap:GetIndex(x,y)
  2669. local plot = Map.GetPlot(x, y)
  2670. if not elevationMap:IsBelowSeaLevel(x,y) then
  2671. if rainfallMap.data[i] < desertThreshold then
  2672. if temperatureMap.data[i] < mc.snowTemperature then
  2673. plot:SetTerrainType(terrainSnow,false,false)
  2674. elseif temperatureMap.data[i] < mc.tundraTemperature then
  2675. plot:SetTerrainType(terrainTundra,false,false)
  2676. elseif temperatureMap.data[i] < mc.desertMinTemperature then
  2677. plot:SetTerrainType(terrainPlains,false,false)
  2678. else
  2679. --if rainfallMap.data[i] < (PWRand() * (desertThreshold - minRain) + desertThreshold - minRain)/2.0 + minRain then
  2680. plot:SetTerrainType(terrainDesert,false,false)
  2681. --else
  2682. --plot:SetTerrainType(terrainPlains,false,false)
  2683. --end
  2684. end
  2685. elseif rainfallMap.data[i] < plainsThreshold then
  2686. if temperatureMap.data[i] < mc.snowTemperature then
  2687. plot:SetTerrainType(terrainSnow,false,false)
  2688. elseif temperatureMap.data[i] < mc.tundraTemperature then
  2689. plot:SetTerrainType(terrainTundra,false,false)
  2690. else
  2691. if rainfallMap.data[i] < (PWRand() * (plainsThreshold - desertThreshold) + plainsThreshold - desertThreshold)/2.0 + desertThreshold then
  2692. plot:SetTerrainType(terrainPlains,false,false)
  2693. else
  2694. plot:SetTerrainType(terrainGrass,false,false)
  2695. end
  2696. end
  2697. else
  2698. if temperatureMap.data[i] < mc.snowTemperature then
  2699. plot:SetTerrainType(terrainSnow,false,false)
  2700. elseif temperatureMap.data[i] < mc.tundraTemperature then
  2701. plot:SetTerrainType(terrainTundra,false,false)
  2702. else
  2703. plot:SetTerrainType(terrainGrass,false,false)
  2704. end
  2705. end
  2706. end
  2707. end
  2708. end
  2709. --now we fix things up so that the border of tundra and ice regions are hills
  2710. --this looks a bit more believable. Also keep desert away from tundra and ice
  2711. --by turning it into plains
  2712. for y = 0, gridHeight - 1,1 do
  2713. for x = 0,gridWidth - 1,1 do
  2714. local i = elevationMap:GetIndex(x,y)
  2715. local plot = Map.GetPlot(x, y)
  2716. if not elevationMap:IsBelowSeaLevel(x,y) then
  2717. if plot:GetTerrainType() == terrainSnow then
  2718. local lowerFound = false
  2719. for dir = mc.W,mc.SW,1 do
  2720. local xx,yy = elevationMap:GetNeighbor(x,y,dir)
  2721. local ii = elevationMap:GetIndex(xx,yy)
  2722. if ii ~= -1 then
  2723. local nPlot = Map.GetPlot(xx,yy)
  2724. local terrainVal = nPlot:GetTerrainType()
  2725. if not elevationMap:IsBelowSeaLevel(xx,yy) and terrainVal ~= terrainSnow then
  2726. lowerFound = true
  2727. end
  2728. if terrainVal == terrainDesert then
  2729. nPlot:SetTerrainType(terrainPlains,false,false)
  2730. end
  2731. end
  2732. end
  2733. if lowerFound and plot:GetPlotType() == PlotTypes.PLOT_LAND then
  2734. plot:SetPlotType(PlotTypes.PLOT_HILLS,false,false)
  2735. end
  2736. elseif plot:GetTerrainType() == terrainTundra then
  2737. local lowerFound = false
  2738. for dir = mc.W,mc.SW,1 do
  2739. local xx,yy = elevationMap:GetNeighbor(x,y,dir)
  2740. local ii = elevationMap:GetIndex(xx,yy)
  2741. if ii ~= -1 then
  2742. local nPlot = Map.GetPlot(xx,yy)
  2743. local terrainVal = nPlot:GetTerrainType()
  2744. if not elevationMap:IsBelowSeaLevel(xx,yy) and terrainVal ~= terrainSnow and terrainVal ~= terrainTundra then
  2745. lowerFound = true
  2746. end
  2747. if terrainVal == terrainDesert then
  2748. nPlot:SetTerrainType(terrainPlains,false,false)
  2749. end
  2750. end
  2751. end
  2752. if lowerFound and plot:GetPlotType() == PlotTypes.PLOT_LAND then
  2753. plot:SetPlotType(PlotTypes.PLOT_HILLS,false,false)
  2754. end
  2755. else
  2756. local higherFound = false
  2757. for dir = mc.W,mc.SW,1 do
  2758. local xx,yy = elevationMap:GetNeighbor(x,y,dir)
  2759. local ii = elevationMap:GetIndex(xx,yy)
  2760. if ii ~= -1 then
  2761. local nPlot = Map.GetPlot(xx,yy)
  2762. local terrainVal = nPlot:GetTerrainType()
  2763. if terrainVal == terrainSnow or terrainVal == terrainTundra then
  2764. higherFound = true
  2765. end
  2766. end
  2767. end
  2768. if higherFound and plot:GetPlotType() == PlotTypes.PLOT_HILLS then
  2769. plot:SetPlotType(PlotTypes.PLOT_LAND,false,false)
  2770. end
  2771. end
  2772. end
  2773. end
  2774. end
  2775. end
  2776. ------------------------------------------------------------------------------
  2777. function AddFeatures()
  2778. print("Adding Features PerfectWorld3");
  2779.  
  2780. local terrainPlains = GameInfoTypes["TERRAIN_PLAINS"];
  2781. local featureFloodPlains = FeatureTypes.FEATURE_FLOOD_PLAINS
  2782. local featureIce = FeatureTypes.FEATURE_ICE
  2783. local featureJungle = FeatureTypes.FEATURE_JUNGLE
  2784. local featureForest = FeatureTypes.FEATURE_FOREST
  2785. local featureOasis = FeatureTypes.FEATURE_OASIS
  2786. local featureMarsh = FeatureTypes.FEATURE_MARSH
  2787.  
  2788. local gridWidth, gridHeight = Map.GetGridSize();
  2789.  
  2790. local zeroTreesThreshold = rainfallMap:FindThresholdFromPercent(mc.zeroTreesPercent,false,true)
  2791. local jungleThreshold = rainfallMap:FindThresholdFromPercent(mc.junglePercent,false,true)
  2792. --local marshThreshold = rainfallMap:FindThresholdFromPercent(marshPercent,false,true)
  2793. for y = 0, gridHeight - 1,1 do
  2794. for x = 0,gridWidth - 1,1 do
  2795. local i = elevationMap:GetIndex(x,y)
  2796. local plot = Map.GetPlot(x, y)
  2797. if not plot:IsWater() then
  2798. if rainfallMap.data[i] < jungleThreshold then
  2799. if not plot:IsMountain() then
  2800. local treeRange = jungleThreshold - zeroTreesThreshold
  2801. if rainfallMap.data[i] > PWRand() * treeRange + zeroTreesThreshold then
  2802. if temperatureMap.data[i] > mc.treesMinTemperature then
  2803. plot:SetFeatureType(featureForest,-1)
  2804. end
  2805. end
  2806. end
  2807. else
  2808. local marshRange = 1.0 - jungleThreshold
  2809. if rainfallMap.data[i] > PWRand() * marshRange + jungleThreshold and temperatureMap.data[i] > mc.treesMinTemperature then
  2810. plot:SetPlotType(PlotTypes.PLOT_LAND,false,false)
  2811. plot:SetFeatureType(featureMarsh,-1)
  2812. else
  2813. if not plot:IsMountain() then
  2814. if temperatureMap.data[i] < mc.jungleMinTemperature and temperatureMap.data[i] > mc.treesMinTemperature then
  2815. plot:SetFeatureType(featureForest,-1)
  2816. elseif temperatureMap.data[i] >= mc.jungleMinTemperature then
  2817. if plot:IsHills() then
  2818. --jungle on hill looks terrible in Civ5. Can't use it.
  2819. plot:SetFeatureType(featureForest,-1)
  2820. plot:SetTerrainType(terrainGrass,false,false)
  2821. else
  2822. plot:SetFeatureType(featureJungle,-1)
  2823. plot:SetTerrainType(terrainPlains,false,false)
  2824. end
  2825. end
  2826. end
  2827. end
  2828. end
  2829. if plot:CanHaveFeature(featureFloodPlains) then
  2830. plot:SetFeatureType(featureFloodPlains,-1)
  2831. end
  2832. end
  2833. end
  2834. end
  2835. for y = 0, gridHeight - 1,1 do
  2836. for x = 0,gridWidth - 1,1 do
  2837. local plot = Map.GetPlot(x, y)
  2838. if not plot:IsWater() then
  2839. PlacePossibleOasis(x,y)
  2840. else
  2841. PlacePossibleAtoll(x,y)
  2842. PlacePossibleIce(x,y)
  2843. end
  2844. end
  2845. end
  2846. end
  2847.  
  2848. function StartPlotSystem()
  2849. -- Get Resources setting input by user.
  2850. local res = Map.GetCustomOption(2)
  2851. if res == 6 then
  2852. res = 1 + Map.Rand(3, "Random Resources Option - Lua");
  2853. end
  2854.  
  2855. local starts = Map.GetCustomOption(1)
  2856. local divMethod = nil
  2857. if starts == 1 then
  2858. divMethod = 2
  2859. else
  2860. divMethod = 1
  2861. end
  2862.  
  2863. print("Creating start plot database.");
  2864. local start_plot_database = AssignStartingPlots.Create()
  2865.  
  2866. print("Dividing the map in to Regions.");
  2867. -- Regional Division Method 2: Continental or 1:Terra
  2868. local args = {
  2869. method = divMethod,
  2870. resources = res,
  2871. };
  2872. start_plot_database:GenerateRegions(args)
  2873.  
  2874. print("Choosing start locations for civilizations.");
  2875. start_plot_database:ChooseLocations()
  2876.  
  2877. print("Normalizing start locations and assigning them to Players.");
  2878. start_plot_database:BalanceAndAssign()
  2879.  
  2880. --error(":P")
  2881. print("Placing Natural Wonders.");
  2882. start_plot_database:PlaceNaturalWonders()
  2883.  
  2884. print("Placing Resources and City States.");
  2885. start_plot_database:PlaceResourcesAndCityStates()
  2886. end
  2887.  
  2888. function AddRivers()
  2889. local gridWidth, gridHeight = Map.GetGridSize();
  2890. for y = 0, gridHeight - 1,1 do
  2891. for x = 0,gridWidth - 1,1 do
  2892. local plot = Map.GetPlot(x, y)
  2893.  
  2894. local WOfRiver, NWOfRiver, NEOfRiver = riverMap:GetFlowDirections(x,y)
  2895.  
  2896. if WOfRiver == FlowDirectionTypes.NO_FLOWDIRECTION then
  2897. plot:SetWOfRiver(false,WOfRiver)
  2898. else
  2899. local xx,yy = elevationMap:GetNeighbor(x,y,mc.E)
  2900. local nPlot = Map.GetPlot(xx,yy)
  2901. if plot:IsMountain() and nPlot:IsMountain() then
  2902. plot:SetPlotType(PlotTypes.PLOT_LAND,false,false)
  2903. end
  2904. plot:SetWOfRiver(true,WOfRiver)
  2905. --print(string.format("(%d,%d)WOfRiver = true dir=%d",x,y,WOfRiver))
  2906. end
  2907.  
  2908. if NWOfRiver == FlowDirectionTypes.NO_FLOWDIRECTION then
  2909. plot:SetNWOfRiver(false,NWOfRiver)
  2910. else
  2911. local xx,yy = elevationMap:GetNeighbor(x,y,mc.SE)
  2912. local nPlot = Map.GetPlot(xx,yy)
  2913. if plot:IsMountain() and nPlot:IsMountain() then
  2914. plot:SetPlotType(PlotTypes.PLOT_LAND,false,false)
  2915. end
  2916. plot:SetNWOfRiver(true,NWOfRiver)
  2917. --print(string.format("(%d,%d)NWOfRiver = true dir=%d",x,y,NWOfRiver))
  2918. end
  2919.  
  2920. if NEOfRiver == FlowDirectionTypes.NO_FLOWDIRECTION then
  2921. plot:SetNEOfRiver(false,NEOfRiver)
  2922. else
  2923. local xx,yy = elevationMap:GetNeighbor(x,y,mc.SW)
  2924. local nPlot = Map.GetPlot(xx,yy)
  2925. if plot:IsMountain() and nPlot:IsMountain() then
  2926. plot:SetPlotType(PlotTypes.PLOT_LAND,false,false)
  2927. end
  2928. plot:SetNEOfRiver(true,NEOfRiver)
  2929. --print(string.format("(%d,%d)NEOfRiver = true dir=%d",x,y,NEOfRiver))
  2930. end
  2931. end
  2932. end
  2933. end
  2934.  
  2935. function oceanMatch(x,y)
  2936. local plot = Map.GetPlot(x,y)
  2937. if plot:GetPlotType() == PlotTypes.PLOT_OCEAN then
  2938. return true
  2939. end
  2940. return false
  2941. end
  2942.  
  2943. function jungleMatch(x,y)
  2944. local terrainGrass = GameInfoTypes["TERRAIN_GRASS"];
  2945. local plot = Map.GetPlot(x,y)
  2946. if plot:GetFeatureType() == FeatureTypes.FEATURE_JUNGLE then
  2947. return true
  2948. --include any mountains on the border as part of the desert.
  2949. elseif (plot:GetFeatureType() == FeatureTypes.FEATURE_MARSH or plot:GetFeatureType() == FeatureTypes.FEATURE_FOREST) and plot:GetTerrainType() == terrainGrass then
  2950. local nList = elevationMap:GetRadiusAroundHex(x,y,1)
  2951. for n=1,#nList do
  2952. local xx = nList[n][1]
  2953. local yy = nList[n][2]
  2954. local ii = elevationMap:GetIndex(xx,yy)
  2955. if 11 ~= -1 then
  2956. local nPlot = Map.GetPlot(xx,yy)
  2957. if nPlot:GetFeatureType() == FeatureTypes.FEATURE_JUNGLE then
  2958. return true
  2959. end
  2960. end
  2961. end
  2962. end
  2963. return false
  2964. end
  2965.  
  2966. function desertMatch(x,y)
  2967. local terrainDesert = GameInfoTypes["TERRAIN_DESERT"];
  2968. local plot = Map.GetPlot(x,y)
  2969. if plot:GetTerrainType() == terrainDesert then
  2970. return true
  2971. --include any mountains on the border as part of the desert.
  2972. elseif plot:GetPlotType() == PlotTypes.PLOT_MOUNTAIN then
  2973. local nList = elevationMap:GetRadiusAroundHex(x,y,1)
  2974. for n=1,#nList do
  2975. local xx = nList[n][1]
  2976. local yy = nList[n][2]
  2977. local ii = elevationMap:GetIndex(xx,yy)
  2978. if 11 ~= -1 then
  2979. local nPlot = Map.GetPlot(xx,yy)
  2980. if nPlot:GetPlotType() ~= PlotTypes.PLOT_MOUNTAIN and nPlot:GetTerrainType() == terrainDesert then
  2981. return true
  2982. end
  2983. end
  2984. end
  2985. end
  2986. return false
  2987. end
  2988.  
  2989. function DetermineContinents()
  2990. print("Determining continents for art purposes (PerfectWorld)")
  2991. -- Each plot has a continent art type. Mixing and matching these could look
  2992. -- extremely bad, but there is nothing technical to prevent it. The worst
  2993. -- that will happen is that it can't find a blend and draws red checkerboards.
  2994.  
  2995. -- Command for setting the art type for a plot is: <plot object>:SetContinentArtType(<art_set_number>)
  2996.  
  2997. -- CONTINENTAL ART SETS
  2998. -- 0) Ocean
  2999. -- 1) America
  3000. -- 2) Asia
  3001. -- 3) Africa
  3002. -- 4) Europe
  3003.  
  3004. -- Here is an example that sets all land in the world to use the European art set.
  3005.  
  3006. --~ for i, plot in Plots() do
  3007. --~ if plot:IsWater() then
  3008. --~ plot:SetContinentArtType(0)
  3009. --~ else
  3010. --~ plot:SetContinentArtType(4)
  3011. --~ end
  3012. --~ end
  3013.  
  3014. local continentMap = PWAreaMap:New(elevationMap.width,elevationMap.height,elevationMap.wrapX,elevationMap.wrapY)
  3015. continentMap:DefineAreas(oceanMatch)
  3016. table.sort(continentMap.areaList,function (a,b) return a.size > b.size end)
  3017.  
  3018. --check for jungle
  3019. for y=0,elevationMap.height - 1,1 do
  3020. for x=0,elevationMap.width - 1,1 do
  3021. local i = elevationMap:GetIndex(x,y)
  3022. local area = continentMap:GetAreaByID(continentMap.data[i])
  3023. area.hasJungle = false
  3024. end
  3025. end
  3026. for y=0,elevationMap.height - 1,1 do
  3027. for x=0,elevationMap.width - 1,1 do
  3028. local plot = Map.GetPlot(x,y)
  3029. if plot:GetFeatureType() == FeatureTypes.FEATURE_JUNGLE then
  3030. local i = elevationMap:GetIndex(x,y)
  3031. local area = continentMap:GetAreaByID(continentMap.data[i])
  3032. area.hasJungle = true
  3033. end
  3034. end
  3035. end
  3036. local firstArtStyle = PWRandint(1,3)
  3037. print("firstArtStyle = %d",firstArtStyle)
  3038. for n=1,#continentMap.areaList do
  3039. --print(string.format("area[%d] size = %d",n,desertMap.areaList[n].size))
  3040. -- if not continentMap.areaList[n].trueMatch and not continentMap.areaList[n].hasJungle then
  3041. if not continentMap.areaList[n].trueMatch then
  3042. continentMap.areaList[n].artStyle = (firstArtStyle % 4) + 1
  3043. --print(string.format("area[%d] size = %d, artStyle = %d",n,continentMap.areaList[n].size,continentMap.areaList[n].artStyle))
  3044. firstArtStyle = firstArtStyle + 1
  3045. end
  3046. end
  3047. for y=0,elevationMap.height - 1,1 do
  3048. for x=0,elevationMap.width - 1,1 do
  3049. local plot = Map.GetPlot(x,y)
  3050. local i = elevationMap:GetIndex(x,y)
  3051. local area = continentMap:GetAreaByID(continentMap.data[i])
  3052. local artStyle = area.artStyle
  3053. if plot:IsWater() then
  3054. plot:SetContinentArtType(0)
  3055. elseif jungleMatch(x,y) then
  3056. plot:SetContinentArtType(4)
  3057. else
  3058. plot:SetContinentArtType(artStyle)
  3059. end
  3060. end
  3061. end
  3062. --Africa has the best looking deserts, so for the biggest
  3063. --desert use Africa. America has a nice dirty looking desert also, so
  3064. --that should be the second biggest desert.
  3065. local desertMap = PWAreaMap:New(elevationMap.width,elevationMap.height,elevationMap.wrapX,elevationMap.wrapY)
  3066. desertMap:DefineAreas(desertMatch)
  3067. table.sort(desertMap.areaList,function (a,b) return a.size > b.size end)
  3068. local largestDesertID = nil
  3069. local secondLargestDesertID = nil
  3070. for n=1,#desertMap.areaList do
  3071. --print(string.format("area[%d] size = %d",n,desertMap.areaList[n].size))
  3072. if desertMap.areaList[n].trueMatch then
  3073. if largestDesertID == nil then
  3074. largestDesertID = desertMap.areaList[n].id
  3075. else
  3076. secondLargestDesertID = desertMap.areaList[n].id
  3077. break
  3078. end
  3079. end
  3080. end
  3081. for y=0,elevationMap.height - 1,1 do
  3082. for x=0,elevationMap.width - 1,1 do
  3083. local plot = Map.GetPlot(x,y)
  3084. local i = elevationMap:GetIndex(x,y)
  3085. if desertMap.data[i] == largestDesertID then
  3086. plot:SetContinentArtType(3)
  3087. elseif desertMap.data[i] == secondLargestDesertID then
  3088. plot:SetContinentArtType(1)
  3089. end
  3090. end
  3091. end
  3092.  
  3093. end
  3094.  
  3095. ------------------------------------------------------------------------------
  3096.  
  3097. --~ mc = MapConstants:New()
  3098. --~ PWRandSeed()
  3099.  
  3100. --~ elevationMap = GenerateElevationMap(100,70,true,false)
  3101. --~ FillInLakes()
  3102. --~ elevationMap:Save("elevationMap.csv")
  3103.  
  3104. --~ rainfallMap, temperatureMap = GenerateRainfallMap(elevationMap)
  3105. --~ temperatureMap:Save("temperatureMap.csv")
  3106. --~ rainfallMap:Save("rainfallMap.csv")
  3107.  
  3108. --~ riverMap = RiverMap:New(elevationMap)
  3109. --~ riverMap:SetJunctionAltitudes()
  3110. --~ riverMap:SiltifyLakes()
  3111. --~ riverMap:SetFlowDestinations()
  3112. --~ riverMap:SetRiverSizes(rainfallMap)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement