NPC movement: pick one exit at random, move NPC to new room each player turn

Hello there

This has been asked before, but I'm struggling to interpret previous replies so: what is the simplest way to (each player turn) randomly pick an exit from the NPC's current room and move it to the new room? I have 5 rooms and an NPC that I want to shuffle between them aimlessly, each turn, while the player does other stuff.

PickOneExit sounds useful but, um,I can't figure out how to use it.

Thanks, as ever...


You'd want a turnscript. Something like:

exit = PickOneExit(npc_name.parent)
destination = exit.to
MoveObject (npc_name, destination)

(where npc_name is the name of the npc object)


If you have more than one NPC you want to do this for, it would probably be more efficient to have one turnscript for all of them. So you could give the NPC a boolean attribute wandering to say they're moving around randomly. When you want to start an NPC wandering, you would do (for example) john.wandering = true.

Then the turnscript would look like this:

foreach (npc, AllObjects()) {
  if (GetBoolean (npc, "wandering")) {
    exit = PickOneExit(npc.parent)
    destination = exit.to
    MoveObject (npc, destination)
  }
}

That loops over all objects in the game, and if they have a "wandering" flag, it moves them randomly.


But there's still issues with that.

  1. If the NPC walks into the room where the player is, it doesn't tell them. So the player wouldn't realise the NPC is there unless they see it in the "Places & Objects" pane.

  2. The NPC can walk through locked doors. (easily fixed by using PickOneUnlockedExit instead)

  3. Exits without a destination will cause an error if the NPC picks them.

  4. There will be an error if the NPC ends up in a room with no exits

  5. PickOneExit uses ScopeExitsForRoom, which assumes the room's darklevel is correct (Quests darkness functions are somewhat broken). this means that an NPC walking into a dark room will treat it as a room with no exits, and it doesn't check if the NPC is carrying a torch.

I'm being called for lunch now; but if you let me know which of those issues you care about, I can show you how to change the script.


Hi

I figured out how to use PickOneExit to move something, just before seeing your reply:

    exit = PickOneExit (npc.parent)
    npc.parent = exit.to

So I will fiddle about with this in an attempt to do it myself with a turnscript, as a learning exercise, before reverting back to your solution if I can't make it work. Thanks for being incredibly helpful in helping me with these questions.


The simple solution is very short. A solution that works in all the peculiar cases that might crop up is pretty complex. And the most obvious way to write it turns out to be quite ineffecient.

I've written a script that deals with most of the things I mentioned above, but still could be expanded on. Just in case you're interested to see how huge and unwieldy a simple turnscript can get:

Way too much code If I wanted to make it work with all the issues above, it'd probably end up with something like this:
// Counting the exits in a room involves looping over all exits, which is quite slow, and checking for darkness is slower.
// So to reduce server load, I make a list of rooms with NPCs in, and then loop over exits checking "Is there an NPC here?"
npcs_to_move = NewObjectList()
rooms_with_npcs = NewDictionary()
foreach (obj, AllObjects()) {
  if (GetBoolean (obj, "wandering")) {
    list add (npcs_to_move, obj)
    if (not DictionaryContains (rooms_with_npcs, obj.parent.name)) {
      dictionary add (rooms_with_npcs, obj.parent.name, NewObjectList())
    }
  }
}

// Then, because checking for darkness is slow, we do it once per room
// Note that we can't use the default `CheckDarkness()` because that only works if the player is in the room
foreach (roomname, rooms_with_npcs) {
  room = GetObject (roomname)
  room.isdark = CheckDarknessForRoom (room)
}

// Find all the valid exits in those rooms:
//    This is basically what ScopeExitsForRoom() does; but we're checking for multiple rooms at once
//    and not checking for darkness yet
foreach (exit, AllExits()) {
  if (GetBoolean (exit, "visible")) {
    if (not GetBoolean (exit.parent, "isdark") and not GetBoolean (exit, "locked")) {
      if (HasObject (exit, "to") or HasScript (exit, "npcscript")) {
        if (DictionaryContains (rooms_with_npcs, exit.parent.name)) {
          list add (DictionaryItem (rooms_with_npcs, exit.parent.name), exit)
        }
      }
    }
  }
}

// Finally, look at the NPCs and check which way they can go:
foreach (npc, npcs_to_move) {
  exits = DictionaryItem (rooms_with_npcs, npc.parent.name)
  // Check if there are exits before picking one
  if (ListCount (exits) > 0) {
    exit = PickOneObject (exits)
    // For exits like a teleporter that sends the player to a random place,
    //    allow a script attribute "npcscript" that runs when an NPC uses them
    if (HasScript (exit, "npcscript")) {
      do (exit, "npcscript", QuickParams ("npc", npc))
    }
    else {
      npc.parent = exit.to
    }
  }
}

And a function:

<function name="CheckDarknessForRoom" parameters="room" type="boolean">
  if (GetBoolean (room, "dark")) {
    // Room is dark, so check if it contains a light source
    objects_to_check = GetDirectChildren (room)
    while (ListCount (objects_to_check) > 0) {
      obj = ListItem (objects_to_check, 0)
      list remove (objects_to_check, obj)
      if (GetBoolean (obj, "visible")) {
        if (GetBoolean (obj, "lightsource")) {
          if (obj.lightstrength = "strong") {
            return (false)
          }
        }
        if (GetBoolean (obj, "isopen") or GetBoolean (obj, "transparent") or DoesInherit (obj, "npc_type") or obj = game.pov) {
          objects_to_check = ListCombine (objects_to_check, GetDirectChildren (obj)
        }
      }
    }
    // We've run out of objects to check and not found a light source, so it's dark
    return (true)
  }
  else {
    return (false)
  }
</function>

And in the course of typing that out, I had to cut out a few bits to make sure it doesn't get stupidly large.
The version in my head also has support for:

  • NPCs who have darkvision
  • NPCs who check a room's darkness before entering, so they don't wander into a dark room unless they have a torch
  • NPCs who will turn on a flashlight in their inventory if the room is dark
    • With the option of doing so right away, or taking a turn to switch the light on
  • NPCs who, finding themselves in a dark room, will look for an objects with an is_lightswitch attribute
  • NPCs who are able to unlock doors
    • With the option of either going straight through, or taking a turn to unlock the door
    • With an optional % chance of forgetting to lock it again after they passed through
  • NPCs being less likely to leave a room the way they entered
  • NPCs who can wait instead of moving

Thank! Your illustration is instructive. I'll file the long bit of code away for future reference. At the moment, I'm working on a small adventure with the primary aim of finding out how the system works and what it can do - so I know what I'm doing when I come to write something larger that I have in mind. So starting simple and building up complexity only when required is what I'm trying at the moment (starting from a low base of absolutely no coding experience whatsoever...I've improved over the last 2 weeks!).


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums