Celowin - Part VII: Scripting for Items
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.
Introduction
I've been seeing a lot of questions like "How do I attach a script to an item?"
Well, the short answer is "you can't." That isn't a very satisfying response, though, so let me explain further. For whatever reason (my guess has to do with balance on exported characters) [and performance - ed.], BioWare has not allowed any way to directly put a script to an item. However, you can still script actions for an item. You have to do it in a manner that seems somewhat roundabout.
When editing an item, under the "cast spell" option, there are two extra choices: Unique Power, and Unique Power Self Only. By using these two things, we can add all sorts of special abilities, if we go about it right. The "Unique Power Self Only" option is for items that are activated and go off, the plain "Unique Power" option is for abilities that need to be targeted.
If we can't attach any scripts to the items, though, how can we make them do anything? Well, go to Edit > Module Properties > Events tab, and you will see a place for attaching scripts that apply to the entire module. One of the options is "OnActivateItem", and this is where all the fun happens. There are four functions that we will need to understand in order to script for items:
- GetItemActivated – This returns the item that was used.
- GetItemActivatedTarget – If the item was targeted on an object, this returns it. (Remember, of course, that creatures are objects.)
- GetItemActivatedTargetLocation – If the item was targeted on the ground, this returns the location. If the item was targeted on an object, this returns the location of that object.
- GetItemActivator – This returns the PC that activated the item.
These descriptions are very brief, but we'll be seeing each one in action through our examples.
Example 1: Flawed Healing Amulet
Let's make an amulet that can be used to heal the PC. To make it a bit more interesting, though, let's say that it isn't a perfect heal – it only heals half of the damage that the PC has suffered, and to keep it from being too overpowered, we'll restrict it to one use per day.
First, create the base item:
- Open up the item wizard.
- Select "Amulet" as the type.
- Give it the name "Cracked Amulet"
- Check the "magical" box, Item Level 1-5, Low quality
- On the next screen, put it on the palette under Miscellaneous -> Jewelry -> Amulet
- Finally, check the "Launch Item Properties" box, and finish.
- On the General tab, change the tag to HEALNECK
- On the Properties tab, remove whatever abilities it currently has
- Add in the power Cast Spell -> Unique Power Self Only
- Select that ability on the right, then underneath click on "Edit Property"
- Click on "1 Use/Day", Select, then OK
- For ease of testing, check the Identified box.
- OK out of the item properties.
Now, for the scripting. Go to Edit -> Module Properties -> Events, and edit the OnActivateItem script. Put in the following script:
// Module OnActivateItem Script: tm_activate // // This script handles all the unique item properties for the module. // Currently scripted items: // Cracked Amulet // // Written By: Celowin // Last Updated: 7/15/02 // void main() { // General Initialization: Find what item was used object oUsed = GetItemActivated(); // The following section is for the Cracked Amulet // The amulet heals half the PCs damage when used. if (GetTag(oUsed) == "HEALNECK") { // Get the PC, first. object oPC = GetItemActivator(); // Find out how much damage was taken, we're healing half of that. int nDamageTaken = GetMaxHitPoints(oPC)-GetCurrentHitPoints(oPC); int nHealing = FloatToInt(IntToFloat(nDamageTaken)/2); // Perform the healing, and a little visual effect to show it working. effect eHeal = EffectHeal(nHealing); ApplyEffectToObject(DURATION_TYPE_INSTANT, eHeal, oPC); effect eVisual = EffectVisualEffect(VFX_FNF_SMOKE_PUFF); ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eVisual, oPC, 1.0); } }
Save the script, and go test it out. (Don't forget to drop a copy of the amulet into your module, if you haven't done so yet.)
There are two things about this particular script that bear mentioning. The first is the line: int nHealing = FloatToInt ( IntToFloat( nDamageTaken ) / 2 );
Now, the basic idea here is pretty simple: we want to cut the damage taken in half in order to get how much we heal. What is all this IntToFloat and FloatToInt stuff, then? Well, remember that an int is a whole number. There is no decimal allowed there. When we divide two numbers, though, odds are we are going to get a decimal part. So, using an integer in a division is always suspect.
A "float" is a number with a decimal part. So, since we're doing a division, the first thing we want to do is convert the integer nDamageTaken into a float... hence the IntToFloat. Then, since we have a decimal number, the division is allowed. But then, after the division occurs, we need our final result to be an integer... so we do the reverse, FloatToInt. This basically "chops off" the decimal part of the number, and leaves us with the whole number part.
Let's look at an example of the math: Say a PC has taken 17 points of damage. We change that to a float, so it becomes 17.0. Then we divide by 2, it becomes 8.5. We change it back to an integer, the decimal part is dropped, and we're left with 8. So, we would be healing 8 points of damage.
The second part of the script worth mentioning is all the "Effect" stuff down at the bottom. Pretty much any time you are going to directly affect an object in any way, you need to use effects. Healing, visuals, special abilities, knockdowns.... all these are effects of one kind or another. I'm doing two effects here... one is the heal, and the other is a visual of smoke around the player. (Since the amulet is supposed to be not working quite properly, I thought the smoke was appropriate.) Each effect has two parts... defining the effect, and then applying it to the PC.
For the heal, we first have to tell it how much to heal. That is handled with 'effect eHeal = EffectHeal(nHealing);', and then, we heal the PC with 'ApplyEffectToObject ( DURATION_TYPE_INSTANT , eHeal, oPC);'. When applying the effect, DURATION_TYPE_INSTANT says that it happens and then is over. eHeal is the effect we defined, then oPC is the target of the effect.
The second effect is similar. We define the visual effect of a smoke puff, then apply it to the PC. This one is going to run for a few seconds, so it is _TEMPORARY for the duration type. We again say what effect it is, and the target. The final number is the duration.
There are so many different possible effects that it is often really hard to find the one you want. All of them are applied in basically the same manner, though.
Example Two: Alfred's Ring
For those of you just interested in scripting, skip this paragraph. I'm going to get maudlin and remember one of my old friends who was a fantastic D&D player and DM. At one point he was in humorous campaign that unfortunately I was not a part of, though I heard plenty of stories. His character was "Alfred the were-chicken." I don't remember the backstory of the character, but he had all the usual lycanthropic powers... shapeshifting under a full moon, immunity to non-silver weapons, and of course, control over appropriate animals. Well, if anyone has a copy of the first edition AD&D players handbook, you can look up the cost of a chicken under livestock... 1 copper piece. There were 200 copper pieces to a gold. So, for 2 gold, he had an army of 400 chickens following him around. Sure, an individual chicken can't do much... but if you're mobbed by 400 of them at once, all clawing and pecking...
The way this ring will work is to summon a chicken. If we click on the ground, it will just create the chicken. If we click on a creature, the chicken will attack that creature. (Well, for a moment... chickens aren't known for their valor in battle.)
Go ahead and create the item. It is basically the same process as for the first item, so I won't go through it step by step. Give it a name of Alfred's Ring, a tag of CHCKRING, and unlimited uses per day. (If it were a useful creature, that would be unbalancing. But a chicken? )
Now, to make this item work, we have to add to the same script. If we still want the healing ring to work, we have to leave all the former stuff in. We just put in another section to check for the new item we're scripting for.
// The following section is for Alfred's Ring // Summons a chicken, which will attack the target if a creature if (GetTag(oUsed) == "CHCKRING") { // Get where the ring was used location lTarget = GetItemActivatedTargetLocation(); // Set up the "gate in" effect effect eChickIn = EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_3); // Summon the chicken object oNewChicken = CreateObject(OBJECT_TYPE_CREATURE, "nw_chicken", lTarget, TRUE); // If the ring was targeted on a creature, the chicken will be summoned offset a bit. // Update the location, then apply the effect. lTarget = GetLocation(oNewChicken); ApplyEffectAtLocation(DURATION_TYPE_TEMPORARY, eChickIn, lTarget, 1.5); // Finally, if check to see if an object was targeted. If so, attack it. object oEnemy = GetItemActivatedTarget(); if (GetIsObjectValid(oEnemy)) AssignCommand(oNewChicken, ActionAttack(oEnemy)); }
Also, update your header comments. (I know, it is boring to update comments every time, but it is a really good practice to get into.)
Go ahead, save, and test it out. (Note when testing: the chicken won't actually attack anything that it is friendly with... so you might need to play with factions a bit to get it to work. Once you do, it is sort of funny... the chicken will attack, then get scared and run away.)
Some Cautionary Notes
Although I'm sure people are brimming with ideas of neat items you can script, you really should limit yourself to only a few in a module. One reason is stylistic: the more such items you put in, the less impact they have on a person. Another reason is for efficiency... since all the effects get added to the one script, that script can get huge if you put in too many items.
The other warning about items is that you need to remember that they won't work in another module. You can certainly cut and paste your OnActivateItem script to other modules, but it won't happen automatically.
For these reasons, it is common to restrict unique item powers to items needed for the plot. You are not forced into it, but it is something that you may want to consider.
Exercises
I would imagine that people have their own ideas on how to use this, but if you want some practice here are a few things to try:
- Make two altars in different places. Praying at one of these altars will set it to be your recall point. Then, using an item will teleport you to that altar. (Moderate)
- If your PC goes overboard with Alfred's ring, too many chickens might burden your system. Modify it so that the chicken "unsummons" after a certain amount of time. (Easy if you find the right command.)
- Create a charged wand that will heal the target, but take 100 XP away from the user. (Moderate, or Difficult if you put in a beam effect for the wand.)
Anyway, go have fun with it!
author: Celowin, editor: Charles Feduke