// v2.1.0 - Updated to new version of AI.
// v2.0.0 -
//::////////////////////////////////////////////////////////////////////////////
//:: FileName:  hark_i0_hench_ai
//::////////////////////////////////////////////////////////////////////////////
/*
   This file contains functions used in the default On* scripts for combat and
  contains options for the henchman ai.
*/
//::////////////////////////////////////////////////////////////////////////////
#include "hark_i0_auto"
//::////////////////////////////////////////////////////////////////////////////


// * FUNCTION PROTOTYPES


// Sets the location of an unheard or unseen enemy.
// Either moved out of range or did attack while not detected.
void SetEnemyLocation(object oEnemy);

// Clears the last unheard, unseen enemy location.
void ClearEnemyLocation();

// Moves to the last perceived enemy location.
void MoveToLastSeenOrHeard();

// Cleans all temporary values used during combat.
void CleanCombatVars();

// Modified DetermineCombatRound call. Determines if call should cause main AI script to run.
void HenchDetermineCombatRound(object oIntruder = OBJECT_INVALID, int bForceInterrupt = FALSE);

// Modified ActionAttack. Will also equip a weapon appropriate for oIntruder.
void HenchStartAttack(object oIntruder = OBJECT_INVALID);

// Modified TalentFlee routine.
int HenchTalentFlee(object oIntruder = OBJECT_INVALID);

// Modified GetNearestSeenOrHeardEnemy to not get dead creatures.
object GetNearestSeenOrHeardEnemyNotDead();

// Used to attack non creature items. (Placeables and Doors)
void HenchAttackObject(object oTarget);

// Determines if enemy is perceived by the Henchman or not.
int HenchGetIsEnemyPerceived();

int CheckStealth();

int HenchTalentBardSong();

// Forumla: Total Challenge of Enemy = log(Sum(2**challenge))
float GetEnemyChallenge(object oRelativeTo = OBJECT_SELF);

// Used in DetermineCombatRound to keep a henchmen bashing doors.
int HenchBashDoorCheck(object oIntruder = OBJECT_INVALID);

// Initializes all of the base AI options. Called on Module load.
void InitializeAIOptions();

// Set AI nOption to bValue.
void SetAIOption(int nOption, int bValue = TRUE);

// Returns TRUE if nOption is on.
int GetAIOption(int nOption);

// Random battle cries based on class. 8% chance.
void HenchBattleCry();


// * FUNCTION DEFINITIONS


