[Map Script] PerfectWorld.py

Ever wondered what happens when you plug real earth altitude data into PerfectWorld? The answer is here. :)

I found a reasonable relief map of the world, converted it to a grayscale image of size 144x80, and used it in the script to initialize the height values of each plot. After adding a tiny bit of fractal noise (20% as strong as the earth data), the map of course was run through the usual wind, rain, and temperature functions and the terrain was all generated. I've tweaked the image a bit and the PerfectWorld parameters a lot to make it seem pretty reasonable. Here's an example start using that derivative script, with the following settings:

-Huge map (144x80)
-Emperor difficulty
-Normal speed
-Aggressive AI
-Raging Barbs

It should be pretty fun to play out. If you want a save with different settings, I can take requests. I don't want to post the script itself yet because it's still changing every few hours. ;)

This is a fun idea! Thanks for the upload I am enjoying this map and would like to see more!
 
I finally released my mod if anyone's curious about what Cephalo's script can do with a little bit of creativity in the terrain types. :)

Maybe some of the praise I sang about this map script will make Cephalo famous too...
 
I finally released my mod if anyone's curious about what Cephalo's script can do with a little bit of creativity in the terrain types. :)

Maybe some of the praise I sang about this map script will make Cephalo famous too...

Cool, I've been waiting for this. Hopefully I'll have time this weekend to try it out. Thanks again for all your help in ironing out the bugs.
 
I just had an interesting idea on how to improve continent shapes. That idea is to sink coastal tiles.

The premise is that big, round continents have a smaller percent of coastal tiles than long, skinny, winding ones. I like the former shape better. By eliminating coastal tiles, you eliminate land tiles mostly from the latter type. Of course, to compensate you have to up the land tile % first.

Another side effect this has is that it will tend to separate continents by wider strips of water, making them unreachable by galley. (There is still plenty of land reachable by galley though. Remember we're adding more land, and some of the long continents are going to be broken apart into galley-reachable islands.) Because we're now artificially separating continents, we can up the land percent even more.

Here is the base method you need to add to HeightMap:
Code:
	def FloodCoast(self, minOrthogonals, minTotalAdjacents, chance):
		#Each land plot orthogonal to at least <minOrthogonals> water plots and
		#adjacent to at least <minTotalAdjacents> water plots becomes water
		#with probability <chance>.
		floodMap = array('d')
		for i in range(0,self.mapHeight*self.mapWidth):
			floodMap.append(self.OCEAN)
		for y in range(self.mapHeight):
			for x in range(self.mapWidth):
				i = self.GetIndex(x,y)
				if self.plotMap[i] != self.OCEAN:
					adjCount = 0
					orthCount = 0
					for yy in range(y-1,y+2):
						for xx in range(x-1,x+2):
							ii = self.GetIndex(xx,yy)
							if self.plotMap[ii] == self.OCEAN:
								adjCount += 1
								if xx==x or yy==y:
									orthCount += 1
					if(orthCount <= minOrthogonals or adjCount <= minTotalAdjacents or chance <= PWRand.random()):
						floodMap[i] = self.plotMap[i]
		self.plotMap = floodMap

There's a lot of ways to use this; here's what I'm trying right now. It's pretty arbitrary.

Instead of
Code:
		self.GenerateFinalPlotTypes()
		self.FillInLakes()
I have
Code:
		self.GenerateFinalPlotTypes()
		self.FloodCoast(1,1,.10)
		self.FillInLakes()
		self.FloodCoast(2,2,.9)
		self.FloodCoast(1,2,.25)
		self.FloodCoast(2,5,.9)
		self.FloodCoast(1,2,.35)

I also upped the land percent to .38 and I'm using my modifications to the tectonics that allow plate boundaries to be lowered instead of raised. Finally I removed the initial random peaks (1s) from the heightmap generation. I just leave everything initially at zero before the midpoint displacement.

I'll try to post some screenshots soon.
 
Hi,

I learned of this great map script through seven05's world piece mod. This script is totally different from what ive seen in vannilla. Looking at some maps i thought id try and tone down the desert area. I did this by decreasing the desert percent by 0.1 and increasing the plainspercent by 0.1. Doing this i found that the desert area was a bit too little in comparison to your latest build. Should i look at other factors?

I found that, imo, the desert area a bit too big. I dont know if maybe increasing the number of oasis entice the ai to found some cities in the desert since now they wont even though there is incense to be found in abundance.

Also some civs like the spanish start too close to colder climates. This i found on large map 18 civs. moreover they were my neighbours (me as korea)
I plugged this mapscript into the total realism mod and it works, all extra resouces also are seeded.
 
Some of those results (like the civ starting positions) are specific to my mod, not the map script :)

