[Map Script] PerfectWorld.py

Is there any guide to reading mapscripts? I have quickly scanned through some, but not sure about where to start with them if I want to look specifically for starting location assignment and normalization for example.

Most map scripts use the default procedures for most of their functionality. Only insane people try to replace everything. :blush:

The best place to get the 'official' way of particular map scripting tasks is in the SDK. Also, make sure to look at CvMapScriptInterface.py. That explains alot.
 
Funny thing is those are alot fo the issues I had with the base game that Cephalo's script finally gave me a means to overcome. The biggest thing for me is the climate based terrain, for instance instead of simply one type of grassland I have three; hot, temperate and cool. This gives me very specific control over features and resources such as jungles on hot grass only (during map creation) or corn on cool and temperate grass but not hot grass. With the way climates are generated by Perfect World resource distribution is far better than the old terrain & lattitude method even before his updated change. Unfortunately it's a complicated change that involves several different files. The cool part is that with this script we have access to more than simple plot types, areas and lattitudes so the ability to generate other terrain types based on altitude, rainfall and temperature opens up a lot of possibilities. With a little effort these map attributes can also be used to modifiy resource placement. Imagine if corn and wheat would only appear at sealevel +/- 10%, gems on plate boundries or whatever else you can dream up.
 
Is the link in the OP the most up to date copy? Because the file is quite a bit older than the post recent posts in this thread.

What is the status of start area normalization?

Props on all the great work, guys!
 
1.06 is the most recent version and it's the file in the original post. It has the normilization code for starting positions in it and they work fairly well. And just for clarification this is all Cephalo's work, there are just a few of us that like to change his script to suit our own tastes.
 
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. ;)
 
River placement seems problematic, as some river mouths not only look ugly but also leave diagonal normally river (but not riverside) tile without access to the river.
I can redraw the river mouth in worldbuilder so that it gets better graphics and also gives proper river access to possible diagonal tile, but obviously I can't do that for games I want to play.

All problematic river mouths I've seen have been of the wide mouth type (as again seen in the attachment below).
 

Attachments

  • PerfectWorld-Rivermouthproblem.jpg
    PerfectWorld-Rivermouthproblem.jpg
    99.3 KB · Views: 383
River placement seems problematic, as some river mouths not only look ugly but also leave diagonal normally river (but not riverside) tile without access to the river.
I can redraw the river mouth in worldbuilder so that it gets better graphics and also gives proper river access to possible diagonal tile, but obviously I can't do that for games I want to play.

All problematic river mouths I've seen have been of the wide mouth type (as again seen in the attachment below).

I think I have finally figured out why this happens. First of all, this only happens when you 'regenerate' the map after starting a game, and the easiest way to fix it is to save and reload.

I think what is happening is that since my lakes are caused by rivers, and the addLakes function is called after addRivers, the placing of rivers before lakes requires some redrawing of rivers that is not happening in this case. Placing lakes after rivers is designed for lakes that are not connected to rivers in any way. The fix for this will be a big job however, I'm not sure when I will have time for it.
 
