//==============================================================================
// Scn10p2: AI Scenario Script for scenario 10 player 2
//==============================================================================
/*
   AI owner:  Mike Kidd
   Scenario owner: Jeff Brown

   Overview:
   The player needs to rescue Chiron, but must achieve a number of 
   intermediate objectives.  First, he picks up a bunch of centaurs, then uses
   them to kill prison guards to claim one area and get resources.  The player 
   makes a few more units, then goes to capture first one part, then another, of 
   the city.  Finally, the player can use siege to destroy the bandits' migdol
   fortress.

   The AI has a few components.  A simple economic framework will keep villagers 
   gathering in several zones of the map.  Triggers will spawn units and call a 
   spawn() AI function, with parameters indicating that it should attack P1 units,
   P1 buildings, or P8 (P1 ally) buildings using a pair of attack routes.

   Finally, when the player is about to start his assault on the fortress, the
   CPAI will spawn one last set of defenders, which will be absorbed into a defend plan.

   The CPAI does no unit training and no upgrading, and very limited god power usage.

   Difficulty is adjusted through the size and timing of spawned unit groups.
  
   7/31/2002:  Added economic AI bits to keep gatherers busy.

*/
//==============================================================================


include "scn lib.xs";

// Globals
int         lastAttackPlanID = -1;        // Updated with plan ID as each attack is launched

int         startTime = -1;               // Updated with start time when cinematic is done.  In seconds.
int         visionTime = -1;              // Updated by wakeup() function, time (in sec) to cast vision.
int         age3Time = 720;               // OK to go to age 3 at 12 minutes
int         age4Time = 1500;              // OK to go to age 4 at 25 minutes
int         nextAttackTime = 300;
int         attackInterval = 180;
int         nextAttackSize = 3;
int         attackSizeIncrement = 1;

int         siegeMaintainPlan = -1;
int         mythMaintainPlan = -1;
int         barracksUnitMaintainPlan = -1;
int         barracksUnit = -1;
int         chariotMaintainPlan = -1;

int         attackQuery = -1;             // Shared by all armies
int         attackQuery2 = -1;            // Additional query for building targets
int         attackQuery3 = -1;            // Additional query for player 3 buildings

int         gathererTypeID = -1;
int         mainBase = -1;                // Used for gathering, centered north of TC
int         woodBase = -1;                // Used for wood only, west of TC, centered on lumber camp.


// Cinematic block markers

const string   cbRouteInnerA = "6438";
const string   cbRouteInnerB = "6439";
const string   cbRouteInnerC = "6440";

const string   cbRouteOuterA = "6441";
const string   cbRouteOuterB = "6442";
const string   cbRouteOuterC = "6443";

const string   cbTownCenter = "6444";
const string   cbBase2 = "6450";




int   routeInner = -1;
int   routeOuter = -1;


// *****************************************************************************
//
//                                FUNCTIONS
//
// *****************************************************************************


int initMainBase(vector center=vector(-1,-1,-1), float radius = 50.0)
{
   int baseID = -1;
   // Nuke bases, add one base to rule them all
   kbBaseDestroyAll(cMyID);

   baseID = kbBaseCreate(cMyID, "Base "+kbBaseGetNextID(), center, radius);
   if (baseID < 0)
      aiEcho("***** Main base creation failed. *****");

   vector baseFront=xsVectorNormalize(kbGetMapCenter()-center);     // Set front
   kbBaseSetFrontVector(cMyID, mainBase, baseFront);                 
   kbBaseSetMaximumResourceDistance(cMyID, baseID, radius+20.0);                    // Gather up to 20m beyond base perimeter
   kbBaseSetMain(cMyID, baseID, true);     // Make this the main base

   // Add the buildings
   int buildingQuery = -1;
   int count = 0;
   buildingQuery = kbUnitQueryCreate("Building Query");     // All buildings in the base
   configQuery(buildingQuery, cUnitTypeBuilding, -1, cUnitStateAliveOrBuilding, cMyID, center, false, radius);
   kbUnitQueryResetResults(buildingQuery);
   count = kbUnitQueryExecute(buildingQuery);

   int i = 0;
   int buildingID = -1;
   for (i=0; < count)
   {
      buildingID = kbUnitQueryGetResult(buildingQuery, i);
      // Add it to the base
      kbBaseAddUnit( cMyID, baseID, buildingID );
   }
   return(baseID);
}



