// v2.1.0 - Made 3 slight changes to HenchTalentMeleeAttack().
// v2.0.0 -
//::////////////////////////////////////////////////////////////////////////////
//:: FileName:  hark_i0_melee
//::////////////////////////////////////////////////////////////////////////////
/*
   This file contains a modified form of the original TalentMeleeAttack.
   Major modification to not randomly pick feats to use.
*/
//::////////////////////////////////////////////////////////////////////////////
#include "hark_i0_hench_ai"
//::////////////////////////////////////////////////////////////////////////////


// * FUNCTION PROTOTYPES


// Determine whether the specified X0_COMBAT_FLAG_* is set on the target.
int GetCombatCondition(int nCond, object oTarget = OBJECT_SELF);

// Special tactics for cowardly creatures
// Cowards act as follows:
// - if you and your friends outnumber the enemy by 6:1 or
//   by more than 10, fall through to normal combat.
// - if you are currently being attacked by a melee attacker,
//   fight defensively (see SpecialTacticsDefensive).
// - if there is a "NW_SAFE" waypoint in your area that is
//   out of sight of the target, run to it.
// - otherwise, run away randomly from the target.
// Returns TRUE on success, FALSE on failure.
int SpecialTacticsCowardly(object oTarget);

// Special tactics for defensive fighters
// This will attempt to use the active defensive feats such as
// Knockdown and Expertise, and also use Parry mode, when these
// are appropriate. Falls through to standard combat on failure.
// Returns TRUE on success, FALSE on failure.
int SpecialTacticsDefensive(object oTarget);

// Does a check to determine if the NPC has an attempted spell or attack target.
int GetIsFighting(object oFighting);

int MeleeAttackBonus(object oTarget = OBJECT_SELF);

int RangeAttackBonus(object oTarget = OBJECT_SELF);

// ISSUE 1: Talent Melee Attack should set the Last Spell Used to 0 so that
// melee casters can use a single special ability. Auldar: Made changes here to
// use Taunt when appropriate as well as more accurate calculations for To-Hit
// vs Target AC. Tony: Made major changes in using attack feats. Heavily
// modified code from Jasperre.
int HenchTalentMeleeAttack(object oIntruder, float fThresholdDistance, int iMeleeAttackers, int iCreatureType);

// Returns TRUE if the given opponent is a melee attacker, meaning they are in
// melee range and equipped with a melee weapon.
int GetIsMeleeAttacker(object oAttacker);


// * FUNCTION DEFINITIONS


