[Map Script] PerfectWorld.py

cephalo

Deity
Joined
Jul 26, 2007
Messages
2,058
Location
Missouri, USA
Hi Folks!

This file has been superceded by PerfectWorld2, which you can get from
this thread. The original PerfectWorld map script is available below for those who prefer it to the new one.

This map script for Warlords or BtS and most mods based on Civ4 generates a random, earth-like map, usually with a 'New World' with no starting locations that can only be reached with ocean going technology. Landforms are created using a random heightfield and a plate tectonic scheme. Climate is simulated using the interaction between the landforms and geostrophic winds. Though great pains are taken to semi-accurately simulate landforms and climate, the goal must be to make unpredictible, beautiful looking maps that are fun to play on.

Download the original version of PerfectWorld here from the bottom of this thread.

To use this map, put it in your Civilization 4\PublicMaps folder, not in your My Documents\My Games\Civilization 4\PublicMaps folder.

-- Map Options --
New World Rules:
Start in Old World(Default): Reserves some continents to act as a new world
Start Anywhere: Disables the new world functionality

Pangaea Rules:
Break Pangaeas(Default): Breaks up Pangaeas with a simulated meteor shower (really big meteors)
Allow Pangaeas: Allow pangaeas to exist. Keep in mind that this will likely eliminate any 'New World' as well.

-- Summary of creation process: --
First, a random heightfield is created using midpoint displacement. The resulting altitudes are then modified by a plate tectonics scheme that grows random plates and raises the altitudes near the plate borders to create mountain ranges and island chains.

In generating the plot types from a heightmap, I had found that using peaks for high altitude and land for less altitude created large clusters of peaks, surrounded by a donut of hills, surrounded again by a donut of land. This looked absolutely terrible for Civ, so I made it such that peaks and hills are determined by altitude *differences* rather than by absolute altitude. This approach looks much better and more natural.

The terrain generator gives the other needed visual cues to communicate altitude. Since air temperature gets colder with altitude, the peaks will be plots of ice and tundra, even near the equator, if the altitude is high enough. Prevailing winds, temperature and rainfall are all simulated in the terrain generator. You will notice that the deserts and rainforests are where they should be, as well as rain shadows behind mountain ranges.

Rivers and lakes are also generated from the heightmap and follow accurate drainage paths, although with such a small heightmap some randomness needs to be thrown in to prevent rivers from being merely straight lines.

Map bonuses are placed following the XML Rules but slightly differently than the default implimentation to better accomodate this map script.

I've always felt that the most satisfying civ games are the ones that provide a use for those explorers and caravels. Though the map generator does not explicitly create a 'New World', it will take advantage of any continents that can serve that purpose. No starting locations will be placed on these continents. Therefore, the likelyhood of a significant new world is very high, but not guaranteed. It might also come in the form of multiple smaller 'New Worlds' rather than a large continent.

Enjoy!

Here are some screenshots to illustrate some of the features of PerfectWorld.

Here is a typical example of a standard sized map overview.


From the same map as above, here is a valley near the equator. Notice how the terrain and features subtly communicate altitude. The hot jungle lowlands are kindof snaking through some higher, and slightly colder, altitudes.


From the same map again, here is a nice coastline. This continent is part of the 'New World' so no starting places were put here.


Here is an overview of a huge sized map. It looks like this world found itself on the wrong side of some very large meteors in the distant past. Meteors are used to break up pangaeas which naturally interfere with the new world functionality, but if you happen to like pangaeas, you can turn this option off with the many tuning variables at the beginning of the script.


From the huge map now, another example of altitude affecting the terrain. This northern peninsula has a ridge of tundra which, along with the direction of the rivers, helps create an illusion of altitude.


Here is an example on the huge map how resources are placed. I captured a junction of three continents to show that, to encourage trading, different continents have different resource sets. One continent has wheat and pigs, another cows and corn, and the other sheep and rice. These resources are designed in the XML to avoid appearing on the same continent, and this rule is respected when starting plots are normalized also.


