Monday, March 12, 2007

Exteriors

I've created my first exterior area and...well, let's just say my skills in this department are seriously underwhelming. I started out with a basic concept, make a small town with an inn, a temple/church, a merchant, and a miscellaneous building. A few of my frustrations:

1. Texturing
There doesn't appear to be an easy way to remove a texture once it's been applied. Perhaps this is covered in some tutorials online (and if I find them, I'll definitely update with a link to them), but from all appearances, once you've applied a texture to a section of the map, it's there.

2. Height-Mapping
Ugh, the tools for raising and lowering the landscape are TOUCHY!! I basically settled on creating my town as a small, flat area, surrounded on all sides by large rocks (aka placeables). I did do some decent height mapping at the entrance to town, but that's just an experiment to imitate the feeling of walking down into a valley where the town is located.

3. Placeables v. Environment Objects
I played around with some different settings, and at one point accidentally made all my placeables into environment objects. This wasn't really much of a problem (if you dont' mind being able to walk through a building like a ghost), but when I switched them back, all of the doors and related objects to the building placeables were lost, and none of the doors that I tried to attach would "snap" to the doorway. Needless to say, I had to delete all my buildings and start over from scratch.

Along with the basic town layout (which as of this writing only has one NPC, who's a guard standing at the town entrance), I created the interior of an inn. And there encountered the deadly combination that is created by too many placeables and a baked walkmesh. I tried to put a variety of sizes of tables with bottles on them (playing with the X, Y, and Z size attributes of each bottle and table), and wound up somehow overpopulating some tiles -- or at least that's what it seems. If I add a chair to any portion of one tile, baking it destroys the walkmesh for that entire tile. Delete that chair, and it works just fine. So there's one table that looks REALLY weird, with only three chairs at it.

The other fun thing I experimented with was using the walkmesh cutter around some placeables that I'd converted to environment objects. Specifically for the bar and fireplace, there didn't seem to be any good reason to eat up CPU cycles with placeables that didn't really need to be such. Plus, things that are square are really easy to use the walkmesh cutter around.

For today's scripting item, I wanted to make the lamps in the exterior portion of the town turn on or off depending on the time of day. Unfortunately, the on/off state of lights is not a scriptable option (according to all of the forum postings that I could find), so I had to set it up such that the lights could be created/destroyed depending on the time of day. First I created the lampposts, as usual, then created waypoints on the map, at the same height that I wanted the lights to be. Then, I created a blueprint for the light effect that I was looking for. Next, I created the following script, and call it in the OnHeartbeat event for the area:


void main () {
// This script creates the appropriate lights for the lamps in the Marketh town.
int iHour = GetTimeHour();
if (iHour <= 6 || iHour >= 18)
{
if (GetLocalInt(OBJECT_SELF,"iLightsOn") == 0) {
if (GetObjectByTag("mod_lt_lamp1") == OBJECT_INVALID) {
object oLightWP1 = GetObjectByTag("mod_001_wp_light1");
location lLocation1 = GetLocation(oLightWP1);
CreateObject(OBJECT_TYPE_LIGHT,"mod_lt_lamplight",lLocation1,FALSE,"mod_lt_lamp1");
}
if (GetObjectByTag("mod_lt_lamp2") == OBJECT_INVALID) {
object oLightWP2 = GetObjectByTag("mod_001_wp_light2");
location lLocation2 = GetLocation(oLightWP2);
CreateObject(OBJECT_TYPE_LIGHT,"mod_lt_lamplight",lLocation2,FALSE,"mod_lt_lamp2");
}
SetLocalInt(OBJECT_SELF,"iLightsOn",1);
}
}
else
{
if (GetLocalInt(OBJECT_SELF,"iLightsOn") == 1) {
if (GetObjectByTag("mod_lt_lamp1") != OBJECT_INVALID) {
object oLight1 = GetObjectByTag("mod_lt_lamp1");
DestroyObject(oLight1);
}
if (GetObjectByTag("mod_lt_lamp2") != OBJECT_INVALID) {
object oLight2 = GetObjectByTag("mod_lt_lamp2");
DestroyObject(oLight2);
}
SetLocalInt(OBJECT_SELF,"iLightsOn",0);
}
}
}

Friday, March 9, 2007

Custom Treasure Systems

One thing that I found increasingly annoying as I was playing the original campaign and experimenting with some of my first areas in the Toolset was the fact that I could search a weapon rack and wind up picking up a healer's kit...or a ring. Why would anyone store a healer's kit on a weapon rack!? So I decided to take the treasure table that I'd created for an adventure awhile back and create a custom script that could be used in the game to hand out what seemed appropriate.

The script itself is pretty straightforward, so I'll just let the code speak for itself. This weekend I plan on finishing up my first exterior area and completing the town of Marketh, so I'm sure I'll have some new tidbits to share come Monday...


// First attempt at making a script to assign weapon treasure to an object (weapon rack).
// Cliff G~~~~~~ (~~~~~~@gmail.com) - February 11, 2007

#include "x2_inc_treasure"
#include "nw_o2_coninclude"
#include "x2_inc_compon"
#include "ginc_debug"

void main()
{

if (GetLocalInt(OBJECT_SELF,"NW_DO_ONCE") != 0)
{
return;
}

object oOpener = GetLastOpener();

int iCount = d100(1);
int x = 0;

// Calculate the number of items that will be found on the weapon rack:
// 01 - 40 = 1 item
// 41 - 80 = 2 items
// 81 - 95 = 3 items
// 95 - 99 = 4 items
// 00 = 0 items (critical failure)

if (iCount < 51) {
iCount = 1;
}
else if (iCount > 51 && iCount < 76) {
iCount = 2;
}
else if (iCount > 75 && iCount < 96) {
iCount = 3;
}
else if (iCount < 100){
iCount = 4;
}
else {
FloatingTextStringOnCreature("You find nothing but dust and metal shavings.",OBJECT_SELF,FALSE,5.0);
return;
}

for (x = 0;x < iCount;x++)
{

string strObjectTag = GetTag(OBJECT_SELF);
int iDiceRoll = 0;
int iDiceRollSub = 0;

if (strObjectTag == "MOD_PLC_MC_WEAPRCK03")
{
//Weapon Rack 1 is the most primitive-looking, so it has the least valuable items:
// 01 - 50 = Simple Weapon
// 51 - 80 = Martial Weapon
// 81 - 00 = Light Armor

iDiceRoll = d100(1);
if (iDiceRoll < 51) {
CreateGenericSimple(OBJECT_SELF,oOpener,0);
}
else if (iDiceRoll > 50 && iDiceRoll < 81) {
CreateGenericMartial(OBJECT_SELF,oOpener,0);
}
else {
CreateGenericLightArmor(OBJECT_SELF,oOpener,0);
}
}

else if (strObjectTag == "MOD_PLC_MC_WEAPRCK02")
{
// Weapon Rack 2 looks less primitive, so it should have some better stuff:
// 01 - 25 = Simple Weapon
// 26 - 50 = Martial Weapon
// 51 - 75 = Class Weapon
// 01 - 33 = Wizard Weapon
// 34 - 66 = Monk Weapon
// 67 - 00 = Druid Weapon
// 75 - 00 = Armor
// 01 - 75 = Light
// 76 - 00 = Medium

iDiceRoll = d100(1);
if (iDiceRoll < 26) {
CreateGenericSimple(OBJECT_SELF,oOpener,0);
}
else if (iDiceRoll > 25 && iDiceRoll < 51) {
CreateGenericMartial(OBJECT_SELF,oOpener,0);
}
else if (iDiceRoll > 50 && iDiceRoll < 76) {
iDiceRollSub = d100(1);
if (iDiceRollSub < 34) {
CreateGenericWizardWeapon(OBJECT_SELF,oOpener,0);
}
else if (iDiceRollSub > 33 && iDiceRollSub < 67) {
CreateGenericMonkWeapon(OBJECT_SELF,oOpener,0);
}
else {
CreateGenericDruidWeapon(OBJECT_SELF,oOpener,0);
}
}
else {
iDiceRollSub = d100(1);
if (iDiceRollSub < 76) {
CreateGenericLightArmor(OBJECT_SELF,oOpener,0);
}
else {
CreateGenericMediumArmor(OBJECT_SELF,oOpener,0);
}
}
}

else if (strObjectTag == "MOD_PLC_MC_WEAPRCK01")
{
// Weapon Rack 1 looks the most advanced; thus, it has the best loot table:
// 01 - 50 = Weapon
// 01 - 33 = Martial
// 34 - 66 = Class Specific
// 01 - 33 = Wizard
// 34 - 66 = Monk
// 67 - 00 = Druid
// 67 - 00 = Exotic
// 51 - 90 = Armor
// 01 - 50 = Light
// 51 - 75 = Medium
// 76 - 00 = Heavy
// 91 - 00 = Wand/Staff/Wand

iDiceRoll = d100(1);
if (iDiceRoll < 51) {
iDiceRollSub = d100(1);
if (iDiceRollSub < 34) {
CreateGenericMartial(OBJECT_SELF,oOpener,0);
}
else if (iDiceRollSub > 33 && iDiceRollSub < 67) {
int iPercClass = d100(1);
if (iPercClass < 34) {
CreateGenericWizardWeapon(OBJECT_SELF,oOpener,0);
}
else if (iPercClass > 33 && iPercClass < 67) {
CreateGenericMonkWeapon(OBJECT_SELF,oOpener,0);
}
else {
CreateGenericDruidWeapon(OBJECT_SELF,oOpener,0);
}
}
else {
CreateGenericExotic(OBJECT_SELF,oOpener,0);
}
}
else if (iDiceRoll > 50 && iDiceRoll < 91) {
iDiceRollSub = d100(1);
if (iDiceRollSub < 51) {
CreateGenericLightArmor(OBJECT_SELF,oOpener,0);
}
else if (iDiceRollSub > 50 && iDiceRoll < 76) {
CreateGenericMediumArmor(OBJECT_SELF,oOpener,0);
}
else {
CreateGenericHeavyArmor(OBJECT_SELF,oOpener,0);
}
}
else {
CreateGenericRodStaffWand(OBJECT_SELF,oOpener,0);
}
}
else {
// If this is not one of the identified Weapon Rack types, put some gold on the item.
CreateGold(OBJECT_SELF,oOpener,4,0);
}
}

SetLocalInt(OBJECT_SELF,"NW_DO_ONCE",1);
ShoutDisturbed();

}

Thursday, March 8, 2007

Riddle Door!

The next stage of the adventure requires the PC's to attempt to pass a riddle door. I'm sure everyone who has any experience in the fantasy world understands the concept - a door is locked, you have to answer X number of riddles to unlock it. In the original campaign, on which this module is based, I allowed the players to figure out the riddles on their own. They varied in success, but in all honesty kicked my (carefully-chosen) riddles' ass. Maybe I had smart friends, maybe I picked riddles that were too easy...either way, I figured to make it a little more difficult in the module.

And how, you might ask? Well, I set the riddles' difficulties according to the players' Wisdom attributes. The first riddle has a threshold of 14, the second 16, and the third 18. If the player's Wisdom meets these thresholds, then they have the answer available to them in the conversation. If not, then they cannot solve the riddles. Okay, so maybe that's a bit harsh (understandably), so I created an item called a "Riddle Book" that will be available to purchase in town that allows PCs to bypass the wisdom checks in order to move forward (I'm nothing if not a forgiving DM...sometimes).

I had a little bit of trouble translating the exact desctiption of the door from the campaign as I designed it into a NWN2 module. The exact description, as I originally wrote it was:

"The keyhole is located in what appears to be the mouth of a demon, and the eyes glow a dim, eerie red. Upon inserting the key, the door animates, the eyes glowing more intently as it swallows the key. It then begins speaking..."

So there's no door that fits that description (and as I said before, I'm no 3D modeler)...so I used the closest thing I could find - the Illefarn door, with some lighting added for effect. And after playing with some of the effects, red just didn't look "right", so I settled on blue for the "glow" effect...



Overall, I'm very happy with the visual effect - it pulses from a white light to a blue light, which is placed behind the door, but leaks out around it. A very cool effect that (unfortunately) you can't really grasp just from the screenshot.

So how does the riddle door work? Well, first of all it's locked, so if the PC's encounter the door without the proper key, they just get a quick cutscene saying "The strange door glows an eerie blue"...

Once they have the correct key, the real fun begins. The door has a custom script on it (assigned to the OnFailToOpen event -- door set to Locked, Plot, no required Key, but Open Lock DC of 99) that I created, called "mod_talking_door" which simply triggers the conversation "mod_goblincave_strangedoor" set in the "conversation" property. This conversation checks to see whether the PC has the key, and has additional conditionals to determine whether the PC has interacted with the door before, whether there are any remaining riddles to be answered, and whether the door should be open:


As you can tell, there are a lot of conditionals and actions involved in this conversation. The general idea is that each of the three riddles have their own "state" in the local int "iRiddles". If the PC hasn't answered any, and hasn't interacted with the door previously, this value is zero. If the PC has interacted but not answered any riddles, the value is 1. If the PC has interacted, and answered 1, 2, or 3 of the riddles correctly, the values are 2, 3, or 4, respectively. If the value is 4, then the door simply opens (assuming it somehow gets closed).

As commented on before, the riddle nodes are in reverse order (remember that conversation nodes trigger on the first node that has a TRUE conditional, and ignores the rest). So we start with the end state, iRiddle = 4, where the door simply opens. We then check whether iRiddle > 0, and if so fire off the second-interaction node, which also then checks whether none, 1, or 2 of the riddles are already answered. Next we check to see if the PC has the "Strange Key", and if so we initiate the riddle dialogue. Finally, we have the generic, no key and no prior interaction "teaser" node.

Also, you'll notice that there's an action called "mod_004_riddle_effects()" with the optional parameter of "1", "2", or "3". This is where the fun begins...each of the riddles has an effect that hints at the answer to that riddle. The first has a blindness effect associated with it, the second has a Stinking Cloud effect (downgraded from the CloudKill effect in the original campaign), and the last riddle has a Summon Skeletons effect associated with it.


// Function wrapper for the Stinking Cloud effect.
// Requires a creature in an inaccessible area to issue AssignCommand to.
// Usage: AssignCommand(oPlaceHolder,CreateCloudAOE(oPC));
void CreateCloudAOE(object oTarget) {
float fDuration = IntToFloat(d6()*6);
effect eAOE = EffectAreaOfEffect(AOE_PER_FOGSTINK);
location lLocation = GetLocation(oTarget);
ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, eAOE, lLocation, fDuration);
}
void main (int iAction=2) {
// This script defines the actions taken during the "Riddle Door" interaction.
// 1 = Blindness cast on PC Speaker.
// 2 = Cloudkill cast on location of PC Speaker.
// 3 = 1d4 skeletons summoned on waypoint "mod_001_wp_skspawn".

if (iAction <> 3) return;
object oPC = GetPCSpeaker();
if (GetIsPC(oPC) == FALSE) {
return;
}
if (iAction == 1) {
// FloatingTextStringOnCreature("Blindness Effect",oPC,FALSE,10.0);
effect eBlindVFX = EffectVisualEffect(VFX_DUR_SPELL_BLIND_DEAF,FALSE);
effect eBlind = EffectBlindness();
ApplyEffectToObject(DURATION_TYPE_TEMPORARY,eBlindVFX,oPC);
ApplyEffectToObject(DURATION_TYPE_TEMPORARY,eBlind,oPC,120.0);
}
else if (iAction == 2) {
// FloatingTextStringOnCreature("Stinking Cloud Effect",oPC,FALSE,10.0);
object oPlaceHolder = GetObjectByTag("mod_004_c_StinkCloud");
AssignCommand(oPlaceHolder,CreateCloudAOE(oPC));
}
else if (iAction == 3) {
// FloatingTextStringOnCreature("Skeleton Summoning",oPC,FALSE,10.0);
effect eSkeletonSpawn = EffectVisualEffect(VFX_FNF_SUMMON_UNDEAD,FALSE);
location lSpawn = GetLocation(GetObjectByTag("mod_001_wp_skspawn"));
int iSpawn =0;
ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY,eSkeletonSpawn,lSpawn,1.5);
for (iSpawn; iSpawn <= Random(4); iSpawn++) {
CreateObject(OBJECT_TYPE_CREATURE,"mod_c_skeleton",lSpawn,FALSE);
}
}
}


