Now we are going to explore the world of animations and effects. we have our script working and really nothing further must be done - however I think it is a little drab, don't you agree? Lets fancy it up a bit with some visual effects to make the player say "What the ...."
First we need to revise the concept:
When the PC comes near the chest,
1. Make the PC play a "surprised" looking animation.
2. At the same time, target the PC with a lighting bolt that emits from the chest (an effect).
3. Then port the PC into the inside_chest area.
Animations:
I think everyone knows what an animation is, so I will jump right into applying an animation to a PC (or NPC for that matter). We can tell certain objects within the toolset to perform certain animations. For example, we can tell a door object to shut (placeable animation) or we can tell an NPC to Point. Obviously we can't tell a door to point however we can tell a creature object to shut a door. So when using animations, a little common sense is in order.
Animations are performed by using the ActionPlayAnimation() function. we then can play any of the ANIMATION_* constants in the Global tab of the script editor.
Important Note Unless the object doing the animation is the caller of the script (or script owner), you will always use AssignCommand() when using animations. There are a few exceptions to this rule, but until those are learned it is best just to assign the action to the object you want to perform that action unless the object is the caller of the script.
Lets look at the ActionPlayAnimation Funstion,
// Cause the action subject to play an animation
// - nAnimation: ANIMATION_*
// - fSpeed: Speed of the animation
// - fDurationSeconds: Duration of the animation (this is not used for Fire and Forget animations)
void ActionPlayAnimation(int nAnimation, float fSpeed=1.0, float fDurationSeconds=0.0);
This function has 3 parameters, only one of which MUST be specified, int nAnimation. The other 2 have defaults so they don't need to be specified unless we need to. nAnimation can be any one of the ANIMATION_* type constants listed in the "Global's" Tab. fSpeed is supposed to be the speed the animation is played - however I have yet to get this parameter to have any effect on the animation it self. I always leave this at default. fDurationSeconds only needs to be specified if a ANIMATION_LOOPING_* type is used. The animation is then played for that length of time in seconds. There are 3 types of animations; FIREFORGET, LOOPING, and PLACABLE. FIREFORGET play once and they are done, LOOPING will play over and over for as long as the fDurationSeconds is spesified, and PLACABLE, these are animations performed by placable objects.
Effects:
There are many types of effect in nwn, more than I can cover here. However they all follow the same basic rule. First you construct an effect - then apply that effect. For this part of the tutorial, we will be using a Visual effect. Visual effects are just that, something visual. Ours will bw a special type of visual effect called a "beam" effect, in this case a lighting bolt.
To construct an effect you first declare it as an effect type variable; for example,
effect eVis = EffectBeam( parameter definitions here );
Lets look at the EffectBeam() funtion definition;
// Create a Beam effect.
// - nBeamVisualEffect: VFX_BEAM_*
// - oEffector: the beam is emitted from this creature
// - nBodyPart: BODY_NODE_*
// - bMissEffect: If this is TRUE, the beam will fire to a random vector near or past the target
// * Returns an effect of type EFFECT_TYPE_INVALIDEFFECT if nBeamVisualEffect is not valid.
effect EffectBeam(int nBeamVisualEffect, object oEffector, int nBodyPart, int bMissEffect=FALSE);
This function has 4 parameters, 3, must be specified and the last has a default. nBeamVisualEffect can be any of the VFX_BEAM_* constants from the global list. If you enter VFX into the script assist search box, you will see there are a lot of visuals to choose from but for this function, you would need to choose one of the VFX_BEAM_* effects. oEffector is the creatutre or object the beam effect emits from. nBodyPart is the BODY_NODE_* constant that specifies which part of the body the beam emits from. Note that when emitting from a placable, BODY_NODE_CHEST should be used, also note that triggers cannot emit beam effect. nMissEffect can be either TRUE or FALSE, the default is FALSE. What this does is make the effect actually miss the target by some random direction and distance.
After you define the effect variable, you must use either the ApplyEffectToObject() or ApplyEffectToLocation() function. Note that some effects must be applied to a location, and others must be applied to an object, while some can be applied to either. It will take some time to learn which is which, but most are self explanatory. For this tutorial we will be using AplyEffectToObject() so we will look at that function now.
// Apply eEffect to oTarget.
void ApplyEffectToObject(int nDurationType, effect eEffect, object oTarget, float fDuration=0.0f);
The notes in the toolset for this function isn't very descriptive for some reason. As you can see there are 4 parameters that must be specified in this function and only 1 default. nDurationType must be one of 3 types, DURATION_TYPE_INSTANT, DURATION_TYPE_PERMANENT, and DURATION_TYPE_TEMPORARY. Again here - some of the DURATION_TYPE_* won't work for some of the effects. So when trouble shooting effects, try to determine if your are using the correct duration type. Try them all if need be. Instant applies the effect instantly, Permanent applies the effect permanently until rested or removed, and temporary applies the effect for a set amount of time then it removes itself. eEffect is the effect variable constructed as above. oTarget is the object you wish to apply the effect to and fDuration is the time in seconds. This parameter is only used by the DURATION_TYPE_TEMPORARY constant.
Open your Tutorial_2 module and then select "File > Save As" and save it as "tutorial_3". Go to the Area palett and open the "outside_chest" area. Because we are going to be adding a lighting bolt Beam effect, we will want the beam to emit from the chest. So we are going to need to get the chest object. Select the chest and go to the properties tab. Under the TAG parameter, enter "CHEST_GRABBER" (without the quotes). This will give your chest a unique tag for our function we will be adding to the script. Now go to the scripts tab and open the "t_ent_chestgrabber" script. We will need to declare and define another variable under our "//Declare Variables" comment. we can do this the same way we got the Waypoint in tutorial_1, with GetObjectbyTag(). Add this to your script now, object oChest = GetObjectByTag("CHEST_GRABBER"); remember to include the quotes this time.
void main()
{
// Declare Variables
object oEnter = GetEnteringObject();
object oWP = GetObjectByTag("WP_INSIDE_CHEST");
object oChest = GetObjectByTag("CHEST_GRABBER");
Now we need to construct the effect of the lighting bolt. The Lighting bolt is a beam effect because it emits from one object to another. If you enter "Beam" in the script assist filter, you wil see EffectBeam as described above. You construct an effect just like you would any other variable. The data type is effect, it needs a variable name, and we need to complete any parameters that do not have defaults. Under the // declare variables section, add effect eBeam = EffectBeam( Now click the "Globals" tab in the script assist and enter "VFX_BEAM" in the search box. You will get a long list of beam type effects, double click on the "VFX_BEAM_LIGHTING" to enter it into the script. This is our VFX effect. For our next parameter oEffector, (don't forget to separate the parameters with a comma) enter oChest. This specifies that the beam will emit from the chest object. remember that beam effects can't come from a trigger. Lastly we come to the nBodyPart parameter. Enter "BODY" in the script assist (still under "global's") you will get a list of "BODY_NODE_* constants to choose from. Because we are using a chest to emit the beam, select "BODY_NODE_CHEST". NOTE: This means the "CHEST" of a creature type, or "main body" of a object, not because it is an actual "chest" object, just wanted to clarify that.
void main()
{
// Declare Variables
object oEnter = GetEnteringObject();
object oWP = GetObjectByTag("WP_INSIDE_CHEST");
object oChest = GetObjectByTag("CHEST_GRABBER");
effect eBeam = EffectBeam(VFX_BEAM_LIGHTNING,oChest,BODY_NODE_CHEST);
Inside the if statement, at the start of the scope, enter a new comment,
void main()
{
// Declare Variables
object oEnter = GetEnteringObject();
object oWP = GetObjectByTag("WP_INSIDE_CHEST");
object oChest = GetObjectByTag("CHEST_GRABBER");
effect eBeam = EffectBeam(VFX_BEAM_LIGHTNING,oChest,BODY_NODE_CHEST);
// Check for Player
if(GetIsPC(oEnter))
{
// Play effects and animations
On the next line we want to use a new function, ClearAllActions();. This function does just what it says, it clears the objects actions. Most likely our player will be running towards the chest. We want to stop them cold before we hit them with the lighting bolt. Assigning this command to the oEnter (or PC) will do just that.
void main()
{
// Declare Variables
object oEnter = GetEnteringObject();
object oWP = GetObjectByTag("WP_INSIDE_CHEST");
object oChest = GetObjectByTag("CHEST_GRABBER");
effect eBeam = EffectBeam(VFX_BEAM_LIGHTNING,oChest,BODY_NODE_CHEST);
// Check for Player
if(GetIsPC(oEnter))
{
// Play effects and animations
AssignCommand(oEnter,ClearAllActions());
Now we want to make the PC play an animation, one that might look like surprise or pain. You can pick any animation you want - infact, later I encourage you to try different ones. For now, lets use the ANIMATION_FIREFORGET_VICTORY2 animation. Again, because this is an animation, we need to Assign this command to the Player.
void main()
{
// Declare Variables
object oEnter = GetEnteringObject();
object oWP = GetObjectByTag("WP_INSIDE_CHEST");
object oChest = GetObjectByTag("CHEST_GRABBER");
effect eBeam = EffectBeam(VFX_BEAM_LIGHTNING,oChest,BODY_NODE_CHEST);
// Check for Player
if(GetIsPC(oEnter))
{
// Play effects and animations
AssignCommand(oEnter,ClearAllActions());
AssignCommand(oEnter,ActionPlayAnimation(ANIMATION_FIREFORGET_VICTORY2));
Now it is time to apply the effect we constructed. The effect is going to be between the chest object and the player object, so we will want to use the function ApplyEffectToObject(). When we constructed the effect, we specified where (or what object) the effect will emit from, so this function won't need to be assigned. The DURATION_TYPE_ will be TEMPORARY, we only want it to last long enough for the player to see it until they jump. The effect is eBeam (the variable name when we constructed the effect) and the target is the player oEnter, the object in wich we want to apply the effect to. Do you see the logic here? The effect is constructed with the object the effect emits from (the chest) and it is applied to the object we want the beam effect to emit to (the player). nDuration weill be set to about 3 sec. This is a float variable so make sure you include a 'point zero" after the number or 3.0. Feal free to play with other timings, maybe try 2.5.
void main()
{
// Declare Variables
object oEnter = GetEnteringObject();
object oWP = GetObjectByTag("WP_INSIDE_CHEST");
object oChest = GetObjectByTag("CHEST_GRABBER");
effect eBeam = EffectBeam(VFX_BEAM_LIGHTNING,oChest,BODY_NODE_CHEST);
// Check for Player
if(GetIsPC(oEnter))
{
// Play effects and animations
AssignCommand(oEnter,ClearAllActions());
AssignCommand(oEnter,ActionPlayAnimation(ANIMATION_FIREFORGET_VICTORY2));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY,eBeam,oEnter,3.0);
//AssignCommand oEnter to Jump
AssignCommand(oEnter,JumpToObject(oWP));
}
}
Now our new script is complete, or so it appears. If you like, go ahead and compile the script - save - and test your mod. Then come back here to answer your immediate question. My Pc jumped but I did not see the lighting effect (or maybe you did) and the animation did not play, what is wrong??" Remember when I discussed the differences between the function JumpToObject() and ActionJumpToObject()? (look back) This is a perfect time to demonstrate the differences. If you run the module with the script as is, then most likely your PC jumped immediately and you missed the animation and possibly the effect. This is because the function JumpToObject() inserts istelf at the top of the objects action queue, in this case the PC. So that command is executed first. To correct this issue we simply need to change the function from JumpToObject to ActionjumpToObject. This will place the jump command at the end of the PC action queue, so the PC will do the animation first - then jump. Our completed script should now look like this;
void main()
{
// Declare Variables
object oEnter = GetEnteringObject();
object oWP = GetObjectByTag("WP_INSIDE_CHEST");
object oChest = GetObjectByTag("CHEST_GRABBER");
effect eBeam = EffectBeam(VFX_BEAM_LIGHTNING,oChest,BODY_NODE_CHEST);
// Check for Player
if(GetIsPC(oEnter))
{
// Play effects and animations
AssignCommand(oEnter,ClearAllActions());
AssignCommand(oEnter,ActionPlayAnimation(ANIMATION_FIREFORGET_VICTORY2));
ApplyEffectToObject(DURATION_TYPE_TEMPORARY,eBeam,oEnter,3.0);
//AssignCommand oEnter to Jump
AssignCommand(oEnter,ActionJumpToObject(oWP));
}
}
Compile, save and Run Module. make sure everything is working as expected. Now run the module a second time but this time when your character enters the trigger and the gets hit my the lighting, click someplace to move or just hit the "W" key to move - do this before the PC jumps. You will see that you can interrupt the commands and your action of moving will cancel the jump action. This is a issue easily corrected (Editors note: this appears to only be an issue with NWN1, but will be covered anyway's) and that will be the basis of our next lesson. I will also be introducing Delays.