• Sign up for the Black Forest 2v2 Tournament on Community Patch 1.6 RC and win a Voobly premium membership.     

    [ More Information ]

4.5 Random map scripting

There is not too much in the field of random map scripting that I can add. The best thing I can do is share the most complete guide I know of Definitive Random Map Scripting Guide. It’s a very complete and up to date guide, that even informs which version of the game supports each command, so that people programming for older versions of the game are aware of it.

What I can contribute is with ways to include features that are present in Definitive Edition or that, historically, have required the use of a different data mod to work.

Allied vision​

Having allied vision activated at the start of the game is easy. That functionality depends on a resource that is initially set to zero until the players build the market.

To set it to one at the start, in the section PLAYER SETUP, add the following:
#const REVEAL_ALLY 50
effect_amount MOD_RESOURCE REVEAL_ALLY ATTR_ADD 1


The first line declares a value and gives it the resource ID and the second one adds 1 to the value of the resource, setting it to 1.

No onager cutting​

Similarly to the allied vision, preventing onagers from cutting trees depends on disabling the tech that allows it. In the section PLAYER SETUP, add the following:
effect_amount SET_ATTRIBUTE ONAGER ATTR_BLAST_LEVEL 2
#const R_OCUTTING 153
effect_amount ENABLE_TECH R_OCUTTING ATTR_DISABLE 153

Longer lasting resources​

This feature can be added into the map by changing the ammount fo resources the objects carry. You need to add the following lines in the PLAYER SETUP section:

Code:
effect_amount SET_ATTRIBUTE BAMBOO_TREE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE OAKTREE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE PINETREE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE PALMTREE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE TREE_A ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_B ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_C ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_D ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_E ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_F ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_G ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_H ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_I ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_J ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_K ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE TREE_L ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE FOREST_TREE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE SNOWPINETREE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE JUNGLETREE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE DRAGONTREE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE BAOBABTREE ATTR_STORAGE_VALUE 2000
effect_amount SET_ATTRIBUTE ACACIA_TREE ATTR_STORAGE_VALUE 1500
effect_amount SET_ATTRIBUTE DLC_MANGROVE_TREE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE DLC_RAINTREE ATTR_STORAGE_VALUE 1000

#const GURJARA_BUSH 1628
effect_amount SET_ATTRIBUTE FORAGE_BUSH ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE DLC_ORANGEBUSH ATTR_STORAGE_VALUE 1250
effect_amount SET_ATTRIBUTE GURJARA_BUSH ATTR_STORAGE_VALUE 1250

effect_amount SET_ATTRIBUTE GOLD_MINE ATTR_STORAGE_VALUE 8000
effect_amount SET_ATTRIBUTE STONE_MINE ATTR_STORAGE_VALUE 3500

effect_amount SET_ATTRIBUTE FISH ATTR_STORAGE_VALUE 2000
effect_amount SET_ATTRIBUTE SHORE_FISH ATTR_STORAGE_VALUE 2000
effect_amount SET_ATTRIBUTE GREAT_FISH_MARLIN ATTR_STORAGE_VALUE 3500
effect_amount SET_ATTRIBUTE GREAT_FISH_MARLIN2 ATTR_STORAGE_VALUE 3500
effect_amount SET_ATTRIBUTE DORADO ATTR_STORAGE_VALUE 2250
effect_amount SET_ATTRIBUTE SALMON ATTR_STORAGE_VALUE 2250
effect_amount SET_ATTRIBUTE TUNA ATTR_STORAGE_VALUE 2250
effect_amount SET_ATTRIBUTE 458 ATTR_STORAGE_VALUE 2250

#const GOOSE 382
#const PIG 997
effect_amount SET_ATTRIBUTE DLC_LLAMA ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE GOOSE ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE SHEEP ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE DLC_COW ATTR_STORAGE_VALUE 1500
effect_amount SET_ATTRIBUTE TURKEY ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE PIG ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE DLC_GOAT ATTR_STORAGE_VALUE 1000
effect_amount SET_ATTRIBUTE DLC_WATERBUFFALO ATTR_STORAGE_VALUE 1500