The easiest of these are the effects flagged by 1 and 3. Effect #1 simply applies a blindness effect and animation on the PC attempting to open the door. Effect #3 summons 1-4 skeletons behind the PC(s), at the mod_001_wp_skspawn waypoint. Effect #2, the Stinking Cloud, required a little more work. It appears that doors can't have actions associated with them (as long as they have zero hit points), so I had to create a placeholder creature, in a room waaaay off from the PC's location, to which I could assign the action ApplyEffectAtLocation. I recently read a post that suggested you could get around this by setting the door's Hit Point value > 0, but I haven't actually tested this out.

The fun part about this part is that if the user gets an answer wrong, they suffer some form of punishment - the first answer blinds them if incorrect, the second casts a Stinking Cloud effect on the PC's location, and the third answer summons skeletons to attack if incorrect. The boring part is, if the PC's wisdom is high enough, or they have the Riddle Book, they just pass through and open the door.

Wednesday, March 7, 2007

Making a Cinematic

Okay, I know that in my last post I said that I'd talk about my riddle door, but it seems that there are a lot of questions on the boards about cinematics and cutscenes, and given that's actually the next step that the PCs in my campaign come upon, I thought I'd take that on instead. Plus, the riddle door is ULTRA cool, so I'm hoping I can bait you all into coming back again tomorrow to read that article. :-)

