///////////////////////////////////////////////////////////////////////////////////
// REAL TIME STRATEGY ADVENTURE - Kit
// FILE: rtsa_headera
// NAME: header functions for unit AI
// SCRIPTED BY: Deva Bryson Winblood
// DATE: 4/13/2003
///////////////////////////////////////////////////////////////////////////////////
// This header contains pathing and other AI critical parts for the AI
///////////////////////////////////////////////////////////////////////////////////

#include "rtsa_headers"

/////////////////////////////////////////
// FUNCTIONS
/////////////////////////////////////////
object fnRandomObject(object oArea,int nObType=0)
{ // This will return a random object from the area of the specified type
  // if none is specified it will return a random object placeable, door, waypoint,
  // or trigger
  // This WILL NOT return area transition objects  use fnRandomTrans for that
  object oRet=OBJECT_INVALID;
  int nObT=nObType;
  int nR;
  int nC;
  float fDist=0.0;
  int nLim=0;
  object oObB=GetFirstObjectInArea(oArea);
  SendMessageToPC(GetFirstPC(),"fnRandomObject("+GetName(oArea)+","+IntToString(nObType)+")");
  SendMessageToPC(GetFirstPC(),"   First Object in Area:"+GetTag(oObB)+" Type:"+IntToString(GetObjectType(oObB)));
  while(fDist<4.0&&nLim<3)
  { // find something not close
  if (nObType==0)
  {
    nR=d4();
    if (nR==1) nObT=OBJECT_TYPE_PLACEABLE;
    else if (nR==2) nObT=OBJECT_TYPE_WAYPOINT;
    else if (nR==3) nObT=OBJECT_TYPE_DOOR;
    else if (nR==4) nObT=OBJECT_TYPE_TRIGGER;
  }
  SendMessageToPC(GetFirstPC(),"   Object type chosen:"+IntToString(nObT));
  nC=0;
  while(nC<4&&(nObT==GetObjectType(oObB)||oObB==OBJECT_INVALID))
  { // make sure checker object is of a different type
    if(nObT!=OBJECT_TYPE_WAYPOINT)
      oObB=GetNearestObject(OBJECT_TYPE_WAYPOINT,oObB,1);
    else
      oObB=GetNearestObject(OBJECT_TYPE_PLACEABLE,oObB,1);
    nC++;
  } // make sure checker object is of a different type
  SendMessageToPC(GetFirstPC(),"  Object to compare against:"+GetTag(oObB));
  if (nObT==OBJECT_TYPE_PLACEABLE)
    nC=GetLocalInt(oArea,"nNumPlaceables");
  else if (nObT==OBJECT_TYPE_WAYPOINT)
    nC=GetLocalInt(oArea,"nNumWP");
  else if (nObT==OBJECT_TYPE_DOOR)
    nC=GetLocalInt(oArea,"nNumDoors");
  else if (nObT==OBJECT_TYPE_TRIGGER)
    nC=GetLocalInt(oArea,"nNumTriggers");
  if (nC==0&&nObType==0)
  { // no objects of that type exist
    if (nObT!=OBJECT_TYPE_PLACEABLE&&GetObjectType(oObB)!=OBJECT_TYPE_PLACEABLE&&GetLocalInt(oArea,"nNumPlaceables")>0)
    { // try for placeables
      nObT=OBJECT_TYPE_PLACEABLE;
      nC=GetLocalInt(oArea,"nNumPlaceables");
    } // try for placeables
    else if (nObT!=OBJECT_TYPE_WAYPOINT&&GetObjectType(oObB)!=OBJECT_TYPE_WAYPOINT&&GetLocalInt(oArea,"nNumWP")>0)
    { // try for waypoints
      nObT=OBJECT_TYPE_WAYPOINT;
      nC=GetLocalInt(oArea,"nNumWP");
    } // try for waypoints
    else if (nObT!=OBJECT_TYPE_DOOR&&GetObjectType(oObB)!=OBJECT_TYPE_DOOR&&GetLocalInt(oArea,"nNumDoors")>0)
    { // try a door
      nObT=OBJECT_TYPE_DOOR;
      nC=GetLocalInt(oArea,"nNumDoors");
    } // try a door
    else if (nObT!=OBJECT_TYPE_TRIGGER&&GetObjectType(oObB)!=OBJECT_TYPE_TRIGGER&&GetLocalInt(oArea,"nNumTriggers")>0)
    { // try a trigger
      nObT=OBJECT_TYPE_TRIGGER;
      nC=GetLocalInt(oArea,"nNumTriggers");
    } // try a trigger
  } // no objects of that type exist
  if (nC>0&&oObB!=OBJECT_INVALID)
  { // get the random object
    nR=Random(nC)+1;
    if (nObT==OBJECT_TYPE_PLACEABLE)
      oRet=GetNearestObject(OBJECT_TYPE_PLACEABLE,oObB,nR);
    else if (nObT==OBJECT_TYPE_WAYPOINT)
      oRet=GetNearestObject(OBJECT_TYPE_WAYPOINT,oObB,nR);
    else if (nObT==OBJECT_TYPE_DOOR)
      oRet=GetLocalObject(oArea,"oDoor"+IntToString(nR));
    else if (nObT==OBJECT_TYPE_TRIGGER)
      oRet=GetLocalObject(oArea,"oTrigger"+IntToString(nR));
  } // get the random object
  else if (oObB==OBJECT_INVALID)
   oRet=GetFirstObjectInArea(oArea);
   nLim++;
   fDist=GetDistanceToObject(oRet);
  } // find something not close
  return oRet;
} // fnRandomObject()

