Index / Last updated: Oct 2007

How to make NPCs in the ifMUD move

The plan was to make the NPCs wander around the CE Realm at random. This is how I did it.

First, I told the rooms where all the exits are. That is, all the exits that the NPCs may use. For example, in the Spaceport Lobby (#8856), there are six exits, but only five are valid for the NPCs to use. They are: north (#8911), west (#10753), east (#8894), south (#8994), and down (#9150). So the code to teach the Lobby about its exits is:

@field #8856 = nexits : 5
@field #8856 = exit_0 : 8911
@field #8856 = exit_1 : 8994
@field #8856 = exit_2 : 10753
@field #8856 = exit_3 : 8894
@field #8856 = exit_4 : 9150

It doesn't matter in what order the exits are listed, but I numbered them starting from 0, because I knew I'd be using @rand() to pick from them randomly. I did this for every room that the NPCs could visit.

Second, select one object (or room) as the master control object, and teach it who all the NPCs are. My master control object is the Elder (#10633), and since all my NPCs are aliens, the code looks like:

@field #10633 = alien_plant : 10881
@field #10633 = alien_laser : 10876
@field #10633 = alien_silencer : 10389
@field #10633 = alien_healer : 10403
@field #10633 = alien_calculator : 10649
@field #10633 = alien_dragon : 10647
...etc...

This part was easy, since I only plan to use @fieldloop() to cycle thru the aliens, and I didn't care what order that @fieldloop() would process them in.

Third, I taught the Elder how to move just one alien. This code assumes that I've previously set the the_alien and the the_room fields:

@field #10633 = the_alien : 10390      // Which alien am I moving?
@field #10633 = the_room : 9000        // Which room is it moving from?
@field #10633 = the_exit : 9121        // Which exit is it using?
@field #10633 = the_newroom : 9000     // Which room is it moving to?

// If there are less than 1 exits from the_room, do nothing.
// Else do_wander2 50-percent of the time.
@field #10633 = do_wander :
  @switch(1,@lt(@getfield(@g("the_room"),"nexits"),1),
  "", @rand(2),@call("10633","do_wander2"));

// First, calculate which of the_room's exits will be the_exit.
// Second, calculate the_newroom, the room that exit goes to.
// If the exit has a destination, use the exit's normal osuccess
//  and odrop messages to inform the two rooms of the alien's movement,
//  and of course, move the alien inbetween the osuccess and odrop.
// Else test the exit's lock2, and use the exit's osuccess2, odrop2,
//  and action2 fields as appropriate. [this latter bit was added later
//  to handle semi-locked exits like the washrooms]
@field #10633 = do_wander2 :
 @s("the_exit",@getfield(@g("the_room"),@print("exit_",
  @rand(@getfield(@g("the_room"),"nexits")))));
 @s("the_newroom",@getfield(@g("the_exit"),"action"));
 @switch(1,@not(@eq(@g("the_newroom"),-3)),@print(
  @tellroom(@g("the_room"),"",@print(
   @shortname(@g("the_alien"))," ", @getfield(@g("the_exit"),"osuccess"))),
  @move(@g("the_alien"),@g("the_newroom")),
  @tellroom(@g("the_newroom"),"",@print(
   @shortname(@g("the_alien"))," ", @getfield(@g("the_exit"),"odrop")))
 ),
 @call(@g("the_exit"),"lock2"),@print( 
  @s("the_newroom",@getfield(@g("the_exit"),"action2")),
  @tellroom(@g("the_room"),"",@print(
   @shortname(@g("the_alien"))," ", @getfield(@g("the_exit"),"osuccess2"))),
   @move(@g("the_alien"),@g("the_newroom")),
   @tellroom(@g("the_newroom"),"",@print(
    @shortname(@g("the_alien"))," ", @getfield(@g("the_exit"),"odrop2")))));

Fourth, I taught the Elder the each_turn routine, which moves all the aliens each turn. (What do I mean by "each turn"? Keep reading.)

@field #10633 = each_turn :
  @fieldloop(10633,"alien_",@print(
    @s("the_alien","%v"),
    @s("the_room",@location(@g("the_alien"))),
    @switch(@getfield(@g("the_alien"),"task"),
      "wander",@call(10633,"do_wander"))
  ));

Note, my true version of each_turn does more than this, but I wanted to simplify things here a bit. Also note, the way my code is written, only aliens whose task field is "wander" will wander. In the future, aliens will get new tasks, but in the meantime:

@field plant = task : wander
@field assassin = task : wander
@field mind = task : wander
@field philanthropist = task : wander
...etc...

Fifth, I changed all of my descriptions to call the Elder's each_turn function. That means, all my room descriptions, all my object descriptions, and all my exit descriptions. So whenever a player "looks" at anything that's mine, the NPCs will take a turn.

What this usually means is that I wrapped the description in a @tell(); followed by @call(10633,"each_turn"). It's not okay to use @print instead of @tell, because of the timing of what gets printed when.

Books are exempt from this fun, because of how signing works. But for everything else, I had to be ruthless...

@desc here = @tell("%#","The lobby is both the entrance/exit point to
  the Cosmic Encounter Realm, but also at the hub of a major spaceport
  in the vicinity of the dread Warp. The curious traveller can explore
  strange new worlds, meet exotic aliens, solve a puzzle or two, or just
  play \"tourist\". Come in and have fun!"); @call(10633,"each_turn");
@desc desk = @tell("%#","A large block of black onyx serves as the
  information desk for the lobby. It hardly seems practical. There is 
  a guestbook on the desk."); @call(10633,"each_turn");
@desc mind = @tell("%#","Oh, lovely. It's a big ambulatory brain.
  Since its one large eyeball is sporting an equally large false eyelash,
  you suppose that it must be a girl."); @call(10633,"each_turn");
@desc north = @tell("%#","To the north, you see the North Departure Lounge.
  From there, you can board shuttles to the gas giant, space dust, and
  rings systems."); @call(10633,"each_turn");
...etc...etc...etc...

That handled "looking". Next step, "taking"...

Sixth, I changed all of my objects' @success and @fail messages to also call each_turn. And @fail messages for exits, too, if applicable. But I didn't change the @success messages for normal exits, since the room description on the player's arrival would already call each_turn. And of course, I had to change the @success and @fail messages of actions on objects, too -- unless they move a player to another one of my rooms, in which case I can leave it alone.

Seventh and last, I changed any special fields like eat, search, push, pull, etc. to likewise call each_turn, in case players want to use any of those actions too.

And that's how I made NPCs move about on their own in my area in the ifMUD.