[Map Script] PerfectWorld.py

I'm running Civ IV on a Mac (PPC). When I try PerfectWorld, even version 1.04a, I get no elevations. I get small lakes and some forestation, but the world is all grasslands and two-square-long rivers.

Any ideas?

-K

Can you post a screenshot? The only time I've had that happen is when the script crashed in the middle of map creation. This map script is starting to become something of a beast, and it wouldn't surprise me if some machines on the lower end of things might not work well. Does the same thing happen every time? Do you get an error message? Also, try generating a duel size map first and work your way up. That might tell you if you have some kind of problem with computer resources.
 
You should ask AlanH for mac compatibility in this thread . The bug occur probably with operator.attrgetter .

Tcho !
 
You should ask AlanH for mac compatibility in this thread . The bug occur probably with operator.attrgetter .

Tcho !

Thanks for the tip! Yikes, so Mac civ is using Python 2.3? Ouch. I'll try to work that in.
 
And you thought the MP side was a pain ;D

I have finally tweaked & tuned the script almost to my own version of perfection, or more accurately to better work with my mod. The climate based terrain works like a charm, incidentally I saw you tried adding some noise to the rain to break up the banding, add that noise to the temperature map instead and you'll solve the banding without messing up the climate too much. Altitude based terrain is probably going in soon, not a big deal but since the data is there, why not use it? :)

I have a couple of issues I'd like to adjust a bit more, but I need your help since I don't fully understand how you're doing a couple things.

First, and most important to me, is desert rivers & lakes. Ideally, I would like to remove every lake that is surrounded by desert and disallow any rivers through desert terrain that don't terminate at the ocean. Is there a good way to trace a river backwards or would it be better to modify the flowmap before rivers are even generated?

The next thing I'd like to do is reduce the frequency of large inland seas. I don't want to simply fill them all in with land though. It would be ideal if I could fill some in, force channels out from others and if a channel is created plot out a few small islands inside the sea.

Both of these would help a bit with the overall gameplay in my mod. The deserts are critical since I use them to hold the majority of the key strategic resource (oil, iron and copper) and I only allow desert cities to be built on the coast or tiles with fresh water. The large number of lakes & rivers in the desert makes it too easy to gain access to all of the resources. The inland sea issue is more for strategic importance and to give the AI a helping hand since it doesn't understand that building a massive fleet of warships won't help them if they can't go anywhere.

Also, I don't know if you would want it or not but I added a few simple steps to the tectonics code so the generator has a chance of creating 'typical perfect world' maps, pangea maps (with no land at the poles) or mixed continents. And I added an oceanic divide to the initial heightmap generation to help ensure that there is some ocean separating the landmasses. Of course I kept everything random so you really have no clue what you're getting yourself into since thats the way I like to play.

Anyway, thanks again for the incredible script and the continued updates. I'll keep you posted on the MP issue when I have a chance to test it again.
 
You're in for alot of work Seven05. I'll try to answer your questions.

Getting rid of desert rivers will also get rid of the lakes. The final riverMap in the RiverMap class is just like the flowMap, but with all non rivers set to rm.O. Any rm.L(Lake or Pit) you find you can check to see if it's in the desert, and if so you can follow the river backwards and erase each river length by setting it to O. No lake will be generated without the river there, when you see a lake without one, it's because the lake ate the river that caused it.

The large inland seas come from either the river system making a huge lake or else because one plate sunk below all the surrounding plates. In the first case, you can set LakeSizePerRiverLength to a smaller value. In the second case, you might increase the size tolerance for the initial lake filling, but you might end up with a very large swath of land if you go too far with that. The makeHarbor function will cause ocean to invade lakes to a degree of one map square. You might try to extend it to be more invasive.

The difficulty with some of this stuff you want to do is that on this map, the heightmap is the law of the land. Rain will go downhill, even if that is the middle of your continent, and the larger the continent, the more likely this will happen.
 
Great script, however I found a problem around the poles.

Sometimes the unpassable territory is overwritten by tundra or ice.

Code:
U=Unpassable t=tundra P=Peak  w=water (ocean)

UUUUUUttPtUUUUUU
wwwwwttttttwwww
wwwwwttppwwwww
wwwwwttwwwwww

In addition sometimes the starting positions are on those small areas and the AI just builds a city in the top row, cutting the workable area to half and with only low value terrains, there is no much hope for the AI.

There is another noteworthy side-effect, there is a large number of resources put on the first row (copper, iron and uranium)
 
Great script, however I found a problem around the poles.

Sometimes the unpassable territory is overwritten by tundra or ice.

Code:
U=Unpassable t=tundra P=Peak  w=water (ocean)