So you want to make a cinematic, eh? Well, the one great thing about NWN2 is that it has a built-in cinematic/cutscene engine - conversations!! And it's actually pretty darn easy to put one together, once you get the basics down.

To put this part of the project into perspective, once the PCs have either (1) helped the ogre chef out and received his secret key or (2) killed the goblin shaman and obtained the other key, they go to the throne room, where the chief of the goblins is located (actually a Bugbear, so there's a little bit of license taken there, but he's still a badass barbarian). When the PC's enter the doorway, they trigger a cutscene where a Goblin lackey comes to the Chief to report a missing patrol. The chief, of course, wants nothing to do with this, and strikes down the goblin.

So, knowing that a cutscene/cinematic is really just a conversation, the first step (obviously) is to generate the conversation. The trick here is that there are no PC lines in the conversation. One-sided, you say? I say thee nay!! You can specify the speaker and listener for NPC conversation nodes in the Nodes properties view (see below, click to make biggerer):



So we've got the conversation set up (you'll see some actions, but we'll get to those in a little bit - they're tricksy, they are). Now we need to set the stage for the scene itself:


There are six key components to this scene: three static cameras (mod_cam_bugbear_chief, mod_cam_goblin_tattler, and mod_cam_goblin_death), two creatures (mod_c_goblin_tattler and mod_c_bugbear_chief), and one waypoint (mod_wp_tattler). For the cameras, every node in the conversation is set to use one of those static cameras in its Node properties. For most of them, I used Pan, Medium, and Static Camera to get the effects that I needed.

