! MOVECLASS, a library file to provide movement for NPCs, whether random or
! directed.
!
! Version 5, written by Neil Brown-> neil@highmount.demon.co.uk
!
! Last altered 15 September 1997.
!
! Developed on an Acorn. Some weaker PC text editors (eg Notepad) don't like
! the fact that lines aren't terminated in exactly the same way as on PCs.
!
! The functions of this library are too complex to go into here, so please
! refer to the brief manual which should be near to where you found this
! file, and is named 'moveclass.txt' or something similar.
!
! This version accepts <dir>_to properties defined as routines as well as
! objects, so if, for example, you wish to code a lift, where east can
! either lead to the reception or the first floor, define e_to as a routine
! that returns the relevant location, or false if the lift is on the move.
! (Don't put any text messages here - if you need to put them in for the
! main player, use the before property of the room, and catch the 'GO'
! action.) With that, NPCs can now get in and out of lifts with no problems.
!
! If you are including the library file FOLLOWER.H in your game code, please
! include this file AFTERWARDS and not before, otherwise errors will occur.

Attribute explored;
Attribute blocked;
Property  npc_open;
Property  after_action;
Property  before_action;
Global    path_size_limit = 10;

Message "!! Compiling library extension MoveClass3 !!";

Class moveclass
  with walkoff "walks off",
       walkon "walks into the room",
       time_left 0,
       daemon
        [ p i n k j st sr tr tmp;
          if (self provides before_action) self.before_action();
          switch(self.move_type)
          { 0: ! Random movement
#ifdef DEBUG;
              print "Started first daemon section.^";
#endif;
              if (random(3)~=1) rfalse;
              p=parent(self);
              objectloop (i in compass)
              { j=p.(i.door_dir);
                if (ZRegion(j)==1 || ZRegion(j)==2) n++;
              }
              if (n==0) rfalse;
              k=random(n); n=0;
              objectloop (i in compass)
              { j=p.(i.door_dir);
                if (ZRegion(j)==1 || ZRegion(j)==2) n++;
                if (n==k)
                { if (ZRegion(j)==2) ! deal with it if it points to a routine
                  { tmp=j();
                    if (ZRegion(tmp)~=1) rfalse; ! can't get an object
                    j=tmp;
                  }
                  if (j has door) ! return location beyond the door
                  { if (~~(j provides npc_open)) rfalse; ! No npc_open
                    st=parent(j);
                    move j to parent(self);
                    sr=j.door_to();
                    move j to st;
                    tr=j.npc_open(self);  ! Attempts to open door
                    if (tr==false) rfalse; ! Can't open the door
                    j=sr;
                  }
                  MoveNPC(self, j, ##Go, i);
                  if (p==location)
                  { if (ZRegion(self.walkoff)==3)
                    { print "^", (The) self, " ", (string) self.walkoff, " ";
                      GiveDir(i); print ".^";
                    }
                    else
                    { self.walkoff(i);
                    }
                    if (self provides after_action) self.after_action();
                    rtrue;
                  }
                  if (j==location)
                  { if (ZRegion(self.walkon)==3)
                    { print "^", (The) self, " ", (string) self.walkon, ".^";
                    }
                    else
                    { self.walkon(i);
                    }
                  }
                  if (self provides after_action) self.after_action();
                  rtrue;
                }
              }
            1: ! Following the set path
#ifdef DEBUG;
              print "In second daemon bit.^";
#endif;
              i=self.&npc_dirs->self.npc_stage;
#ifdef DEBUG;
              print "i is ", (the) i, ", self is ", (the) self,
               "npc_stage is ", self.npc_stage, ".^";
#endif;
              p=parent(self);
              j=p.(i.door_dir);
              if (ZRegion(j)==2)
              { tmp=j();
#ifdef DEBUG;
                print "Trying first npc_blocked condition^";
#endif;
                if (ZRegion(tmp)~=1) {self.npc_blocked(); rfalse; }
                j=tmp;
              }
              if (j has door)
              { if ((~~(j provides npc_open)) &&
                  (j hasnt open || j has locked))
                 {
#ifdef DEBUG;
                   print "Second npc_blocked^";
#endif;
                   self.npc_blocked(); rfalse; }
                st=parent(j);
                move j to parent(self);
                sr=j.door_to();
                move j to st;
                if (j hasnt open || j has locked)
                { tr=j.npc_open(self);
                  if (tr==false) {
#ifdef DEBUG;
                                   print "Third npc_blocked bit^";
#endif;
                                   self.npc_blocked(); rfalse; }
                }
                j=sr;
              }
              MoveNPC(self, j, ##Go, i);
              self.npc_stage++;
              if (p==location)
              { if (ZRegion(self.walkoff)==3)
                { print "^", (The) self, " ", (string) self.walkoff, " ";
                  GiveDir(i); print ".^";
                }
                else
                { self.walkoff(i);
                }
              }
              if (j==location)
              { if (ZRegion(self.walkon)==3)
                { print "^", (The) self, " ", (string) self.walkon, ".^";
                }
                else
                { self.walkon(i);
                }
              }
              if (self provides after_action) self.after_action();
              if (self.npc_stage==path_size_limit ||
                  parent(self)==self.npc_target)
               self.npc_arrived();
              rtrue;
            2: ! Do nothing.
            default: "** ERROR: move_type set to an unacceptable value for \
             NPC ", (the) self, ". **";
          }
        ],
       npc_dirs 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
                0 0, ! Stores the directions NPC has to take
       npc_target, ! The target destination
       npc_ifblocked 0, ! Which action to take if the route is blocked
       npc_backtrack 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
                     0 0 0 0, ! Used by the search algorithm
       npc_blocked ! - Not user definable - acts on ifblocked number
        [ i;
#ifdef DEBUG;
          print "___ NPC has found route blocked ___^";
#endif;
          switch(self.npc_ifblocked)
          { 0: self.move_type=0;
            1: ! Not yet coded, but will wait for route to be unblocked.
            2: i=NPC_path(self,self.npc_target,0);
               if (i==false) self.move_type=0;
          }
        ],
       npc_arrived  ! Redefine this within the actual NPC object
        [;          ! for more sophisticated results. Deals with what
                    ! happens when an NPC arrives at its destination. In
                    ! this case, it returns to random movement.
          self.move_type=0;
        ],
       npc_stage 0, ! position along movement array - do not alter this
       move_type 0, ! 0 is random, 1 is following path, 2 is stand still
       follow_action, ! } In case Follower has been included but the NPC
       follow_object; ! } isn't of FollowClass.