#const IBEX 1472
effect_amount SET_ATTRIBUTE DEER ATTR_STORAGE_VALUE 1400
effect_amount SET_ATTRIBUTE DLC_ZEBRA ATTR_STORAGE_VALUE 1400
effect_amount SET_ATTRIBUTE DLC_OSTRICH ATTR_STORAGE_VALUE 1400
effect_amount SET_ATTRIBUTE IBEX ATTR_STORAGE_VALUE 1400

effect_amount SET_ATTRIBUTE WILD_BOAR ATTR_STORAGE_VALUE 3400
effect_amount SET_ATTRIBUTE JAVELINA ATTR_STORAGE_VALUE 3400
effect_amount SET_ATTRIBUTE ELEPHANT ATTR_STORAGE_VALUE 4000
effect_amount SET_ATTRIBUTE DLC_RHINO ATTR_STORAGE_VALUE 4000

You can change the resource numbers to whatever you want. In this example, it’s just the normal value multiplied by 10.

Triple tech mode​

The triple technology mode is a game mode where most of the technologies (excluding upgrades and ages, for example) can be researched three times. It has existed for years as a separated data mod but there is a way to implemented in combination with the normal data mod.

First, we need to program the extra technologies, effects, queuable versions and extra resources to prevent them from researching more than once, all that we saw about queuable technologies. Additionally, we program the new technologies to appear with the original one. For example, technologies 1480 and 1482 are researched once technology 816 is researched. These three are the ''make available'' technologies for Bloodlines. This way, if a civilization has no access to a technology, the duplicates are also disabled.

With this implementation, a normal game should have the triplicated technologies. To avoid this, we program a disabler in technology 1240, ''Triple Tech Off''. This technology is researched when the Town Center is built, the reason for it we'll see it in a moment, and disables all the duplicates of technologies.

To summarize, we have the triplicated technologies programmed to appear in every game and a disabling technology that is researched at the beginning of every game that prevents the duplicates from appearing. This rather convoluted implementation is done so that the map script can control whether triple tech mode is enabled or not but the mode is disabled unless stated otherwise.

By default, every map doesn't have the triple tech mode but, if we disable the ''Triple Tech Off'', technology, the duplicated technologies appear. For this reason, ''Triple Tech Off'' is connected to the Town Center; we need it to be researched a bit after the map is generated to give time to an eventual disabling.

Therefore, every map that has triple tech mode enabled, has these lines in the PLAYER SETUP section:
#const TRIPLE_TECH_OFF 1240
effect_amount ENABLE_TECH TRIPLE_TECH_OFF ATTR_DISABLE 1240

Empire wars​

The empire wars in the Community Patch differs from the Definitive Edition one in the fact that, although the Barracks, Blacksmith and houses appear at the start of the game, the economic buildings such as mill and lumber camps have to be built. However, the first buildings of this type are free and instantaneous and, once they are placed, buildings return to normal.

Enabling the mode​

The empire wars mode work in a similar way to the triple tech mode. We have technology 2008, ''Empire Wars'', that makes economic buildings free and instantaneous. We also have technology 2007, ''Disable Empire Wars'' that disables the ''Empire Wars'' technology. Both of these technologies are researched when the Town Center is built but the order of them is important; ''Disable Empire Wars'' has to be researched first so as to disable ''Empire Wars'' if needed.

In the map script, we add in the PLAYER SETUP section:
#const DISABLE_EW 2007
effect_amount ENABLE_TECH DISABLE_EW ATTR_DISABLE 2007

Adding initial units and buildings

The PLAYER SETUP section is not the only thing we need to do in order to have the mode functional. We also have to add the barracks, blacksmith, houses, farms, and all the additional villagers. Also, we need to make more of the map visible. This last part is done with a copy of the Outpost that has negative health, so that it discovers map and dies instantly.

The code for adding these outposts is the following:
#const EW_OUTPOST 2801
create_object EW_OUTPOST
{
number_of_objects 1
number_of_groups 8
temp_min_distance_group_placement 6
set_place_for_every_player
group_placement_radius 1
min_distance_to_players 13
max_distance_to_players 15
}


