//////////////////////////////////////////////////////////////////////////////
// Real Time Strategy - NWN - Header file for some useful functions
//===========================================================================
// By Deva Bryson Winblood.  02/24/2003
//////////////////////////////////////////////////////////////////////////////


// FILE: rts_header
int fnMoveToDestination(object oWP,int nAS=FALSE,int nInitStack=FALSE);

////
int MAX_AI_SCAN = 4; // used to prevent TMI errors

int fnCheckForLight()
{ // fnCheckForLight
  object oMe=OBJECT_SELF;
  object oMod=GetModule();
  int nSun=GetLocalInt(oMod,GetTag(oMe)+"_light");
  return nSun;
} // fnCheckForLight

int fnArePCsPresent(object oArea)
{
  int nRet=0;
  object oOb=GetFirstObjectInArea(oArea);
  if (GetObjectType(oOb)!=OBJECT_TYPE_WAYPOINT) oOb=GetNearestObject(OBJECT_TYPE_WAYPOINT,oOb,1);
  if (GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR,PLAYER_CHAR_IS_PC,oOb,1)!=OBJECT_INVALID) return 1;
  return nRet;
} // fnCountPCsInArea()


object fnRandomObject(object oArea,int nType=OBJECT_TYPE_ALL)
{
  object oRet=OBJECT_INVALID;
  object oTest=GetFirstObjectInArea(oArea);
  int nC=GetLocalInt(oArea,"nAllObjects");
  if (nType==OBJECT_TYPE_PLACEABLE) nC=GetLocalInt(oArea,"nPlaceables");
  else if (nType==OBJECT_TYPE_TRIGGER) nC=GetLocalInt(oArea,"nTriggers");
  else if (nType==OBJECT_TYPE_DOOR) nC=GetLocalInt(oArea,"nDoors");
  else if (nType==OBJECT_TYPE_WAYPOINT) nC=GetLocalInt(oArea,"nWaypoints");
  oRet=GetNearestObject(nType,oTest,Random(nC)+1);
  return oRet;
} // fnRandomObject()


object fnExposed(object oD)
{ // return OBJECT_INVALID if has an RTS_SURFACE
   object oRet=OBJECT_INVALID;
   object oW;
   if (GetTransitionTarget(oD)!=OBJECT_INVALID)
   {
    oW=GetNearestObjectByTag("RTS_SURFACE",GetTransitionTarget(oD));
    if (oW==OBJECT_INVALID)
    { // flee here
      oRet=fnRandomObject(GetArea(GetTransitionTarget(oD)),OBJECT_TYPE_PLACEABLE);
    } // flee here
   }
   return oRet;
} // fnExposed

void fnInitializeStack(object oNPC)
{ // init the stack
  int nTop=GetLocalInt(oNPC,"nPSTop");
  int nL=1;
  //SendMessageToPC(GetFirstPC(),"fnInitializeStack was called");
  while (nL<=nTop)
  {
   DeleteLocalObject(oNPC,"nPSPath"+IntToString(nL));
   nL++;
  }
  SetLocalInt(oNPC,"nPSTop",0);
} // fnInitalizeStack()

void fnPush(object oNPC,object oPath)
{ // push a path on
  int nTop=GetLocalInt(oNPC,"nPSTop");
  nTop++;
  //SendMessageToPC(GetFirstPC(),"fnPush("+GetName(oNPC)+","+GetName(GetArea(oPath))+") TOP:"+IntToString(nTop));
  SetLocalInt(oNPC,"nPSTop",nTop);
  SetLocalObject(oNPC,"nPSPath"+IntToString(nTop),oPath);
} // fnPush

object fnPop(object oNPC)
{ // pop an item off of the stack
  int nTop=GetLocalInt(oNPC,"nPSTop");
  object oRet=OBJECT_INVALID;
  if (nTop>0)
  {
    oRet=GetLocalObject(oNPC,"nPSPath"+IntToString(nTop));
    DeleteLocalObject(oNPC,"nPSPath"+IntToString(nTop));
    nTop--;
    SetLocalInt(oNPC,"nPSTop",nTop);
  }
  return oRet;
} // fnPop()

object fnTransitionBetweenAreas(object oArea1,object oArea2)
{ // find a transition
  object oRet=OBJECT_INVALID;
  object oTest=GetFirstObjectInArea(oArea1);
  oTest=GetNearestObject(OBJECT_TYPE_PLACEABLE,oTest,1);
  if (oTest==OBJECT_INVALID) SendMessageToPC(GetFirstPC(),"RTS ERROR in area "+GetName(oArea1)+" no placeables available for algorithm to use.");
  int nDoors=GetLocalInt(oArea1,"nDoors");
  int nTriggers=GetLocalInt(oArea1,"nTriggers");
  int nC=1;
  object oWork=GetNearestObject(OBJECT_TYPE_DOOR,oTest,nC);
  object oTarg=GetTransitionTarget(oWork);
  //SendMessageToPC(GetFirstPC(),"fnTBA("+GetName(oArea1)+","+GetName(oArea2)+"):Test("+GetName(oTest)+") nDoors:"+IntToString(nDoors)+" nTriggers:"+IntToString(nTriggers));
  while(oRet==OBJECT_INVALID&&nC<=nDoors)
  { // test doors
    if (GetArea(oTarg)==oArea2) oRet=oTarg;
    nC++;
    oWork=GetNearestObject(OBJECT_TYPE_DOOR,oTest,nC);
    oTarg=GetTransitionTarget(oWork);
  } // test doors
  if (oRet==OBJECT_INVALID)
  { // keep looking
    nC=1;
    oWork=GetNearestObject(OBJECT_TYPE_TRIGGER,oTest,nC);
    oTarg=GetTransitionTarget(oWork);
    while(oRet==OBJECT_INVALID&&nC<=nTriggers)
    { // test Triggers
     if (GetArea(oTarg)==oArea2) oRet=oTarg;
     nC++;
     oWork=GetNearestObject(OBJECT_TYPE_TRIGGER,oTest,nC);
     oTarg=GetTransitionTarget(oWork);
    } // test Triggers
  } // keep looking
  return oRet;
} //fnTransitionBetweenAreas()

object fnGetNearTransitionTarget(object oTransition)
{ // Get a closeby object for the transition
  object oRet=OBJECT_INVALID;
  float fDist;
  object oTest=GetNearestObject(OBJECT_TYPE_PLACEABLE,oTransition,1);
  fDist=GetDistanceBetween(oTest,oTransition);
  if (oTest==OBJECT_INVALID||fDist>10.0)
  { // keep looking
     oTest=GetNearestObject(OBJECT_TYPE_ITEM,oTransition,1);
     fDist=GetDistanceBetween(oTest,oTransition);
     if (oTest==OBJECT_INVALID||fDist>10.0)
     { // keep looking
       oTest=GetNearestObject(OBJECT_TYPE_STORE,oTransition,1);
       fDist=GetDistanceBetween(oTest,oTransition);
       if (oTest==OBJECT_INVALID||fDist>10.0)
       { // keep looking
         oTest=GetNearestObject(OBJECT_TYPE_WAYPOINT,oTransition,1);
         fDist=GetDistanceBetween(oTest,oTransition);
         if (oTest==OBJECT_INVALID||fDist>10.0)
         { // keep looking
           oRet=fnRandomObject(GetArea(oTransition),OBJECT_TYPE_WAYPOINT);
         } // keep looking
         else
          oRet=oTest;
       } // keep looking
       else
        oRet=oTest;
     } // keep looking
     else
       oRet=oTest;
  } // keep looking
  else
   oRet=oTest;
   //SendMessageToPC(GetFirstPC(),"fnGetNearTransitionTarget("+GetTag(oTransition)+":area["+GetName(GetArea(oTransition))+"])="+GetTag(oRet));
  return oRet;
} // fnGetNearTransitionTarget()

int fnNotInStack(object oArea)
{
   int nRet=TRUE;
   int nC=0;
   int nTop=GetLocalInt(OBJECT_SELF,"nStackTop");
   if (nTop>0)
   {
     while(nC<nTop&&nRet)
     { // check stack entries
       nC++;
       if (GetLocalObject(OBJECT_SELF,"oStack"+IntToString(nC))==oArea) nRet=FALSE;
     } // check stack entries
   }
   return nRet;
} // fnNotInStack

void fnPushPSR(object oArea)
{ // push Path Stack Recursive
  int nTop=GetLocalInt(OBJECT_SELF,"nPSRTop");
  nTop++;
  //SendMessageToPC(GetFirstPC(),"fnPushPSR("+GetName(oArea)+") TOP:"+IntToString(nTop));
  SetLocalObject(OBJECT_SELF,"oPSR"+IntToString(nTop),oArea);
  SetLocalInt(OBJECT_SELF,"nPSRTop",nTop);
} // fnPushPSR()