So regenerate causes initialization to be different from what it would be from clean game startup? That must be a bit annoying to work around :(
Save & reload seemed to work indeed - so with workaround available it's not a big deal.
 
The problem with river mouths is more than just graphical. This picture is after save & reload so graphical issue was fixed, still showing that the river mouth corners don't get fresh water and river commerce as they should.
The two tiles marked with black dots should be river corners, getting fresh water and commerce, possibly flood plain overlay (although this is just a bonus - standard map scripts just add floodplain to all desert river tiles).
 

Attachments

  • PerfectWorld-Rivermouthproblem-2.jpg
    PerfectWorld-Rivermouthproblem-2.jpg
    54.8 KB · Views: 326
The problem with river mouths is more than just graphical. This picture is after save & reload so graphical issue was fixed, still showing that the river mouth corners don't get fresh water and river commerce as they should.
The two tiles marked with black dots should be river corners, getting fresh water and commerce, possibly flood plain overlay (although this is just a bonus - standard map scripts just add floodplain to all desert river tiles).

They should definately be fresh water, especially since there's a freshwater lake right there. The floodplains require a river crossing, which I guess is not happening there. I guess that when the rivers are placed those lakes don't exist yet and the engine isn't designed to handle that scenario. It thinks you can just go around the end and not cross a river. Are there any map scripts that have rivers flowing into freshwater lakes?
 
Hey Cephalo,

I like your script, it generates some great maps.
I would however request a version that spreads the starting civs more evenly.
How would one make that tweak? (I realize you specifically made it so that certain areas are abandoned).

Also, why don’t you add the feature of few, normal & lots of resources?

Great work.
 
I'm quite sure Big'n'Small and Hemispheres (the maps I mainly used in BTS) do that. However, this isn't limited to lakes. I founded a city where a river flowed to sea, and the diagonal tile didn't have fresh water. No screenie or save from that though - I'll try to remember it when it comes up next time.
 
As starting location terrain is not modified in normalization but rather resources added, sometimes things go awry. I don't think four stones in capitol is all that good method in normalizing the location :)

Would it be possible to make sure that when adding resources, the same resource isn't added multiple times? A bit more diversity might make this look good.
 

Attachments

  • PerfectWorld-startlocnormalizer.jpg
    PerfectWorld-startlocnormalizer.jpg
    66.9 KB · Views: 307
How come the giant and gigantic settings only produce grassland maps?
Those must be additional options enabled by a mod you're using, and the map script isn't expecting them.

If you open up the map script and search for "huge" you will see the list of maptype-to-dimension mappings. Maybe if you add your own giant and gigantic information it will work?
 
Ok, so now that I've changed most of the mapscript to create maps adjusted for my mod I was still facing one annoyance- players starting on really small 'continents' The problem was the way the new world was defined, it would go through the list of continents sorted by ID and keep adding them to the old world until the old world total land size was better than 60% of the total land. The problem was that there were frequently small continents mixed into the list, too small for a decent start (often as small as 9 tiles). My solution is to enforce a minimum continent size which scales based on the amount of available land.

Below is the entire 'getNewWorldID' function, most of it is unchanged from Cephalo's original code. The important part comes in after the two largest continents are assigned. Since I didn't comment the code I'll walk through it first...

After the largest and second largest continents are assigned to the old world and new world respectively we take the remaining list and sort it by continent ID rather than size (this is Cephalo's code) which helps with variety. Next we determine the minimum old world continent size, making sure it is at least 12 tiles (for smaller maps) but otherwise 2.5% of the total land. Then we evaluate each continent in the list, note that I didn't use a ranged loop here, doing so can throw an index out of bounds error since we're deleting items from the list as we go. Anyway, we step through the list and evaluate each continent, if it's larger than the minimum old world continent size it gets added to the old world. This continues until the old world uses at least 60% of the total available land or we run out of continents. If the old world is big enough with a single continent the entire process is skipped.

If you have logging enabled you can review the entire process in the log file. Anyway, here's the code:

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(lambda x,y:cmp(x.size,y.size))
        continentList.reverse()
        
        print ''
        print "All continents"
        print self.PrintList(continentList)

        #now remove a percentage of the landmass to be considered 'Old World'
        oldWorldSize = 0
        #biggest continent is automatically 'Old World'
        oldWorldSize += continentList[0].size
        del continentList[0]

        #get the next largest continent and temporarily remove from list
        #add it back later and is automatically 'New World'
        biggestNewWorld = continentList[0]
        del continentList[0]

        #sort list by ID rather than size to make things
        #interesting and possibly bigger new worlds
        continentList.sort(lambda x,y:cmp(x.ID,y.ID))
        continentList.reverse()

        oldWorldMinSize = max(12,totalLand * 0.025)
        idealOldWorldSize = int(totalLand * 0.60)
        oldWorldPercent = float(oldWorldSize)/float(totalLand)

        print "Ideal Old World total size is %(w)2d" % {"w":idealOldWorldSize}
        print "Current Old World size is %(w)2d" % {"w":oldWorldSize}

        if oldWorldSize < idealOldWorldSize:
            print "Minimum Old World continent size is %(w)2d" % {"w":oldWorldMinSize}
            print "There are %(c)2d continents in the list." % {"c":len(continentList)}
            o = len(continentList) - 1
            n = 0
            while n < o:
                if oldWorldPercent > 0.60:
                    print "Old world continent selection complete, total size is %(t)2d" % {"t":oldWorldSize}
                    break
                print "Evaluating %(c)2d, ID is %(d)2d." % {"c":n,"d":continentList[n].ID}
                if continentList[n].size > oldWorldMinSize:
                    oldWorldSize += continentList[n].size
                    oldWorldPercent = float(oldWorldSize)/float(totalLand)
                    print "Adding continent %(c)2d to old world, total size is now %(t)2d" % {"c":continentList[n].ID,"t":oldWorldSize}
                    del continentList[n]
                    #decrease the list length (o) by one to prevent going out of bounds
                    #do not incriment the current count (n) here since that list item was removed
                    o -= 1
                else: #incriment the counter if no continent was removed from the list
                    n += 1

        #add back the biggestNewWorld continent
        continentList.append(biggestNewWorld)
        
        #what remains in the list will be considered 'New World'
        print "New World Continents"
        print self.PrintList(continentList)

        #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
 
Here is the version of PerfectWorld I use. I tried to mark all the changes carefully, and here is a list of them:

I. PLATES
1) Changed tectonics so that some plates go down instead of up.
2) Added additional height variation to plates.
3) Fewer plates.
II. START POSITIONS
1) Improved city valuation (in my opinion)
2) Made city "boosting" algorithm weaker and subtler (E.g. you are less likely to end
up with four stone resources and no food.)
3) Changed player distribution algorithm so that it values quality of location more,
and being as far away from others as possible less.
III. PLOT TYPES
1) Made small tweaks to some thresholds based on personal preference.
2) Removed Flood Plains' ability to spawn in very cold areas.
3) If a Flood Plain or Oasis would spawn on a hill, that hill is converted to a plains hill.
IV. RIVERS
1) Rivers are based on normalized square of rainfall, resulting in longer rivers.
2) Rivers like to flow towards the sea more, and can overcome small valleys to accomplish this.