object fnRandomTrans(object oArea)
{ // Returns a random area transition target (trigger or door)
  object oRet=OBJECT_INVALID;
  int nC=GetLocalInt(oArea,"nNumTrans");
  int nR=Random(nC)+1;
  if (nC>0)
    oRet=GetLocalObject(oArea,"oTrans"+IntToString(nR));
  return oRet;
} // fnRandomTrans()

void fnMakeSureNotImpossible(object oDest,float fRange)
{
  float fDist=GetDistanceToObject(oDest);
  object oMe=OBJECT_SELF;
  if (fDist>fRange)
  { // not possible
    SetLocalInt(oMe,"nWanderCount",0);
    SetLocalFloat(oMe,"fDistR",fDist);
    SetLocalInt(oMe,"nASC",1000);
  } // not possible
} // fnMakeSureNotImpossible()

int fnMoveToDestination(object oDest,int nAS=TRUE,float fRange=2.5)
{ // Move to destination and anti-stuck procedures
  // RETURN VALUES  0 = moving, 1 = appear to have arrived, -1 = unreachable
  int nRet=0;
  object oMe=OBJECT_SELF;
  object oNear=GetLocalObject(oMe,"oNear");
  int nASC=GetLocalInt(oMe,"nASC"); // Anti-Stuck counter
  float fDistR=GetLocalFloat(oMe,"fDistR"); // distance recorded last time
  float fDist=GetDistanceToObject(oDest);
  int nRun=GetLocalInt(oMe,"nRun");
  object oBackup;
  int nSpeedB=0;
  int nWander=GetLocalInt(oMe,"nWander");
  int nR=GetMovementRate(oMe);
  int nWC=GetLocalInt(oMe,"nWanderCount");
  nSpeedB=7-nR;
  if (oNear!=OBJECT_INVALID&&fDist!=-1.0)
    DeleteLocalObject(oMe,"oNear");
  if (nSpeedB<1) nSpeedB=0;
  if (nWander!=TRUE)
  { // not currently wandering
  SendMessageToPC(GetFirstPC(),"fnMoveToDestination:"+GetTag(oDest)+" nASC:"+IntToString(nASC)+" fDist:"+FloatToString(fDist)+" fDistR:"+FloatToString(fDistR)+" nSpeedB:"+IntToString(nSpeedB));
  if (GetTransitionTarget(oDest)!=OBJECT_INVALID)
  { // this is a transition object
    oBackup=GetNearestObject(OBJECT_TYPE_ALL,oDest,1);
    if (GetTransitionTarget(oBackup)!=OBJECT_INVALID)
     oBackup=GetNearestObject(OBJECT_TYPE_ALL,oDest,2);
    if (GetTransitionTarget(oBackup)!=OBJECT_INVALID)
     oBackup=GetNearestObject(OBJECT_TYPE_ALL,oDest,3);
    if (oBackup!=OBJECT_INVALID)
    { // destination
      oDest=oBackup;
    } // destination
    else
      return -1;
    fDist=GetDistanceToObject(oDest);
  } // this is a transition object
  if (fDist!=-1.0&&fDist==fDistR&&nAS==TRUE)
  { // appears to be stuck
    nASC++;
    if (nASC>5+nSpeedB)
    { // extreme anti-stuck cannot reach destination
      SetLocalObject(oMe,"oDest",OBJECT_INVALID);
      SetLocalInt(oMe,"nASC",0);
      SetLocalInt(oMe,"nWanderCount",0);
      return -1; // signal failure to reach area
    } // extreme anti-stuck cannot reach destination
    else if (nASC>3+nSpeedB)
    { // badly stuck - teleport
      AssignCommand(oMe,ClearAllActions());
      AssignCommand(oMe,JumpToObject(oDest));
      SetLocalInt(oMe,"nWanderCount",0);
      SetLocalFloat(oMe,"fDistR",GetDistanceToObject(oDest));
      DelayCommand(2.5,fnMakeSureNotImpossible(oDest,fRange));
    } // badly stuck - teleport
    else if (nASC>1+nSpeedB)
    { // pick a nearby target to move to
      if (nWC<2)
      { // only 2 wander tries
      if (nWander!=TRUE)
      { // pick target
        oBackup=fnRandomObject(GetArea(oMe));
        if (oBackup==OBJECT_INVALID)
        { // pick our own
          oBackup=GetNearestObject(OBJECT_TYPE_ALL,oMe,d6());
          if (GetTransitionTarget(oBackup)!=OBJECT_INVALID)
           oBackup=OBJECT_INVALID;
        } // pick our own
        if (oBackup!=OBJECT_INVALID)
        { // valid object
          SetLocalInt(oMe,"nWander",TRUE);
          nWC++;
          SetLocalInt(oMe,"nWanderCount",nWC);
          AssignCommand(oMe,ClearAllActions());
          AssignCommand(oMe,ActionMoveToObject(oBackup,nRun,3.0));
          nR=4+d4();
          DelayCommand(IntToFloat(nR),AssignCommand(oMe,ClearAllActions()));
          DelayCommand(IntToFloat(nR)+0.1,AssignCommand(oMe,ActionMoveToObject(oDest,nRun,fRange)));
          DelayCommand(IntToFloat(nR)+4.0,SetLocalInt(oMe,"nWander",FALSE));
        } // valid object
        SetLocalFloat(oMe,"fDistR",fDist);
      } // pick target
      } // only 2 wander tries
      else
      { // abort - set to teleport time
        SetLocalFloat(oMe,"fDistR",GetDistanceToObject(oDest));
        SetLocalInt(oMe,"nASC",3+nSpeedB);
      } // abort - set to teleport time
    } // pick a nearby target to move to
    else if (nASC==2)
    { // kick start - move to object
      AssignCommand(oMe,ClearAllActions());
      AssignCommand(oMe,ActionMoveToObject(oDest,nRun,fRange));
      SetLocalFloat(oMe,"fDistR",fDist);
    } // kick start - move to object
    else
    { // move to object
      AssignCommand(oMe,ActionMoveToObject(oDest,nRun,fRange));
      SetLocalFloat(oMe,"fDistR",fDist);
    } // move to object
    SetLocalInt(oMe,"nASC",nASC);
  } // appears to be stuck
  else if (nAS!=TRUE&&fDist!=-1.0&&fDist==fDistR)
  { // Might be stuck but, don't use anti-stuck
    AssignCommand(oMe,ActionMoveToObject(oDest,nRun,fRange));
  } // Might be stuck but, don't use anti-stuck
  else if (fDist==-1.0)
  { // different area
    if (oNear==OBJECT_INVALID)
    { // near
      oNear=GetNearestObject(OBJECT_TYPE_ALL,oMe,1);
      fDist=GetDistanceToObject(oNear);
      if (fDist!=fDistR)
      { // seems to be moving
          AssignCommand(oMe,ActionMoveToObject(oDest,nRun,fRange));
          SetLocalInt(oMe,"nASC",0);
          SetLocalFloat(oMe,"fDistR",fDist);
      } // seems to be moving
      else
      { // might be stuck
        nASC++;
        SetLocalInt(oMe,"nASC",nASC);
        if (nASC>3)
        { // abort this
          return -1;
        } // abort this
        else if (nASC==2)
        { // kickstart
          AssignCommand(oMe,ClearAllActions());
          AssignCommand(oMe,ActionMoveToObject(oDest,nRun,fRange));
        } // kickstart
        else
        { // kickstart
          AssignCommand(oMe,ActionMoveToObject(oDest,nRun,fRange));
        } // kickstart
      } // might be stuck
    } // near

  } // different area
  else
  { // not stuck
    SetLocalInt(oMe,"nASC",0);
    SetLocalFloat(oMe,"fDistR",fDist);
    if (fDist!=-1.0&&fDist<(fRange+0.1))
    { // arrived
      SetLocalInt(oMe,"nWanderCount",0);
      return 1;
    } // arrived
  } // not stuck
  } // not currently wandering
  return nRet;
} // fnMoveToDestination()