The full list of objects to add to the map, save for the distance which may vary, is the following. Also, map scripts should not have lurable animals or predators nearby.

Code:
create_object FARM
{
number_of_objects 2
set_place_for_every_player
min_distance_to_players 4
max_distance_to_players 4
}

create_object VILLAGER
{
number_of_objects 6
set_place_for_every_player
min_distance_to_players 2
max_distance_to_players 2
}

create_object VILLAGER
{
number_of_objects 2
group_placement_radius  1
set_place_for_every_player
min_distance_to_players 4
max_distance_to_players 4
}

create_object VILLAGER
{
number_of_objects 4
number_of_groups  5
temp_min_distance_group_placement 6
set_place_for_every_player
group_placement_radius  1
min_distance_to_players 10
max_distance_to_players 12
}
#const EW_OUTPOST 2801
create_object EW_OUTPOST
{
number_of_objects 1
number_of_groups  8
temp_min_distance_group_placement 6
set_place_for_every_player
group_placement_radius  1
min_distance_to_players 13
max_distance_to_players 15
}

create_object HERDABLE_A
{
  set_place_for_every_player
  min_distance_to_players 1
  max_distance_to_players 1
  }

create_object HOUSE
{
number_of_objects 6
group_placement_radius  5
set_place_for_every_player
min_distance_to_players 7
max_distance_to_players 7
min_distance_group_placement  1
}

create_object BARRACKS
{
set_place_for_every_player
min_distance_to_players 8
max_distance_to_players 9
min_distance_group_placement  1
}

create_object BLACKSMITH
{
set_place_for_every_player
min_distance_to_players 8
max_distance_to_players 9
min_distance_group_placement  1
}

Re-setting building cost​

Once you have built instantaneously and for free the initial buildings, that is, three lumber camps, one mill and one mining camp, prices should return to normal. Armenians and Georgians have four mule carts and a mill available.

In order to achieve this, economic buildings have annex units that trigger technologies to record that a building is done. Let's look at the case of the lumber camp, as it is more complex:
  1. \item Lumber camps have an annex unit, 2793, called ''EW lumber camp annex 1a''.
  2. ''EW lumber camp annex 1a'' dies instantly and turns into unit 2794, ''EW lumber camp annex 1b''.
  3. ''EW lumber camp annex 1b'' triggers technology 2009, ''EW lumber camp 1''.
  4. The effect of ''EW lumber camp 1'' is to upgrade ''EW lumber camp annex 1a'' to ''EW lumber camp annex 2a'' and ''EW lumber camp annex 1b'' to ''EW lumber camp annex 2b''.
With the different versions of the annex units we can keep track of how many lumber camps have been built. The third annex unit to the lumber camp triggers technology 2011 that resets the cost of the lumber camp.

All these technologies are also disabled with the ''Disable Empire Wars'' technology, so as to not produce any unwanted behavior when not playing empire wars.

Apart from these technologies to reset costs, we have to program additional technologies for the civilizations that have a bonus related to the cost of buildings, like Japanese or Malians. These technologies are researched after the reset of costs.

Circle and fixed placement​

These two features are based on using the “direct_placement” option, that puts objects in speciffic coordinates, rather than the “random_placement” that is usually used in random maps.

Circle placement​

The command "circle-radius" is used to set the bases of the players at the same distance to the center, along a circle of a given radius. This helps avoiding edges of the map.

The idea of placing players along a circle was around before this command was implemented, I’ve seen old scripts by Chrazini that do that. However, I’m not aware of a tool that generates that code automatically, as it can easily be 10,000 extra lines, which is impossible to copy from an existing script in order to adapt it.

