home *** CD-ROM | disk | FTP | other *** search
/ Fatal Distractions! / fataldistractions.bin / chap01 / ccr / ccr-npc.t < prev    next >
Text File  |  1993-07-10  |  26KB  |  1,096 lines

  1. /*
  2.  * Colossal Cave Revisited
  3.  *
  4.  * A remake of Willie Crowther and Don Woods' classic Adventure.
  5.  * Converted from Donald Ekman's PC port of the original FORTRAN source.
  6.  * TADS version by David M. Baggett for ADVENTIONS.
  7.  *
  8.  * Please document all changes in the history so we know who did what.
  9.  *
  10.  * This source code is copylefted under the terms of the GNU Public
  11.  * License.  Essentially, this means that you are free to do whatever
  12.  * you wish with this source code, provided you do not charge any
  13.  * money for it or for any derivative works.
  14.  *
  15.  * ADVENTIONS distributes this game, but you are free to do what you will
  16.  * with it, provided you adhere to the terms in the GNU Public License.
  17.  * Send correspondence regarding this game or original works distributed
  18.  * by ADVENTIONS to 
  19.  *
  20.  *    ADVENTIONS
  21.  *    PO Box 851
  22.  *    Columbia, MD 21044
  23.  *
  24.  * If you would like a catalog of releases, please enclose a SASE.  Thanks!
  25.  *
  26.  * Contributors
  27.  *
  28.  *    dmb    In real life:    David M. Baggett
  29.  *        Internet:    <dmb@ai.mit.edu>
  30.  *        Compu$erve:    76440,2671 (ADVENTIONS account)
  31.  *        GEnie:        ADVENTIONS
  32.  *
  33.  * Modification History
  34.  *
  35.  *  1-Jan-93    dmb    rec.arts.int-fiction BETA release (source only)
  36.  *                      For beta testing only -- not for general
  37.  *            distribution.
  38.  * 20-Apr-93    dmb    Incorporated Mike Roberts' changes to make
  39.  *            this a lot faster.  Uses the new intersect
  40.  *            built-in, so CCR must now be built with TADS
  41.  *            >= 2.0.13.
  42.  *
  43.  */
  44.  
  45. /*
  46.  * This file handles non-player character (dwarves and pirate) movement.
  47.  *
  48.  * Be sure to update exitlist and/or npclist if you add any new travel
  49.  * verbs to CCR_Room.
  50.  */
  51. initNPC: function
  52. {
  53.     local    o;
  54.  
  55.     //
  56.     // Construct list of NPC exits for each room
  57.     //
  58.     o := firstobj(CCR_room);
  59.     while (o <> nil) {
  60.         if (not o.noNPCs) {
  61.             //
  62.             // Add this room to the global list of rooms
  63.             // the NPC's can be in.
  64.             //
  65.             global.NPCrooms := global.NPCrooms + o;
  66.  
  67.             do_exitlist(o);
  68.             do_npclist(o);
  69.         }
  70.         else if (global.debug) {
  71.             //
  72.             // Debugging info:
  73.             // 
  74.             "\b\"<< o.sdesc >>\" is off limits to NPC's."; 
  75.         }
  76.  
  77.         o := nextobj(o, CCR_room);
  78.     }
  79. }
  80.  
  81. /*
  82.  * Add standard exits to the list of exits that NPC's should check
  83.  * when they're wandering about randomly.
  84.  */
  85. do_exitlist: function(o)
  86. {
  87.     local    exitlist, i, j, gotit;
  88.         local   tot1, tot2;
  89.  
  90.     //
  91.     // List of all exit property names that NPC's will consider.
  92.     // Note that magic words are left out because NPC's don't
  93.     // know them.
  94.     //
  95.     exitlist := [
  96.         &north &south &east &west
  97.         &ne &nw &se &sw
  98.         &up &down &in &out
  99.  
  100.         &jump &upstream &downstream &forwards &outdoors 
  101.         &left &right &cross &over &across &road &forest 
  102.         &valley &stairs &building &gully &stream &rock &bed 
  103.         &crawl &cobble &tosurface &dark &passage &low &canyon 
  104.         &awkward &giant &view &pit &crack &steps &dome &hall 
  105.         &barren &debris &hole &wall &broken &floor &toroom 
  106.         &slit &slab &depression &entrance &secret &cave 
  107.         &bedquilt &oriental &cavern &shell &reservoir &main 
  108.         &office &fork
  109.     ];
  110.         tot1 := length(exitlist);
  111.  
  112.     for (i := 1; i <= tot1; i++) {
  113.         //
  114.         // If this exit property is a simple
  115.         // object (prop 2), NPC's can use it, so
  116.         // add it to the room's NPC exit list.
  117.         //
  118.         // Make sure we don't add the same
  119.         // desination room twice.  Just because
  120.         // there are multiple travel verbs from
  121.         // one place to another doesn't mean the
  122.         // destination should be more likely.
  123.         //
  124.         if (proptype(o, exitlist[i]) = 2
  125.                     and not o.(exitlist[i]).noNPCs) {
  126.  
  127.             //
  128.             // Search current exitlist for
  129.             // this exit's destination.
  130.             //
  131.                         gotit := (find(o.NPCexits, o.(exitlist[i])) <> nil);
  132.             if (not gotit)
  133.                 o.NPCexits := o.NPCexits + exitlist[i];
  134.         }
  135.     }
  136. }
  137.  
  138. /*
  139.  * Add NPC special exits to the list of exits that NPC's should check
  140.  * when they're wandering about randomly.
  141.  */
  142. do_npclist: function(o)
  143. {
  144.     local    npclist, i, tot;
  145.  
  146.     //
  147.     // NPC exits.  These get considered if even if they're methods.
  148.     // The only way they won't be added to the list of exits to
  149.     // try is if they're = nil and not methods.
  150.     //
  151.     npclist := [
  152.         &NPCexit1 &NPCexit2 &NPCexit3 &NPCexit4
  153.         &NPCexit5 &NPCexit6 &NPCexit7 &NPCexit8
  154.     ];
  155.  
  156.         tot := length(npclist);
  157.     for (i := 1; i <= tot; i++) {
  158.         //
  159.         // If this NPC exit property is anything but
  160.         // nil (i.e., just nil, and not a method
  161.         // that returns nil). then NPC's can use it.
  162.         // Methods that return nil are fine because
  163.         // they might be conditional on some game
  164.         // events, like the crystal bridge having
  165.         // been created, etc.
  166.         //
  167.         if (proptype(o, npclist[i]) <> 5) // not = nil
  168.             o.NPCexits := o.NPCexits + npclist[i];
  169.     }
  170. }
  171.  
  172. /*
  173.  * Make sure NPC room connections are sound.
  174.  */
  175. check_connections: function
  176. {
  177.     local    o;
  178.  
  179.     o := firstobj(CCR_room);
  180.     while (o <> nil) {
  181.         if (not o.noNPCs)
  182.             do_debug(o);
  183.  
  184.         o := nextobj(o, CCR_room);
  185.     }
  186. }
  187.     
  188. do_debug: function(o)
  189. {
  190.     local    i, tot;
  191.  
  192.     if (length(o.NPCexits) = 0) {
  193.         P(); I();
  194.         "Oh dear, someone seems to have damaged one of my 
  195.         room connections.  The room \"";
  196.  
  197.         o.sdesc;
  198.  
  199.         "\" has no exits for NPC's to follow, but it's not 
  200.         listed as off limits to NPC's.  Please notify the 
  201.         cave management as soon as possible!";
  202.     }
  203.     else if (global.debug) {
  204.         //
  205.         // Debugging info:
  206.         //             
  207.         "\b\""; o.sdesc; "\" has "; say(length(o.NPCexits));
  208.         if (length(o.NPCexits) > 1)
  209.             " NPC exits:";
  210.         else
  211.             " NPC exit:";
  212.  
  213.             tot := length(o.NPCexits);
  214.         for (i := 1; i <= tot; i++) {
  215.             "\b\t-> ";
  216.             if (o.(o.NPCexits[i]))
  217.                 o.(o.NPCexits[i]).sdesc;
  218.             else
  219.                 "(nil)";
  220.         }
  221.     }
  222. }
  223.  
  224. /*
  225.  * A class defining some common things for NPC's.
  226.  */
  227. class NPC: object
  228.     //
  229.     // List of currect pirate locations.
  230.     //
  231.     loclist = []        // where they are
  232.     newloclist = []        // where they're going
  233.  
  234.     //
  235.     // Scatter any NPC's that are currently in
  236.     // the room to random rooms in the cave.  (We
  237.      // have to make sure the new rooms aren't off
  238.     // limits to dwarves, though.) 
  239.     //
  240.     scatter = {
  241.         local    i, dest, len, r, tot;
  242.                 local   melocs;
  243.  
  244.         for (melocs := [], i := Me.location ; i ; i := i.location)
  245.                 melocs += i;
  246.                 if (length(intersect(melocs, self.loclist))) return;
  247.  
  248.         self.newloclist := [];
  249.         tot := length(self.loclist);
  250.                 len := length(global.NPCrooms);
  251.         for (i := 1; i <= tot; i++) {
  252.             if (find(melocs, self.loclist[i])) {
  253.                 //
  254.                 // Make sure we get a real location.
  255.                 //
  256.                 dest := nil;
  257.                 while (dest = nil) {
  258.                     r := rand(len);
  259.                     dest := global.NPCrooms[r];
  260.                 }
  261.  
  262.                 self.newloclist += dest;
  263.             }
  264.             else {
  265.                 self.newloclist += self.loclist[i];
  266.             }
  267.  
  268.         }
  269.         self.loclist := self.newloclist;
  270.     }
  271.  
  272.     //
  273.     // Returns true if any NPC's of this type are in locations
  274.     // adjacent to the player.  (I.e., if any NPC's could take
  275.     // any exit that would bring them to the player's current
  276.     // location.)
  277.     //
  278.     anyadjacent = {
  279.         local    adjacent, i, j, len, dir, dest, melocs;
  280.             local   tot1;
  281.         local   cur;
  282.  
  283. //"\nanyadjacent(enter)\n";
  284.         for (melocs := [], i := Me.location ; i ; i := i.location)
  285.                 melocs += i;
  286.  
  287.         adjacent := nil;
  288.             tot1 := length(self.loclist);
  289.         for (i := 1; i <= tot1; i++) {
  290.             len := length(self.loclist[i].NPCexits);
  291.                     cur := self.loclist[i];
  292.             for (j := 1; j <= len; j++) {
  293.                             dest := cur.(cur.NPCexits[j]);
  294.  
  295.                 //
  296.                 // We need to check the destination
  297.                 // to be sure it exists.  It may be
  298.                 // nil if we called an NPCexit method.
  299.                 //
  300.                 if (dest) if (find(melocs, dest)) {
  301.                     adjacent := true;
  302.                     break;
  303.                 }
  304.             }
  305.  
  306.             //
  307.             // If we've found an adjacent pirate we
  308.             // can stop looking.
  309.             //
  310.             if (adjacent)
  311.                 break;
  312.         }
  313.  
  314. //"\nanyadjacent(exit)\n";
  315.         return adjacent;
  316.     }
  317. ;
  318.  
  319. /*
  320.  * Move the dwarves.  See the global object in ccr-std.t for paramters.
  321.  */
  322. Dwarves: NPC, Actor
  323.     rhetoricalturn = -999    // hack -- see yesVerb in ccr-verbs.t
  324.     attackers = 0        // number of dwarves that attack this turn
  325.     
  326.     sdesc = "threatening little dwarf"
  327.     ldesc = {
  328.         "It's probably not a good idea to get too close.  Suffice
  329.         it to say the little guy's pretty aggressive.";
  330.     }
  331.  
  332.     noun = 'dwarf' 'dwarves' 'guy'
  333.     adjective = 'threatening' 'nasty' 'little' 'mean'
  334.  
  335.     //
  336.     // We don't use actorDesc for the dwarves because it gets printed
  337.     // too early.  (We want to let the player know that a dwarf is
  338.     // in the room as soon as the dwarf moves into the room, not at
  339.     // the start of the next turn.)
  340.     //
  341.     actorDesc = {}
  342.  
  343.     locationOK = true    // tell compiler OK for location to be method
  344.     location = {
  345.         local    i, melocs;
  346.  
  347.         for (melocs := [], i := Me.location ; i ; i := i.location)
  348.                 melocs += i;
  349.         if (length(intersect(melocs, self.loclist)) = 0)
  350.             return(nil);
  351.  
  352.         //
  353.         // Check each dwarf's location.  If at least one dwarf
  354.         // is in the same location as the player, make our
  355.         // location the same as the player's.
  356.         //
  357.         for (i := 1; i <= length(self.loclist); i++)
  358.             if (find(melocs, self.loclist[i]))
  359.                 return self.loclist[i];
  360.  
  361.         return nil;
  362.     }
  363.  
  364.     verDoKick(actor) =  {}
  365.     doKick(actor) = {
  366.         "You boot the dwarf across the room.  He curses, then 
  367.         gets up and brushes himself off.  Now he's madder 
  368.         than ever!";
  369.     }
  370.     verDoAttack(actor) = {
  371.         if (not axe.isIn(Me)) {
  372.             "With what?  Your bare hands?";
  373.             self.rhetoricalturn := global.turnsofar;
  374.         }
  375.     }
  376.     doAttack(actor) = { self.doAttackWith(actor, axe); }
  377.  
  378.     //
  379.     // The following method is called when the player responds, "yes"
  380.     // to "With what?  Your bare hands?"
  381.     //
  382.     nicetry = { "You wish."; }
  383.     
  384.     verDoAttackWith(actor, io) = {}
  385.     doAttackWith(actor, io) = {
  386.         //
  387.         // If the player throws the axe at the dwarf, he
  388.         // might reduce the dwarf to a cloud of greasy
  389.         // black smoke.
  390.         //
  391.         if (io = axe) {
  392.             if (rand(100) <= global.dwarfhit) {
  393.                 "You killed a little dwarf.  The body 
  394.                 vanishes in a cloud of greasy black 
  395.                 smoke.";
  396.  
  397.                 //
  398.                 // Remove this location from our list
  399.                 // of locations where dwarves are.
  400.                 //
  401.                 self.loclist -= self.location;
  402.             }
  403.             else {
  404.                 "You attack a little dwarf, but he 
  405.                 dodges out of the way.";
  406.             }
  407.  
  408.             axe.moveInto(Me.location);
  409.         }
  410.         else if (io = Hands)
  411.             self.nicetry;
  412.         else
  413.             "Somehow I doubt that'll be very effective.";
  414.     }
  415.     
  416.     verIoGiveTo(actor) = {}
  417.     ioGiveTo(actor, dobj) = {
  418.         if (dobj = tasty_food) {
  419.             self.doFeed(Me);
  420.         }
  421.         else {
  422.             "The dwarf is not at all interested in your 
  423.             offer.  (The reason being, perhaps, that if 
  424.             he kills you he gets everything you have 
  425.             anyway.)";
  426.         }
  427.     }
  428.  
  429.     verIoThrowAt(actor) = { self.verIoGiveTo(actor); }
  430.     ioThrowAt(actor, dobj) = {
  431.         if (dobj = axe)
  432.             self.doAttackWith(actor, dobj);
  433.         else
  434.             self.ioGiveTo(actor, dobj);
  435.     }
  436.     verIoThrowTo(actor) = { self.verIoGiveTo(actor); }
  437.     ioThrowTo(actor, dobj) = {
  438.         if (dobj = axe)
  439.             self.doAttackWith(actor, dobj);
  440.         else
  441.             self.ioGiveTo(actor, dobj);
  442.     }
  443.  
  444.     verDoFeed(actor) = {}
  445.     doFeed(actor) = {
  446.         if (tasty_food.isIn(Me)) {
  447.             "You fool, dwarves eat only coal!  Now you've 
  448.             made him *really* mad!!";
  449.         }
  450.         else {
  451.             "You have no food to give the dwarf.";
  452.         }
  453.     }
  454.  
  455.     //
  456.     // Place dwarves in starting locations.
  457.     //
  458.     place = {
  459.         local    i, loc, r;
  460.  
  461.         self.loclist := [];
  462.         for (i := 1; i <= global.dwarves; i++) {
  463.             //
  464.             // If there are any fixed starting locations
  465.             // for dwarves left, put this dwarf in the
  466.             // next one.  Otherwise place him randomly.
  467.             //
  468.             loc := nil;
  469.             if (length(global.dwarfloc) >= i)
  470.                 loc := global.dwarfloc[i];
  471.  
  472.             //
  473.             // Invalidate initial location if it's off limits
  474.             // to NPC's.
  475.             //
  476.             if (loc)
  477.                 if (loc.noNPCs)
  478.                     loc := nil;
  479.  
  480.             //
  481.             // Make sure we get a real location.
  482.             //
  483.             while (loc = nil) {
  484.                 r := rand(length(global.NPCrooms));
  485.                 loc := global.NPCrooms[r];
  486.             }
  487.             
  488.             //
  489.             // Add this dwarf's location to the list.
  490.             //
  491.             self.loclist := self.loclist + loc;
  492.         }
  493.     }
  494.  
  495.     //
  496.     // Move dwarves.
  497.     //
  498.     move = {
  499.         local    i, j, len, r, dest, done, dir, loc;
  500.         local   melocs;
  501. //"\ndwarves.move(enter)\n";
  502.  
  503.         //
  504.         // Move each remaining dwarf.
  505.         //
  506.         // If the dwarf is currently in the player's location,
  507.         // he stays where he is.
  508.         //
  509.         // If a dwarf is in a location adjacent to the player's
  510.         // current location, he moves into the player's location
  511.         // if he can.  (We check his possible exits to see if
  512.         // any of them go the player's location.)  A consequence
  513.         // of this is that dwarves will follow the player
  514.         // relentlessly once they've spotted him.  (But the global
  515.         // value dwarftenacity can be set to prevent dwarves
  516.         // from *always following*, of course.)
  517.         //
  518.         // If a dwarf isn't adjacent to the player, he just moves
  519.         // around randomly.
  520.         //
  521.         
  522.         for (melocs := [], i := Me.location ; i ; i := i.location)
  523.                 melocs += i;
  524.  
  525.         self.newloclist := [];
  526.         self.attackers := 0;    // assume no dwarves attack this turn
  527.         for (i := length(self.loclist); i > 0; i--) {
  528.             //
  529.             // Get a copy of this dwarf's location for speed.
  530.             //
  531.             loc := self.loclist[i];
  532.  
  533.             //
  534.             // Haven't found a new location yet.
  535.             //
  536.             done := nil;
  537.  
  538.             //
  539.             // In player's current location?
  540.             //
  541.                     if (find(melocs, loc)) {
  542.                 dest := loc;    // stay put
  543.                 done := true;
  544.             }
  545.  
  546.             //
  547.             // Try each exit and see if we can reach the
  548.             // player.  If we have an exit that leads to
  549.             // the player, we know it's an OK destination
  550.             // location, since we pruned off all the noNPCs
  551.             // rooms when we constructed the exit lists. 
  552.             //
  553.             len := length(loc.NPCexits);
  554.             if (not done) for (j := len; j > 0; j--) {
  555.                 dir := loc.NPCexits[j];
  556.                 dest := loc.(dir);
  557.  
  558.                 //
  559.                 // We need to check the destination
  560.                 // to be sure it exists.  It may be
  561.                 // nil if we called an NPCexit method.
  562.                 //
  563.                     if (dest <> nil and find(melocs, dest) <> nil)
  564.                     {
  565.                     //
  566.                     // Is this dwarf tenacious enough
  567.                     // to follow the player?
  568.                     //
  569.                     if (rand(100) <= global.dtenacity)
  570.                         done := true;
  571.                     break;
  572.                 }
  573.             }
  574.  
  575.             //
  576.             // Have we found a destination yet?  If not,
  577.             // move dwarf to a randomly selected adjacent
  578.             // location.
  579.             //
  580.             // We need to check the destination because
  581.             // the NPCexit methods in the rooms can sometimes
  582.             // return nil.  (For example, when the crystal
  583.             // bridge doesn't exist yet, the giant's door
  584.             // has not been opened, etc.)
  585.             //
  586.             while (not done) {
  587.                 dir := loc.NPCexits[rand(len)];
  588.                 dest := loc.(dir);
  589.  
  590.                 if (dest)
  591.                     done := true;
  592.             }
  593.  
  594.             //
  595.             // Set new destination.
  596.             //
  597.             self.newloclist += dest; 
  598.  
  599.             //
  600.             // If the dwarf didn't move, he has an opportunity
  601.             // to attack.
  602.             //
  603.             if (loc = dest) {
  604.                     if (find(melocs, loc))
  605.                     if (rand(100) <= global.dwarfattack)
  606.                         self.attackers++;
  607.  
  608.                 //
  609.                 // Print some debugging info if in debug mode
  610.                 //
  611.                 if (global.debug) {
  612.                     P();
  613.                     "Dwarf stays \""; dest.sdesc; ".\"\n";
  614.                 }
  615.             }
  616.             else {
  617.                 //
  618.                 // Print some debugging info if in debug mode
  619.                 //
  620.                 if (global.debug) {
  621.                     P();
  622.                     "Dwarf moves from \"";
  623.                     self.loclist[i].sdesc; "\" to \"";
  624.                     dest.sdesc; ".\"\n";
  625.                 }
  626.             }
  627.         }
  628.  
  629.         //
  630.         // Replace old locations with destinations.
  631.         //
  632.         self.loclist := self.newloclist;
  633.  
  634.         self.tell;
  635. //"\ndwarves.move(exit)\n";
  636.     }
  637.  
  638.     //
  639.     // Tell the player what's going on with the dwarves.
  640.     //
  641.     tell = {    
  642.         local    i, j, len, r, dest, done, dir, count, adjacent;
  643.         local   melocs;
  644. //"\ntell(enter)\n";
  645.         //
  646.         // Count how many dwarves are in the room with the player.
  647.         //
  648.         for (melocs := [], i := Me.location ; i ; i := i.location)
  649.                 melocs += i;
  650.         count := length(intersect(melocs, self.loclist));
  651.  
  652.         //
  653.         // If any dwarves are in the room with the player and
  654.         // the axe hasn't been thrown yet, throw the axe and
  655.         // scatter the dwarves.
  656.         //
  657.         if (count > 0 and axe.location = nil) {
  658.             P(); I();
  659.  
  660.             "A little dwarf just walked around a corner, 
  661.             saw you, threw a little axe at you which 
  662.             missed, cursed, and ran away.";
  663.  
  664.             axe.moveInto(self.location);
  665.  
  666.             //
  667.             // Scatter any dwarves in the room.
  668.             //
  669.             self.scatter;
  670.  
  671.             //
  672.             // No dwarves in the room.  Be sure we take back
  673.             // those attacks too...
  674.             //
  675.             count := 0;
  676.             self.attackers := 0;
  677.         }
  678.  
  679.         //
  680.         // Tell the player if any dwarves are in the room with him,
  681.         // or if any are nearby.
  682.         //
  683.         if (count = 0) {
  684.             //
  685.             // If no dwarves are in the room, but at least
  686.             // one dwarf is in an adjacent location, tell
  687.             // the player he hears something.
  688.             //
  689.             // (Only the pirate makes noise in the original,
  690.             // which seem a bit strange and not as much fun.)
  691.             //
  692.             if (self.anyadjacent) {
  693.                 P(); I(); "You hear the pitter-patter 
  694.                 of little feet.";
  695.             }
  696.         }
  697.         else if (count = 1) {
  698.             P(); I();
  699.             "There is a threatening little dwarf in the 
  700.             room with you.";
  701.         }
  702.         else if (count > 1) {
  703.             P(); I();
  704.             "There are "; say(count); " threatening 
  705.             little dwarves in the room with you.";
  706.         }
  707.  
  708.         //
  709.         // Handle dwarf attacks.
  710.         //
  711.         if (self.attackers > 0) {
  712.             if (self.attackers = 1) {
  713.                 if (count = 1)
  714.                     " He throws a knife at you!";
  715.                  else
  716.                     " One of them throws a knife 
  717.                     at you!";
  718.             }
  719.             else {
  720.                 if (self.attackers = count) {
  721.                     if (count = 2)
  722.                         " Both of them throw 
  723.                         knives at you!";
  724.                     else 
  725.                         " All of them throw 
  726.                         knives at you!";
  727.                 }
  728.                 else {
  729.                     say(self.attackers); " of them throw 
  730.                     knives at you!";
  731.                 }
  732.             }
  733.  
  734.             //
  735.             // Curtains for our hero?!
  736.             //
  737.             count := 0;
  738.             for (i := 1; i <= self.attackers; i++) {
  739.                 if (rand(100) <= global.dwarfaccuracy)
  740.                     count++;
  741.             }
  742.  
  743.             P(); I();
  744.             if (count > 0) {
  745.                 if (count = self.attackers) {
  746.                     if (count = 1)
  747.                         "It gets you!";
  748.                     else if (count = 2)
  749.                         "Both of them get you!";
  750.                     else
  751.                         "All of them get you!";
  752.                 }
  753.                 else if (count = 1) {
  754.                     "One of them gets you!";
  755.                 }
  756.                 else {
  757.                     say(count); " of them get 
  758.                     you!";
  759.                 }
  760.  
  761.                 die();
  762.             }
  763.             else {
  764.                 if (self.attackers = 1) 
  765.                     "It misses you!";
  766.                 else if (self.attackers = 2)
  767.                     "Both of them miss you!";
  768.                 else
  769.                     "They all miss you!";
  770.             }
  771.         }
  772. //"\ntell(exit)\n";
  773.     }
  774. ;
  775. /*
  776.  * The player can never get the dwarves' knives (that would be too easy),
  777.  * but we'll let him examine them anyway.
  778.  */
  779. DwarfKnives: decoration
  780.     sdesc = "dwarf's knife"
  781.     ldesc = { self.verifyRemove(Me); }
  782.     noun = 'knife' 'knives'
  783.     adjective = 'sharp' 'nasty' 'dwarf\'s' 'dwarvish' 'dwarven'
  784.             'dwarfish'
  785.  
  786.     locationOK = true    // tell compiler OK for location to be method
  787.     location = {
  788.         return Dwarves.location;
  789.     }
  790.  
  791.     verifyRemove(actor) = {
  792.         "The dwarves' knives vanish as they strike the walls 
  793.         of the cave.";
  794.     }
  795.  
  796.     verIoAttackWith(actor) = {
  797.         "You don't have the dwarf's knife!";
  798.     }
  799. ;
  800.  
  801. /*
  802.  * Move the pirate(s).  See the global object in ccr-std.t for paramters.
  803.  *
  804.  * This code is quite similar to the Dwarves code, but is simple because
  805.  * there's never any interaction between the player and the pirates. (The
  806.  * pirates just come in, do their stuff, and vanish.)
  807.  *
  808.  * Note that even if there's more than one pirate, the text printed in
  809.  * in this object will treat all the pirates as a single one. So the
  810.  * only difference having multiple pirates makes is that the more you
  811.  * have, the more likely the player is to run into "him."
  812.  */
  813. Pirates: NPC, Actor
  814.     location = nil    // not a real actor, so pretend we don't exist
  815.  
  816.     seen = nil    // has the player seen (one of) us?
  817.  
  818.     //
  819.     // Place pirates in starting locations.
  820.     //
  821.     place = {
  822.         local    i, loc, r;
  823.  
  824.         self.loclist := [];
  825.         for (i := 1; i <= global.pirates; i++) {
  826.             //
  827.             // If there are any fixed starting locations
  828.             // for pirates left, put this pirate in the
  829.             // next one.  Otherwise place him randomly.
  830.             //
  831.             loc := nil;
  832.             if (length(global.pirateloc) >= i)
  833.                 loc := global.pirateloc[i];
  834.  
  835.             //
  836.             // Invalidate initial location if it's off limits
  837.             // to NPC's.
  838.             //
  839.             if (loc)
  840.                 if (loc.noNPCs)
  841.                     loc := nil;
  842.  
  843.             //
  844.             // Make sure we get a real location.
  845.             //
  846.             while (loc = nil) {
  847.                 r := rand(length(global.NPCrooms));
  848.                 loc := global.NPCrooms[r];
  849.             }
  850.             
  851.             //
  852.             // Add this pirate's location to the list.
  853.             //
  854.             self.loclist := self.loclist + loc;
  855.         }
  856.     }
  857.  
  858.     //
  859.     // Move pirates.
  860.     //
  861.     move = {
  862.         local    i, j, len, r, dest, done, dir, melocs;
  863.  
  864. //"\npirates.move(enter)\n";
  865.         //
  866.         // Move each remaining pirate.
  867.         //
  868.         // If the pirate is currently in the player's location,
  869.         // he stays where he is.
  870.         //
  871.         // If a pirate is in a location adjacent to the player's
  872.         // current location, he moves into the player's location
  873.         // if he can.  We limit this with the ptenacity global.
  874.         //
  875.         // If a pirate isn't adjacent to the player, he just moves
  876.         // around randomly.
  877.         //
  878.         self.newloclist := [];
  879.         for (melocs := [], i := Me.location ; i ; i := i.location)
  880.                 melocs += i;
  881.  
  882.         for (i := 1; i <= length(self.loclist); i++) {
  883.             //
  884.             // Haven't found a new location yet.
  885.             //
  886.             done := nil;
  887.  
  888.             //
  889.             // In player's current location?
  890.             //
  891.             if (find(melocs, self.loclist[i])) {
  892.                 dest := self.loclist[i];    // stay put
  893.                 done := true;
  894.             }
  895.  
  896.             //
  897.             // Try each exit and see if we can reach the
  898.             // player.  If we have an exit that leads to
  899.             // the player, we know it's an OK destination
  900.             // location, since we pruned off all the noNPCs
  901.             // rooms when we constructed the exit lists. 
  902.             //
  903.             len := length(self.loclist[i].NPCexits);
  904.             if (not done) for (j := 1; j <= len; j++) {
  905.                 dir := self.loclist[i].NPCexits[j];
  906.                 dest := self.loclist[i].(dir);
  907.  
  908.                 //
  909.                 // We need to check the destination
  910.                 // to be sure it exists.  It may be
  911.                 // nil if we called an NPCexit method.
  912.                 //
  913.                 if (dest) if (find(melocs, dest)) {
  914.                     //
  915.                     // Is this pirate tenacious enough
  916.                     // to follow the player?
  917.                     //
  918.                     if (rand(100) <= global.ptenacity)
  919.                         done := true;
  920.                     break;
  921.                 }
  922.             }
  923.  
  924.             //
  925.             // Have we found a destination yet?  If not,
  926.             // move pirate to a randomly selected adjacent
  927.             // location.
  928.             //
  929.             // We need to check the destination because
  930.             // the NPCexit methods in the rooms can sometimes
  931.             // return nil.  (For example, when the crystal
  932.             // bridge doesn't exist yet, the giant's door
  933.             // has not been opened, etc.)
  934.             //
  935.             while (not done) {
  936.                 dir := self.loclist[i].NPCexits[rand(len)];
  937.                 dest := self.loclist[i].(dir);
  938.  
  939.                 if (dest)
  940.                     done := true;
  941.             }
  942.  
  943.             //
  944.             // Set new destination.
  945.             //
  946.             self.newloclist += dest; 
  947.  
  948.             //
  949.             // Print some debugging info if in debug mode
  950.             //
  951.             if (self.loclist[i] = dest) {
  952.                 if (global.debug) {
  953.                     P();
  954.                     "Pirate stays \""; dest.sdesc; ".\"\n";
  955.                 }
  956.             }
  957.             else {
  958.                 if (global.debug) {
  959.                     P();
  960.                     "Pirate moves from \"";
  961.                     self.loclist[i].sdesc; "\" to \"";
  962.                     dest.sdesc; ".\"\n";
  963.                 }
  964.             }
  965.         }
  966.  
  967.         //
  968.         // Replace old locations with destinations.
  969.         //
  970.         self.loclist := self.newloclist;
  971.  
  972.         self.tell;
  973. //"\npirates.move(exit)\n";
  974.     }
  975.  
  976.     //
  977.     // Tell the player what's going on with the pirates.
  978.     //
  979.     tell = {    
  980.         local    i, t, count, snagged, melocs;
  981. //"\npirates.tell(enter)\n";
  982.  
  983.         //
  984.         // Count how many pirates are in the room with the player.
  985.         // (We really only need to know if there are any at all,
  986.         // but this is just as easy.)
  987.         //
  988.         for (melocs := [], i := Me.location ; i ; i := i.location)
  989.                 melocs += i;
  990.         count := length(intersect(melocs, self.loclist));
  991.                         
  992.         //
  993.         // Tell the player if any pirates are nearby.
  994.         //
  995.         if (count = 0) {
  996.             //
  997.             // If no pirates are in the room, but at least
  998.             // one pirate is in an adjacent location, tell
  999.             // the player he hears something.
  1000.             //
  1001.             if (self.anyadjacent) {
  1002.                 P(); I();
  1003.                 "There are faint rustling noises from 
  1004.                 the darkness behind you.";
  1005.             }
  1006.         }
  1007.         else if (count > 0) {
  1008.             //
  1009.             // A pirate has snagged the player.
  1010.             // Move any treasures the player is carring
  1011.             // to the pirate's repository, currently
  1012.             // hard-coded as Dead_End_13 because there's
  1013.             // code in that room that can't easily be
  1014.             // made general.
  1015.             //
  1016.             // Since the player may be keeping his treasures
  1017.             // in containers, it's actually easier just
  1018.             // to search through the global list of
  1019.             // treasures and check each one's location to
  1020.             // see if it's in the player, rather than
  1021.             // recursively checking all the player's belongings
  1022.             // and seeing if they're treasures.  (Also, we want
  1023.             // to get treasures that are just lying around in
  1024.             // the room too.)
  1025.             //
  1026.             snagged := 0;
  1027.             for (i := 1; i <= length(global.treasurelist); i++) {
  1028.                 t := global.treasurelist[i] ;
  1029.                 if (t.isIn(Me.location)) {
  1030.                     t.moveInto(Dead_End_13);
  1031.                     snagged++;
  1032.                 }
  1033.             }
  1034.  
  1035.             //
  1036.             // Print a message telling the player what happened.
  1037.             //
  1038.             if (snagged > 0) {
  1039.                 P();
  1040.                 I(); "Out from the shadows behind you 
  1041.                 pounces a bearded pirate!  \"Har, 
  1042.                 har,\" he chortles.  \"I'll just take 
  1043.                 all this booty and hide it away with 
  1044.                 me chest deep in the maze!\"  He 
  1045.                 snatches your treasure and vanishes 
  1046.                 into the gloom.";
  1047.             }
  1048.             else {
  1049.                 //
  1050.                 // In the original code, if you weren't
  1051.                 // holding the lamp, you just wouldn't
  1052.                 // see the pirate when you weren't
  1053.                 // carrying any treasures.  This seems
  1054.                 // bogus, so I've added a conditional here.
  1055.                 //
  1056.                 P();
  1057.                 I(); "There are faint rustling noises 
  1058.                 from the darkness behind you.  As you 
  1059.                 turn toward them, ";
  1060.  
  1061.                 if (brass_lantern.isIn(Me))
  1062.                     "the beam of your lamp falls 
  1063.                     across";
  1064.                 else
  1065.                     "you spot";
  1066.  
  1067.                 " a bearded pirate. He is carrying a 
  1068.                 large chest.  \"Shiver me timbers!\" 
  1069.                 He cries, \"I've been spotted!  I'd 
  1070.                 best hie meself off to the maze to 
  1071.                 hide me chest!\" With that, he 
  1072.                 vanishes into the gloom.";
  1073.             }
  1074.  
  1075.             //
  1076.             // Install the treasure chest if it hasn't
  1077.             // already been installed.  No worries about
  1078.             // the chest appearing out of nowhere when
  1079.             // the player's at Dead_End_13, because the
  1080.             // pirate can't go there.  (It's off limits
  1081.             // to NPC's.)
  1082.             //
  1083.             if (not self.seen) {
  1084.                 treasure_chest.moveInto(Dead_End_13);
  1085.                 self.seen := true;
  1086.             }
  1087.  
  1088.             //
  1089.             // Scatter any pirates in the room.
  1090.             //
  1091.             self.scatter;
  1092.         }
  1093. //"\npirates.tell(enter)\n";
  1094.     }
  1095. ;
  1096.