You must be logged in to post messages.
Please login or register

Modding Discussions
Moderated by Alexastor, MosheLevi, Mister SCP

Hop to:    
Welcome! You are not logged in. Please Login or Register.6 replies
Age of Empires III Heaven » Forums » Modding Discussions » Some research on AI scripting
Bottom
Topic Subject:Some research on AI scripting
kangcliff
Skirmisher
posted 01-04-18 10:48 PM EDT (US)         
As I've not been active on modding for some time, I decided to share some of my research on AI scripting itself. Starting with transport plan, which modders including me have been struggling to get it to work correctly. (There is method to transport units using a fake attack plan though.)
However, after locating the hardcoded plan in the EXE which was created by attack plan with name AttackXPort, I was able to create a working plan as shown below:

//==============================================================================
// createSimpleTransportPlan
//==============================================================================
int createSimpleTransportPlan(int transportID = -1, vector gatherPoint = cInvalidVector, vector targetPoint = cInvalidVector, int pri = 100, bool economy = true, int baseID = -1)
{
int transportType = kbUnitGetProtoUnitID(transportID);
int planID = aiPlanCreate(kbGetUnitTypeName(transportType)+" Transport Plan", cPlanTransport);

aiPlanSetBaseID(planID, baseID);

// Priority.
aiPlanSetDesiredPriority(planID, pri);
// Mil vs Econ.
if (economy == true)
aiPlanSetMilitary(planID, false);
else
aiPlanSetMilitary(planID, true);

// Transport unit.
aiPlanSetVariableInt(planID, cTransportPlanTransportTypeID, 0, transportType);
aiPlanSetVariableInt(planID, cTransportPlanTransportID, 0, transportID);

// Gather point.
aiPlanSetVariableVector(planID, cTransportPlanGatherPoint, 0, gatherPoint);
// Target point.
aiPlanSetVariableVector(planID, cTransportPlanTargetPoint, 0, targetPoint);

aiPlanSetVariableBool(planID, cTransportPlanReturnWhenDone, 0, true);
aiPlanSetVariableBool(planID, cTransportPlanPersistent, 0, false);
aiPlanSetVariableBool(planID, cTransportPlanMaximizeXportMovement, 0, true);
aiPlanSetVariableInt(planID, cTransportPlanPathType, 0, cTransportPathTypePoints);
aiPlanSetVariableBool(planID, cTransportPlanTakeMoreUnits, 0, false);

aiPlanSetActive(planID);

return(planID);
}

Based on my tests, cTransportPathTypeAreas doesn't seem to be working, and should probably always set cTransportPlanMaximizeXportMovement to true as it maximizes the water transport distance.
Here is a example for the Ceylon map to transport covered Wagon and scouts to the main Island before doing anything.

//==============================================================================
// initCeylon
//
// migrate to main island before calling init.
//==============================================================================

extern vector gCeylonMainIslandClosestLocation = cInvalidVector;
extern int gCeylonTransportID = -1;

void initCeylon(void)
{
int areaGroupNumberAreas = -1;

vector myLocation = cInvalidVector;
int myAreaGroup = -1;

int area = 0;
int areaGroup = -1;
int i = 0;
int unit = getUnit(cUnitTypeCoveredWagon, cMyID, cUnitStateAlive);

areaGroupNumberAreas = xsArrayCreateInt(gAreaGroupCount, 0, "Area group number areas");
for (area = 0; < gAreaCount)
{
areaGroup = kbAreaGroupGetIDByPosition(kbAreaGetCenter(area));
xsArraySetInt(areaGroupNumberAreas, areaGroup, xsArrayGetInt(areaGroupNumberAreas, areaGroup) + 1);
}

myLocation = kbUnitGetPosition(unit);
myAreaGroup = kbAreaGroupGetIDByPosition(myLocation);

int closestArea = -1;
float closestAreaDistance = kbGetMapXSize();

for (area = 0; < gAreaCount)
{
if (kbAreaGetType(area) == cAreaTypeWater)
continue;

areaGroup = kbAreaGroupGetIDByPosition(kbAreaGetCenter(area));
if (xsArrayGetInt(areaGroupNumberAreas, areaGroup) - xsArrayGetInt(areaGroupNumberAreas, myAreaGroup) <= 10)
continue;

bool bordersWater = false;
int borderAreaCount = kbAreaGetNumberBorderAreas(area);
for (i = 0; < borderAreaCount)
{
if (kbAreaGetType(kbAreaGetBorderAreaID(area, i)) == cAreaTypeWater)
{
bordersWater = true;
break;
}
}

if (bordersWater == false)
continue;

float dist = xsVectorLength(kbAreaGetCenter(area) - myLocation);
if (dist < closestAreaDistance)
{
closestAreaDistance = dist;
closestArea = area;
}
}

gCeylonMainIslandClosestLocation = kbAreaGetCenter(closestArea);
gCeylonTransportID = getUnit(cUnitTypeypMarathanCatamaran, cMyID, cUnitStateAlive);
aiTaskUnitMove(gCeylonTransportID, gCeylonMainIslandClosestLocation);
xsEnableRule("initCeylonWaitForExplore");
}