void fnPushStack(object oArea)
{ // push Path Stack Recursive
  int nTop=GetLocalInt(OBJECT_SELF,"nStackTop");
  nTop++;
  //SendMessageToPC(GetFirstPC(),"fnPushStack("+GetName(oArea)+") TOP:"+IntToString(nTop));
  SetLocalObject(OBJECT_SELF,"oStack"+IntToString(nTop),oArea);
  SetLocalInt(OBJECT_SELF,"nStackTop",nTop);
} // fnPushPSR()

object fnPopStack()
{
  object oRet=OBJECT_INVALID;
  int nTop=GetLocalInt(OBJECT_SELF,"nStackTop");
  if (nTop!=0)
  { // pop
    oRet=GetLocalObject(OBJECT_SELF,"oStack"+IntToString(nTop));
    DeleteLocalObject(OBJECT_SELF,"oStack"+IntToString(nTop));
    nTop--;
    //SendMessageToPC(GetFirstPC(),"fnPopStack("+GetName(oRet)+") TOP:"+IntToString(nTop));
    SetLocalInt(OBJECT_SELF,"nStackTop",nTop);
  } // pop
  //if (nTop==0) SendMessageToPC(GetFirstPC(),"Empty Stack - oStack");
  return oRet;
} // fnPopPSR()

int fnFindAreasBetween(object oSource,object oD,int nMax,int nNode=0)
{ // build a one time stack of areas for use in fnBuildPath
    int nRet=nNode;
    int nHigh=nMax;
    int nRes;
    int nDoors=GetLocalInt(oSource,"nDoors");
    int nTriggers=GetLocalInt(oSource,"nTriggers");
    object oFOB=fnRandomObject(oSource,OBJECT_TYPE_WAYPOINT);
    int nC=0;
    object oNull;
    object oPath=fnRandomObject(oSource,OBJECT_TYPE_DOOR);
    object oTarg=GetTransitionTarget(oPath);
    //SendMessageToPC(GetFirstPC(),"==Pathing:"+GetName(oSource)+","+GetName(oD)+","+IntToString(nMax)+","+IntToString(nNode));
    if (nNode==nMax) return 0;
    while(nC<2)
    { // check 2 door paths
      if (oTarg!=OBJECT_INVALID)
      { // is a transition
        if(oD==GetArea(oTarg))
        { // target destination
          fnPushPSR(GetArea(oTarg));
          return nRet+1; // destination reached
        } // target destination
        else if (fnNotInStack(GetArea(oTarg))==TRUE)
        { // haven't been here before
          fnPushStack(GetArea(oTarg));
          nRes=fnFindAreasBetween(GetArea(oTarg),oD,nMax,nNode+1);
          oNull=fnPopStack();
          if (nRes!=0)
          {
            fnPushPSR(oNull);
            return nRes;
          }
        } // haven't been here before
      } // is a transition
      nC++;
      oPath=fnRandomObject(oSource,OBJECT_TYPE_DOOR);
    } // check 2 door paths
    oPath=fnRandomObject(oSource,OBJECT_TYPE_TRIGGER);
    oTarg=GetTransitionTarget(oPath);
    nC=0;
    while(nC<2)
    { // check 2 TRIGGER paths
      if (oTarg!=OBJECT_INVALID)
      { // is a transition
        if(oD==GetArea(oTarg))
        { // target destination
          fnPushPSR(GetArea(oTarg));
          return nRet+1; // destination reached
        } // target destination
        else if (fnNotInStack(GetArea(oTarg))==TRUE)
        { // haven't been here before
          fnPushStack(GetArea(oTarg));
          nRes=fnFindAreasBetween(GetArea(oTarg),oD,nMax,nNode+1);
          oNull=fnPopStack();
          if (nRes!=0)
          {
            fnPushPSR(oNull);
            return nRes;
          }
        } // haven't been here before
      } // is a transition
      nC++;
      oPath=fnRandomObject(oSource,OBJECT_TYPE_TRIGGER);
    } // check 2 TRIGGER path
    return 0;
} // fnFindAreasBetween()


