Monday, June 23, 2008

Scripting: Roster Companions and Sitting NPCs

I know I said I wasn't going to be working on Fate of a City for two weeks, but I've managed to steal some time in my hectic schedule (no, it's not a holiday, unfortunately) to work on a few things, from tweaks and bugfixes to NPC verisimilitude.

Little things being the operative word here, but I must admit, I do get a small of a kick out making those little touches that add a little bit of extra class. To that end, I've done some minor fiddling with the visual effects editor, which although didn't result in massive production, it has at least made one encounter a little bit nicer.

Two bugfixes were in regards to the handling of roster companions. In numerous generic scripts I've written, it is only the main PC who should be affected or interact with a specific object. I discovered that my previous solution to this problem was actually a little flaky, and unfortunately I was forced to resort to using GetFirstPC(), which basically ends any notion I had of trying to support cooperative multiplayer in Fate of a City.

The second bugfix, and this is rather a big flaw, I feel, is that the default ga_take_item does not function as it should when the item is in a rostered companion's inventory - namely gc_check_item return true, yet ga_take_item will not remove the object! Obviously, this is very significant if you have rostered companions, so I coded up a replacement version to deal with that instead.

// ga_take_item
/*
This takes an item from a player
sItemTag = This is the string name of the item's tag
nQuantity = The number of items (default is 1). -1 is all of the Player's items of that tag.
bAllPartyMembers = If set to 1 it gives the item to all PCs in party (MP only)
*/
// FAB 9/30
// MDiekmann 4/9/07 -- using GetFirst and NextFactionMember() instead of GetFirst and NextPC(), changed nAllPCs to bAllPartyMembers
// AmstradHero 22/06/08 -- Recoded it to go through Roster members instead of faction members

#include "nw_i0_plot"

void main(string sItemTag, int nQuantity, int bAllPartyMembers)
{

int nTotalItem;
object oPC = (GetPCSpeaker()==OBJECT_INVALID?OBJECT_SELF:GetPCSpeaker());
object oTarg;
object oItem; // Items in inventory

if ( nQuantity == 0 ) nQuantity = 1;

if ( bAllPartyMembers == 0 )
{
if ( nQuantity < 0 ) // Destroy all instances of the item
{
nTotalItem = GetNumItems( oPC,sItemTag );
TakeNumItems( oPC,sItemTag,nTotalItem );
}
else
{
TakeNumItems( oPC,sItemTag,nQuantity );
}
}
else // For companions
{
string sTarg = GetFirstRosterMember();
while ( sTarg != "" )
{
oTarg = GetObjectFromRosterName(sTarg);
if ( nQuantity < 0 ) // Destroy all instances of the item
{
nTotalItem = GetNumItems( oTarg,sItemTag );
TakeNumItems( oTarg,sItemTag,nTotalItem );
}
else
{
TakeNumItems( oTarg,sItemTag,nQuantity );
}
sTarg = GetNextRosterMember();
}
}

}
Note that I've removed the GetFirst/NextFactionMember calls entirely, so this will probably only function correctly if you have rostered companions only. I haven't experimented with all the different methods of adding people to your party, so I can't really comment further. All I know is that this is working for me.

I finally decided to bite the bullet and fiddle with making sitting NPC function in a reasonable manner, which took a bit of effort as I tweaked various things. I realise a lot of people have probably already implemented a similar system, but I thought I'd present mine here. The first was producing a heartbeat script for NPCs to have them sit and then randomly talk occasionally while staying seated.

//b_hb_sit
//Heartbeat script for creatures to simulate sitting and talking occasionally

//Function used to delay the playing of the sitidle animation to avoid "skipping"
void delayIdle()
{
PlayCustomAnimation(OBJECT_SELF, "sitidle", TRUE, 1.0);
}


void main()
{
//Determine if I want to sit randomly or if I want to talk
int nAnim = d2();
if (nAnim == 1)
{
PlayCustomAnimation(OBJECT_SELF, "sitidle", TRUE, 1.0);
}
else
{
//I'm not idling - grab a different animation
//These animations last for 5.33 seconds, hence the delay command to switch it to idle once they're done
nAnim = d3();
switch (nAnim)
{
case 1:
PlayCustomAnimation(OBJECT_SELF, "sitfidget", FALSE, 1.0);
DelayCommand(5.33, delayIdle());
break;

case 2:
PlayCustomAnimation(OBJECT_SELF, "sittalk01", FALSE, 1.0);
DelayCommand(5.33, delayIdle());
break;

case 3:
PlayCustomAnimation(OBJECT_SELF, "sittalk02", FALSE, 1.0);
DelayCommand(5.33, delayIdle());
break;
}

}
}
The delay function is necessary because you can't use DelayCommand() directly on PlayCustomAnimation(), and you want to make sure that you play "sitidle" immediately after any other sit animation finishes because otherwise the NPC may decide to stand up "in" their seat, which looks weird. You also can't loop the other commands because then you get a jump as it switches between them. I also steered away from the "sitdrink" and "siteat" animations because it looks a little silly when people are drink or eating without anything in their hand. If anyone has a good solution to this, I'd love to hear about it.

The second issue was making it so that anyone sitting down would not turn to face the PC when clicked on, which typically makes their legs or some other part of their body magically pass through the seat they are sitting on. To solve this problem, all that is needed is a simple one line script added as an action to each start node of their conversation. (I'm only using barkstrings, but it should work for larger conversations too because I set bLockOrientation to TRUE.)

//ga_stay_face
//Makes an NPC stay facing the same direction rather than turning to face the PC
void main()
{
SetFacing(GetFacing(OBJECT_SELF), TRUE);
}
So with those two scripts, voila! People will happily sit down, occasionally animating to talk to the person opposite, and will not turn to face the player for a conversation.

All that you need to do is to create a seat placeable, make sure it is not static and doesn't have dynamic collisions, and then place the person "inside" the seat. Basically the torso of the character will not move forward from where you place them in the toolset, only their legs will move forward when they go into a sitting pose. Also, if you're using benches (as is often the cases with taverns), you may need to create a walkmesh cutter to prevent PCs from walking through it - or just place more people on the bench! :-) Also make sure that you set your NPC's bump state to un-bumpable.

I'd like to thank weby and GrinningFool from the NWN2 IRC chat server for their help and input on the various issues I was having with some of the scripts I've mentioned.

Finally, congratulations to all the winners of the AME Golden Dragon Awards! They were all well-deserved accolades.

Friday, June 13, 2008

Status Update 13-Jun: More Screenshots

The next couple of weeks will see me with little time to work on 'Fate of a City', so I've decided to post a quick update on my recent progress. This update includes another bunch of screenshots, which can all be found here.

As for recent progress, I've developed quite a few areas (some of which are visible in the posted screenshots), and a substantial amount of dialog. I've also been taking the time to add some extra polish to previously done work (see my previous post :-) )

