Monday, 14 January 2008

Boohoo, I want to sleep on the ground too!

Been a while. Lots of work at my job....

Okay, you have seen my resting on ground screen shots. Here's a little information that can help out if you want to do your own laydown sleeping animation. The resting and healing part is so Zork specific that I don't believe it's usefull for anyone else, so I skip that part. Besides, it's the easy part ;)

First thing: The animations in NWN2 is a bit more complicated than in NWN1 but in the same time more powerfull (and more easy to mess upp), but more pretty (and big trouble)... Yeah, you get it.

Second: All animations are of different lenght

Third: Animations are different depending on what gender your creature have. Female animations are slower, male are faster.

I presume that you have some experience of writing scripts for NWN1 or 2. If you don't I recommend that you start learning. It's really fun and rewarding. I have a few good links down to the left to get you started. You can also start here. I hope you get some ideas of how to use animations and can pick out a few goodies for your own module. I know that I would have jumped in joy over it when I fumbled in the dark.

First of all you need some functions to call from your script:

//This one you call to be able make a series of animation with DelayCommand
void PlayCAnim(object oObject, string sAnimation, int nLoop, float fSpeed = 1.0f)
{
PlayCustomAnimation(oObject, sAnimation, nLoop, fSpeed);
}
////////////////////////////////////////////////////////


//This one come in handy when you want to know how long a certain animation takes
float AnimationLength(object oPC, string sAnim)
{
if (GetGender(oTarget) == GENDER_FEMALE)
{
if (sAnim == "disableground") { return 5.0; }
if (sAnim == "standupb") { return 3.0; }
if (sAnim == "laydownb") { return 3.33; }
if (sAnim == "proneb") { return 2.66; }
}
else
{
if (sAnim == "disableground") { return 5.0; }
if (sAnim == "standupb") { return 2.66; }
if (sAnim == "laydownb") { return 2.33; }
if (sAnim == "proneb") { return 2.0; }
}
}
////////////////////////////////////////////////////////


//If you want to make a bedroll somewhere in the animation sequence you need to call
a function like this one*/
void CreateBedroll(object oPC)
{
string sResRef = "plc_mr_blanketwool1"; //The Blanked Placeable
object oRestbedroll = CreateObject (OBJECT_TYPE_PLACEABLE, sResRef, GetLocation(oPC));
SetLocalObject (oPC, "oBedroll", oRestbedroll); // Remember the Bedroll
}
////////////////////////////////////////////////////////


/*If you want you can use a function to call when laying down like this one and call it from within the Main() of your script You see that it uses several of the functions above to get the timing right.*/
void GetDown(object oPC)
{
SetCutsceneMode(oPC, TRUE);//Freeze the camera
float fTDg = AnimationLength(oPC, "disableground");
float fTSu = AnimationLength(oPC, "standupb") + fTDg;
float fTLd = AnimationLength(oPC, "laydownb") + fTSu;
string sSound;
if (GetGender(oPC) == GENDER_FEMALE){sSound = "as_pl_snoringf1";}
else {sSound = "as_pl_snoringm1";} //Female or Male Zzzz sound
DelayCommand(0.3, SetCommandable(FALSE, oPC)); //Imobilize the PC
PlayCustomAnimation(oPC, "disableground", 0, 1.0);
DelayCommand(fTDg/2.0, CreateBedroll(oPC));
DelayCommand(fTDg, PlayCAnim(oPC, "standup", 0, 1.0));
DelayCommand(fTSu,
PlayCAnim(oPC, "laydownb", 0, 1.0));
DelayCommand(fTLd,
PlayCAnim(oPC, "proneb", 1, 1.0));
DelayCommand(fTLd+1.0, AssignCommand(GetLocalObject (oPC, "oBedroll"),PlaySound(sSound)));
}////////////////////////////////////////////////////////