int makeBase(vector center=vector(-1,-1,-1), float radius = 50.0)
{
   int baseID = -1;

   baseID = kbBaseCreate(cMyID, "Base "+kbBaseGetNextID(), center, radius);
   if (baseID < 0)
      aiEcho("***** Base creation failed. *****");

   vector baseFront=xsVectorNormalize(kbGetMapCenter()-center);     // Set front
   kbBaseSetFrontVector(cMyID, mainBase, baseFront);                 
   kbBaseSetMaximumResourceDistance(cMyID, baseID, radius+20.0);                    // Gather up to 20m beyond base perimeter

   // Add the buildings
   int buildingQuery = -1;
   int count = 0;
   buildingQuery = kbUnitQueryCreate("Base Query");     // All buildings in the base
   configQuery(buildingQuery, cUnitTypeBuilding, -1, cUnitStateAliveOrBuilding, cMyID, center, false, radius);
   kbUnitQueryResetResults(buildingQuery);
   count = kbUnitQueryExecute(buildingQuery);

   int i = 0;
   int buildingID = -1;
   for (i=0; < count)
   {
      buildingID = kbUnitQueryGetResult(buildingQuery, i);
      // Add it to the base
      kbBaseAddUnit( cMyID, baseID, buildingID );
   }
   return(baseID);
}


void initEcon()
{
   aiSetAttackResponseDistance(20.0);   // Kill escrows
   kbEscrowSetPercentage( cEconomyEscrowID, cAllResources, 0.0);
   kbEscrowSetPercentage( cMilitaryEscrowID, cAllResources, 0.0);
   kbEscrowAllocateCurrentResources();

   aiSetAutoGatherEscrowID(cRootEscrowID);
   aiSetAutoFarmEscrowID(cRootEscrowID);
   gathererTypeID = kbTechTreeGetUnitIDTypeByFunctionIndex(cUnitFunctionGatherer,0);

   
   int herdPlanID=aiPlanCreate("GatherHerdable Plan", cPlanHerd);
   if (herdPlanID >= 0)
   {
      aiPlanAddUnitType(herdPlanID, cUnitTypeHerdable, 0, 100, 100);
      aiPlanSetVariableInt(herdPlanID, cHerdPlanBuildingTypeID, 0, cUnitTypeSettlementLevel1);
      aiPlanSetActive(herdPlanID);
   }

   aiSetResourceGathererPercentageWeight(cRGPScript, 1);
   aiSetResourceGathererPercentageWeight(cRGPCost, 0);

   kbSetAICostWeight(cResourceFood, 1.0);
   kbSetAICostWeight(cResourceWood, 0.7);
   kbSetAICostWeight(cResourceGold, 0.8);
   kbSetAICostWeight(cResourceFavor, 7.0);

   aiSetResourceGathererPercentage(cResourceFood, 7.0/11.0, false, cRGPScript);
   aiSetResourceGathererPercentage(cResourceWood, 3.0/11.0, false, cRGPScript);
   aiSetResourceGathererPercentage(cResourceGold, 1.0/11.0, false, cRGPScript);
   aiSetResourceGathererPercentage(cResourceFavor, 0.0, false, cRGPScript);
   aiNormalizeResourceGathererPercentages(cRGPScript);

   //bool aiSetResourceBreakdown( int resourceTypeID, int resourceSubTypeID, int numberPlans, int planPriority, float percentage, int baseID )
//	aiSetResourceBreakdown(cResourceFood, cAIResourceSubTypeEasy, numFoodEasyPlans, 50, 1.0, gMainBaseID);
//   aiSetResourceBreakdown(cResourceFood, cAIResourceSubTypeHuntAggressive, numFoodHuntAggressivePlans, 100, 1.0, gMainBaseID);
   aiSetResourceBreakdown(cResourceFood, cAIResourceSubTypeFarm, 1, 50, 1.0, mainBase);
   aiSetResourceBreakdown(cResourceWood, cAIResourceSubTypeEasy, 1, 50, 1.0, woodBase);
	aiSetResourceBreakdown(cResourceGold, cAIResourceSubTypeEasy, 1, 50, 1.0, mainBase);
//   aiSetResourceBreakdown(cResourceFavor, cAIResourceSubTypeEasy, numFavorPlans, 50, 1.0, gMainBaseID);

   // Create villager maintain plan
   createSimpleMaintainPlan(gathererTypeID, 11, true, mainBase);
}




