Celowin - Part X: Henchmen

Preamble


The purpose of this sequence of lessons is to take a complete beginner to programming, and teach him or her how to use NWNScript to write modules. The early lessons will be very basic, and anyone that has done any coding at all will be able to skip over them. The goal here is to make the lessons so that even the people that just shudder at any type of code can learn.


Feel free to post these lessons on any forum, print them out, or modify them. However, just give me credit for doing them.


Any comments on these lessons, good or bad, can be sent to me, Celowin.


I am going to assume that anyone looking at these lessons has at least played around with the Aurora Toolset a bit. If there is enough feedback that people don't know how to do the simple placements that I have in these lessons, I will consider spelling out in more detail what needs to be done.


A Few Notes on this Lesson


This lesson is a lot different from all my previous ones. In fact, I was a little hesitant to write this, because there isn't really all that much scripting involved with this. Most of it is just knowing where to use the functions already written by Bioware. However, there are so many questions on doing this that I decided to cave in.


I rewrote Bioware's function for leveling up henchmen. I did this for a few reasons:

Also, please note that putting a henchman into a module is not a trivial task, even with step by step instructions. Expect to spend a few hours getting even a basic one set up, and a lot more if you want to give them interesting dialogue.


Other people have worked on extensions to henchmen... improving the AI, flexibility in equipping them, and multiple henchmen are some of the ones that I've heard about. I make no claims whatsoever that this lesson will mesh with anyone else's scripts. I'm making a basic henchman here, any modifications you wish to make are not my responsibility.


Step 1: Create the Lowest Level Version


The first thing to do is to create the "first" henchman, at the lowest level. (For the official campaign, the lowest level henchmen are level 4, but there is no reason here why you need to stick to that). My suggestion on handling this (and all higher level versions) is to start out with a PC. Create it using the "Create New Character" in the game, and then put the character into a module with a "level up" lever. Here's how:


Create a simple module, with one tiny area. Insert a placeable lever, and into the OnUsed script, attach the following:


// On Used Script:  tm_xplever_ou
// Award the lever puller with tons of XP.
// To be used only for testing purposes, remove from final
// module.
//
// Written By: Celowin
// Last Updated: 7/25/02
//
void main()
{
  object oPC = GetLastUsedBy();
  GiveXPToCreature(oPC, 10000);
}

Save everything, and start up the module with your "PC version" of the henchman. Pull the lever, and start leveling up your character. Pick all the skills, spells, feats, etc. as you feel fit the character. Then, when you have the "henchman" to the appropriate level for the first version, stop and write everything down.


