Controlling NPCs

So a recent thread prompted me to write a new tutorial on controlling non-player characters (NPCs):
http://docs.textadventures.co.uk/quest/patrolling_npcs.html
http://docs.textadventures.co.uk/quest/independent_npcs.html

I now have a library that does all the hard work.
https://raw.githubusercontent.com/ThePix/quest/master/NpcLib.aslx

To use, go to the new NPC tab, set the NPC to be an NPC. Then add items to the list.

NPC tab

It is not fully tested yet, so let me know of any issues!


Whoo-hoo!

Tabs and everything!

Thanks, Pix!

I'll give it a run this evening.


This can be alot of fun i think, even managed to do a very minor bit of editing to a library for the first time, just removed the announcing of "Doing" and the exit number(I think) when sending an NPC searching.


Talon, those bits you removed were debugging messages I had left in by accident!

I have just uploaded version 1.2.

This uses functions from PathLib by Jay Nabonne to give the GoTo command. This will make the NPC go to the specified room, one room per turn.
http://docs.textadventures.co.uk/quest/libraries/path_library.html

If you use ConvLib, an NPC will be automatically paused, but this has to be after ConvLib!

You can add an "npc_using" script to an exit to have that run instead of the standard phrase for moving. It has access to a local variable "npc". It will need to check for itself if the player is present to see it happened (or use PrintIfHere).

For standard moves, you can now override the templates, or the NpcLeaving function (which tells the player which way the NPC was heading).

If game.npcdebug is set to true, you will see debugging info in blue; this will tell you what NPCs are dong and planning to do, which should help you work out why they are not behaving.

There are also groups now; NPCs can join groups and act together whilst in the group...

Groups

An NPC can belong to one or zero groups. Membership is indicated by the "group" attribute. Use the GroupMembers(group) function to get a list of members. The group alias is the members of the group (use its "resetalias" script to set; the built-in functions do this for you).

A group has actions just as an NPC does (in fact they inherit from NPC). When an NPC is in a group, her actions are not done, but will resume if she leaves the group.

An NPC can "Join" a group, or a group can "Include" an NPC. Either way, the group object will be moved to the location of the NPC, but any members will not, so you should ensure the NPC is in the same room when joining a group with other members.

When the group moves via an action, all members do too.

Use the Pause function to pause an NPC for one turn; this will ensure the whole group pauses if appropriate.

WaitFor will make a group wait until the specified NPC joins the group. Count will wait until the number of members in the group is equal to the "count" attribute of the given object (which may well be the group itself).

Actions...
For NPCs only: Get, Drop, Wear, Remove, Join
For groups only: Include, Exclude, Disband, WaitFor, Count
For either, but with groups a random member is picked: Open, Close, Lock, Unlock, Give
For either: Move, Search, Pause, Wait, GoTo, GoToParent, Script

So what can you do with this?

By way of an example, let us suppose Mary and Bob are breaking into a house together, taking two things, then leaving together. Suppose we start with Mary waiting for Bob on the street corner. Start her in a group by setting her "group" attribute. The group could then "WaitFor" Bob, to "Join" the group.

The group could then go to the hall location inside the house, at which point it could "Disband" (disband does not need an object, so just send it player as a kind of default). Each NPC will then do their own thing, Mary going to the kitchen to get a knife, Bob to the attic to get a painting, then come back and join the group. The group, with "count" set to 2, waits for its membership Count to get to 2, at which point the group heads back to the street corner, to await the player.

Here is how it is set up:

Mary (starts in the street corner location, with her "group" attribute set to the Mary and Bob group object).

Move:kitchen
Get:knife
Move:hall
Join:Mary and Bob group

Bob

GoTo:street corner
Join:Mary and Bob group
GoTo:attic
Get:painting
GoTo:hall
Join:Mary and Bob group

Mary and Bob group (has its "count" attribute set to 2)

GoTo:hall
Disband:player
Count:Mary and Bob group
GoTo:street corner
Wait:player


