Robert Straughan - Capture Glory: Dwarf NPC

Snow White and...


If you played the module, you will no doubt have seen Kilinar. He's a dwarf NPC that I've placed into the module to act as a henchman. He has a conversation with several scripts throughout them. Let's take a look.


//Script "cave_hench1"
int StartingConditional()
{
     int iResult;
     iResult = GetLocalInt(OBJECT_SELF, "SPOKEN") == 0;
     return iResult;
}

This is actually the default one generated by the script editor when you press edit on the TextAppearsWhen handler in a conversation. It can also be written like this:


//Example Script
int StartingConditional()
{
     if (GetLocalInt(OBJECT_SELF, "SPOKEN") == 0)
          return 1;
          
     return 0;
}

Essentially, iResult is a 1 or a 0, depending on whether the comparison statement is true or false. That value is then returned, since this is a StartingConditional script, not a void main().


Almost the only use for this kind of script is in conversations, and essentially, if a 1 is returned, then the node this is attached to will either be said by the NPC, or will be an option available to the PC. If a zero, then it won't show.


What does the script do? If the local integer labelled SPOKEN is equal to one, then the script returns a one, and the line will be said. I'm using this to stop the line from being said twice. How? Read on:


//Script "cave_hench4"
void main()
{
     SetLocalInt(OBJECT_SELF, "SPOKEN", 1);
}

By placing this script in the ActionsTaken handler of the very same conversation node, it means that the line will only show if the local integer hasn't been touched, and then immediately sets it to one to prevent the line from being said again.


Later on, I do another script as follows:


//Script "cave_hench2"
int StartingConditional()
{
     int iResult;
     iResult = GetLocalInt(OBJECT_SELF, "SPOKEN") == 1;
     return iResult;
}

This means that if I place this node above the one that checks for the same value to be set to zero, the line won't get spoken until the 'one-liner' node has been spoken. This is not necessary, but it can help when placing nodes into a specific order.


At some point, Kilinar joins the player's party. This was done with the following script:


//Script "cave_hench3"
void main()
{
     AddHenchman(GetPCSpeaker());
}

Notice that I've made this just one line. What can we tell from this? GetPCSpeaker is an object returner like all the others that are based upon which handler you are using. It gets whatever PC is currently involved in the conversation.


AddHenchman is the function which actually makes the NPC join the targetted object's party. There is actually a hidden parameter here, which if you look in the definitions, you'll find is already declared to OBJECT_SELF, which is fine given the caller of this script.


//Script "cave_hench5"
int StartingConditional()
{
     int iResult;
     iResult = GetMaster() == GetPCSpeaker();
     return iResult;
}

This script prevents a line being said unless the player talking to the NPC is their master. See how it does it? If you don't then you should look at the definition of the GetMaster function.


Still don't understand it? GetMaster returns an object variable based on who is the master of a specified object. By default, the function uses OBJECT_SELF, so I don't need to add a parameter value to it. If the speaking NPCs master is the speaking PC, then the node shows.


In case you're wondering, yes. Comparison operators can work with any kind of variable. The difference is that the operators == and != are equality tests, rather than mathematical, such as <= and >=. They are essentially EQUAL TO and NOT EQUAL TO. That does not require numeric values to be used.


Really most of the dwarf's scripting is down to timing in the conversation file. I'm not even going to begin teaching you how to use conversations properly, or even that you should use it (a lot of DMs possess NPCs rather than use their own conversations). The scripting involved is simple.



Do we trust him?


The dwarf's conversation leads the player to believe that he/she might not be able to trust Kilinar. As a little extra, in the OnExhausted handler of one of the encounters in the tunnel leading away from the cave, I placed the following:


void main()
{
     object oWP = GetObjectByTag("JOURNAL_POINTER");
     object oPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, 
          PLAYER_CHAR_IS_PC,
          oWP
     );
    if (GetLocalInt(oPC, "NW_JOURNAL_ENTRYHenchKilinar") > 10)
          AddJournalQuestEntry ("HenchKilinar", 30, oPC);
}

Here's a useful function to know. AddJournalQuestEntry. Kilinar's conversation used the OtherActions tab to deal with the journal entries, but what if you wish to set a quest journal to a particular place without a conversation.