Version History
1.13 - Fixed a bug where starting on a goody hut would crash the game. Prevented start plots from being on mountain peaks. Changed an internal distance calculation from a straight line to a path distance, improving start locations somewhat. Created a new tuning variable called DesertLowTemp. Since deserts in civ are intended to be hot deserts, this variable will prevent deserts from appearing near the poles where the desert texture clashes horribly with the tundra texture.

1.12 - Found a small bug in the bonus placer that gave bonuses a minimum of zero, this is why duel size maps were having so much trouble.

1.11 - limited the features mixing with bonuses to forests only. This eliminates certain undesireable effects like floodplains being erased by or coinciding with oil or incense, or corn appearing in jungle.

1.10 - Wrapped all map constants into a class to avoid all those variables being loaded up when PW is not used. Also this makes it a little easier to change them programatically. Added two in-game options, New World Rules and Pangaea Rules. Added a tuning variable that allows bonuses with a tech requirement to co-exist with features, so that the absence of those features does not give away their location.

1.09 - Fixed a starting placement bug introduced in 1.07. Added a tuning variable to turn off 'New world' placement.

1.08 - Removed the hemispheres logic and replaced it with a simulated meteor shower to break up pangeas. Added a tuning variable to allow pangeas.

1.07 - Placing lakes and harbors after river placement was not updating river crossings. Resetting rivers after lake placement should solve this. Fixed a small discrepancy between Python randint and mapRand to make them behave the same way. Bonuses of the same bonus class, when forced to appear on the same continent, were sometimes crowding each other off the map. This was especially problematic on the smaller maps. I added some additional, less restrictive, passes to ensure that every resource has at least one placement unless the random factors decide that none should be placed. Starting plot normalization now will place food if a different bonus can not be used due to lack of food. Changed heightmap generation to more likely create a new world.

1.06 - Overhauled starting positions and resource placement to better suit the peculiarities of PerfectWorld

1.05 - Fixed the Mac bug and the multi-player bug.

1.04a - I had unfairly slandered getMapRand in my comments. I had stated
that the period was shortened unnecessarily, which is not the case.

1.04 - Added an option to use the superior Python random number generator or the getMapRand that civ uses. Made the number of rivers generated tunable. Fixed a bug that prevented floodplains on river corners. Made floodplains in desert tunable.

1.03a - very minor change in hope of finding the source of a multi-player glitch.

1.03 - Improved lake generation. Added tuning variables to control some new features. Fixed some minor bugs involving the Areamap filler and fixed the issue with oasis appearing on lakes. Maps will now report the random seed value that was used to create them in the PythonDbg file, so they can be easily re-created for debugging purposes.

1.02 - Fixed a bug in the way desert placement was calculated. This also required a readjustment of the default values.

1.01 - Added some global tuning variables for easier customization. Fixed some minor bugs that caused deserts to get out of control sometimes.
 

Attachments

  • Standard Overview 2.jpg
    Standard Overview 2.jpg
    48.7 KB · Views: 1,463
  • Standard Overview 3.jpg
    Standard Overview 3.jpg
    49 KB · Views: 1,535
  • Standard Overview.jpg
    Standard Overview.jpg
    50 KB · Views: 36,149
  • Standard Jungle Valley.jpg
    Standard Jungle Valley.jpg
    141.9 KB · Views: 36,458
  • Standard Coastline.jpg
    Standard Coastline.jpg
    108 KB · Views: 36,405
  • Huge Resources.jpg
    Huge Resources.jpg
    101.8 KB · Views: 35,484
  • Huge Altitude.jpg
    Huge Altitude.jpg
    119.5 KB · Views: 36,688
  • Huge Overview.jpg
    Huge Overview.jpg
    44.4 KB · Views: 36,943
  • PerfectWorld.zip
    39.2 KB · Views: 4,087
This is fantastic, especially the way climates are generated. I got so tired of playing on maps with jungle and desert splattered randomly across the same latitudes that I pretty much stopped playing except for on maps that I've created. :) The only place where I think it needs work is the shape of the continents - they're a bit web-like and all seem to snake together. Also, having Sahara-like deserts that don't fade into grassland after three tiles is wonderful, but some of these deserts might be a little *too* big. If the way they're generated is based on the shape of the continent (this is great!), having some different options for continent generation might be helpful. Nercury's Planet Generator script does a good job of creating realistically-formed continents - if you could incorporate that with your wonderful climate generator code the script would be, well, perfect. Good work!
 
