!--------------------------------------------------------------------------
! THE THIEF: an interactive demonstration by Gareth Rees
!
! The Inform designer's manual has a brief example of a thief who can walk
! around the map like the player does.  However, that example didn't really
! exploit the full complexities of Inform movement rules, so here's an
! example game in which the thief can walk through doors, pick locks, cross
! bridges and so on, and change his actions as the map changes.
!
! There are various subtleties.  First of all, some movement routines print
! messages when the player travels (e.g. "You duck your head as you go
! through the door.").  Clearly, these should be modified or omitted when
! the thief moves, so we provide a global variable `moving_thief' which is
! set to 1 when the thief is moving so that the movement routines can
! modify their behaviour appropriately.
!
! Second, doors report their destination and direction by checking to see
! which room they are in, for example:
!
!       door_dir [;
!           if (self in EastSide) return w_to;
!           return e_to;
!       ],
!
! If you want this to work with the thief you should be careful to avoid
! the following, which although apparently equivalent, doesn't quite work:
!
!       door_dir [;
!           if (location == EastSide) return w_to;
!           return e_to;
!       ],
!
! The problem is that `location' always refers to where the player is, and
! the thief might be somewhere else entirely.  Even in the first case, we
! still have to take care to move the door to where the thief is (it might
! have been moved elsewhere by `MoveFloatingObjects').
!--------------------------------------------------------------------------

Constant DEBUG;
Constant Story "THE THIEF";
Constant Headline "^An interactive demonstration^by Gareth Rees^";
Global moving_thief = 0;

Include "parser";
Include "verblib";

[ Initialise;
   location = EastSide;
   StartDaemon(Thief);
   "^^^^^The question is, who's going to get the treasure, you or that \
   pesky thief?^^";
];


!--------------------------------------------------------------------------
! PEOPLE WHO CARRY/WEAR OBJECTS
!
! People who are members of CarryingClass can wear clothing and carry
! objects, and (unless concealed) they will be listed (in two lists, one
! for clothing, one for everything else) when the person is examined.
!
! Note: any object of this class should not print a new-line after their
! description, because the listing of their inventory follows directly.
!--------------------------------------------------------------------------

Class   CarryingClass
has    animate transparent
with   before [ i j k;
        Examine:
           if (location == thedark) return L__M(##Examine,1);
           PrintOrRun(self,description,1);
           objectloop (k in self) {
               if (k hasnt worn && k hasnt concealed) {
                   give k workflag;
                   i++;
               }
               else give k ~workflag;
           }
           if (i > 0) {
               print " ", (CPronounNom) self, " is carrying ";
               WriteListFrom(child(self), FULLINV_BIT + ENGLISH_BIT +
                   WORKFLAG_BIT + CONCEAL_BIT);
           }
           objectloop (k in self) {
               if (k has worn && k hasnt concealed) {
                   give k workflag;
                   j++;
               }
               else give k ~workflag;
           }
           if (j > 0) {
               if (i == 0) {
                   print " ", (CPronounNom) self, " is";
               }
               else print ", and";
               print " wearing ";
               WriteListFrom(child(self), ENGLISH_BIT + WORKFLAG_BIT);
           }
           if (i > 0 || j > 0) print ".";
           new_line;
           rtrue;
       ];


!--------------------------------------------------------------------------
! THINGS THAT CAN BE FOLLOWED
!
! Members of FollowClass can be followed when they travel from the player's
! current room into another room. The code maintains the room that the
! object just left (in just_visited) and the direction that it went (in
! follow_dir). When the player types "follow thing", FollowScope adds to
! scope all objects that are is_followable, and which have just_visited
! equal to the location. If a match is made, the player is moved in the
! follow_dir direction. This copes with, e.g. the object going through a
! door and locking it.
!
! The various properties are maintained by MoveNPC() and MovePrintNPC() -
! the latter describing the movement of the object, though it has to assume
! that map connections never twist and turn in order to print the direction
! of arrival.
!
! The entry point NewRoom() is used to clear the just_visited property,
! otherwise you could follow objects you never even saw move.
!--------------------------------------------------------------------------

Property follow_dir 0;
Property just_visited 0;
Attribute is_followable;

Class   FollowClass
has    is_followable
with   just_visited 0,
       follow_dir 0;

[ NewRoom i;
   for (i = selfobj + 1: i <= top_object: i++)
       if (i has is_followable)
           i.just_visited = 0;
];

[ FollowScope i;
   if (scope_stage == 1) rfalse;
   if (scope_stage == 2) {
       for (i = selfobj + 1: i <= top_object: i++) {
           if (i has is_followable && i.just_visited == location)
               PlaceInScope(i);
       }
       rfalse;
   }
   "You've no idea where that is.";
];

[ FollowSub;
   if (noun == player) "You can't follow yourself.";
   if (noun in location) "No need!";
   if (noun.follow_dir == 0)
       print_ret "You start after ", (the) noun, " but you can't find ",
           (PronounAcc) noun, ". As far as you're concerned, ",
           (PronounNom) noun, " has vanished into thin air.";
   <Enter noun.follow_dir>;
];

[ NoFollowSub;
   if (noun == player) "You can't follow yourself.";
   print_ret (The) noun, " is right here.";
];

[ PronounAcc i;
   if (i hasnt animate) print "it";
   else { if (i has female) print "her"; else print "him"; } ];

[ PronounNom i;
   if (i hasnt animate) print "it";
   else { if (i has female) print "she"; else print "he"; } ];

[ CPronounNom i;
   if (i hasnt animate) print "It";
   else { if (i has female) print "She"; else print "He"; } ];

[ MoveNPC n dest dir;
   if (n has is_followable) {
       n.just_visited = parent(n);
       n.follow_dir = dir;
   }
   if (dest == 0) remove n;
   else move n to dest;
];

[ MovePrintNPC n dest dir;
   if (n in location) {
       print "^", (The) n, " stalks away";
       if (dir ~= in_obj or out_obj or u_obj && dir ~= d_obj)
           print "to the ", (DirectionName) dir.door_dir;
       print ".^";
   }
   MoveNPC(n, dest, dir);
   if (n in location)
       print "^", (The) n, " stalks in.^";
];

Include "grammar";

Verb "follow" "chase" "pursue" "trail"
   * scope=FollowScope                 -> Follow
   * "after" scope=FollowScope         -> Follow
   * noun                              -> NoFollow
   * "after" noun                      -> NoFollow;


!--------------------------------------------------------------------------
! THE DUNGEON
!
! Some scenery for the thief to wander round, with changing exits and
! entrances.
!--------------------------------------------------------------------------

Constant TooWide "The fissure is too wide to cross.";

Object  EastSide "East side of the fissure"
has    light
with   name "fissure",
       description "Mist rises from the gaping fissure to the west, and \
           passages lead north and east.",
       n_to [;
           if (moving_thief == 0)
               print "The passage turns to the right.^";
           return PassageA;
       ],
       e_to PassageB,
       w_to TooWide,
       s_to 0,
       before [;
        Jump:
           deadflag = 1;
           "You plunge to your doom.";
        Wave:
           if (noun == BlackRod) {
               if (CrystalBridge hasnt absent) {
                   remove CrystalBridge;
                   give CrystalBridge absent;
                   self.w_to = TooWide;
                   WestSide.e_to = TooWide;
                   "The crystal bridge shimmers and then fades away \
                   completely.";
               }
               move CrystalBridge to self;
               give CrystalBridge ~absent;
               self.w_to = CrystalBridge;
               WestSide.e_to = CrystalBridge;
               "There is a shimmering to the west and a crystal bridge \
               appears, spanning the fissure.";
           }
       ];

Object  CrystalBridge "crystal bridge"
has    static door open absent
with   name "crystal" "bridge",
       initial "A crystal bridge spans the fissure.",
       found_in WestSide EastSide,
       door_dir [;
           if (self in EastSide) return w_to;
           return e_to;
       ],
       door_to [;
           if (self in EastSide) return WestSide;
           return EastSide;
       ];

Object  Mist "mist"
has    scenery
with   name "mist",
       description "cold and clammy",
       found_in WestSide EastSide;

Object  Fissure "fissure"
has    scenery
with   name "fissure" "pit" "chasm" "gaping",
       description "Very deep.",
       found_in WestSide EastSide;

Object  WestSide "West Side of the Fissure"
has    light
with   description "Mist coils and writhes from a gaping fissure to the \
           east. There are no other exits.",
       e_to TooWide;

Object  PassageA "Twisty Passage"
has    light
with   description "Little twisty passages lead west and south, and there \
           is an exit to daylight to the north.",
       w_to [;
           if (moving_thief == 0)
               print "The passage turns to the left.^";
           return EastSide;
       ],
       n_to [;
           if (moving_thief == 1) rfalse;
           if (GoldCoin notin player)
               "You're not leaving without the treasure.";
           deadflag = 2;
           rtrue;
       ],
       s_to PassageB;

Nearby  Lever "lever"
has    static
with   name "lever",
       initial "There is a lever here.",
       before [;
        Push,Pull:
           if (self has general) "Nothing happens.";
           give self general;
           give SecretDoor ~absent;
           EastSide.s_to = SecretDoor;
           "You hear a grinding sound away to the southwest.";
       ];

Nearby  BlackRod "black rod"
with   name "black" "rod" "star",
       initial "There is a black rod with a star on the end here.";

Object  PassageB "Twisty Passage"
has    light
with   description "Little twisty passages lead west and north.",
       w_to EastSide,
       n_to PassageA;

Nearby  ShinyKey "shiny key"
with   name "shiny" "key";

Object  TreasureChamber "Treasure Chamber"
has    light
with   description "The secret treasure chamber of Zork, once the legend \
           that all adventurers sought after; now it seems a bit \
           depleted. There is an exit to the north.",
       n_to SecretDoor;

Nearby  GoldCoin "gold coin"
with   name "treasure" "gold" "coin",
       initial "A single gold coin is all that remains of the treasure \
           of the Flatheads.";

Object  SecretDoor "secret door"
has    static locked lockable openable door absent
with   name "secret" "door",
       found_in EastSide TreasureChamber,
       with_key ShinyKey,
       describe [ i;
           i = RunRoutines(self,door_dir);
           print_ret "^There is a secret door here, leading ",
               (DirectionName) i, "!";
       ],
       door_dir [;
           if (self in TreasureChamber) return n_to;
           return s_to;
       ],
       door_to [;
           if (self in TreasureChamber) return EastSide;
           return TreasureChamber;
       ];


!--------------------------------------------------------------------------
! THE THIEF HIMSELF
!
! As of library 5/11, it's possible to steal things from thief using "get
! object from thief"; this is because of a bug in `RemoveSub' in
! `verblib.h' that will be corrected in 5/12.
!--------------------------------------------------------------------------

Object  Thief "thief" PassageA
class  FollowClass CarryingClass
with   name "thief" "gentleman" "distinguished",
       initial "There is a distinguished gentleman here.",
       description "A distinguished gentleman.",
       life [;
        Attack: deadflag = 1; "He skewers you with his stiletto.";
        Ask,Answer,Order,Tell: "The thief only laughs.";
        ThrowAt: "He dodges your missile with ease.";
       ],
       daemon [ i j k;
           ! The thief steals the gold coin if possible
           if (GoldCoin in parent(self)) {
               move GoldCoin to self;
               if (self in location)
                   print "^~My, what an interesting gold coin,~ says the \
                       thief, picking it up.^";
           }
           if (GoldCoin in player && self in location) {
               move GoldCoin to self;
               print "^The thief steals the gold coin from you.^";
           }

           ! Count the available exits quickly
           objectloop(i in Compass)
               if (ZRegion(parent(self).(i.door_dir)) == 1 or 2)
                   k ++;
           if (k == 0) rtrue;

           ! Pick an exit at random and attempt to travel that way
           j = random(k); k = 0;
           objectloop(i in Compass) {
               if (ZRegion(parent(self).(i.door_dir)) == 1 or 2) {
                   k ++;
                   if (k == j) {
                       MoveThief(i);
                       rtrue;
                   }
               }
           }
       ];

Nearby  Stiletto "stiletto dagger"
with   name "stiletto" "dagger" "knife";

Nearby  Cloak "black cloak"
has    clothing worn
with   name "cloak" "black";

! Returns 1 if it moved the thief successfully
! Returns 0 if it couldn't move the thief in the requested direction

[ MoveThief
   dir   ! the direction to move the thief (n_obj, s_obj etc)
   dest  ! the room in which the thief ends up
   p     ! the room in which the thief starts out
   zr    ! the ZRegion of the destination
   o     ! the door the thief is trying to go through
   po;   ! the parent of the door

   ! Find out what's in the requested direction.  This code won't work if
   ! the thief can enter vehicles, but in this demo he can't, so this is
   ! OK.
   p = parent(Thief);
   dest = p.(dir.door_dir);
   zr = ZRegion(dest);

   ! It might be a string which would be printed to the player.
   ! Assume that this means that the thief can't go in that direction.
   if (zr == 3) rfalse;

   ! It might be a routine which is activated when the player tries to
   ! move in that direction.  We'll set moving_thief to 1 and hope that
   ! the routine does the right thing (it should return the
   ! destination if possible, or else 0 or 1 if there is no exit).
   if (zr == 2) {
       moving_thief = 1;
       dest = RunRoutines(p, (dir.door_dir));
       moving_thief = 0;
       if (dest == 0 or 1) rfalse;
   }

   ! If there's no exit in the given direction
   if (zr == 0 || dest == 0) rfalse;

   ! If the destination has the `door' attribute, then it's a door
   if (dest has door) {
       ! Find out where the door goes. Make sure the door is (temporarily)
       ! in the same room as the thief, otherwise the code will go wrong
       ! (see discussion at top of file).
       o = dest;
       po = parent(o);
       move o to p;
       dest = ValueOrRun(o,door_to);
       move o to po;
       if (dest == 0) rfalse; ! It might not lead anywhere.

       if (o has locked) {
           ! Need a check here to see if the thief has the keys or
           ! lockpicks required to open this door.  In the demo, he always
           ! succeeds.
           give o ~locked;
           if (p == location)
               print "^", (The) Thief, " picks the lock of ", (the) o, ".^";
       }
       if (o hasnt open && o has openable && o hasnt locked) {
           ! The thief can open closed doors. Here, he leaves then open
           ! behind him.
           give o open;
           if (p == location)
               print "^", (The) Thief, " opens ", (the) o, ".^";
           if (dest == location)
               print "^", (The) o, " opens.^";
       }
       if (o hasnt open) rfalse;
   }

   ! Now dest contains the destination room, so move the thief
   MovePrintNPC(Thief,dest,dir);
   rtrue;
];

End;