The OnExhausted occurs when an encounter's spawned creatures have been killed off. However, because the encounter is not physically represented in the game, we need to use a waypoint as a reference to get the nearest player.


So I've gotten the waypoint, and then the nearest creature to it which matches the criteria I specified (read the function definition if you're ever not sure of what values to use).


Another useful piece of information about journal entries is that they are stored on a player as a local integer, labelled NW_JOURNAL_ENTRY and then with the quest tag given to it in the journal editor added onto the end.


So the if statement compares the current state of the player's path in the HenchKilinar quest, and if it is greater than 10 (which I happen to know means that Kilinar has joined the player as a henchman) then this will change the player's journal entry accordingly.


It is a matter of judgement as to whether you should use AddJournalQuestEntry, or SetLocalInt, in order to change a player's current position in a quest.



What have I just done?


Well, you've learned how to use scripts in conjunction with a conversation, and the use of the StartingConditional scripts. You've also seen how to manipulate the journal through use of scripts.


This was a pretty simple bunch of scripts, but you should be sure you've read the box before moving on. Try making an NPC of your own, and making a conversation to add or remove them from the player's party.



Tasks


Create a conversation which presents options to the player that allow him/her to make an ability check against any ability to gain an NPC as a henchman. The check is made against the NPCs ability check.


To make this more interesting, you could also remove any ability check from the conversation that the player would be unable to pass given their score and the target DC of the check.


For something even more interesting, try making it so that the player can only make an attempt at each ability check once.



Hint


The conversation is easy to create. When you've got your six options for the tests, in each one, have a local string set in the ActionsTaken handlers, that stores which test is being done.
Then, have a single node that is linked to by the six options, which gets which test is being done, and runs the necessary calculations to figure out if the test was successful. Do this in the starting conditional for the node, and place a 'You have failed' line underneath that.
This way, you can have the script return 1 if successful, and if it returns a 0 for failure, the success node will be skipped, and the failure node will show instead.


To make it so the checks don't show if the player can't do them, in the StartingConditional scripts for each test, have a check to see what the maximum scores are, and return 0 if the player can't get higher than the NPC.


To have each one be done once, you need to use the Get and Set local variable functions to have a toggle value on each option. In the starting conditional script, you check to see if the variable is zero, returning a 1 if true, and in the ActionsTaken you set that variable to 1, so the player won't see that option again.


Conversations


Remember that when running a conversation file, the game runs from the top most node, down to the bottom one.


This can be important when using StartingConditional scripts to get lines to be stated at different points in time.


A further note that I would make is that a StartingCOnditional is run before a node appears. This acn be useful for doing things that you would normally do in a void main() script prior to the conversation option appearing.


In this instance, all you do is add the line return 1; at the end of the StartingConditional script, and the node will always appear, but you will have been able to do other scripting that you needed to that couldn't wait until the ActionsTaken handler fired.


Callers


I've mentioned before now about callers, and I'm going to make sure that you understand the concept.


A caller is whatever object in the module (or even the module itself) called the script. This is usually defined by which object has the script in one of its handlers.


It's important to remember that all functions in a script are run by the caller, which is why some of the scripts use AssignCommand to get other objects to use a function.


OBJECT_SELF always refers to the script caller, which is also why when AssignCommand is used on a door, ActionOpenDoor (OBJECT_SELF) is what you usually see, since OBJECT_SELF becomes the door, as it is now the caller of that function.


Note that I've had intermittent results with this concept. Sometimes it does work, but sometimes you might find it better to actually declare the objects involved and just use those instead, since it is more certain that you are manipulating the correct objects.


Henchmen


To add or remove a henchman is a single script. However, there is a more complicated procedure you must do before trying to add henchmen.


In a creature's script handlers, when first created in the palette, you'll find they all begin with nw_c2_default#, where # is a number or letter which corresponds to the handler the script is in.


If you wish to create a henchman out of an NPC, you must change these scripts so that they are nw_ch_ac#, where # is the same number or letter as the default scripts used for each handler.



Screenshots





 author: Robert Straughan, editors: Charles Feduke, Mistress, contributor: Ken Cotterill