object fnPopPSR()
{
  object oRet=OBJECT_INVALID;
  int nTop=GetLocalInt(OBJECT_SELF,"nPSRTop");
  if (nTop!=0)
  { // pop
    oRet=GetLocalObject(OBJECT_SELF,"oPSR"+IntToString(nTop));
    DeleteLocalObject(OBJECT_SELF,"oPSR"+IntToString(nTop));
    nTop--;
    SetLocalInt(OBJECT_SELF,"nPSRTop",nTop);
  } // pop
  return oRet;
} // fnPopPSR()

void fnBuildPath(object oA,object oD,int nMaxNodes=8)
{ // build the reverse stack path
  int nRes;
  object oNode;
  object oNull;
  //SendMessageToPC(GetFirstPC(),"Look for path");
  nRes=fnFindAreasBetween(GetArea(oD),oA,nMaxNodes);
  if (nRes==0)
  { // delay then look again
    DelayCommand(4.0,fnBuildPath(oA,oD,nMaxNodes)); // try again
  } // delay then look again
  else
  { // path found
   //SendMessageToPC(GetFirstPC(),"Path found");
   fnPush(OBJECT_SELF,oD);
   //fnPush(OBJECT_SELF,fnRandomObject(GetArea(oD),OBJECT_TYPE_WAYPOINT));
   oNode=fnPopPSR();
   while(oNode!=OBJECT_INVALID)
   { // build path
     fnPush(OBJECT_SELF,fnRandomObject(oNode,OBJECT_TYPE_WAYPOINT));
     oNode=fnPopPSR();
     //if (GetLocalInt(OBJECT_SELF,"nPSRTop")==1) oNull=fnPopPSR();
   } // build path
   SetLocalInt(OBJECT_SELF,"nPathingActive",FALSE);
  } // path found
} // fnBuildPath()

int fnMoveTo(object oDest,int nAS=FALSE)
{ // move
  int nRet=0;
  object oMe=OBJECT_SELF;
  int nASC=GetLocalInt(oMe,"nASC"); // Anti-stuck counter
  float fDist=GetDistanceBetween(oDest,oMe);
  int nPCs=fnArePCsPresent(GetArea(oMe))+fnArePCsPresent(GetArea(oDest));
  int nRun=GetLocalInt(oMe,"nRun");
  int nConfused=GetLocalInt(oMe,"nConfused");
  object oConfused=GetLocalObject(oMe,"oConfused");
  float fLDist=GetLocalFloat(oMe,"fLastDist");
  int nMR=GetMovementRate(oMe);
  object oArbitrary=GetFirstObjectInArea(GetArea(oMe));
  if (GetObjectType(oArbitrary)!=OBJECT_TYPE_WAYPOINT) oArbitrary=GetNearestObject(OBJECT_TYPE_WAYPOINT,oArbitrary,1);
  if (GetArea(oMe)!=GetArea(oDest)) fDist=GetDistanceBetween(oMe,oArbitrary);
  if (nMR==7) nMR=4;
  if (oDest==OBJECT_INVALID) return -1;
  if (nAS==TRUE)
  { // anti-stuck is engaged
     if (fLDist==fDist)
     { // same distance as last time
       nASC++;
       SetLocalInt(oMe,"nASC",nASC);
       if (nPCs>0)
       { // there are PCs - aggressive anti-stuck
         //SendMessageToPC(GetFirstPC(),"Anti-Stuck PCs");
         if (nASC>11)
         { // that did not work - pick a better destination
           nRet=-1;
         } // that did not work - pick a better destination
         else if (nASC>9)
         { // very stuck - teleport
           AssignCommand(oMe,ClearAllActions());
           AssignCommand(oMe,ActionJumpToObject(oDest));
         } // very stuck - teleport
         else if (nASC>4)
         { // moderately stuck - look for nearby
           if (oConfused==OBJECT_INVALID)
             SetLocalObject(oMe,"oConfused",oDest);
           else if (oConfused!=oDest) nConfused=0;
           AssignCommand(oMe,ClearAllActions());
           oDest=GetNearestObject(OBJECT_TYPE_WAYPOINT,oMe,d12());
           AssignCommand(oMe,ActionMoveToObject(oDest,nRun,1.0));
           DelayCommand(8.0,AssignCommand(oMe,ClearAllActions()));
           nConfused++;
           if (nConfused>3) nRet=-1;
           SetLocalInt(oMe,"nConfused",nConfused);
         } // moderately stuck - look for nearby
         else
         { // normal reminder
           AssignCommand(oMe,ActionMoveToObject(oDest,nRun,1.0));
         } // normal reminder
       } // there are PCs - aggressive anti-stuck
       else
       { // no PCs-- teleport after 10
         //SendMessageToPC(GetFirstPC(),"Anti-Stuck NO PCs");
         if (nASC>(15-nMR))
         { // that did not work - pick a better destination
           nRet=-1;
         } // that did not work - pick a better destination
         else if (nASC>(13-nMR))
         { // teleport
           AssignCommand(oMe,ClearAllActions());
           AssignCommand(oMe,ActionJumpToObject(oDest));
           SetLocalInt(oMe,"nASC",0);
         } // teleport
       } // no PCs-- teleport after 10
     } // same distance as last time
     else
     { // moved
       SetLocalInt(oMe,"nASC",0);
       SetLocalFloat(oMe,"fLastDist",fDist);
     } // moved
  } // anti-stuck is engaged
  else
  { // no anti-stuck
    AssignCommand(oMe,ActionMoveToObject(oDest,nRun,1.0));
  } // no anti-stuck
  return nRet;
} // fnMoveTo()