object fnDirectConnection(object oStart,object oEnd)
{ // find out if there is a direct area transition between the two places
  object oRet=OBJECT_INVALID;
  int nX=GetLocalInt(oStart,"nNumTrans");
  int nC=1;
  object oT=GetLocalObject(oStart,"oTrans"+IntToString(nC));
  object oX;
  oX=GetTransitionTarget(oT);
  while (nC<=nX&&GetArea(oX)!=oEnd)
  { // look
    oT=GetLocalObject(oStart,"oTrans"+IntToString(nC));
    oX=GetTransitionTarget(oT);
    nC++;
  } // look
  if (GetArea(oX)==oEnd) oRet=oT;
  return oRet;
} // fnDirectConnection()


int fnBuildPath(object oEnd,int nMax=10)
{ // build a reverse path between this location and me
  int nRet=FALSE;
  object oMe=OBJECT_SELF;
  int nX;
  int nR;
  int nDepth=0;
  object oAt=oEnd;
  object oPrev;
  object oH1;
  object oH2;
  fnPush("Pathing","",0,oEnd); // put end point on stack
  oH1=fnDirectConnection(GetArea(oEnd),GetArea(oMe));
  if (oH1!=OBJECT_INVALID)
  { // direct connection
    oH2=GetTransitionTarget(oH1);
    fnPush("Pathing","",0,oH2);
    fnPush("Pathing","",0,GetNearestObject(OBJECT_TYPE_ALL,oH1,1));
    return TRUE;
  } // direct connection
  while(oAt!=oMe&&nDepth<=nMax)
  { // traverse nodes
    oH1=fnDirectConnection(GetArea(oAt),GetArea(oMe));
    if (oH1!=OBJECT_INVALID)
    { // direct connection
      oH1=GetTransitionTarget(oH1);
      fnPush("Pathing","",0,oH1);
      return TRUE;
    } // direct connection
    oH1=fnRandomTrans(GetArea(oAt));
    oH1=GetTransitionTarget(oH1);
    nX=0;
    while (GetArea(oH1)==oPrev&&nX<3)
    { // keep looking
      oH1=fnRandomTrans(GetArea(oAt));
      oH1=GetTransitionTarget(oH1);
      nX++;
    } // keep looking
    if (GetArea(oH1)!=oPrev)
    { // found a direction to try
      oPrev=GetArea(oAt);
      fnPush("Pathing","",0,oH1);
      oAt=oH1;
      nDepth++;
    } // found a direction to try
  } // traverse nodes
  return nRet;
} // fnBuildPath()