If you don't want to use static cameras, though, you can use some of the preset camera functionality. The different types of shots are pretty straightforward, if you know anything about cinematics, that is. But, if you don't, here's a rundown:
  • Behind The Head: Similar to a standard 3rd-person perspective.
  • Close-Up: "I'm ready for my close-up, Mr. DeMille..."
  • Establishing: A wide shot that shows much of the environment around the target.
  • Long: Wide shot, but more narrowly focused than an Establishing shot.
  • Medium: Between a Long and Close-Up shot.
  • Side: View of the target's side.
  • Three-Fourths: Just like it says, tries for 3/4 of the target in the shot (from head down).
  • Top-Down: Pretty straightforward.
  • Torso: Focuses in on the speaker's torso.
  • Two-Shot: Attempts to get both speaker and listener in the view.
  • Walk-By: The only moving camera setting - camera tracks past the speaker as they talk.
  • Worm's Eye: What a worm would see, up from the floor toward the speaker.
So the characters themselves are probably also pretty self-explanatory, right? At least I hope they are!! But what's the waypoint for? Simple - I didn't want this to be an entirely static scene, so I made sure that three actions were assigned: 1) The goblin walks up to the chief and speaks, 2) the chief strikes down the gobline, and 3) the goblin dies.

Now which of these three do you think was the easiest to do? Yep - walking to the waypoint. There's a simple action script called ga_cutscene_move() that takes the waypoint tag, a delay value in milliseconds, and a boolean value for running. Just set it and forget it (but make sure that your speaker for that node is the one you want moving!!).