!----------------------------------------------------------------------------
! Note: To calculate a pathway for an NPC and send it on its way, use the
! call 'NPC_path(npc_in_question,target_room,what_to_do_if_blocked)'.
! The last parameter can either be set to 0 or 2 at the moment - 0 means
! that if the path is found to be blocked (by, for example, a suddenly
! locked door), the NPC should stop following the path and move randomly.
! Passing 2 will attempt to calculate a different path. The routine will
! return true if a path has been found, or false if it hasn't. This routine
! and the class only support a search eight rooms deep, so if the target is
! more than eight rooms away on the shortest path, it will not be found.
! This is a temporary limitation and should become user-definable in a
! future version.
!----------------------------------------------------------------------------

[ NPC_path npc targetroom ifblocked thisroom count i numunbl numunex j tmp
           magicdir nextdir atmp nextunbl whattodo;
! targetroom and ifblocked are passed from the originator.
! thisroom = current room. count = no. of paths (pos of array)
! i = compass locations, j = places leading off, tmp = temporary variable
! numunbl = number of rooms unblocked from here
! numunex = number of rooms unexplored from here
! magicdir = only set if a dir from current location leads to targetroom
! nextdir = next likely direction to try, atmp = another temp variable
! nextunbl = next unblocked direction, if none are unexplored
! whattodo = deciding action
  thisroom=parent(npc);
  count=0;
  npc.npc_target=targetroom;
  npc.npc_stage=0;
  npc.npc_ifblocked=ifblocked;
  for (i=0: i<=(path_size_limit-1): i++)
  { npc.&npc_dirs->i=0;
  }
  do
   { numunbl=0; numunex=0; magicdir=0; nextdir=0; nextunbl=0;
     whattodo=0;
#ifdef DEBUG;
     print "*** Iteration *** - room is ", (the) thisroom, "^";
#endif;
     give thisroom explored;
     objectloop (i in compass)
     { j=thisroom.(i.door_dir);
       if (ZRegion(j)>0)
       { if (ZRegion(j)==2)
         { tmp=j();
           j=tmp;
         }
         if (ZRegion(j)==1)
         { if (j has door)
           { tmp=parent(j);
#ifdef DEBUG;
             print "Inside initial door check routine.:";
#endif;
             move j to thisroom;
             atmp=j.door_to();
             move j to tmp;
             if (j has locked || (j hasnt open && (~~(j provides npc_open))))
              {give j blocked explored;
#ifdef DEBUG;
               print "Door not openable.^";
#endif;
              }
             else
              {j=atmp;}
#ifdef DEBUG;
             print "target room now ", (the) j, ".^";
#endif;
           }
           if (j hasnt blocked) {numunbl++; nextunbl=i;}
           if (j hasnt explored) {numunex++; nextdir=i;}
           if (j==targetroom) magicdir = i;
         }
       }
     }
     if (numunbl==1) give thisroom blocked;
   ! Decide whattodo: 1 = found room, 2 = no path possible, 3 = backtrack,
   !                  4 = move forward
     if (ZRegion(magicdir)==1) whattodo=1;
     if (numunbl==0) whattodo=2;
     if (count>0)
     { if (numunbl==1 && Leadsto(nextunbl,thisroom)==
           npc.&npc_backtrack->(count-1))
        whattodo=3;
       if (whattodo==0)
       { if (numunex>0) whattodo=4;
         if (whattodo==0 && numunbl==1 &&
             Leadsto(nextunbl,thisroom)~=npc.&npc_backtrack->(count-1))
         { whattodo=4; nextdir=nextunbl; }
         if (numunex==0 && numunbl>1) whattodo=3; 
       }
     }
     if (count==(path_size_limit-1) && whattodo~=1) whattodo=3;
    ! backtrack if target not acquired by max. count no.
     if (count==0 && whattodo==0)
     { if (numunex>0) whattodo=4;
     }
     switch(whattodo)
     { 1: npc.&npc_dirs->count=magicdir; npc.move_type=1; CleanRooms(); rtrue;
       2: CleanRooms(); rfalse;
       3:
#ifdef DEBUG;
          print "** BACKTRACK ** count is ", count, "(-1)^";
#endif;
          count--;
          thisroom=npc.&npc_backtrack->count;
          npc.&npc_dirs->count=0;
       4: npc.&npc_dirs->count=nextdir;
#ifdef DEBUG;
          print "*** Move forward - nextdir is ", (the) nextdir,
           "***^";
          print "The relevant array reads ",
           (the) npc.&npc_dirs->count, ", count is ", count, "(+1)^";
#endif;
          count++;
          j=thisroom.(nextdir.door_dir);
          if (ZRegion(j)==2)
          { tmp=j();
            j=tmp;
          }
          if (ZRegion(j)~=1) "Error in npc_move - can't move";
          if (j has door)
          { tmp=parent(j);
            move j to thisroom;
            atmp=j.door_to();
            move j to tmp;
            j=atmp;
          }
#ifdef DEBUG;
          print "Attempting to set ", (the) j, " as current room.^";
#endif;
          npc.&npc_backtrack->(count-1)=thisroom;
          thisroom=j;
       default:
#ifdef DEBUG;
                print "** Error - whattodo is unset ***^nextdir = ",
                 (the) nextdir, ", nextunbl = ", (the) nextunbl,
                 ", number unexplored = ", numunex, ", number \
                 unblocked = ", numunbl, ", count = ", count, "thisroom = ",
                 (the) thisroom, ".^";
#endif;
                CleanRooms();
                rfalse;
     }
   }
  until (false);
];