void fnFindPathTo(object oDest,int nMaxNodes=10)
{ // find a path
  object oMe=OBJECT_SELF;
  object oMyArea=GetArea(oMe);
  object oDestArea=GetArea(oDest);
  int nFoundPath=FALSE;
  int nPathing=GetLocalInt(oMe,"nPathing");
  if (nPathing!=TRUE)
  { // starting pathing
    fnFlushS("Pathing");
    fnInitializeStack("Pathing",3); // object stack
    if (oMyArea==oDestArea)
    { // same area - no pathing needed
      SendMessageToPC(GetFirstPC(),"Pathing: Same Area");
      fnPush("Pathing","",0,oDest);
      nFoundPath=TRUE;
    } // same area - no pathing needed
    else if (fnDirectConnection(oMyArea,oDestArea)!=OBJECT_INVALID)
    { // there is a direct link - no pathing needed
      fnPush("Pathing","",0,GetNearestObject(OBJECT_TYPE_ALL,oDest,1));
      fnPush("Pathing","",0,oDest);
      SendMessageToPC(GetFirstPC(),"Pathing: Direct link");
      nFoundPath=TRUE;
    } // there is a direct link - no pathing needed
    else
    { // build a path
      SendMessageToPC(GetFirstPC(),"Call fnBuildPath");
      nFoundPath=fnBuildPath(oDest,nMaxNodes);
    } // build a path
    if (nFoundPath==TRUE)
      SetLocalInt(oMe,"nPathing",FALSE);
  } // starting pathing
  else
  { // still looking
    SendMessageToPC(GetFirstPC(),"Pathing: Still calling fnBuildPath");
    nFoundPath=fnBuildPath(oDest,nMaxNodes);
    if (nFoundPath==TRUE)
      SetLocalInt(oMe,"nPathing",FALSE);
  } // still looking
} // fnFindPathTo()

