1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

The Map Building Process

Discussion in 'Civ4 - Modding Tutorials & Reference' started by Temudjin, Feb 9, 2010.

  1. Temudjin

    Temudjin Chieftain

    Joined:
    Oct 16, 2007
    Messages:
    90
    I've mentioned this already in the SDK forum, but here may be a better place for a reference list. I hope this can be of use for aspiring map-makers: :hatsoff:
    Code:
    [FONT="Courier New"][B]-------------------------------------------------------------------
    The Map Building Process according to Temudjin
        --> also see Bob Thomas in "CvMapScriptInterface.py"
        (in ..\Assets\Python\EntryPoints)
    -------------------------------------------------------------------[/B]
    0)     - Get Map-Options
    0.1)     getNumHiddenCustomMapOptions()
    0.2)     getNumCustomMapOptions()
    0.3)     getCustomMapOptionDefault()
    0.4)     isAdvancedMap()
    0.5)     getCustomMapOptionName()
    0.6)     getNumCustomMapOptionValues()
    0.7)     isRandomCustomMapOption()
    0.8)     getCustomMapOptionDescAt()
    0.9)     - Get Map-Types
    0.9.1)     isClimateMap()
    0.9.2)     isSeaLevelMap()
    1)     beforeInit()
    2)     - Initialize Map
    2.2)     getGridSize()
    2.3.1)   getTopLatitude()            # always use both
    2.3.2)   getBottomLatitude()         # always use both
    2.4.1)   getWrapX()                  # always use both
    2.4.2)   getWrapY()                  # always use both
    3)     beforeGeneration()
    4)     - Generate Map
    4.1)     generatePlotTypes()
    4.2)     generateTerrainTypes()
    4.3)     addRivers()
    4.4)     addLakes()
    4.5)     addFeatures()
    4.6)     addBonuses()
    4.6.1)     isBonusIgnoreLatitude()*
    4.7)     addGoodies()
    5)     afterGeneration()
    6)     - Select Starting-Plots
    6.1)     minStartingDistanceModifier()
    6.2)     assignStartingPlots()
    7)     - Normalize Starting-Plots
    7.1)     normalizeStartingPlotLocations()+
    7.2)     normalizeAddRiver()
    7.3)     normalizeRemovePeaks()
    7.4)     normalizeAddLakes()
    7.5)     normalizeRemoveBadFeatures()+
    7.6)     normalizeRemoveBadTerrain()+
    7.7)     normalizeAddFoodBonuses()+
    7.7.1)     isBonusIgnoreLatitude()*
    7.8)     normalizeGoodTerrain()+
    7.9)     normalizeAddExtras()
    7.9.1)     isBonusIgnoreLatitude()*
    8 )    startHumansOnSameTile()
    
    * used by default 'CyPythonMgr().allowDefaultImpl()' in:
      addBonuses(), normalizeAddFoodBonuses(), normalizeAddExtras()
    + ['Fall From Heaven 2' and modmods] Mods in which the Flavour Start
      option is selected, will NOT call these functions.
    [/FONT]
     
  2. LeHam

    LeHam Chieftain

    Joined:
    Jun 16, 2009
    Messages:
    375
    Location:
    Pacific Coast
    Er--I can't tell--what does all that do???
     
  3. Temudjin

    Temudjin Chieftain

    Joined:
    Oct 16, 2007
    Messages:
    90
    These are the functions that are called by Civ4 to build a map.

    If you define those functions in your own map-script, you can control how to build the map. For those functions you don't define, a default process - which you can access with CyPythonMgr().allowDefaultImpl() - will be called.

    You might want to have a look at the map-scripts in the 'Public Maps' folder to see how it is done. Also see "CvMapScriptInterface.py" (in ..\Assets\Python\EntryPoints) for a more detailed explanation.
     
  4. PhoenixMuse

    PhoenixMuse Mage

    Joined:
    Dec 6, 2007
    Messages:
    86
    Location:
    Walnut Creek, CA
    Is there a key like this for the xml for editing the terrain itself?
     
  5. vktj

    vktj Chieftain

    Joined:
    May 6, 2012
    Messages:
    199
    This is a very useful tool for writing map scripts; thank you for preparing this.
     
  6. vktj

    vktj Chieftain

    Joined:
    May 6, 2012
    Messages:
    199
    Some notes on the items that decide and grab the pull-down options for random maps, based on looking at Totestra's (PW2's) source code:

    getNumHiddenCustomMapOptions() Not in Totestra (optional)

    getNumCustomMapOptions(): Input: None. Output: Int, number of parameters (besides size, climate, and sea level) that can be adjusted by the user

    getCustomMapOptionDefault(): Input: Array. One element: The option number. Output: Int, the default value for this option

    isAdvancedMap(): Input: None. Output 0 if this map is "not" an "advanced" map (a map which needs "custom game" to be seen) Make this 0; some mods don't work with "custom game".

    getCustomMapOptionName(): Input: Array, one element. One element: The option number. The output is the name for this option. Keep in mind that in "quick game", this is preceded by the string "Select a" so it should ideally be a grammatically singular noun.

    getNumCustomMapOptionValues(): Input: Array, one element. Element 0: The option number. Output: The number of possible values this option can have.

    isRandomCustomMapOption(): Input: Array, one element. Element 0: The option number. Output: False if this option's value should be randomly chosen, True if it should be random.

    getCustomMapOptionDescAt(): Input: Array, two elements. Element 0: The option number. Element 1: The choice for this option. Output: A string describing a given choice for a given option.

    isClimateMap(): Input: None. Output: 0 if we are not allowed to select the climate for this map; 1 if we are allowed the select the climate for this map (Temperate, Arid, Cold, Rocky, and Tropical, but not in that order).

    isSeaLevelMap(): Input: None. Output: 0 if we are not allowed to select the sea level for this map; 1 if we are

    beforeInit(): Input: None. Output: None. Used by the map script to store the options selected by the user. To get options, start by having CyGlobalContext() create an object. Use the method getMap() in the CyGlobalContext()-created object to get map information. For example: foo = CyGlobalContext() ; bar = foo.getMap(). This second "getMap()" object has the following methods of interest for getting map script options:

    • getClimate() Get the "climate" option. Input: None. Output: 0: Temperate, 1: Tropical, 2: Arid, 3: Rocky, 4: Cold
    • getSeaLevel() Get the "sea level" option. Input: None. Output: 0: Low 1: Medium (normal) 2: High
    • getCustomMapOption() Get any other option. Input: Integer (the option in question). Output: Integer (the value for this option)

    getGridSize(): Input: Array; one element. Array element: Integer with map size (0: Dual, 1: Tiny, 2: Small, 3: Standard, 4: Large, 5: huge). Presumably mods with even larger maps will give this function values like 6, 7, 8, etc. Output: If this element has the value -1, the Python script must return an empty array. Otherwise, return a tuple in the form (width, height), where width is the width of the map to be generated (in a unit which represents 4 squares), and height is the height divided by 4.

    In other words, if this returns (36,24), that means Civilization 4 should generate a 144x96 map (144 squares wide, 96 squares high). Note that the "multiply by 4" rule was removed in the Colonization total conversion.
     
  7. vktj

    vktj Chieftain

    Joined:
    May 6, 2012
    Messages:
    199
    Once getGridSize() is called, the random map generator has all of the information it needs to generate a map. The subsequent functions are used strictly to give to Civilization 4 the information about the generated map.

    They are:

    getTopLatitude(): Input: none. Output: An integer between -90 and 90. Does not appear to affect gameplay, but the higher this value is to 90, the more likely trees near the north of the map will be drawn with snow on them. Value needs to be higher than getBottomLatitude().

    getBottomLatitude(): Input: none. Output: An integer between -90 and 90. Does not appear to affect gameplay, but the lower this number is, the more likely trees near the south of the map will be drawn with snow on them. Value needs to be lower than getTopLatitude().

    To be pedantic: we can have getTopLatitude() return, say, -89, and getBottomLatitude() return, say, -90, and have maps with lots of snowy trees on them. Civ4, presumably, calculates the latitude of each horizontal line in the map based on these two values and performing interpolation; the higher the absolute value for a given line based on this calculation determines how frequently we have snowy trees.

    getWrapX(): Input: None. Output: True if the map wraps on the X axis, otherwise false. A flat map would have "False" here; a cylindrical or toric map would have "True" here.

    getWrapY(): Input: None. Output: True if the map wraps on the Y axis, otherwise false. A non-toric map would usually have "False" here; a toric map would have "True" here. Note that this should be set to "False" for maps played in vanilla Civ4 or in Civ4 Warlords; the rendering of toric maps was buggy until Beyond the Sword.

    beforeGeneration(): A map script can safely function without this call

    The following functions return one-dimensional arrays the correspond to the Civ4 map thusly:

    The one-dimensional array starts in the lower left corner of the map, and goes left to right, bottom to top. For example, the point on the lower left of a 144x96 map will be the first element of the array (which is index number 0 because Python uses 0-indexed arrays). The point to the right of that will be the second element (element #1), and element #144 (the 145th element) will be the point directly above the lower left corner of the map.

    In a 5x10 map (not that a map script can actually make a 5x10 map, but humor me), it goes like this:

    Code:
    40 41 42 43 44 45 46 47 48 49
    30 31 32 33 34 35 36 37 38 39
    20 21 22 23 24 25 26 27 28 29
    10 11 12 13 14 15 16 17 18 19
     0  1  2  3  4  5  6  7  8  9
    
    generatePlotTypes(): Input: None. Output: A flat one-dimensional array of "plot types". A single plot type can have one of four possible values:

    • CvPythonExtensions.PlotTypes.PLOT_OCEAN
    • CvPythonExtensions.PlotTypes.PLOT_LAND
    • CvPythonExtensions.PlotTypes.PLOT_HILLS
    • CvPythonExtensions.PlotTypes.PLOT_PEAK

    generateTerrainTypes(): Input. None. Output: A flat one-dimensional array of integers. The values are 0: Grass 1: Plains 2: Desert 3: Tundra 4: Snow 5: Coast 6: Ocean 7: Peak 8: Hill.

    addRivers(): Input: None. Output: None.

    Rivers are placed between squares. To add a river to a given square (x, y) on the map, do something like this:

    Code:
    foo = CyGlobalContext()
    bar = foo.getMap()
    baz = bar.plot(x, y)
    
    And then one or more of:

    Code:
    baz.setWOfRiver(True,CardinalDirectionTypes.CARDINALDIRECTION_SOUTH)
    baz.setNOfRiver(True,CardinalDirectionTypes.CARDINALDIRECTION_EAST)
    baz.setWOfRiver(True,CardinalDirectionTypes.CARDINALDIRECTION_NORTH)
    baz.setNOfRiver(True,CardinalDirectionTypes.CARDINALDIRECTION_WEST)
    
    The second argument determines which direction the river flows.

    addLakes(): Input: None. Output: None.

    To add a lake to the map at point (x,y), do something like this:

    Code:
    foo = CyGlobalContext()
    bar = foo.getMap()
    baz = bar.plot(x, y)
    baz.setTerrainType(5, True, True)
    
    5 corresponds to a "coast" (shallow water) terrain type.

    addFeatures(): Input: None. Output: None.

    This is one place where we can add forests, jungles, ice, oasis, and flood plains. To put an oasis at point (x,y), do something like:

    Code:
    foo = CyGlobalContext()
    bar = foo.getMap()
    baz = bar.plot(x, y)
    baz.setFeatureType(2,0)
    
    Possible values to give setFeatureType():

    • (0,0) Ice
    • (1,0) Jungle
    • (2,0) Oasis
    • (3,0) Flood plains
    • (4,0) Leafy forest
    • (4,1) Evergreen forest
    • (4,2) Snowy forest

    addBonuses(): Input: None. Output: None.

    This is one possible place to add bonuses (resources).

    To put a bonus (resource) at point (x,y), do something like:

    Code:
    foo = CyGlobalContext()
    bar = foo.getMap()
    baz = bar.plot(x, y)
    count = foo.getNumBonusInfos()
    if count > 0:
        baz.setBonusType(0)
    
    isBonusIgnoreLatitude(): A map script can safely run without declaring this function

    addGoodies(): This adds huts to the map. If this is not defined, default huts are added. If it is defined, it can be used to add huts, or to disable huts being added (by simply returning without doing anything)

    afterGeneration(): A map script can safely run without declaring this function

    minStartingDistanceModifier(): A map script can safely run without declaring this function

    assignStartingPlots(): Input: None. Output: None.

    To make a given square a given player's starting plot, where (x, y) is where they will start and playerNum is the number for this player:

    Code:
    foo = CyGlobalContext()
    bar = foo.getMap()
    baz = bar.plot(x, y)
    boo = foo.getPlayer(playerNum)
    boo.setStartingPlot(baz, true)
    
    The "normalize" functions are not used by Totestra:

    Code:
    def normalizeAddRiver():
        return
    def normalizeAddLakes():
        return
    def normalizeAddGoodTerrain():
        return
    def normalizeRemoveBadTerrain():
        return
    def normalizeRemoveBadFeatures():
        return
    def normalizeAddFoodBonuses():
        return
    def normalizeAddExtras():
        return
    def normalizeRemovePeaks():
        return
    
     
  8. vktj

    vktj Chieftain

    Joined:
    May 6, 2012
    Messages:
    199
    Here is a very simple map script. This makes a tiny 12x12 map where most of the squares are desert; there are only two non-desert squares on the map, as well as two oases near these non-desert squares.

    It sets the starting plots for only two players, and can be played as a very simple 2-player game.

    Here is the source code:

    Code:
    # How simple can we make a functioning map script
    # Placed in the public domain 2012 by Sam Trenholme
    
    from CvPythonExtensions import *
    import CvUtil
    import CvMapGeneratorUtil 
    
    def getNumCustomMapOptions():
            return 0
    def getCustomMapOptionDefault(x):
            return 0
    def isAdvancedMap():
            return 0
    def getCustomMapOptionName(x):
            return ""
    def getNumCustomMapOptionValues(x):
            return ""
    def isRandomCustomMapOption(x):
            return False
    def getCustomMapOptionDescAt(x):
            return ""
    def isClimateMap():
            return 0
    def isSeaLevelMap():
            return 0
    def beforeInit():
            return
    def getGridSize(x):
            return (3,3) # Tiny 12x12 map
    def getTopLatitude():
            return 0
    def getBottomLatitude():
            return 0
    def getWrapX():
            return False
    def getWrapY():
            return False
    def generatePlotTypes():
            out = []
            for a in range(12 * 12):
                    out.append(PlotTypes.PLOT_LAND)
            return out
    def generateTerrainTypes():
            out = []
            for a in range(12 * 12):
                    if a != 27 and a != 116:
                            out.append(2) # Desert
                    else:
                            out.append(0) # Grassland
            return out
    def addRivers():
            return
    def addLakes():
            return
    def addFeatures():
            gc = CyGlobalContext()
            map = gc.getMap()
            plot = map.plot(3,1)
            plot.setFeatureType(2,0)
            plot = map.plot(8,10)
            plot.setFeatureType(2,0)
    def addBonuses():
            return
    def addGoodies():
            return # Disables huts
    def assignStartingPlots():
            gc = CyGlobalContext()
            map = gc.getMap()
            player1 = gc.getPlayer(0)
            plot = map.plot(3,3)
            player1.setStartingPlot(plot, true)
            player2 = gc.getPlayer(1)
            plot = map.plot(9,9)
            player2.setStartingPlot(plot, true)
    def normalizeAddRiver():
            return
    def normalizeAddLakes():
            return
    def normalizeAddGoodTerrain():
            return
    def normalizeRemoveBadTerrain():
            return
    def normalizeRemoveBadFeatures():
            return
    def normalizeAddFoodBonuses():
            return
    def normalizeAddExtras():
            return
    def normalizeRemovePeaks():
            return
    
     

Share This Page