The script I’m sharing here creates the necessary code to copy directly into the random map script and handle the random placement of players. It uses the command “assign_to AT_TEAM” that gives the position to a member of the team without considering order. Note the following:
  • You have to set the placement to “direct_placement” in the section. Normal random maps have “random_placement” instead.
  • This code is meant to be placed in the section, where the “create_land” of the players should be.
  • When using it, you need to modify the variable constants with the lines of code that should go in all “create_land” entries, such as terrain_type, land_percent, base size, etc.
  • You can set the radius of the circle for each number of players.
  • This code will add a bit of deviation in the circle when having 4 or less players and put players equidistantly when having 5 or more, so as not to have bases intersecting.
Python:
import math
'''
function to generate all the team distributions of a given number of players
taking care that teams have at least 2 players and only the first teams are filled
n: number of players to assign
distrib: list with the number of players in each team. Initially all zeros
    indexes: 0: unassigned players, 1,2,3,4: teams
t: team to asign, initially 1
'''
def team_variations(n,distrib,t):
    #list with all the variations to be retrieved after all team_variations executions finish
    global variations
 
    #case of unassigned players, team 5
    if t == 5:
        distrib[0]=n #add players to the unasigned players team
        variations.append(distrib)#add distribution to the list of variations
        return
 
    #don't assign less than 2 players to a team
    elif n < 2: #if you have less than two players to assign
        team_variations(n,distrib,t+1) #step team counter until reaching 5, unassigned case
        return
 
    #don't leave teams empty
    elif t >= 2 and distrib[t-1] == 0: #if the team to assign is 2 or more but the previous is empty
        team_variations(n,distrib,t+1) #step team counter until reaching 5, unassigned case
        return
 
    #fill the team with every possible variation of number of players
    #for number of players between 2 and n
    for i in range(2,n+1):
        #create a copy of the current distribution and set the number of players
        #on the current team to assign to i
        distrib_aux = distrib[:]
        distrib_aux[t] = i
        #recursive call with less available players and for the next team
        team_variations(n-i,distrib_aux,t+1)

    #recursive call with all the players in the next team, used to get to the unassigned case
    team_variations(n,distrib,t+1)

'''
function that computes the number of teams with positive number of players
variation: list with the number of players in each team.
    indexes: 0: unassigned players, 1,2,3,4: teams
'''
def number_of_teams(variation):
    k = 0
    for i in range(1,5):
        if variation[i] > 0:
            k += 1
    return k

'''
function that returns a string with the team and the number of players in it
variation: list with the number of players in each team.
    indexes: 0: unassigned players, 1,2,3,4: teams
i: team to display
'''
def team_size(variations,i):
    return 'TEAM'+str(i)+'_SIZE'+str(variations[i])