I think the changes I made to starting city placement are a definite improvement. The other ones might just be personal preference.

Note: I can't attach a .py file. You will have to rename this to have a .py extension if you want to try it out.
 
I'l definately have to take a look at your changes to the starting plots, thanks for posting that. What ever happened to your attempts at loading an image for the initial heightmap?
 
Those must be additional options enabled by a mod you're using, and the map script isn't expecting them.

If you open up the map script and search for "huge" you will see the list of maptype-to-dimension mappings. Maybe if you add your own giant and gigantic information it will work?

I tried this and it did not work. Still end up with wall to wall grasslands.

On another note is it possible to make the additional resources placed with the starting city rellevant to the starting techs? I have yet to see a starting position where I can use aggriculture makes me wonder how my people learned it.:lol:
 
I tried this and it did not work. Still end up with wall to wall grasslands.

This script has rather strict dimension limits. Specifically, the map must be evenly divisible, in Width and in Height, by a power of two that is at least mapWidth/16. It will throw a Python Exception if the size doesn't meet that criteria. Also keep in mind that the 'cell' size or whatever is 4x4 map squares I think. Any time you get wall to wall grassland it means the map script crashed.

PW is not the fastest map script on the block. Maps bigger than huge seems like it would take forever to generate. Also, this script is kindof a memory hog, I think Civ puts limits to how much memory is available to Python and there were some past versions of PW were I hit that limit with Huge sized maps. Good luck.
 
Top Bottom