Writing an AI script for Age of Empires III (Part III)

Here you can post about your scenario's, mods, custom maps and YouTube channels!
Madagascar AlistairJah
Crossbow
Posts: 14
Joined: May 6, 2018
ESO: Aliwest
Location: Madagascar

Writing an AI script for Age of Empires III (Part III)

  • Quote

Post by AlistairJah »

Go to Part I
Go to Part II
Download the reference

Yo people, it's been a year since the last time I was actively working on this tutorial. I hope there still are learners around :D

INTRODUCTION
In this part, we're getting back to XS and explain certain super-useful features of this language. This couldn't be discussed in Part I because it wouldn't make sense at that point. This couldn't be discussed in Part II either, because it's more of a language feature (the same as functions and rules) than an AI-related feature (like training units or researching techs). This part is going to be rather short, but far more complicated :D



EVENT HANDLERS

Event handlers are functions that are called automatically as soon as certain events happen. For example, you can create a function iGotShipment and set it as an event handler for when the AI gets one point of shipment:

Code: Select all


void iGotShipment( int param = -1 )
{
for ( player = 1 ; < cNumberPlayers )
{
if ( kbIsPlayerAlly( player ) == true )
aiChat(player, "Guys, I got a shipment. I'm gonna pick the card TEAM 4 sheeps.");
}
// The rest of the handler goes here
}

void main( void )
{
kbAreaCalculate();
aiSetHandler("iGotShipment", cXSShipResourceGranted);
}
The integer parameter is mandatory. I'll explain it later. For now, just test the example above. When you test it, use the cheat code nova & orion to get a shipment asap if you don't want to wait for the gauge to fill. See? We didn't have to call the function (i.e. we didn't have to write iGotShipment(); anywhere): it was called automatically as soon as a shipment point was granted!

Note that every granted point causes the handler to be called so if you get multiple shipments at the same time, the handler is called multiple times too.


I. TYPES OF EVENTS
There are two types of events that can cause an event handler to be called:

1. Game events
• cXSAgeHandler - calls an event handler when the context player reaches another age
• cXSResignHandler - calls an event handler when the human player responds to a resign request
• cXSBuildHandler - calls an event handler when the context player finishes the construction of a building
• cXSHomeCityTransportArriveHandler - calls an event handler when a selected homecity card arrives
• cXSHomeCityTransportReturnHandler - calls an event handler when a selected homecity card returns to the homecity (in case all drop points get destroyed or an enemy researched the Blockade tech)
• cXSHomeCityLevelUpHandler - calls an event handler when a homecity levels up
• cXSTreatyBrokenHandler - DOES NOT WORK, but is supposed to call an event handler when the treaty is broken
• cXSShipResourceGranted - calls an event handler for each granted shipment point
• cXSAutoCreatePlanHandler - calls an event handler each time the context AI player generates a plan (like the cPlanGather generated by aiSetResourceBreakdown in Part II)
• cXSNuggetHandler - calls an event handler each time a player claims a treasure. For hostage treasures (like the settler on a tree surrounded by wolves), this event is triggered before the hostage is fully free.
• cXSPlayerAgeHandler - calls an event handler each time a player reaches another age
• cXSScoreOppHandler - calls an event handler each time an opportunity needs to be scored. We'll talk about oppportunities later, when we talk about Goals and high level military management
• cXSMissionStartHandler - calls an event handler each time an opportunity stars a mission
• cXSMissionEndHandler - calls an event handler each time a started mission ends
• cXSGameOverHandler - calls an event handler when the game is over
• cXSMonopolyStartHandler - calls an event handler when a player starts the monopoly victory timer
• cXSMonopolyEndHandler - calls an event handler when a monopoly timer prematurely ends
• cXSKOTHVictoryStartHandler - calls an event handler when a player starts the KOTH victory timer
• cXSKOTHVictoryEndHandler - calls an event handler when a KOTH timer prematurely ends

2. Plan events
• cPlanEventDone - calls an event handler when the plan's execution is a success
• cPlanEventFailed - calls an event handler when the plan fails
• cPlanEventPoll - untested
• cPlanEventIdle - untested
• cPlanEventStateChange - calls an event handler when the plan's state changes

3. Communication events
These events call an event handler each time the context player receives a request from itself (opportunity), another player, or mother nature (scenario trigger): attack, defend, tribute, strategy, unit choice, cancel previous requests, ...

II. SETTING A FUNCTION AS AN EVENT HANDLER

1. Set as game event handler
• firstly, create the function with an integer parameter:
• secondly, call the function aiSetHandler to set that function as an event handler

Code: Select all


// the function to be used as an event handler:
void myFirstHandler( int theMandatoryIntParameter = -1 )
{

}

