///////////////////////////////////////////////////////////////////////////////
//
// MPBScriptedPawn
//
// This is the multiplayer bot class that is used for bot MP games.
//
// Originally created by EdGann
//
// May, 20002: Heavily modified by DarksTalkeR (chrism_hopkins@yahoo.com)
//             to bring it up to speed for MP games.
//
///////////////////////////////////////////////////////////////////////////////

class BOTScriptedPawn expands ScriptedPawn;

///////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------
// state Attacking
// ----------------------------------------------------------------------

// Store the last 10 navigation points so we don't use them again
var int             NumSavedNavPoints;
var int             MaxSavedNavPoints;
var NavigationPoint LastNavPoint[10];
var int             NextPointIndex;
var int             NumTimesWithNoDestination;
var bool            bNeedToSwitchToBestWeapon;

State Attacking
{
  function ReactToInjury(Pawn instigatedBy, Name damageType,
			 EHitLocation hitPos)
  {
    local Pawn oldEnemy;
    local bool bHateThisInjury;
    local bool bFearThisInjury;
    
    if ((health > 0) && (bLookingForInjury || bLookingForIndirectInjury))
    {
      oldEnemy = Enemy;

      bHateThisInjury = ShouldReactToInjuryType(damageType, bHateInjury,
						bHateIndirectInjury);
      bFearThisInjury = ShouldReactToInjuryType(damageType, bFearInjury,
						bFearIndirectInjury);

      if (bHateThisInjury)
	IncreaseAgitation(instigatedBy, 1.0);

      if (bFearThisInjury)
	IncreaseFear(instigatedBy, 2.0);
      
      if (ReadyForNewEnemy())
	SetEnemy(instigatedBy);

      if (ShouldFlee())
      {
	SetDistressTimer();
	PlayCriticalDamageSound();
	if (RaiseAlarm == RAISEALARM_BeforeFleeing)
	  SetNextState('Alerting');
	else
	  SetNextState('Fleeing');
      }
      else
      {
	SetDistressTimer();
	if (oldEnemy != Enemy)
	  PlayNewTargetSound();
	SetNextState('Attacking', 'ContinueAttack');
      }
      GotoDisabledState(damageType, hitPos);
    }
  }

  function SetFall()
  {
    StartFalling('Attacking', 'ContinueAttack');
  }

  function HitWall(vector HitNormal, actor Wall)
  {
    if (Physics == PHYS_Falling)
      return;
    Global.HitWall(HitNormal, Wall);
    CheckOpenDoor(HitNormal, Wall);
  }

  function Reloading(DeusExWeapon reloadWeapon, float reloadTime)
  {
    Global.Reloading(reloadWeapon, reloadTime);
    if (bReadyToReload)
      if (IsWeaponReloading())
	if (!IsHandToHand())
	  TweenToShoot(0);
  }

  function EDestinationType PickDestination()
  {
    local vector               distVect;
    local vector               tempVect;
    local rotator              enemyDir;
    local float                magnitude;
    local float                calcMagnitude;
    local int                  iterations;
    local EDestinationType     destType;
    local NearbyProjectileList projList;

    destPoint = None;
    destLoc   = vect(0, 0, 0);
    destType  = DEST_Failure;

    if (enemy == None)
      return (destType);

    if (bCrouching && (CrouchTimer > 0))
      destType = DEST_SameLocation;

    if (destType == DEST_Failure)
    {
      //log("$$ Calling AICanShoot...");
      if (AICanShoot(enemy, true, false, 0.025) || ActorReachable(enemy))
      {
	destType = ComputeBestFiringPosition(tempVect);
	if (destType == DEST_NewLocation)
	  destLoc = tempVect;
      }
    }
    
    if (destType == DEST_Failure)
    {
      MoveTarget = FindPathToward(enemy);
      if (MoveTarget != None)
      {
	if (!bDefendHome || IsNearHome(MoveTarget.Location))
	{
	  if (bAvoidHarm)
	    GetProjectileList(projList, MoveTarget.Location);
	  if (!bAvoidHarm || !IsLocationDangerous(projList,
						  MoveTarget.Location))
	  {
	    destPoint = MoveTarget;
	    destType  = DEST_NewLocation;
	  }
	}
      }
    }

    // Default behavior, so they don't just stand there...
    if (destType == DEST_Failure)
    {
      enemyDir = Rotator(Enemy.Location - Location);
      if (AIPickRandomDestination(60, 150,
				  enemyDir.Yaw, 0.5, enemyDir.Pitch, 0.5, 
				  2, FRand()*0.4+0.35, tempVect))
      {
	if (!bDefendHome || IsNearHome(tempVect))
	{
	  destType = DEST_NewLocation;
	  destLoc  = tempVect;
	}
      }
    }
    
    return (destType);
  }

  function bool FireIfClearShot()
  {
    local DeusExWeapon dxWeapon;
    
    dxWeapon = DeusExWeapon(Weapon);

    // MPBots EdGann KLUDGE: bReadyToFire doesn't seem to work right for
    //                       Scripted Pawns. So I set it and execute
    //                       ReadyToFire() automatically.
    dxWeapon.bReadyToFire=false;
    dxWeapon.ReadyToFire();

    //log("    Firing Weapon " $ dxWeapon);
    if (dxWeapon != None)
    {
      if ((dxWeapon.AIFireDelay > 0) && (FireTimer > 0))
      {
        //log("    AIFireDelay : " $ dxWeapon.AIFireDelay);
        //log("    FireTimer   : " $ FireTimer);
        //log("    Returning false from FireIfClearShot...");
	return false;
      }
      else if (AICanShoot(enemy, true, true, 0.025))
      {
        //log("    FIRING! " $ Weapon $ " !FIRING");

	// MPBots EdGann: Changed from Fire to ClientFire.
	Weapon.ClientFire(0);

	FireTimer = dxWeapon.AIFireDelay;
	return true;
      }
      else
      {
        //log("    Returning false(2) from FireIfClearShot...");
	return false;
      }
    }
    else
      return false;
  }

  function CheckAttack(bool bPlaySound)
  {
    local bool bCriticalDamage;
    local bool bOutOfAmmo;
    local Pawn oldEnemy;
    local bool bAllianceSwitch;
    
    oldEnemy = enemy;
    
    bAllianceSwitch = false;

    // DTR: Old style check. We now use IsValidMPEnemy
    // if (!IsValidEnemy(enemy))
    //log("    checking valid mp enemy 1");
    if (!IsValidMPEnemy(enemy))
    {
      //log("    My enemy is not valid...");

      // DTR: Old style check. We now use IsValidMPEnemy
      // if (IsValidEnemy(enemy, false))
//      //log("    checking valid mp enemy 2");
//      if (IsValidMPEnemy(enemy))
//        bAllianceSwitch = true;

      SetEnemy(None, 0, true);
    }

    if (enemy == None && Orders == 'Attacking')
    {
      //log("    No enemy and my orders are to attack...finding new one");
      FindOrderActor();
      SetEnemy(Pawn(OrderActor), 0, true);
    }

    if (ReadyForNewEnemy())
    {
      //log("    I'm ready for a new enemy...");
      FindBestEnemy(false);
    }

    if (Enemy == None)
    {
      //log("    My enemy is null...back to normal.");

      Enemy = oldEnemy;  // hack
      if (bPlaySound)
      {
	if (bAllianceSwitch)
	  PlayAllianceFriendlySound();
	else
	  PlayAreaSecureSound();
      }

      Enemy = None;
      if (Orders != 'Attacking')
      {
        //log("    Following orders of " $ Orders);
	FollowOrders();
      }
      else
      {
        //log("    Wandering around for a bit...");
	GotoState('Wandering');
      }

      return;
    }

    SwitchToBestWeapon();
    //log("    Weapon is " $ Weapon);

    if (bCrouching && (CrouchTimer <= 0) && !ShouldCrouch())
    {
      EndCrouch();
      TweenToShoot(0.15);
    }

    bCriticalDamage = False;
    bOutOfAmmo      = False;

    if (ShouldFlee())
    {
      //log("    We need to run...seeya");
      bCriticalDamage = True;
    }
    else if (Weapon == None)
    {
      //log("    Weapon is None...we must be out of ammo");
      bOutOfAmmo = True;
    }
    else if (Weapon.ReloadCount > 0)
    {
      if (Weapon.AmmoType == None || Weapon.AmmoType.AmmoAmount < 1)
      {
        //log("    Our reloadcount is > 0 with AmmoType " $ Weapon.AmmoType);
	bOutOfAmmo = True;
      }
    }

    // DTR: IFF we are out of ammo, we need to switch to our best weapon
    if(bOutOfAmmo)
      bNeedToSwitchToBestWeapon = True;

    if (bCriticalDamage || bOutOfAmmo)
    {
      if (bPlaySound)
      {
	if (bCriticalDamage)
	  PlayCriticalDamageSound();
	else if (bOutOfAmmo)
	  PlayOutOfAmmoSound();
      }

      if (RaiseAlarm == RAISEALARM_BeforeFleeing)
	GotoState('Alerting');
      else
	GotoState('Fleeing');
    }
    else if (bPlaySound && (oldEnemy != Enemy))
      PlayNewTargetSound();
  }

  function Tick(float deltaSeconds)
  {
    local bool   bCanSee;
    local float  yaw;
    local vector lastLocation;
    local Pawn   lastEnemy;
    local float  surpriseTime;

    Global.Tick(deltaSeconds);

    if (CrouchTimer > 0)
    {
      CrouchTimer -= deltaSeconds;
      if (CrouchTimer < 0)
	CrouchTimer = 0;
    }

    EnemyTimer += deltaSeconds;

    UpdateActorVisibility(Enemy, deltaSeconds, 1.0, false);

    if ((Enemy != None) && HasEnemyTimedOut())
    {
      //log("  Enemy has time out...");
      lastLocation = Enemy.Location;
      lastEnemy    = Enemy;

      FindBestEnemy(true);

      if (Enemy == None)
      {
        //log("  Seeking new enemy...");
        SetSeekLocation(lastEnemy, lastLocation, SEEKTYPE_Guess, true);
        GotoState('Seeking');
      }
    }
    else if (bCanFire && (Enemy != None))
    {
      ViewRotation = Rotator(Enemy.Location-Location);

      if (bFacingTarget)
      {
        //log("  We are facing our target...firing.");
	FireIfClearShot();
      }
      else if (!bMustFaceTarget)
      {
	yaw = (ViewRotation.Yaw-Rotation.Yaw) & 0xFFFF;

	if (yaw >= 32768)
	  yaw -= 65536;

	yaw = Abs(yaw)*360/32768;  // 0-180 x 2

	if (yaw <= FireAngle)
        {
          //log("  We are not facing...but we're close enough. firing...");
	  FireIfClearShot();
	}
      }
    }

    //UpdateReactionLevel(true, deltaSeconds);
  }

  function bool IsHandToHand()
  {
    if (Weapon != None)
    {
      if (DeusExWeapon(Weapon) != None)
      {
	if (DeusExWeapon(Weapon).bHandToHand)
	  return true;
	else
	  return false;
      }
      else
	return false;
    }
    else
      return false;
  }
  
  function bool ReadyForWeapon()
  {
    local bool bReady;
    
    bReady = false;
    if (DeusExWeapon(weapon) != None)
    {
      if (DeusExWeapon(weapon).bReadyToFire)
	if (!IsWeaponReloading())
	  bReady = true;
    }

    if (!bReady)
      if (enemy == None)
	bReady = true;

    if (!bReady)
    {
      //log("$$ Calling AICanShoot 2");
      if (!AICanShoot(enemy, true, false, 0.025))
	bReady = true;
    }
    
    return (bReady);
  }

  function bool ShouldCrouch()
  {
    if (bCanCrouch && !Region.Zone.bWaterZone && !IsHandToHand() &&
	((enemy != None) && (VSize(enemy.Location-Location) > 300)) &&
	((DeusExWeapon(Weapon) == None) ||
	 DeusExWeapon(Weapon).bUseWhileCrouched))
      return true;
    else
      return false;
  }

  function StartCrouch()
  {
    if (!bCrouching)
    {
      bCrouching = true;
      SetBasedPawnSize(CollisionRadius, GetCrouchHeight());
      CrouchTimer = 1.0+FRand()*0.5;
    }
  }
  
  function EndCrouch()
  {
    if (bCrouching)
    {
      bCrouching = false;
      ResetBasedPawnSize();
    }
  }

  function BeginState()
  {
    StandUp();

    // hack
    if (MaxRange < MinRange+10)
      MaxRange = MinRange+10;
    bCanFire      = false;
    bFacingTarget = false;
    
    SwitchToBestWeapon();
    
    //EnemyLastSeen = 0;
    BlockReactions();
    bCanConverse = False;
    bAttacking = True;
    bStasis = False;
    SetDistress(true);
    
    CrouchTimer = 0;
    EnableCheckDestLoc(false);
  }

  function EndState()
  {
    EnableCheckDestLoc(false);
    bCanFire      = false;
    bFacingTarget = false;
    
    ResetReactions();
    bCanConverse = True;
    bAttacking = False;
    bStasis = True;
    bReadyToReload = false;
    
    EndCrouch();
  }

Begin:
  //log("    Attacking - Begin:");
  if (Enemy == None)
  {
    //log("    We have no enemy...going to Seeking state.");
    GotoState('Seeking');
  }

  //EnemyLastSeen = 0;
  //log("    Checking attack...");
  CheckAttack(false);

Surprise:
/*
  //log("    Attacking - Surprise:");
  if ((1.0 - ReactionLevel) * SurprisePeriod < 0.25)
    Goto('BeginAttack');

  Acceleration=vect(0,0,0);
  PlaySurpriseSound();
  PlayWaiting();

  while (ReactionLevel < 1.0)
  {
    TurnToward(Enemy);
    Sleep(0);
  }
*/

BeginAttack:
  //log("    Attacking - BeginAttack:");
  EnemyReadiness = 1.0;
  ReactionLevel  = 1.0;

  if (PlayerAgitationTimer > 0)
    PlayAllianceHostileSound();
  else
    PlayTargetAcquiredSound();

  if (PlayBeginAttack())
  {
    Acceleration = vect(0,0,0);
    TurnToward(enemy);
    FinishAnim();
  }

RunToRange:
  //log("    Attacking - RunToRange:");
  bCanFire       = false;
  bFacingTarget  = false;
  bReadyToReload = false;
  EndCrouch();

  if (Physics == PHYS_Falling)
    TweenToRunning(0.05);

  WaitForLanding();

  if (!IsWeaponReloading() || bCrouching)
  {
    if (ShouldPlayTurn(Enemy.Location))
      PlayTurning();

    TurnToward(enemy);
  }
  else
    Sleep(0);

  bCanFire = true;
  while (PickDestination() == DEST_NewLocation)
  {
    if (bCanStrafe && ShouldStrafe())
    {
      PlayRunningAndFiring();
      if (destPoint != None)
	StrafeFacing(destPoint.Location, enemy);
      else
	StrafeFacing(destLoc, enemy);
      bFacingTarget = true;
    }
    else
    {
      bFacingTarget = false;
      PlayRunning();
      if (destPoint != None)
	MoveToward(destPoint, MaxDesiredSpeed);
      else
	MoveTo(destLoc, MaxDesiredSpeed);
    }

    CheckAttack(true);
  }

Fire:
  //log("    Attacking - Fire:");
  bCanFire      = false;
  bFacingTarget = false;
  Acceleration = vect(0, 0, 0);
  
  SwitchToBestWeapon();

  if (FRand() > 0.5)
    bUseSecondaryAttack = true;
  else
    bUseSecondaryAttack = false;
  //log("          $$ bUseSecondaryAttack: " $ bUseSecondaryAttack);

  if (IsHandToHand())
    TweenToAttack(0.15);
  else if (ShouldCrouch() && (FRand() < CrouchRate))
  {
    //log("          $$ Crouching...");
    TweenToCrouchShoot(0.15);
    FinishAnim();
    StartCrouch();
  }
  else
  {
    TweenToShoot(0.15);
  }

  if (!IsWeaponReloading() || bCrouching)
  {
    TurnToward(enemy);
  }

  FinishAnim();
  bReadyToReload = true;

ContinueFire:
  //log("    Attacking - ContinueFire:");
  while (!ReadyForWeapon())
  {
    if (PickDestination() != DEST_SameLocation)
    {
      Goto('RunToRange');
    }

    CheckAttack(true);

    if (!IsWeaponReloading() || bCrouching)
      TurnToward(enemy);
    else
      Sleep(0);
  }

  CheckAttack(true);

  //log("    Checking for clear shot");
  if (!FireIfClearShot())
  {
    //log("    No clear shot! Continuing attack!");
    Goto('ContinueAttack');
  }

  bReadyToReload = false;
  if (bCrouching)
    PlayCrouchShoot();
  else if (IsHandToHand())
    PlayAttack();
  else
    PlayShoot();

  FinishAnim();

  if (FRand() > 0.5)
    bUseSecondaryAttack = true;
  else
    bUseSecondaryAttack = false;

  bReadyToReload = true;

  if (!IsHandToHand())
  {
    if (bCrouching)
      TweenToCrouchShoot(0);
    else
      TweenToShoot(0);
  }

  CheckAttack(true);

  if (PickDestination() != DEST_NewLocation)
  {
    if (!IsWeaponReloading() || bCrouching)
      TurnToward(enemy);
    else
      Sleep(0);

    Goto('ContinueFire');
  }

  Goto('RunToRange');

ContinueAttack:
  //log("    Attacking - ContinueAttack:");
ContinueFromDoor:
  //log("    Attacking - ContinueFromDoor:");
  CheckAttack(true);
  if (PickDestination() != DEST_NewLocation)
    Goto('Fire');
  else
    Goto('RunToRange');
}