/*Here's the a function to stand up quick. Maybe the sleeper has been attack or something. It also equips the items the PC held.*/
void GetUpQuick(object oTarget)
{
float fTSu = AnimationLength(oPC, "standupb");
SetCommandable(TRUE, oPC);
SetCommandable(TRUE, oPC);
SetCutsceneMode(oPC, FALSE);
PlayCAnim(oPC, "standupb", 0, 1.0);
DelayCommand(fTSu, PlayCAnim(oPC, "idle", 1, 1.0));
DelayCommand(fTSu + 0.1, AssignCommand(oPC, ActionEquipItem(GetLocalObject (oPC, "oLeftHand"), INVENTORY_SLOT_LEFTHAND)));
DelayCommand(fTSu + 0.1, AssignCommand(oPC, ActionEquipItem(GetLocalObject (oPC, "oRightHand"), INVENTORY_SLOT_RIGHTHAND)));
DelayCommand(fTSu + 0.1, DeleteLocalObject (oPC, "oBedroll"));
DelayCommand(fTSu + 0.1, DestroyObject (GetLocalObject (oPC, "oBedroll"))); //Remove the beddroll
}
////////////////////////////////////////////////////////


//This is a full wake up cycle with picking up the bedroll. By now you're familiar with how everything works.
void GetUp(object oPC)
{
float fTSu = AnimationLength(oPC, "standupb");
float fTId = AnimationLength(oPC, "idle") + fTSu;
fTId = 2.0 + fTSu;
float fTDg = AnimationLength(oPC, "disableground") + fTId;
string sSound;
if (GetGender(oPC) == GENDER_FEMALE){sSound = "as_pl_yawningf1";} //Male of female yawn
else {sSound = "as_pl_yawningm1";}
PlayCAnim(oPC, "standupb", 0, 1.0);
DelayCommand(fTSu, PlayCAnim(oPC, "idle", 1, 1.0));
DelayCommand(fTId, PlayCAnim(oPC, "disableground", 0, 1.0));
DelayCommand(fTDg-2.0, DeleteLocalObject (oPC, "oBedroll"));
DelayCommand(fTDg-2.1, DestroyObject (GetLocalObject (oPC, "oBedroll")));
DelayCommand(fTDg, SetCommandable(TRUE, oPC));
DelayCommand(fTDg+0.1, SetCommandable(TRUE, oPC));
DelayCommand(fTDg+0.3, SetCutsceneMode(oPC, FALSE));
DelayCommand(fTDg+0.3, AssignCommand(oPC, ActionEquipItem(GetLocalObject (oPC, "oLeftHand"), INVENTORY_SLOT_LEFTHAND)));
DelayCommand(fTDg+0.3, AssignCommand(oPC, ActionEquipItem(GetLocalObject (oPC, "oRightHand"), INVENTORY_SLOT_RIGHTHAND)));
DelayCommand(fTDg +0.5, PlayCAnim(oPC, "yawn", 0));
DelayCommand(fTDg +0.7, AssignCommand(oPC, PlaySound(sSound)));
}
////////////////////////////////////////////////////////


/*Okay, now we use all our fine functions to make the PC laydown and sleep and later
wake up and pick up the blanket. In this example the script is fired with from
an items on_activated_script*/
void main()
{
object oPC = GetItemActivator();
//Remember what things the PC had in left and right hand
SetLocalObject(oPC, "oLeftHand", GetItemInSlot(INVENTORY_SLOT_LEFTHAND , oPC));
SetLocalObject(oPC, "oRightHand", GetItemInSlot(INVENTORY_SLOT_RIGHTHAND , oPC));
//Unequip it before laying down
AssignCommand(oPC, ClearAllActions());
AssignCommand(oPC, ActionUnequipItem(GetItemInSlot(INVENTORY_SLOT_LEFTHAND , oPC)));
AssignCommand(oPC, ActionUnequipItem(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND , oPC)));
//Call the lay down function
DelayCommand(1.0,GetDown(oPC));
//Wait some time and wake up
DelayCommand(20.0,GetUp(oPC));
}
////////////////////////////////////////////////////////

That's about it. Now you just have to add in some enimies nearby-check and a healing loop. But that's another story... I've written this code from my memory while sitting waiting for a customer to show up to our meeting so don't be afraid if there's a compile error. I just want to share the principles.

3 comments:

Ernie Noa said...

Holy Cow! That's a mountain of scripting. :-)

Amraphael said...

Yeah, but most of it is repeating code for every step in the animation sequence. In a mod where you have many animations it would help to make a few functions and put then in an include file. I don't know how much bugs I would have introduced if I write all the code over and over again.

Blogger isn't the best place to put code I realized after writing it all.

Wyrin said...

Gods, I don;t envy you writing and testing that out!