@Falconian - I has used this in LoME and it seems to provide a bit more variety. It has a main attack rule and a few subroutines that calculate different player profiles getClosestPlayer, findWeakestPlayer, findStrongestPlayer. There is also a routine called aiEhcoNew which I use to track stuff for debugging.
It may have some other subroutines that I have missed but happy to pass them on. This set of rules tends to avoid the probability of player one being attacked - the downside is that the enemy will sometimes by-pass you on their way to the chosen target. It works well in multi-vs-multi because you usually end up with multiple battle fronts of varying intensity.
The main rules I can recall are:
rule updatePlayerToAttack
minInterval 20
active
{
string fName = "UPDATE_PLAYER_TO_ATTACK";
bool showMessage = false;
//Define Player Type variables
int closestPlayerID = -1;
int weakestPlayerID = -1;
int strongestPlayerID = -1;
int randomPlayerID = -1;
int colocatePlayerID = -1;
int attackingPlayerID = -1;
int currentID = -1;
int actualPlayerID = -1;
//Get our main base.
int mainBaseID=kbBaseGetMainID(cMyID);
if (mainBaseID <
0)
return;//For Nomad and things like that when establishing a base is the main priority.
//Determine if we are under attack.
bool underAttack = false;
int waitTime = (4 - aiGetWorldDifficulty()) * 15;//Nightmare = 15 secs, Easy = 60 Seconds
int forceSize = (aiGetWorldDifficulty() + 1) * 2;//Nightmare = 8 units, Easy = 2 units
if( (kbBaseGetTimeUnderAttack(cMyID, mainBaseID) > waitTime) &&
(kbBaseGetNumberUnits(cMyID, mainBaseID, cPlayerRelationEnemy, cUnitTypeUnit) > forceSize ) )
{
underAttack = true;
}
//If under attack, forget the current opponent for now
if(underAttack == true)
{
currentID = -1;
}
//Else, find out whether we already have a target and whether they are still active
else
{
currentID = aiGetMostHatedPlayerID();
if ( (kbIsPlayerEnemy(currentID) == true) &&//We have an enemy
(kbIsPlayerResigned(currentID) == false) &&//They haven't resigned
(kbHasPlayerLost(currentID) == false) )//They haven't been beaten
{
return;//We have someone to hate, so don't bother changing
}
}
// SCENARIO 1: WE ARE NOT UNDER ATTACK AND NOT ON AN ISLAND MAP
// DETERMINE OUR FOUR BASIC LAND ATTACK OPTIONS: Closest, Weakest, Strongest, Random
if(underAttack == false)
{
//Determine Closest Player
closestPlayerID = getClosestPlayer(cPlayerRelationEnemy);
if (closestPlayerID == -1)
{
aiEchoNew(fName, "Explore a bit more before deciding", showMessage);
return;
}
//Determine Weakest Player
weakestPlayerID = findWeakestPlayer(true);
//Determine Strongest Player
strongestPlayerID = findStrongestPlayer(true);
//Select a Player at Random
randomPlayerID = -1;
static int startIndex=-1;
if (startIndex < 0)
startIndex=aiRandInt(cNumberPlayers);
for (i=0; < cNumberPlayers)
{
//If we're past the end of our players, go back to the start.
int actualIndex=i+startIndex;
if (actualIndex >= cNumberPlayers)
actualIndex=actualIndex-cNumberPlayers;
if (actualIndex <= 0)
continue;
if ( (kbIsPlayerEnemy(actualIndex) == true) &&
(kbIsPlayerResigned(actualIndex) == false) &&
(kbHasPlayerLost(actualIndex) == false) )
{
randomPlayerID = actualIndex;
break;
}
}
}
// SCENARIO 2: WE ARE NOT UNDER ATTACK BUT WE ARE ON AN ISLAND MAP
// DETERMINE WHETHER WE SHARE OUR ISLAND WITH AN OPPONENT
if( (underAttack == false) &&// We are not under attack but we are on an "island" map
(gTransportMap == true) &&//
(gWaterMap == true) )// Calculate island attack options
{
vector playerBase = cInvalidVector;
for (i=0; < cNumberPlayers)
{
playerBase = kbBaseGetLocation(i, kbBaseGetMainID(i));
if( (isOnMyIsland(playerBase) == true) && (i != cMyID) )
{
colocatePlayerID = i;
aiEchoNew(fName, "Enemy Player #" + colocatePlayerID + " is on our Island", showMessage);
break;
}
}
}
// SCENARIO 3: WE ARE UNDER ATTACK
if(underAttack == true)
{
int largestForce = -1;
int baseAttackerQuery = -1;
int attackerCount = 0;
//Determine the main attacking force (Military within 100m of main base)
for (i=0; < cNumberPlayers)
{
if(kbIsPlayerEnemy(i) == true)
{
//Create a Query
baseAttackerQuery = kbUnitQueryCreate("Base Attacker Query"+i);
configQuery(baseAttackerQuery, cUnitTypeMilitary, -1, cUnitStateAlive, i,
kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID)), false, 100.0);
kbUnitQueryResetResults(baseAttackerQuery);
attackerCount = kbUnitQueryExecute(baseAttackerQuery);
aiEchoNew("Player #" + i + " has " + attackerCount + " attacking our base");
if(attackerCount > largestForce)
largestForce = i;
}
}
attackingPlayerID = largestForce;
}
//We now have 6 potential options closest, weakest, strongest, random, shared island and attacking force
//
//Basic attack assumptions are:
//
// 1. If we are under attack the main attacker is the most hated.
// 2. If we are sharing an Island with an enemy attack them first.
// 3. On other maps bias is to attack close as it simplifies supply routes.
// 4. Try and match the opponents strength to our level of Offence
//
// 99. Do not make it too predictable
//
float oddsClosest = 0.0;
if (closestPlayerID != -1)
{
oddsClosest = aiRandInt(25);
}
float oddsWeakest = 0.0;
if (weakestPlayerID != -1)
{
oddsWeakest = aiRandInt(25);
}
float oddsStrongest = 0.0;
if (strongestPlayerID != -1)
{
oddsStrongest = aiRandInt(25);
}
float oddsRandom = 0.0;
if (randomPlayerID != -1)
{
oddsRandom = aiRandInt(25);
}
float normalise = 100.0000/(oddsClosest + oddsWeakest + oddsStrongest + oddsRandom);
oddsClosest = oddsClosest * normalise;//
oddsWeakest = oddsWeakest * normalise;// These values will now add up to 100
oddsStrongest = oddsStrongest * normalise;//
oddsRandom = oddsRandom * normalise;//
aiEchoNew(fName, "Normalised Odds .............", showMessage);
aiEchoNew(fName, "Closest Player is " + closestPlayerID + ", attack odds " + oddsClosest +"%", showMessage);
aiEchoNew(fName, "Weakest Player is " + weakestPlayerID + ", attack odds " + oddsWeakest +"%", showMessage);
aiEchoNew(fName, "Strongest Player is " + strongestPlayerID + ", attack odds " + oddsStrongest +"%", showMessage);
aiEchoNew(fName, "Random Player is " + randomPlayerID + ", attack odds " + oddsRandom +"%", showMessage);
aiEchoNew(fName, "Colocate Player is " + colocatePlayerID, showMessage);
int temp = -1;
if(attackingPlayerID > -1)//We are under attack
{
aiEchoNew(fName, "Using Defend Own Base Option", showMessage);
actualPlayerID = attackingPlayerID;
}
else if (colocatePlayerID > -1)//We are not alone
{
aiEchoNew(fName, "Using Co-locate Option", showMessage);
actualPlayerID = colocatePlayerID;//alway defend home island first
}
else if (closestPlayerID == weakestPlayerID)
{
aiEchoNew(fName, "Using Closest = Weakest Player", showMessage);
temp = aiRandInt(5);
if (temp != 0)
actualPlayerID = closestPlayerID;//get it out of the way 80% of the time,
else
actualPlayerID = randomPlayerID;//else random
}
else if( (closestPlayerID == strongestPlayerID) || (cvOffenseDefenseSlider > 0.5) )
{
aiEchoNew(fName, "Using Closest = Strongest Player", showMessage);
temp = aiRandInt(10);
if (temp < 6)
actualPlayerID = strongestPlayerID;//60% of the time - Bring em on!
else if (temp < 8)
actualPlayerID = closestPlayerID;//20% of the time - Expand the Empire!
else if (temp < 9)
actualPlayerID = weakestPlayerID;//10% of the time - Crush the minnows!
else
actualPlayerID = randomPlayerID;//10% random
}
else if (cvOffenseDefenseSlider < -0.5)//We don't want too much trouble
{
aiEchoNew(fName, "Using Defensive Option", showMessage);
if(closestPlayerID != strongestPlayerID)
actualPlayerID = closestPlayerID;
else
actualPlayerID = weakestPlayerID;
}
else
{
aiEchoNew(fName, "Using Random Option", showMessage);
temp = aiRandInt(100) + 1;//between 1 and 100
if ( temp > (oddsClosest + oddsWeakest +oddsStrongest) )
{
actualPlayerID = randomPlayerID;
}
else if ( temp > (oddsClosest + oddsWeakest) )
{
actualPlayerID = strongestPlayerID;
}
else if ( temp > oddsClosest )
{
actualPlayerID = weakestPlayerID;
}
else
{
actualPlayerID = closestPlayerID;
}
}
aiEchoNew(fName, "Calculated Most Hated Player is Player " + actualPlayerID, showMessage);
//If another player has been set as a control variable use this
if (cvPlayerToAttack != -1)
{
actualPlayerID = cvPlayerToAttack;
aiEchoNew(fName, "Overriding to cvPlayerToAttack" + actualPlayerID, showMessage);
}
//Override all of this is I am Mordor playing against a Fellowship opponent
if ( (gFellowshipGame == true) && (cMyCulture == cCultureAtlantean) )
{
for(i=0; < cNumberPlayers)
{
if(kbGetCivForPlayer(i) == cCivThor)
{
actualPlayerID = i;
aiEchoNew(fName, "Fellowship Overide, We Hates player " + actualPlayerID, showMessage);
break;
}
}
}
//Set most hated player
aiSetMostHatedPlayerID(actualPlayerID);
}
//=========================================================
// getClosestPlayer
//=========================================================
int getClosestPlayer(int relationType = cPlayerRelationEnemy)
{
string fName = "GET_CLOSEST_PLAYER";
bool showMessage = false;
vector whereAmI = kbBaseGetLocation(cMyID,kbBaseGetMainID(cMyID));
aiEchoNew(fName, "My Base Location is " + whereAmI, showMessage);
vector whereAreThey = cInvalidVector;
float closestPos = 100000.0;//in a galaxy far, far away ...
float dist = 0.0;
int closestPlayerID = -1;
for (i=1; < cNumberPlayers)//Not zero as 0 = Gaia
{
if ( (i != cMyID) && (kbIsPlayerResigned(i) == False) )
{
if( ( (kbIsPlayerEnemy(i) == true) && (relationType == cPlayerRelationEnemy) ) ||
( (kbIsPlayerEnemy(i) == false) && (relationType == cPlayerRelationAlly) ) )
{
whereAreThey = kbBaseGetLocation(i, kbBaseGetMainID(i));
if (whereAreThey != vector(-1,-1,-1))
{
aiEchoNew(fName, "Player " + i + ": Base Located at " + whereAreThey, showMessage);
dist = distanceBetween(whereAmI, whereAreThey);
}
if (dist == 0.0)
aiEchoNew(fName, "Can't see Player " + i, showMessage);
if ( (dist > 0.0) && ( dist < closestPos) )
{
closestPos = dist;
closestPlayerID = i;
aiEchoNew(fName, "Closest Player is #" + closestPlayerID, showMessage);
}
}
}
}
if ( (closestPos > 0.0) && (closestPos < 100000.0) )
{
return(closestPlayerID);
}
else
{
return(-1);
}
}
//=========================================================
// findStrongestPlayer Returns the player number of the strongest player
//=========================================================
int findStrongestPlayer(bool isEnemy = true)
{
string fName = "FIND_STRONGEST_PLAYER";
bool showMessage = false;
int ageScore = 0;
int troopScore = 0;
int totalScore = 0;
int highestScore = -1;
int strongestPlayer = -1;
for (i=1; < cNumberPlayers)
{
if( (kbIsPlayerResigned(i) == False) && (kbIsPlayerEnemy(i) == isEnemy) )
{
ageScore = kbGetAgeForPlayer(i);
troopScore = kbUnitCount(i,cUnitTypeHumanSoldier,cUnitStateAlive) +
(kbUnitCount(i,cUnitTypeHero,cUnitStateAlive) * 2) +
(kbUnitCount(i,cUnitTypeMythUnit,cUnitStateAlive) * 2) +
(kbUnitCount(i,cUnitTypeTower,cUnitStateAlive) * 5) +
(kbUnitCount(i,cUnitTypeAbstractWall,cUnitStateAlive) * 3);
//kbGetCombatEfficiency( int playerID1, int unitTypeID1, int playerID2, int unitTypeID2 );
totalScore = (ageScore * 40) + troopScore;
aiEchoNew(fName, "Player #" + i + " Total Score = " + totalScore, showMessage);
if( (totalScore > 0) && (totalScore > highestScore) )
{
highestScore = totalScore;
strongestPlayer = i;
aiEchoNew(fName, "Strongest Player is #" + strongestPlayer, showMessage);
}
}
}
return(strongestPlayer);
}
//=========================================================
/ / findWeakestPlayer Returns the player number of the weakest player
//=========================================================
int findWeakestPlayer(bool isEnemy = true)
{
string fName = "FIND_WEAKEST_PLAYER";
bool showMessage = false;
int ageScore = 0;
int troopScore = 0;
int totalScore = 0;
int lowestScore = -1;
int weakestOpponent = -1;
for (i=1; < cNumberPlayers)
{
if( (kbIsPlayerResigned(i) == False) && (kbIsPlayerEnemy(i) == isEnemy) )
{
ageScore = kbGetAgeForPlayer(i);
troopScore = kbUnitCount(i,cUnitTypeHumanSoldier,cUnitStateAlive) +
(kbUnitCount(i,cUnitTypeHero,cUnitStateAlive) * 2) +
(kbUnitCount(i,cUnitTypeMythUnit,cUnitStateAlive) * 2) +
(kbUnitCount(i,cUnitTypeTower,cUnitStateAlive) * 5) +
(kbUnitCount(i,cUnitTypeAbstractWall,cUnitStateAlive) * 3);
//kbGetCombatEfficiency( int playerID1, int unitTypeID1, int playerID2, int unitTypeID2 )
totalScore = (ageScore * 40) + troopScore;
aiEchoNew(fName, "Player #" + i + " Total Score = " + totalScore, showMessage);
if( (lowestScore == -1) || ( (totalScore > 0) && (totalScore < lowestScore) ) )
{
lowestScore = totalScore;
weakestOpponent = i;
aiEchoNew(fName, "Weakest Player is #" + weakestOpponent, showMessage);
}
}
}
if (lowestScore < 0)//called too early to decide
return(-1);
else
return(weakestOpponent);
}
//=========================================================
// aiEchoNew - same as aiEcho, but includes function header and show/no-show option
//=========================================================
void aiEchoNew(string functionName = "" ,string text="", bool show = false)
{
string message = functionName + ": " + text;
if (show == true)
aiEcho(message);
}[This message has been edited by Stephen Caines (edited 03-12-2009 @ 06:35 AM).]