Difference between Crafted and Generated Maps
-
Crafted Maps is the “easy” way to create maps for AoE IV without programming knowledge, but as I have experienced it can be a buggy and difficult challenge. Those maps does not, regenerate or much vary when you load into the match, neither it can have different map sizes, therefore you get the exact map as you created them in the Content Editor manually.
-
Generated Maps will generate each time a match starts, it leads to a more “random” map experience. Those maps vary and cannot be edited manually except by your code.
| Feature | Crafted Maps | Generated Maps |
|---|---|---|
| Map Generation | Manually editable, Fixed | Regenerating by code, (Random) |
| Map Size | Fixed size up to Large (16 Players) | Dynamic size, depending on the lobby settings |
| Time consuming | Medium | Large |
| Difficulty | Beginner* | Advanced* |
*Recommened by the Content Editor
Creating a Gererated Map project
- Open the Content Editor
- Under the Get Started, we select the Create A New Mod button
- From all the existing Mods we choose the Generated Map option
Template
From there we can choose between 2 templates:
- Basic: Includes basic instructions as an example, for beginners
- Advanced: Also has example instructions but also rivers and ocean examples, for advanced mep generation
We will use the basic template for our project.
Names
The Generated Map Name has to be written without! any whitespaces or tabs. Otherwise the output log will give us an error.
The Display Name is the name, which players can see inside the game.
After pressing next you should add your mod description, set your save location and the language of your mod. When your done you can press Fininsh.
Programming Order
As soon as your Content Editor finished loading you will be greeted by a big long code with many, many commentary! Thats good but, how do we actually use all those things they throwing at us? Let’s start step-by-step… Here it what the code looks like without the commentary and examples:
terrainLayoutResult = {}
gridHeight, gridWidth, gridSize = SetCoarseGrid()
if (gridHeight % 2 == 0) then
gridHeight = gridHeight - 1
end
if (gridWidth % 2 == 0) then
gridWidth = gridWidth - 1
end
gridSize = gridWidth
playerStarts = worldPlayerCount
n = tt_none
h = tt_hills
s = tt_mountains_small
m = tt_mountains
i = tt_impasse_mountains
b = tt_hills_low_rolling
mr = tt_hills_med_rolling
hr = tt_hills_high_rolling
low = tt_plateau_low
med = tt_plateau_med
high = tt_plateau_high
p = tt_plains
t = tt_impasse_trees_plains
v = tt_valley
--bounty squares are used to populate an area of the map with extra resources
bb = tt_bounty_berries_flatland
bg = tt_bounty_gold_plains
--the following are markers used to determine player and settlement spawn points
s = tt_player_start_hills
sp = tt_settlement_plains
sh = tt_settlement_hills
seb = tt_settlement_hills_high_rolling
--BASIC MAP SETUP-------------------------------------------------------------------------------------------------
terrainLayoutResult = SetUpGrid(gridSize, p, terrainLayoutResult)
baseGridSize = 13
mapMidPoint = math.ceil(gridSize / 2)
--set a few more useful values to use in creating specific types of map features
mapHalfSize = math.ceil(gridSize/2)
mapQuarterSize = math.ceil(gridSize/4)
mapEighthSize = math.ceil(gridSize/8)
--Here's a basic loop that will iterate through all squares in your map
for row = 1, gridSize do
for col = 1, gridSize do
--do stuff with the grid here
end
end
-- SETUP PLAYER STARTS-------------------------------------------------------------------------------------------------
teamsList, playersPerTeam = SetUpTeams()
teamMappingTable = CreateTeamMappingTable()
minPlayerDistance = 3.5
minTeamDistance = 8.5
edgeBuffer = 1
innerExclusion = 0.4
cornerThreshold = 2
playerStartTerrain = tt_player_start_classic_plains
impasseTypes = {}
table.insert(impasseTypes, tt_impasse_mountains)
table.insert(impasseTypes, tt_mountains)
table.insert(impasseTypes, tt_plateau_med)
table.insert(impasseTypes, tt_ocean)
table.insert(impasseTypes, tt_river)
impasseDistance = 2.5
topSelectionThreshold = 0.02
startBufferTerrain = tt_plains
startBufferRadius = 2
placeStartBuffer = true
terrainLayoutResult = PlacePlayerStartsRing(teamMappingTable, minTeamDistance, minPlayerDistance, edgeBuffer, innerExclusion, cornerThreshold, impasseTypes, impasseDistance, topSelectionThreshold, playerStartTerrain, startBufferTerrain, startBufferRadius, placeStartBuffer, terrainLayoutResult)
The start
Our map generates based on a grid layout, so at the beginning we:
- Get the Grid Height, Width and Size.
- Making sure the Grid Width and Height is always an odd number like (13).
- Setting the Size, same as the Width to gurantee a Square Map.
- (Optional) we get the count of players, our match starts with.
-- Init Layout
terrainLayoutResult = {}
-- Get Height, Width, Size
gridHeight, gridWidth, gridSize = SetCoarseGrid()
-- If they can be divided 2 without decimal places -> subtract 1 from the value
if (gridHeight % 2 == 0) then
gridHeight = gridHeight - 1
end
if (gridWidth % 2 == 0) then
gridWidth = gridWidth - 1
end
-- Make it equal Size for a square map
gridSize = gridWidth
-- (Optional) getting the player count
playerStarts = worldPlayerCount
Tile definitions (Terrain-Types)
After the start section you can find also some (optional) declarations of terrain information per tile:
n = tt_none
h = tt_hills
s = tt_mountains_small
m = tt_mountains
i = tt_impasse_mountains
b = tt_hills_low_rolling
mr = tt_hills_med_rolling
hr = tt_hills_high_rolling
low = tt_plateau_low
med = tt_plateau_med
high = tt_plateau_high
p = tt_plains
t = tt_impasse_trees_plains
v = tt_valley
--bounty squares are used to populate an area of the map with extra resources
bb = tt_bounty_berries_flatland
bg = tt_bounty_gold_plains
--the following are markers used to determine player and settlement spawn points
s = tt_player_start_hills
sp = tt_settlement_plains
sh = tt_settlement_hills
seb = tt_settlement_hills_high_rolling
These values like “tt_plains” describes a terrain information and we will use these for each single tile, to create our map! They also define their terrain heights, that means: tt_plateau_med is lower than tt_plateau_high inside the game. Defining them as “m = tt_mountains” makes the code looks smaller, but I prefer to write them down -> Makes it clear what it really is!
Here I list my personally favorites to use:
| Synonym | Meaning |
|---|---|
| Low | Low raised terrain |
| Med | Medium raised terrain |
| High | High raised terrain |
| Impasse | No passable by units, like mountains |
| Settlement | Sometimes called markets |
| Market | Sometimes called settlements |
Map setup: Tiles
This is where we will spend our most time(appart from the game). Here we tell each tile of our map, what kind of terrain it will use. The most simple way is to use For loops -> Iterates each column and row of our grid(iterating). That means instead of writing 20+ lines of code, we have it all central in 1 place(but its your choice). It also wont matter how big the grid size will be, since the loop always iterate through all tiles.
--BASIC MAP SETUP-------------------------------------------------------------------------------------------------
terrainLayoutResult = SetUpGrid(gridSize, p, terrainLayoutResult)
baseGridSize = 13
mapMidPoint = math.ceil(gridSize / 2)
--set a few more useful values to use in creating specific types of map features
mapHalfSize = math.ceil(gridSize/2)
mapQuarterSize = math.ceil(gridSize/4)
mapEighthSize = math.ceil(gridSize/8)
--Here's a basic loop that will iterate through all tiles in your map
for row = 1, gridSize do
for col = 1, gridSize do
--do stuff with the grid here
end
end
You can also add more loops and lines to set your map up, but it needs to be in between:
terrainLayoutResult = SetUpGrid(gridSize, p, terrainLayoutResult)
and:
teamsList, playersPerTeam = SetUpTeams()
Player start location
Finally we setting up the players start location, there are many values where we can change spawning locations for players. We will go through each of them:
-- SETUP PLAYER STARTS-------------------------------------------------------------------------------------------------
teamsList, playersPerTeam = SetUpTeams()
teamMappingTable = CreateTeamMappingTable()
minPlayerDistance = 3.5
minTeamDistance = 8.5
edgeBuffer = 1
innerExclusion = 0.4
cornerThreshold = 2
playerStartTerrain = tt_player_start_classic_plains
impasseTypes = {}
table.insert(impasseTypes, tt_impasse_mountains)
table.insert(impasseTypes, tt_mountains)
table.insert(impasseTypes, tt_plateau_med)
table.insert(impasseTypes, tt_ocean)
table.insert(impasseTypes, tt_river)
impasseDistance = 2.5
topSelectionThreshold = 0.02
startBufferTerrain = tt_plains
startBufferRadius = 2
placeStartBuffer = true
terrainLayoutResult = PlacePlayerStartsRing(teamMappingTable, minTeamDistance, minPlayerDistance, edgeBuffer, innerExclusion, cornerThreshold, impasseTypes, impasseDistance, topSelectionThreshold, playerStartTerrain, startBufferTerrain, startBufferRadius, placeStartBuffer, terrainLayoutResult)
In the first part we get players information from the lobby settings like: Team Lists and Players per Team. After this we create a detailed list of the teams for this lobby with players information.
teamsList, playersPerTeam = SetUpTeams()
teamMappingTable = CreateTeamMappingTable()
The minimum space between players:
minPlayerDistance = 3.5
Minimum space between teams:
minTeamDistance = 8.5
Tiles between the Edge of the Map and the player:
edgeBuffer = 1
(In Percentage!) Defining in what area the players can not spawn away from the center tile -> 0.4 means 40% from from center of the map -> this results in a 20% in left, right, up. down direction where no players are allowed to spawn.
innerExclusion = 0.4
Tiles between the Corner of the Map and the player:
cornerThreshold = 2
Tiles on where players spawn and information about their starting resources:
playerStartTerrain = tt_player_start_classic_plains
Impasse types are tiles where players cannot spawn on, additional to the previous settings. All tiles we insert prevent the generator from spawning a player on this terrain type. Like for example oceans or rivers:
impasseTypes = {}
table.insert(impasseTypes, tt_impasse_mountains)
table.insert(impasseTypes, tt_mountains)
table.insert(impasseTypes, tt_plateau_med)
table.insert(impasseTypes, tt_ocean)
table.insert(impasseTypes, tt_river)
The distance from the player to the defined Impasse Types is set here:
impasseDistance = 2.5
topSelectionThreshold controls how small the “best spawn area” should be. The system sorts all spawn locations by distance to the allies, then it only keeps a small percentage of the closest ones.
Example: A value of 0.02 means only the closest 2% of all spawn points are allowed. If the map has 500 possible spawn locations, 2% of 500 = 10. So only the 10 closest positions will be used for spawning.
topSelectionThreshold = 0.02
Start Buffer Terrain:
This buffer terrain, ensure that around the players start location is enough space or building… Its optional but recommended. The startBufferTerrain tells the type of terrain(Make sure it fits your created map tiles, otherwise you may encounter Evalutaion differencies). The startBufferRadius it the tile radius around the player spawn, on which the startBufferTerrain will be override your set up terrain. placeStartBuffer and here we can choose if we want to use the Buffer Terrain true or if we do not want false.
startBufferTerrain = tt_plains
startBufferRadius = 2
placeStartBuffer = true
Finally: How should the game spawn our players? In a ring? In a line? We will do that here:
-- Places players in a ring formation
terrainLayoutResult = PlacePlayerStartsRing(teamMappingTable, minTeamDistance, minPlayerDistance, edgeBuffer, innerExclusion, cornerThreshold, impasseTypes, impasseDistance, topSelectionThreshold, playerStartTerrain, startBufferTerrain, startBufferRadius, placeStartBuffer, terrainLayoutResult)
Next steps:
- Create a Basic Mountain pass
Related
- Browse the API reference
- Debug your Mod