wow... this is awesome feature implementation! Pixie harnessing the power of quest!, hehe :D


Trying to get my mind around something to try with this coding, the general idea is to have the both the player(hero) and a killer hunt for objects(In this case victims), plan on half a dozen or so victims, whenever the killer moves meets a victim there killed and added to his tally. Player gets to someone he can have them follow them to be safe(Haven't decided how I want the final confrontation to work out, but will have a score depending on how many people were saved)

The logic I have so far is this , using the explore code would have the killer search around easy enough.
Player just moving and finding the victims to save likewise is easy enough.. the problem I have here is having the victim group react accordingly. Was thinking something of using the group and when the killer was included in the group to trigger the kill..

Would it be easier if I had the groups react with a script if they were in the same room as the killer?

--edit
Error running script: Error compiling expression 'exit.to': Variable does not refer to an object: 'exit
I still get though

also can't seem to get a script to trigger. have the npc stop moving when they run into the killer(simple test having them both in rooms with only one exit to the same room) but when using the script command it just seems to function like a wait, the killer keeps going off but leaves the npc body rather than having them be TPed to a storeroom(



I'm having all sorts of fun with this!

Is there an easy way to filter the NPCs to make my take command switch to NpcGive(object) if the object is in the NPCs inventory?

(The NPC is a surface, so you can see what he's 'carrying', which is the best way I've heard to make the happen.)

Something like:

foreach (obj, object) {
  if (HasAttribute(obj.parent,"npc")) {
    NpcGive (obj.parent, obj)
  }
  else {
    DoTake (obj, multiple)
  }
}

Found it as soon as I clicked POST. (Sorry about that.)

//EDITED 
//take command script
foreach (obj, object) {
  if (DoesInherit(obj.parent,"NpcType")) {
    if (obj.take) {
      NpcGive (obj.parent, obj)
    }
    else {
      msg (obj.takemsg)
    }
  }
  else {
    DoTake (obj, multiple)
  }
}

You can see Ralph and a cap.

You can go southwest.

"Took ya' long enough," chuckles Ralph. "I don't mean to ruffle your feathers, but it'll be dark soon. We really need to get a move on."

Ralph picks up the cap.

> take cap
Ralph gives you the cap.


Whoops.

That seems to throw an error on something that contains a GroupType (the flock of wild geese):


> x the sky
The sky is a lovely, super-intelligent shade of blue.

You can see the sun and a flock of wild geese.

> take the sky
Error running script: Error evaluating expression 'DoesInherit(obj.parent,"NpcType")': DoesInherit function expected object parameter but was passed 'null'

> take the sun
You can't take it.

> take the geese
You can't take them.


Added CapFirst() and a hyperlink to this one:

<function name="NpcLeaving" parameters="npc, exit" type="boolean">
    msg(CapFirst(GetDisplayName(npc)) + " leaves, heading {exit:" + exit.name + "}.")
  </function>

Added CapFirst to this one, too:

<dynamictemplate name="NpcEnters">CapFirst(GetDisplayName(object)) + " enters the room."</dynamictemplate>

I'm trying figure out how you're handling the groups.

Is there an easy way to make a group follow the player (or another NPC)? Or should I just whip up a turn script, using NpcGotoParent() and the group as the npc parameter?


How do we add an NPC to a group, again?

  • I made an object: posse.

  • I made three NPCs.

  • I set posse to Group.

  • I set each NPC to NPC.

One of my NPCs (Chainsaw) has this list of actions:

Wait:Ralph
Join:posse

I also have Chainsaw's group attribute set to the posse object.


So, whenever the player enters the room containing Chainsaw, I'm thinking they will form a posse.

...but I get this error when Ralph's and Chainsaw's paths cross:

Error running script: Error compiling expression '"Outstanding actions: " + Join(this.actions, ",")': FunctionCallElement: Could find not function 'Join(Object, String)'

I don't think I'm doing something correctly. (In fact, I'm quite certain of it. Ha-ha!)


My goal:

When the player and Ralph enter Chainsaw's location, Chainsaw and Ralph will then be referred to as "your posse", along with anyone who joins up.

The posse follows you, or you can send the posse wherever you please.

You can instruct a single NPC to go do something, and said NPC will break apart from the posse.

If the posse gets down to one member, the group is disbanded.


I'll post my code separately. The site won't let me edit it into this post.


My code (which pretty much works now and is a very rough draft to test out groups):

<!--Saved by Quest 5.7.6404.15496-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <include ref="NpcLib.aslx" />
  <game name="Groups">
    <gameid>707b9c7f-4bba-4ba4-89a6-df6d0ed0a1ad</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <enter type="script">
    </enter>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="Ralph">
      <inherit name="editor_object" />
      <inherit name="NpcType" />
      <inherit name="namedmale" />
      <actions type="stringlist">
        <value>Script:Ralph</value>
      </actions>
      <npcscript type="script">
        NpcGoToParent (Ralph, player)
        if (posse.parent = Ralph.parent) {
          NpcInclude (posse, Ralph)
        }
        this.deletefromlist = false
      </npcscript>
    </object>
    <exit alias="north" to="room1">
      <inherit name="northdirection" />
    </exit>
  </object>
  <object name="room1">
    <inherit name="editor_room" />
    <object name="Chainsaw">
      <inherit name="editor_object" />
      <inherit name="NpcType" />
      <inherit name="namedmale" />
      <actions type="stringlist">
        <value>Wait:Ralph</value>
      </actions>
      <npcscript type="script">
      </npcscript>
    </object>
    <exit alias="south" to="room">
      <inherit name="southdirection" />
    </exit>
    <exit alias="north" to="room2">
      <inherit name="northdirection" />
    </exit>
    <object name="posse">
      <inherit name="editor_object" />
      <inherit name="GroupType" />
      <inherit name="plural" />
      <actions type="stringlist">
        <value>Wait:Ralph</value>
        <value>Include:Ralph</value>
        <value>Include:Chainsaw</value>
        <value>Script:posse</value>
      </actions>
      <visible type="boolean">false</visible>
      <npcscript type="script">
        NpcGoToParent (posse, player)
        this.deletefromlist = false
      </npcscript>
      <usedefaultprefix type="boolean">false</usedefaultprefix>
    </object>
  </object>
  <object name="room2">
    <inherit name="editor_room" />
    <object name="Lester">
      <inherit name="editor_object" />
      <inherit name="namedmale" />
      <inherit name="NpcType" />
      <actions type="stringlist">
        <value>Wait:posse</value>
        <value>Script:Lester</value>
      </actions>
      <npcscript type="script">
        NpcInclude (posse, Lester)
      </npcscript>
    </object>
    <exit alias="south" to="room1">
      <inherit name="southdirection" />
    </exit>
    <exit alias="north" to="room3">
      <inherit name="northdirection" />
    </exit>
  </object>
  <object name="room3">
    <inherit name="editor_room" />
    <exit alias="south" to="room2">
      <inherit name="southdirection" />
    </exit>
  </object>
</asl>

The error is telling you actions is null for something. Not clear what - but not had a chance to look at the code.

Chainsaw will not do his actions as he is already in the group. I would have the group just wait until it has three members.


Ah, I see.

Thanks!


The code in the above post has been edited.

My posse now forms itself, and it follows me.

I only need to iron out a couple of pauses.


You are in a room.
You can see Ralph.
You can go north.

> n

You are in a room1.
You can see Chainsaw.
You can go south or north.
Ralph enters the room.

> z
Time passes.

> z
Time passes.

> n

You are in a room2.
You can see Lester.
You can go south or north.
Ralph and Chainsaw enters the room.

> z
Time passes.

> n

You are in a room3.
You can go south.
Ralph, Chainsaw and Lester enters the room.


Ralph, Chainsaw and Lester enters the room.

I have made a small update to the library, so if you set he posse to be a plural object, if should give:

Ralph, Chainsaw and Lester enter the room.


I'm having trouble downloading the library in a usable file. I right click / save as and it just becomes a text file even though it retains the right tag. How do you download it? :)

FIXED -- I didn't spot the RAW thing.


Thanks for your work. Thumbs up! I'll use that one hundred percent in my adventure. :)


Is there a way to bypass the pause?

For instance, when an NPC is waiting for the player to enter their location, I'd like the next action to fire on that turn.

Right now I'm using the actual Npc functions in the onenter scripts, so I've pretty much got it going on. I was just curious as to how I could bypass that pause. I wouldn't have thought about it, but I read in the notes that the pause was added in the second version of the library. So, I deduced that there was originally no pause. (I know... I'm so clever! Cough... Choke...) Hence, there must be a script which calls the actions with no pause.


This library is super-awesome, by the way!


I have updated the library (with input from KV), and added a tutorial page too:
https://github.com/ThePix/quest/wiki/NpcLib

Right now I'm using the actual Npc functions in the onenter scripts, so I've pretty much got it going on. I was just curious as to how I could bypass that pause. I wouldn't have thought about it, but I read in the notes that the pause was added in the second version of the library. So, I deduced that there was originally no pause. (I know... I'm so clever! Cough... Choke...) Hence, there must be a script which calls the actions with no pause.