// Called by a trigger, to let AI know that it's time to start maintain plans, etc.
void defend(int parm=-1)
{
   // Init low-priority defend plan to manage hoplites
   int defendPlan =aiPlanCreate("Defend Plan", cPlanDefend);
   if (defendPlan >= 0)
   {
      aiPlanAddUnitType(defendPlan, cUnitTypeMilitary, 0, 200, 200);    // All unassigned mil units
      aiPlanSetDesiredPriority(defendPlan, 10);                       // Way low, below scouting and attack
      aiPlanSetVariableVector(defendPlan, cDefendPlanDefendPoint, 0, kbGetBlockPosition(cbTownCenter));
      aiPlanSetVariableFloat(defendPlan, cDefendPlanEngageRange, 0, 60);
      aiPlanSetVariableBool(defendPlan, cDefendPlanPatrol, 0, false);
      aiPlanSetVariableFloat(defendPlan, cDefendPlanGatherDistance, 0, 20.0);
      aiPlanSetInitialPosition(defendPlan, kbGetBlockPosition(cbTownCenter));
      aiPlanSetUnitStance(defendPlan, cUnitStanceDefensive);

      aiPlanSetVariableInt(defendPlan, cDefendPlanRefreshFrequency, 0, 5);
      aiPlanSetNumberVariableValues(defendPlan, cDefendPlanAttackTypeID, 2, true);
      aiPlanSetVariableInt(defendPlan, cDefendPlanAttackTypeID, 0, cUnitTypeUnit);
      aiPlanSetVariableInt(defendPlan, cDefendPlanAttackTypeID, 1, cUnitTypeBuilding);

      aiPlanSetActive(defendPlan); 
      aiEcho("Creating defend plan");
   }
}


// Called by a trigger
void serpents(int parm=-1)
{
      xsEnableRule("useSerpents");
}


void age2EventHandler(int bogus=-1)
{

}

void age3EventHandler(int bogus=-1)
{

}


void age4EventHandler(int bogus=-1)
{

}


    



void attack(int player = -1)
{
   int qty = -1;
   qty = getUnassignedUnitCount(kbGetBlockPosition(cbTownCenter), 50.0, cMyID, cUnitTypeMilitary);
   if (qty < 1)
      return;

   int   attackID=aiPlanCreate("Attack "+timeString()+"  ", cPlanAttack);
   if (attackID < 0)
   {
      return;
   }

   if (aiPlanSetVariableInt(attackID, cAttackPlanPlayerID, 0, player) == false)
   {
      return;
   }

   if (aiPlanSetNumberVariableValues(attackID, cAttackPlanTargetTypeID, 2, true) == false)
   {
      return;
   }

// aiPlanSetVariableInt(attackID, cAttackPlanQueryID, 0, attackQuery);

   // add "unit" and "building" to attack list
   aiPlanSetVariableInt(attackID, cAttackPlanTargetTypeID, 0, cUnitTypeUnit);
   aiPlanSetVariableInt(attackID, cAttackPlanTargetTypeID, 1, cUnitTypeBuilding);

   if (aiRandInt(2) > 0)
      aiPlanSetVariableInt(attackID, cAttackPlanAttackRouteID, 0, routeInner);
   else
      aiPlanSetVariableInt(attackID, cAttackPlanAttackRouteID, 0, routeOuter);
 
   aiPlanSetVariableInt(attackID, cAttackPlanLastRefreshTime, 0, 30);

   aiPlanSetVariableVector(attackID, cAttackPlanGatherPoint, 0, kbGetBlockPosition(cbTownCenter));
   aiPlanSetVariableFloat(attackID, cAttackPlanGatherDistance, 0, 15.0);

   aiPlanAddUnitType(attackID, cUnitTypeMilitary, 0, qty, qty);


   aiPlanSetInitialPosition(attackID, kbGetBlockPosition(cbTownCenter));
   aiPlanSetRequiresAllNeedUnits(attackID, true);
   aiPlanSetActive(attackID);
   aiEcho("Activating attack plan "+attackID+" with "+qty+" units.");
}



void spawn(int parm = -1)
{
   aiEcho("Spawn fired with parm "+parm);
   if (parm == 0)
      attack(1);
   if (parm == 1)
      attack(8);
   if (parm == 2)
      attack(1);
}






void main()
{
   aiEcho("Starting Scn10p2.xs");
   kbSetTownLocation(kbGetBlockPosition(cbTownCenter));

   //Calculate some areas.
   kbAreaCalculate(1200.0);

   aiRandSetSeed();

   mainBase = initMainBase(kbGetBlockPosition(cbTownCenter), 60.0);
   woodBase = makeBase(kbGetBlockPosition(cbBase2), 20.0);
   initEcon();

   aiSetAgeEventHandler(cAge2, "age2EventHandler");
   aiSetAgeEventHandler(cAge3, "age3EventHandler");
   aiSetAgeEventHandler(cAge4, "age4EventHandler");

   routeInner = attackRoute("Inner Attack Route",cbRouteInnerA, cbRouteInnerB, cbRouteInnerC);
   routeOuter = attackRoute("Outer Attack Route",cbRouteOuterA, cbRouteOuterB, cbRouteOuterC);

}