rule initCeylonWaitForExplore
inactive
minInterval 3
{
if (kbLocationVisible(gCeylonMainIslandClosestLocation) == false)
{
aiTaskUnitMove(gCeylonTransportID, gCeylonMainIslandClosestLocation);
return;
}

int unit = getUnit(cUnitTypeCoveredWagon, cMyID, cUnitStateAlive);
vector location = kbUnitGetPosition(unit);

int baseID = kbBaseCreate(cMyID, "Transport gather base", location, 10.0);
kbBaseAddUnit(cMyID, baseID, unit);

int transportPlan = createSimpleTransportPlan(gCeylonTransportID, location, gCeylonMainIslandClosestLocation, 100, true, baseID);

aiPlanSetEventHandler(transportPlan, cPlanEventStateChange, "initCeylonTransportHandler");
int numberNeeded = kbUnitCount(cMyID, cUnitTypeAbstractWagon, cUnitStateAlive);
aiPlanAddUnitType(transportPlan, cUnitTypeAbstractWagon, numberNeeded, numberNeeded, numberNeeded);

numberNeeded = kbUnitCount(cMyID, cUnitTypeLogicalTypeScout, cUnitStateAlive);
aiPlanAddUnitType(transportPlan, cUnitTypeLogicalTypeScout, numberNeeded, numberNeeded, numberNeeded);

xsDisableSelf();
}

void initCeylonTransportHandler(int planID = -1)
{
static bool transporting = false;
switch(aiPlanGetState(planID))
{
case -1:
{

if (transporting == true)
{
// transport done.
aiTaskUnitMove(getUnit(cUnitTypeCoveredWagon, cMyID, cUnitStateAlive), kbGetMapCenter());
xsEnableRule("initRule");
xsSetRuleMinInterval("initRule", 10 + aiRandInt(10));
}
break;
}
case cPlanStateGather:
{
// hack drop-off point
vector targetPoint = aiPlanGetVariableVector(planID, cTransportPlanTargetPoint, 0);
vector centerPoint = kbGetMapCenter();
aiPlanSetVariableVector(planID, cTransportPlanDropOffPoint, 0, xsVectorSet(0.2 * xsVectorGetX(centerPoint) + 0.8 * xsVectorGetX(targetPoint), 0.0, 0.2 * xsVectorGetZ(centerPoint) + 0.8 * xsVectorGetZ(targetPoint)));
break;
}
case cPlanStateEnter:
{
xsEnableRule("initCeylonFailsafe");
break;
}
case cPlanStateGoto:
{
transporting = true;
break;
}
}
}

rule initCeylonFailsafe
inactive
minInterval 10
{
int transportPlan = aiPlanGetIDByTypeAndVariableType(cPlanTransport, cTransportPlanTransportTypeID, cUnitTypeypMarathanCatamaran);
switch(aiPlanGetState(transportPlan))
{
case -1:
{
xsDisableSelf();
break;
}
case cPlanStateEnter:
{
aiTaskUnitMove(gCeylonTransportID, aiPlanGetVariableVector(transportPlan, cTransportPlanGatherPoint, 0));
break;
}
}
}

Added an plan state handler because transport often fails during the progress, mostly at cPlanStateEnter which the ship stays idle and if a 'bad' cTransportPlanDropOffPoint is used, garrisoned units ended up not being ejected on land.

And well, it's known that AI cannot repair buildings and use abilities, there are currently no easy hacks to create XS syscalls based on my research on the EXE.
Also, if anyone need help create XS syscalls or cheats with our Unhardcode Patch plug-in system, feel free to use this thread to ask questions.

[This message has been edited by kangcliff (edited 01-04-2018 @ 10:49 PM).]

AuthorReplies:
Panmaster2014
Skirmisher
posted 01-07-18 03:58 AM EDT (US)     1 / 6       
I've never got the transport plan to work at all for ungarrisoning but I know it works for ships since the hardcoded part of the ai creates them.