Second easiest? Believe it or not, that was the death of the goblin. Making the bugbear attack took me far too long to figure it out. Now, I don't claim that either of these are the most elegant solutions, but they're what worked for me...

For the goblin death, I created a custom script that grabs the goblin's current hit points and inflicts damage on that goblin for his current HP+1.

void main (string sTargetTag) {
object oTarget = GetObjectByTag(sTargetTag);
int iHP = GetCurrentHitPoints(oTarget) + 1;
effect eDamage = EffectDamage(iHP);
DelayCommand(0.5,ApplyEffectToObject(DURATION_TYPE_INSTANT,eDamage,oTarget));
}

As for the attack, it took me forever to figure it out. I knew that it had to be some form of animation, but couldn't figure out what that animation was or how to trigger it. After several hours of research and LOTS of trial and error, I found a post that mentioned the ability to wildcard animation names. So, I created this script for the attack animation, and lo and behold, it worked!!

void main (string sAttackerTag) {
object oAttacker = GetObjectByTag(sAttackerTag);
string sCustomAnimation = "*1attack01";
object oSound = GetObjectByTag("mod_snd_bugbear_atk3");
SoundObjectPlay(oSound);
PlayCustomAnimation(oAttacker,sCustomAnimation,0,1.0);
}

For the actual module, I combined both of the above into a single script that runs the attack animation and causes the death of the goblin, in one fell swoop.