//=============================================================================
// (DTR)
//
// Override this function so we can find out what exactly is going on here
//=============================================================================

function FollowOrders(optional bool bDefer)
{
  local bool bSetEnemy;
  local bool bUseOrderActor;

  //log("$$ Inside of FollowOrders: bDefer is : " $ bDefer);
  //log("$$ Inside of FollowOrders: Orders are: " $ Orders);

  if (Orders != '')
  {
    if ((Orders == 'Fleeing') || (Orders == 'Attacking'))
    {
      bSetEnemy      = true;
      bUseOrderActor = true;
    }
    else if ((Orders == 'WaitingFor') || (Orders == 'GoingTo') ||
	     (Orders == 'RunningTo') || (Orders == 'Following') ||
	     (Orders == 'Sitting') || (Orders == 'Shadowing') ||
	     (Orders == 'DebugFollowing') || (Orders == 'DebugPathfinding'))
    {
      bSetEnemy      = false;
      bUseOrderActor = true;
    }
    else
    {
      bSetEnemy      = false;
      bUseOrderActor = false;
    }

    if (bUseOrderActor)
    {
      //log("$$ Finding order actor...");
      FindOrderActor();
      if (bSetEnemy)
      {
        //log("$$ Setting enemy...orderActor is " $ OrderActor);
	SetEnemy(Pawn(OrderActor), 0, true);
      }
    }

    //log("$$ Setting state to " $ Orders);
    if (bDefer)  // hack
      SetState(Orders);
    else
      GotoState(Orders);
  }
  else
  {
    //log("$$ Setting state to Wandering");
    if (bDefer)
      SetState('Wandering');
    else
      GotoState('Wandering');
  }
}

//=============================================================================
// (DTR)
//
// Override this state so we can have control over how bots seek
//=============================================================================