// *****************************************************************************
//
// RULES
//
// *****************************************************************************


rule goToAge3
   inactive
   mininterval 20
{
   if ( (xsGetTime()/1000) < age3Time )
      return;
   researchTech(cTechAge3Nephthys);
   xsEnableRule("goToAge4");
   xsEnableRule("getAge3UnitUpgrades");
   xsEnableRule("getAge3ArmoryUpgrades");
   xsDisableSelf();
}

rule goToAge4
   inactive
   mininterval 20
{
   if ( (xsGetTime()/1000) < age4Time )
      return;
   researchTech(cTechAge4Horus);
   xsEnableRule("getAge4UnitUpgrades");
   xsEnableRule("getAge4ArmoryUpgrades");
   xsDisableSelf();
}



rule getAge2UnitUpgrades
   inactive
   minInterval 20
{
   if ( (xsGetTime()/1000) < (startTime + startTime + age3Time)/3 )
      return;     // Wait till 1/3 to age3
   researchTech(cTechMediumAxemen);
   researchTech(cTechMediumSlingers);
   researchTech(cTechMediumSpearmen);
   xsDisableSelf();
}

rule getAge2ArmoryUpgrades
   inactive
   minInterval 20
{
   if ( (xsGetTime()/1000) < (startTime + age3Time + age3Time)/3 )
      return;     // Wait till 2/3 to age3
   aiEcho("Getting age 2 armory upgrades");
   researchTech(cTechCopperWeapons);
   researchTech(cTechCopperMail);
   researchTech(cTechCopperShields);
   xsDisableSelf();
}

rule getAge3UnitUpgrades
   inactive
   minInterval 20
{
   if ( (xsGetTime()/1000) < (age3Time+300) )
      return;
   researchTech(cTechHeavyAxemen);
   researchTech(cTechHeavySlingers);
   researchTech(cTechHeavySpearmen);
   researchTech(cTechHeavyChariots);
   xsDisableSelf();
}

rule getAge3ArmoryUpgrades
   inactive
   minInterval 20
{
   if ( (xsGetTime()/1000) < (age3Time+300) )
      return;
   researchTech(cTechBronzeWeapons);
   researchTech(cTechBronzeMail);
   researchTech(cTechBronzeShields);
   xsDisableSelf();
}

rule getAge4UnitUpgrades
   inactive
   minInterval 20
{
   if ( (xsGetTime()/1000) < (age4Time+600) )
      return;
   researchTech(cTechChampionAxemen);
   researchTech(cTechChampionSlingers);
   researchTech(cTechChampionSpearmen);
   researchTech(cTechChampionChariots);
   xsDisableSelf();
}

rule getAge4ArmoryUpgrades
   inactive
   minInterval 20
{
   if ( (xsGetTime()/1000) < (age4Time+300) )
      return;
   researchTech(cTechIronWeapons);
   researchTech(cTechIronMail);
   researchTech(cTechIronShields);
   xsDisableSelf();
}



rule scout
   inactive
{
   // just set up an explore plan
   int exploreID = aiPlanCreate("Explore", cPlanExplore);
   if(exploreID >= 0)
   {
      //aiPlanAddVariableFloat( exploreID, cExplorePlanLOSMultiplier, "LOS Multiplier", 1);
      aiPlanSetVariableFloat( exploreID, cExplorePlanLOSMultiplier,  0, 4.0 );
      aiPlanAddUnitType(exploreID, cUnitTypeSpearman, 1, 1, 1);
      aiPlanSetActive(exploreID);
   }
   xsDisableSelf();
}


/* Sample
rule attackGenerator
   minInterval 10
   active
{
   //aiEcho("attack check running, next time is "+nextAttackTime);
   if ( (xsGetTime()/1000) < nextAttackTime )
      return;

   testAttack();
   nextAttackTime = (xsGetTime()/1000) + attackInterval;
   attackSize = attackSize + attackSizeIncrement;
   aiEcho("Next attack size will be "+attackSize+".");
}
*/