I answered you in my mod thread but I'll answer you here for the benfit of evrybody else. The changes to the thresholds need to be very small, 0.1 is a big change so you can try adjusting it in 0.01 incriments or something a little bigger like 0.025. The problem that you'll run into is that the deserts are based on the continent shapes & sizes so different maps will produce different results even with the same settings. So make sure you try any changes out on several maps, not just one :)

There are also settings for rain noise and smoothing below all of the threshold values. I tend to agree with Cephalo's comments in the code that increasing those negates the climate effect to a degree, however a very small change in rain noise can help break up the desert with scattered plains tiles. Currently I have those values set to:

RainNoise = .2
RainSmoothingFactor = .02

You can try changing them to .25 and .025 respectively.

For those of you using the 'stock' PerfectWorld.py script you'll want even lower numbers, I've modified the rain code slightly since I relocated the temperature change with altitude code so I needed stronger noise for any effect at all.
 
Ok, version 1.07 coming soon.

I fixed the problem with the funky river mouths! Placing lake tiles after removing rivers did not update the river crossing count stored in CvPlot. This problem affected not only lakes but also any harbors I may have created with the lake system. I tried removing rivers after placing lakes but that didn't work, so I replaced the affected rivers after lake placement and that solved the problem.

I also took steps to ensure that at least one resource of each type is placed on a map, and I did this carefully to minimize the violation of the XML rules which are very strict for a reason.

The next thing I want to do for this update is solve the problem of boosting resources without having enough food to utilize the added resources. No food is a worse thing than the the 1.06 version currently recognizes. I looked at the changes to city evaluation made by SevenSpirits, and I agree with many of issues that I saw in his code, but I'm not sure if I identified them all.

To summarize, some starting locations do not have enough food. It is impossible to completely eliminate the possiblility of a completely desert or tundra start, because sometimes the major continents end up very inhospitable. I can add fish, I can add commerce and production. Is that OK as long as I can add enough fish? That's the only problem that I'm seeing with 1.06 is that it adds resources to a starting city that can't be used. What other issues are there?
 
I actually ended up not using my last code to "flood" the coasts after all. I think I was getting lucky with my results because I don't like what it's doing now.

However, I did write two other functions for Heightmap which I am liking a lot. One is a simple aesthetic one that shifts the entire map sideways so as little land as possible lies on the seam imposed by the map being rectangular. The only drawback of this is that when you get Calendar it gives you a tiny bit of extra information.

The second one is more interesting. It figures out the areas in the ocean where, if there was land, that land would not be reachable by galley from existing land. It then slightly raises those tiles - causing some of them to become land. I use it after determining the sea level but before generating the plot types, so this results in more land overall without any additional connectivity and no more risk of making a large supercontinent. Some screenshots are attached of large maps using both of these changes. For these I have LandPercent at .27; it could probably stand to be a little lower.

I think it makes the world a bit less realistic but I like how it ends up playing a lot. There's just so much more interesting land to explore.

The code to raise level of oceans. Call by putting self.RaiseOceanAreas() right after self.FindSeaLevel().
Spoiler :
Code:
	def RaiseOceanAreas(self):
		#init ocean map to be 1 at every water space
		oceanMap = array('i')
		for i in range(0,self.mapHeight*self.mapWidth):
			oceanMap.append(0)	
		
		csea = 0
		cland = 0
		ctot = 0
		
		for y in range(1,self.mapHeight-1):
			for x in range(0,self.mapWidth):
				i = self.GetIndex(x,y)
				if self.map[i] <= self.seaLevel:
					oceanMap[i] = 1
					
