//============================================================================== /* aiMilitary.xs Contains the military functionality for the AI */ //============================================================================== //============================================================================== // Global vars //============================================================================== extern int gMainAttackPlan = -1; // The main attack plan for the AI. extern int gLandReservePlan = -1; extern int gMainBase = -1; // Handle to our main base extern int gNumAttacksLaunched = 0; // The number of attacks the AI has launched extern int gLastAttackMissionTime = -1; // The last time the AI ran an attack mission extern int gLastDefendMissionTime = -1; // The last time the AI rand a defense mission extern int gWatchArmy = -1; // The army ID for units that we're watching to make sure they don't get too low on health (used by objectives) extern float gPlayerHateThreshold = 1.0; // when this is > 1.0, most hated enemy will switch based on cUnitTypeLogicalTypeNeededForVictory //============================================================================== // Defense //============================================================================== extern int gLandDefensePlan = -1; // Primary land defend plan extern bool gDefenseReflex = false; // Set true when a defense reflex is overriding normal ops. extern bool gNoEnemyBases = false; //============================================================================== // Function prototypes //============================================================================== mutable void setUnitPickerPreference(int upID = -1) {} //============================================================================== // Unit queries //============================================================================== extern int gEnemyArmyQuery = -1; extern int gTowerSearch = -1; extern int gPalintononQuery = -1; extern int gSpawnedUnitDefenseQuery = -1; //============================================================================== // Plans //============================================================================== extern int gMilitaryUpgradePlan = -1; //============================================================================== // Better Attack Manager variables (aka BAM) //============================================================================== extern bool gUseBetterAttackManager = false; extern int gBAMMaxAttackGroups = 20; // things to tweak extern int gBAMAttackGroups = 1; // number of groups to send each attack extern int gBAMGroupInterval = 30; // interval in seconds between each group being sent extern int gBAMAttackInterval = 180; // interval in seconds between each attack extern int gBAMInitialAttackDelay = 240; // initial attack delay in seconds extern int gBAMTargetAge1GroupSize = 0; // group size in age 1 extern int gBAMTargetAge2GroupSize = 0; // group size in age 2 extern int gBAMTargetAge3GroupSize = 0; // group size in age 3 extern int gBAMTargetAge4GroupSize = 0; // group size in age 4 extern int gBAMGroupSizeRampUp = 0; // number of attacks it will take to get to the next age's group size if we were to never age up extern int gBAMCount_NotEnoughUnits = 0; // number of times a BAM attack failed due to insufficient units extern int gBAMCount_AttackFormed = 0; // number of times a BAM attack formed properly extern float gBAMMinUnitThreshold = 0.8; // percentage of our group size that we must have in order to create an attack plan extern float gBAMEngageRadius = 20; // radius used when attacking extern vector gBAMTransportPickUpLoc = cInvalidVector; extern bool gBAMForceTransportUsage = false; // internal variables for keeping track of things extern int gBAMAttackGroupSize = 20; extern int gBAMCurrentGroupIndex = -1; extern int gBAMLastGroupSentTime = 0; extern int gBAMLastAttackStartTime = 0; extern int gBAMAttackPlanIDArray = -1; // attach route pattern: // cAttackPlanAttackRoutePatternLRU Least Recently Used // cAttackPlanAttackRoutePatternMRU Most Recently Used // cAttackPlanAttackRoutePatternRandom Random // cAttackPlanAttackRoutePatternBest Best extern int gBAMAttackRoutePattern = cAttackPlanAttackRoutePatternRandom; // attach modes: // cAttackPlanBaseAttackModeNone // cAttackPlanBaseAttackModeWeakest // cAttackPlanBaseAttackModeStrongest // cAttackPlanBaseAttackModeLRU // cAttackPlanBaseAttackModeMRU // cAttackPlanBaseAttackModeRandom // cAttackPlanBaseAttackModeClosest // cAttackPlanBaseAttackModeFarthest // cAttackPlanBaseAttackModeExplicit extern int gBAMAttackMode = cAttackPlanBaseAttackModeClosest; extern int gBAMMaxNumRams = -1; extern int gBAMMaxNumCatapults = -1; extern int gBAMMaxNumTrebuchets = -1; //============================================================================== // calculateTrainPercentBonus // used to add a bonus percentage of units when calculating military size //============================================================================== mutable float calculateTrainPercentBonus() { return(0.0); } //============================================================================== // createSimpleAttackPlan //============================================================================== int createSimpleAttackPlan(string name="BUG", int attackPlayerID=-1, int unitPickerID=-1, int repeat=-1, int minAge=-1, int maxAge=-1, int baseID=-1, bool allowRetreat=false) { //aiEcho("createSimpleAttackPlan: Name="+name+", AttackPlayerID="+attackPlayerID+"."); //aiEcho(" UnitPickerID="+unitPickerID+", Repeat="+repeat+", baseID="+baseID+"."); //aiEcho(" MinAge="+minAge+", maxAge="+maxAge+", allowRetreat="+allowRetreat+"."); //Create the goal. int goalID=aiPlanCreate(name, cPlanGoal); if (goalID < 0) return(-1); //Priority. aiPlanSetDesiredPriority(goalID, 90); //Attack player ID. if (attackPlayerID >= 0) aiPlanSetVariableInt(goalID, cGoalPlanAttackPlayerID, 0, attackPlayerID); else aiPlanSetVariableBool(goalID, cGoalPlanAutoUpdateAttackPlayerID, 0, true); //Base. if (baseID >= 0) aiPlanSetBaseID(goalID, baseID); else aiPlanSetVariableBool(goalID, cGoalPlanAutoUpdateBase, 0, true); //Attack. aiPlanSetAttack(goalID, true); aiPlanSetVariableInt(goalID, cGoalPlanGoalType, 0, cGoalPlanGoalTypeAttack); aiPlanSetVariableInt(goalID, cGoalPlanAttackStartFrequency, 0, 5); //Military. aiPlanSetMilitary(goalID, true); aiPlanSetEscrowID(goalID, cMilitaryEscrowID); //Ages. aiPlanSetVariableInt(goalID, cGoalPlanMinAge, 0, minAge); aiPlanSetVariableInt(goalID, cGoalPlanMaxAge, 0, maxAge); //Repeat. aiPlanSetVariableInt(goalID, cGoalPlanRepeat, 0, repeat); //Unit Picker. aiPlanSetVariableInt(goalID, cGoalPlanUnitPickerID, 0, unitPickerID); //Retreat. aiPlanSetVariableBool(goalID, cGoalPlanAllowRetreat, 0, allowRetreat); //Handle maps where the enemy player is usually on a diff island. if (isCurrentMapNaval()) { aiPlanSetVariableBool(goalID, cGoalPlanSetAreaGroups, 0, true); aiPlanSetVariableInt(goalID, cGoalPlanAttackRoutePatternType, 0, cAttackPlanAttackRoutePatternRandom); } //Done. return(goalID); } //============================================================================== // rule mostHatedEnemy // // Determine who we should attack, checking control variables //============================================================================== rule mostHatedEnemy inactive group military minInterval 16 { int newEnemyID = -1; int currentEnemyID = aiGetMostHatedPlayerID(); if ( (cvPlayerToAttack > 0) && (kbHasPlayerLost(cvPlayerToAttack) == false) && (kbUnitCount(cvPlayerToAttack, cUnitTypeLogicalTypeNeededForVictory, cUnitStateAlive) > 0) ) { newEnemyID = cvPlayerToAttack; aiEcho("aiMilitary: --- mostHatedEnemy: forcing hated enemy [player" + newEnemyID + "]"); } else { // Calculate most hated player using one of two algorithms // NOTES: bfricks 3/15/2012 // * There are two algorithms for determining most hated enemy // the original algorithm uses building counts, and can fail if the enemy has no units // the new algorithm uses counts of all units that are cUnitTypeLogicalTypeNeededForVictory // * The new method is preferred, but high impact enough that we have decided to only use it on // a case by case basis, or when the bug occurs and there are no units under the control of the AI bool bUseOldAlgorithm = true; if (currentEnemyID > 0) { int currentEnemyCount = kbUnitCount(currentEnemyID, cUnitTypeLogicalTypeNeededForVictory, cUnitStateAlive); if (currentEnemyCount <= 0) { // if the current enemy count is zero, use the new algorithm bUseOldAlgorithm = false; } else if(gPlayerHateThreshold > 1.0) { // if the AI has been given a hate Threshold, use the new algorithm bUseOldAlgorithm = false; } } if (bUseOldAlgorithm) { newEnemyID = aiCalculateMostHatedPlayerID(currentEnemyID); if (newEnemyID != currentEnemyID) { aiEcho("aiMilitary: --- mostHatedEnemy (OLD): [player" + newEnemyID + "] hated more than [player" + currentEnemyID + "]"); } } else { for (i=1; <=cNumberPlayers) { if ( (kbIsPlayerEnemy(i) == true) && (kbHasPlayerLost(i) == false) ) { int tempCount = kbUnitCount(i, cUnitTypeLogicalTypeNeededForVictory, cUnitStateAlive); if( tempCount > (currentEnemyCount * gPlayerHateThreshold) ) { newEnemyID = i; } } } if ( (newEnemyID > -1) && (newEnemyID != currentEnemyID) ) { aiEcho("aiMilitary: --- mostHatedEnemy (NEW): [player" + newEnemyID + "] hated more than [player" + currentEnemyID + "] Threshold[" + gPlayerHateThreshold + "]"); } } } // update the hated playerID if ( (newEnemyID > -1) && (newEnemyID != currentEnemyID) ) { aiEcho("aiMilitary: --- mostHatedEnemy: setting to to [player" + newEnemyID + "]"); aiSetMostHatedPlayerID(newEnemyID); // Update the unit picker if (gLandUnitPicker >= 0) { kbUnitPickSetEnemyPlayerID(gLandUnitPicker, newEnemyID); } // update any BSAM attacks that have not yet begun moving but are waiting for units if(gUseBetterAttackManager) { int planCount = xsArrayGetSize(gBAMAttackPlanIDArray); for (i = 0; < planCount) { int plan = xsArrayGetInt(gBAMAttackPlanIDArray, i); if ( plan > -1) { // target player int attackPlayerID = aiGetMostHatedPlayerID(); aiPlanSetVariableInt(plan, cAttackPlanPlayerID, 0, attackPlayerID); // attack point, this will only be used if the attack mode selected fails to find anything vector attackPoint = kbBaseGetLocation(attackPlayerID, kbBaseGetMainID(attackPlayerID)); aiPlanSetVariableVector(plan, cAttackPlanAttackPoint, 0, attackPoint); } } } } else { aiEcho("aiMilitary: --- mostHatedEnemy: still [player" + currentEnemyID + "]"); } } //============================================================================== // findEnemyBase() // // Create a scouting plan to attempt to find an enemy base at the given location //============================================================================== void findEnemyBase(vector targetLocation = cInvalidVector) { // SM 1/13/2012: This function was set to immediately return, because we show the AI where player bases are. // If you are looking for the scouting routine that you commonly see running, that lives in aiEcon.xs. // Added block comment to the meat of this function just to make the fact this is not running super clear. return(); //TD: turning off in Age 4 since we show the AI where the player bases are. /*if (isCurrentMapNaval() == true) return(); // TODO...make a water version, look for enemy home island? if (cvOkToExplore == false) return(); if (xsVectorIsValid(targetLocation) == false) return(); int exploreID=aiPlanCreate("Probe Enemy Base", cPlanExplore); if (exploreID >= 0) { aiPlanAddUnitType(exploreID, cUnitTypeLogicalTypeExplorer, 1, 1, 1); aiPlanAddWaypoint(exploreID, targetLocation); aiPlanSetVariableBool(exploreID, cExplorePlanDoLoops, 0, true); aiPlanSetVariableInt(exploreID, cExplorePlanExploreDistance, 0, 300); aiPlanSetVariableInt(exploreID, cExplorePlanPointsInLoop, 0, 16); aiPlanSetVariableBool(exploreID, cExplorePlanQuitWhenPointIsVisible, 0, true); aiPlanSetVariableBool(exploreID, cExplorePlanAvoidingAttackedAreas, 0, false); aiPlanSetVariableBool(exploreID, cExplorePlanReExploreAreas, 0, true); aiPlanSetRequiresAllNeedUnits(exploreID, true); aiPlanSetVariableVector(exploreID, cExplorePlanQuitWhenPointIsVisiblePt, 0, targetLocation); aiPlanSetDesiredPriority(exploreID, 99); // Make sure this is lower priority than keeping our leaders at home aiPlanSetActive(exploreID); }*/ } //============================================================================== // rule findEnemyBaseRule // // Create a plan (using scouting units) to go find the enemy base. Run it periodically // to give the appearance of scouting. //============================================================================== /*rule findEnemyBaseRule inactive group military minInterval 3000 { if (isCurrentMapNaval() == true) return(); // TODO...make a water version, look for enemy home island? if (cvOkToExplore == false) return(); // If we know where the enemy base is, go there vector targetLocation = kbBaseGetLocation(aiGetMostHatedPlayerID(), kbBaseGetMainID(aiGetMostHatedPlayerID())); // Nope, go to the mirror position of our base if (xsVectorIsValid(targetLocation) == false) { aiEcho("Don't know location, so mirroring."); vector myBaseLocation=kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID)); // Main base location...need to find reflection. vector centerOffset = kbGetMapCenter() - myBaseLocation; targetLocation = kbGetMapCenter() + centerOffset; } findEnemyBase(targetLocation); }*/ //============================================================================== // allowAttacksAfterTransports // // BF 2/18/2012: Updated from Skirmish: // when transport is required is flagged on certain maps, they do not try to // attack until a transport exists // MC 3/12/2012: Moved this from aiNavy to aiMilitary and it no longer sets // cvOkToAttack. BAM and militaryManager now call this function when they // check to see if it is okay to make attacks. //============================================================================== bool allowAttacksAfterTransports() { if(aiIsMapType("AITransportRequired") == false) { return (true); } return(getUnit(cUnitTypeUnitTypeShipUtility1, cMyID, cUnitStateAlive) >= 0); } //============================================================================== // initUnitPicker() // // Initialize the unit picker and give it a set of units to try and build. // This is for land units only. // BF 2/18/2012: Updated from Skirmish: // removes gSPC handling (obsolete) // switches to cUnitTypeLogicalTypeLandPickerChoice from cUnitTypeLogicalTypeLandMilitary // switches to cUnitTypeLogicalTypeLandPickerTarget from cUnitTypeLogicalTypeLandMilitary //============================================================================== mutable int initUnitPicker(string name="BUG", int numberTypes=1, int minUnits=10, int maxUnits=20, int minPop=-1, int maxPop=-1, int numberBuildings=1, bool guessEnemyUnitType=false) { //aiEcho("Initializing unit picker"); // Ensure that we have a most hated enemy when we start this plan mostHatedEnemy(); //Create it. int upID=kbUnitPickCreate(name); if (upID < 0) return(-1); //Default init. //aiEcho("kbUnitPickResetAll " + upID); kbUnitPickResetAll(upID); kbUnitPickSetPreferenceWeight(upID, 1.0); kbUnitPickSetCombatEfficiencyWeight(upID, 1.0); kbUnitPickSetCostWeight(upID, 0.0); //Desired number units types, buildings. kbUnitPickSetDesiredNumberUnitTypes(upID, numberTypes, numberBuildings, true); //Min/Max units and Min/Max pop. kbUnitPickSetMinimumNumberUnits(upID, minUnits); // Sets "need" level on attack plans kbUnitPickSetMaximumNumberUnits(upID, maxUnits); // Sets "max" level on attack plans, sets "numberToMaintain" on train plans for primary unit, // half that for secondary, 1/4 for tertiary, etc. kbUnitPickSetMinimumPop(upID, minPop); // Not sure what this does... kbUnitPickSetMaximumPop(upID, maxPop); // If set, overrides maxNumberUnits for how many of the primary unit to maintain. //Default to land units. kbUnitPickSetEnemyPlayerID(upID, aiGetMostHatedPlayerID()); kbUnitPickSetAttackUnitType(upID, cUnitTypeLogicalTypeLandPickerChoice); kbUnitPickSetGoalCombatEfficiencyType(upID, cUnitTypeLogicalTypeLandPickerTarget); setUnitPickerPreference(upID); // Set generic preferences for this civ //Done. return(upID); } //============================================================================== // updateUnitPickerManually // // This rule is used in conjunction with the betterAttackManager. It will // initialize the unit picker if needed and then update maintain plans based // on the unit picker results. //============================================================================== rule updateUnitPickerManually inactive minInterval 15 { aiEcho("updateUnitPicker: updating"); if(gLandUnitPicker == -1) { gLandUnitPicker = initUnitPicker("Land military units", gMaxUnitLines, 1, 30, -1, -1, 1, true); } setUnitPickerPreference(gLandUnitPicker); kbUnitPickResetResults(gLandUnitPicker); kbUnitPickResetCombatEfficiencyTypes(gLandUnitPicker); kbUnitPickSetEnemyPlayerID(gLandUnitPicker, aiGetMostHatedPlayerID()); kbUnitPickGetCombatEfficiencyTypes(gLandUnitPicker, aiGetMostHatedPlayerID()); int unitTypes = kbUnitPickRun(gLandUnitPicker); int desiredUnitTypes = kbUnitPickGetDesiredNumberUnitTypes(gLandUnitPicker); if(unitTypes < desiredUnitTypes) desiredUnitTypes = unitTypes; int unitCount = kbUnitPickGetMaximumNumberUnits(gLandUnitPicker); int numCappedPlans = 0; for(i = 0; < desiredUnitTypes) { int id = kbUnitPickGetResult(gLandUnitPicker, i); int existingPlan = aiPlanGetIDByTypeAndVariableType(cPlanTrain, cTrainPlanUnitType, id, true); // 6/20/2012 MCC: put caps on siege units int unitCountMax = -1; // rams if(id == kbFindUnit(cUnitTypeUnitTypeSiegeRam1)) { unitCountMax = gBAMMaxNumRams; } // catapults, ballistas, war wagon else if(id == kbFindUnit(cUnitTypeUnitTypeSiegeCatapult1) || id == kbFindUnit(cUnitTypeUnitTypeSiegeBallista1)) { unitCountMax = gBAMMaxNumCatapults; } // palintonons else if(id == kbFindUnit(cUnitTypeUnitTypeSiegeTrebuchet1)) { unitCountMax = gBAMMaxNumTrebuchets; } int planUnitCount = unitCount / (i - numCappedPlans + 1); // if we have a unit count and our desired count for the plan is over that max, cap it. if(unitCountMax != -1 && planUnitCount > unitCountMax) { planUnitCount = unitCountMax; aiEcho("updateUnitPicker: CAPPING[" + kbUnitGetProtoName(id) + "] TO[" + planUnitCount + "]"); numCappedPlans = numCappedPlans + 1; } // maintain plan for this type doesn't exist if(existingPlan == -1) { int planID = createSimpleMaintainPlan(id, planUnitCount, false, kbBaseGetMainID(cMyID), 1); aiEcho("updateUnitPicker: creating maintain plan[" + planID + "] for type[" + kbUnitGetProtoName(id) + "]"); bool success = aiPlanAddUserVariableBool(planID, 0, "BAM Generated", 1); if(success == false) { aiEcho("updateUnitPicker: WEREN'T ABLE TO ADD USER VARIABLE TO MAINTAIN PLAN, FORECASTING WILL BREAK"); } else { aiPlanSetUserVariableBool(planID, 0, 0, true); } } else { aiEcho("updateUnitPicker: plan[" + existingPlan + "] type[" + kbUnitGetProtoName(id) + "] count[" + planUnitCount + "]"); aiPlanSetVariableInt(existingPlan, cTrainPlanNumberToMaintain, 0, planUnitCount); } } } //============================================================================== // rule militaryManager // // ? //============================================================================== rule militaryManager inactive group military minInterval 30 { static bool init = false; // Flag to indicate vars, plans are initialized int i = 0; int proto = 0; int planID = -1; if (init == false) { // Need to initialize, if we're allowed to. if (cvOkToAttack == true && allowAttacksAfterTransports()) { init = true; gLandUnitPicker = initUnitPicker("Land military units", gMaxUnitLines, 1, 30, -1, -1, 1, true); // now the goal int attackPlanID=-1; for (i=0; = 0) return(false); if (kbUnitCount(cMyID, buildingType, cUnitStateAlive) < count) { createSimpleBuildPlan(buildingType, 1, 96, true, escrowID, kbBaseGetMainID(cMyID), 1); return (true); } return(false); } //============================================================================== // rule militaryBuildMonitor // // Make sure the appropriate buildings are built in each age //============================================================================== rule militaryBuildMonitor inactive group military mininterval 3 { bool stop=false; /*if (cvOKToBuild == false) return;*/ //aiEcho("Building stuff!"); // Make sure we're good on pop //if (kbGetPopCap() - kbGetPop() < 10) //return; // Build any buildings in the building list int i=0; for (i = 0; < gMaxBuildings) { int buildingID = xsArrayGetInt(gBuildings, i); int buildingCount = xsArrayGetInt(gBuildingCount, i); int buildingEscrow = xsArrayGetInt(gBuildingEscrow, i); bool buildBuilding = xsArrayGetBool(gBuildingBuild, i); int buildingAgeState = xsArrayGetInt(gBuildingAgeState, i); if (buildingID == -1) break; if(buildingEscrow != cMilitaryEscrowID) continue; if (buildBuilding == false) continue; //aiEcho("" + kbUnitGetProtoName(buildingID) + " " + buildingAgeState + " vs. " + getAgeState()); if (buildingAgeState > getAgeState()) continue; checkAndBuildBuilding(buildingCount, buildingID, buildingEscrow); } } //============================================================================== // updateUnitLines() // // Cap the number of unit lines to our age. // 3/2/2012 MCC: Unit lines per Age is now always equal to the cap, no ifs, ands // or butts (tee hee) //============================================================================== mutable void updateUnitLines() { if (kbGetAge() == cAge1) { gMaxUnitLines = gAge1UnitLineCap; } else if (kbGetAge() == cAge2) { gMaxUnitLines = gAge2UnitLineCap; } else if (kbGetAge() == cAge3) { gMaxUnitLines = gAge3UnitLineCap; } else if (kbGetAge() == cAge4) { gMaxUnitLines = gAge4UnitLineCap; } kbUnitPickSetDesiredNumberUnitTypes(gLandUnitPicker, gMaxUnitLines, 1, true); aiEcho("AI can now build a maximum of " + gMaxUnitLines + " unit lines."); } //============================================================================== // updateMilitarySize() // // Update the army size for the AI to build and set the unit picker to use it. // BF 2/18/2012: Updated from Skirmish: // incorporates the new variables btTargetAge2ArmyCount and btTargetAge3ArmyCount // adjusts system to use the four values and a smoother transition between them // adds handling for High Resource starts //============================================================================== mutable void updateMilitarySize() { // Update our military army size if (gLandUnitPicker != -1) { float temp = btAttacksThisAge - 1; if(temp < 0) temp = 0; int bonus = 0; int armySize = 1; if (kbGetAge() == cAge1) { armySize = btTargetAge1ArmyCount; } if (kbGetAge() == cAge2) { bonus = (temp / btAttackRampUp) * (btTargetAge3ArmyCount - btTargetAge2ArmyCount); armySize = btTargetAge2ArmyCount + bonus; if(armySize > btTargetAge3ArmyCount) armySize = btTargetAge3ArmyCount; } if (kbGetAge() == cAge3) { bonus = (temp / btAttackRampUp) * (btTargetAge4ArmyCount - btTargetAge3ArmyCount); armySize = btTargetAge3ArmyCount + bonus; if(armySize > btTargetAge4ArmyCount) armySize = btTargetAge4ArmyCount; } if (kbGetAge() == cAge4) { armySize = btTargetAge4ArmyCount; } if(btHighStartingResources && kbGetPlayerTeam(cMyID) != 1) { armySize = armySize * 2; } // after getting the raw value, calculate the adjustedArmySize // NOTE: this hook is added to allow for additional training, when required, on a case by case basis. // generally speaking the game under-trains for BAM using AI, this allows us to compensate for that // when the fix is desired or needed. - bfricks 7/3/2012 int adjustedArmySize = armySize * (1.0 + calculateTrainPercentBonus()); aiEcho("updateMilitarySize: armySize[" + armySize + "] adjustedArmySize[" + adjustedArmySize + "]"); armySize = adjustedArmySize; int min = armySize * 0.9; kbUnitPickSetMinimumNumberUnits(gLandUnitPicker, min); // Sets "need" level on attack plans kbUnitPickSetMaximumNumberUnits(gLandUnitPicker, armySize); // Update our number of unit lines updateUnitLines(); } } //============================================================================== // updateBAMGroupSize // // Very similiar to the above function that updates military size. This function // will update the desired group size based on each age's target and the number // of attacks we've made while in this age. //============================================================================== void updateBAMGroupSize() { float temp = btAttacksThisAge - 1; if(temp < 0) temp = 0; int bonus = 0; int groupSize = 1; if (kbGetAge() == cAge1) { groupSize = gBAMTargetAge1GroupSize; } if (kbGetAge() == cAge2) { if(gBAMGroupSizeRampUp > 0) { bonus = (temp / gBAMGroupSizeRampUp) * (gBAMTargetAge3GroupSize - gBAMTargetAge2GroupSize); } groupSize = gBAMTargetAge2GroupSize + bonus; if(groupSize > gBAMTargetAge3GroupSize) groupSize = gBAMTargetAge3GroupSize; } if (kbGetAge() == cAge3) { if(gBAMGroupSizeRampUp > 0) { bonus = (temp / gBAMGroupSizeRampUp) * (gBAMTargetAge4GroupSize - gBAMTargetAge3GroupSize); } groupSize = gBAMTargetAge3GroupSize + bonus; if(groupSize > gBAMTargetAge4GroupSize) groupSize = gBAMTargetAge4GroupSize; } if (kbGetAge() == cAge4) { groupSize = gBAMTargetAge4GroupSize; } gBAMAttackGroupSize = groupSize; } //============================================================================== // betterAttackManager // // Instead of using goal plans and opportunities and all that junk, the BAM // simply uses attack plans and updates a unit picker separately. This allows // for better control over when to send attacks. //============================================================================== rule betterAttackManager inactive minInterval 5 { //BAM needs to wait until we are allowed to attack if(cvOkToAttack == false || allowAttacksAfterTransports() == false) { return; } // first time setting up the array if(gBAMAttackPlanIDArray == -1) { gBAMAttackPlanIDArray = xsArrayCreateInt(gBAMMaxAttackGroups, -1, "Attack Plan Array"); } float time = xsGetTime(); // see if we are passed our initial attack delay if(time < gBAMInitialAttackDelay * 1000) { return; } // time to start a new attack if(time > gBAMLastAttackStartTime + (gBAMAttackInterval * 1000)) { aiEcho("- - - BAM - - - Starting Attack Mission"); btAttacksThisAge = btAttacksThisAge + 1; gBAMLastAttackStartTime = time; gBAMCurrentGroupIndex = 0; updateBAMGroupSize(); updateMilitarySize(); } if(gBAMCurrentGroupIndex != -1) { if(time > gBAMLastGroupSentTime + (gBAMGroupInterval * 1000)) { gBAMLastGroupSentTime = time; // increase unit lines and group size updateMilitarySize(); int unitCount = aiPlanGetNumberUnits(gLandReservePlan, cUnitTypeLogicalTypeLandPickerChoice); aiEcho("- - - BAM - - - Reserve Units " + unitCount + ", Group Size " + gBAMAttackGroupSize + ", Min Units " + (gBAMAttackGroupSize * gBAMMinUnitThreshold)); // check to see if we have enough units to make an attack plan if(unitCount >= gBAMAttackGroupSize * gBAMMinUnitThreshold) { // make a new attack int plan = aiPlanCreate("Attack Plan " + gBAMCurrentGroupIndex, cPlanAttack); // target player int attackPlayerID = aiGetMostHatedPlayerID(); aiPlanSetVariableInt(plan, cAttackPlanPlayerID, 0, attackPlayerID); // set attack aiPlanSetAttack(plan, true); // refresh frequency aiPlanSetVariableInt(plan, cAttackPlanRefreshFrequency, 0, 30000); // set attack and retreat behavior aiPlanSetVariableInt(plan, cAttackPlanBaseAttackMode, 0, gBAMAttackMode); aiPlanSetVariableInt(plan, cAttackPlanRetreatMode, 0, cAttackPlanRetreatModeNone); // target types aiPlanSetNumberVariableValues(plan, cAttackPlanTargetTypeID, 2, false); aiPlanSetVariableInt(plan, cAttackPlanTargetTypeID, 0, cUnitTypeUnit); aiPlanSetVariableInt(plan, cAttackPlanTargetTypeID, 1, cUnitTypeBuilding); // add unit type aiPlanAddUnitType(plan, cUnitTypeLogicalTypeLandPickerChoice, gBAMAttackGroupSize, gBAMAttackGroupSize, gBAMAttackGroupSize); aiPlanSetVariableInt(plan, cAttackPlanPopAmount, 0, gBAMAttackGroupSize); // base stuff int base = kbBaseGetMainID(cMyID); aiPlanSetBaseID(plan, base); aiPlanSetVariableVector(plan, cAttackPlanGatherPoint, 0, kbBaseGetMilitaryGatherPoint(cMyID, base)); aiPlanSetInitialPosition(plan, kbBaseGetMilitaryGatherPoint(cMyID, base)); // attack point, this will only be used if the attack mode selected fails to find anything vector attackPoint = kbBaseGetLocation(attackPlayerID, kbBaseGetMainID(attackPlayerID)); aiEcho("ATTACK POINT " + attackPoint + " BASE ID " + kbBaseGetMainID(attackPlayerID) + " PLAYER ID " + attackPlayerID); aiPlanSetVariableVector(plan, cAttackPlanAttackPoint, 0, attackPoint); // attack point engage range aiPlanSetVariableFloat(plan, cAttackPlanAttackPointEngageRange, 0, gBAMEngageRadius); // route, 2 is random aiPlanSetVariableInt(plan, cAttackPlanAttackRoutePattern, 0, gBAMAttackRoutePattern); // add custom transport pick up loc if we have one if(gBAMTransportPickUpLoc != cInvalidVector) { aiPlanSetVariableVector(plan, cAttackPlanTransportPickUpLocation, 0, gBAMTransportPickUpLoc); } aiPlanSetVariableBool(plan, cAttackPlanForceTransportUsage, 0, gBAMForceTransportUsage); if(gBAMForceTransportUsage == true) { aiPlanSetVariableVector(plan, cAttackPlanTransportDropOffLocation, 0, attackPoint); } // gather distance //aiPlanSetVariableInt(plan, cAttackPlanGatherDistance, 0, 40); // auto use GPs...what are GPs? maybe it is like GPS so the units don't get lost //aiPlanSetVariableInt(plan, cAttackPlanAutoUseGPs, 0, true); // set military aiPlanSetMilitary(plan, true); aiPlanSetEscrowID(plan, cMilitaryEscrowID); // require all needs aiPlanSetRequiresAllNeedUnits(plan, true); // add area groups if naval if (isCurrentMapNaval()) { aiPlanSetVariableInt(plan, cAttackPlanTargetAreaGroups, 0, kbAreaGroupGetIDByPosition(attackPoint)); } // activate aiPlanSetActive(plan); // add the plan to the plan ID array xsArraySetInt(gBAMAttackPlanIDArray, gBAMCurrentGroupIndex, plan); aiEcho("- - - BAM - - - Sending Attack: plan ID [ " + plan + " ]"); // track our successes gBAMCount_AttackFormed = gBAMCount_AttackFormed + 1; aiEcho("- - - BAM - - - SUCCESS COUNT[" + gBAMCount_AttackFormed + "]FAILURE COUNT[" + gBAMCount_NotEnoughUnits + "]"); } else { aiEcho("- - - BAM - - - Not enough units for an attack. unitCount[" + unitCount + "] needed[" + (gBAMAttackGroupSize * gBAMMinUnitThreshold) + "]"); // track our failures gBAMCount_NotEnoughUnits = gBAMCount_NotEnoughUnits + 1; aiEcho("- - - BAM - - - SUCCESS COUNT[" + gBAMCount_AttackFormed + "]FAILURE COUNT[" + gBAMCount_NotEnoughUnits + "]"); } gBAMCurrentGroupIndex = gBAMCurrentGroupIndex + 1; // we've sent our desired number of groups this attack if(gBAMCurrentGroupIndex >= gBAMAttackGroups) { gBAMCurrentGroupIndex = -1; } } } } //============================================================================== // initMilitary() // // Initialize the military behaviors for the AI and enable military rules //============================================================================== mutable void initMilitary() { aiSetAttackResponseDistance(65.0); // Enable military rules xsEnableRuleGroup("military"); // Setup to track who we hate the most mostHatedEnemy(); // Immediately try to find the enemy base // If we know where the enemy base is, go there vector targetLocation = kbBaseGetLocation(aiGetMostHatedPlayerID(), kbBaseGetMainID(aiGetMostHatedPlayerID())); // Nope, go to the mirror position of our base if (xsVectorIsValid(targetLocation) == false) { //aiEcho("Don't know location, so mirroring."); vector myBaseLocation=kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID)); // Main base location...need to find reflection. vector centerOffset = (kbGetMapCenter() - myBaseLocation) * 1.2; targetLocation = kbGetMapCenter() + centerOffset; } //findEnemyBase(targetLocation); //TD: turning off in Age 4 since we show the AI where the player bases are. // See if we have a Transport Pickup Location int pickupUnitID = getUnit(cUnitTypeAITransportPickup, cMyID); if(pickupUnitID != -1) { gBAMTransportPickUpLoc = kbUnitGetPosition(pickupUnitID); aiEcho("initMilitary: Setting Transport PickUp Location to " + kbUnitGetPosition(pickupUnitID)); } // Make sure we're all started up if(gUseBetterAttackManager == true) { xsDisableRule("militaryManager"); xsEnableRule("betterAttackManager"); xsEnableRule("updateUnitPickerManually"); gLandUnitPicker = initUnitPicker("Land military units", gMaxUnitLines, 1, 30, -1, -1, 1, true); } else { militaryManager(); } if (gLandUnitPicker != -1) setUnitPickerPreference(gLandUnitPicker); // Update preferences in case btBiasEtc vars have changed, or cvPrimaryArmyUnit has changed. updateMilitarySize(); } //============================================================================== // forecastMilitaryUnits() // // Add units to the forecast depending on what we want to build. // BF 2/18/2012: Updated from Skirmish: // Remove the forecastMilCap //============================================================================== mutable void forecastMilitaryUnits() { int militaryUnit=-1; int milQty=0; int milDesiredQty=0; int milCurrentQty=0; int i = 0; // Add in forecast for attack goal maintain plans. This is for the dynamic units that the attack // goals want to build as determined by the unit picker. int numAttackGoalMaintainPlans = aiPlanGetAttackGoalMaintainPlanCount(); int maintainPlanID = -1; //aiEcho ("Forecasting military units, number of plans: "+numAttackGoalMaintainPlans); for (i = 0; < numAttackGoalMaintainPlans) { // Get attack goal maintain plan data maintainPlanID = aiPlanGetAttackGoalMaintainPlanIDByIndex(i); militaryUnit = aiPlanGetVariableInt(maintainPlanID, cTrainPlanUnitType, 0); milDesiredQty = aiPlanGetVariableInt(maintainPlanID, cTrainPlanNumberToMaintain, 0); milCurrentQty = aiPlanGetAttackGoalMaintainPlanCurrentNumberUnits(maintainPlanID); milQty = milDesiredQty - milCurrentQty; if (milQty > 0) { if (aiGetNumberTrainers(militaryUnit) > 0) //TD: quick change to only forecast military units that we can currently train { //aiEcho("Adding unit " + milQty + " " + kbUnitGetProtoName(militaryUnit)); addItemToForecasts(militaryUnit, milQty, true); } } } // if we are using BAM, we aren't using goal plans, so the forecasting above won't work if(gUseBetterAttackManager == true) { int plans = aiPlanGetNumber(cPlanTrain, cPlanStateNone, true); for(i = 0; < plans) { int id = aiPlanGetIDByIndex(cPlanTrain, cPlanStateNone, true, i); // do this check to see if we have added the BAM Generated user variable to the train plan if(aiPlanGetUserVariableBool(id, 0, 0) == true) { militaryUnit = aiPlanGetVariableInt(id, cTrainPlanUnitType, 0); milDesiredQty = aiPlanGetVariableInt(id, cTrainPlanNumberToMaintain, 0); milCurrentQty = getUnitCountByLocation(militaryUnit); milQty = milDesiredQty - milCurrentQty; //aiEcho("militaryUnit: "+militaryUnit+" milDesiredQty: "+milDesiredQty+" milCurrentQty: "+milCurrentQty); if (milQty > 0) { if (aiGetNumberTrainers(militaryUnit) > 0) //TD: quick change to only forecast military units that we can currently train { //aiEcho("Adding unit " + milQty + " " + kbUnitGetProtoName(militaryUnit)); addItemToForecasts(militaryUnit, milQty, true); } } } } } // Add in forecast for active progression plans. This is for the dynamic stuff that the attack // goals want to build, plus any other progression plans made in script int numProgressions = aiPlanGetActiveProgressionPlanCount(); int progressionType = -1; int progressionID = -1; int planID = -1; int escrowID = -1; //int i = 0; for (i = 0; < numProgressions) { // Get progression plan data planID = aiPlanGetActiveProgressionPlanIDByIndex(i); progressionType = aiPlanGetVariableInt(planID, cProgressionPlanCurrentGoalType, 0); progressionID = aiPlanGetVariableInt(planID, cProgressionPlanCurrentGoalID, 0); escrowID = aiPlanGetEscrowID(planID); if (progressionType == cProgressionTypeUnit) { if (kbUnitCount(cMyID, progressionID, cUnitStateAlive) < 1) //TD: This is a hack to prevent forecasting military buildings that are already built. { //aiEcho("FORECAST Progression: Adding unit " + kbUnitGetProtoName(progressionID) + " (id=" + progressionID + ")"); addItemToForecasts(progressionID, 1, (escrowID == cMilitaryEscrowID)); } } else if (progressionType == cProgressionTypeTech) { //aiEcho("FORCAST Progression: Adding tech " + kbTechGetName(progressionID) + " (id=" + progressionID + ")"); addTechToForecasts(progressionID, (escrowID == cMilitaryEscrowID)); } } } //============================================================================== // moveDefenseReflex(vector, radius) // // Move the defend and reserve plans to the specified location // Sets the gLandDefensePlan to a high pop count, so it steals units from the reserve plan, // which will signal the AI to not start new attacks as no reserves are available. //============================================================================== void moveDefenseReflex(vector location=cInvalidVector, float radius=-1.0) { //aiEcho("moveDefenseReflex"); if (radius < 0.0) radius = cvDefenseReflexRadiusActive; if (location != cInvalidVector) { aiPlanSetVariableVector(gLandDefensePlan, cDefendPlanDefendPoint, 0, location); aiPlanSetVariableFloat(gLandDefensePlan, cDefendPlanEngageRange, 0, radius); aiPlanSetVariableFloat(gLandDefensePlan, cDefendPlanGatherDistance, 0, radius - 10.0); aiPlanAddUnitType(gLandDefensePlan, cUnitTypeLogicalTypeLandMilitary, 0, 5, 200); aiPlanSetVariableVector(gLandReservePlan, cDefendPlanDefendPoint, 0, location); aiPlanSetVariableFloat(gLandReservePlan, cDefendPlanEngageRange, 0, radius); aiPlanSetVariableFloat(gLandReservePlan, cDefendPlanGatherDistance, 0, radius - 10.0); aiPlanAddUnitType(gLandReservePlan, cUnitTypeLogicalTypeLandMilitary, 0, 0, 1); gDefenseReflex = true; } //aiEcho("******** Defense reflex moved with radius "+radius+" and location "+location); } //============================================================================== // endDefenseReflex() // // Move the defense location back to the main base, and move the defend // and reserve plans back to their default locations. //============================================================================== void endDefenseReflex() { vector resLoc = kbBaseGetMilitaryGatherPoint(cMyID, kbBaseGetMainID(cMyID)); vector defLoc = kbBaseGetLocation(cMyID,kbBaseGetMainID(cMyID)); if ( resLoc == cInvalidVector ) resLoc = defLoc; // [6/2/2009 CJS] TODO Put this back when/if we do forward bases /*if ( gForwardBaseState != cForwardBaseStateNone ) { resLoc = gForwardBaseLocation; defLoc = gForwardBaseLocation; }*/ aiPlanSetVariableVector(gLandDefensePlan, cDefendPlanDefendPoint, 0, defLoc); // Main base or forward base (if forward base exists) aiPlanSetVariableFloat(gLandDefensePlan, cDefendPlanEngageRange, 0, cvDefenseReflexRadiusActive); aiPlanSetVariableFloat(gLandDefensePlan, cDefendPlanGatherDistance, 0, cvDefenseReflexRadiusActive - 10.0); aiPlanAddUnitType(gLandDefensePlan, cUnitTypeLogicalTypeLandMilitary, 0, 0, 1); // Defend plan will use 1 unit to defend against stray snipers, etc. aiPlanSetVariableVector(gLandReservePlan, cDefendPlanDefendPoint, 0, resLoc); aiPlanSetVariableFloat(gLandReservePlan, cDefendPlanEngageRange, 0, cvDefenseReflexRadiusPassive); // Small radius aiPlanSetVariableFloat(gLandReservePlan, cDefendPlanGatherDistance, 0, cvDefenseReflexRadiusPassive - 10.0); aiPlanAddUnitType(gLandReservePlan, cUnitTypeLogicalTypeLandMilitary, 0, 5, 200); // All unused troops //aiEcho("******** Defense reflex terminated"); //aiEcho("******** Returning to "+resLoc); //aiEcho(" Forward base ID is "+gForwardBaseID+", location is "+gForwardBaseLocation); gDefenseReflex = false; } //============================================================================== // rule endDefenseReflexDelay // // Used to delay ending the defense reflex so that new BaseIDs are available. //============================================================================== rule endDefenseReflexDelay inactive minInterval 1 { xsDisableSelf(); endDefenseReflex(); } //============================================================================== // numEnemiesInBase // BF 2/18/2012: Updated from Skirmish: // updates the type of units being counted to match standard victory conditions // adds handling for a baseless enemy //============================================================================== mutable int numEnemiesInBase(int playerID=-1, int baseID=-1) { if (playerID == -1 || baseID == -1) return (0); // Create our basic enemy army query if needed if (gEnemyArmyQuery < 0) { gEnemyArmyQuery = kbUnitQueryCreate("Enemy army query"); kbUnitQuerySetIgnoreKnockedOutUnits(gEnemyArmyQuery, true); kbUnitQuerySetPlayerRelation(gEnemyArmyQuery, cPlayerRelationEnemyNotGaia); kbUnitQuerySetUnitType(gEnemyArmyQuery, cUnitTypeLogicalTypeNeededForVictory); kbUnitQuerySetState(gEnemyArmyQuery, cUnitStateAlive); kbUnitQuerySetSeeableOnly(gEnemyArmyQuery, true); // Ignore units we think are under fog } // See if the main is under attack kbUnitQuerySetPosition(gEnemyArmyQuery, kbBaseGetLocation(playerID, baseID)); kbUnitQuerySetMaximumDistance(gEnemyArmyQuery, cvDefenseReflexSearchRadius); if(gNoEnemyBases) { kbUnitQuerySetSeeableOnly(gEnemyArmyQuery, false); } else { kbUnitQuerySetSeeableOnly(gEnemyArmyQuery, true); } kbUnitQuerySetState(gEnemyArmyQuery, cUnitStateAlive); kbUnitQueryResetResults(gEnemyArmyQuery); return (kbUnitQueryExecute(gEnemyArmyQuery)); } //============================================================================== // rule defenseReflex // // Check on the status of the main base and all secondary bases. If enemy // troops are found, take my standing military units and defend the base. // If the enemy has more troops available than I do, cancel any attack missions // and also use those troops to help defend the base. // BF 2/18/2012: Updated from Skirmish: // adjusts interval duration // adds handling for a baseless enemy //============================================================================== rule defenseReflex inactive minInterval 11 group military { //aiEcho("Defense reflex"); int enemiesToAttack = 2; if(gNoEnemyBases) { enemiesToAttack = 1; } if ( cvOKToDefend == false ) return; // Current place we should defend int largestEnemyArmy = -1; vector defenseTarget = cInvalidVector; // How big is my army int armySize = aiPlanGetNumberUnits(gLandDefensePlan, cUnitTypeLogicalTypeLandMilitary) + aiPlanGetNumberUnits(gLandReservePlan, cUnitTypeLogicalTypeLandMilitary); int enemyArmySize = numEnemiesInBase(cMyID, kbBaseGetMainID(cMyID)); if (enemyArmySize >= enemiesToAttack) { largestEnemyArmy = enemyArmySize * 2; // Multiply to give more weight to defending our main base defenseTarget = kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID)); //aiEcho("******** Main base ("+kbBaseGetMainID(cMyID)+") under attack."); //aiEcho("******** Enemy count "+enemyArmySize+", my army count "+armySize); } // Nope, check any secondary bases int numBases = kbBaseGetNumber(cMyID); int i = 0; int baseID = -1; int iBase = 0; //aiEcho("I have " + numBases + " base(s)."); for (i = 0; < numBases) { baseID = kbBaseGetIDByIndex(cMyID, i); // Don't check the main base again if (baseID == kbBaseGetMainID(cMyID)) continue; //aiEcho("Checking base " + i); enemyArmySize = numEnemiesInBase(cMyID, baseID); //aiEcho("There are " + enemyArmySize + " enemy units at this base."); if (enemyArmySize >= enemiesToAttack) { if (enemyArmySize > largestEnemyArmy) { //aiEcho("******** Secondary base ("+i+") under attack."); //aiEcho("******** Enemy count "+enemyArmySize+", my army count "+armySize); largestEnemyArmy = enemyArmySize; defenseTarget = kbBaseGetLocation(cMyID, baseID); } } } // If our army is smaller than the enemy army, see if there are any attacks or defends we can cancel if (largestEnemyArmy > 0) { moveDefenseReflex(defenseTarget, cvDefenseReflexRadiusActive); if (armySize < largestEnemyArmy) { //aiEcho("Uh-oh, the enemy has more troops than I do."); int plan = -1; int pri = -1; int oppID = -1; int oppType = -1; int planCount = aiPlanGetNumber(cPlanMission, cPlanStateWorking, true); //if (planCount > 0) //aiEcho("I have "+planCount+" missions--maybe I can cancel one?"); for (i=0; < planCount) { plan = aiPlanGetIDByIndex(cPlanMission, cPlanStateWorking, true, i); if (plan < 0) continue; oppID = aiPlanGetVariableInt(plan, cMissionPlanOpportunityID, 0); oppType = aiGetOpportunityType(oppID); if (oppType != cOpportunityTypeDestroy && oppType != cOpportunityTypeDefend) continue; // Make sure it was auto-generated, otherwise this was a trigger or something pri = aiGetOpportunitySourceType(oppID); if (pri == cOpportunitySourceAutoGenerated) { // Destroying the plan makes the troops available, so the defense plan will // grab them. //aiEcho("Cancelling plan "+plan); aiPlanDestroy(plan); } } } } // Nothing going on, see if an ally base is under attack. else if (cvGuardAlliedBases) { //aiEcho("Checking allied bases"); for (i = 1; = 2) { if (enemyArmySize > largestEnemyArmy) { //aiEcho("******** Ally base ("+i+") under attack."); //aiEcho("******** Enemy count "+enemyArmySize+", my army count "+armySize); largestEnemyArmy = enemyArmySize; defenseTarget = kbBaseGetLocation(i, baseID); } } } } } if (largestEnemyArmy > 0) { moveDefenseReflex(defenseTarget, cvDefenseReflexRadiusActive); } else if(gDefenseReflex == true) { endDefenseReflex(); } } // Nothing going on. If we're still in a defensive stance, drop us out else if (gDefenseReflex == true) { endDefenseReflex(); } } //============================================================================== // rule createDefensePlan // // Create a defense and reserve plan for the main base // BF 2/18/2012: Updated from Skirmish: // converts over to the new unit picker logical types //============================================================================== rule createDefensePlan inactive group military minInterval 13 { if (gLandDefensePlan < 0) { // Create the defense plan. This is the plan for troops that are defending our bases and buildings. gLandDefensePlan = aiPlanCreate("Primary Land Defend", cPlanDefend); aiPlanAddUnitType(gLandDefensePlan, cUnitTypeLogicalTypeLandPickerChoice , 0, 0, 1); // Small, until defense reflex aiPlanSetVariableVector(gLandDefensePlan, cDefendPlanDefendPoint, 0, kbBaseGetMilitaryGatherPoint(cMyID, kbBaseGetMainID(cMyID))); aiPlanSetVariableFloat(gLandDefensePlan, cDefendPlanEngageRange, 0, cvDefenseReflexRadiusActive); aiPlanSetVariableFloat(gLandDefensePlan, cDefendPlanGatherDistance, 0, cvDefenseReflexRadiusActive - 10.0); aiPlanSetVariableBool(gLandDefensePlan, cDefendPlanPatrol, 0, false); aiPlanSetInitialPosition(gLandDefensePlan, kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID))); aiPlanSetUnitStance(gLandDefensePlan, cUnitStanceDefensive); aiPlanSetVariableInt(gLandDefensePlan, cDefendPlanRefreshFrequency, 0, 5); aiPlanSetVariableInt(gLandDefensePlan, cDefendPlanAttackTypeID, 0, cUnitTypeUnit); // Only units aiPlanSetDesiredPriority(gLandDefensePlan, 10); // Very low priority, don't steal from attack plans aiPlanSetActive(gLandDefensePlan); //aiEcho("Creating primary land defend plan"); // Create the reserve plan. This is the plan that holds all currently unused military units at our base. gLandReservePlan = aiPlanCreate("Land Reserve Units", cPlanDefend); aiPlanAddUnitType(gLandReservePlan, cUnitTypeLogicalTypeLandPickerChoice , 0, 5, 200); // All mil units, high MAX value to suck up all excess aiPlanSetVariableVector(gLandReservePlan, cDefendPlanDefendPoint, 0, kbBaseGetMilitaryGatherPoint(cMyID, kbBaseGetMainID(cMyID))); if (kbBaseGetMilitaryGatherPoint(cMyID, kbBaseGetMainID(cMyID)) == cInvalidVector) if (getUnit(cUnitTypeAIStart, cMyID) >= 0) // If no mil gather point, but there is a start block, use it. aiPlanSetVariableVector(gLandReservePlan, cDefendPlanDefendPoint, 0, kbUnitGetPosition(getUnit(cUnitTypeAIStart, cMyID))); else aiPlanSetVariableVector(gLandReservePlan, cDefendPlanDefendPoint, 0, kbUnitGetCentroid(cUnitTypeLogicalTypeLandMilitary)); if (aiPlanGetVariableVector(gLandReservePlan, cDefendPlanDefendPoint, 0) == cInvalidVector) // If all else failed, use the average location. aiPlanSetVariableVector(gLandReservePlan, cDefendPlanDefendPoint, 0, kbUnitGetCentroid(cUnitTypeLogicalTypeLandMilitary)); aiPlanSetVariableFloat(gLandReservePlan, cDefendPlanEngageRange, 0, cvDefenseReflexRadiusPassive); // Loose aiPlanSetVariableBool(gLandReservePlan, cDefendPlanPatrol, 0, false); aiPlanSetVariableFloat(gLandReservePlan, cDefendPlanGatherDistance, 0, cvDefenseReflexRadiusPassive - 10.0); aiPlanSetInitialPosition(gLandReservePlan, kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID))); aiPlanSetUnitStance(gLandReservePlan, cUnitStanceDefensive); aiPlanSetVariableInt(gLandReservePlan, cDefendPlanRefreshFrequency, 0, 5); aiPlanSetVariableInt(gLandReservePlan, cDefendPlanAttackTypeID, 0, cUnitTypeUnit); // Only units aiPlanSetDesiredPriority(gLandReservePlan, 5); // Very very low priority, gather unused units. aiPlanSetActive(gLandReservePlan); if (gMainAttackPlan >= 0) aiPlanSetVariableInt(gMainAttackPlan, cGoalPlanReservePlanID, 0, gLandReservePlan); //aiEcho("Creating reserve plan"); xsEnableRule("endDefenseReflexDelay"); // Reset to relaxed stances after plans have a second to be created. xsDisableSelf(); } } //============================================================================== // towerManager // // BF 2/18/2012: Updated from Skirmish: // updates the tower manager to places towers...better than before. //============================================================================== rule towerManager active minInterval 30 { if (cvNumTowers == 0 || gTower == -1) { xsDisableSelf(); return; // Oops. I shouldn't be running. } int towerCount = cTowerCount; if (towerCount >= cvNumTowers || kbGetAge() < cvMinTowerAge) return; // We have enough, thank you, and no idle outpost wagons. //if (aiPlanGetIDByTypeAndVariableType(cPlanBuild, cBuildPlanBuildingTypeID, gTower) >= 0) // return; // We're already building one. calculateEnemyDirection(); // Need more, not currently building any. Need to select a location. int attempt = 0; vector testVec = cInvalidVector; float spacingDistance = 30;//kbBaseGetSize(cMyID, kbBaseGetMainID(cMyID)); float exclusionRadius = spacingDistance / 2.0; float dx = spacingDistance; float dz = spacingDistance; vector direction = cEnemyDirection; bool success = false; //aiEcho("SPACING" + spacingDistance); if(towerCount > 7) { towerCount = towerCount - 8; spacingDistance = 45; exclusionRadius = 15; //0.9238, 0.3827 lets do some trig vector temp = cEnemyDirection; direction = xsVectorSetX(direction, xsVectorGetX(direction) + (0.866 * xsVectorGetX(temp)) + (0.5 * xsVectorGetZ(temp))); direction = xsVectorSetZ(direction, xsVectorGetZ(direction) + (-0.5 * xsVectorGetX(temp)) + (0.866 * xsVectorGetZ(temp))); direction = xsVectorNormalize(direction); } for (attempt = 0; < 10) // Take ten tries to place it { vector base = kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID)); // Start with base location switch(towerCount) { case 0: { testVec = xsVectorSetX(testVec, xsVectorGetX(base) + (xsVectorGetX(direction) * spacingDistance)); testVec = xsVectorSetZ(testVec, xsVectorGetZ(base) + (xsVectorGetZ(direction) * spacingDistance)); break; } case 3: { testVec = xsVectorSetX(testVec, xsVectorGetX(base) + (xsVectorGetZ(direction) * -1 * spacingDistance)); testVec = xsVectorSetZ(testVec, xsVectorGetZ(base) + (xsVectorGetX(direction) * spacingDistance)); break; } case 4: { testVec = xsVectorSetX(testVec, xsVectorGetX(base) + (xsVectorGetZ(direction) * spacingDistance)); testVec = xsVectorSetZ(testVec, xsVectorGetZ(base) + (xsVectorGetX(direction) * -1 * spacingDistance)); break; } case 5: { testVec = xsVectorSetX(testVec, xsVectorGetX(base) + (xsVectorGetX(direction) * -1 * spacingDistance)); testVec = xsVectorSetZ(testVec, xsVectorGetZ(base) + (xsVectorGetZ(direction) * -1 * spacingDistance)); break; } case 1: { testVec = xsVectorSetX(testVec, xsVectorGetX(base) + (xsVectorGetX(direction) * spacingDistance * 0.6) + (xsVectorGetZ(direction) * -1 * spacingDistance * 0.6)); testVec = xsVectorSetZ(testVec, xsVectorGetZ(base) + (xsVectorGetZ(direction) * spacingDistance * 0.65) + (xsVectorGetX(direction) * spacingDistance * 0.6)); break; } case 2: { testVec = xsVectorSetX(testVec, xsVectorGetX(base) + (xsVectorGetX(direction) * spacingDistance * 0.6) + (xsVectorGetZ(direction) * spacingDistance * 0.6)); testVec = xsVectorSetZ(testVec, xsVectorGetZ(base) + (xsVectorGetZ(direction) * spacingDistance * 0.6) + (xsVectorGetX(direction) * -1 * spacingDistance * 0.6)); break; } case 6: { testVec = xsVectorSetX(testVec, xsVectorGetX(base) + (xsVectorGetX(direction) * -1 * spacingDistance * 0.6) + (xsVectorGetZ(direction) * -1 * spacingDistance * 0.6)); testVec = xsVectorSetZ(testVec, xsVectorGetZ(base) + (xsVectorGetZ(direction) * -1 * spacingDistance * 0.6) + (xsVectorGetX(direction) * spacingDistance * 0.6)); break; } case 7: { testVec = xsVectorSetX(testVec, xsVectorGetX(base) + (xsVectorGetX(direction) * -1 * spacingDistance * 0.6) + (xsVectorGetZ(direction) * spacingDistance * 0.6)); testVec = xsVectorSetZ(testVec, xsVectorGetZ(base) + (xsVectorGetZ(direction) * -1 * spacingDistance * 0.6) + (xsVectorGetX(direction) * -1 * spacingDistance * 0.6)); break; } } //aiEcho("Testing tower location "+testVec); if (gTowerSearch < 0) { // init gTowerSearch = kbUnitQueryCreate("Tower placement search"); kbUnitQuerySetPlayerRelation(gTowerSearch, cPlayerRelationAny); kbUnitQuerySetUnitType(gTowerSearch, gTower); kbUnitQuerySetState(gTowerSearch, cUnitStateABQ); } kbUnitQuerySetPosition(gTowerSearch, testVec); kbUnitQuerySetMaximumDistance(gTowerSearch, exclusionRadius); kbUnitQueryResetResults(gTowerSearch); if (kbUnitQueryExecute(gTowerSearch) < 1) { // Site is clear, use it //if ( kbAreaGroupGetIDByPosition(testVec) == kbAreaGroupGetIDByPosition(kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID))) ) //{ // Make sure it's in same areagroup. float heightDiff = kbGetHeightAtXZ(xsVectorGetX(base), xsVectorGetZ(base)) - kbGetHeightAtXZ(xsVectorGetX(testVec), xsVectorGetZ(testVec)); //aiEcho("HEIGHT DIFF: " + heightDiff); if(heightDiff < 0) { heightDiff = heightDiff * -1; } if(heightDiff < 8) { success = true; break; } //} } spacingDistance = spacingDistance * 1.05; } // We have found a location (success == true) or we need to just do a brute force placement around the TC. if (success == false) { testVec = kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID)); aiEcho("PLACEMENT FAILED"); } int buildPlan=aiPlanCreate("Tower build plan ", cPlanBuild); // What to build aiPlanSetVariableInt(buildPlan, cBuildPlanBuildingTypeID, 0, gTower); // Priority. aiPlanSetDesiredPriority(buildPlan, 95); // Econ, because mil doesn't get enough wood. aiPlanSetMilitary(buildPlan, false); aiPlanSetEconomy(buildPlan, true); // Escrow. aiPlanSetEscrowID(buildPlan, cEconomyEscrowID); // Builders. aiPlanAddUnitType(buildPlan, gEconUnit, 1, 1, 1); // Instead of base ID or areas, use a center position and falloff. aiPlanSetVariableVector(buildPlan, cBuildPlanCenterPosition, 0, testVec); if (success == true) aiPlanSetVariableFloat(buildPlan, cBuildPlanCenterPositionDistance, 0, exclusionRadius); else aiPlanSetVariableFloat(buildPlan, cBuildPlanCenterPositionDistance, 0, 50.0); // Add position influence for nearby towers //aiPlanSetVariableInt(buildPlan, cBuildPlanInfluenceUnitTypeID, 0, gTower); // Russian's won't notice ally towers and vice versa...oh well. //aiPlanSetVariableFloat(buildPlan, cBuildPlanInfluenceUnitDistance, 0, spacingDistance); //aiPlanSetVariableFloat(buildPlan, cBuildPlanInfluenceUnitValue, 0, -20.0); // -20 points per tower //aiPlanSetVariableInt(buildPlan, cBuildPlanInfluenceUnitFalloff, 0, cBPIFalloffLinear); // Linear slope falloff // Weight it to stay very close to center point. aiPlanSetVariableVector(buildPlan, cBuildPlanInfluencePosition, 0, testVec); // Position influence for landing position aiPlanSetVariableFloat(buildPlan, cBuildPlanInfluencePositionDistance, 0, exclusionRadius); // 100m range. aiPlanSetVariableFloat(buildPlan, cBuildPlanInfluencePositionValue, 0, 10.0); // 10 points for center aiPlanSetVariableInt(buildPlan, cBuildPlanInfluencePositionFalloff, 0, cBPIFalloffLinear); // Linear slope falloff aiPlanSetVariableBool(buildPlan, cBuildPlanNoFailOutOfUnits, 0, true); aiPlanSetAllowHandleDamage(buildPlan, false); //aiEcho("Starting building plan ("+buildPlan+") for tower at location "+testVec); aiPlanSetActive(buildPlan); cTowerCount = cTowerCount + 1; } //============================================================================== // resignHandler() // // Handles the response from the resign message box //============================================================================== void resignHandler(int result =-1) { aiEcho("***************** Resign handler running with result "+result); if (result == 0) { xsEnableRule("resignRetry"); return; } //aiEcho("Resign handler returned "+result); aiResign(); return; } //============================================================================== // rule ShouldIResign // // Determine if the AI should resign //============================================================================== rule ShouldIResign minInterval 7 active { static bool hadHumanAlly = false; // Early out if we're not allowed to think about this. if (cvOkToResign == false) { return; } // Don't resign if you have a human ally that's still in the game int i = 0; bool humanAlly = false; // Set true if we have a surviving human ally. int humanAllyID = -1; bool wasHumanInGame = false; // Set true if any human players were in the game bool isHumanInGame = false; // Set true if a human survives. If one existed but none survive, resign. // Look for humans for (i=1; <=cNumberPlayers) { if (kbIsPlayerHuman(i) == true) { wasHumanInGame = true; if (kbHasPlayerLost(i) == false) isHumanInGame = true; } if ((kbIsPlayerAlly(i) == true) && (kbHasPlayerLost(i) == false) && (kbIsPlayerHuman(i) == true)) { humanAlly = true; // Don't return just yet, let's see if we should chat. hadHumanAlly = true; // Set flag to indicate that we once had a human ally. humanAllyID = i; // Player ID of lowest-numbered surviving human ally. } } // Resign if my human allies have quit. if ((hadHumanAlly == true) && (humanAlly == false)) { //aiResign(); // If there are no humans left, and this wasn't a bot battle from the start, quit. aiEcho("Resign: no human allies SP version"); aiResign(); // I had a human ally or allies, but do not any more. Our team loses. return; // Probably not necessary, but whatever... } // Check for MP with human allies gone. This trumps the OkToResign setting, below. if ((aiIsMultiplayer() == true) && (hadHumanAlly == true) && (humanAlly == false)) { // In a multiplayer game...we had a human ally earlier, but none remain. Resign immediately aiEcho("Resign: no human allies MP version"); aiResign(); // Don't ask, just quit. xsEnableRule("resignRetry"); xsDisableSelf(); return; } } //============================================================================== // rule resignRetry // // Re-enable the resign rule after 4 minutes //============================================================================== rule resignRetry inactive minInterval 240 { aiEcho("resignRetry"); xsEnableRule("ShouldIResign"); xsDisableSelf(); } //============================================================================== // rule researchUpgrades // // Check to see if we can research any upgrades for the units we're building. //============================================================================== rule researchMilitaryUpgrades inactive group military minInterval 30 { if (cvOKToResearchMilitary == false) { xsDisableSelf(); return; } // BF: 2/18/2012: this check is not going to work with the current AI scripts nor will it be desired // // If we're not rushing and we're not at max age, don't research techs // if (btRushBoom > -0.5 && kbGetAge() < cvMaxAge) // return; if (gMilitaryUpgradePlan > 0) { // Make sure we don't already have an upgrade plan running. int planState = aiPlanGetState(gMilitaryUpgradePlan); if (planState > 0) return; else { aiPlanDestroy(gMilitaryUpgradePlan); gMilitaryUpgradePlan = -1; } } int numUnits = kbUnitPickGetNumberResults(gLandUnitPicker); int i = 0; // Cap the number of units we look at so we don't go too far on research (we only build the first two // unit types, anyway). if (numUnits > 2) numUnits = 2; for (i = 0; < numUnits) { int unitID = kbUnitPickGetResult(gLandUnitPicker, i); if (unitID > 0) { int techID = kbTechTreeGetCheapestUnitUpgrade(unitID); if (techID > 0) { //aiEcho("E"); gMilitaryUpgradePlan = createSimpleResearchPlan(techID, -1, cMilitaryEscrowID, 75); return; } } } // Do research for towers if (gTower == -1 || cvNumTowers == 0) return; techID = kbTechTreeGetCheapestUnitUpgrade(gTower); if (techID > 0) { gMilitaryUpgradePlan = createSimpleResearchPlan(techID, -1, cMilitaryEscrowID, 75); return; } } //============================================================================== // rule delayWalls // // Create a plan to wall in our base //============================================================================== /*rule delayWalls inactive group military minInterval 10 { if ( (kbGetPopCap()-kbGetPop()) < 20 ) return; // Don't start walls until we have pop room int wallPlanID=aiPlanCreate("WallInBase", cPlanBuildWall); if (wallPlanID != -1) { float baseSize = kbBaseCalculateRadius(cMyID, kbBaseGetMainID(cMyID)) + 10.0; // Invalid base, bail if (baseSize == 0.0) return; aiEcho("Building wall--radius "+baseSize); aiPlanSetVariableInt(wallPlanID, cBuildWallPlanWallType, 0, cBuildWallPlanWallTypeRing); aiPlanAddUnitType(wallPlanID, gEconUnit, 0, 1, 1); aiPlanSetVariableVector(wallPlanID, cBuildWallPlanWallRingCenterPoint, 0, kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID))); aiPlanSetVariableFloat(wallPlanID, cBuildWallPlanWallRingRadius, 0, baseSize); aiPlanSetVariableInt(wallPlanID, cBuildWallPlanNumberOfGates, 0, 2); aiPlanSetBaseID(wallPlanID, kbBaseGetMainID(cMyID)); aiPlanSetEscrowID(wallPlanID, cEconomyEscrowID); aiPlanSetDesiredPriority(wallPlanID, 80); aiPlanSetActive(wallPlanID, true); //Enable our wall gap rule, too. xsEnableRule("fillInWallGaps"); aiEcho("Enabling Wall Plan for Base ID: "+kbBaseGetMainID(cMyID)); } xsDisableSelf(); }*/ //============================================================================== // rule fillInWallGaps // // Check our walls and fill in any gaps //============================================================================== /*rule fillInWallGaps minInterval 31 inactive { //If we're not building walls, go away. if (gBuildWalls == false) { xsDisableSelf(); return; } //If we already have a build wall plan, don't make another one. if(aiPlanGetIDByTypeAndVariableType(cPlanBuildWall, cBuildWallPlanWallType, cBuildWallPlanWallTypeRing, true) >= 0) return; int wallPlanID=aiPlanCreate("FillInWallGaps", cPlanBuildWall); if (wallPlanID != -1) { aiPlanSetVariableInt(wallPlanID, cBuildWallPlanWallType, 0, cBuildWallPlanWallTypeRing); aiPlanAddUnitType(wallPlanID, gEconUnit, 1, 1, 1); aiPlanSetVariableVector(wallPlanID, cBuildWallPlanWallRingCenterPoint, 0, kbBaseGetLocation(cMyID, kbBaseGetMainID(cMyID))); aiPlanSetVariableFloat(wallPlanID, cBuildWallPlanWallRingRadius, 0, 30.0); aiPlanSetVariableInt(wallPlanID, cBuildWallPlanNumberOfGates, 0, 2); aiPlanSetBaseID(wallPlanID, kbBaseGetMainID(cMyID)); aiPlanSetEscrowID(wallPlanID, cEconomyEscrowID); aiPlanSetDesiredPriority(wallPlanID,80); aiPlanSetActive(wallPlanID, true); } } */ //============================================================================== // unpackPalintonons // // If we detect that our most hated player has no main base, we'll try to assign one. //============================================================================== rule updateMainBaseForHatedPlayer active minInterval 23 { int playerID = aiGetMostHatedPlayerID(); if(playerID == -1) { aiEcho("updateMainBaseForHatedPlayer: no hated player"); } else if(kbBaseGetMainID(playerID) == -1) { aiEcho("updateMainBaseForHatedPlayer: Player " + playerID + " has no MAIN BASE ID"); int baseCount = kbBaseGetNumber(playerID); if(baseCount == 0) { aiEcho("updateMainBaseForHatedPlayer: Player " + playerID + " has no more bases"); } else { bool baseSet = false; for(i = 0; < baseCount) { int id = kbBaseGetIDByIndex(playerID, i); if(kbBaseGetNumberUnits(playerID, id, cPlayerRelationEnemyNotGaia, cUnitTypeUnitTypeBldgTownCenter) > 0) { aiEcho("updateMainBaseForHatedPlayer: Setting base " + id + " as the MAIN BASE"); kbBaseSetMain(playerID, id, true); baseSet = true; } } if(baseSet == false) { aiEcho("updateMainBaseForHatedPlayer: NO TOWN CENTERS! Setting base " + id + " as the MAIN BASE"); kbBaseSetMain(playerID, 0, true); } } } } //============================================================================== // unpackPalintonons // // BF 2/18/2012: Updated from Skirmish: // manage palintonon unpacking //============================================================================== rule unpackPalintonons active minInterval 19 { if(gPalintononQuery == -1) { gPalintononQuery = kbUnitQueryCreate("Palintonon Query"); kbUnitQuerySetPlayerID(gPalintononQuery, cMyID); kbUnitQuerySetUnitType(gPalintononQuery, cUnitTypeUnitTypeSiegeTrebuchet1); kbUnitQuerySetState(gPalintononQuery, cUnitStateAlive); } kbUnitQueryResetResults(gPalintononQuery); int palCount = kbUnitQueryExecute(gPalintononQuery); //aiEcho("palintonons: " + palCount); for(i = 0; < palCount) { int ID = kbUnitQueryGetResult(gPalintononQuery, i); int tactic = aiUnitGetTactic(ID); aiEcho("Tactic: " + tactic); // MCC 9/18/2012 These tactic IDs changed, but I'm not sure why. Fricks told me not to worry about it // and maybe someday we'll get a proper way to pack/unpack palintonons. if(tactic == 3) { //int units = getUnitCountByLocation(cUnitTypeLogicalTypeLandPickerTarget, cPlayerRelationEnemyNotGaia, cUnitStateAlive, kbUnitGetPosition(ID), 60.0); //aiEcho("Units: " + units); //if(units == 0) //{ // aiUnitSetTactic(ID, 3); //} if(kbUnitGetTargetUnitID(ID) == -1) { aiUnitSetTactic(ID, 4); } } } } //============================================================================== // checkForEnemyBases // // BF 2/18/2012: Updated from Skirmish: // Adds handling for no enemy base situations //============================================================================== rule checkForEnemyBases active minInterval 19 { int enemyBases = 0; int i = 0; for (i = 0; 0) { // if this is a new location, echo away if (distance(testLOC, aiPlanGetVariableVector(planID, cDefendPlanDefendPoint, 0)) > 1) { //echo away aiEcho("SpawnedUnitDefense: --- plan[" + aiPlanGetName(planID) + "] new Goal[" + testLOC + "]"); } // update the plan aiPlanSetInitialPosition(planID, testLOC); aiPlanSetVariableVector(planID, cDefendPlanDefendPoint, 0, testLOC); // if we tested with a radius different than the engage radius, force an update to the engage radius float engageRadius = aiPlanGetVariableFloat(planID, cDefendPlanEngageRange, 0); if ( (radius > engageRadius) || (radius < engageRadius) ) { aiEcho("SpawnedUnitDefense: --- plan[" + aiPlanGetName(planID) + "] adjusting engage radius/ gather radius from[" + engageRadius + "] to[" + radius + "]"); aiPlanSetVariableFloat(planID, cDefendPlanEngageRange, 0, radius); aiPlanSetVariableFloat(planID, cDefendPlanGatherDistance, 0, radius); } // return success return(true); } // return failure return(false); } //============================================================================== // UpdateSpawnedUnitDefenseForcedLoc // // force a location and update the defense plan with this as its next goal //============================================================================== void UpdateSpawnedUnitDefenseForcedLoc(int planID = -1, vector testLOC = cInvalidVector) { // error check if (planID < 0) { // return failure return; } // if this is a new location, echo away if (distance(testLOC, aiPlanGetVariableVector(planID, cDefendPlanDefendPoint, 0)) > 1) { //echo away aiEcho("SpawnedUnitDefense: --- plan[" + aiPlanGetName(planID) + "] new Goal[" + testLOC + "]"); } // update the plan aiPlanSetInitialPosition(planID, testLOC); aiPlanSetVariableVector(planID, cDefendPlanDefendPoint, 0, testLOC); }