! To use NPCPrePath: NPCPrePath(name_of_npc,name_of_array,number_of_entries,
! target_room);

[ NPCPrePath npcname arname arentries targetrm i;
#ifdef DEBUG;
  print "arname given as ", arname, ".^";
#endif;
  if (arentries>path_size_limit) "*** MoveClass Error: PrePath passed too \
   large a path ***";
  if (npcname ofclass moveclass)
  { for (i=0: i<arentries: i++)
    { npcname.&npc_dirs->i=arname->i;
#ifdef DEBUG;
      print " ", i, ": ", (the) arname->i, ".^";
#endif;
    }
    npcname.npc_stage=0;
    npcname.move_type=1;
    npcname.npc_target=targetrm;
  }
  else
  { "*** MoveClass Error: NPCPrePath called for non-moveclass object '",
     (the) npcname, "' ***";
  }
];

[ CleanRooms i;
  objectloop (i has explored)
   {
#ifdef DEBUG;
     if (parser_trace>=2) print "Explored: ", (the) i, ".^";
#endif;
     give i ~explored;
   }
  objectloop (i has blocked)
   {
#ifdef DEBUG;
     if (parser_trace>=2) print "Blocked: ", (the) i, ".^";
#endif;
     give i ~blocked;
   }
#ifdef DEBUG;
  print"ClearRooms finished.^";
#endif;
];

[ GiveDir i;
  switch(i)
  { n_obj: print "to the north";
    s_obj: print "to the south";
    e_obj: print "to the east";
    w_obj: print "to the west";
    ne_obj: print "to the northeast";
    nw_obj: print "to the northwest";
    se_obj: print "to the southeast";
    sw_obj: print "to the southwest";
    u_obj: print "up the stairs";
    d_obj: print "down the stairs";
    in_obj: print "inside";
    out_obj: print "outside";
  }
];

[ Leadsto i thisroom  j tmp atmp;
          j=thisroom.(i.door_dir);
          if (ZRegion(j)==2)
          { tmp=j();
            j=tmp;
          }
          if (ZRegion(j)~=1) "Error in npc_move - can't move";
          if (j has door)
          { tmp=parent(j);
            move j to thisroom;
            atmp=j.door_to();
            move j to tmp;
            j=atmp;
          }
  return j;
];

#ifndef MoveNPC; ! Provides MoveNPC if program isn't including 'Follower'

[ MoveNPC tomove dest actn objn;
  move tomove to dest;
  actn=actn;
  objn=objn;
];

#endif;