UUUUUUttPtUUUUUU
wwwwwttttttwwww
wwwwwttppwwwww
wwwwwttwwwwww

In addition sometimes the starting positions are on those small areas and the AI just builds a city in the top row, cutting the workable area to half and only low value terrain.

There is another noteworthy side-effect, there is a large number of resources put on the first row (copper, iron and uranium)

Is the top row unpassable? I guess I never really checked that. The starting positions are going to be revamped. I'm right now neck deep in the craziest, hopefully Mac compatible, starting plot code the world has ever seen. Will it work? Is it even feasable? Nobody knows.

The resource code is all default placement. I have been forbidden to touch that as such things are supposed to be handled in the XML.

I remember the night I first published version 1.0. I said, "Ahhhhh, I'm finally finished!" Don't worry, If I can't fix the starting code, I can at least create Santa Claus as that civs UU.
 
Is the top row unpassable?
No, but it is typically impassable Ice. I adjusted the terrain generation script to always force that top and bottom row as water myself, mainly for asthetic reasons.

The resource code is all default placement. I have been forbidden to touch that as such things are supposed to be handled in the XML.
You can actually do a bit without breaking the XML rules. The problem comes from non-standard resources, if you write custom resource placement any mod that adds custom resources won't be able to use your script. Of course you've already 'broken' custom features so it really shouldn't matter :)

I remember the night I first published version 1.0. I said, "Ahhhhh, I'm finally finished!"
Famous last words :)
 
You're in for alot of work Seven05. I'll try to answer your questions.

Getting rid of desert rivers will also get rid of the lakes. The final riverMap in the RiverMap class is just like the flowMap, but with all non rivers set to rm.O. Any rm.L(Lake or Pit) you find you can check to see if it's in the desert, and if so you can follow the river backwards and erase each river length by setting it to O. No lake will be generated without the river there, when you see a lake without one, it's because the lake ate the river that caused it.
Forgive me since I'm not looking at the code right now, but is it easy to track the flowmap (like knowing which adjacent plot to check/modify). I remember bits of it vaguely, like the L and O parts, isn't the rest N,S,E,W? If so is it a matter of saying, "ok, this plot is a W so if I go one plot E I'm tracking the flow 'upstream'."

The large inland seas come from either the river system making a huge lake or else because one plate sunk below all the surrounding plates. In the first case, you can set LakeSizePerRiverLength to a smaller value. In the second case, you might increase the size tolerance for the initial lake filling, but you might end up with a very large swath of land if you go too far with that. The makeHarbor function will cause ocean to invade lakes to a degree of one map square. You might try to extend it to be more invasive.
Didn't you drop the code to clean up lakes, or is it just hidden now? The makeHarbor function is working very well right now, the problem is that I seem to have a bit of luck in getting seas surrounded by land that is two tiles thick, perhaps a quick change to the horbor code is all I need. Is that executed before or after the area lists are generated? If it's after it should be a simple matter to identify inland seas but if it's before I can work directly with the heightmap... Gah! :)

The difficulty with some of this stuff you want to do is that on this map, the heightmap is the law of the land. Rain will go downhill, even if that is the middle of your continent, and the larger the continent, the more likely this will happen.
Yeah, I tried some basic 'evaporation' code, but I wasn't happy with the results since it affected all of the rivers. Although I suppose I could run a check after the terrain is generated but before the rivers are to only apply evaporation in desert squares (I was using temperature & rainfall data before). The again, maybe my evaporation code was just flawed :)
 
Forgive me since I'm not looking at the code right now, but is it easy to track the flowmap (like knowing which adjacent plot to check/modify). I remember bits of it vaguely, like the L and O parts, isn't the rest N,S,E,W? If so is it a matter of saying, "ok, this plot is a W so if I go one plot E I'm tracking the flow 'upstream'."
Yes, the riverMap works that way also. Keep in mind that when you go backwards, each plot might branch off to tributaries, so you'll have to put them on some kind of stack for deletion later. For each plot you think, "ok, who of my four neighbors is flowing into me?" If the answer is nobody, then you are done with that branch of river. Drastic stuff! Maybe theres a better way. Remember that the latest version allows you to control floodplain generation, so maybe you can render those rivers and lakes impotent.

Didn't you drop the code to clean up lakes, or is it just hidden now? The makeHarbor function is working very well right now, the problem is that I seem to have a bit of luck in getting seas surrounded by land that is two tiles thick, perhaps a quick change to the horbor code is all I need. Is that executed before or after the area lists are generated? If it's after it should be a simple matter to identify inland seas but if it's before I can work directly with the heightmap... Gah! :)
I fill lakes right at the beginning during heightmap generation, with the idea that they will be recreated via the river system. The Areamap class that you speak of is general purpose, if you learn how to use it you can do something on your own concerning areas. I use it for several different things.

