Introduction
This is a VERY quick guide put together to quickly learn to create triggers in random maps to allow you create random map scenario type games as I feel they are lacking. I will start from a basic script which we will modify to show off certain features. This is not to teach you how to script land - plenty of guides out there for that but how instead to script triggers.
This guide assumes basic programming knowledge - I am not going over age of mythology's syntax but there is hopefully enough in the examples below to "learn by example".
Set Up
I recommend downloading Notepad++ to edit your random map script. When open select the C# language which give syntax highlighting that is almost perfect matching the language used here making it easier to read. You also will want a copy of your triggers file open which is likely to be called typetest.xml or some similar name.
Starting Script
void code(string xs="") { //inject trigger effect
rmAddTriggerEffect("SetIdleProcessing");
rmSetTriggerEffectParam("IdleProc",");"+xs+"//");
}
void con(string xs2="") { //inject trigger condition
rmAddTriggerCondition("Player Defeated");
rmSetTriggerConditionParam("PlayerID","0)|| ("+xs2+"));//");
}
void main(void) {
rmSetStatusText("", 0.01); //set load bar to start - fraction determines how much filled
int mapSize = 100; //define map size
if(cMapSize == 1){
mapSize = 150; //larger map on large
}
rmSetMapSize(mapSize, mapSize); //set map size
rmSetSeaLevel(0); //water level if there is sea
rmSetSeaType("greek river"); //default sea type
rmTerrainInitialize("grassB",1); //initial land height and type
rmSetGaiaCiv(cCivZeus); //gaia civ
rmSetLightingSet("Default"); //lighting
int c = cNumberNonGaiaPlayers; //number of players
int id = 0; //id - used for RMS object referencing
id = rmCreateArea("mainArea"); //main area
rmSetAreaTerrainType(id,"GrassB"); //terrian for area
rmSetAreaBaseHeight(id,0); //height of area
rmSetAreaCoherence(id,1.0); //how "round" the area is
rmSetAreaLocation(id,0.5,0.5); //location (as a fraction)
rmSetAreaSize(id,1,1); //size (as a fraction) min size/max size
rmBuildArea(id); //build the area
rmPlacePlayersSquare(0.3,0.0,0.0); //place in square
for(i = 1; <= c) {
id = rmCreateArea("playerArea"+i); //player area
rmSetAreaCoherence(id,1.0); //how "round" the area is
rmSetAreaLocPlayer(id,i); //location is player location
rmSetPlayerArea(i,id); //this is where player starts
rmSetAreaTerrainType(id,"CityTileA"); //terrain to use
rmSetAreaBaseHeight(id,5); //height of area
rmSetAreaHeightBlend(id,2); //how smooth area connects to surroundings
rmSetAreaSize(id,0.1,0.1); //size of area as fraction
rmBuildArea(id); //build the area
}
for(i = 1; <= c) { //do for all players index i
id = rmCreateObjectDef("playerUnit"+i); //unit definition
rmAddObjectDefItem(id,"Settlement Level 1",1,0.0); //what to place, how many and radius
rmPlaceObjectDefAtAreaLoc(id,i,rmAreaID("playerArea"+i),1); //place units: player, area and count
}
rmCreateTrigger("t1"); //declare trigger
rmSwitchToTrigger(rmTriggerID("t1")); //set the trigger we are changing
con("(trTime()-cActivationTime) >= 0"); //timer condition of 0 seconds
code("trSetPlayerWon(1);"); //set player 1 won
rmSetTriggerPriority(4); //high priority
rmSetTriggerActive(true); //trigger will currently run
rmSetTriggerRunImmediately(true); //trigger should immediately active
rmSetTriggerLoop(false); //trigger should loop
rmSetStatusText("",1.00); //set loading bar to end to say finished
}
This script has a simple trigger that sets player 1 to win. obviously this is pretty dull and we want something a little more exciting!
Adding Effects
Trigger effects are added by adding commands in a code("CODE HERE") method. I will briefly go through a worked example of adding a trigger effect to grant a player a god power:
(1)Search trigger file:
Search for the text: Grant God Power. You will find an entry like this:
(2)Extract the command:
<Effect name="$$22450$$Grant God Power">
<Param name="PlayerID" dispName="$$22301$$Player" varType="player"> 0< /Param>
<Param name="PowerName" dispName="$$22451$$Power" varType="godpower"> default< /Param>
<Param name="Count" dispName="$$22452$$Uses" varType="long"> 1< /Param>
<Command> trTechGodPower(%PlayerID%, "%PowerName%", %Count%);< /Command>
</Effect>
The only part you need is the part between the Command tags. If there are multiple lines you need to repeat the below for all of them:
(3)Substitue ":
trTechGodPower(%PlayerID%, "%PowerName%", %Count%);
-Replace every occurrence of " with \" This is because we are going to put the command into the code(""); method which has ". Putting \" tells the game we are not done with the code tag. We now have:
(4)Fill in parameters:
trTechGodPower(%PlayerID%, \"%PowerName%\", %Count%);
Here you fill in the details replacing
%field%
with what you want. In this particular case we want to grant player 1 3 tornados:(5)Add it to the code command:
trTechGodPower(1, \"tornado\", 3);
Put your command into the code tag to give the final command:
code("trTechGodPower(1, \"tornado\", 3);");
Try replacing setting a player won to give 5 meteors on the script. You should end up with something like:
rmCreateTrigger("t1"); //declare trigger
rmSwitchToTrigger(rmTriggerID("t1")); //set the trigger we are changing
con("(trTime()-cActivationTime) >= 0"); //timer condition of 0 seconds
code("trTechGodPower(1, \"meteor\", 5);"); //grant meteors
rmSetTriggerPriority(4); //high priority
rmSetTriggerActive(true); //trigger will currently run
rmSetTriggerRunImmediately(true); //trigger should immediately active
rmSetTriggerLoop(false); //trigger should loop
Adding Conditions
Adding conditions is the same except for the following changes:
-Use the con(""); command.
-Use code between Expression tags
Some conditions do not translate across nicely as they require commands beforehand which my system currently does not support such as unit based conditions. In these situations use instead "conditionals" in this following format:
code("pre condition command 1");
code("pre condition command 2");
code("if(your condition){");
code("effect 1");
code("effect 2");
code("}");
For example the code below sends a chat every 3 seconds providing the town center is selected:
rmCreateTrigger("t1"); //declare trigger
rmSwitchToTrigger(rmTriggerID("t1")); //set the trigger we are changing
con("(trTime()-cActivationTime) >= 3"); //timer condition of 3 seconds
code("trUnitSelectClear();"); //clear trigger selection
code("trUnitSelect(\"0\");"); //select unit 0 - this is the first unit placed on the map
code("if(trUnitIsSelected()){"); //condition on it being selected
code("trChatSend(1, \"Player 1 town center selected.\");"); //send chat
code("}");
rmSetTriggerPriority(4); //high priority
rmSetTriggerActive(true); //trigger will currently run
rmSetTriggerRunImmediately(true); //trigger should immediately active
rmSetTriggerLoop(true); //trigger should loop
Substitution
You can concatenate text with variables to fill in in details to make nice short cuts to avoid repeating code. If you wanted to make the selection work for all players you can do something like:
rmCreateTrigger("t1"); //declare trigger
rmSwitchToTrigger(rmTriggerID("t1")); //set the trigger we are changing
con("(trTime()-cActivationTime) >= 3"); //timer condition of 3 seconds
code("for(i = 1; <"+c+"){"); //loop over all players with i being player
code("trUnitSelectClear();"); //clear trigger selection
code("trUnitSelect(\"\"+(i-1));"); //select unit i-1. Remember that 0 is first unit so player 1 unit is 0, player 2 is 1, etc.
code("if(trUnitIsSelected()){"); //condition on it being selected
code("trChatSend(i, \"Player \"+i+\" town center selected.\");"); //sent chat from player i and substitute the player into the message.
code("}");
code("}");
rmSetTriggerPriority(4); //high priority
rmSetTriggerActive(true); //trigger will currently run
rmSetTriggerRunImmediately(true); //trigger should immediately active
rmSetTriggerLoop(true); //trigger should loop
That covers the basics for inserting triggers. You can insert more triggers by following the same structure giving the trigger a different name.
Misc
Custom Triggers
Yes you can use custom triggers even if the other players do not have them as we are making direct command calls. An example below changes the sun color to red.
rmCreateTrigger("t1"); //declare trigger
rmSwitchToTrigger(rmTriggerID("t1")); //set the trigger we are changing
con("(trTime()-cActivationTime) >= 0"); //timer condition of 0 seconds
code("sunColor(255, 0, 0);"); //set the sun color red
rmSetTriggerPriority(4); //high priority
rmSetTriggerActive(true); //trigger will currently run
rmSetTriggerRunImmediately(true); //trigger should immediately active
rmSetTriggerLoop(false); //trigger should loop
Some awkward triggers
Take a look at modify protounit:
<Effect name="$$23942$$Modify Protounit">
<Param name="ProtoUnit" dispName="$$22449$$ProtoUnit" varType="protounit"> Villager< /Param>
<Param name="PlayerID" dispName="$$22301$$Player" VarType="player"> 0< /Param>
<Param name="Field" dispName="$$23943$$Field" varType="pufield"> 0< /Param>
<Param name="Delta" dispName="$$23944$$Delta" varType="float"> 1.0< /Param>
<Command> trModifyProtounit("%ProtoUnit%", %PlayerID%, %Field%, Ūlta%);< /Command>
</Effect>
Notice that the field is an integer? This is because you use one. To work out what it should be go to the editor and insert a Modify Protounit trigger. Now the integers match up with the fields in the following order:
0 Hitpoints
1 Speed
2 LOS
etc.
Sorry I know it is awkward haha. This code gives greek villagers of player 1 10 speed.
con("trModifyProtounit("villager greek", 1, 1, 10.0);");
Set tech status has a similar problem being the tech uses a tech Id with the status and tech being an integer. There are tech ID lists around for download but not the indexing is different between AOM:EE and AOMX:
code("trTechSetStatus(1, 2, 4);"); //set player 1 to age 3
Sounds don't quite work as expected:
<Effect name="$$22383$$Sound Filename">
<Param name="Sound" dispName="$$22384$$Sound" varType="sound"> default< /Param>
<Param name="EventID" dispName="$$22362$$Trigger" varType="event"> -1< /Param>
<Param name="Subtitle" dispName="$$22385$$Subtitle" varType="stringid"> < /Param>
<Param name="Portrait" dispName="$$22386$$Portrait" varType="string"> < /Param>
<Command> trSoundPlayFN("%Sound%", %EventID%, "%Subtitle%","%Portrait%");< /Command>
</Effect>
Actually comes out for the gong sound to be:
code("trSoundPlayFN(\"cinematics\15_in\gong.wav\",\"1\",-1,\"\",\"\");");
This is due to "%Sound%" becoming \"cinematics\15_in\gong.wav\",\"1\" where there is an extra ,\"1\" added due to the way file names work.
Fire event. Something sneaky happens in the background (It is solvable but is complicated) for those I advise using xsEnableRule("trigger name") instead. Note you need to put an extra _ at the front:
code("xsEnableRule(\"_t1\");"); //enable trigger t1
Basic Troubleshooting
Trip Ups
-Careful with quotes especially with substitution. The syntax highlighting can help a bit. If you are unsure have a look at my substitution example or one of my scripts.
-There is a line limit which the triggers/script will refuse to run if your trigger line is too long. If you get that try and split it up into multiple lines.
Script failed to load
There is a coding mistake in your script. Use /* and */ to comment out sections you think may be causing it and try loading the script again. If your script loads, you may still find the triggers may not run - see below:
Triggers won't run
This means you have an error in the code inside one of your code("") or con("") tags but the rest of your script is fine. Comment out triggers with /* and */ till your script runs.
Random Map Script won't appear
Use the below code in your script .xml file. You can change the image path to give a custom icon later.:
<?xml version = "1.0" encoding = "UTF-8"?>
<mapinfo details = "" imagepath = "NONE" displayNameID="" cannotReplace=""/>
[This message has been edited by nottud (edited 04-23-2015 @ 03:42 PM).]