'''
Function that computes the coordinates of a player base along a circle
radius: of the circle from the center of the map, measured in percent of total length
players: number of  players to fit in the circle
per_player: number of positions available to every player.
    this controls that, when computing for 2 players, one can create more than 2 positions
    but still have the players opposed.
'''
def circle(radius, players, per_player=1, add_deviations=True):
    '''
    rotations and events handle the probability distribution.
    rotations decide the overall position of players, there are as many as the value of per-player
    events decide which of the positions belong to which player, there are as many as the value of players
    '''
    #rotations, given by per_player
    rotations = [100//per_player]*per_player #probabilities have to be an integer number
    #events, given by players
    events = [100//players]*players #probabilities have to be an integer number

    #distribute the remining probability up to 100
    #per_player
    rem = 100 - rotations[0]*per_player
    while rem > 0:
        for i in range(per_player):
            rotations[i] += 1
            rem -= 1
            if rem == 0:
                break
    #players
    rem = 100 - events[0]*players
    while rem > 0:
        for p in range(players):
            events[p] += 1
            rem -= 1
            if rem == 0:
                break

    #angle to generate as many rotations as given in per_player
    pp_angle = 2*math.pi/(players*per_player)
    #angle between players
    angle = 2*math.pi/(players)

    '''
    output string will hold all the text that nedds to be added to the rms file
    '''
    output = ''
    #write the random sample that generates the cases:
    #rotations
    if per_player > 1:
        output+='  start_random\n'
        for i in range(per_player):
            output+='    percent_chance '+str(rotations[i])
            output+=' #define ROTATION_'+str(i+1)+'\n'
        output+='  end_random\n\n'

    #events
    output+='  start_random\n'
    for p in range(players):
        output+='    percent_chance '+str(events[p])
        output+=' #define PLACE_'+str(p+1)+'\n'
    output+='  end_random\n\n\n'


    #write the coordinates of the positions
    global variations, constants
    '''
    variations will hold all the valid combinations of players and teams
    constants holds the text that has to go in all the create_land entries
    '''
    variations = []
    team_variations(players,[0]*5,1)
    #now variations holds all the possible variations of players per team

    #Create as many lands as players, adding the random cases given by rotation
    for p in range(players):
        '''
        create the player land
        '''
        output += '  create_land {\n'
        output += constants #text that has to be written always
 
        if per_player > 1: #if there is more than one rotation
            first_rotation = True #flag to write "if" or "elseif"
            for i in range(per_player):
                if first_rotation:
                    output += '    if '
                    first_rotation = False
                else:
                    output += '    elseif '
                output += 'ROTATION_'+str(i+1)+'\n'
                #include a random deviation in the angle
                if add_deviations:
                    output += '      start_random\n'
                    for j in range(-1,2):
                        output+='        percent_chance '+str(40-10*abs(j))
                        coord_x = round(50 + radius*math.cos(angle*p+pp_angle*i+angle/4*j))
                        coord_y = round(50 + radius*math.sin(angle*p+pp_angle*i+angle/4*j))
                        output+=' land_position '+str(coord_x)+' '+str(coord_y)+'\n'
                    output += '      end_random\n'
                else:
                    coord_x = round(50 + radius*math.cos(angle*p+pp_angle*i))
                    coord_y = round(50 + radius*math.sin(angle*p+pp_angle*i))
                    output+='      land_position '+str(coord_x)+' '+str(coord_y)+'\n'
            output += '    endif\n\n'
        else: #per_player == 1
            #include a random deviation in the angle
            if add_deviations:
                output += '    start_random\n'
                for j in range(-1,2):
                    output+='      percent_chance '+str(40-10*abs(j))
                    coord_x = round(50 + radius*math.cos(angle*p+angle/4*j))
                    coord_y = round(50 + radius*math.sin(angle*p+angle/4*j))
                    output+=' land_position '+str(coord_x)+' '+str(coord_y)+'\n'
                output += '    end_random\n'
            else:
                coord_x = round(50 + radius*math.cos(angle*p))
                coord_y = round(50 + radius*math.sin(angle*p))
                output+='    land_position '+str(coord_x)+' '+str(coord_y)+'\n\n'

        '''
        assign the player land
        '''
        #for each variation of teams
        for v in variations:
            if v[0] == 0 and v[1] == players:#ignore the case where all players are in team 1
                continue
            output += '    if '
            output += str(number_of_teams(v))+'_TEAM_GAME\n'
   
            #write all the sizes of each team in the variation (with actual players)
            #this creates several nested "if" clauses to act as "and"
            for t in range(1,5): #skip team 0, unassigned
                if v[t] == 0: #if the team is empty, the others will be too
                    break
                output += '      if '+team_size(v,t)+'\n'

            #Distribute land for teams
            #if there are no teams
            if number_of_teams(v) == 0:
                output += '      assign_to AT_TEAM 0 0 0\n'
                output += '    endif\n\n' #this closes the "if" of variation v
                continue #the rest of the loop is needed in case of teams
   
            #if there are teams
            #list with as many elements as the number of players
            #with as many 0s as unassigned players, as many 1s as players in team 1, etc
            teams = [0]*v[0]+[1]*v[1]+[2]*v[2]+[3]*v[3]+[4]*v[4]
            first_players = True #flag to write "if" or "elseif"
            #for each player
            for l in range(players):
                string = 'PLACE_'+str(l+1)#random sample given by the events list, one per player
                if first_players:
                    output+='        if '
                    first_players = False
                else:
                    output+='        elseif '
                output += string+'\n'
                #in each iteration of the l loop, the index (p+l)%players will increase by one
                #the value p is placed so that each land is assigned to consecutive teams
                output += '          assign_to AT_TEAM '+str(teams[(p+l)%players])+' 0 0\n'
            output += '        endif\n'

            #endif for team sizes, this closes the nested "if" from above
            for t in range(1,5): #skip team 0, unassigned
                if v[t] == 0: #if the team is empty, the others will be too
                    break
                output += '      endif\n'
            #endif of variation v
            output += '    endif\n\n'

        output += '  }\n\n' #end of the create_land
    return output


global constants #text that has to be written in every create_land
'''
EDIT THIS with the constants of the particular map
'''
constants='    terrain_type LAYER_A\n    number_of_tiles 300\n    base_size 12\n    clumping_factor 15\n    other_zone_avoidance_distance 8\n'

file=open('circles.txt','w')
per_players = [4,2,2,1,1,1,1] #number of rotations for each number of players
radius = [34,34,35,36,37,38,38] #radius of the circles for each number of players
for p in range(2,9):
    if p == 2:
        file.write('if ')
    else:
        file.write('elseif ')
    file.write(str(p)+'_PLAYER_GAME\n')
    #generate the code for the circle of the given number of players and write it in the file
    if p <=4:
        file.write(circle(radius[p-2],p,per_players[p-2],True))
    else:
        file.write(circle(radius[p-2],p,per_players[p-2],False))
file.write('endif')
file.close()

See the map “DE_Big_Freeze” for an example of where to put the code.

Fixed Positions​

Definitive Edition introduced the option Team Positions that places players of the same team in order, so that middle numbers are always pockets. For Wololo Kingdoms, this was adapted in some maps of the ECL. The script I did is very similar to the one that creates circles except that it only works for 3v3 and 4v4 games and that it replaces the “assign_to AT_TEAM” command with “assign_to AT_COLOR”, that gives the land to the speciffic color. For this reason, players can only use speciffic colors, as explained in Features.

Python:
import math
'''
function to generate all the team distributions of a given number of players
taking care that teams have at least 2 players and only the first teams are filled
n: number of players to assign
distrib: list with the number of players in each team. Initially all zeros
    indexes: 0: unassigned players, 1,2,3,4: teams
t: team to asign, initially 1
'''
def team_variations(n,distrib,t):
    #list with all the variations to be retrieved after all team_variations executions finish
    global variations
 
    #case of unassigned players, team 5
    if t == 5:
        distrib[0]=n #add players to the unasigned players team
        variations.append(distrib)#add distribution to the list of variations
        return
 
    #don't assign less than 2 players to a team
    elif n < 2: #if you have less than two players to assign
        team_variations(n,distrib,t+1) #step team counter until reaching 5, unassigned case
        return
 
    #don't leave teams empty
    elif t >= 2 and distrib[t-1] == 0: #if the team to assign is 2 or more but the previous is empty
        team_variations(n,distrib,t+1) #step team counter until reaching 5, unassigned case
        return
 
    #fill the team with every possible variation of number of players
    #for number of players between 2 and n
    for i in range(2,n+1):
        #create a copy of the current distribution and set the number of players
        #on the current team to assign to i
        distrib_aux = distrib[:]
        distrib_aux[t] = i
        #recursive call with less available players and for the next team
        team_variations(n-i,distrib_aux,t+1)

    #recursive call with all the players in the next team, used to get to the unassigned case
    team_variations(n,distrib,t+1)

'''
function that computes the number of teams with positive number of players
variation: list with the number of players in each team.
    indexes: 0: unassigned players, 1,2,3,4: teams
'''
def number_of_teams(variation):
    k = 0
    for i in range(1,5):
        if variation[i] > 0:
            k += 1
    return k

'''
function that returns a string with the team and the number of players in it
variation: list with the number of players in each team.
    indexes: 0: unassigned players, 1,2,3,4: teams
i: team to display
'''
def team_size(variations,i):
    return 'TEAM'+str(i)+'_SIZE'+str(variations[i])

'''
Function that computes the coordinates of a player base along a circle
radius: of the circle from the center of the map, measured in percent of total length
players: number of  players to fit in the circle
per_player: number of positions available to every player.
    this controls that, when computing for 2 players, one can create more than 2 positions
    but still have the players opposed.
'''
def circle(radius, players, per_player=1):
    '''
    rotations and events handle the probability distribution.
    rotations decide the overall position of players, there are as many as the value of per-player
    events decide which of the positions belong to which player, there are as many as the value of players
    '''
    #rotations, given by per_player
    rotations = [100//per_player]*per_player #probabilities have to be an integer number
    #events, given by players
    events = [100//players]*players #probabilities have to be an integer number

    #distribute the remining probability up to 100
    #per_player
    rem = 100 - rotations[0]*per_player
    while rem > 0:
        for i in range(per_player):
            rotations[i] += 1
            rem -= 1
            if rem == 0:
                break
    #players
    rem = 100 - events[0]*players
    while rem > 0:
        for p in range(players):
            events[p] += 1
            rem -= 1
            if rem == 0:
                break

    #angle to generate as many rotations as given in per_player
    pp_angle = 2*math.pi/(players*per_player)
    #angle between players
    angle = 2*math.pi/(players)

    '''
    output string will hold all the text that nedds to be added to the rms file
    '''
    output = ''
    #write the random sample that generates the cases:
    #rotations
    if per_player > 1:
        output+='  start_random\n'
        for i in range(per_player):
            output+='    percent_chance '+str(rotations[i])
            output+=' #define ROTATION_'+str(i+1)+'\n'
        output+='  end_random\n\n'

    #events
    output+='  start_random\n'
    for p in range(players):
        output+='    percent_chance '+str(events[p])
        output+=' #define PLACE_'+str(p+1)+'\n'
    output+='  end_random\n\n\n'


    #write the coordinates of the positions
    global variations, constants
    '''
    variations will hold all the valid combinations of players and teams
    constants holds the text that has to go in all the create_land entries
    '''
    variations = []
    team_variations(players,[0]*5,1)
    #now variations holds all the possible variations of players per team

    #Create as many lands as players, adding the random cases given by rotation
    for p in range(players):
        '''
        create the player land
        '''
        output += '  create_land {\n'
        output += constants #text that has to be written always
 
        if per_player > 1: #if there is more than one rotation
            first_rotation = True #flag to write "if" or "elseif"
            for i in range(per_player):
                if first_rotation:
                    output += '    if '
                    first_rotation = False
                else:
                    output += '    elseif '
                output += 'ROTATION_'+str(i+1)+'\n'
                coord_x = round(50 + radius*math.cos(angle*p+pp_angle*i))
                coord_y = round(50 + radius*math.sin(angle*p+pp_angle*i))
                output+='      land_position '+str(coord_x)+' '+str(coord_y)+'\n'
            output += '    endif\n\n'
        else: #per_player == 1
            coord_x = round(50 + radius*math.cos(angle*p))
            coord_y = round(50 + radius*math.sin(angle*p))
            output+='    land_position '+str(coord_x)+' '+str(coord_y)+'\n\n'

        '''
        assign the player land
        '''
        #for each variation of teams
        for v in variations:
            if v[1]==v[2] and v[1]+v[2]==players and v[1]>=3: #if there are two even teams
                output += '    if '
                output += str(number_of_teams(v))+'_TEAM_GAME\n'
    
                #write all the sizes of each team in the variation (with actual players)
                #this creates several nested "if" clauses to act as "and"
                for t in range(1,5): #skip team 0, unassigned
                    if v[t] == 0: #if the team is empty, the others will be too
                        break
                    output += '      if '+team_size(v,t)+'\n'

                #Distribute land for teams
                #list with as many elements as the number of players, sorted by team (odds, evens)
                colors =list(range(1,players+1,2))+list(range(2,players+1,2))
                first_players = True #flag to write "if" or "elseif"
                #for each player
                for l in range(players):
                    string = 'PLACE_'+str(l+1)#random sample given by the events list, one per player
                    if first_players:
                        output+='        if '
                        first_players = False
                    else:
                        output+='        elseif '
                    output += string+'\n'
                    #in each iteration of the l loop, the index (p+l)%players will increase by one
                    #the value p is placed so that each land is assigned to consecutive players
                    output += '          assign_to AT_COLOR '+str(colors[(p+l)%players])+' 0 0\n'
                output += '        endif\n'

                #endif for team sizes, this closes the nested "if" from above
                for t in range(1,5): #skip team 0, unassigned
                    if v[t] == 0: #if the team is empty, the others will be too
                        break
                    output += '      endif\n'
                #endif of variation v
                output += '    endif\n\n'

        output += '  }\n\n' #end of the create_land
    return output


global constants #text that has to be written in every create_land
'''
EDIT THIS with the constants of the particular map
'''
constants = '    terrain_type LAYER_A\n    land_percent 4\n    base_size 9\n'

file=open('fixed_positions.txt','w')

file.write('if ')
file.write('6'+'_PLAYER_GAME\n')
#generate the code for the circle of the given number of players and write it in the file
file.write(circle(37,6,1))

file.write('elseif ')
file.write('8'+'_PLAYER_GAME\n')
#generate the code for the circle of the given number of players and write it in the file
file.write(circle(38,8,1))

file.write('endif')
file.close()

Handicap mode​

The Handicap mode is a new feature introduced in Definitive Edition designed to balance teams where players have very different levels. It boosts some players in several ways:
  • Faster gather rate for villagers.
  • Higher carry capacity.
  • More starting resources.
  • Higher Villager HP.
  • Faster build speed.
  • More HP on all buildings.
  • More bonus damage for counter units.
  • Faster work rate for military buildings.
The handicaps levels are set via a percentage, 100% being no boost at all and 200% the maximum value.

Basic implementation of handicap​

An implementation of the handicap mode is very straightforward to come up with. We just need several technologies, each with an effect that boosts the aforementioned stats by the desired percentage. This technologies have positive research time so that they don’t activate on their own and no button to research it with, so that they are hidden from players.

Now, how can we have those technologies affect only specific players? If we were to program them in an scenario, that’s easy; we just need some triggers at the beginning of the game that research the handicap technologies for each player. With that in mind, I made sure that the handicap technologies are visible in the scenario editor, for people to access them easily.

Handicap mode in a random map​

Unfortunately, this idea of the technologies can not be ported to random maps. Random map scripts are very versatile in what you can modify from the game (names, stats, resources…) but all the changes affect all players, there is no way to control that effects only affect some of the players.

However, there is something that we can control for individual players: the units they receive. This neither is a very simple concept, it uses several concepts of random map scripting:
  • Players have to be created with the “direct_placement” command.
  • We create lands and assign them for specific player numbers with the “ASSING_TO_AT COLOR” command.
  • Additionally, we need to identify those lands with the command “land_id”.
  • When creating objects, we can control which players receive them by using the command “place_on_specific_land_id”.
This method gives us a land for each player and the possibility to place individual units in that land that will appear only for the players owning them. These lands with id will be different from the actual player lands where the Town Center and villagers are placed.

Since the only thing we can link to these lands are units, we need units that trigger the handicap technologies. This is easy to do, as buildings have an “Initiates Technology” field. I have created several buildings with no volume, no graphics, that die instantly and that trigger the desired handicap technology.

Let’s see an example. Say you want the player using blue to receive a 120% handicap. In the map script we need to create a land, assign it to color 1 and give it an id. Then we create an object HANDICAP_120 and place it on that land id. Since the land belongs to color 1, it will have that color. When the game begins, the handicap unit will trigger its technology and die instantly, giving the handicap effect.

Creating maps with specific handicap​

The final problem we need to solve is the huge casuistic that comes with colors, teams and handicap levels. Since creating every combination on a map would result on a huge mod with too many files to be possible to find the correct one, I created a program that creates the specific combination the players need. You can see the source code by downloading the Custom map creator mod.
 
Last edited by a moderator:
Copyright © 2009 - 2026 Member Plus Headquarters. All rights reserved.
This website was created by volunteers of the Member Plus Program and is not affiliated with Voobly.
Back
Top Bottom