Yeah, 'perfect' is quite a boast. I called it 'PerfectWorld' because I wanted to make a map that was my ideal for a good Civ game. It's not really perfect just yet.

About those large deserts; I had also thought that there might be a little too much desert with the current setup. However, the real world is mostly plains and desert. Rainforests and lush farmlands are actually somewhat rare. In game terms, having an abundance of the drier climates makes the grasslands alot more valueable for food production, while the dry interors will naturally have many of the important mineral resources. After some playtesting I decided to keep it.

Also, bringing in more rain is tricky lol. The only things that are controlled by lattitude are the prevailing winds and initial temperature. Tweaking certain variables by a few tenths of a percent makes the world a gigantic jungle. :blush: I tweaked and tweaked until most of the maps were balanced lol. If, for example, all the continents end up in the rain zones at the equator and +/- 60 degrees, then even now the world is mostly rainforest. It's a big 'what if' scenario.
 
I must try it! Great to see someone else tried a generator like this. A pity your thumbnails are so small, I can't visualize the results until I've tried it myself.
 
I would suggest that before anyone commits to a game, generate a few maps and use the console command 'game.toggledebugmode' to make sure it's to your liking. Perhaps I can put together a better screenshot when I get home.
 
is this only for vanilla?

I do not own the expansions yet, so I'm not actually sure what additional requirements they have regarding map scripts. It may work fine for all I know.
 
Here are some more detailed screen shots of a typical map result. The one on the left is the main 'Old World' area where people start.

The middle one is an overview of the 'New World' continents with no starting areas.

The picture on the right is a close up example of a valley in the northern rainy lattitudes. The icy and tundra area to the north west is what high altitude looks like. It takes a little imagination to see it that way, but it looks fairly natural, especially since rivers tend to lead away from these areas.
 

Attachments

  • PWSS7.JPG
    PWSS7.JPG
    106.8 KB · Views: 1,986
  • PWSS6.JPG
    PWSS6.JPG
    117.5 KB · Views: 2,584
  • PWSS8.JPG
    PWSS8.JPG
    171 KB · Views: 2,241
This script works fine with the expansions. I didn't check if the feature generator makes it compatible with all mods, but it's perfectly usable with regular BtS.
I'll make a few comments if I may:
I think the deserts tend to get overboard quite easily. Very often I see huge deserts and almost no usable land in the most part of a continent, despite one or two lakes in the middle of the continent, which only generate a single plain nearby. It's very hard to tune unfortunately.
The rivers generated are gorgeous. Very nice work. I'm probably going to rewrite my own rivers along the same lines if I can find the time.
I also think there are too few hills with regards the number of peaks generated.
Nice job overall. :goodjob:
 
A word of warning in regards to river generation. Even though the generateTerrain overridable function wants a list of terrains with (0,0) in the NW corner, the plots in CvMap are arranged with (0,0) in the SW corner! Jeez, it took me days to figure out why my rivers were running backwards. With this being a random map generator, I was oblivious to the fact that I was looking at my maps upside down. :lol:
 
The maps generated with this script are very nice, especially the terrain distribution which itself is fantastic.

My only complaint so far is that the use of tundra & ice to simulate high altitude frequently has some 'unpleasant' results like a single tile of ice between peaks and jungles.

If you're accepting any 'wish lists' it would be very nice to have more control in the game. In particular the ability to control continents touching the poles, tundra/ice simulating high altitude and overall temperature & 'roughness' of the world. Nercury's planet generator has excellent controls and great results for landmasses.

Anyway, great work! :)
 
Ok, so I'm on vacation visiting family and showing my brother in law my map script. The first thing out of his mouth is, "Wow, look at all that desert." When I get back I'll make a fully tunable version.