Throughout all of this, I've made certain that there are plentiful opportunities to use the various skills available, and you can be assured that conversation skills will come in handy. I've also tweaked the city itself, so that it is a vibrant and dynamic environment. The townsfolk wander about, entering and leaving areas in a way that feels realistic, and the weather ranges from fine to raining with moderate force.

The mod currently sits at 65000 words and 40 areas, and is approximately 50% complete. A recent playtest I did took about an hour and a half, and I covered less than half of the current content I have implemented. As such, I've readjusted my stats on the sidebar of my blog to indicate that the size of the module will probably be about 4-8 hours long, and will enable the PC to reach up to level 7. A low level adventure doesn't have to be about killing rats and beetles, and you'll be swept up into a dangerous plot of death and deceit as soon as you start.

Monday, June 9, 2008

Design: Plan and Prepare to Re-Imagine

One vital tool that any builder has in their arsenal is a good design document. I could go into a large amount of detail on how I've gone about making my design documents for 'Fate Of A City', but that would be an entire post, if not more. (If people are interested, let me know, and I'll cover it in future!) Instead, I'd like to focus on a little thing I've been covering lately, which is 're-imagining'.

Every builder has gone through the process of making an area/character/conversation/cutscene in line with their idea/design document/auguries of fate/pick your inspiration of choice, and finished it with a sense of joy and achievement. Often, the builder is then likely to leave it alone and not touch it again until they are ready to bug test/release their module. Especially in a work/professional environment, this is necessary in order to meet deadlines. However, where possible, I would strongly encourage people to go back and look at previously completed work a month or two later, and re-imagine and refine that work (making sure to keep its scope the same).

There are numerous reasons why. From a bug testing point of view, you've got the exact nuances of the work out of your head, which will sometimes result in you doing things in a fashion you hadn't thought of during the creation process - and help you identify a bug or inconsistency. I've picked up a few such inconsistencies or conversation flow anomalies in a couple of side quests by going back and checking them over the past week.

The second major reason to revisit is to help improve overall coherence. Part of the work I've been doing lately has been improving my introductory sequence. I've seen many good books on game design or writing suggest that you do the introduction last! While this might seems strange, just for starters, it means that you don't spend all your time on this segment of the game and hence let the rest of the game suffer. It also means there is a clear idea of where the story is headed, hence giving you the best opportunity for throwing in bits of information or ties to the main sequence of the greater plot. Mask of the Betrayer and Mass Effect do this very well. In the introductory sequences of both, there are key points of the plot that you are exposed to (I won't go into details to avoid potential spoilers) that you don't realise are significant until quite a bit later.

Lastly, revisiting allows you to take advantage of any improvements in your knowledge or work processes that you hadn't come across when you were developing it the first time around. I know I've been finding quite a few things lately that have allowed me to improve little things in my module, whether it be changing the appearance of tiles (yes, I know that sounds hideously basic, but I'd manage to miss how to include variations!), scripted waypoints, playing with weather and sounds via scripts, or whatever new trick you've just discovered. Or you could have just got better at area design. For example, take my redesign of my fountain in the rich quarter, which went from its old, original, and somewhat stale design below...


To the new...


Ignoring the obvious considerations of camera position and lighting, the latter has significantly more aesthetic appeal. The first consisted of two placeables, some water, reeds and rocks (not visible in that screenshot), whereas the second includes terrain, placeables, water, grass, tinting, and visual effects. Yes, the second involves significantly more effort, but I didn't necessarily have the skill to do it the first time around. (Or at least, I wouldn't have been able to do it as well!)

So in closing, don't be afraid to revisit and re-imagine. You'll likely be pleased you did, and so will your players!