State Seeking
{
  function SetFall() {
    StartFalling('Seeking', 'ContinueSeek');
  }

  function HitWall(vector HitNormal, actor Wall) {
    if (Physics == PHYS_Falling)
      return;
    Global.HitWall(HitNormal, Wall);
    CheckOpenDoor(HitNormal, Wall);
  }

  function bool GetNextLocation(out vector nextLoc) {
    local float   dist;
    local rotator rotation;
    local bool    bDone;
    local float   seekDistance;
    local Actor   hitActor;
    local vector  HitLocation, HitNormal;
    local vector  diffVect;
    local bool    bLOS;
    
    if (bSeekLocation) {
      if (SeekType == SEEKTYPE_Guess)
	seekDistance = (200+FClamp(GroundSpeed*EnemyLastSeen*0.5, 0, 1000));
      else
	seekDistance = 300;
    }
    else
      seekDistance = 60;
    
    dist  = VSize(Location-destLoc);
    bDone = false;
    bLOS  = false;
    
    if (dist < seekDistance) {
      bLOS = true;
      foreach TraceVisibleActors(Class'Actor', hitActor, hitLocation,
                                 hitNormal, destLoc,
				 Location+vect(0,0,1)*BaseEyeHeight)
      {
	if (hitActor != self) {
	  if (hitActor == Level)
	    bLOS = false;
	  else if (IsPointInCylinder(hitActor, destLoc, 16, 16))
	    break;
	  else if (hitActor.bBlockSight && !hitActor.bHidden)
	    bLOS = false;
	}

	if (!bLOS)
	  break;
      }
    }

    if (!bLOS) {
      if (PointReachable(destLoc)) {
	rotation = Rotator(destLoc - Location);

	if (seekDistance == 0)
	  nextLoc = destLoc;
	else if (!AIDirectionReachable(destLoc, rotation.Yaw, rotation.Pitch,
	                               0, seekDistance, nextLoc))
	  bDone = true;

	if (!bDone && bDefendHome && !IsNearHome(nextLoc))
	  bDone = true;

        // hack, because Unreal's movement code SUCKS
	if (!bDone) {
	  diffVect = nextLoc - Location;
	  if (Physics == PHYS_Walking)
	    diffVect *= vect(1,1,0);
	  if (VSize(diffVect) < 20)
	    bDone = true;
	  else if (IsPointInCylinder(self, nextLoc, 10, 10))
	    bDone = true;
	}
      }
      else {
	MoveTarget = FindPathTo(destLoc);
	if (MoveTarget == None)
	  bDone = true;
	else if (bDefendHome && !IsNearHome(MoveTarget.Location))
	  bDone = true;
	else
	  nextLoc = MoveTarget.Location;
      }
    }
    else
      bDone = true;
    
    return (!bDone);
  }

  function UseNavPoint(NavigationPoint navPoint)
  {
    LastNavPoint[NextPointIndex] = navPoint;
    NextPointIndex++;

    if(NumSavedNavPoints < (MaxSavedNavPoints - 1))
      NumSavedNavPoints++;

    if(NextPointIndex >= MaxSavedNavPoints)
      NextPointIndex = 0;
  }

  function bool IsUsedNavPoint(NavigationPoint navPoint)
  {
    local bool bUsedPoint;
    local int index;

    index = 0;
    bUsedPoint = false;

    // Check against all of our saved nav points
    //log("$$ NumSavedNavPoints: " $ NumSavedNavPoints);
    while(index < NumSavedNavPoints) {
      if(navPoint == LastNavPoint[index]) {
        //log("  $$ Skipping used pathnode: " $ navPoint.Name);
        bUsedPoint = true;
        break;
      }
      index++;
    }

    return bUsedPoint;
  }

  function bool PickDestination() {
    local bool bValid;
    local NavigationPoint navPoint;
    local float distance;
    local int choice;
    local NavigationPoint availableNavPoints[20];
    local int             maxAvailNavPoint;
    local int             numAvailNavPoints;

    maxAvailNavPoint = 0;
    numAvailNavPoints = 0;
    foreach ReachablePathnodes(Class'NavigationPoint', navPoint, None,
			       distance)
    {
      //log("$$ in reachablepathnodes...");

      // We don't care about anything that is not a PathNode or an Ambushpoint
      if(navPoint.IsA('PathNode') || navPoint.IsA('Ambushpoint'))
      {
        //log("$$ PathNode available: " $ navPoint.Name);

        // Keep track of how many nav points we have available to us
        numAvailNavPoints++;

        // Have we recently used this navpoint? If so, skip adding it to the
        // available navigation points
        if(IsUsedNavPoint(navPoint))
          continue;

        availableNavPoints[maxAvailNavPoint] = navPoint;
        maxAvailNavPoint++;
      }
    }

    //log("** Number available navpoints to choose from: " $ maxAvailNavPoint);
    //log("** Number total navpoints to choose from    : " $ numAvailNavPoints);

    if(maxAvailNavPoint > 0)
    {
      choice = Rand(maxAvailNavPoint);
      //log("** choice is " $ choice);
      //log("$$ Using " $ availableNavPoints[choice].Name);
      destLoc = availableNavPoints[choice].Location;
      UseNavPoint(availableNavPoints[choice]);
      return true;
    }

    return false;
  }

  function NavigationPoint GetOvershootDestination(float randomness,
						   optional float focus)
  {
    local NavigationPoint navPoint, bestPoint;
    local float           distance;
    local float           score, bestScore;
    local int             yaw;
    local rotator         rot;
    local float           yawCutoff;

    if (focus <= 0)
      focus = 0.6;

    yawCutoff = int(32768*focus);
    bestPoint = None;
    bestScore = 0;
    
    foreach ReachablePathnodes(Class'NavigationPoint', navPoint, None,
			       distance)
    {
      if (distance < 1)
	distance = 1;

      rot = Rotator(navPoint.Location-Location);
      yaw = rot.Yaw + (16384*randomness);
      yaw = (yaw-Rotation.Yaw) & 0xFFFF;

      if (yaw > 32767)
	yaw  -= 65536;

      yaw = abs(yaw);
      if (yaw <= yawCutoff)
      {
	score = yaw/distance;
	if ((bestPoint == None) || (score < bestScore))
	{
	  bestPoint = navPoint;
	  bestScore = score;
	}
      }
    }
    
    return bestPoint;
  }

  function Tick(float deltaSeconds)
  {
    animTimer[1] += deltaSeconds;
    Global.Tick(deltaSeconds);
    UpdateActorVisibility(Enemy, deltaSeconds, 1.0, true);
  }

  function HandleLoudNoise(Name event, EAIEventState state, XAIParams params)
  {
    local Actor bestActor;
    local Pawn  instigator;
    
    if (state == EAISTATE_Begin || state == EAISTATE_Pulse)
    {
      bestActor = params.bestActor;
      if ((bestActor != None) && (EnemyLastSeen > 2.0))
      {
	instigator = Pawn(bestActor);
	if (instigator == None)
	  instigator = bestActor.Instigator;

	if (instigator != None)
	{
          // DTR: Old style check. We now use IsValidMPEnemy
	  // if (IsValidEnemy(instigator))
	  //log("$$ checking valid mp enemy 3");
	  if (IsValidMPEnemy(instigator))
	  {
	    SetSeekLocation(instigator, bestActor.Location, SEEKTYPE_Sound);
	    destLoc = LastSeenPos;
	    if (bInterruptSeek)
	      GotoState('Seeking', 'GoToLocation');
	  }
	}
      }
    }
  }

  function HandleSighting(Pawn pawnSighted)
  {
    // DTR: Old style check. We now use IsValidMPEnemy
    // if ((EnemyLastSeen > 2.0) && IsValidEnemy(pawnSighted))
    //log("$$ checking valid mp enemy 4");
    if ((EnemyLastSeen > 2.0) && IsValidMPEnemy(pawnSighted))
    {
      SetSeekLocation(pawnSighted, pawnSighted.Location, SEEKTYPE_Sight);
      destLoc = LastSeenPos;
      if (bInterruptSeek)
	GotoState('Seeking', 'GoToLocation');
    }
  }
  
  function BeginState()
  {
    StandUp();
    Disable('AnimEnd');
    destLoc = LastSeenPos;
    SetReactions(true, true, false, true, true, true,
		 true, true, true, false, true, true);
    bCanConverse = False;
    bStasis = False;
    SetupWeapon(true);
    SetDistress(false);
    bInterruptSeek = false;
    EnableCheckDestLoc(false);
  }

  function EndState()
  {
    EnableCheckDestLoc(false);
    Enable('AnimEnd');
    ResetReactions();
    bCanConverse = True;
    bStasis = True;
    StopBlendAnims();
    SeekLevel = 0;
  }

Begin:
  //log("  Begin seeking state...");
  WaitForLanding();
  PlayWaiting();

  if ((Weapon != None) && bKeepWeaponDrawn && (Weapon.CockingSound != None) &&
      !bSeekPostCombat)
    PlaySound(Weapon.CockingSound, SLOT_None,,, 1024);

  Acceleration = vect(0,0,0);
  if (!PickDestination())
  {
    //log("  Could not find a destination...");
    NumTimesWithNoDestination++;
    Goto('DoneSeek');
  }

  NumTimesWithNoDestination = 0;

GoToLocation:
  bInterruptSeek = true;
  Acceleration = vect(0,0,0);

  //log("  Checking for reload.");
  if ((DeusExWeapon(Weapon) != None) && DeusExWeapon(Weapon).CanReload() &&
      !Weapon.IsInState('Reload'))
    DeusExWeapon(Weapon).ReloadAmmo();

  if (bSeekPostCombat)
    PlayPostAttackSearchingSound();
  else if (SeekType == SEEKTYPE_Sound)
    PlayPreAttackSearchingSound();
  else if (SeekType == SEEKTYPE_Sight)
  {
    if (ReactionLevel > 0.5)
      PlayPreAttackSightingSound();
  }
  else if ((SeekType == SEEKTYPE_Carcass) && bSeekLocation)
    PlayCarcassSound();

  StopBlendAnims();
  
  if ((SeekType == SEEKTYPE_Sight) && bSeekLocation)
    Goto('TurnToLocation');

  EnableCheckDestLoc(true);
  while (GetNextLocation(useLoc))
  {
    if (ShouldPlayWalk(useLoc))
      PlayRunning();

    //log("  Moving to next location...");
    MoveTo(useLoc, MaxDesiredSpeed);

    CheckDestLoc(useLoc);
  }

  EnableCheckDestLoc(false);

  if ((SeekType == SEEKTYPE_Guess) && bSeekLocation)
  {
    MoveTarget = GetOvershootDestination(0.5);
    if (MoveTarget != None)
    {
      if (ShouldPlayWalk(MoveTarget.Location))
	PlayRunning();
      MoveToward(MoveTarget, MaxDesiredSpeed);
    }

    if (AIPickRandomDestination(CollisionRadius*2, 200+FRand()*200,
				Rotation.Yaw, 0.75, Rotation.Pitch, 0.75, 2,
				0.4, useLoc))
    {
      //log("  Moving to random destination");
      if (ShouldPlayWalk(useLoc))
	PlayRunning();

      MoveTo(useLoc, MaxDesiredSpeed);
    }
  }

TurnToLocation:
  Acceleration = vect(0,0,0);
  PlayTurning();

  if ((SeekType == SEEKTYPE_Guess) && bSeekLocation)
    destLoc = Location + Vector(Rotation+(rot(0,1,0)*(Rand(16384)-8192)))*1000;

  if (bCanTurnHead)
  {
    Sleep(0);  // needed to turn head
    LookAtVector(destLoc, true, false, true);
    TurnTo(Vector(DesiredRotation)*1000+Location);
  }
  else
    TurnTo(destLoc);

  bSeekLocation = false;
  bInterruptSeek = false;

  PlayWaiting();
//  Sleep(FRand()*1.5+3.0);

LookAround:
/*
  if (bCanTurnHead)
  {
    if (FRand() < 0.5)
    {
      if (!bSeekLocation)
      {
	PlayTurnHead(LOOK_Left, 1.0, 1.0);
	Sleep(1.0);
      }

      if (!bSeekLocation)
      {
	PlayTurnHead(LOOK_Forward, 1.0, 1.0);
	Sleep(0.5);
      }

      if (!bSeekLocation)
      {
	PlayTurnHead(LOOK_Right, 1.0, 1.0);
	Sleep(1.0);
      }
    }
    else
    {
      if (!bSeekLocation)
      {
	PlayTurnHead(LOOK_Right, 1.0, 1.0);
	Sleep(1.0);
      }

      if (!bSeekLocation)
      {
	PlayTurnHead(LOOK_Forward, 1.0, 1.0);
	Sleep(0.5);
      }

      if (!bSeekLocation)
      {
	PlayTurnHead(LOOK_Left, 1.0, 1.0);
	Sleep(1.0);
      }
    }

    PlayTurnHead(LOOK_Forward, 1.0, 1.0);
    Sleep(0.5);
    StopBlendAnims();
  }
  else
  {
    if (!bSeekLocation)
      Sleep(1.0);
  }
*/

FindAnotherPlace:
  //log("  Finding a new place to go...");
  SeekLevel--;

  if (PickDestination())
  {
    NumTimesWithNoDestination = 0;
    Goto('GoToLocation');
  }
  else
    NumTimesWithNoDestination++;

DoneSeek:
/*
  if (bSeekPostCombat)
    PlayTargetLostSound();
  else
    PlaySearchGiveUpSound();

  bSeekPostCombat = false;
  SeekPawn = None;

  if (Orders != 'Seeking')
    FollowOrders();
  else
    GotoState('Wandering');
*/

ContinueSeek:
ContinueFromDoor:
  //log("");
  //log("  Starting over again..." $ NumTimesWithNoDestination);

  if(NumTimesWithNoDestination > 5)
  {
    //log("  Going to wandering state");
    GotoState('Wandering');
  }
  else
  {
    NumSavedNavPoints = 0;
    NextPointIndex = 0;
    FinishAnim();
    Goto('FindAnotherPlace');
  }
}

///////////////////////////////////////////////////////////////////////////////
// For the CheckForPlayer method, we use RadiusActors to see if can "see" a
// player. Later on, we can add additional levels of intelligence here so
// that we have smarter (and dumber) bots.
function bool CheckForPlayer()
{
  local DeusExPlayer mpPlayer;

  // Set a radius of 100 feet for now
//  log("CHECKING FOR PLAYER");
  foreach RadiusActors(Class'DeusExPlayer', mpPlayer, 100 * 16)
  {
//    mpPlayer.ClientMessage("You've been seen!");
    bNeedToSwitchToBestWeapon = True;
    SetEnemy(mpPlayer);
    return true;
  }

  return false;
}

///////////////////////////////////////////////////////////////////////////////
// Should we drop our current weapon? Perhaps we can tweak this a bit for
// varying levels of AI
function bool ShouldDropWeapon()
{
  if (((HealthArmLeft <= 0) || (HealthArmRight <= 0)) && (Health > 0))
    return true;
  else
    return false;
}