Whew! So we've got the conversation set up, and the actions ready to go. Now...how in the heck do we trigger the conversation? I wanted it to happen immediately after the characters entered a door, so I placed triggers at both of the possible entry points (screenshot):


Again, it took a lot of research and trial and error to set up the triggers to work properly. I finally managed to track down the scene in the original campaign where Qara is found, about to be attacked by the other wizards outside the Flagon. It was there that the secret was uncovered: gtr_speak_node()!!!

This grand and glorious script reads variables off of the trigger to which it is assigned, and runs the conversation that is associated with that trigger according to the parameters that it finds. To set the variables, you click the option in the trigger's Properties window that's above all the scripts (appropriately titled "Variables"). This pops up a new window where you create the necessary variables:
  • NPC_Tag (string) = the tag of the NPC that's starting the conversation.
  • Conversation (string) = the refname for the conversation (the name in the conversation editor).
  • TalkNow (boolean) = Whether to initiate the conversation immediately, or cause the NPC to walk to the PC.
  • Multiuse (boolean) = Whether this is a one-time conversation/cutscene, or can it be triggered more than once by the PC?
  • Run (integer) = Should the NPC run(0) or walk(1) to the PC, or (-1) not move at all.
  • CutsceneBars (boolean) = Should the top and bottom black bars appear during the conversation.
  • OnceOnly (boolean) = Should this conversation ever be triggered by another PC (probably specific to MP modules).
Once those variables were set on the trigger, and the gtr_speak_node script was assigned to the OnEnter event, everything was good to go.

I hope this helps someone out, if for no other reason than it took me almost an hour to just type all this up! :-) Just kidding - if you are working on cinematics of your own, this should be a good stepping-off point for you. Happy modding!!

Tuesday, March 6, 2007

First Challenge - the Ogre Chef!

Yep, you read that right - Ogre CHEF, not chief. In the original campaign that I had written, the PCs are hired to clear out a cave full of goblins near the town - the usual 1st or 2nd level quest. But in the cave, they encounter an ettin (definitely NOT a 1st or 2nd level encounter). The thing is, though, he's (they are) a chef. A proud chef, who wants nothing more than someone to feed on a daily basis. So there's a conversation to be had, and the PC's go away with their lives intact.

When I was translating this into the NWN2 toolset, though, I found out there's no Ettin creature! And being no 3-D artist myself, there wasn't much likelihood that I would be creating my own custom blueprint to add one in. So I looked through the various critters, and found one that looked promising - the Ogre Magi!!



Sure enough - he looks imposing! So now that my model was chosen, I built the "kitchen" and placed him in it. Next, I wanted him to talk to the PC as soon as they were perceived, so that they couldn't know right away that he was not hostile (though if they sneak or are invisible entering the room, they might figure that out themselves). So I created a UserDefined behavior script that had an OnPerception handler that fires off his assigned Conversation upon his perception of a PC in his range.

if (nEvent == 1002) // OnPerceive event
{
object oPC = GetLastPerceived();
if(GetIsPC(oPC) && GetLocalInt(oPC, "Dlg_Init_" + GetTag(OBJECT_SELF)) == FALSE && !IsInConversation(OBJECT_SELF))
{
ClearAllActions();
AssignCommand(oPC, ClearAllActions());
ActionStartConversation(oPC);
}
}