//::////////////////////////////////////////////////////////////////////////////
void SetEnemyLocation(object oEnemy)
{
  location lLoc = GetLocation(oEnemy);
  SetLocalInt(OBJECT_SELF, "LastSeenOrHeard", TRUE);
  SetLocalLocation(OBJECT_SELF, "LastSeenOrHeard", lLoc);

  // Get the area, position and facing. Then build a new location, which faces
  // in the opposite direction.
  object oArea = GetAreaFromLocation(lLoc);
  vector vPos  = GetPositionFromLocation(lLoc);
  float fFace  = GetFacingFromLocation(lLoc);
  vPos.z += 100.0;
  lLoc = Location(oArea, vPos, fFace);

  // Now create the invis object for pathfinding, at the adjusted location.
  object oInvisTarget = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_invisobj", lLoc);
  SetLocalObject(OBJECT_SELF, "LastSeenOrHeard", oInvisTarget);
}
//::////////////////////////////////////////////////////////////////////////////
void ClearEnemyLocation()
{
  DeleteLocalInt(OBJECT_SELF, "LastSeenOrHeard");
  DeleteLocalLocation(OBJECT_SELF, "LastSeenOrHeard");
  object oInvisTarget = GetLocalObject(OBJECT_SELF, "LastSeenOrHeard");
  if (GetIsObjectValid(oInvisTarget))
    {
     DestroyObject(oInvisTarget);
     DeleteLocalObject(OBJECT_SELF, "LastSeenOrHeard");
    }
}
//::////////////////////////////////////////////////////////////////////////////
void MoveToLastSeenOrHeard()
{
  location moveToLoc = GetLocalLocation(OBJECT_SELF, "LastSeenOrHeard");
  if (GetDistanceBetweenLocations(GetLocation(OBJECT_SELF), moveToLoc) <= 5.0)
    {
     ClearEnemyLocation();
     // Go to search.
     if (GetDetectMode(OBJECT_SELF) != DETECT_MODE_ACTIVE)
       { ActionUseSkill(SKILL_SEARCH, OBJECT_SELF); }
    }
  else
    {
     object oInvisTarget = GetLocalObject(OBJECT_SELF, "LastSeenOrHeard");
     ActionMoveToObject(oInvisTarget, TRUE);
    }
}
//::////////////////////////////////////////////////////////////////////////////
void CleanCombatVars()
{
    DeleteLocalInt(OBJECT_SELF, "tkCombatRoundCount");
    DeleteLocalInt(OBJECT_SELF, "tkLastDragonBreath");
    DeleteLocalInt(OBJECT_SELF, "nRoundsSkipped");
    DeleteLocalInt(OBJECT_SELF, "tkFirstSpellLoc");
    DeleteLocalInt(OBJECT_SELF, "tkNoAttackSpellTalents");
    DeleteLocalInt(OBJECT_SELF, "tkNoEnhanceSpellTalents");
    DeleteLocalInt(OBJECT_SELF, "tkNoConditionalSpellTalents");
    DeleteLocalInt(OBJECT_SELF, "tkNoPotions");
    DeleteLocalInt(OBJECT_SELF, "CloseRangeEnhanced");
    DeleteLocalObject(OBJECT_SELF, "LastTarget");
}
//::////////////////////////////////////////////////////////////////////////////
void HenchDetermineCombatRound(object oIntruder = OBJECT_INVALID, int bForceInterrupt = FALSE)
{
  string sAIScript = GetLocalString(OBJECT_SELF, "AIScript");
  if (sAIScript == "") { sAIScript = sHenAIScript; }

  // Check if we have to actually determine to rerun ai.
  if (bForceInterrupt)
    {
     SetLocalInt(OBJECT_SELF, "AIIntruder", 1);
     SetLocalObject(OBJECT_SELF, "AIIntruderObj", oIntruder);
     ExecuteScript(sAIScript, OBJECT_SELF);
     return;
    }

  // Check if we have to actually determine to rerun ai.
  int iAIIntruderLevel = GetLocalInt(OBJECT_SELF, "AIIntruder");
  if (iAIIntruderLevel == 0)
    {
     // First run of HenchDetermineCombatRound.
     SetLocalInt(OBJECT_SELF, "AIIntruder", 1);
     SetLocalObject(OBJECT_SELF, "AIIntruderObj", oIntruder);
     ExecuteScript(sAIScript, OBJECT_SELF);
     return;
    }

  object oLastTarget = GetLocalObject(OBJECT_SELF, "LastTarget");
  if (!GetIsObjectValid(oLastTarget) || GetIsDead(oLastTarget) ||
      !GetIsEnemy(oLastTarget) || GetLocalInt(oLastTarget, "RunningAway"))
    { oLastTarget = OBJECT_INVALID; }
  else if (!GetObjectSeen(oLastTarget) && !GetObjectHeard(oLastTarget))
    { oLastTarget = OBJECT_INVALID; }

  if (iAIIntruderLevel == 1)
    {
     // Second or later run of HenchDetermineCombatRound without calling hark_o0_hench_ai.
     if (!GetIsObjectValid(oLastTarget) &&
         !GetIsObjectValid(GetLocalObject(OBJECT_SELF, "AIIntruderObj")))
       {
        // If no intruder set already, set ours.
        SetLocalObject(OBJECT_SELF, "AIIntruderObj", oIntruder);
       }
     return;
    }

  if (!GetIsObjectValid(oLastTarget))
    {
     // Prevent too many calls to main script if already moving to an unseen
     // and unheard monster.
     if (!GetLocalInt(OBJECT_SELF, "LastSeenOrHeard") ||
          GetObjectSeen(oIntruder) || GetObjectHeard(oIntruder))
       {
        SetLocalObject(OBJECT_SELF, "AIIntruderObj", oIntruder);
        ExecuteScript(sAIScript, OBJECT_SELF);
       }
    }
  else if (GetIsObjectValid(oIntruder))
    {
     if (GetDistanceToObject(oIntruder) <= 5.0 &&
         GetDistanceToObject(oLastTarget) > 5.0)
       {
        SetLocalObject(OBJECT_SELF, "AIIntruderObj", oIntruder);
        ExecuteScript(sAIScript, OBJECT_SELF);
       }
     else if (GetObjectSeen(oIntruder) && !GetObjectSeen(oLastTarget))
       {
        SetLocalObject(OBJECT_SELF, "AIIntruderObj", oIntruder);
        ExecuteScript(sAIScript, OBJECT_SELF);
       }
    }
}
//::////////////////////////////////////////////////////////////////////////////
void HenchStartAttack(object oIntruder = OBJECT_INVALID)
{
  SetLocalObject(OBJECT_SELF, "AIIntruderObj", oIntruder);
  ExecuteScript(sAttackScript, OBJECT_SELF);
}
//::////////////////////////////////////////////////////////////////////////////
int HenchTalentFlee(object oIntruder = OBJECT_INVALID)
{
  object oTarget = oIntruder;
  if (!GetIsObjectValid(oTarget))
    {
     oTarget = GetLastHostileActor();
     if (!GetIsObjectValid(oTarget) || GetIsDead(oTarget))
       {
        oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN);
        float fDist = GetDistanceBetween(OBJECT_SELF, oTarget);
        if (!GetIsObjectValid(oTarget))
          { return FALSE; }
       }
    }
  ClearActions(CLEAR_I0_AI_HenchTalentFlee_1);
  ActionMoveAwayFromObject(oTarget, TRUE, 10.0);
  DelayCommand(4.0, ClearActions(CLEAR_I0_AI_HenchTalentFlee_2));
  return TRUE;
}
//::////////////////////////////////////////////////////////////////////////////
object GetNearestSeenOrHeardEnemyNotDead()
{
  object oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE);
  if (!GetIsObjectValid(oTarget) && GetAIOption(HS_AI_HEARING))
    {
     oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD_AND_NOT_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE);
     if (!GetIsObjectValid(oTarget))
       { return OBJECT_INVALID; }
    }
  return oTarget;
}
//::////////////////////////////////////////////////////////////////////////////
void HenchAttackObject(object oTarget)
{
  if (GetWeaponRanged(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND)))
    { ActionAttack(oTarget); }
  else if (GetHasFeat(FEAT_IMPROVED_POWER_ATTACK))
    { ActionUseFeat(FEAT_IMPROVED_POWER_ATTACK, oTarget); }
  else if (GetHasFeat(FEAT_POWER_ATTACK))
    { ActionUseFeat(FEAT_POWER_ATTACK, oTarget); }
  else
    { ActionAttack(oTarget); }
}
//::////////////////////////////////////////////////////////////////////////////
int HenchGetIsEnemyPerceived()
{
  object oClosestSeen = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE);
  if (GetIsObjectValid(oClosestSeen))
    { return TRUE; }

  if (GetAIOption(HS_AI_HEARING))
    {
     object oClosestHeard = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD_AND_NOT_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE);
     if (GetIsObjectValid(oClosestHeard))
       {
        if (GetDistanceToObject(oClosestHeard) <= HS_HENCH_HEAR_DISTANCE)
          { return TRUE; }

        object oMaster = GetRealMaster();
        if (GetIsObjectValid(oMaster))
          {
           if (GetDistanceBetween(oMaster, oClosestHeard) <= HS_MASTER_HEAR_DISTANCE)
             { return TRUE; }
          }
       }
    }
  return FALSE;
}
//::////////////////////////////////////////////////////////////////////////////
int CheckStealth()
{
  int nStealthCheck = GetLocalInt(OBJECT_SELF, "canStealth");
  if (nStealthCheck == 0)
    {
     int nDexCheck = GetAbilityModifier(ABILITY_DEXTERITY);
     if (nDexCheck < 0) { nDexCheck = 0; }
     int nHideCheck = GetSkillRank(SKILL_HIDE, OBJECT_SELF) - nDexCheck;
     int nMoveSilentCheck = GetSkillRank(SKILL_MOVE_SILENTLY, OBJECT_SELF) - nDexCheck;
     nStealthCheck = ((nHideCheck > 0) || (nMoveSilentCheck > 0)) ? 1 : 2;
     SetLocalInt(OBJECT_SELF, "canStealth", nStealthCheck);
    }
  return nStealthCheck == 1;
}
//::////////////////////////////////////////////////////////////////////////////
int HenchTalentBardSong()
{
  if (!GetHasSpellEffect(411))
    {
     if (GetHasFeat(FEAT_BARD_SONGS))
       {
        ActionUseFeat(FEAT_BARD_SONGS, OBJECT_SELF);
        return TRUE;
       }
    }
  return FALSE;
}
//::////////////////////////////////////////////////////////////////////////////
float GetEnemyChallenge(object oRelativeTo = OBJECT_SELF)
{
  float fChallenge = 0.;
  object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, 20.0, GetLocation(OBJECT_SELF), TRUE);
  while (GetIsObjectValid(oTarget))
    {
     if (GetIsEnemy(oTarget))
       {
        if (GetObjectSeen(oTarget))
          { fChallenge += pow(1.5, GetChallengeRating(oTarget)); }
       }
     oTarget = GetNextObjectInShape(SHAPE_SPHERE, 20.0, GetLocation(OBJECT_SELF), TRUE);
    }
  return (log(fChallenge) / log(1.5)) - IntToFloat(GetHitDice(oRelativeTo));
}
//::////////////////////////////////////////////////////////////////////////////
int HenchBashDoorCheck(object oIntruder = OBJECT_INVALID)
{
  int bDoor = FALSE;
  // This code is here to make sure that henchmen keep bashing doors and placables.
  object oDoor = GetLocalObject(OBJECT_SELF, "NW_GENERIC_DOOR_TO_BASH");
  if (GetIsObjectValid(oDoor))
    {
     int nDoorMax = GetMaxHitPoints(oDoor);
     int nDoorNow = GetCurrentHitPoints(oDoor);
     int nCnt = GetLocalInt(OBJECT_SELF, "NW_GENERIC_DOOR_TO_BASH_HP");
     if (GetLocked(oDoor) || GetIsTrapped(oDoor))
       {
        if (nDoorMax == nDoorNow)
          {
           nCnt++;
           SetLocalInt(OBJECT_SELF,"NW_GENERIC_DOOR_TO_BASH_HP", nCnt);
          }
        if (nCnt <= 2)
          {
           bDoor = TRUE;
           HenchAttackObject(oDoor);
          }
       }

     if (bDoor == FALSE)
       {
        DeleteLocalObject(OBJECT_SELF, "NW_GENERIC_DOOR_TO_BASH");
        DeleteLocalInt(OBJECT_SELF, "NW_GENERIC_DOOR_TO_BASH_HP");
        VoiceCuss();
        ActionDoCommand(HenchEquipAppropriateWeapons(OBJECT_SELF, -5., FALSE));
       }
    }
  return bDoor;
}
//::////////////////////////////////////////////////////////////////////////////
void InitializeAIOptions()
{
  if (GetLocalInt(GetObjectByTag(HS_VARS_OBJ), HS_VAR_RAISE))
    { SetAIOption(HS_AI_RAISE); }
  if (GetLocalInt(GetObjectByTag(HS_VARS_OBJ), HS_VAR_HEARING))
    { SetAIOption(HS_AI_HEARING); }
  if (GetLocalInt(GetObjectByTag(HS_VARS_OBJ), HS_VAR_ATTACK))
    { SetAIOption(HS_AI_ATTACK); }
  if (GetLocalInt(GetObjectByTag(HS_VARS_OBJ), HS_VAR_HAS_INV))
    { SetAIOption(HS_AI_INVENTORY); }
  if (GetLocalInt(GetObjectByTag(HS_VARS_OBJ), HS_VAR_POTION))
    { SetAIOption(HS_AI_POTION); }
  if (GetGameDifficulty() >= GAME_DIFFICULTY_CORE_RULES)
    {
     SetAIOption(HS_AI_STEALTH);
     SetAIOption(HS_AI_WANDER);
     SetAIOption(HS_AI_UNLOCK);
     SetAIOption(HS_AI_OPEN);
     SetAIOption(HS_AI_DISTRIB);
     SetAIOption(HS_AI_HASTE);
    }
}
//::////////////////////////////////////////////////////////////////////////////
void SetAIOption(int nOption, int bValue = TRUE)
{
  int nCond = GetLocalInt(GetObjectByTag(HS_VARS_OBJ), HS_AI_CONDITION);
  if (bValue == TRUE)
    {
     nCond = nCond | nOption;
     SetLocalInt(GetObjectByTag(HS_VARS_OBJ), HS_AI_CONDITION, nCond);
    }
  else
    {
     nCond = nCond & ~nOption;
     SetLocalInt(GetObjectByTag(HS_VARS_OBJ), HS_AI_CONDITION, nCond);
    }
}
//::////////////////////////////////////////////////////////////////////////////
int GetAIOption(int nOption)
{
  int nCond = GetLocalInt(GetObjectByTag(HS_VARS_OBJ), HS_AI_CONDITION);
  return (nCond & nOption);
}
//::////////////////////////////////////////////////////////////////////////////
void HenchBattleCry()
{
  int nBattleCry = d100(1);
  if (GetClassByPosition(1) == CLASS_TYPE_FIGHTER ||
      GetClassByPosition(1) == CLASS_TYPE_RANGER ||
      GetClassByPosition(1) == CLASS_TYPE_BARBARIAN)
    {
     switch (nBattleCry)
       {
        case 1: SpeakString(WARRIOR1); break;
        case 2: SpeakString(WARRIOR2); break;
        case 3: SpeakString(WARRIOR3); break;
        case 4: SpeakString(WARRIOR4); break;
        case 5: SpeakString(WARRIOR5); break;
        case 6: SpeakString(WARRIOR6); break;
        case 7: SpeakString(WARRIOR7); break;
        case 8: SpeakString(WARRIOR8); break;
        default: break;
       }
    }
  else if (GetClassByPosition(1) == CLASS_TYPE_ROGUE)
    {
     switch (nBattleCry)
       {
        case 1: SpeakString(ROGUE1); break;
        case 2: SpeakString(ROGUE2); break;
        case 3: SpeakString(ROGUE3); break;
        case 4: SpeakString(ROGUE4); break;
        case 5: SpeakString(ROGUE5); break;
        case 6: SpeakString(ROGUE6); break;
        case 7: SpeakString(ROGUE7); break;
        case 8: SpeakString(ROGUE8); break;
        default: break;
       }
    }
  else if (GetClassByPosition(1) == CLASS_TYPE_PALADIN)
    {
     switch (nBattleCry)
       {
        case 1: SpeakString(PALADIN1); break;
        case 2: SpeakString(PALADIN2); break;
        case 3: SpeakString(PALADIN3); break;
        case 4: SpeakString(PALADIN4); break;
        case 5: SpeakString(PALADIN5); break;
        case 6: SpeakString(PALADIN6); break;
        case 7: SpeakString(PALADIN7); break;
        case 8: SpeakString(PALADIN8); break;
        default: break;
       }
    }
  else if (GetClassByPosition(1) == CLASS_TYPE_CLERIC)
    {
     switch (nBattleCry)
       {
        case 1: SpeakString(CLERIC1); break;
        case 2: SpeakString(CLERIC2); break;
        case 3: SpeakString(CLERIC3); break;
        case 4: SpeakString(CLERIC4); break;
        case 5: SpeakString(CLERIC5); break;
        case 6: SpeakString(CLERIC6); break;
        case 7: SpeakString(CLERIC7); break;
        case 8: SpeakString(CLERIC8); break;
        default: break;
       }
    }
  else if (GetClassByPosition(1) == CLASS_TYPE_DRUID)
    {
     switch (nBattleCry)
       {
        case 1: SpeakString(DRUID1); break;
        case 2: SpeakString(DRUID2); break;
        case 3: SpeakString(DRUID3); break;
        case 4: SpeakString(DRUID4); break;
        case 5: SpeakString(DRUID5); break;
        case 6: SpeakString(DRUID6); break;
        case 7: SpeakString(DRUID7); break;
        case 8: SpeakString(DRUID8); break;
        default: break;
       }
    }
  else if (GetClassByPosition(1) == CLASS_TYPE_SORCERER ||
           GetClassByPosition(1) == CLASS_TYPE_WIZARD)
    {
     switch (nBattleCry)
       {
        case 1: SpeakString(MAGE1); break;
        case 2: SpeakString(MAGE2); break;
        case 3: SpeakString(MAGE3); break;
        case 4: SpeakString(MAGE4); break;
        case 5: SpeakString(MAGE5); break;
        case 6: SpeakString(MAGE6); break;
        case 7: SpeakString(MAGE7); break;
        case 8: SpeakString(MAGE8); break;
        default: break;
       }
    }
  else if (GetClassByPosition(1) == CLASS_TYPE_BARD)
    {
     switch (nBattleCry)
       {
        case 1: SpeakString(BARD1); break;
        case 2: SpeakString(BARD2); break;
        case 3: SpeakString(BARD3); break;
        case 4: SpeakString(BARD4); break;
        case 5: SpeakString(BARD5); break;
        case 6: SpeakString(BARD6); break;
        case 7: SpeakString(BARD7); break;
        case 8: SpeakString(BARD8); break;
        default: break;
       }
    }
  else if (GetClassByPosition(1) == CLASS_TYPE_MONK)
    {
     switch (nBattleCry)
       {
        case 1: SpeakString(MONK1); break;
        case 2: SpeakString(MONK2); break;
        case 3: SpeakString(MONK3); break;
        case 4: SpeakString(MONK4); break;
        case 5: SpeakString(MONK5); break;
        case 6: SpeakString(MONK6); break;
        case 7: SpeakString(MONK7); break;
        case 8: SpeakString(MONK8); break;
        default: break;
       }
    }
}
//::////////////////////////////////////////////////////////////////////////////
//void main(){}