I also have to do something about those starting areas. It's basically the default with no new world placement, and that really only works with roundish continents. Sometimes you can be placed on a snaky peninsula that goes through the arctic, and the game thinks you're on the best continent so it's ok. I'll have to work on it some more.
 
I have done some tweaking that I'd be happy to share with you, mainly with the player starting positions and removing the tundra/ice simulating altitude. To 'fix' the player starts what I did is re-write the entire section to that it first checks to see if the map has a single huge landmass, if it does that becomes the old world and everything else is the new world. If it's not a single huge landmass it makes the largest landmass the new world, the second largest the old world and then continues to add to the old world until the total old world size is 60% of the total land. What happens most often with that change is the players start out in smaller clusters on different continents, normally 2-4 civs per landmass (except on the pangea-like worlds). I was going to add a latitude check to add some weight to staring closer to the equator but I don't think it really needs anything else from what I've seen in my countless test builds.

Anyway, if you'd like I can post the code for that to save you some trouble.

As for customization options it would be nice to have some control over the frequency of rivers (especially in deserts), overall temperature, overall rainfall and the 'roughness' of maps.

Asthetically, the only thing that could be improved is the long straight mountain ranges as they can look pretty bad when you end up with a straight line of 10+ peaks. Creating some channels in the snakey continents to break them up into multiple continents would be a nice feature too but not really critical.
 
So I've been playing around with this a bit to tweak it more to my liking and figured I should at least share the code in case you find it helpful.

The first is my re-write of getNewWorldID(), it should be pretty self-explanatory:
Code:
    def getNewWorldID(self):
        nID = 0
        continentList = list()
        for a in self.areaList:
            if a.water == False:
                continentList.append(a)

        totalLand = 0             
        for c in continentList:
            totalLand += c.size
            
        print totalLand

        #sort all the continents by size, largest first
        continentList.sort(key=operator.attrgetter('size'),reverse=True)
        print ''
        print "All continents"
        print self.PrintList(continentList)

        #First, make sure we don't have a single huge landmass, if we
        #do, that will be the old world
        oldWorldSize = 0

        if float(continentList[0].size)/float(totalLand) > 0.50:
            oldWorldSize += continentList[0].size
            del continentList[0]
        else:
        #second largest continent is 'Old World' giving us a larger 'new world'
        #to explore and forcing the game to split player starts up into smaller
        #groups on the remaining landmasses if possible
            oldWorldSize += continentList[1].size
            del continentList[1]

        #if the remaining continents are large enough we can continue
        #if not we can just call everything else 'new world'
        if float(oldWorldSize)/float(totalLand) < 0.50:
            biggestNewWorld = continentList[0]
            del continentList[0]

            for n in range(len(continentList)):
                oldWorldSize += continentList[0].size
                del continentList[0]
                if float(oldWorldSize)/float(totalLand) > 0.60:
                    break
        
        #add back the biggestNewWorld continent
            continentList.append(biggestNewWorld)

        #get ID for the next continent, we will use this ID for 'New World'
        #designation
        nID = continentList[0].ID
        del continentList[0] #delete to avoid unnecessary overwrite

        #now change all the remaining continents to also have nID as their ID
        for i in range(self.mapHeight*self.mapWidth):
            for c in continentList:
                if c.ID == self.areaMap[i]:
                    self.areaMap[i] = nID
 
        return nID