#		mapString = ""
#		for y in range(0,self.mapHeight):
#			for x in range(0,self.mapWidth):
#				i = self.GetIndex(x,y)	
#				if oceanMap[i] == 1:
#					mapString += "."
#				else:
#					mapString += "X"
#			mapString += "\n"
#		print "Ocean Map"
#		print mapString
		
		#shrink it, now it's only ocean spaces
		oceanMap = self.ShrinkOceanAreaBits(oceanMap, 1)
		#again, now there's a buffer of ocean tiles
		oceanMap = self.ShrinkOceanAreaBits(oceanMap, 1)
		#one more time for a buffer for any land we create
		outerOceanMap = oceanMap
		oceanMap = self.ShrinkOceanAreaBits(oceanMap, 1)
		#now some random shrinkage
		oceanMap = self.ShrinkOceanAreaBits(oceanMap, .4)
		oceanMap = self.ShrinkOceanAreaBits(oceanMap, .4)
		oceanMap = self.ShrinkOceanAreaBits(oceanMap, .4)
		
		
		if self.mapWidth >= 88:
			oceanMap = self.ShrinkOceanAreaBits(oceanMap, .3)
		if self.mapWidth >= 104:
			oceanMap = self.ShrinkOceanAreaBits(oceanMap, .3)
		if self.mapWidth >= 128:
			oceanMap = self.ShrinkOceanAreaBits(oceanMap, .3)
		
#		mapString = ""
#		for y in range(0,self.mapHeight):
#			for x in range(0,self.mapWidth):
#				i = self.GetIndex(x,y)	
#				if oceanMap[i] == 1:
#					mapString += "."
#				else:
#					mapString += "O"
#			mapString += "\n"
#		print "Ocean Map after shrinking"
#		print mapString
					
		innerOceanMap = self.ShrinkOceanAreaBits(oceanMap, 1)

		#OK, now it's safe to raise the ocean up!
		for y in range(self.mapHeight):
			for x in range(self.mapWidth):
				i = self.GetIndex(x,y)
				if innerOceanMap[i] == 1:
					self.map[i] = self.map[i]  + .4
				elif oceanMap[i] == 1:
					self.map[i] = (self.map[i] + self.seaLevel)/2.0 + .2
				elif outerOceanMap[i] == 1:
					self.map[i] = self.seaLevel - .01
					
		
		
	def ShrinkOceanAreaBits(self, map, chance):
		#Each 'True' adjacent to at least one 'False' becomes a 'False' with probability <chance>
		newMap = array('i')
		for i in range(0,self.mapHeight*self.mapWidth):
			newMap.append(0)
		for y in range(self.mapHeight):
			for x in range(self.mapWidth):
				i = self.GetIndex(x,y)
				if map[i] == 1:
					newMap[i] = 1
					if (chance == 1) or (chance > PWRand.random()):
						for yy in range(y-1,y+2):
							if newMap[i] == 0:
								break
							for xx in range(x-1,x+2):
								ii = self.GetIndex(xx,yy)
								if map[ii] == 0:
									newMap[i] = 0
									break
		
		return newMap

The code to realign the map so less land is on the international date line. Use by calling self.ShiftLandToMiddle() after self.FillInLakes().
Spoiler :
Code:
	def ShiftLandToMiddle(self):
		bestColumn = -1
		bestColumnSeaTileCount = -1
		for x in range(self.mapWidth):
			columnSeaTileCount = 0
			for y in range(self.mapHeight):
				i = self.GetIndex(x,y)
				if self.plotMap[i] == self.OCEAN:
					columnSeaTileCount += 1
			if columnSeaTileCount > bestColumnSeaTileCount:
				bestColumnSeaTileCount = columnSeaTileCount
				bestColumn = x
			elif (columnSeaTileCount == bestColumnSeaTileCount) and (PWRand.random() < .25):
				bestColumn = x
		if bestColumn <= 0:
			return
		newPlotMap = array('i')
		#initialize map with 0CEAN
		for i in range(0,self.mapHeight*self.mapWidth):
			newPlotMap.append(self.OCEAN)
		newMap = array('d')
		#initialize map with zeros
		for i in range(0,self.mapHeight*self.mapWidth):
			newMap.append(0.0)
		for x in range(self.mapWidth):
			xprime = x - bestColumn - 1
			for y in range(self.mapHeight):
				i = self.GetIndex(x,y)
				iprime = self.GetIndex(xprime,y)
				newPlotMap[iprime] = self.plotMap[i]
				newMap[iprime] = self.map[i]
		self.map = newMap
		self.plotMap = newPlotMap
 