Yeah, I tried some basic 'evaporation' code, but I wasn't happy with the results since it affected all of the rivers. Although I suppose I could run a check after the terrain is generated but before the rivers are to only apply evaporation in desert squares (I was using temperature & rainfall data before). The again, maybe my evaporation code was just flawed :)
I have an idea. Find the following code:

Code:
        #Create average rainfall map so that each intersection is an average
        #of the rainfall from rm.rainMap
        for y in range(hm.mapHeight):
            for x in range(hm.mapWidth):
                i = hm.GetIndex(x,y)
                avg = 0.0;
                for yy in range(y,y-2,-1):
                    for xx in range(x,x+2):
                        ii = hm.GetIndex(xx,yy)
                        avg += rm.rainMap[ii]
                avg = avg/4.0
                self.averageRainfallMap[i] = avg

see what happens when you square 'avg'.
Code:
Change
self.averageRainfallMap[i] = avg

to

self.averageRainfallMap[i] = avg * avg

This might eliminate desert rivers altogether. If so you can try some other tweak on this value. This will steepen the curve, and if all rivers become diminished you can pump up the RiverThreshold global.
 
Here's a screen shot of a duel-sized map. I play on a 23" monitor at 1280x800. I can go up to 1920x1200, but then I'd need to move the monitor closer or put on my glasses. I'm lazy.

This isn't a low-end Mac. It's one of the last non-Intel Macs made. It has two dual core 2.5GHz G5 processors and 2GB of RAM. The graphics card is a GeForce 7800GT driving a digital Mac LCD monitor. I've got plenty of RAM. This is basically a server-class machine. It's my development system.

BTW, I'm a programmer, but not a Python programmer. I normally write in Java, C++, or Perl and develop database applications, so this is a bit out of my line, but I can probably learn to read Python. I've got a book.

I've never really felt like the Mac ports are that great, but they're what I can get. I do have a 2.4GHz P4 mobile Dell laptop sitting around the house, but I gave my Windows CivIV away to a friend.

Oh, I play with the Blue Marble textures, so they'll look a bit different from the standard textures.

Thanks,

-K
 

Attachments

  • Picture 1.jpg
    Picture 1.jpg
    244.4 KB · Views: 403
I have an idea. Find the following code:


see what happens when you square 'avg'.

This might eliminate desert rivers altogether. If so you can try some other tweak on this value. This will steepen the curve, and if all rivers become diminished you can pump up the RiverThreshold global.
That actually works very well, except I had to reduce the RiverThreshold, not increase it. At 2.0 and above I would have maybe a single river on the entire map, at 1.0 there were more but they were all 1-2 tiles at most, at 0.2 it's very good (for my taste) with plenty of rivers around the map and only a few scarce rivers in the desert. At any rate, that's certainly a more effecient method rather than cycling through the entire map another three of four times to trace rivers :)

I had the chance to test it in MP except I was running into a problem. When setting it to not use python random the map was flat grassland, no water, hills, peaks or anything but grassland and a bunch of long rivers. I have a feeling the mapRand isn't precise enough for the changes I put into the heightmap. Next time I get a chance I'll load up an un-modified version of your script with the python random disabled.
 
You should ask AlanH for mac compatibility in this thread . The bug occur probably with operator.attrgetter .

Tcho !

Well, your "full of resources" scripts work fine... I've been using them for months with no problems.

I read through the thread and didn't notice any references to operator.attrGettr. What does it do? What should we be doing instead? The script definitely uses it, I think four times. Twice with 'size' and once with 'ID', and once with 'altitude'.

-K
 
I read through the thread and didn't notice any references to operator.attrGettr. What does it do? What should we be doing instead? The script definitely uses it, I think four times. Twice with 'size' and once with 'ID', and once with 'altitude'.

You can try that waiting for cephalo update ( windows use python 2.4 and Mac use python 2.3 where operator.attrGettr is not defined ) :

Code:
        #self.areaList.sort(key=operator.attrgetter('size'),reverse=True)
        tpList = [ [ item.size , item ] for item in self.areaList ]
        tpList.sort()
        tpList.reverse()
        self.areaList = [ item[1] for item in tpList ]

        #continentList.sort(key=operator.attrgetter('size'),reverse=True)
        tpList = [ [ item.size , item ] for item in continentList ]
        tpList.sort()
        tpList.reverse()
        continentList = [ item[1] for item in tpList ]

        #continentList.sort(key=operator.attrgetter('ID'),reverse=True)
        tpList = [ [ item.ID , item ] for item in continentList ]
        tpList.sort()
        tpList.reverse()
        continentList = [ item[1] for item in tpList ]

        #lakeNeighbors.sort(key=operator.attrgetter('altitude'),reverse=False)
        tpList = [ [ item.altitude , item ] for item in lakeNeighbors ]
        tpList.sort()
        lakeNeighbors = [ item[1] for item in tpList ]