//::////////////////////////////////////////////////////////////////////////////
int GetCombatCondition(int nCond, object oTarget = OBJECT_SELF)
{
  return (GetLocalInt(oTarget, X0_COMBAT_CONDITION) & nCond);
}
//::////////////////////////////////////////////////////////////////////////////
int SpecialTacticsCowardly(object oTarget)
{
  // See what our overall situation looks like.
  struct sSituation sitCurrent = CountEnemiesAndAllies();
  if (((sitCurrent.ENEMY_NUM + 10) <= (sitCurrent.ALLY_NUM + 1)) ||
      ((sitCurrent.ENEMY_NUM * 6) <= (sitCurrent.ALLY_NUM + 1)))
    {
     // Me and my pals really outnumber the enemy.
     Debug("Cowardly, but we outnumber the enemy");
     Debug("Enemies: " + IntToString(sitCurrent.ENEMY_NUM));
     Debug("Allies: " + IntToString(sitCurrent.ALLY_NUM));
     return FALSE;
    }

  // Are we pinned down by a melee attacker?
  object oLastEnemy = GetLastHostileActor();
  if (GetIsObjectValid(oLastEnemy) && GetIsMeleeAttacker(oLastEnemy) &&
      GetAttackTarget(oLastEnemy)==OBJECT_SELF)
    {
     // We're being attacked, so try and hold 'em off.
     Debug("Under melee attack by " + GetName(oLastEnemy));
     return SpecialTacticsDefensive(oLastEnemy);
    }

  // Try and find someplace safe to run to.
  int nNth = 1;
  object oSafe = GetNearestObjectByTag("NW_SAFE", OBJECT_SELF, nNth);
  while (GetIsObjectValid(oSafe) && GetObjectSeen(oSafe, oTarget))
    {
     nNth++;
     oSafe = GetNearestObjectByTag("NW_SAFE", OBJECT_SELF, nNth);
    }

  if (GetIsObjectValid(oSafe))
    {
     Debug("Running to safepoint");
     ClearActions(CLEAR_I0_MELEE_SpecialTacticsCowardly_1);
     ActionMoveToObject(oSafe);
     return TRUE;
    }

  // Failed, just try and run anywhere.
  Debug("Running away anywhere");
  ClearActions(CLEAR_I0_MELEE_SpecialTacticsCowardly_2);
  ActionMoveAwayFromObject(oTarget, TRUE, 30.0);
  return TRUE;
}
//::////////////////////////////////////////////////////////////////////////////
int SpecialTacticsDefensive(object oTarget)
{
  int nFeat = -1;
  int nSkill = -1;

  // Use knockdown occasionally if we have it and the target is not immune.
  int nRand = Random(4);
  if (nRand == 0)
    {
     int nMySize = GetCreatureSize(OBJECT_SELF);
     if (GetHasFeat(FEAT_IMPROVED_KNOCKDOWN))
       {
        nFeat = FEAT_IMPROVED_KNOCKDOWN;
        nMySize++;
       }
     else if (GetHasFeat(FEAT_KNOCKDOWN))
       { nFeat = FEAT_KNOCKDOWN; }

     // Prevent silly use of knockdown on immune or too-large targets.
     if (GetIsImmune(oTarget, IMMUNITY_TYPE_KNOCKDOWN) ||
         GetCreatureSize(oTarget) > nMySize + 1)
       { nFeat = -1; }
    }

  // We use improved expertise if BAB+10 > opponent AC.
  // We use expertise if BaseAttackBonus+15 > opponent AC.
  // If we have both, use both.
  int tempExpertise = 389;
  int tempImprovedExpertise = 390;
  int bHasExpertise = GetHasFeat(tempExpertise);
  int bHasImprovedExpertise = GetHasFeat(tempImprovedExpertise);
  if (nFeat == -1 && (bHasExpertise || bHasImprovedExpertise))
    {
     int nBAB = GetBaseAttackBonus(OBJECT_SELF);
     int nAC = GetAC(oTarget);
     if (nBAB + 15 > nAC)
       {
        // We can use either.
        if (nRand < 2)
          { nFeat = tempImprovedExpertise; }
        else
          { nFeat = tempExpertise; }
       }
     else if (nBAB + 10 > nAC)
       {
        // Only use improved.
        nFeat = tempImprovedExpertise;
       }
    }

  if (nFeat != -1)
    {
     Debug("Defensive combat: Using feat: " + IntToString(nFeat));
     //bkEquipAppropriateWeapons(oTarget);
     ActionUseFeat(nFeat, oTarget);
     return TRUE;
    }

  // Only use parry on an active melee attacker, and only if our parry skill
  // is > our AC - 10.
  object oEnemy = GetLastHostileActor();
  if (GetIsObjectValid(oEnemy) && GetAttackTarget(oEnemy) == OBJECT_SELF &&
      GetIsMeleeAttacker(oEnemy))
    {
     int nParrySkill = GetSkillRank(SKILL_PARRY);
     int nAC = GetAC(OBJECT_SELF);
     if (nParrySkill > (nAC - 10))
       {
        Debug("Defensive combat: Using parry skill ");
        //bkEquipAppropriateWeapons(oTarget);
        ActionUseSkill(SKILL_PARRY, oTarget);
        return TRUE;
       }
    }
  Debug("falling through to normal tactics");
  Debug("last hostile: " + GetName(oEnemy));
  Debug("their attack target: " + GetName(GetAttackTarget(oEnemy)));
  return FALSE;
}
//::////////////////////////////////////////////////////////////////////////////
int GetIsFighting(object oFighting)
{
  object oAttack = GetAttemptedAttackTarget();
  object oSpellTarget = GetAttemptedSpellTarget();
  if (GetIsObjectValid(oAttack) || GetIsObjectValid(oSpellTarget))
    { return TRUE; }
  else
    { return FALSE; }
}
//::////////////////////////////////////////////////////////////////////////////
int MeleeAttackBonus(object oTarget = OBJECT_SELF)
{
  int nReturn = 0;

  // Primary weapon. If enchanted, add a few on.
  object oWeaponRight = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oTarget);
  if (GetItemHasItemProperty(oWeaponRight, ITEM_PROPERTY_ENHANCEMENT_BONUS))
    { nReturn++; }
  if (GetItemHasItemProperty(oWeaponRight, ITEM_PROPERTY_ATTACK_BONUS))
    { nReturn++; }

  object oWeaponLeft = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oTarget);
  int nOffHandWeaponSize = GetMeleeWeaponSize(oWeaponLeft);
  if (nOffHandWeaponSize > 0)
    {
     if (nOffHandWeaponSize == GetCreatureSize(oTarget))
       { nReturn -= 2; }
     else
       { nReturn -= 4; }
    }
  // Finess only if we are using a proper weapon (ie no monster attacks).
  int nStrMod = GetAbilityModifier(ABILITY_STRENGTH, oTarget);
  int nDexMod = GetAbilityModifier(ABILITY_DEXTERITY, oTarget);
  if (GetHasFeat(FEAT_WEAPON_FINESSE) && (nDexMod > nStrMod) && GetIsObjectValid(oWeaponRight))
    { nReturn += nDexMod; }
  else
    { nReturn += nStrMod; }
  return nReturn;
}
//::////////////////////////////////////////////////////////////////////////////
int RangeAttackBonus(object oTarget = OBJECT_SELF)
{
  int nReturn = 0;

  // Primary weapon. If enchanted, add a few on.
  object oWeaponRight = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oTarget);
  if (GetItemHasItemProperty(oWeaponRight, ITEM_PROPERTY_ENHANCEMENT_BONUS))
    { nReturn++; }
  if (GetItemHasItemProperty(oWeaponRight, ITEM_PROPERTY_ATTACK_BONUS))
    { nReturn++; }

  int nDexMod = GetAbilityModifier(ABILITY_DEXTERITY, oTarget);
  int nWisMod = GetAbilityModifier(ABILITY_WISDOM, oTarget);
  if (GetHasFeat(FEAT_ZEN_ARCHERY) && (nWisMod > nDexMod) && GetWeaponRanged(oWeaponRight))
    { nReturn += nWisMod; }
  else
    { nReturn += nDexMod; }
  return nReturn;
}
//::////////////////////////////////////////////////////////////////////////////
int HenchTalentMeleeAttack(object oIntruder, float fThresholdDistance, int iMeleeAttackers, int iCreatureType)
{
  object oTarget = oIntruder;

  HenchBattleCry();

  object oRightWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND);
  int bRangedWeapon = GetWeaponRanged(oRightWeapon);
  if (GetHasFeat(FEAT_SNEAK_ATTACK))
    {
     // Sneak Attack. Flanking attack.
     float fRange = iMeleeAttackers ? 3.5 : bRangedWeapon ? 50.0 : 5.0;
     int iEnemyAC, iAC = 100;
     object oMaster = GetRealMaster();
     int bEnemyMaster = FALSE;
     object oLastSneakTarget = GetLocalObject(OBJECT_SELF, "LastSneakTarget");
     object oAttackTarget;
     int nCnt = 1;
     object oEnemy = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, nCnt, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE);
     object oBest = OBJECT_INVALID;
     while (GetIsObjectValid(oEnemy) && GetDistanceToObject(oEnemy) <= fRange)
       {
        if (!GetPlotFlag(oEnemy) && !GetIsImmune(oEnemy, IMMUNITY_TYPE_SNEAK_ATTACK, OBJECT_SELF))
          {
           oAttackTarget = GetAttackTarget(oEnemy);
           if (oAttackTarget != OBJECT_SELF)
             {
              if (oEnemy == oLastSneakTarget)
                {
                 oBest = oEnemy;
                 break;
                }
              iEnemyAC = GetAC(oEnemy);
              if (oAttackTarget == oMaster)
                {
                 if (!bEnemyMaster)
                   {
                    bEnemyMaster = TRUE;
                    iAC = iEnemyAC;
                    oBest = oEnemy;
                   }
                 else if (iAC > iEnemyAC)
                   {
                    iAC = iEnemyAC;
                    oBest = oEnemy;
                   }
                }
              else if (iAC > iEnemyAC)
                {
                 iAC = iEnemyAC;
                 oBest = oEnemy;
                }
             }
          }
        nCnt++;
        oEnemy = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, nCnt, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE);
       }

     if (GetIsObjectValid(oBest))
       {
        oTarget = oBest;
        SetLocalObject(OBJECT_SELF, "LastSneakTarget", oBest);
        HenchEquipAppropriateWeapons(oTarget, fThresholdDistance, iCreatureType == 1);
        ActionAttack(oTarget);
        return TRUE;
       }
    }

  HenchEquipAppropriateWeapons(oTarget, fThresholdDistance, iCreatureType == 1);

  int iAC = GetAC(oTarget);
  int iNewBAB = GetBaseAttackBonus(OBJECT_SELF) + 5 + d4(2);

  if (bRangedWeapon)
    {
     iNewBAB += RangeAttackBonus();

     // Less Get's. We need -2 for all ranged feats.
     if ((iNewBAB - 2) >= iAC)
       {
        // This provides another attack, at -2 to hit.
        if (GetHasFeat(FEAT_RAPID_SHOT))
          {
           ActionUseFeat(FEAT_RAPID_SHOT, oTarget);
           return TRUE;
          }
       }
     ActionAttack(oTarget);
     return TRUE;
    }
  iNewBAB += MeleeAttackBonus();
  // For use against them evil pests! Top - one use only anyway.
  if (GetHasFeat(FEAT_SMITE_EVIL) && GetAlignmentGoodEvil(oTarget) == ALIGNMENT_EVIL &&
     (GetChallengeRating(OBJECT_SELF) - GetChallengeRating(oTarget) <= 2.0))
    {
     ActionUseFeat(FEAT_SMITE_EVIL, oTarget);
     return TRUE;
    }
  if (d6() == 1 && !GetIsImmune(oTarget, IMMUNITY_TYPE_CRITICAL_HIT, OBJECT_SELF) && (iNewBAB >= iAC))
    {
     int bHasQuiveringPalm = GetHasFeat(FEAT_QUIVERING_PALM);
     int bHasStunningFist = GetHasFeat(FEAT_STUNNING_FIST);
     int bHasSAP = GetHasFeat(FEAT_SAP);
     int bHasCalledShot = GetHasFeat(FEAT_CALLED_SHOT) && d3() == 1;
     if ((bHasQuiveringPalm || bHasStunningFist || bHasSAP || bHasCalledShot) &&
         (GetChallengeRating(OBJECT_SELF) - GetChallengeRating(oTarget)) <= 2.0  &&
        ((GetHitDice(OBJECT_SELF) / 2 + 10 + GetAbilityModifier(ABILITY_WISDOM)) >=
         (GetFortitudeSavingThrow(oTarget) + 5 + Random(15))))
       {
        if (bHasQuiveringPalm && !GetIsObjectValid(oRightWeapon))
          {
           ActionUseFeat(FEAT_QUIVERING_PALM, oTarget);
           return TRUE;
          }
        if (!GetHasEffect3(EFFECT_TYPE_STUNNED, EFFECT_TYPE_PARALYZE, EFFECT_TYPE_DAZED, oTarget))
          {
           if (bHasStunningFist)
             {
              int iMod = iNewBAB;
              if (GetIsObjectValid(oRightWeapon) || (GetLevelByClass(CLASS_TYPE_MONK) == 0))
                { iMod -= 4; }
              if (iMod >= iAC)
                {
                 ActionUseFeat(FEAT_STUNNING_FIST, oTarget);
                 return TRUE;
                }
             }
           // OK, not for PCs, but may be on an NPC. -4 Attack. Above the one below.
           else if (bHasSAP)
             {
              if ((iNewBAB - 4) >= iAC)
                {
                 ActionUseFeat(FEAT_SAP, oTarget);
                 return TRUE;
                }
             }
           // At a -2 to hit, this can disarm the arms or legs. Speed or Attack bonus.
           else if (((iNewBAB - 2) >= iAC) && !GetHasFeatEffect(FEAT_CALLED_SHOT, oTarget))
             {
              ActionUseFeat(FEAT_CALLED_SHOT, oTarget);
              return TRUE;
             }
          }
       }
    }
  // Tony K: Add chance for knockdown?
  int bHasImprovedKnockdown = GetHasFeat(FEAT_IMPROVED_KNOCKDOWN);
  if ((bHasImprovedKnockdown || GetHasFeat(FEAT_KNOCKDOWN)) &&
      !GetIsImmune(oTarget, IMMUNITY_TYPE_KNOCKDOWN, OBJECT_SELF) &&
      !GetHasFeatEffect(FEAT_KNOCKDOWN, oTarget) &&
      !GetHasFeatEffect(FEAT_IMPROVED_KNOCKDOWN, oTarget))
    {
     // By far the BEST feat to use - knocking them down lets you freely attack
     // them! These return 1-5, based on size.
     int iOurSize = GetCreatureSize(OBJECT_SELF);
     int iTheirSize = GetCreatureSize(oTarget);
     if (bHasImprovedKnockdown)
       { iOurSize++; }
     int bUse = iOurSize > iTheirSize;
     if (!bUse && ((iOurSize + 1) >= iTheirSize))
       {
        int iMod = iNewBAB - 4;
        // Check if one size larger.
        if (iOurSize != iTheirSize)
          { iMod -= 4; }
        if (iMod >= iAC)
          { bUse = iMod > GetSkillRank(SKILL_DISCIPLINE, oTarget); }
       }
     if (bUse)
       {
        ActionUseFeat(bHasImprovedKnockdown ? FEAT_IMPROVED_KNOCKDOWN : FEAT_KNOCKDOWN, oTarget);
        return TRUE;
       }
    }

  if (GetCombatCondition(X0_COMBAT_FLAG_DEFENSIVE))
    {
     int bHasExpertise = GetHasFeat(FEAT_EXPERTISE);
     int bHasImprovedExpertise = GetHasFeat(FEAT_IMPROVED_EXPERTISE);
     if (bHasExpertise || bHasImprovedExpertise)
       {
        if (bHasImprovedExpertise && (iNewBAB - 10) >= iAC)
          {
           ActionUseFeat(FEAT_IMPROVED_EXPERTISE, oTarget);
           return TRUE;
          }
        if ((iNewBAB - 5) >= iAC)
          {
           ActionUseFeat(FEAT_EXPERTISE, oTarget);
           return TRUE;
          }
       }

     // Only use parry on an active melee attacker, and only if our parry
     // skill > our AC - 10.
     object oEnemy = GetLastHostileActor();
     if (GetIsObjectValid(oEnemy) && GetAttackTarget(oEnemy) == OBJECT_SELF &&
         GetIsMeleeAttacker(oEnemy))
       {
        int nParrySkill = GetSkillRank(SKILL_PARRY);
        int nAC = GetAC(OBJECT_SELF);
        if (nParrySkill > (nAC - 10) )
          {
           ActionUseSkill(SKILL_PARRY, oTarget);
           return TRUE;
          }
       }
    }

  // Auldar: Give 10% chance to Taunt if target is within 3.5 meters and is a
  // challenge, if Skill points have been spent in Taunt skill indicating
  // intention to use, and a taunt isn't in effect.
  if ((d10 () == 1) && ((GetChallengeRating(OBJECT_SELF) - GetChallengeRating(oTarget) <= 2.0) ||
      (GetHitDice(oTarget) > (GetHitDice(OBJECT_SELF) - 2))) &&
      (GetDistanceToObject(oTarget) <= 3.5))
    {
     // Auldar: Adding a check for the Taunt skill, and ensuring that no one
     // with ONLY a CHA mod to Taunt will use the skill. This confirms that some
     // points are spent in the skill indicating an intention for the NPC to use
     // them. Also using 69MEH69's idea to check for a negative modifier so we
     // don't subtract a negative number (ie add) to the skill check.
     int chaCheck = GetAbilityModifier(ABILITY_CHARISMA);
     int tauCheck = GetSkillRank(SKILL_TAUNT);
     if (((tauCheck - chaCheck) > 0) && ((tauCheck + 5 + d4(2)) > GetSkillRank(SKILL_CONCENTRATION, oTarget)))
       {
        ActionUseSkill(SKILL_TAUNT, oTarget);
        return TRUE;
       }
    }
  object oTargetRightWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oTarget);
  if (d10() == 1 && (GetHasFeat(FEAT_IMPROVED_DISARM) || GetHasFeat(FEAT_DISARM)) &&
      GetIsObjectValid(oRightWeapon))
    {
     if (GetIsObjectValid(oTargetRightWeapon) && !GetWeaponRanged(oTargetRightWeapon))
       {
        int iWeaponSize = GetMeleeWeaponSize(oRightWeapon);
        int iTargetWeaponSize = GetMeleeWeaponSize(oTargetRightWeapon);

        // No AOO, and only a -4 penalty to hit.
        if (GetHasFeat(FEAT_IMPROVED_DISARM))
          {
           // Apply weapon size penalites/bonuses to check - Use right weapons.
           int iMod = iWeaponSize - iTargetWeaponSize;
           if (iMod != 0)
             { iMod += (iMod * 4); }
           if ((iNewBAB - 4 + iMod) >= iAC)
             {
              ActionUseFeat(FEAT_IMPROVED_DISARM, oTarget);
              return TRUE;
             }
          }
        // Provokes an AOO. Improved does not, but this is -6, and bonuses
        // depend on weapons used (sizes).
        else if (d2() == 1)
          {
           // Apply weapon size penalites/bonuses to check - Use right weapons.
           int iMod = iWeaponSize - iTargetWeaponSize;
           if (iMod != 0)
             { iMod += (iMod * 4); }
           if ((iNewBAB - 6 + iMod) >= iAC)
             {
              ActionUseFeat(FEAT_DISARM, oTarget);
              return TRUE;
             }
          }
       }
    }

  // This activates an extra attack, at -2 to hit. Of course, only unarmed and Kama.
  if (GetHasFeat(FEAT_FLURRY_OF_BLOWS) && ((iNewBAB - 2) >= iAC) && (!GetIsObjectValid(oRightWeapon) ||
     (GetBaseItemType(oRightWeapon) == BASE_ITEM_KAMA)))
    {
     ActionUseFeat(FEAT_FLURRY_OF_BLOWS, oTarget);
     return TRUE;
    }
  // -10 to hit - make sure by extra 5.
  if (GetHasFeat(FEAT_IMPROVED_POWER_ATTACK) && ((iNewBAB - 15) >= iAC))
    {
     ActionUseFeat(FEAT_IMPROVED_POWER_ATTACK, oTarget);
     return TRUE;
    }
  // Is a -5 to hit - make sure by extra 5.
  if (GetHasFeat(FEAT_POWER_ATTACK) && ((iNewBAB - 10) >= iAC))
    {
     ActionUseFeat(FEAT_POWER_ATTACK, oTarget);
     return TRUE;
    }
  ActionAttack(oTarget);
  return TRUE;
}
//::////////////////////////////////////////////////////////////////////////////
int GetIsMeleeAttacker(object oAttacker)
{
  if (GetDistanceToObject(oAttacker) > HS_MELEE_DISTANCE)
    { return FALSE; }
  object oRight = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oAttacker);
  if (GetIsObjectValid(oRight) && MatchMeleeWeapon(oRight))
    { return TRUE; }
  object oLeft = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oAttacker);
  if (GetIsObjectValid(oLeft) && MatchMeleeWeapon(oLeft))
    { return TRUE; }
  return FALSE;
}
//::////////////////////////////////////////////////////////////////////////////
//void main(){}