How can you obtain useful information from the age3y.exe ?

I am certain there are still many "hidden" functions and values e.g. the TownBell plan. There are also unnamed area types such as starting positions and trade socket positions. Some things are shown in the debug files, some in the .exe and some are completely hidden.
kangcliff
Skirmisher
posted 01-07-18 05:41 AM EDT (US)     2 / 6       
I've never got the transport plan to work at all for ungarrisoning but I know it works for ships since the hardcoded part of the ai creates them.
Well, transport plans requires aiSetWaterMap(true), which means only transporting over water, otherwise it will be destroyed instantly after activation.
How can you obtain useful information from the age3y.exe ?

I am certain there are still many "hidden" functions and values e.g. the TownBell plan. There are also unnamed area types such as starting positions and trade socket positions. Some things are shown in the debug files, some in the .exe and some are completely hidden.
Although the XS functions and constants can be seen as strings in the EXE, most time the information provided by strings are not enough, so I had to look into the assembly code itself using some reverse engineering tools. And since you mentioned the TownBell plan, it does work.


extern const int cTownBellPlanTownCenterID = 0;
int plan = aiPlanCreate("Town bell", cPlanTownBell);
aiPlanSetVariableInt(plan, cTownBellPlanTownCenterID, 0, getUnit(cUnitTypeTownCenter, cMyID, cUnitStateAlive));
aiPlanSetActive(plan);

Panmaster2014
Skirmisher
posted 01-07-18 07:18 AM EDT (US)     3 / 6       
Your assistance has been most helpful. I'd have never got it working.
Ungarrisoning is now working.

void UnGarrison(int buildID = -1)
{
int planID = -1;
int unitID = -1;
vector v = kbUnitGetPosition(buildID);
planID = aiPlanCreate("DeGarrison"+buildID,cPlanTransport);
aiPlanAddUnitType(planID,cUnitTypeAbstractVillager,1,1,99);
aiPlanSetVariableInt(planID,cTransportPlanTransportID,0,buildID);
aiPlanSetVariableInt(planID,cTransportPlanTransportTypeID,0,kbGetUnitBaseTypeID(buildID));
aiPlanSetVariableInt(planID,cTransportPlanPathType,0,cTransportPathTypePoints);
aiPlanSetVariableBool(planID,cTransportPlanTakeMoreUnits,0,false);
aiPlanSetVariableBool(planID,cTransportPlanPathPlanned,0,true);
aiPlanSetVariableBool(planID,cTransportPlanPersistent,0,false);
aiPlanSetVariableBool(planID,cTransportPlanReturnWhenDone,0,false); //no effect
aiPlanSetVariableBool(planID,cTransportPlanMaximizeXportMovement,0,false); //no effect
aiPlanSetVariableVector(planID,cTransportPlanGatherPoint,0, v);
aiPlanSetActive(planID,true);
for(i=0;<99)
{
unitID = SelectNearestUnit(cUnitTypeAbstractVillager,cMyID,v,6.0,i);
if (unitID > -1)
aiPlanAddUnit(planID, unitID);
else
return;
}}//end function

[This message has been edited by Panmaster2014 (edited 01-07-2018 @ 09:24 AM).]

kangcliff
Skirmisher
posted 01-07-18 09:48 AM EDT (US)     4 / 6       
Your assistance has been most helpful. I'd have never got it working.
Ungarrisoning is now working.
Hmm, your code seems interesting, I have never used transport plans to solve the eject problem. Anyway, glad that I helped you.

[This message has been edited by kangcliff (edited 01-07-2018 @ 09:50 AM).]

Panmaster2014
Skirmisher
posted 01-15-18 12:59 PM EDT (US)     5 / 6       
Found anything else interesting or have any insights?

My layman attempt at dissembling the .exe in x64dbg failed miserably to find anything. I can't even tell the difference between variable types. All call 2 pointers regardless and have the same amount of instructions. Surely a vector would have 3 times as much as a float? I don't understand it at all.
kangcliff
Skirmisher
posted 01-18-18 11:05 AM EDT (US)     6 / 6       
Found anything else interesting or have any insights?
I wrote a tutorial about how to develop UHC plug-ins, which can be used to add new functions to XS scripts, or porting existing functions to the AI like repairing buildings shown in the post.

Tutorial: Developing a plugin for AoE3 Unhardcode Patch
You must be logged in to post messages.
Please login or register

Hop to:    

Age of Empires III Heaven | HeavenGames