The next is some 'noise' generation. This is kind of dirty but it does the trick and would be easy to impliment as an optional process. I simply added two passes to the end of GenerateHeightField(). The intention was to break up the landmasses a little more, particularly near the poles. It needs some work though as it ends up adding a lot of peaks on most maps. I did it here, before all of your other processing to help conceal the 'noise' effect a little better. Since this takes place before PerformTectonics() you still get the really good landmass shapes and natural looking terrain distribution. In fact, it tends to create some very nice island chains on larger maps.
Code:
        #Now a quick 'noise' pass that is biased to add more land near the equator and
        #subtract more away from the equator
        for y in range(0,self.mapHeight,1):
            addBias = 1 - (abs((self.mapHeight * 0.5) - y) / (self.mapHeight * 0.5))
            subBias = abs((self.mapHeight * 0.5) - y) / (self.mapHeight * 0.5)
            for x in range(0,self.mapWidth,1):
                i = self.GetIndex(x,y)
                rand = random()
                if rand < 0.2:
                    self.map[i] += random() * addBias * 0.5
                elif rand > 0.7:
                    self.map[i] -= random() * subBias
        #Finally a smoothing pass.  The effect is stronger and more frequent near
        #the equator.
        for y in range(0,self.mapHeight,1):
            bias = 1 - (abs((self.mapHeight * 0.5) - y) / (self.mapHeight * 0.5))
            for x in range(0,self.mapWidth,1):
                i = self.GetIndex(x,y)
                avg = 0.0
                if random() > bias:
                    for yy in range(y - 1,y + 2,1):
                        for xx in range(x - 1,x + 2,1):
                            ii = self.GetIndex(xx,yy)
                            avg += self.map[ii]
                    avg = avg/8
                    if avg < self.map[i]:
                        self.map[i] -= abs(avg - self.map[i]) * ((1.0 + bias) / 2)

Hope some of that helps. :)
 
Well seven05, the first change you made basically changes the desired size of the old world from .60 to .50. You can accomplish the same thing probably by just changing that one hardcoded constant. Keep in mind that in this continent designation, coast is considered land, so when you generate a world with .25 landmass vs. water, you are almost always going to get a large continent bigger than .50.

The second change with the added noise just makes it noisier than the noise that comes with the heightfield generation. I can't remember all the places where I added noise to different aspects of the map generator, but the result has been continents of any size along with small islands dotting the seas here and there. That's noisy enough isn't it? Also, what's wrong with having land near the poles? In the real world after all, some of the largest continents come right up against the ice.

Next week I'm to change quite a few things and add alot of tunable variables like desert/plains/hills/peaks percentages and put them right at the beginning of the script as globals. So instead of saying "no rain = desert" I'm going to say 'no rain compared to the rest of the world = desert'. That way if the continents form mostly in the desert lattitudes they won't all be desert.
 
Well, first of all congratulations on your excellent map script. It's the best one I've ever used.
Secondly, I agree with Seven05 that there's too much land around the poles. CIV map is a rectangle which can't represent the Earth surface accurately. The equatorial zones end up shrinked and polar zones are stretched. To balance things out more ocean should be added around the poles to keep the tundra-rainforest ratio correct and create more earthlike maps (for example Rhye stretched the oceans near the poles to balance his Earth map).
I'm looking forward to the changes you want to make. I would also like to have an option to customize the number of continents and islands.

Rael
 
Well seven05, the first change you made basically changes the desired size of the old world from .60 to .50.
Well, it's a little more than that :) You know your old method, the first check for 50% is simply to optimize the process since if it finds that than that continent is the old world and everything else is assumed to be the new world without stepping further into the process. The biggest change is in cases where you don't have one dominant landmass, in that case the largest is the new world and then the remaining continents are used to define the old world until the old world consists of at least 50% of the available land. On standard maps the difference between 50-60% is so small it doesn't matter, however on large and huge maps it makes quite a difference in player starting positions. From my experience (and with my noise changes) typical maps consist of two larger continents and anywhere from 1-3 smaller continents (continents being landmasses seperated by ocean). The next most frequent result is a single huge continent with some scattered islands and maybe a single additional small continent (room for 3-4 cities). Maybe one out of every ten maps gives me multiple continents of relatively equal size. Anyway, in all but the pangea style worlds the end result has the players more spread out over multiple continents whereas your original code preferred to put most of them on a single landmass.