Ok, 1.07 is released.

I fixed the river bug.

Added some stuff to the resource placer so that resources are guaranteed to appear unless random factors don't allow them.

Maps will now be more likely to have a new world.

The starting plot normalizer has been improved so that non-food resources aren't placed that can't be used. Food will be placed instead.

Let me know if you run into any bugs or problems.
 
Yay, more merging work :)

It'll probably be a week or two before I can get this into my mod and test it. I'll let you know if I run into any problems, of course once I start changing things...
 
The only thing about this new version that is less than ideal is the way I increased the new world frequency. I simply sank the middle of the map in the X direction so that a vertical line in the middle is diminished somewhat. This works well to divide up the world east/west, but why can't the new world be south? or north? or northwest?

I felt that I was getting a 'coastal pangea' far too often. Usually, the map would be fine except for some island chain coastally connecting several major continents. I would prefer to find a way to analyze the shape of those large masses and pinch them off in the middle in the least noticible way.
 
Ah... Ok, I'll leave that bit out then since I use a simple pair of 'canyons' and carve them out of the heightmap before applying plate tectonics. They're random so I don't get obvious straight lines or anything like that and you can almost see them with my new 'deep ocean' terrain :)

I also have an optimized sea level finder, typically it's done in 2-3 passes with more accurate results than your original method.

I think the only way to ensure you have good continental seperation is to generate the heightmap like the default scripts generate landmasses. So select a number of regions on the map and build the land up within each region leaving a buffer of zero height plots between each region. You won't be able to use simple midpoint displacement though or you'll end up with square-ish continents frequently so you'll need some sort of fractal ridge generator. Perhaps something like an overlay layer similar to the plate generation that you use would also work, so generate the base heightmap first, then a second layer of continental shapes. When I first found your script I had played around with that, I generated plots using the default fractal world and then used them for the continental shapes by parsing the plot map and subtracting from the heightmap on every plot that was ocean. It was way too slow though since I was effectively generating two maps.

Oh, and if you're curious what an extra shade of ocean terrain can do for a map, here's a screenshot that shows the effect well on the minimap.
 
Oh, and if you're curious what an extra shade of ocean terrain can do for a map, here's a screenshot that shows the effect well on the minimap.

Yeah, I noticed that when I was messing around with your mod. It does look very nice. Hey, didn't you once say that you were using multiple grassland/plains types? I didn't notice that in there. Did you decide against it for some reason?
 
They're all there, it's just subtle. I have three climate variations (warm, temperate and cool) of most terrain types but the different between either warm & temperate or cool & temperate isn't glaring so they blend fairly well. That screenshot only shows the warm climate so it's not a good example. The only thing I haven't worked out in a way that looks good is altitude based terrains (other than the deep ocean). Unfortunately the map resolution is just too coarse to make anything look good consistantly.

Your script has made a bunch of cool things possible too. With the climate info being used to generate the terrains I have been able to work up a somewhat realistic global warming effect that actually warms the terrain. I've also been able to enforce global warming related weather events occuring in propper climates. Its kind of funny, the maps look awesome with my modified PerfectWorld script, and then you run one of the standard maps and it just looks... bleh. Even with the new terrain kinda-sorta supported in the standard scripts they just can't compete.
 
I love this map script. Is there any way to get it to work with mods? :(

In theory, the only mods it won't work with are one's that use custom feature types or custom terrain types.
 
If you can't get it to work with a mod check the mod's ini file and see if public maps are enabled. Assuming you have the mapscript in the right place (Civ4 directory/Public Maps, NOT in your my documents folder) it should work fine for most mods.
 
I love this map script. Is there any way to get it to work with mods? :(
I love it too... works just fine in my mod.
 
Top Bottom