///////////////////////////////////////////////////////////////////////////////
// Given our current inventory, switch us over to the best possible weapon
function bool SwitchToBestWeapon()
{
  local Inventory    inv;
  local DeusExWeapon curWeapon;
  local float        score;
  local DeusExWeapon dxWeapon;
  local DeusExWeapon bestWeapon;
  local float        bestScore;
  local int          fallbackLevel;
  local int          curFallbackLevel;
  local bool         bBlockSpecial;
  local bool         bValid;
  local bool         bWinner;
  local float        minRange, accRange;
  local float        range, centerRange;
  local float        cutoffRange;
  local float        enemyRange;
  local float        minEnemy, accEnemy, maxEnemy;
  local ScriptedPawn enemyPawn;
  local Robot        enemyRobot;
  local DeusExPlayer enemyPlayer;
  local float        enemyRadius;
  local bool         bEnemySet;
  local int          loopCount, i;  // hack - check for infinite inventory
  local Inventory    loopInv;       // hack - check for infinite inventory
  
  //log("  Switching to best weapon...");

  if(!bNeedToSwitchToBestWeapon)
  {
    return false;
  }

  // If we need to drop this weapon, we should
  if (ShouldDropWeapon())
  {
    //log("  " $ Self $ " dropping weapon...");
    DropWeapon();
    return false;
  }

  // Get pointer to current weapon, if we have one
  bBlockSpecial = false;
  dxWeapon = DeusExWeapon(Weapon);
  if (dxWeapon != None)
  {
    if (dxWeapon.AITimeLimit > 0)
    {
      if (SpecialTimer <= 0)
      {
	bBlockSpecial = true;
	FireTimer = dxWeapon.AIFireDelay;
      }
    }
  }

  bestWeapon      = None;
  bestScore       = 0;
  fallbackLevel   = 0;
  inv             = Inventory;
  
  bEnemySet   = false;
  minEnemy    = 0;
  accEnemy    = 0;
  enemyRange  = 400;  // default
  enemyRadius = 0;
  enemyPawn   = None;
  enemyRobot  = None;

  if (Enemy != None)
  {
    bEnemySet   = true;
    enemyRange  = VSize(Enemy.Location - Location);
    enemyRadius = Enemy.CollisionRadius;

    // DTR AICHANGE???: Might want to limit this to the better AI levels but
    // leave it for now.
    if (DeusExWeapon(Enemy.Weapon) != None)
      DeusExWeapon(Enemy.Weapon).GetWeaponRanges(minEnemy, accEnemy, maxEnemy);

    enemyPawn   = ScriptedPawn(Enemy);
    enemyRobot  = Robot(Enemy);
    enemyPlayer = DeusExPlayer(Enemy);
  }

  loopCount = 0;
  //log("  Inv is " $ inv);

  while (inv != None)
  {
    //log("  Inv(loop) is " $ inv);

    // THIS IS A MAJOR HACK!!!
    loopCount++;
    if (loopCount == 9999)
    {
      //log("******** RUNAWAY LOOP IN SWITCHTOBESTWEAPON ("$self$") ********");
      loopInv = Inventory;
      i = 0;
      while (loopInv != None)
      {
	i++;
	if (i > 300)
	  break;

	//log("   Inventory "$i$" - "$loopInv);
	loopInv = loopInv.Inventory;
      }
    }

    curWeapon = DeusExWeapon(inv);
    if (curWeapon != None)
    {
      bValid = true;
      if (curWeapon.ReloadCount > 0)
      {
	if (curWeapon.AmmoType == None)
	  bValid = false;
	else if (curWeapon.AmmoType.AmmoAmount < 1)
	  bValid = false;
      }

      // Ensure we can actually use this weapon here
      if (bValid)
      {
	// lifted from DeusExWeapon...
	if ((curWeapon.EnviroEffective == ENVEFF_Air) ||
	    (curWeapon.EnviroEffective == ENVEFF_Vacuum) ||
	    (curWeapon.EnviroEffective == ENVEFF_AirVacuum))
	  if (curWeapon.Region.Zone.bWaterZone)
	    bValid = false;
      }

      if (bValid)
      {
	GetWeaponBestRange(curWeapon, minRange, accRange);
	cutoffRange = minRange+(CollisionRadius+enemyRadius);
	range = (accRange - minRange) * 0.5;
	centerRange = minRange + range;

	if (range < 50)
	  range = 50;

	if (enemyRange < centerRange)
	  score = (centerRange - enemyRange)/range;
	else
	  score = (enemyRange - centerRange)/range;

	if ((minRange >= minEnemy) && (accRange <= accEnemy))
	  score += 0.5;  // arbitrary

        // do not use long-range weapons on short-range targets
	if ((cutoffRange >= enemyRange-CollisionRadius) &&
	    (cutoffRange >= 256)) 
	  score += 10000;

	curFallbackLevel = 3;
	if (curWeapon.bFallbackWeapon && !bUseFallbackWeapons)
	  curFallbackLevel = 2;

	if (!bEnemySet && !curWeapon.bUseAsDrawnWeapon)
	  curFallbackLevel = 1;

	if ((curWeapon.AIFireDelay > 0) && (FireTimer > 0))
	  curFallbackLevel = 0;

	if (bBlockSpecial && (curWeapon.AITimeLimit > 0) &&
	    (SpecialTimer <= 0))
	  curFallbackLevel = 0;

	// Adjust score based on opponent and damage type.  All damage
	// types are listed here, even the ones that aren't used by
	// weapons... :) (hacky...)
	switch (curWeapon.WeaponDamageType())
	{
	case 'Exploded':
	  // Massive explosions are always good
	  score -= 0.2;
	  break;
	  
	case 'Stunned':
	  if (enemyPawn != None)
	  {
	    if (enemyPawn.bStunned)
	      score += 1000;
	    else
	      score -= 1.5;
	  }

	  if (enemyPlayer != None)
	    score += 10;
	  break;

	case 'TearGas':
	  if (enemyPawn != None)
	  {
	    if (enemyPawn.bStunned)
	      //score += 1000;
	      bValid = false;
	    else
	      score -= 5.0;
	  }

	  if (enemyRobot != None)
	    //score += 10000;
	    bValid = false;
	  break;

	case 'HalonGas':
	  if (enemyPawn != None)
	  {
	    if (enemyPawn.bStunned)
	      //score += 1000;
	      bValid = false;
	    else if (enemyPawn.bOnFire)
	      //score += 10000;
	      bValid = false;
	    else
	      score -= 3.0;
	  }

	  if (enemyRobot != None)
	    //score += 10000;
	    bValid = false;
	  break;

	case 'PoisonGas':
	case 'Poison':
	case 'PoisonEffect':
	case 'Radiation':
	  if (enemyRobot != None)
	    //score += 10000;
	    bValid = false;
	  break;
	  
	case 'Burned':
	case 'Flamed':
	case 'Shot':
	  if (enemyRobot != None)
	    score += 0.5;
	  break;
	  
	case 'Sabot':
	  if (enemyRobot != None)
	    score -= 0.5;
	  break;

	case 'EMP':
	case 'NanoVirus':
	  if (enemyRobot != None)
	    score -= 5.0;
	  else if (enemyPlayer != None)
	    score += 5.0;
	  else
	    //score += 10000;
	    bValid = false;
	  break;
	  
	case 'Drowned':
	default:
	  break;
	}

	// Special case for current weapon
	if ((curWeapon == Weapon) && (WeaponTimer < 10.0))
	{
	  // If we last changed weapons less than five seconds ago,
	  // keep this weapon
	  if (WeaponTimer < 5.0)
	    score = -10;

	  // If between five and ten seconds, use a sliding scale
	  else
	    score -= (10.0 - WeaponTimer)/5.0;
	}

	// Throw a little randomness into the computation...
	else
	{
	  score += FRand()*0.1 - 0.05;
	  if (score < 0)
	    score = 0;
	}

	if (bValid)
	{
	  // ugly
	  if (bestWeapon == None)
	    bWinner = true;
	  else if (curFallbackLevel > fallbackLevel)
	    bWinner = true;
	  else if (curFallbackLevel < fallbackLevel)
	    bWinner = false;
	  else if (bestScore > score)
	    bWinner = true;
	  else
	    bWinner = false;

	  if (bWinner)
	  {
	    bestScore     = score;
	    bestWeapon    = curWeapon;
	    fallbackLevel = curFallbackLevel;
	  }
	}
      }
    }

    inv = inv.Inventory;
  }

  // If we're changing weapons, reset the weapon timers
  if (Weapon != bestWeapon)
  {
    if (!bEnemySet)
      WeaponTimer = 10;  // hack
    else
      WeaponTimer = 0;

    if (bestWeapon != None)
      if (bestWeapon.AITimeLimit > 0)
	SpecialTimer = bestWeapon.AITimeLimit;

    ReloadTimer = 0;
  }

  SetWeapon(bestWeapon);
  bNeedToSwitchToBestWeapon = False;

  return false;
}


///////////////////////////////////////////////////////////////////////////////
// Override this to better understand what is going on with bots
function bool AICanShoot(pawn target, bool bLeadTarget, bool bCheckReadiness,
			 optional float throwAccuracy,
			 optional bool bDiscountMinRange)
{
  local DeusExWeapon dxWeapon;
  local Vector X, Y, Z;
  local Vector projStart, projEnd;
  local float  tempMinRange, tempMaxRange;
  local float  temp;
  local float  dist;
  local float  extraDist;
  local actor  hitActor;
  local Vector hitLocation, hitNormal;
  local Vector extent;
  local bool   bIsThrown;
  local float  elevation;
  local bool   bSafe;
  
  //log("      Inside AICanShoot");
  if (target == None)
  {
    //log("      target is None");
    return false;
  }

  if (target.bIgnore)
  {
    //log("      target.bIgnore is true");
    return false;
  }

  dxWeapon = DeusExWeapon(Weapon);

  if (dxWeapon == None)
  {
    //log("      dxWeapon is None");
    return false;
  }

  if (bCheckReadiness && !dxWeapon.bReadyToFire)
  {
    //log("      bCheckReadiness " $ bCheckReadiness);
    //log("      dxWeapon.bReadyToFire " $ dxWeapon.bReadyToFire);
    return false;
  }

  if (dxWeapon.ReloadCount > 0)
  {
    //log("      reloadCount : " $ dxWeapon.ReloadCount);
    if (dxWeapon.AmmoType == None)
    {
      //log("      ammotType is None");
      return false;
    }

    if (dxWeapon.AmmoType.AmmoAmount <= 0)
    {
      //log("      no more ammo!");
      return false;
    }
  }

  if (FireElevation > 0)
  {
    //log("      FireElevation: " $ FireElevation);
    elevation = FireElevation + (CollisionHeight+target.CollisionHeight);

    if (elevation < 10)
      elevation = 10;

    if (Abs(Location.Z - target.Location.Z) > elevation)
    {
      //log("      elevation is bad");
      return false;
    }
  }

  bIsThrown = IsThrownWeapon(dxWeapon);
  //log("      bIsThrown: " $ bIsThrown);

  extraDist = target.CollisionRadius;
  //extraDist = 0;

  GetPawnWeaponRanges(self, tempMinRange, tempMaxRange, temp);

  if (bDiscountMinRange)
    tempMinRange = 0;

  if (tempMinRange >= tempMaxRange)
  {
    //log("      tempMinRange " $ tempMinRange $
    //    " tempMaxRange " $ tempMaxRange);
    return false;
  }

  ViewRotation = Rotation;
  GetAxes(ViewRotation, X, Y, Z);
  projStart = dxWeapon.ComputeProjectileStart(X, Y, Z);

  if (bLeadTarget && !dxWeapon.bInstantHit && (dxWeapon.ProjectileSpeed > 0))
  {
    //log("      bLeadTarget " $ bLeadTarget);
    //log("      bInstantHit "	$ dxWeapon.bInstantHit);
    //log("      ProjectileSpeed " $ dxWeapon.ProjectileSpeed);

    if (bIsThrown)
    {
      // compute target's position 1.5 seconds in the future
      projEnd = target.Location + (target.Velocity*1.5);
    }
    else
    {
      // projEnd = target.Location + (target.Velocity*dist/dxWeapon.ProjectileSpeed);
      if (!ComputeTargetLead(target, projStart, dxWeapon.ProjectileSpeed,
			     5.0, projEnd))
      {
        //log("      ComputeTargetLead forcing us to return false");
	return false;
      }
    }
  }
  else
    projEnd = target.Location;

  if (bIsThrown)
    projEnd += vect(0,0,-1)*(target.CollisionHeight-5);

  dist = VSize(projEnd - Location);
  if (dist < 0)
    dist = 0;

  if ((dist < tempMinRange) || (dist-extraDist > tempMaxRange))
  {
    //log("      Distance is outside of ranges");
    return false;
  }

  if (!bIsThrown)
  {
    bSafe = FastTrace(target.Location, projStart);
    if (!bSafe && target.bIsPlayer)  // players only... hack
    {
      projEnd += vect(0,0,1)*target.BaseEyeHeight;
      bSafe = FastTrace(target.Location + vect(0,0,1)*target.BaseEyeHeight,
			projStart);
    }

    if (!bSafe)
    {
      //log("      Not safe to shoot!");
      return false;
    }
  }

  if (dxWeapon.bInstantHit)
  {
    //log("      Returning AISafeToShoot...");
    return (AISafeToShoot(hitActor, projEnd, projStart, , true));
  }
  else
  {
    extent.X = dxWeapon.ProjectileClass.default.CollisionRadius;
    extent.Y = dxWeapon.ProjectileClass.default.CollisionRadius;
    extent.Z = dxWeapon.ProjectileClass.default.CollisionHeight;
    if (bIsThrown && (throwAccuracy > 0))
    {
      //log("      Returning AISafeToThrow...");
      return (AISafeToThrow(projEnd, projStart, throwAccuracy,
			    extent));
    }
    else
    {
      //log("      Returning AISafeToShoot(2)...");
      return (AISafeToShoot(hitActor, projEnd, projStart, extent*3));
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// Override these to work on bots
function bool AISafeToShoot(out Actor hitActor, vector traceEnd,
                            vector traceStart, optional vector extent,
                            optional bool bIgnoreLevel)
{
  local Actor            traceActor;
  local Vector           hitLocation;
  local Vector           hitNormal;
  local Pawn             tracePawn;
  local DeusExDecoration traceDecoration;
  local DeusExMover      traceMover;
  local bool             bSafe;

  // Future improvement:
  // Ideally, this should use the ammo type to determine how many shots
  // it will take to destroy an obstruction, and call it unsafe if it takes
  // more than x shots.  Also, if the ammo is explosive, and the
  // obstruction is too close, it should never be safe...

  bSafe    = true;
  hitActor = None;

  //log("          Inside AISafeToShoot");
  foreach TraceActors(Class'Actor', traceActor, hitLocation, hitNormal,
		      traceEnd, traceStart, extent)
  {
    if (hitActor == None)
      hitActor = traceActor;

    if (traceActor == Level)
    {
      if (!bIgnoreLevel)
      {
        //log("          We don't ignore the level and we hit the level!");
	bSafe = false;
      }

      break;
    }

/*
 * DTR: Comment this out for now as alliances are kinda screwy under MP
    tracePawn = Pawn(traceActor);
    if (tracePawn != None)
    {
      if (tracePawn != self)
      {
	if (GetPawnAllianceType(tracePawn) == ALLIANCE_Friendly)
        {
          //log("          We hit a pawn but it's alliance is friendly.");
	  bSafe = false;
        }

	break;
      }
    }
*/

    tracePawn = Pawn(traceActor);
    if (tracePawn != None)
    {
      if(tracePawn.Health <= 0)
      {
        bSafe = false;
        break;
      }

      // DTR: AICHANGE??? - Make smarter bots not shoot other bots
/*
      if(MPBScriptedPawn(tracePawn) != None)
      {
        bSafe = false;
        break;
      }
*/
    }

    traceDecoration = DeusExDecoration(traceActor);
    if (traceDecoration != None)
    {
      if (traceDecoration.bExplosive || traceDecoration.bInvincible)
      {
        //log("          We hit an explosive or invincible decoration");
	bSafe = false;
	break;
      }

      if ((traceDecoration.HitPoints > 20) ||
	  (traceDecoration.minDamageThreshold > 4))  // hack
      {
        //log("          The decoration has HP > 20 or minDamThreshold > 4");
	bSafe = false;
	break;
      }
    }

    traceMover = DeusExMover(traceActor);
    if (traceMover != None)
    {
      if (!traceMover.bBreakable)
      {
        //log("          Hit a mover which is not breakable.");
	bSafe = false;
	break;
      }
      else if ((traceMover.doorStrength > 0.2) ||
	       (traceMover.minDamageThreshold > 8))  // hack
      {
        //log("          DoorStrength > 0.2 || minDamageThreshold > 8");
	bSafe = false;
	break;
      }
      else  // hack
	break;
    }

    if (Inventory(traceActor) != None)
    {
      //log("          We hit an inventory object.");
      bSafe = false;
      break;
    }
  }

  //log("        Returning " $ bSafe $ " from AISafeToShoot");  
  return (bSafe);
}

function bool AISafeToThrow(vector traceEnd, vector traceStart,
                            float throwAccuracy,
                            optional vector extent)
{
  local float                   time1, time2, tempTime;
  local vector                  pos1,  pos2,  tempPos;
  local rotator                 rot1,  rot2,  tempRot;
  local rotator                 bestAngle;
  local bool                    bSafe;
  local DeusExWeapon            dxWeapon;
  local Class<ThrownProjectile> throwClass;

  //log("        In AISafeToThrow");
  // Someday, we should check for nearby friendlies within the blast radius
  // before throwing...

  // Sanity checks
  throwClass = None;
  dxWeapon = DeusExWeapon(Weapon);

  if (dxWeapon != None)
    throwClass = Class<ThrownProjectile>(dxWeapon.ProjectileClass);

  if (throwClass == None)
  {
    //log("        ThrowClass is null");
    return false;
  }

  if (extent == vect(0,0,0))
  {
    extent = vect(1,1,0) * throwClass.Default.CollisionRadius;
    extent.Z = throwClass.Default.CollisionHeight;
  }

  if (throwAccuracy < 0.01)
    throwAccuracy = 0.01;

  bSafe = false;
  if (ComputeThrowAngles(traceEnd, traceStart, dxWeapon.ProjectileSpeed,
			 rot1, rot2))
  {
    //log("        ComputeThrowAngles returned true");
    time1 = ParabolicTrace(pos1, Vector(rot1)*dxWeapon.ProjectileSpeed,
			   traceStart, true, extent, 5.0,
			   throwClass.Default.Elasticity,
			   throwClass.Default.bBounce,
			   60, throwAccuracy);
    time2 = ParabolicTrace(pos2, Vector(rot2)*dxWeapon.ProjectileSpeed,
			   traceStart, true, extent, 5.0,
			   throwClass.Default.Elasticity,
			   throwClass.Default.bBounce,
			   60, throwAccuracy);
    //log("        time1 " $ time1 $ " time2 " $ time2);

    if ((time1 > 0) || (time2 > 0))
    {
      if ((time1 > time2) && (time2 > 0))
      {
	tempTime = time1;
	time1    = time2;
	time2    = tempTime;
	tempPos  = pos1;
	pos1     = pos2;
	pos2     = tempPos;
	tempRot  = rot1;
	rot1     = rot2;
	rot2     = tempRot;
      }

      if (VSize(pos1-traceEnd) <= throwClass.Default.blastRadius)
      {
        //log("        pos1 to traceEnd is within blastRadius");
	if (FastTrace(traceEnd, pos1))
	{
          //log("        we don't hit geometry for pos1 to traceEnd");
	  if ((VSize(pos1-Location) > throwClass.Default.blastRadius*0.5) ||
	      !FastTrace(Location, pos1))
	  {
            //log("        we're either outside the blast radius for pos1 or we hit some geometry from our Location to pos1...good to throw!");
	    bestAngle = rot1;
	    bSafe     = true;
	  }
	}
      }
    }

    if (!bSafe && (time2 > 0))
    {
      if (VSize(pos2-traceEnd) <= throwClass.Default.blastRadius)
      {
        //log("        pos2 to traceEnd is within blastRadius");
	if (FastTrace(traceEnd, pos2))
	{
          //log("        we don't hit geometry for pos2 to traceEnd");
	  if ((VSize(pos2-Location) > throwClass.Default.blastRadius*0.5) ||
	      !FastTrace(Location, pos2))
	  {
            //log("        we're either outside the blast radius for pos2 or we hit some geometry from our Location to pos2...good to throw!");
	    bestAngle = rot2;
	    bSafe     = true;
	  }
	}
      }
    }
    
  }
  
  if (bSafe)
    ViewRotation = bestAngle;

  //log("        Returning " $ bSafe $ " from AISafeToThrow");  
  return (bSafe);
}

///////////////////////////////////////////////////////////////////////////////
// Another override method
function bool ComputeThrowAngles(vector traceEnd, vector traceStart,
                                 float speed,
                                 out Rotator angle1, out Rotator angle2)
{
  local float   deltaX, deltaY;
  local float   x, y;
  local float   tanAngle1, tanAngle2;
  local float   A, B, C;
  local float   m, n;
  local float   sqrtTerm;
  local float   gravity;
  local float   traceYaw;
  local bool    bValid;
  
  bValid = false;

  // Reduce our problem to two dimensions
  deltaX = traceEnd.X - traceStart.X;
  deltaY = traceEnd.Y - traceStart.Y;
  x = sqrt(deltaX*deltaX + deltaY*deltaY);
  y = traceEnd.Z - traceStart.Z;
  
  gravity = -Region.Zone.ZoneGravity.Z;
  if ((x > 0) && (gravity > 0))
  {
    A = -gravity*x*x;
    B = 2*speed*speed*x;
    C = -gravity*x*x - 2*y*speed*speed;

    sqrtTerm = B*B - 4*A*C;
    if (sqrtTerm >= 0)
    {
      m = -B/(2*A);
      n = sqrt(sqrtTerm)/(2*A);

      tanAngle1 = atan(m+n);
      tanAngle2 = atan(m-n);

      angle1 = Rotator(traceEnd - traceStart);
      angle2 = angle1;
      angle1.Pitch = tanAngle1*32768/Pi;
      angle2.Pitch = tanAngle2*32768/Pi;

      bValid = true;
    }
  }
  
  return bValid;
}

///////////////////////////////////////////////////////////////////////////////
// Override this to determine where AICanShoot is being called from
function bool ShouldStrafe()
{
  //log("$$ Inside ShouldStrafe...");

  // This may be overridden from subclasses
  // return (AICanSee(enemy, 1.0, false, true, true, true) > 0);
  return (AICanShoot(enemy, false, false, 0.025, true));
}

///////////////////////////////////////////////////////////////////////////////
// Override this to determine where AICanShoot is being called from
function SetAttackAngle()
{
  local bool bCanShoot;

  bCanShoot = false;
  if (Enemy != None)
    if (AICanShoot(Enemy, true, false, 0.025))
      bCanShoot = true;
      
  if (!bCanShoot)
    ViewRotation = Rotation;
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function bool SetEnemy(Pawn newEnemy, optional float newSeenTime,
                       optional bool bForce)
{
  // DTR: Old style check. We now use IsValidMPEnemy
  // if (bForce || IsValidEnemy(newEnemy))

  //log("  checking valid mp enemy 5");
  if (bForce || IsValidMPEnemy(newEnemy))
  {
    if (newEnemy != Enemy)
      EnemyTimer = 0;
    Enemy         = newEnemy;
    EnemyLastSeen = newSeenTime;
    
    return True;
  }
  else
    return False;
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function Pawn CheckCycle()
{
  local float attackPeriod;
  local float maxAttackPeriod;
  local float sustainPeriod;
  local float decayPeriod;
  local float minCutoff;
  local Pawn  cycleEnemy;

  attackPeriod    = 0.5;
  maxAttackPeriod = 4.5;
  sustainPeriod   = 3.0;
  decayPeriod     = 4.0;
  
  minCutoff = attackPeriod/maxAttackPeriod;

  cycleEnemy = None;
  
  if (CycleCumulative <= 0)  // no enemies seen during this cycle
  {
    CycleTimer -= CyclePeriod;
    if (CycleTimer <= 0)
    {
      CycleTimer = 0;
      EnemyReadiness -= CyclePeriod/decayPeriod;
      if (EnemyReadiness < 0)
	EnemyReadiness = 0;
    }
  }
  else  // I saw somebody!
  {
    CycleTimer = sustainPeriod;
    CycleCumulative *= 2;  // hack

    if (CycleCumulative < minCutoff)
      CycleCumulative = minCutoff;
    else if (CycleCumulative > 1.0)
      CycleCumulative = 1.0;

    EnemyReadiness += CycleCumulative*CyclePeriod/attackPeriod;

    if (EnemyReadiness >= 1.0)
    {
      EnemyReadiness = 1.0;

      // DTR: Old style check. We now use IsValidMPEnemy
      // if (IsValidEnemy(CycleCandidate))
      //log("$$ checking valid mp enemy 6");
      if (IsValidMPEnemy(CycleCandidate))
	cycleEnemy = CycleCandidate;
    }
    else if (EnemyReadiness >= SightPercentage)
    {
      // DTR: Old style check. We now use IsValidMPEnemy
      // if (IsValidEnemy(CycleCandidate))
      //log("$$ checking valid mp enemy 7");
      if (IsValidMPEnemy(CycleCandidate))
	HandleSighting(CycleCandidate);
    }
  }

  CycleCumulative = 0;
  CyclePeriod     = 0;
  CycleCandidate  = None;
  CycleDistance   = 0;

  return (cycleEnemy);
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function bool CheckEnemyPresence(float deltaSeconds,
                                 bool bCheckPlayer, bool bCheckOther)
{
  local int          i;
  local int          count;
  local int          checked;
  local Pawn         candidate;
  local float        candidateDist;
  local DeusExPlayer playerCandidate;
  local bool         bCanSee;
  local int          lastCycle;
  local float        visibility;
  local Pawn         cycleEnemy;
  local bool         bValid;
  local bool         bPlayer;
  local float        surpriseTime;
  local bool         bValidEnemy;
  local bool         bPotentialEnemy;
  local bool         bCheck;

  bValid  = false;
  bCanSee = false;

  if (bReactPresence && bLookingForEnemy)
    return CheckForPlayer();
  else
    return false;

/*
  //log("$$ Checking for enemy presence..." $ bReactPresence $ "" $ bLookingForEnemy $ "" $ bNoNegativeAlliances);

//  if (bReactPresence && bLookingForEnemy && !bNoNegativeAlliances)
  if (bReactPresence && bLookingForEnemy)
  { 
    bCheck = true;

    if (PotentialEnemyAlliance != '')
      bCheck = true;
    else
    {
      for (i=0; i<16; i++)
	if ((AlliancesEx[i].AllianceLevel < 0) ||
	    (AlliancesEx[i].AgitationLevel >= 1.0))
	  break;

      if (i < 16)
	bCheck = true;
    }

    //log("$$ bCheck " $ bCheck);
    if (bCheck)
    {
      bValid       = true;
      CyclePeriod += deltaSeconds;
      count        = 0;
      checked      = 0;
      lastCycle    = CycleIndex;

      foreach CycleActors(Class'Pawn', candidate, CycleIndex)
      {
        // DTR: Old style check. We now use IsValidMPEnemy
	// bValidEnemy = IsValidEnemy(candidate);
	//log("$$ checking valid mp enemy 8" $ candidate.Name);
	bValidEnemy = IsValidMPEnemy(candidate);

	if (!bValidEnemy && (PotentialEnemyTimer > 0))
	  if (PotentialEnemyAlliance == candidate.Alliance)
	    bPotentialEnemy = true;

	if (bValidEnemy || bPotentialEnemy)
	{
	  count++;
	  bPlayer = candidate.IsA('PlayerPawn');
	  if ((bPlayer && bCheckPlayer) || (!bPlayer && bCheckOther))
	  {
	    visibility = AICanSee(candidate, ComputeActorVisibility(candidate),
				  true, true, true, true);

	    if (visibility > 0)
	    {
              // We can see the potential enemy; ergo, we hate him
	      if (bPotentialEnemy)
	      {
		IncreaseAgitation(candidate, 1.0);
		PotentialEnemyAlliance = '';
		PotentialEnemyTimer    = 0;

                // DTR: Old style check. We now use IsValidMPEnemy
		// bValidEnemy = IsValidEnemy(candidate);
		//log("$$ checking valid mp enemy 9");
		bValidEnemy = IsValidMPEnemy(candidate);
	      }

	      if (bValidEnemy)
	      {
		visibility += VisibilityThreshold;
		candidateDist = VSize(Location-candidate.Location);
		if ((CycleCandidate == None) ||
		    (CycleDistance > candidateDist))
		{
		  CycleCandidate = candidate;
		  CycleDistance  = candidateDist;
		}

		if (!bPlayer)
		  CycleCumulative += 100000;  // a bit of a hack...
		else
		  CycleCumulative += visibility;
	      }
	    }
	  }

	  if (count >= 1)
	    break;
	}

	checked++;

	if (checked > 20)  // hacky hardcoded number
	  break;
      }

      if (lastCycle >= CycleIndex)  // have we cycled through all actors?
      {
	cycleEnemy = CheckCycle();
	if (cycleEnemy != None)
	{
	  SetDistressTimer();
	  SetEnemy(cycleEnemy, 0, true);
	  bCanSee = true;
	}
      }
    }
    else
      bNoNegativeAlliances = True;
  }

  // Handle surprise levels...
  UpdateReactionLevel((EnemyReadiness>0) || (GetStateName()=='Seeking') ||
		      bDistressed, deltaSeconds);

  if (!bValid)
  {
    CycleCumulative = 0;
    CyclePeriod     = 0;
    CycleCandidate  = None;
    CycleDistance   = 0;
    CycleTimer      = 0;
  }

  return (bCanSee);
*/
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function bool CheckBeamPresence(float deltaSeconds)
{
  local DeusExPlayer player;
  local Beam         beamActor;
  local bool         bReactToBeam;

  if (bReactPresence && bLookingForEnemy && (BeamCheckTimer <= 0) &&
      (LastRendered() < 5.0))
  {
    BeamCheckTimer = 1.0;
    player = DeusExPlayer(GetPlayerPawn());

    if (player != None)
    {
      bReactToBeam = false;

      // DTR: Old style check. We now use IsValidMPEnemy
      // if (IsValidEnemy(player))
      //log("$$ checking valid mp enemy 10");
      if (IsValidMPEnemy(player))
      {
	foreach RadiusActors(Class'Beam', beamActor, 1200)
	{
	  if ((beamActor.Owner == player) &&
	      (beamActor.LightType != LT_None) &&
	      (beamActor.LightBrightness > 32))
	  {
	    if (VSize(beamActor.Location - Location) <
		(beamActor.LightRadius+1)*25)
	      bReactToBeam = true;
	    else
	    {
	      if (AICanSee(beamActor, , false, true, false, false) > 0)
	      {
		if (FastTrace(beamActor.Location,
			      Location+vect(0,0,1)*BaseEyeHeight))
		  bReactToBeam = true;
	      }
	    }
	  }

	  if (bReactToBeam)
	    break;
	}
      }

      if (bReactToBeam)
	HandleSighting(player);
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function bool CheckCarcassPresence(float deltaSeconds)
{
  local Actor         carcass;
  local Name          CarcassName;
  local int           lastCycle;
  local DeusExCarcass body;
  local DeusExPlayer  player;
  local float         visibility;
  local Name          KillerAlliance;
  local Name          killedAlliance;
  local Pawn          killer;
  local Pawn          bestKiller;
  local float         dist;
  local float         bestDist;
  local float         maxCarcassDist;
  local int           maxCarcassCount;
  
  if (bFearCarcass && !bHateCarcass && !bReactCarcass)  // Major hack!
    maxCarcassCount = 1;
  else
    maxCarcassCount = ArrayCount(Carcasses);

  //if ((bHateCarcass || bReactCarcass || bFearCarcass) && bLookingForCarcass && (CarcassTimer <= 0))

  if ((bHateCarcass || bReactCarcass || bFearCarcass) &&
      (NumCarcasses < maxCarcassCount))
  {
    maxCarcassDist = 1200;
    if (CarcassCheckTimer <= 0)
    {
      CarcassCheckTimer = 0.1;
      carcass           = None;
      lastCycle         = BodyIndex;

      foreach CycleActors(Class'DeusExCarcass', body, BodyIndex)
      {
	if (body.Physics != PHYS_Falling)
	{
	  if (VSize(body.Location-Location) < maxCarcassDist)
	  {
	    if (GetCarcassData(body, KillerAlliance, killedAlliance,
			       CarcassName, true))
	    {
	      visibility = AICanSee(body, ComputeActorVisibility(body),
				    true, true, true, true);

	      if (visibility > 0)
		carcass = body;
	      break;
	    }
	  }
	}
      }

      if (lastCycle >= BodyIndex)
      {
	if (carcass == None)
	{
	  player = DeusExPlayer(GetPlayerPawn());
	  if (player != None)
	  {
	    if (VSize(player.Location-Location) < maxCarcassDist)
	    {
	      if (GetCarcassData(player, KillerAlliance, killedAlliance,
				 CarcassName, true))
	      {
		visibility = AICanSee(player, ComputeActorVisibility(player),
				      true, true, true, true);
		if (visibility > 0)
		  carcass = player;
	      }
	    }
	  }
	}
      }

      if (carcass != None)
      {
	CarcassTimer = 120;
	AddCarcass(CarcassName);
	if (bLookingForCarcass)
	{
	  if (KillerAlliance == 'Player')
	    killer = GetPlayerPawn();
	  else
	  {
	    bestKiller = None;
	    bestDist   = 0;
	    foreach AllActors(Class'Pawn', killer)  // hack
	    {
	      if (killer.Alliance == KillerAlliance)
	      {
		dist = VSize(killer.Location - Location);
		if ((bestKiller == None) || (bestDist > dist))
		{
		  bestKiller = killer;
		  bestDist   = dist;
		}
	      }
	    }
	    killer = bestKiller;
	  }

	  if (bHateCarcass)
	  {
	    PotentialEnemyAlliance = KillerAlliance;
	    PotentialEnemyTimer    = 15.0;
	    bNoNegativeAlliances   = false;
	  }

	  if (bFearCarcass)
	    IncreaseFear(killer, 2.0);
	  
          // DTR: Old style check. We now use IsValidMPEnemy
          // if (bFearCarcass && IsFearful() && !IsValidEnemy(killer))
	  //log("$$ checking valid mp enemy 11");
          if (bFearCarcass && IsFearful() && !IsValidMPEnemy(killer))
	  {
	    SetDistressTimer();
	    SetEnemy(killer, , true);
	    GotoState('Fleeing');
	  }
	  else
	  {
	    SetDistressTimer();
	    SetSeekLocation(killer, carcass.Location, SEEKTYPE_Carcass);
	    HandleEnemy();
	  }
	}
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function float LoudNoiseScore(actor receiver, actor sender, float score)
{
  local Pawn pawnSender;

  // Cull events received from friends
  pawnSender = Pawn(sender);
  if (pawnSender == None)
    pawnSender = sender.Instigator;
  if (pawnSender == None)
    score = 0;
  // DTR: Old style check. We now use IsValidMPEnemy
  // else if (!IsValidEnemy(pawnSender))
  else
  {
    //log("$$ checking valid mp enemy 12");
    if (!IsValidMPEnemy(pawnSender))
      score = 0;
  }
  
  return score;
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function float WeaponDrawnScore(actor receiver, actor sender, float score)
{
  local Pawn pawnSender;

  // Cull events received from enemies
  pawnSender = Pawn(sender);
  if (pawnSender == None)
    pawnSender = Pawn(sender.Owner);
  if (pawnSender == None)
    pawnSender = sender.Instigator;
  if (pawnSender == None)
    score = 0;
  // DTR: Old style check. We now use IsValidMPEnemy
  // else if (IsValidEnemy(pawnSender))
  else
  {
    //log("$$ checking valid mp enemy 13");
    if (IsValidMPEnemy(pawnSender))
      score = 0;
  }
  
  return score;
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function CheckEnemyParams(Pawn checkPawn,
                          out Pawn bestPawn, out int bestThreatLevel,
			  out float bestDist)
{
  local ScriptedPawn sPawn;
  local bool         bReplace;
  local float        dist;
  local int          threatLevel;
  local bool         bValid;
  
  // DTR: Old style check. We now use IsValidMPEnemy
  // bValid = IsValidEnemy(checkPawn);
  //log("$$ checking valid mp enemy 14");
  bValid = IsValidMPEnemy(checkPawn);

  if (bValid && (Enemy != checkPawn))
  {
    // Honor cloaking, radar transparency, and other augs if this guy
    // isn't our current enemy
    if (ComputeActorVisibility(checkPawn) < 0.1)
      bValid = false;
  }

  if (bValid)
  {
    sPawn = ScriptedPawn(checkPawn);

    dist = VSize(checkPawn.Location - Location);
    if (checkPawn.IsA('Robot'))
      dist *= 0.5;  // arbitrary
    if (Enemy == checkPawn)
      dist *= 0.75;  // arbitrary
    
    if (sPawn != None)
    {
      if (sPawn.bAttacking)
      {
	if (sPawn.Enemy == self)
	  threatLevel = 2;
	else
	  threatLevel = 1;
      }
      else if (sPawn.GetStateName() == 'Alerting')
	threatLevel = 3;
      else if ((sPawn.GetStateName() == 'Fleeing') ||
	       (sPawn.GetStateName() == 'Burning'))
	threatLevel = 0;
      else if (sPawn.Weapon != None)
	threatLevel = 1;
      else
	threatLevel = 0;
    }
    else  // player
    {
      if (checkPawn.Weapon != None)
	threatLevel = 2;
      else
	threatLevel = 1;
    }

    bReplace = false;
    if (bestPawn == None)
      bReplace = true;
    else if (bestThreatLevel < threatLevel)
      bReplace = true;
    else if (bestDist > dist)
      bReplace = true;
    
    if (bReplace)
    {
      if ((Enemy == checkPawn) || (AICanSee(checkPawn, , false,
					    false, true, true) > 0))
      {
	bestPawn        = checkPawn;
	bestThreatLevel = threatLevel;
	bestDist        = dist;
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
state Burning
{
  function ReactToInjury(Pawn instigatedBy, Name damageType,
			 EHitLocation hitPos)
  {
    local name newLabel;
    
    if (health > 0)
    {
      if (enemy != instigatedBy)
      {
	SetEnemy(instigatedBy);
	newLabel = 'NewEnemy';
      }
      else
	newLabel = 'ContinueBurn';
      
      if ( Enemy != None )
	LastSeenPos = Enemy.Location;
      SetNextState('Burning', newLabel);
      if ((damageType != 'TearGas') && (damageType != 'HalonGas') &&
	  (damageType != 'Stunned'))
	GotoDisabledState(damageType, hitPos);
    }
  }

  function SetFall()
  {
    StartFalling('Burning', 'ContinueBurn');
  }

  function HitWall(vector HitNormal, actor Wall)
  {
    if (Physics == PHYS_Falling)
      return;
    Global.HitWall(HitNormal, Wall);
    CheckOpenDoor(HitNormal, Wall);
  }

  function PickDestination()
  {
    local float           magnitude;
    local float           distribution;
    local int             yaw, pitch;
    local Rotator         rotator1;
    local NavigationPoint nav;
    local float           dist;
    local NavigationPoint bestNav;
    local float           bestDist;
    
    destPoint = None;
    bestNav   = None;
    bestDist  = 2000;   // max distance to water
    
    // Seek out water
    if (bCanSwim)
    {
      nav = Level.NavigationPointList;
      while (nav != None)
      {
	if (nav.Region.Zone.bWaterZone)
	{
	  dist = VSize(Location - nav.Location);
	  if (dist < bestDist)
	  {
	    bestNav  = nav;
	    bestDist = dist;
	  }
	}
	nav = nav.nextNavigationPoint;
      }
    }
    
    if (bestNav != None)
    {
      // It'd be nice if we could traverse all pathnodes and figure out their
      // distances...  unfortunately, it's too slow.  :(
      
      MoveTarget = FindPathToward(bestNav);
      if (MoveTarget != None)
      {
	destPoint = bestNav;
	destLoc   = bestNav.Location;
      }
    }
    
    // Can't get to water -- run willy-nilly
    if (destPoint == None)
    {
      if (Enemy == None)
      {
	yaw = 0;
	pitch = 0;
	distribution = 0;
      }
      else
      {
	rotator1 = Rotator(Location-Enemy.Location);
	yaw = rotator1.Yaw;
	pitch = rotator1.Pitch;
	distribution = 0.5;
      }

      magnitude = 300*(FRand()*0.4+0.8);  // 400, +/-20%
      if (!AIPickRandomDestination(100, magnitude, yaw, distribution, pitch,
				   distribution, 4,
				   FRand()*0.4+0.35, destLoc))
	destLoc = Location+(VRand()*200);  // we give up
    }
  }
  
  function BeginState()
  {
    StandUp();
    BlockReactions();
    bCanConverse = False;
    SetupWeapon(false, true);
    bStasis = False;
    SetDistress(true);
    EnemyLastSeen = 0;
    SeekPawn = None;
    EnableCheckDestLoc(false);
  }

  function EndState()
  {
    EnableCheckDestLoc(false);
    ResetReactions();
    bCanConverse = True;
    bStasis = True;
  }
  
Begin:
  if (!bOnFire)
    Goto('Done');
  PlayOnFireSound();

NewEnemy:
  Acceleration = vect(0, 0, 0);

Run:
  if (!bOnFire)
    Goto('Done');
  PlayPanicRunning();
  PickDestination();
  if (destPoint != None)
  {
    MoveToward(MoveTarget, MaxDesiredSpeed);
    while ((MoveTarget != None) && (MoveTarget != destPoint))
    {
      MoveTarget = FindPathToward(destPoint);
      if (MoveTarget != None)
	MoveToward(MoveTarget, MaxDesiredSpeed);
    }
  }
  else
    MoveTo(destLoc, MaxDesiredSpeed);
  Goto('Run');

Done:
  // DTR: Old style check. We now use IsValidMPEnemy
  // if (IsValidEnemy(Enemy))
  //log("$$ checking valid mp enemy 15");
  if (IsValidMPEnemy(Enemy))
    HandleEnemy();
  else
    FollowOrders();

ContinueBurn:
ContinueFromDoor:
  Goto('Run');
}

///////////////////////////////////////////////////////////////////////////////
// Provided to determine if a Pawn is an enemy during MP play
function bool IsValidMPEnemy(Pawn potEnemy)
{
  local DeusExPlayer player;

  // DTR: For now we make this *real* simple and just have every bot hate
  //      every player. This will become more complicated as the AI develops
  player = DeusExPlayer(potEnemy);
  if(player != None)
  {
    // If the player is dead, it's not a valid enemy
    if(player.Health <= 0)
      return false;

    //log("$$ In IsValidMPEnemy...returning true: " $ potEnemy.Name);
    return true;
  }

  return false;
}

///////////////////////////////////////////////////////////////////////////////
// 
function InitializeInventory()
{
  local int       i, j;
  local Inventory inv;
  local Weapon    weapons[8];
  local int       weaponCount;
  local Weapon    firstWeapon;

  //log("$$ Inside initialize inventory...");

  // Add initial inventory items
  weaponCount = 0;
  for (i=0; i<8; i++)
  {
    if ((InitialInventory[i].Inventory != None) &&
	(InitialInventory[i].Count > 0))
    {
      //log("$$ InitialInventory " $ InitialInventory[i].Inventory);
      firstWeapon = None;
      for (j=0; j<InitialInventory[i].Count; j++)
      {
	inv = None;
	if (Class<Ammo>(InitialInventory[i].Inventory) != None)
	{
	  inv = FindInventoryType(InitialInventory[i].Inventory);
	  if (inv != None)
          {
	    Ammo(inv).AmmoAmount += Class<Ammo>(InitialInventory[i].Inventory).default.AmmoAmount;
	    //log("Ammo amount = " $ Ammo(inv).AmmoAmount);
	  }
	}

	if (inv == None)
	{
          //log("$$ Spawning weapon " $ InitialInventory[i].Inventory);
	  inv = spawn(InitialInventory[i].Inventory, self);
	  if (inv != None)
	  {
	    inv.InitialState='Idle2';
	    inv.GiveTo(Self);
	    inv.SetBase(Self);
	    if ((firstWeapon == None) && (Weapon(inv) != None))
	      firstWeapon = Weapon(inv);
	  }
	}
      }

      if (firstWeapon != None)
	weapons[WeaponCount++] = firstWeapon;
    }
  }

  //log("$$ weaponCount " $ weaponCount);
  for (i=0; i<weaponCount; i++)
  {
    if ((weapons[i].AmmoType == None) && (weapons[i].AmmoName != None) &&
	(weapons[i].AmmoName != Class'AmmoNone'))
    {
      weapons[i].AmmoType = Ammo(FindInventoryType(weapons[i].AmmoName));
      if (weapons[i].AmmoType == None)
      {
	weapons[i].AmmoType = spawn(weapons[i].AmmoName);
	weapons[i].AmmoType.InitialState='Idle2';
	weapons[i].AmmoType.GiveTo(Self);
	weapons[i].AmmoType.SetBase(Self);
      }
    }
  }
  
  SetupWeapon(false);
}

///////////////////////////////////////////////////////////////////////////////
// Override this to see what's going on
function bool AddInventory( inventory NewItem )
{
  // Skip if already in the inventory.
  local inventory Inv;
	
  // The item should not have been destroyed if we get here.
  //  if (NewItem ==None )
  //log("tried to add none inventory to "$self);

  for( Inv=Inventory; Inv!=None; Inv=Inv.Inventory )
  {
    if( Inv == NewItem )
    {
      //log("$$ Found item...skipping");
      return false;
    }
  }

  // DEUS_EX AJY
  // Update the previous owner's inventory chain
  if (NewItem.Owner != None)
  {
    //log("$$ Deleting inventory");
    Pawn(NewItem.Owner).DeleteInventory(NewItem);
  }

  // Add to front of inventory chain.
  //log("$$ Adding to inventory chain...");
  NewItem.SetOwner(Self);
  NewItem.Inventory = Inventory;
  Inventory = NewItem;

  return true;
}

///////////////////////////////////////////////////////////////////////////////
// Handle methods

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function HandleLoudNoise(Name event, EAIEventState state, XAIParams params)
{
  // React

  local Actor bestActor;
  local Pawn  instigator;

  //log("** Inside HandleLoudNoise");

  if (state == EAISTATE_Begin || state == EAISTATE_Pulse)
  {
    bestActor = params.bestActor;
    if (bestActor != None)
    {
      instigator = Pawn(bestActor);
      if (instigator == None)
	instigator = bestActor.Instigator;
      if (instigator != None)
      {
        // DTR: Old style check. We now use IsValidMPEnemy
	// if (IsValidEnemy(instigator))
	//log("$$ checking valid mp enemy 16");
	if (IsValidMPEnemy(instigator))
	{
	  SetSeekLocation(instigator, bestActor.Location, SEEKTYPE_Sound);
	  HandleEnemy();
	}
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function HandleAlarm(Name event, EAIEventState state, XAIParams params)
{
  // React, Fear
  local AlarmUnit      alarm;
  local LaserTrigger   laser;
  local SecurityCamera camera;
  local Computers      computer;
  local Pawn           alarmInstigator;
  local vector         alarmLocation;
  
  //log("** Inside HandleAlarm");

  if (state == EAISTATE_Begin || state == EAISTATE_Pulse)
  {
    alarmInstigator = None;
    alarm    = AlarmUnit(params.bestActor);
    laser    = LaserTrigger(params.bestActor);
    camera   = SecurityCamera(params.bestActor);
    computer = Computers(params.bestActor);
    if (alarm != None)
    {
      alarmInstigator = alarm.alarmInstigator;
      alarmLocation   = alarm.alarmLocation;
    }
    else if (laser != None)
    {
      alarmInstigator = Pawn(laser.triggerActor);
      if (alarmInstigator == None)
	alarmInstigator = laser.triggerActor.Instigator;
      alarmLocation   = laser.actorLocation;
    }
    else if (camera != None)
    {
      alarmInstigator = GetPlayerPawn();  // player is implicit for cameras
      alarmLocation   = camera.playerLocation;
    }
    else if (computer != None)
    {
      alarmInstigator = GetPlayerPawn();  // player is implicit for computers
      alarmLocation   = computer.Location;
    }
    
    if (bFearAlarm)
    {
      IncreaseFear(alarmInstigator, 2.0);
      if (IsFearful())
      {
	SetDistressTimer();
	SetEnemy(alarmInstigator, , true);
	GotoState('Fleeing');
      }
    }

    if (alarmInstigator != None)
    {
      if (alarmInstigator.Health > 0)
      {
        // DTR: Old style check. We now use IsValidMPEnemy
	// if (IsValidEnemy(alarmInstigator))
	//log("$$ checking valid mp enemy 17");
	if (IsValidMPEnemy(alarmInstigator))
	{
	  AlarmTimer = 120;
	  SetDistressTimer();
	  SetSeekLocation(alarmInstigator, alarmLocation, SEEKTYPE_Sound);
	  HandleEnemy();
	}
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// Override this to control where we call IsValidEnemy
function HandleWeapon(Name event, EAIEventState state, XAIParams params)
{
  // Fear, Hate
  local Pawn pawnActor;

  //log("** Inside HandleWeapon");
  
  if (state == EAISTATE_Begin || state == EAISTATE_Pulse)
  {
    pawnActor = InstigatorToPawn(params.bestActor);
    if (pawnActor != None)
    {
      if (bHateWeapon)
	IncreaseAgitation(pawnActor);
      if (bFearWeapon)
	IncreaseFear(pawnActor, 1.0);
      
      // Let presence checking handle enemy sighting
      
      // DTR: Old style check. We now use IsValidMPEnemy
      // if (!IsValidEnemy(pawnActor))
      //log("$$ checking valid mp enemy 18");
      if (!IsValidMPEnemy(pawnActor))
      {
	if (bFearWeapon && IsFearful())
	{
	  SetDistressTimer();
	  SetEnemy(pawnActor, , true);
	  GotoState('Fleeing');
	}
	else if (pawnActor.bIsPlayer)
	  ReactToFutz();
      }
    }
  }
}

function HandleEnemy()
{
  //log("** Inside HandleEnemy");
  Super.HandleEnemy();
}

function HandleSighting(Pawn pawnSighted)
{
  //log("** Inside HandleSighting " $ pawnSighted.Name);
  Super.HandleSighting(pawnSighted);
}

function EHitLocation HandleDamage(int actualDamage, Vector hitLocation,
				   Vector offset, name damageType)
{
  //log("** Inside HandleDamage");
  Super.HandleDamage(actualDamage, hitLocation, offset, damageType);
}

function HandleFutz(Name event, EAIEventState state, XAIParams params)
{
  //log("** Inside HandleFutz");
  Super.HandleFutz(event, state, params);
}

function HandleHacking(Name event, EAIEventState state, XAIParams params)
{
  //log("** Inside HandleHacking");
  Super.HandleHacking(event, state, params);
}

function HandleShot(Name event, EAIEventState state, XAIParams params)
{
  //log("** Inside HandleShot");
  Super.HandleShot(event, state, params);
}

function HandleProjectiles(Name event, EAIEventState state, XAIParams params)
{
  //log("** Inside HandleProjectiles");
  Super.HandleProjectiles(event, state, params);
}

function HandleDistress(Name event, EAIEventState state, XAIParams params)
{
  //log("** Inside HandleDistress");
  Super.HandleDistress(event, state, params);
}

function Tick(float deltaTime)
{
  local float        dropPeriod;
  local float        adjustedRate;
  local DeusExPlayer player;
  local name         stateName;
  local vector       loc;
  local bool         bDoLowPriority;
  local bool         bCheckOther;
  local bool         bCheckPlayer;
  local float        Dist;
  local PlayerReplicationInfo pri;

//  //log("  ** Tick Tock **");
  player = DeusExPlayer(GetPlayerPawn());

  bDoLowPriority = true;
  bCheckPlayer   = true;
  bCheckOther    = true;

  if (bStandInterpolation)
    UpdateStanding(deltaTime);

  // this is UGLY!
  if (bOnFire && (health > 0))
  {
    stateName = GetStateName();
    if ((stateName != 'Burning') && (stateName != 'TakingHit') &&
	(stateName != 'RubbingEyes'))
      GotoState('Burning');
  }

  // DTR: If we have no enemy, check for one
  if(Enemy == None)
  {
    if(CheckForPlayer())
    {
      //log("ENTERING ATTACK STATE FROM TICK!");
      GotoState('Attacking');
    }
  }

/*
  if (bTickVisibleOnly)
  {
    if (DistanceFromPlayer > 1200)
      bDoLowPriority = false;
    if (DistanceFromPlayer > 2500)
      bCheckPlayer = false;
    if ((DistanceFromPlayer > 600) && (LastRendered() >= 5.0))
      bCheckOther = false;
  }
*/

/*
  if (bDisappear && (InStasis() || (LastRendered() > 5.0)))
  {
  Destroy();
  return;
  }
  
  if (PrePivotTime > 0)
  {
  if (deltaTime < PrePivotTime)
  {
  PrePivot = PrePivot + (DesiredPrePivot-PrePivot)*deltaTime/PrePivotTime;
  PrePivotTime -= deltaTime;
  }
  else
  {
  PrePivot = DesiredPrePivot;
  PrePivotTime = 0;
  }
  }
  
  if (bDoLowPriority)
  Super.Tick(deltaTime);
  
  UpdateAgitation(deltaTime);
  UpdateFear(deltaTime);
  
  AlarmTimer -= deltaTime;
  if (AlarmTimer < 0)
  AlarmTimer = 0;
  
  if (Weapon != None)
  WeaponTimer += deltaTime;
  else if (WeaponTimer != 0)
  WeaponTimer = 0;
  
  if ((ReloadTimer > 0) && (Weapon != None))
  ReloadTimer -= deltaTime;
  else
  ReloadTimer = 0;
  
  if (AvoidWallTimer > 0)
  {
  AvoidWallTimer -= deltaTime;
  if (AvoidWallTimer < 0)
  AvoidWallTimer = 0;
  }
  
  if (AvoidBumpTimer > 0)
  {
  AvoidBumpTimer -= deltaTime;
  if (AvoidBumpTimer < 0)
  AvoidBumpTimer = 0;
  }
  
  if (CloakEMPTimer > 0)
  {
  CloakEMPTimer -= deltaTime;
  if (CloakEMPTimer < 0)
  CloakEMPTimer = 0;
  }
  
  if (TakeHitTimer > 0)
  {
  TakeHitTimer -= deltaTime;
  if (TakeHitTimer < 0)
  TakeHitTimer = 0;
  }
  
  if (CarcassCheckTimer > 0)
  {
  CarcassCheckTimer -= deltaTime;
  if (CarcassCheckTimer < 0)
  CarcassCheckTimer = 0;
  }
  
  if (PotentialEnemyTimer > 0)
  {
  PotentialEnemyTimer -= deltaTime;
  if (PotentialEnemyTimer <= 0)
  {
  PotentialEnemyTimer    = 0;
  PotentialEnemyAlliance = '';
  }
  }
  
  if (BeamCheckTimer > 0)
  {
  BeamCheckTimer -= deltaTime;
  if (BeamCheckTimer < 0)
  BeamCheckTimer = 0;
  }
  
  if (FutzTimer > 0)
  {
  FutzTimer -= deltaTime;
  if (FutzTimer < 0)
  FutzTimer = 0;
  }
  
  if (PlayerAgitationTimer > 0)
  {
  PlayerAgitationTimer -= deltaTime;
  if (PlayerAgitationTimer < 0)
  PlayerAgitationTimer = 0;
  }
  
  if (DistressTimer >= 0)
  {
  DistressTimer += deltaTime;
  if (DistressTimer > FearSustainTime)
  DistressTimer = -1;
  }
  
  if (bHasCloak)
  EnableCloak(Health <= CloakThreshold);
  
  if (bAdvancedTactics)
  {
  if ((Acceleration == vect(0,0,0)) || (Physics != PHYS_Walking) ||
  (TurnDirection == TURNING_None))
  {
  bAdvancedTactics = false;
  if (TurnDirection != TURNING_None)
  MoveTimer -= 4.0;
  ActorAvoiding    = None;
  NextDirection    = TURNING_None;
  TurnDirection    = TURNING_None;
  bClearedObstacle = true;
  ObstacleTimer    = 0;
  }
  }
  
  if (bOnFire)
  {
  burnTimer += deltaTime;
  if (burnTimer >= BurnPeriod)
  ExtinguishFire();
  }
  
  if (bDoLowPriority)
  {
  if ((bleedRate > 0) && bCanBleed)
  {
  adjustedRate = (1.0-FClamp(bleedRate, 0.0, 1.0))*1.0+0.1;  // max 10 drops per second
  dropPeriod = adjustedRate / FClamp(VSize(Velocity)/512.0, 0.05, 1.0);
  dropCounter += deltaTime;
  while (dropCounter >= dropPeriod)
  {
  SpurtBlood();
  dropCounter -= dropPeriod;
  }
  bleedRate -= deltaTime/clotPeriod;
  }
  if (bleedRate <= 0)
  {
  dropCounter = 0;
  bleedRate   = 0;
  }
  }
*/
  
/*
  else
  {
    if (bDoLowPriority)
    {
      // Don't allow radius-based convos to interupt other conversations!
      if ((player != None) && (GetStateName() != 'Conversation') &&
	  (GetStateName() != 'FirstPersonConversation'))
	player.StartConversation(Self, IM_Radius);
    }
    
    if (CheckEnemyPresence(deltaTime, bCheckPlayer, bCheckOther))
      HandleEnemy();
    else
    {
      CheckBeamPresence(deltaTime);
      if (bDoLowPriority || LastRendered() < 5.0)
	CheckCarcassPresence(deltaTime);  // hacky -- may change state!
    }
  }
*/

  // Randomly spawn an air bubble every 0.2 seconds if we're underwater
/*
 * DTR: Who really cares about air bubbles for the time being. We can put
 *      this back in if performance gets better.
  if (HeadRegion.Zone.bWaterZone && bSpawnBubbles && bDoLowPriority)
  {
    swimBubbleTimer += deltaTime;
    if (swimBubbleTimer >= 0.2)
    {
      swimBubbleTimer = 0;
      if (FRand() < 0.4)
      {
	loc = Location + VRand() * 4;
	loc.Z += CollisionHeight * 0.9;
	Spawn(class'AirBubble', Self,, loc);
      }
    }
  }
*/

  // Handle poison damage
  UpdatePoison(deltaTime);
}

defaultproperties
{
     MaxSavedNavPoints=3
     bNeedToSwitchToBestWeapon=True
}