Remember to save the game (we'll be coming back here later), and then open up your "real" module, where you want your henchman, in the toolset. Create your henchman, giving it all the abilities that we figured out using the XP lever.


From here, on, to stop using the term "your henchman," I'm going to be using the name "Coric Galroud." Of course, there is nothing magical about this name, you can call it whatever you want. I do, however, suggest you keep the first name relatively short, about 10 characters maximum.


Important! Badger Alert! I can't count the number of times I've seen a message like this, "Help! I made a henchman, and when it leveled up it turned into a badger!" In almost every single case, this is because the tag and/or blueprint ResRef for the NPC was set wrong. [When asked to create certain object classes, the engine picks defaults if it can't find the blueprint ResRef you specified... the default creature is a badger. For no particular reason that makes me giggle - ed.]


Again, I'm not using the Bioware level up functions, so my naming conventions are a little bit different from theirs. The tag for the henchman should be the first name, but in all caps. In my case, the tag is CORIC. The blueprint ResRef (found on the Advanced tab) is the name in all lower case, plus the two digit version of the henchman level. Again, in my case for a level 4 Coric, the blueprint ResRef would be coric04.


I can't stress this enough. Tag = uppercase, blueprint ResRef = lowercase with level.


One more time –

Moving on, other things to set on our henchman:

Now, we need to set up the basic scripts for our henchman. I'm just going to use a "plug and play" approach here, we're not going to delve into exactly how these scripts work. All these scripts can be selected from the drop down menu, or else the names typed in manually. (Of course, double check you have the name exactly right if you type it.)


A couple of notes: While Bioware references nw_ch_aca for the OnRested script, this script doesn't seem to exist. I left it blank, and I don't really notice any difference in behavior. Further, the nw_ch_acd script does absolutely nothing. I put it in for reference, but you can safely leave it out if you're not planning on modifying your henchman behavior.

OK, check one more time that your tag and blueprint ResRef are set properly, and also make certain that the henchman actually has the feats to use the equipment you've given it. Once everything is set, it is time to work on the conversation.


Step 2: Setting up the Conversation


Actually, before going too much further, I would 'OK' out of the NPC, and save the module. There is no sense in losing all of your hard work if the editor crashes. After that, start back up, open the NPC, and edit the conversation. [If you're working on a big project, I would go further, and make a copy of the .mod file now and then. Better safe than corrupted beyond repair - ed.]


This is probably the most complicated part, because there are lots of annoying little details for the conversation. I stripped the conversation down about as far as it could go, down to just "do this," and there are still a lot of nodes.


Anyway, be sure you have a bit of time to spend setting this up, and edit the tree to look like this. Note that the H3: type things are just so I can reference specific conversation nodes in the tutorial, and need not be typed in. Hxx is something the henchman is saying, Pxx is something the player is saying. [I've typed this into the conversation editor, and prepared a piccy for you. View the source of this page for the original ASCII text. NB. P13-15,17-25 are all followed by a link to node H9, which I've collapsed for convenience. - ed.]


Note, of course, that I would never actually use a conversation this stripped down in an actual adventure. Your henchman would have zero personality. Also, I haven't put any provisions for "memory" of the henchman.


OK, the basic tree is set up, now we have to attach conditions and actions to it. Again, I'm just going to use the Bioware functions as much as possible. A few places I've made my own, but I'll deal with those last. (Note: A few of the Bioware functions reference journal updates. While it would be good form to comment those lines out, it isn't strictly necessary. Just be aware that if you "Build" your module, the editor will throw a hissy fit over the missing journal entries. Ignore it, the module will still run fine.)

Step 3: Writing Our Functions


OK, there were a few functions that we need up there for the conversation. All but the level up one is very easy, so I'll start with those.


For node H1: tm_hirecheck


// Starting Conditional script: tm_hirecheck
// Should only be placed in a henchman conversation file.
// This function returns TRUE if the henchman does not have a master.
//
// Written by: Celowin
// Last Updated: 7/25/02
//
int StartingConditional()
{
  int nResult;
  nResult = GetMaster() == OBJECT_INVALID;
  return nResult;
}

For node H4: tm_othercheck


// Starting Conditional script: tm_othercheck
// Should only be placed in a henchman conversation file.
// This function returns TRUE if the PC speaking is not the henchman's
// current master.
//
// Written by: Celowin
// Last Updated: 7/25/02
//
int StartingConditional()
{
  int nResult;
  nResult = GetPCSpeaker() != GetMaster();
  return nResult;
}

Now for the hard stuff. I'm going to be using a few functions multiple times, so I'm going to write a library. People that went through lesson 9 should know what these are. If you are just coming here to learn about henchmen, a quick rundown: Put in the following as a script tm_henchlib, and save it, not attached to any event handle. It won't compile properly, you'll get an error message like NO VOID MAIN() IN SCRIPT. Just ignore that message.


A further note. I've made it so that it is easy to change three properties of henchmen: the minimum level, the maximum level, and how many levels behind the PC the henchman will "lag." For example, say you're writing a module for characters of level 4-10. You want to give your characters a bit of a boost early on, so you set the minimum henchman level to 6. Later on, you want less of an impact from henchmen, so you set that they will lag 2 levels behind the PCs. And just in case the PCs get more XP than you anticipate, you set the maximum level on the henchman to 10.


In that case, when a level 4 PC gets the first henchman, it will start out at level 6. It will remain level 6 until the PC hits level 9... then the henchman becomes level 7. The henchman will always remain 2 levels behind the PC until the PC hits level 12. From then on, the henchman will always be level 10.


Anyway, to set these things, just alter the numbers at the start of the tm_henchlib file you're about to create:


// My henchman routines: tm_henchlib
//
// Written by: Celowin
// Last updated: 7/25/02

#include "nw_i0_henchman"

// Defines how many levels behind the PC the henchman lags
// Minimum of 0
int HENCH_LAG = 1;

// Defines the lowest level the henchman can be
// Minimum of 1, Maximum of 20
int HENCH_MIN = 4;

// Defines the highest level the henchman can be
// Minimum of HENCHMIN, Maximum of 20
int HENCH_MAX = 14;

// This function figures out what level the henchman
// "should" be, based on the min, max, and lag
// numbers above, as well as the PC level.
int GetTargetLevel(object oHench = OBJECT_SELF)
{
  // Find the PC's level.
  int nMasterLevel = GetHitDice(GetMaster(oHench));
  // Apply the lag.
  int nTargetLevel = nMasterLevel-HENCH_LAG;
  // If that is less than the minimum level, the
  // henchman should be that minimum level.
  if (nTargetLevel < HENCH_MIN)
    nTargetLevel = HENCH_MIN;
  // If the level is over the max, cap the level
  // to that max.
  if (nTargetLevel > HENCH_MAX)
    nTargetLevel = HENCH_MAX;
  return nTargetLevel;
}

// This function checks to see if the henchman
// is ready to level up.
int GetReadyToLevel(object oHench = OBJECT_SELF)
{
  // Find both the current henchman level, and
  // what level the henchman should be.
  int nTargetLevel = GetTargetLevel(oHench);
  int nCurrentLevel = GetHitDice(oHench);

  // If the current level is too low, it is ready
  // to level up.
  if (nCurrentLevel < nTargetLevel)
    return TRUE;
  else
    return FALSE;
}

// This is the biggie.  This is the function to
// actually level up the henchman.
void LevelHench(object oHench = OBJECT_SELF)
{
  // Find the desired level.
  int nTargetLevel = GetTargetLevel(oHench);

  // Whee!  Fun with string manipulation!
  // All this is working to find the appropriate blueprint ResRef
  // to create the "new and improved" henchman.
  string sNewBlueprint = GetTag(oHench);
  sNewBlueprint = GetStringLowerCase(sNewBlueprint);
  if (nTargetLevel <= 9)
    sNewBlueprint = sNewBlueprint + "0";
  sNewBlueprint = sNewBlueprint + IntToString(nTargetLevel);

  // Create the new henchman.
  // There will be a "fade in" of the new one and a "fade out"
  // of the old.
  object oNewHench = CreateObject(OBJECT_TYPE_CREATURE, sNewBlueprint, GetLocation(oHench));

  // Get rid of the old henchman.  You're fired!
  object oMaster = GetMaster(oHench);
  RemoveHenchman(oMaster, oHench);

  // Copy our desired behavior patterns to the new henchman.
  CopyLocals(oHench, oNewHench);
  AssignCommand(oNewHench,SetAssociateListenPatterns());

  // Add in our new henchman.
  AddHenchman(oMaster,oNewHench);

  // Take out the trash.
  DestroyObject(oHench);
}

Now that we have that library set up, the rest of the scripts are easy, and just involve calling the appropriate functions from the library.


For node P6: tm_levcheck


// Starting Conditional script: tm_henchlev
// Should only be put into a conversation file for a henchman
//
// Returns TRUE if the henchman is ready to level up.

#include "tm_henchlib"

int StartingConditional()
{
  int nResult;
  object oHench = GetHenchman(GetPCSpeaker());
  nResult = GetReadyToLevel(oHench) == TRUE;
  return nResult;
}

For node H7: tm_henchlev


// Action Taken script: tm_henchlev
// To be put in a henchman conversation.
//
// This amazingly complicated script causes a henchman to level up.

#include "tm_henchlib"

void main()
{
  LevelHench();
}

OK, enough for the conversation. Save the conversation, and save the module.


Step 4: Creating Higher Level Versions of the Henchman


At this point, our "base" henchman is completely done. It has equipment, stats, the appropriate tag and blueprint ResRef, and a conversation. Now, we need to create all the henchman that it will "level up" into.


Go back to the save game from when we first found the stats for our henchman. Now, using the lever for XP, keep leveling up, and write down the changes as you go. You don't need to write down absolutely everything, but at least write down the information from the "changes" window at the end of the level up process.


For example, if I'm working on a fighter henchman, I might write down something like:

Lev 4: Str +1, HP +14, Disc +1, Parry +1, Spec(longsword)

And so on for each level up, until I'm at the max henchman level.


Now, close out the game, and start up the toolset. Go to your henchman, and add him to your custom palette.


On the palette, select "Edit Copy." Start putting in changes to the next higher level. Update the character level, feats, skills... everything you wrote down from before.


Warning! Badger alert! Remember to update the blueprint ResRef to the appropriate value. You shouldn't have to change the tag, but the blueprint ResRef needs to be changed to the coric05 format. Also, remember that you have to change the class levels for your henchman to account for the new levels you're granting.


Yet another reminder –

Keep doing this. Each time you have the new level in, edit another copy and update it to the next level, until you have all your henchman versions set up.


Now, go back through the henchmen and tweak their equipment, giving them more level appropriate stuff. Starting at level 7, their ranged weapons should have the Unlimited Ammunition ability. (Unfortunately, that makes the weapon level 7 minimum, so it can't be given to lower level henchmen). Stats, spells, skills, feats, levels, blueprint ResRef, and equipment. Remember to change all these things for each new copy of the henchman. Then, we're almost done.


Step 5: Module Properties


Almost everything is set to go at this point. There are just a few minor things to add to the module to make things work.


First, at whatever "home base" place you want in your module, add a waypoint with tag NW_DEATH_TEMPLE. This is where the henchman will return to when it dies.


Second, we need to add a script to have the henchman level up when the PC does. Go to Edit -> Module Properties -> Events -> OnPlayerLevelUp, and put in the following script:


// OnPlayerLevelUp script:  tm_levelup
//
// When a player levels, this checks to see if the henchman is ready to
// level up as well, and takes necessary action.

#include "tm_henchlib"

void main()
{
  // Find the leveling PC and its henchman
  object oPC = GetPCLevellingUp();
  object oHench = GetHenchman(oPC);

  if (GetIsObjectValid(oHench))  // Don't go on if no henchman
  {
    if (GetReadyToLevel(oHench)) // Don't go on if not time to level
      LevelHench(oHench);
  }
}

Conclusion


And that should do it. If you've followed everything, your henchman should pretty much behave exactly like the ones in the official campaign. (No story or quests, of course. You'll have to come up with those on your own.) You have at least a bit more flexibility on henchman level up behavior if you want to exercise it.


Starting next lesson, I'm going to be taking a slightly different tack. I've discussed my plans in a few places, I won't reiterate them here. Anyway, we'll see how it goes, and whether people will like the new format.


Now that all the real basics are covered, I don't feel quite the pressure to churn out lessons. I'll continue to write, but probably at a lower pace. I'm thinking maybe one a week... since Wednesdays we've been getting new stuff from Bioware, maybe I'll try to schedule putting out a new lesson every Friday. That way you have something to play with over the weekend.





 author: Celowin, editors: Charles Feduke, Mistress, additional contributors: Steve Kubere, Ken Cotterill