// set myFirstHandler as an event handler:
void main( void )
{
kbAreaCalculate();
// example: set it as an ageup handler:
aiSetHandler("myFirstHandler", cXSAgeHandler);
// now, each time the context player reaches another age, myFirstHandler will be called
}
2. Set as plan event handler
• firstly, create the function with an integer parameter:
• secondly, call the function aiPlanSetEventHandler to set that function as the plan's event handler
• I recommend to do it before activating the plan

Code: Select all


// the function to be used as an event handler:
void myHandler( int param = -1 )
{

}

// set myHandler as an event handler:
void main( void )
{
kbAreaCalculate();
// example:
// assuming that there's a market and enough resources to research the Hunting Dogs tech
int researchPlan = aiPlanCreate( "Research HuntingDogs", cPlanResearch );
aiPlanSetVariableInt( researchPlan, cResearchPlanTechID, 0, cTechHuntingDogs );
aiPlanSetEscrowID( researchPlan, cEconomyEscrowID );
aiPlanSetEventHandler( researchPlan, cPlanEventStateChange, "myHandler");
aiPlanSetActive( researchPlan, true );
// now, each time the state of this plan changes, myHandler will be called
// we'll probably talk about plan states in the future
}
3. Set as communication event handler
• firstly, create the function with an integer parameter:
• secondly, call the function aiCommsSetEventHandler to set that function as the communication event handler

Code: Select all


// the function to be used as an event handler:
void communicationHandler( int param = -1 )
{

}

// set communicationHandler as a communication event handler:
void main( void )
{
kbAreaCalculate();
aiCommsSetEventHandler("communicationHandler");
}
III. EVENT HANDLER PARAMETERS

You might be wondering why is the integer parameter mandatory. It's because each event passes an information into the function. That information is directly related to the event: cXSAgeHandler indicates which age did the context player reach when the ageup event happened, cXSBuildHandler indicates which type of building has be built when the build event happened, cXSKOTHVictoryStartHandler indicates which team got the King's Hill, etc. However, not all events pass an information, but even in that case, the integer parameter is still mandatory.

1. Game events
• cXSAgeHandler - the age reached by the context player. cAge1=0, cAge2=1, cAge3=2, cAge4=3, cAge5=4. Certain mods have custom age names and IDs.
• cXSResignHandler - the human player's response. Yes=1, No=0.
• cXSBuildHandler - the protounit ID of the building that got built.
• cXSHomeCityTransportArriveHandler - untested
• cXSHomeCityTransportReturnHandler - untested
• cXSHomeCityLevelUpHandler - untested
• cXSTreatyBrokenHandler - untested
• cXSShipResourceGranted - untested
• cXSAutoCreatePlanHandler - the generated plan's ID
• cXSNuggetHandler - the ID of the player who claimed the treasure
• cXSPlayerAgeHandler - the ID of the player who reached another age
• cXSScoreOppHandler - the ID of the opportunity that needs to be scored
• cXSMissionStartHandler - the ID of the started mission
• cXSMissionEndHandler - the ID of the mission that ended
• cXSGameOverHandler - no information passed
• cXSMonopolyStartHandler - the ID of the team that possesses the Monopoly
• cXSMonopolyEndHandler - the ID of the team that lost the Monopoly
• cXSKOTHVictoryStartHandler - the ID of the team that possesses the King's Hill
• cXSKOTHVictoryEndHandler - the ID of the team that lost the King's Hill

2. Plan events
The plan always passes its own ID as the parameter to the event handler.

3. Communication events
The event always passes its own ID as the parameter to the event handler.

IV. NOTE(S)
I am note entirely sure yet, but I might show some examples in the comments later. If I don't, you can always find an example for each of the three types of event in aiMain.xs: for game events, search for aiSetHandler. For plan events, search for aiPlanSetEventHandler and for communication events, search for aiCommsSetEventHandler.



USER-DEFINED PLAN VARIABLES

Sometimes, for various reasons, you might want to add some extra information to different plans. However, doing that with classic variables and arrays might make things super complicated. Fortunately, it is possible to define your own plan variables:

Code: Select all


// Imagine that we're coding the AI of a custom scenario that is supposed to handle
// north-side defenses differently from the south-side ones. For that, we can add a
// flag for each plan to indicate that it is a north-side plan or a south-side plan:

const int cPlanSide = 0;
const int cNorthSide = 0;
const int cSouthSide = 1;

int plan = aiPlanCreate( "Defense", cPlanDefend );
// The plan's details go here: priority, protounit, etc.
// ...
// ...
// Now, add a custom variable:
// Syntax: aiPlanAddUserVariableInt(planID, variableID, description, numberValues)
aiPlanAddUserVariableInt( plan, cPlanSide, "Side", 1 ); // 1 value: north OR south
aiPlanSetUserVariableInt( plan, cPlanSide, 0, cNorthSide ); // Let's flag this plan as a north-side plan
// Activate the plan:
aiPlanSetActive( plan, true );
Later on, you can retrieve that variable:

Code: Select all


// ... other codes, not our concern here
// ...
// Check if this plan is a north-side plan:
if ( aiPlanGetUserVariableInt( plan, cPlanSide, 0 ) == cNorthSide)
{
// Alright, it is. Now act accordingly
}
else
{
// Nah, it's south-side.
}
For that fictive scenario, you might have taken a completely different approach than using user-defined plan variables but it was just for showing how user-defined plan variables work.

The cool thing is that you can change the number of values even after setting it:

Code: Select all


// Syntax:
// aiPlanSetNumberUserVariableValues( planID, variableID, numberValues, clearCurrentValues );
The last parameter is a boolean. If it's true, all of the plan variable's current values are cleared. If it's false, those values remain.

DATA PLAN AS AN ARRAY
Knowing all that, you can, if you want, use a plan as a replacement to arrays. I recommend using the Data Plan because it was made specifically to store data. Here's an example of mine:

Code: Select all


int gArrayPlan = -1;
int gArrayPlanIDs = -1;

int arrayCreateInt( int size = 1, string description = "BUG" )
{
gArrayPlanIDs++;
aiPlanAddUserVariableInt( gArrayPlan, gArrayPlanIDs, description, size );
return( gArrayPlanIDs );
}

void arrayPushInt( int arrayID = -1, int value = -1 )
{
if ( arrayID <= -1 )
return;
if ( arrayID > gArrayPlanIDs )
return;

int numberValues = aiPlanGetNumberUserVariableValues( gArrayPlan, arrayID );
aiPlanSetNumberUserVariableValues( gArrayPlan, arrayID, numberValues + 1, false );
aiPlanSetUserVariableInt( gArrayPlan, arrayID, numberValues, value );
}

void arraySetInt( int arrayID = -1, int arrayIndex = -1, int value = -1 )
{
if ( arrayID <= -1 )
return;
if ( arrayID > gArrayPlanIDs )
return;
if ( arrayIndex <= -1 )
return;
if ( arrayIndex >= aiPlanGetNumberUserVariableValues( gArrayPlan, arrayID ) )
return;

aiPlanSetUserVariableInt( gArrayPlan, arrayID, arrayIndex, value );
}

int arrayGetSize( int arrayID = -1 )
{
if ( arrayID <= -1 )
return( 0 );
if ( arrayID > gArrayPlanIDs )
return( 0 );

return( aiPlanGetNumberUserVariableValues( gArrayPlan, arrayID ) );
}

int arrayGetInt( int arrayID = -1, int arrayIndex = -1 )
{
if ( arrayID <= -1 )
return( -1 );
if ( arrayID > gArrayPlanIDs )
return( -1 );
if ( arrayIndex <= -1 )
return( -1 );
if ( arrayIndex >= aiPlanGetNumberUserVariableValues( gArrayPlan, arrayID ) )
return( -1 );

return( aiPlanGetUserVariableInt( gArrayPlan, arrayID, arrayIndex ) );
}

void main( void )
{
kbAreaCalculate();
gArrayPlan = aiPlanCreate( "Arrays", cPlanData );
}

rule test
active
minInterval 1
{
if ( xsGetTime() > 10000 )
{
xsDisableSelf();
return;
}

static int myTestArray = -1;
if ( myTestArray == -1 )
{
myTestArray = arrayCreateInt( 1, "This is just for testing" );
arraySetInt( myTestArray, 0, aiRandInt(10) );
}
arrayPushInt( myTestArray, aiRandInt(10) );

for ( index = 0 ; < arrayGetSize( myTestArray ) )
aiChat( 1, "At index " + index + ", value is " + arrayGetInt( myTestArray, index ) );
}
Extremity such as this should never happen unless you deliberately decide to adopt this approach, or you need it for various reasons. However, at some point, you might be forced to make your own tools like this due to certain lacks of XS. For example, math functions aren't supported natively so you will have to write them if you ever need to do some more advanced math.



CONCLUSION

I told you it was gonna be short! If you're still not familiar enough with programming yet, chances are that you didn't get much of what has been talked about here. That's fine. If you're not familiar with programming, you wouldn't want to get this far in AI scripting anyway: you'll want to get better at the basics of both programming and AI scripting first.

Next time, I will finally talk about the homecity: "buy" cards and build a deck in pre-game and send shipments in-game. See you :D

Who is online

Users browsing this forum: No registered users and 10 guests

Which top 10 players do you wish to see listed?

All-time

Active last two weeks

Active last month

Supremacy

Treaty

Official

ESOC Patch

Treaty Patch

1v1 Elo

2v2 Elo

3v3 Elo

Power Rating

Which streams do you wish to see listed?

Twitch

Age of Empires III

Age of Empires IV