For cephalo , if you want to know if you're running into a MAC OS and use a global ( because this method is not very fast compare to operator.attrGettr )

Code:
if (sys.platform == 'darwin'): MacOS = True

Tcho !
 
I've never really felt like the Mac ports are that great, but they're what I can get. I do have a 2.4GHz P4 mobile Dell laptop sitting around the house, but I gave my Windows CivIV away to a friend.

Oh, I play with the Blue Marble textures, so they'll look a bit different from the standard textures.

-K

I understand the problem now khuxtable, it will be fixed in the next version. I just have to strip out all Python 2.4isms.

Seven05 said:
I had the chance to test it in MP except I was running into a problem. When setting it to not use python random the map was flat grassland, no water, hills, peaks or anything but grassland and a bunch of long rivers. I have a feeling the mapRand isn't precise enough for the changes I put into the heightmap. Next time I get a chance I'll load up an un-modified version of your script with the python random disabled.

It's an ok random generator, what you are seeing is a map crash. If you use any randoms yourself, be sure to use PWRand. Hopefully it's not a crash that I just failed to test for.
 
You can try that waiting for cephalo update ( windows use python 2.4 and Mac use python 2.3 where operator.attrGettr is not defined ) :

Code:
        #self.areaList.sort(key=operator.attrgetter('size'),reverse=True)
        tpList = [ [ item.size , item ] for item in self.areaList ]
        tpList.sort()
        tpList.reverse()
        self.areaList = [ item[1] for item in tpList ]

        #continentList.sort(key=operator.attrgetter('size'),reverse=True)
        tpList = [ [ item.size , item ] for item in continentList ]
        tpList.sort()
        tpList.reverse()
        continentList = [ item[1] for item in tpList ]

        #continentList.sort(key=operator.attrgetter('ID'),reverse=True)
        tpList = [ [ item.ID , item ] for item in continentList ]
        tpList.sort()
        tpList.reverse()
        continentList = [ item[1] for item in tpList ]

        #lakeNeighbors.sort(key=operator.attrgetter('altitude'),reverse=False)
        tpList = [ [ item.altitude , item ] for item in lakeNeighbors ]
        tpList.sort()
        lakeNeighbors = [ item[1] for item in tpList ]

For cephalo , if you want to know if you're running into a MAC OS and use a global ( because this method is not very fast compare to operator.attrGettr )

Code:
if (sys.platform == 'darwin'): MacOS = True

Tcho !

Thanks Sto, I'll be using the crazy 'lambda' thing from the Python Wiki. So

lakeNeighbors.sort(key=operator.attrgetter('altitude'),reverse=False)
will be
lakeNeighbors.sort(lambda x,y:cmp(x.altitude,y.altitude))

I'm not too concerned about time, I figure that you only have to generate it once per game, and a good map is worth the wait.
 
Thanks Sto, I'll be using the crazy 'lambda' thing from the Python Wiki. So

lakeNeighbors.sort(key=operator.attrgetter('altitude'),reverse=False)
will be
lakeNeighbors.sort(lambda x,y:cmp(x.altitude,y.altitude))

I'm not too concerned about time, I figure that you only have to generate it once per game, and a good map is worth the wait.

I just wanted to say that sto's changes worked for me. Thanks, Sto!

I understand what a crazy lambda thing is from my lisp and perl background. Have fun!

-K (for Kathryn)
 
I just wanted to say that sto's changes worked for me. Thanks, Sto!

-K (for Kathryn)

Good news!

Update coming soon. The new starting code works great, except for dropping people into the ocean once in a while. Hopefully, I'll have a publish this weekend.
 
It's an ok random generator, what you are seeing is a map crash. If you use any randoms yourself, be sure to use PWRand. Hopefully it's not a crash that I just failed to test for.
No idea here... I double checked and every check for a random value that I use is using your PWRand.random() or PWRand.randint(). Anyway, setting it to use the map rand vs. the python rand does the trick in MP games- no more OOS issues when using your base script.
 
No idea here... I double checked and every check for a random value that I use is using your PWRand.random() or PWRand.randint(). Anyway, setting it to use the map rand vs. the python rand does the trick in MP games- no more OOS issues when using your base script.

Excellent!

As for your map crash, if you enable the Python error messages you should get a popup explaining why it crashed. I don't remember how to set that up, because I set mine up years ago. Also, if you have logs enabled you can check PythonErr.log.
 
Top Bottom