I also placed a custom OnHearbeat event in the same UserDefined script, that fires off one of several one-liner statements at random:

if (nEvent == 1001) // OnHeartbeat event
{
int iRandom = Random(10)+1;
switch (iRandom)
{
case 1:
SpeakString("Hmmm...need something more...",TALKVOLUME_SHOUT);
break;
case 2:
SpeakString("No, no, no!!! That not right!!!",TALKVOLUME_SHOUT);
break;
case 3:
SpeakString("Ouch! Too HOT!!!",TALKVOLUME_SHOUT);
break;
case 4:
SpeakString("Not enough garlic...",TALKVOLUME_SHOUT);
break;
case 5:
SpeakString("Why meat so stringy?",TALKVOLUME_SHOUT);
break;
case 6:
SpeakString("Stupid goblins, not know what they eating...",TALKVOLUME_SHOUT);
break;
case 7:
SpeakString("Maybe being with tribe not so bad.",TALKVOLUME_SHOUT);
break;
case 8:
SpeakString("This not taste like deer...",TALKVOLUME_SHOUT);
break;
case 9:
SpeakString("Now that is a tasty stew!",TALKVOLUME_SHOUT);
break;
case 10:
break;
}
}

Unfortunately, I later found out that the NPC OnHeartBeat user-defined event doesn't work properly...so I had to copy the default onHeartbeat script into a custom script and paste the pertinent parts (from int iRandom= Random(10)+1 to the end of the switch statement) into the custom onHeartbeat for the Ogre Chef.

So now the Ogre Chef himself is set up, and his conversation has been skeletoned out...then I had to figure out how to structure it and set the appropriate actions and conditionals. This wasn't too hard at first - there's one conversation when the PC approaches, another if they don't agree to help, and a third if they agree to help but haven't completed the quest (the innkeeper in town is looking for a chef...guess who's supposed to get the job!). The only problem that I ran into was that the PC is supposed to have an option to attack the ogre chef...after all, he's EVIL, right? Unfortunately, it's not as easy as you'd think taking a neutral creature and making him hostile, due to a bug in the SetIsTemporaryEnemy() command. *sigh*

But right now that's what he's using, so he attacks the PC if they provoke him, but the default action on him remains "Talk To..." I'm thinking about switching this so that he jumps factions, and then putting a check in the onHeartbeat script to reset him to Neutral after a certain amount of time. But I haven't done that yet.

So the long and the short of it is my first custom character in this mod was created and works (almost) flawlessly. There are a few other things that occur in the conversation:
  • The Ogre Chef checks to see if the PC has a "help wanted ad" item from the innkeeper, if so (and the PC has agreed to help him), the ogre chef gives the PCs a key to a hidden entrance to the Throne Room and disappears (off to town!).

  • If the PC taunts the ogre chef or jokes with him too much, he'll refuse to talk with the PC again.

  • If the PC is extra nice, the ogre chef gives the leader a bowl of his special stew (Cure Serious Wounds)...if taunted, the PCs will never get this handy item (even if they manage to convince the ogre chef to work in town).

  • There are a couple quest journal updates and bluff checks scripted in.
Well, I think that's it for tonight...tomorrow I'll talk a bit about the riddle door that I created, and how I kludged my way through some of the effects (particularly a spell cast in the PC's area upon one particular wrong answer).

Welcome!

Someone posted earlier today asking if there were any blogs that were being written by NWN2 mod-makers, and I thought, what the heck, why not give it a shot?

I guess a little introduction might be in order...I'm creating a mod that is based on a PnP D&D 3rd Edition campaign that I started with a group of friends a couple years ago. We finished a few adventures into the campaign, when our schedules conflicted and we couldn't meet on a regular basis. So the campaign languished, but it's been sitting in my collection of D&D stuff ever since. And as soon as I got ahold of NWN2 (granted, a little bit post-release, but honestly the reviews weren't all that impressive), I knew exactly what I wanted to do for my first mod. And so far so good.

A few of the highlights I'll be going over that I've got working so far:
  • Triggered conversation with a montsrous NPC that lets the PC decide whether to kill or cooperate...
  • Cinematic introduction that uses sounds and animations...
  • A "riddle door" with effects triggered if the PC answers incorrectly...
I hope people find this blog useful and/or interesting...I'll try to update it on a daily basis, or at the very least when I've completed something interesting, and/or troublesome.