There is no pause as such. The NPC takes a turn after the player, and in that turn will process the Wait command, and decide it has now expired. Thus the NPC will always wait a turn.

A way around that might be to have NPCs take an extra turn when the player enters a room - just do do(NpcTurnScript, "script") in the "Script when entering a room". That will obviously speed up everything they do, so may not be ideal.


The update:

Cool!


The way around the pause:

That will probably fix me up. I just want the NPC to act as soon as I enter when they are waiting for me. Otherwise, I can enter the room, then immediately exit and miss the thing they've been waiting for me to do.


PS

I think it would be cool if this were included in 580.

It's peachy keen!

So, everyone, please, help me try to break it.

...I mean help me test it.


Hi, Only just seen this library tonight. Brilliant.
I'm having some trouble however with the tutorial associated with it and how I can get the group to work.
Is the 'Mary and Bob Group' a separate object, if so do I need to put it where they meet? Is it a visible or invisible object?
Thanks in advance


It is a separate object, and is best put where they meet, yes. It should already be invisible.


Am I correct in thinking that, both 'Mary' and 'Bob's 'NPC' tab, the type should be set to 'NPC' and for the 'Mary and Bob Group', it should be set to group?
Is it only 'Mary', I add the 'group' attribute too. Do I need to add a 'group' attribute to 'Bob' too and also set it to the 'Mary and Bob Group'?