int fnMovePath(object oDest,int nAS,float fRange=2.5)
{ // move following a path - this is the main movement function
  int nRet=0;
  object oMe=OBJECT_SELF;
  object oDirect;
  int nErr;
  int nTop=fnSizeS("Pathing");
  float fDist=GetDistanceToObject(oDest);
  SendMessageToPC(GetFirstPC(),"fnMovePath("+GetTag(oDest)+",AS:"+IntToString(nAS)+",RANGE:"+FloatToString(fRange)+")");
  if (nTop==0)
  { // no path
    if (fDist==-1.0||fDist>fRange)
    { // build a path
      SendMessageToPC(GetFirstPC(),"Building path");
      fnFindPathTo(oDest);
      nErr=fnMovePath(oDest,nAS,fRange);
      if (nErr==-1) return -1;
    } // build a path
    else
    { // we have arrived
      return 1;
    } // we have arrived
  } // no path
  else if (nTop==1)
  { // direct link
    oDirect=fnPopObject("Pathing",FALSE);
    fDist=GetDistanceToObject(oDirect);
    if (fDist!=-1.0&&fDist<=fRange)
    { // arrived
      SendMessageToPC(GetFirstPC(),"We arrived");
      oDirect=fnPopObject("Pathing");
      nRet=fnMovePath(oDest,nAS,fRange);
    } // arrived
    else
    { // move
      SendMessageToPC(GetFirstPC(),"Call Move");
      nErr=fnMoveToDestination(oDirect,nAS,fRange);
      if (nErr==-1)
      { // error
        return nErr;
      } // error
    } // move
  } // direct link
  else
  { // complex path
    SendMessageToPC(GetFirstPC(),"Complex path");
    oDirect=fnPopObject("Pathing",FALSE);
    fDist=GetDistanceToObject(oDirect);
    if (fDist!=-1.0&&fDist<=fRange)
    { // arrived at that node
      oDirect=fnPopObject("Pathing");
      nRet=fnMovePath(oDest,nAS,fRange);
    } // arrived at that node
    else
    { // move
      nErr=fnMoveToDestination(oDirect,nAS,fRange);
      if (nErr==-1)
      { // cannot reach node
        fnFlushS("Pathing");
        nRet=nErr;
      } // cannot reach node
    } // move
  } // complex path
  return nRet;
} // fnMovePath()