Code:
[+] Introduced advanced ERM precompilation mode, called ERM 2.0 and activated via "ZVSE2" first line signature.
Main features:
- Named local variables to write human readable code instead of cryptic one.
Example: !!HE-1:C0/0/?(monType:y)/?(monNum:y) instead of !!HE-1:C0/0/?y23/?y24;
- Global named constants instead of magic numbers to write self-explaining code:
Example: !?FU(OnKeyPressed)&x1=(KEY_1) instead of !?FU(OnKeyPressed)&x1=49; what is 49???
- Strict distinguishing naming for functions, local variables and constants. No way to mix up.
Example: thisIsVariable, THIS_IS_CONSTANT, ThisIsFunction and era_ThisIsFunctionAgain.
==== Named local variables ====
Each ERM trigger (!?XX before next !?XX) can now declare and use own named local variables, allocated from
x1..x16, y1..y100, z-1..z-10, e1..e100, v2..v9 sets.
Named variables are replaced with regular variables during scripts compilation and do not influence the performance at all.
Example: (day) may be compiled to y5;
--- Naming ---
Names of variables must be in so called "camelCase" and contain only [a-zA-Z0-9] characters. They must be wrapped
in parantheses the same way, as function names are wrapped.
Example of valid variables: (hero), (monNum), (isAutocombatMode), (specialObject7).
--- Declaration ---
Variables must be declared on the first usage: i.e their type (x, y, z, e, v) and array length (for arrays) must be specified.
If you write '[some number]' after variable name, variable will become an array (sequence of variables) with specified length.
If you write ':e' after variable name or array length, it will mean, that variable type is "e" (floating point numbers).
Examples:
!!HE-1:N?(hero:y); give some y-variable name "hero" and write current hero ID to it
!#VA(arts[4]:y); allocate 4 y-variables with sequential indexes and name the array "arts"
!#VA instruction is pseudo-command, that is dropped from final compiled code and that can be used to hold variables declarations.
Example:
!?FU(acm_Sum);
; The function calculates sum of two numbers
!#VA(first:x) (second:x) (result:x); [bind "first" to x1, "second" to x2, "result" to x3]
!!VR(result):S(first) +(second); [calculate result]
--- Usage ---
It's allowed to specify the same type and array length for variables in every variable usage place, but it's not necessary.
After you declared variable, there is no more any need to write its type/length.
Example:
!!HE-1:N?(hero:y);
!!HE(hero):K1; kill hero with ID in (hero) variable.
will be compiled to something like that:
!!HE-1:N?y5;
!!HEy5:K1;
--- Arrays ---
If you need not a single variable, but sequence of variables, for instance to hold [x, y, l] coordinates of objects,
then you need an array. Specify array length in square brackets right after variable name during declaration.
!#VA(coords[3]:y); allocate 3 y-variables named 'coords'
Items or elements of arrays are zero-indexed and can be accessed by direct index.
For 3-items array possible indexes are 0, 1, 2.
Example:
!!CM:P?(coords[0])/?(coords[1])/?(coords[2]);
will be compiled to something like that:
!!CM:P?y50/?y51/?y52;
If you don't specify array index, the first array element will be used. It means that
(test) and (test[0]) have the same sense. Regular variables are considered arrays of length 1.
--- Negative array indexes ---
Negative array index means n-th item from the end. -1 will point to the last item, -2 to the one before the last one and so on.
Example:
; allocate array of 10 y-variables and assign the last one value 2000
!#VA(array[10]:y);
!!VR(array[-1]):S2000;
will be compiled to something like that:
; allocate y1..y10
!!VRy10:S2000;
--- Releasing local variables ---
If you don't need large variable array anymore, but want to declare another big array, then free the previous one.
Syntax: !#VA(-variableName); will forget about specified variableName, allowing to reuse indexes, allocated for that variable.
Example:
!#VA(myArts[100]:y); allocate y1..y100 to hold artifact IDs
...; use them
!#VA(-myArts); release 'myArts' name and y1..y100 indexes.
!#VA(coords[3]:y); allocate y1..y3 as 'coords' variable
--- Getting variable address (real index) ---
It's often necessary to get real index of variable or even array element. When you want to output "2" instead of
v2, use address operator '@'.
Example:
!#VA(coords[3]:v);
!!CM:P?(coords[0])/?(coords[1])/?(coords[2]); [write click coordinates into "coords" array]
!!OB(@coords):T?(objType:y); [get object type under cursor]
will be compiled to something like that:
!!CM:P?v2/?v3/?v4;
!!OB2:T?y1;
Address operator "@" compiles to real (final) variable index. For instance, for array "test[10]:y" mapped to y50..y59
(@test[1]) will compile to "51".
Example of declaring array of 10 y-variables and initializing all of them with -1.
!#VA(monTypes[10]:y);
!!re i/(@monTypes)/(@monTypes[-1]):; repeat from i = first array index to i = last array index
!!VRyi:S-1; set -1 for current array item
!!en:;
In other programming languages variables, holding other variables addresses/indexes are usually called "pointers"
and abbreviated as "ptr" or "Ptr". We will rewrite the previous example with named variable in place of quick "i" var
just for learning purposes.
!!re (monTypePtr:y)/(@monTypes)/(@monTypes[-1]):; repeat from (monTypePtr) = first array index to (monTypePtr) = last array index
!!VRy(monTypePtr):S-1; set -1 for current array item
!!en:;
--- Naming function arguments ---
Indexes for named local variables are allocated starting from the smallest possible value.
It means, that we can name even function arguments if we declare them in the same order, as arguments will be passed.
Example:
!?FU(bh_GetHeroSecSkill);
!#VA(hero:x) (skill:x) (result:x); now hero = x1, skill = x2, result = x3
!!HE(hero):S(skill)/?(result);
!?FU(...); some event
!!FU(bh_GetHeroSecSkill)/155/27/?(xeronFirstAidLevel:y); so what's the level of First Aid skill Xeron has? )
--- Redeclaration ---
If you need to declare variable in both branches of if-then block, specify type/length in both of them.
!!if&(day)>90:;
!!VR(price:y):S(day) *100;
...
!!el:;
!!VR(price:y):S(day) *(difficultyLevel) +300;
...
!!en:;
--- Reusing same name in other trigger ---
Variable names are local to nearest trigger only. New trigger starts with no declared variables.
Example:
!?FU(OnHeroScreenMouseClick);
!!CM:F?(flags:y); flags = y1
!?FU(OnHeroScreenMouseClick);
!#VA(flags[23]:e); flags is array, binded to e1..e23
--- Interpolation ---
To substitute local variables in string literals use $(varName) syntax. Example:
!!VR(price:y):S600;
!!VR(heroName:z):S^Robin Hood^;
!!IF:Q2/^Would you like to hire $(heroName) for $(price) gold only?^;
==== Named global constants ====
Constant is named value, that is defined once and never changes. Like 13 (Archangel monster type).
Constant can be used anywere, where numbers can be used. Era currently supports only integer numeric
constants, written in all capitals: (MON_ARCHANGEL), (OBJ_MINE), (PLAYER_RED).
Allowed characters are: [A-Z0-9_].
To define a constant use the following instruction !#DC(CONSTANT_NAME) = 777; where 777 is arbitrary number.
Examples:
!#DC(PLAYER_BLUE) = 1;
!#DC(SKILL_FIRST_AID) = 27;
To use a constant simply write its name in parentheses:
!!OW:R(CURRENT_PLAYER)/(RES_GOLD)/d1000; give 1000 gold to current player
will be compiled to
!!OW:R-1/6/d1000; give 1000 gold to current player
--- Naming ---
Scripts writers must use unique prefix before constant names to prevent names collisions. Any constants without prefix
may be added to ERA in the future and break your script.
Example:
; for mod Battle Heroes let's use prefix "BH_"
!#DC(BH_ART_RING_OF_POWER) = 160;
!#DC(BH_CLASS_WARRIOR) = 1;
!#DC(BH_CLASS_MAGE) = 2;
!#DC(BH_CLASS_RANGER) = 3;
--- Globality ---
Constants are global. It means, that one script can use constants of another script. To ensure, that your constants
are always loaded before other scripts, place them in the script with high priority (ex. "1000 - phoenix consts.erm").
--- Standard constants ---
ERA provides file "1000 - era consts.erm" with many predefined constants, covering most values, mentioned in ERM help.
Look through it before defining your own constant for secondary skill, monster or player color.
==== Named functions ====
Function names must consist of [A-Za-z0-9_] characters only, start with letter and contain at least
single lower case letter (a-z).
There are two allowed naming methods:
1) Start function with capital letter. (CalcHeroArmyPower), (ShowUpgradeDialog).
ERA reserves right to declare prefixless functions, starting with "On" for events. This method is not
recommended, due to possible names collisions in different mods. Two mods may declare functions
will the same names and thus produce hard to debug bugs.
2) Start function with any case unique prefix with '_' character. Prefix is usually mod abbreviation.
For instance, for "Dwellings Extended" mod the following functions are used:
!?FU(dex_SetDwellingSlotByTownType);
!?FU(dex_DwellingPopulation);
...
--- Generating new events ---
You can call function, even if it has no handlers. For instance, in Upgrade All Creatures mod you
want to allow other scripts to be able to notify, what monster can be upgraded to in particular town.
Just call not existing function like !!FU(auc_OnDetermineMonsterUpgrade):P... in your script with all
necessary parameters and other scripts will be able to write new event handlers like:
!?FU(auc_OnDetermineMonsterUpgrade);
...
--- Passing function as handlers or callbacks ---
You can use function as ordinary constant, compiled to number. You can assign it to variable or pass to
another function.
!!VR(spellHandler:y):S(newmagic_DesintegrationSpellHandler);
!!FU(spellHandler):P;
will compile in something like that
!!VRy20:S95003;
!!FUy20:P;