4 Recipes
Slightly more advanced stuff for people without generic Racket language experience to show what is doable with aoe2-rms.
4.1 User Defined Functions
Let’s assume you want to place many lands in your map that are constrained border-wise via the top-border, right-border, bottom-border and left-border.
Such a script can look like this:
#lang aoe2-rms <LAND-GENERATION> (create-land (terrain-type 'WATER) (top-border 10) (right-border 20) (bottom-border 30) (left-border 40)) (create-land (terrain-type 'DIRT) (top-border 11) (right-border 22) (bottom-border 11) (left-border 22)) (create-land (terrain-type 'DIRT) (top-border 12) (right-border 24) (left-border 48)) (create-land (terrain-type 'DIRT) (top-border 13) (right-border 13) (bottom-border 13) (left-border 13))
Obviously this can get really repetitive and error-prone quite quickly. Luckily for us, we can abstract away arbitrary declarations into a function.
Let’s create a function which takes 4 parameters: top, right, bottom, left, and for each specified value creates the {side}-border attribute.
Such a function may look like this:
Beware that define has different semantics compared to Random Map Scripting %define. Former creates Racket variable, whereas latter creates Random Map Scripting condition label.
#lang aoe2-rms (define (inset-border top right bottom left) (when top (top-border top)) (when right (right-border right)) (when bottom (bottom-border bottom)) (when left (left-border left))) <LAND-GENERATION> (create-land (terrain-type 'WATER) (inset-border 10 20 30 40)) (create-land (terrain-type 'DIRT) (inset-border 11 22 11 22)) (create-land (terrain-type 'DIRT) (inset-border 12 24 #f 48)) (create-land (terrain-type 'DIRT) (inset-border 13 13 13 13))
This is actually equivalent to the first example. Notice that in third create-land we pass #f to the bottom argument, which would result in no bottom-border attribute being present.
Notice that no attributes or blocks actually return anything by looking the documentation. Or rather, they return void? which is the representation of no value. Usually the functions in racket return result of their last body, but the way attributes (and most of the other aoe2-rms constructs) work is that they register themselves into the place have have been called from. Such is enabled by Dynamic Variable Scope.
We can however go even further and make our function support multiple arities:
#lang aoe2-rms (define inset-border (begin (define (fn top right bottom left) (when top (top-border top)) (when right (right-border right)) (when bottom (bottom-border bottom)) (when left (left-border left))) (case-lambda [(x) (fn x x x x)] [(y x) (fn y x y x)] [(top right bottom left) (fn top right bottom left)]))) <LAND-GENERATION> (create-land (terrain-type 'WATER) (inset-border 10 20 30 40)) (create-land (terrain-type 'DIRT) (inset-border 11 22)) (create-land (terrain-type 'DIRT) (inset-border 12 24 #f 48)) (create-land (terrain-type 'DIRT) (inset-border 13))
This is also equivalent to earlier examples.
We can also abstract over the creation of the land itself. We can create a shorthand for a land thats always DIRT. To do that, we have to create a function that accepts another function as an argument.
#lang aoe2-rms (define inset-border (begin (define (fn top right bottom left) (when top (top-border top)) (when right (right-border right)) (when bottom (bottom-border bottom)) (when left (left-border left))) (case-lambda [(x) (fn x x x x)] [(y x) (fn y x y x)] [(top right bottom left) (fn top right bottom left)]))) (define (create-dirt-land body-decl) (create-land (terrain-type 'DIRT) (body-decl))) <LAND-GENERATION> (create-land (terrain-type 'WATER) (inset-border 10 20 30 40)) (create-dirt-land (lambda () (inset-border 11 22))) (create-dirt-land (lambda () (inset-border 12 24 #f 48))) (create-dirt-land (lambda () (inset-border 13)))
See more information in Definitions: define, define-syntax, ... and Procedure Expressions: lambda and case-lambda sections.
4.2 Modularizing Code
Its relatively common that multiple maps from the same author have similar aspects, behavior wise. One such example is notion of disabling trade in team games, as trade can be source of infinite resources, which may promote turtle-ish strategies. Indeed, lets take a look at some maps:
Notice how all of them have a variation of trade disabling section:
I have no clue why the ATTR_DISABLE is followed up by number literal again.
/* Disabling Trade Carts and Cogs for Multi-team Games */
if 2_TEAM_GAME
else
#const TRADE_CART_ENABLER 161
#const TRADE_COG_ENABLER 180
#const TRADE_CARAVAN_ENABLER 48
#const HINDU_CARAVANSERAI_ENABLER 518
#const PERSIAN_CARAVANSERAI_ENABLER 552
#const SILK_ROAD 499
effect_amount DISABLE_TECH TRADE_COG_ENABLER ATTR_DISABLE 180
effect_amount DISABLE_TECH TRADE_CART_ENABLER ATTR_DISABLE 161
effect_amount DISABLE_TECH TRADE_CARAVAN_ENABLER ATTR_DISABLE 48
effect_amount DISABLE_TECH HINDU_CARAVANSERAI_ENABLER ATTR_DISABLE 518
effect_amount DISABLE_TECH PERSIAN_CARAVANSERAI_ENABLER ATTR_DISABLE 552
effect_amount DISABLE_TECH SILK_ROAD ATTR_DISABLE 499
endif
Copy pasting this magical invocation is quite annoying a only clutters the contents of the file the author actually cares about.
Lets make it more seamless to re-use a chunk of code across many files.
First abstract above snippet (or most of it) into a function, just like was shown in User Defined Functions:
"map.rkt"
#lang aoe2-rms (define (disable-trade) (define (disable-tech tech-name) (effect-amount 'DISABLE_TECH tech-name 'ATTR_DISABLE tech-name)) (%const 'DISABLE_TRADE_TRADE_CART_ENABLER 161) (%const 'DISABLE_TRADE_TRADE_COG_ENABLER 180) (%const 'DISABLE_TRADE_TRADE_CARAVAN_ENABLER 48) (%const 'DISABLE_TRADE_HINDU_CARAVANSERAI_ENABLER 518) (%const 'DISABLE_TRADE_PERSIAN_CARAVANSERAI_ENABLER 552) (%const 'DISABLE_TRADE_SILK_ROAD 449) (disable-tech 'DISABLE_TRADE_TRADE_CART_ENABLER) (disable-tech 'DISABLE_TRADE_TRADE_COG_ENABLER) (disable-tech 'DISABLE_TRADE_TRADE_CARAVAN_ENABLER) (disable-tech 'DISABLE_TRADE_HINDU_CARAVANSERAI_ENABLER) (disable-tech 'PERSIAN_CARAVANSERAI_ENABLER) (disable-tech 'SILK_ROAD)) (%unless '2_TEAM_GAME (disable-trade))
This is nicer, however still, we want to make this callable from many maps. To archieve that, we can put the function into a separate file:
"trade.rkt"
#lang aoe2-rms (define (disable-trade) (define (disable-tech tech-name) (effect-amount 'DISABLE_TECH tech-name 'ATTR_DISABLE tech-name)) (%const 'DISABLE_TRADE_TRADE_CART_ENABLER 161) (%const 'DISABLE_TRADE_TRADE_COG_ENABLER 180) (%const 'DISABLE_TRADE_TRADE_CARAVAN_ENABLER 48) (%const 'DISABLE_TRADE_HINDU_CARAVANSERAI_ENABLER 518) (%const 'DISABLE_TRADE_PERSIAN_CARAVANSERAI_ENABLER 552) (%const 'DISABLE_TRADE_SILK_ROAD 449) (disable-tech 'DISABLE_TRADE_TRADE_CART_ENABLER) (disable-tech 'DISABLE_TRADE_TRADE_COG_ENABLER) (disable-tech 'DISABLE_TRADE_TRADE_CARAVAN_ENABLER) (disable-tech 'DISABLE_TRADE_HINDU_CARAVANSERAI_ENABLER) (disable-tech 'PERSIAN_CARAVANSERAI_ENABLER) (disable-tech 'SILK_ROAD)) (provide (all-defined-out))
This file can then be required from other files, bringing the disable-trade function into scope:
"map.rkt"
#lang aoe2-rms (require "trade.rkt") (%unless '2_TEAM_GAME (disable-trade))
Read Modules for more information.
4.2.1 Gotchas
Anything you want to reuse from other files HAS to be wrapped into functions that are called from the "main" file, otherwise it does not get included into the generated script.
Following would NOT work:
"setup.rkt"
#lang aoe2-rms (require "trade.rkt") (%unless '2_TEAM_GAME (disable-trade))
And then including such into map file:
"map.rkt"
#lang aoe2-rms (require "setup.rkt")
Transpiling the "map.rkt" file would result into empty result.