rule useVision       // When it's time, cast vision at one of four markers.
   minInterval 5
   inactive
{
/*


   if (visionTime < 0)
      return;

   int rand = aiRandInt(4);
   switch (rand)
   {
   case 0:
      {
         aiCastGodPowerAtPosition(cTechVision, kbGetBlockPosition(cbVision0));
         break;
      }
   case 1:
      {
         aiCastGodPowerAtPosition(cTechVision, kbGetBlockPosition(cbVision1));
         break;
      }
   case 2:
      {
         aiCastGodPowerAtPosition(cTechVision, kbGetBlockPosition(cbVision2));
         break;
      }
   case 3:
      {
         aiCastGodPowerAtPosition(cTechVision, kbGetBlockPosition(cbVision3));
         break;
      }
   }
   aiEcho("Vision has been used");
   */
   xsDisableSelf();
}



rule useSerpents // Same logic as lightning, look for enemy military units
   minInterval 5
   inactive
{

   // look for a group of 7 enemy units, at the Migdol location or at my main army's location
   int targetUnit = -1;

   static int tempQuery = -1;
   if (tempQuery < 0)
   {  // Doesn't exist, set it up
      tempQuery = kbUnitQueryCreate("useSerpents1");
      if ( configQuery(tempQuery, cUnitTypeUnit, -1, cUnitStateAlive, 1, kbGetBlockPosition(cbTownCenter), true, 75) == false)
         return;
   }
   kbUnitQueryResetResults(tempQuery);
   int targetCount = kbUnitQueryExecute(tempQuery); 

   if (targetCount < 7)
   {
      if (lastAttackPlanID < 0)
         return;
      vector pVec = aiPlanGetLocation(lastAttackPlanID);
      if (xsVectorGetX(pVec)>=0)
      {
         static int tempQuery2 = -1;
         if (tempQuery2 < 0)
         {  // Doesn't exist, set it up
            tempQuery2 = kbUnitQueryCreate("useSerpents2");
            if ( configQuery(tempQuery2, cUnitTypeMilitary, -1, cUnitStateAlive, 1, pVec, true, 50) == false)
               return;
         }
         else
            kbUnitQuerySetPosition(tempQuery, pVec); // Because pVec changes as army moves
         kbUnitQueryResetResults(tempQuery2);
         targetCount = kbUnitQueryExecute(tempQuery2); 
         if (targetCount < 7)
            return;
         else
            targetUnit = kbUnitQueryGetResult(tempQuery2, 0);  // grab first unit
      }
   } 
   else
      targetUnit = kbUnitQueryGetResult(tempQuery, targetCount/2);  // grab middle unit


   aiEcho("Using Plague of Serpents at "+kbUnitGetPosition(targetUnit));
   if ( aiCastGodPowerAtPosition(cTechSerpents, kbUnitGetPosition(targetUnit)) == true)
      xsDisableSelf();
   else
      aiEcho("Serpents failed at "+kbUnitGetPosition(targetUnit));
}


rule useAncestors // Same logic as Serpents, look for enemy military units
   minInterval 5
   inactive
{

   // look for a group of 7 enemy units, at the Migdol location or at my main army's location
   int targetUnit = -1;

   static int tempQuery = -1;
   if (tempQuery < 0)
   {  // Doesn't exist, set it up
      tempQuery = kbUnitQueryCreate("useAncestors1");
      if ( configQuery(tempQuery, cUnitTypeUnit, -1, cUnitStateAlive, 1, kbGetBlockPosition(cbTownCenter), true, 75) == false)
         return;
   }
   kbUnitQueryResetResults(tempQuery);
   int targetCount = kbUnitQueryExecute(tempQuery); 

   if (targetCount < 7)
   {
      vector pVec = aiPlanGetLocation(lastAttackPlanID);
      if (xsVectorGetX(pVec)>=0)
      {
         static int tempQuery2 = -1;
         if (tempQuery2 < 0)
         {  // Doesn't exist, set it up
            tempQuery2 = kbUnitQueryCreate("useAncestors2");
            if ( configQuery(tempQuery, cUnitTypeMilitary, -1, cUnitStateAlive, 1, pVec, true, 50) == false)
               return;
         }
         kbUnitQueryResetResults(tempQuery2);
         targetCount = kbUnitQueryExecute(tempQuery2); 
         if (targetCount < 7)
            return;
         else
            targetUnit = kbUnitQueryGetResult(tempQuery2, 0);  // grab first unit
      }
      else
         return;  // No army to check
   } 
   else//SkeletonPower//PharaohRespawnCityoftheDead
      targetUnit = kbUnitQueryGetResult(tempQuery, targetCount/2);  // grab middle unit


   aiEcho("Using Ancestors");
   if ( aiCastGodPowerAtPosition(cTechSkeletonPower, kbUnitGetPosition(targetUnit)) == true)
      xsDisableSelf();
   else
      aiEcho("Ancestors failed at "+kbUnitGetPosition(targetUnit));

}



