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!


Log in to post a reply.

Support

Forums