int fnMoveToDestination(object oWP,int nAS=FALSE,int nInitStack=FALSE)
{ // Move to Waypoint with Anti-Stuck on/off
  // nInitStack = do I need to setup the pathing stack
  int nRet=0;
  object oMe=OBJECT_SELF;
  int nPSTop=GetLocalInt(oMe,"nPSTop");
  object oArea=GetArea(oMe);
  object oDArea=GetArea(oWP);
  object oDest=GetLocalObject(oMe,"oDestWP");
  float fDist;
  object oBinder=GetObjectByTag("RTS_BINDER");
  int nPA=GetLocalInt(oMe,"nPathingActive");
  if (oWP==OBJECT_INVALID) return -1;
  if (nInitStack==FALSE&&nPSTop!=0)
  { // there is stack info to process still
    if(oDest==oBinder)
    { // first call
     //SendMessageToPC(GetFirstPC(),"First Stack Pathing Call");
     oDest=fnPop(oMe);
     SetLocalObject(oMe,"oDestWP",oDest);
    } // first call
    if (fnTransitionBetweenAreas(GetArea(oMe),GetArea(oDest))==OBJECT_INVALID)
    { // not a valid path
      oDest=fnPop(oMe);
      SetLocalObject(oMe,"oDestWP",oDest);
    } // not a valid path
    fDist=GetDistanceBetween(oDest,oMe);
    if ((fDist==0.0&&GetArea(oDest)!=oArea)||fDist>7.0)
    { // still too far away
     //SendMessageToPC(GetFirstPC(),"Stack Path Dest:"+GetTag(oDest)+"  Area:"+GetName(GetArea(oDest)));
     nRet=fnMoveTo(oDest,nAS);
    } // still too far away
    else
    { // pop an item off the stack
      oDest=fnPop(oMe);
      //SendMessageToPC(GetFirstPC(),"Stack Path: Pop off next destination:"+GetTag(oDest)+" Area:"+GetName(GetArea(oDest)));
      SetLocalObject(oMe,"oDestWP",oDest);
      nRet=fnMoveTo(oDest,nAS);
    } // pop an item off the stack
  } // there is stack info to process still
  else if (nInitStack==TRUE)
  { // initialize the stack
    fnInitializeStack(oMe);
    SetLocalInt(oMe,"nPathingActive",TRUE);
    SetLocalObject(oMe,"oDestWP",oBinder);
    fnBuildPath(oArea,oWP);
    // build pathing stack backwards from destination
  } // initialize the stack
  else if (nPA==FALSE)
  { // No stack items & not waiting on complex pathing
    if (oArea==oDArea)
    { // same area
     nRet=fnMoveTo(oWP,nAS);
     if (nRet==-1)
        { // abort
          SetLocalObject(oMe,"oDestWP",OBJECT_INVALID);
        } // abort
    } // same area
    else
    { // different areas
      oDest=fnTransitionBetweenAreas(oArea,oDArea);
      if (oDest!=OBJECT_INVALID)
      { // adjacent areas
        nRet=fnMoveTo(oDest,nAS);
      } // adjacent areas
      else
      { // no direct connection
        nRet=fnMoveToDestination(oWP,nAS,TRUE); // initialize stack
      } // no direct connection
    } // different areas
  } // No stack items & not waiting on complex pathing
  return nRet;
} // fnMoveToDestination()