the type should be set to 'NPC' and for the 'Mary and Bob Group', it should be set to group

Yes.

only 'Mary', I add the 'group' attribute too

Yes. She starts in the group. Bob's "group" attribute gets set when he joins the group, so is not set at the start.

Setting an NPC's "group" attribute will stop her doing anything, because she acts as part of the group. That is fine for Mary, she is just waiting. Bob has to act on his own at first, and setting his "group" attribute would stop him doing so.


Is it possible to set up a command
say #text# to #object1#
eg. say follow me to Mary
and then script it as follows:

if (object1=Mary and text="follow me") {
  NpcAct (Mary, "Follow:player")
}

Would 'NpcAct' be the correct function to call?


if((object1 = Mary) and text = "follow me") {
  NpcFollow(Mary, player)
}

That should work. (Can't test it at the moment.)


No, that's not working KV.

I was hoping if I got this working, to then make a much broader command for any 'npc' and then any command to give them.
eg. 'say get apple to tom', or 'say kill bill to ben'.
but would I need to type 'say get:apple to tom'.

Do I need to do something with either the 'actions' attribute or the 'NpcTurnScript'.


Pixie's follow works differently than mine.

Mine makes the NPC follow the object until you call NpcUnfollow.

His looks like you can tell the NPC to follow another NPC for one turn.

To test that, put Bob in another room, then SAY FOLLOW BOB TO MARY.


<!--Saved by Quest 5.7.6606.39184-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Mary Mary (Why You Buggin'?)">
    <gameid>f50bcb19-581f-4860-a619-fa7cb6504044</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="Mary">
      <inherit name="editor_object" />
      <inherit name="namedfemale" />
      <inherit name="NpcType" />
    </object>
    <exit alias="north" to="another room">
      <inherit name="northdirection" />
    </exit>
  </object>
  <command name="tell_it_to_follow_it">
    <pattern>tell #object1# to follow #text#</pattern>
    <script>
      if (LCase(text) = "bob") {
        object2 = Bob
        if (object1 = Mary) {
          NpcFollow (Mary, Bob)
        }
      }
    </script>
  </command>
  <object name="another room">
    <inherit name="editor_room" />
    <exit alias="south" to="room">
      <inherit name="southdirection" />
    </exit>
    <object name="Bob">
      <inherit name="editor_object" />
      <inherit name="NpcType" />
      <inherit name="namedmale" />
    </object>
  </object>
</asl>

Yep. That works. Mary goes to the room Bob is in when I enter TELL MARY TO FOLLOW BOB, but only once.


PS

There's an "Order to" thingy on the ASK/TELL tab under an object when ASK is enabled in the game.

image


This way, you can handle it like ASK ABOUT, entering a script for each different thing.

This way is easier:

<!--Saved by Quest 5.7.6404.15496-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <include ref="NpcLib.aslx" />
  <game name="mary2">
    <gameid>9be68bb5-b42a-4afb-afac-5a98618cc8cd</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <feature_asktell />
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="Mary">
      <inherit name="editor_object" />
      <inherit name="NpcType" />
      <inherit name="namedfemale" />
      <tellto type="scriptdictionary" />
      <telltodefault type="script">
        if (LCase(text) = "follow bob") {
          NpcGoToParent (Mary, Bob)
        }
        else {
          msg (CapFirst(this.gender)+" ignores you.")
        }
      </telltodefault>
    </object>
    <exit alias="north" to="another_room">
      <inherit name="northdirection" />
    </exit>
  </object>
  <object name="another_room">
    <inherit name="editor_room" />
    <enter type="script">
    </enter>
    <firstenter type="script">
      NpcGoTo (Bob, room)
    </firstenter>
    <object name="Bob">
      <inherit name="editor_object" />
      <inherit name="namedmale" />
      <inherit name="NpcType" />
    </object>
    <exit alias="south" to="room">
      <inherit name="southdirection" />
    </exit>
  </object>
</asl>

You could use a switch script instead of an if statement if you planned on having a wide range of things to tell NPCs to do.

(Also, if you want the NPC to tail someone every time they move, I have code for that.)


image

image


I made it work with the default functions.

If you tell Mary to follow Bob in this example, she will never stop following him. (Just because I'm lazy. It can be fixed easily)

Also, Bob leaves the room every time you enter, just to keep Mary on her toes.

<!--Saved by Quest 5.7.6404.15496-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <include ref="NpcLib.aslx" />
  <game name="mary2">
    <gameid>9be68bb5-b42a-4afb-afac-5a98618cc8cd</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <feature_asktell />
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <enter type="script">
      if (Bob.parent = this) {
        NpcGoTo (Bob, another_room)
      }
    </enter>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="Mary">
      <inherit name="editor_object" />
      <inherit name="NpcType" />
      <inherit name="namedfemale" />
      <telltodefault type="script"><![CDATA[
        if (LCase(text) = "follow bob") {
          msg ("\"Okay!\"")
          NpcGoToParent (Mary, Bob)
          Mary.actions = NewStringList()
          list add (Mary.actions, "Script:Mary")
          Mary.npcscript => {
            NpcGoToParent (Mary, Bob)
            Mary.deletefromlist = false
          }
        }
        else {
          msg (CapFirst(this.gender)+" ignores you.")
        }
      ]]></telltodefault>
      <tellto type="scriptdictionary" />
    </object>
    <exit alias="north" to="another_room">
      <inherit name="northdirection" />
    </exit>
  </object>
  <object name="another_room">
    <inherit name="editor_room" />
    <enter type="script">
      if (Bob.parent = this) {
        NpcGoTo (Bob, room)
      }
    </enter>
    <object name="Bob">
      <inherit name="editor_object" />
      <inherit name="namedmale" />
      <inherit name="NpcType" />
    </object>
    <exit alias="south" to="room">
      <inherit name="southdirection" />
    </exit>
  </object>
  <walkthrough name="kv">
    <steps type="simplestringlist">
      mary, follow bob
      n
      s
      n
      s
      n
    </steps>
  </walkthrough>
</asl>

image


Okay...

Last one.

If you get the new release of Quest (available on GitHub right now!), you can use "world" for the scope and the command can include an object that's anywhere in the game. So, no need to use text and match it against things! We can do this now:

image


<!--Saved by Quest 5.7.6606.42741-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <include ref="NpcLib.aslx" />
  <game name="mary3">
    <gameid>9be68bb5-b42a-4afb-afac-5a98618cc8cd</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <feature_asktell />
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <enter type="script">
      if (Bob.parent = this) {
        NpcGoTo (Bob, another_room)
      }
    </enter>
    <beforeenter type="script">
    </beforeenter>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="Mary">
      <inherit name="editor_object" />
      <inherit name="NpcType" />
      <inherit name="namedfemale" />
      <telltodefault type="script">
        if (LCase(text) = "follow bob") {
          MaryFollowBob
        }
        else {
          msg (CapFirst(this.gender)+" ignores you.")
        }
      </telltodefault>
      <tellto type="scriptdictionary" />
    </object>
    <exit alias="north" to="another_room">
      <inherit name="northdirection" />
    </exit>
  </object>
  <object name="another_room">
    <inherit name="editor_room" />
    <enter type="script">
      if (Bob.parent = this) {
        NpcGoTo (Bob, room)
      }
    </enter>
    <object name="Bob">
      <inherit name="editor_object" />
      <inherit name="namedmale" />
      <inherit name="NpcType" />
    </object>
    <exit alias="south" to="room">
      <inherit name="southdirection" />
    </exit>
  </object>
  <command name="tell_npc_to_follow">
    <pattern>tell #object1# to follow #object2#</pattern>
    <scope>world</scope>
    <script>
      msg (object1)
      msg (object2)
      if (object1 = Mary and (object2 = Bob)) {
        MaryFollowBob
      }
    </script>
  </command>
  <function name="MaryFollowBob"><![CDATA[
    msg ("\"Okay!\"")
    NpcGoToParent (Mary, Bob)
    Mary.actions = NewStringList()
    list add (Mary.actions, "Script:Mary")
    Mary.npcscript => {
      NpcGoToParent (Mary, Bob)
      Mary.deletefromlist = false
    }
  ]]></function>
  <walkthrough name="kv">
    <steps type="simplestringlist">
      tell mary to follow bob
      n
      s
      n
      s
      n
    </steps>
  </walkthrough>
</asl>

KV already has this covered in his posts, or he can help with this if not already in his posts


so, just making a post of the simple code itself (to see its concept):

the simple 'follower' code (originally from sgrieg):

'npc' Object following the 'player' Player Object

<object name="room">
</object>

<object name="player">

  <attr name="parent" type="object">room</attr>

  <!--
  I'm using the 'changedparent' Script, so the 'npc' will always be in the same room as the 'player' (or you can use a global Turnscript instead too)
  -->

  <attr name="changedparent" type="script">

    npc.parent = player.parent // Sgreig's 'follower' code

  </attr>

</object>

<object name="npc">

  <attr name="parent" type="object">room</attr>

</object>

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

Support

Forums