The second change with the added noise just makes it noisier than the noise that comes with the heightfield generation. I can't remember all the places where I added noise to different aspects of the map generator, but the result has been continents of any size along with small islands dotting the seas here and there. That's noisy enough isn't it? Also, what's wrong with having land near the poles? In the real world after all, some of the largest continents come right up against the ice.
The noise (and smoothing pass) is biased by latitude so it's easy to tune the results. With my current settings the result is more subtractive noise near the poles which tends to break up large landmasses near the poles and replace them with scattered islands. The smoothing pass, being more prominent near the equator, has the effect of reducing the number of small islands away from the poles. So combined, it's not simply more noise, it's more noise near the poles and less noise near the equator. Adjusting the values used can create anything ranging from scattered islands everywhere to huge polar landmasses and equatorial islands. I chose to use noise to keep the landmasses interesting rather than simply applying a linear add/subtract pass.

It's not so much a matter of wanting NO land near the poles as much as it is a matter of preventing large landmasses near the poles. Having massive amounts of tundra/ice increases the chance of civs starting in those regions which can severely cripple them. While it's a fun start for a human player against AI opponents it makes for an exceptionally easy game if it's the AI that gets stuck there instead.

Next week I'm to change quite a few things and add alot of tunable variables like desert/plains/hills/peaks percentages and put them right at the beginning of the script as globals. So instead of saying "no rain = desert" I'm going to say 'no rain compared to the rest of the world = desert'. That way if the continents form mostly in the desert lattitudes they won't all be desert.
I'm looking forward to those changes :) I'll have to grab some in game screenshots showing some of the hidden potential of using your mapscript. In particular is the ability to apply different versions of grass, plains and deserts based on temperature which makes the maps look absolutely incredible.
 
I'll have to grab some in game screenshots showing some of the hidden potential of using your mapscript. In particular is the ability to apply different versions of grass, plains and deserts based on temperature which makes the maps look absolutely incredible.

Wow, that sounds like fun.

Ok, I'm going to do this in two passes. First, I'm going to do the tunable stuff and publish it, since I can do it fairly quickly and easily. Then I'm going to work really hard on making those starting locations alot better.

The difficulty with placing starting locations is that much of the value depends on the starting locations of other civs, which are not necessarily known at the time of placement. You might think the guy with first pick has the best location, but that can be ruined by someone else being placed between him and the rest of his continent. A fine starting location can end up being a terrible one. You can fix that perhaps, but can you do it without ruining someone else's and ending up in an endless process?

I have some ideas on how to do this but it will be some work. I can create a value map with the values of each tile, then I can do some simultanious seed fills to find out what territory is likely to be claimed first and make adjustments in passes until everyone is within a certain value tolerance.

I really don't mind having a lot of unusable tiles on a map. I like large ice fields and large deserts and too much ocean. I think it adds a sense of adventure to the game. However, for that to work the starting areas have to take that into account so that one guy isn't getting the desert half of a good continent while his opponent gets all the grassland.
 
I really don't mind having a lot of unusable tiles on a map. I like large ice fields and large deserts and too much ocean. I think it adds a sense of adventure to the game. However, for that to work the starting areas have to take that into account so that one guy isn't getting the desert half of a good continent while his opponent gets all the grassland.
That will definately be the tough part although in most cases the start positions haven't been too bad in my games. I've adjusted a lot of things though, both in your mapscript and with the mod that we use.

What is probably the best part of this map script is that the worlds created are 'tactically interesting.' The mountain ranges not only make sense and look good but they create natural obstacles rather than just being some randomly scattered peaks. The deserts are really good as well and create a good natural barrier to expansion, almost like oceans on land. It's really cool to see how the game evolves too, what was poor land early in the game can quickly become critical as resources are revealed.

Speaking of resources, if you write your own resource script make sure it's optional :) Right now this is working out very well of us with the combination of climate based terrain and resources restricted by terrain. For instance I have hot, temperate and cool plains so I can have a resource only appear on hot plains. Just like your script made the world look better by not banding terrain strictly by latitude this makes resource placement much more interesting.
 
This map script caught my attention at once :). Nice job!

CIV map is a rectangle which can't represent the Earth surface accurately. The equatorial zones end up shrinked and polar zones are stretched. To balance things out more ocean should be added around the poles to keep the tundra-rainforest ratio correct and create more earthlike maps

I don't exactly know how landmasses are generated, but i guess it may be possible to randomly sink some land-heavy areas which are near poles to achieve that.
 
Top Bottom