!-------------------------------------------------------------------------- ! 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."; ; ]; [ 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;