%@ LANGUAGE="JScript" %>
Show the Explanation
The default behavior for dealing with items in the game goes like this. When you pick up an item or get one added into your player's inventory the module's OnAcquireItem script runs. When an item leaves your inventory the OnUnAcquireItem script runs. Equipping and unequpping items cause the OnPlayerEquipItem and OnPlayerUnEquipItem scripts to run. When you use an item with a Cast Spell - Activate type or Cast Spell - Unique Power type property, the OnActivateItem script goes off. If you use a weapon with the OnHitCastSpell-Unique Power (OnHit) property then whenever you score a hit, a script called x2_s3_hitcast runs.
So if you have a special item that needs some extra scripting when any of these events occurs, you add changes into one or more of those scripts to get the behavior you need. The system works fine and is very logical too, but there are a couple of drawbacks. First, if your module has lots of items that need special scripting, the scripts attached to those events start getting very big. Typically you end up with lots and lots of if-statements to figure out which item is being equipped or acquired or whatever and to do the special processing. Large scripts tend to be harder to maintain when you need to make changes to them, and stuff get harder and harder to find as the volume of code you have to sift through increases. Second, the special processing you add for any particular item is scattered around in several scripts. When you want to change how an item works or add in a new item, you have to make changes to several different scripts which introduces the possibility of causing a bug in one of the other items. All the processing for each item is not centralized. This makes a lot of work to have the same special item in two different modules too. If you are working on one module and make several changes to how the item works, then you must manually edit all the corresponding scripts in the other module if you expect the two modules to handle the item identically.
This is where tag-based scripts come in. In the tag-based system whenever one of the events mentioned above occurs, the game looks at the tag of the item involved and if a script exists that has the same name, it runs that script. This allows you to create a script that matches the tag of your special item to handle all those events in one place. You can still use the default system along with the tag-based system and for some items, particularly ones that have the simpler customizations, its appropriate to stick with the default method. It makes sense to not create a whole new script if all you need to do is add a couple of lines to the OnAcquireItem script, for example. One of the greatest advantages to using tag-based scripting of special items is that it becomes so easy to port them between modules. All you have to do is copy the one and only tag-based script for an item to another module and BOOM you got the same item in a different module. Of course both modules have to be set up to use tag-based scripting and so this next section describes how to do that.
Show the Instructions
First you gotta be using these specific Bioware scripts in the module events:
OnModuleLoad | - x2_mod_def_load |
OnPlayerEquipItem | - x2_mod_def_equ |
OnPlayerUnEquipItem | - x2_mod_def_unequ |
OnAcquireItem | - x2_mod_def_aqu |
OnUnAcquireItem | - x2_mod_def_unaqu |
OnActivateItem | - x2_mod_def_act |
If you have a custom script assigned to any of these events, just add a line at the very beginning of your custom script to call the default script. For example, if you have a custom OnModuleLoad script you would add this line right after the
first '{' following void main():
ExecuteScript( "x2_mod_def_load",
OBJECT_SELF);
Or, if you have something already in your OnActivateItem
script, you would put in this line:
ExecuteScript( "x2_mod_def_act",
OBJECT_SELF);
Show an Example
Suppose you have a custom script in your module's OnPlayerEquipItem event slot. The script will look like this:
// Likely a bunch of stuff here but sometimes not
void main()
{
// A bunch of stuff
}
// Sometimes a bunch of stuff down here too but not always
To modify it so that it will work with tag-based scripting, you must add the call to x2_mod_def_equ (from the table shown above) into the main function of the script so it looks like this:
// Likely a bunch of stuff here but
sometimes not
void main()
{
ExecuteScript( "x2_mod_def_equ",
OBJECT_SELF);
// A bunch of stuff
}
// Sometimes a bunch of stuff down here too but not always
Note: it is a good idea to look over the custom script to make sure it isn't already calling the default one with an ExecuteScript statement before adding the line in. You definately don't want two of them in there.
Next, edit the module properties, click the Variables button, and put in a new variable:
name
X2_SWITCH_ENABLE_TAGBASED_SCRIPTS
type integer
value 1
If you have a custom OnModuleLoad script you could add the
following line at the beginning of the void main function as an alternative to
using a module variable
SetModuleSwitch (
MODULE_SWITCH_ENABLE_TAGBASED_SCRIPTS, TRUE);
but if you do, make sure you also add this include line at the top of your custom script:
#include "x2_inc_switches"
That's all there is to it. Now any script you create that is named the same as some item's tag will get run whenever any item with that tag is involved in one of the events listed at the beginning of this instructional.
Example: You make a potion tagged "extrahealing" and give it the Activate or the Unique Power property. In your script named "extrahealing" you put code in the X2_ITEM_EVENT_ACTIVATE case (see below) that heals the user to full HP level. When a player uses the potion, your script runs and he gets fully healed. Now you can create another item, let's say a stone with the Unique Power Self-Only property, give it the tag "extrahealing" and it will (with no additional scripting required) also fully heal the player when used. Any item with the Unique Power or Activate property that is tagged "extrahealing" will perform this way once the tag-based script is in place (very slick eh?).
Show the Instructions
All tag-based scripts should look like this as a skeleton:
// Tag-Based Item Script - Skeleton
Template
//:://////////////////////////////////////////////////////////
#include "x2_inc_switches"
// This is the main function for the tag-based
script.
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_EQUIP:
{ // The item
was just equipped.
object oEquipper
= GetPCItemLastEquippedBy();
object oItem
= GetPCItemLastEquipped();
if( !GetIsObjectValid( oEquipper) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// This is where you
enter your item equipping customizations.
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_UNEQUIP:
{ // The item
was just un-equipped.
object oUnequipper
= GetPCItemLastUnequippedBy();
object oItem
= GetPCItemLastUnequipped();
if( !GetIsObjectValid( oUnequipper) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// This is where you
put the item un-equipping effects.
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_ACQUIRE:
{ // The item
was just acquired (picked up, purchased, stolen, etc).
object oNewOwner
= GetModuleItemAcquiredBy();
object oItem
= GetModuleItemAcquired();
object oOldOwner
= GetModuleItemAcquiredFrom();
int
iStackSize = GetModuleItemAcquiredStackSize();
if( !GetIsObjectValid( oNewOwner) || !GetIsObjectValid(
oItem) || (iStackSize <= 0))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
//
This is where you add modifications done when the item is acquired.
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_UNACQUIRE:
{ // The item
was just unacquired (dropped, sold, lost, etc).
object oOldOwner
= GetModuleItemLostBy();
object oItem
= GetModuleItemLost();
object oNewOwner
= GetItemPossessor( oItem);
if( !GetIsObjectValid( oOldOwner) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
//
This is where you add modifications done when the item is un-acquired.
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_ACTIVATE:
{ // The
item's CastSpell Activate or CastSpell UniquePower was just activated.
object
oItemUser = GetItemActivator();
object
oItem =
GetItemActivated();
object
oTarget = GetItemActivatedTarget();
location
lTarget = (GetIsObjectValid( oTarget) ?
GetLocation( oTarget) : GetItemActivatedTargetLocation());
if( !GetIsObjectValid( oItemUser) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// This is where you put your Unique power activation modifications.
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_ONHITCAST:
{ // The
item's OnHitCastSpell Unique OnHit power was just triggerred.
object oWielder
= OBJECT_SELF;
// weapon wielder/armor wearer
object oTarget
= GetSpellTargetObject(); // weapon target/armor attacker
object oItem
= GetSpellCastItem();
// the item with the OnHitCast property on it.
if( !GetIsObjectValid( oTarget) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// This is where you process OnHitCastSpell-UniquePower(OnHit) activation
// due to a
weapon scoring a hit or equipment getting hit.
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
Now just fill in the code appropriate for each event you wish to customize into the corresponding case-block where indicated just after the green comment lines. If you don't need any custom code for one of the events, just delete that whole case-block from the script (remove everything from the word case up to and including the word return;). Then save the script using the name of the tag (don't forget to compile it) and you're done!
For a nice example and another explanation of how this tag-based system works, take a look at the explanation provided by Grimlar in the Lexicon scripting reference: Grimlar's tag-based Tutorial from the Lexicon.
Show the Examples
What follows are some specific examples of items created using tag-based scripting as described on this help page. Hopefully studying them will help you to solve your scripting problems related to item events. Each example focuses on a different event(s) to illustrate how you might go about identifying events that need to be hooked for your purpose, and possibly gives some ideas on how tag-based scripting can be used. In fact, most of the examples are very common uses of item scripting.
This example illustrates using tag-based scripting to make an item commonly seen in NWN. It is a piece of equipment that makes an effect happen when you put it on, and removes the effect when you take it off. The three events related to this are the OnPlayerEquipItem, OnPlayerUnequipItem, and OnUnacquireItem module events. Conventionally, you would add your code into these three scripts having it check the TAG of the item and either add the effect or remove it depending on what item and event you are dealing with. In tag-based scripting all the code for all three events is in the one tag-based script.
Show the Chicken Ring Example
Our Chicken Ring will turn the player into a chicken when he puts it on and turn him back to normal when he takes it off again. The first step is to start with the skeleton script shown above. Since we are only concerned with three events, the skeleton script is trimmed of all the stuff we aren't gonna need resulting in the following:
Show the Chicken Ring Template
// Tag-Based Item Script - The Chicken Ring
template
//::////////////////////////////////////////////////////////////////////
#include "x2_inc_switches"
// This is the main function for the tag-based
script.
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_EQUIP:
{ // The item
was just equipped.
object oEquipper
= GetPCItemLastEquippedBy();
object oItem
= GetPCItemLastEquipped();
if( !GetIsObjectValid( oEquipper) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// This is where you
enter your item equipping customizations.
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_UNEQUIP:
{ // The item
was just un-equipped.
object oUnequipper
= GetPCItemLastUnequippedBy();
object oItem
= GetPCItemLastUnequipped();
if( !GetIsObjectValid( oUnequipper) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// This is where you
put the item un-equipping effects.
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_UNACQUIRE:
{ // The item
was just unacquired (dropped, sold, lost, etc).
object oOldOwner
= GetModuleItemLostBy();
object oItem
= GetModuleItemLost();
object oNewOwner
= GetItemPossessor( oItem);
if( !GetIsObjectValid( oOldOwner) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
//
This is where you add modifications done when the item is un-acquired.
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
Next, all the code is added into the script for all three events to make the wearer of the ring change back and forth from normal to chicken form. Note the code for the OnPlayerEquipItem that turns him into a chicken is placed where it says to put customizations for that event in the skeleton's comment lines - and likewise for the other two events. In case you're wondering why it is necessary to hook into the OnUnacquireItem event, it is in case he is wearing the ring and it gets destroyed or otherwise taken from him without him unequipping it first then the OnPlayerUnequipItem event may not occur. So we need to make sure that he gets changed back to normal again because the ring was removed.
Show the Complete Chicken Ring tag-based Item Script
// Tag-Based Item Script - The Chicken Ring
//::////////////////////////////////////////////////////////
#include "x2_inc_switches"
// This is the main function for the tag-based
script.
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_EQUIP:
{ // The item
was just equipped.
object oEquipper
= GetPCItemLastEquippedBy();
object oItem
= GetPCItemLastEquipped();
if( !GetIsObjectValid( oEquipper) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// Change the
wearer's appearance to that of a chicken if he wasn't already wearing a chicken
ring.
if( GetLocalInt( oEquipper,
"WearingChickenRing"))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
SetLocalInt(
oEquipper, "WearingChickenRing",
TRUE);
// Save his original appearance so when he removes the ring we'll know what to
change him back into.
if( !GetLocalInt( oEquipper,
"OriginalAppearance"))
SetLocalInt( oEquipper, "OriginalAppearance",
GetAppearanceType( oEquipper) +1);
// Now make him look like a chicken.
SetCreatureAppearanceType( oEquipper,
APPEARANCE_TYPE_CHICKEN);
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_UNEQUIP:
{ // The item
was just un-equipped.
object oUnequipper
= GetPCItemLastUnequippedBy();
object oItem
= GetPCItemLastUnequipped();
if( !GetIsObjectValid( oUnequipper) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// If he is
still wearing another chicken ring, do nothing. We check all the inventory
// slots in
case there is an amulet or a sword etc. that is tagged "chicken_ring".
int iSlot = NUM_INVENTORY_SLOTS;
while( --iSlot
>= 0)
{
object oItemInSlot
= GetItemInSlot( iSlot, oUnequipper);
if(
(oItemInSlot != oItem) && (GetTag(
oItemInSlot) == "chicken_ring"))
{ SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
}
//
Otherwise change the wearer's appearance back to normal.
int iOriginal = GetLocalInt( oUnequipper,
"OriginalAppearance") -1;
if( iOriginal >= 0)
SetCreatureAppearanceType( oUnequipper, iOriginal);
DeleteLocalInt( oUnequipper, "WearingChickenRing");
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_UNACQUIRE:
{ // The item
was just unacquired (dropped, sold, lost, etc).
object oOldOwner
= GetModuleItemLostBy();
object oItem
= GetModuleItemLost();
object oNewOwner
= GetItemPossessor( oItem);
if( !GetIsObjectValid( oOldOwner) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// If he is
still wearing another chicken ring, do nothing. We check all the inventory
// slots in
case there is an amulet or a sword etc. that is tagged "chicken_ring".
int iSlot = NUM_INVENTORY_SLOTS;
while( --iSlot
>= 0)
{
object oItemInSlot
= GetItemInSlot( iSlot, oOldOwner);
if(
(oItemInSlot != oItem) && (GetTag(
oItemInSlot) == "chicken_ring"))
{ SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
}
//
Otherwise change the wearer's appearance back to normal only if he was wearing
the ring.
if( GetLocalInt( oOldOwner,
"WearingChickenRing"))
{
int iOriginal = GetLocalInt( oOldOwner,
"OriginalAppearance") -1;
if( iOriginal >=
0) SetCreatureAppearanceType( oOldOwner,
iOriginal);
}
DeleteLocalInt( oOldOwner, "WearingChickenRing");
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
To complete our ring, we just create some ring item -- doesn't even hafta have a custom blueprint for it but it is a good idea to make one anyway. Set the TAG of the ring (or blueprint) to "chicken_ring", save the above script under the name "chicken_ring", rebuild your module and that's it.
You may have noticed in the script, it checks all the equipment slots when the ring is removed to make sure that if the guy is wearing two chicken rings, he will have to remove both of them to return to normal. You may wonder why not just check the ring slots only? Well I did it that way to specifically illustrate a feature of tag-based item scripting that is important to understand. Since it all works off the item's TAG, if you create a chicken ring and then make an amulet or a cloak or some other equipment item and set its TAG to "chicken_ring", then the TAG based script for the chicken ring will also run when an item event happens to the cloak or amulet. The game makes no distinction between the two item types (although your tag-based script certainly could do that if desired) if any item has the right TAG then it will be considered a chicken ring.. Tag-based scripts should be written to deal with this contingency/feature.
Now to show some of the power of tag-based scripting. Try this: put a Ring of Cyan on the ground next to your module's start location. Now edit its properties and set the TAG to "chicken_ring". Do the same with an Amulet of Protection. Boom, with no further scripting required, you now have a Cyan Chicken Ring and a Chicken Amulet of Protection. You will look like a chicken whenever you are wearing either one.
This next example was designed to teach a technique that uses hooking into and modifying standard Bioware items using tag-based scripting. This technique helps keep the number of blueprints down by allowing some modified behaviors to be added to standard items thus eliminating the requirement to create a custom blueprint. Bioware has placed the blueprints of all the standard items right there in the item palettes so you can go in and see what TAG each item will have by default when created during play. Because of this, you can write tag-based scripts for standard items simply by saving the script under Bioware's chosen TAG name for that item. These scripts will not replace the default behavior of the item, but can be used to add to it. What you can and can't do using this technique is highly dependent on what standard item it is. For this example, we are going to modify the Boots of Speed item so by default they will also grant invisibility when worn.
Show the Boots of Invisible Speed Example
The first step in making any custom item is to figure out what events need to have a response written for them in the tag-based script. This is going to be almost identical to the last example since it deals with equipping and unequipping events causing the effect to occur. The same three events will be coded. So the skeleton script will be trimmed down to exactly the same one used in the chicken ring example above.
Show the Complete Boots of Invisible Speed tag-based Item Script
// Tag-Based Item Script - Boots of
Invisible Speed
//::///////////////////////////////////////////////////////////////////
#include "x2_inc_switches"
// This function causes the specified creature to become invisible using a
normal invisibility effect.
// OBJECT_SELF will be marked as the effect creator..
void MakeInvisible( object
oWearer)
{
if( !GetIsObjectValid( oWearer)) return;
effect eInvisible =
SupernaturalEffect( EffectInvisibility( INVISIBILITY_TYPE_NORMAL));
ApplyEffectToObject( DURATION_TYPE_PERMANENT,
eInvisible, oWearer);
}
// This function removes all normal invisibility effects from the specified
creature that were created
// by OBJECT_SELF.
void MakeVisible( object oWearer)
{
if( !GetIsObjectValid( oWearer)) return;
effect eInvisible = GetFirstEffect(
oWearer);
while( GetIsEffectValid(
eInvisible))
{ if( (GetEffectType( eInvisible)
== EFFECT_TYPE_INVISIBILITY) &&
(GetEffectSubType( eInvisible) == SUBTYPE_SUPERNATURAL) &&
(GetEffectCreator(
eInvisible) == OBJECT_SELF))
RemoveEffect( oWearer,
eInvisible);
eInvisible = GetNextEffect( oWearer);
}
}
// This is the main function for the tag-based
script.
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_EQUIP:
{ // The item
was just equipped.
object oEquipper
= GetPCItemLastEquippedBy();
object oItem
= GetPCItemLastEquipped();
if( !GetIsObjectValid( oEquipper) || !GetIsObjectValid( oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// Make him invisible.
AssignCommand(
oItem, MakeInvisible( oEquipper));
}
break;
case X2_ITEM_EVENT_UNEQUIP:
{ // The item
was just un-equipped.
object oUnequipper
= GetPCItemLastUnequippedBy();
object oItem
= GetPCItemLastUnequipped();
if( !GetIsObjectValid( oUnequipper) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
//
Make him visible again.
AssignCommand( oItem, MakeVisible( oUnequipper));
}
break;
case X2_ITEM_EVENT_UNACQUIRE:
{ // The item
was just unacquired (dropped, sold, lost, etc).
object oOldOwner
= GetModuleItemLostBy();
object oItem
= GetModuleItemLost();
object oNewOwner
= GetItemPossessor( oItem);
if( !GetIsObjectValid( oOldOwner) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
//
Make him visible again.
AssignCommand( oItem, MakeVisible( oOldOwner));
}
break;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
This time it is not expected that other item types besides boots will have the right TAG since it's a standard item and there is but one inventory slot for boots. Therefore all the code that dealt with searching the inventory slots for another similarly tagged item was deemed unnecessary and removed. Also, because we don't want to mess with any of the default Boots of Speed behavior, we always return X2_EXECUTE_SCRIPT_CONTINUE to the calling script so that the default behavior will continue normally.
Boots of Speed has its default TAG set to "NW_IT_MBOOTS005" in the standard blueprint, so by saving the above script under that name and recompiling the module, we will have what we want. When you save a script using one of the standard items' TAG as its name, the toolset will pop up a warning about conflicting name-space. This shouldn't usually be an issue so you can ignore the message and go ahead and save the script under that name. However, future releases of the product or patches may cause the script to stop working or interfere with the default game scripts if Bioware changes how standard items are implemented at some time in the future. This example was written June 2005 when version 1.65 was the most current released patch. It is something to be aware of when using this particular tag-based scripting technique.
Once the script is saved, normal Boots of Speed will grant invisibility as well as haste. Our boots will not say Invisibility when the item is examined because we used an effect instead of an item property to make the invisibility happen -- they will appear to the player in every way as a normal standard pair of Boots of Speed. Also the item cost won't change even though it is a much better item. These are two of the limitations/advantages of using this technique in this way.
You should be getting a feel for how to go about creating tag-based custom items now. So for this example and the rest of them below I will not waste space with skeleton scripts or discussions concerning general tag-based mechanics. Instead the examples will focus on how to analyze the custom need and formulate a tag-based solution for it.
A common custom item as you know is a cursed one. There are many varying opinions on what a cursed item is and how it should affect its possessor. Bioware has provided a sort of default cursed type which it refers to as "undroppable". When any item is marked as undroppable, the player is not able to sell, give away, or drop the item. This is an adequate method for handling regular items, but if you want cursed equipment the Bioware system is not very good at all. Yes the player will be stuck with the item, but he will know what it is (identify is a requirement before you can equip anything) and can freely equip/unequip the thing once he has it. Many players are quite unwilling to wear equipment with only penal properties on them.
Show the Gauntlets of Ogre Weakness Example
For equipment a better solution might be to make it so the item gets automatically identified by the player when he acquires it if he has the ability to do so. If he identifies it or if it was already identified, the player is forced to equip the item if he can. It will be marked as undroppable so that he won't be able to drop it, and every time he tries to unequip it, he will be forced to re-equip it immediately. This should make it so that once equipped it cannot be unequipped or removed and he is stuck with it. Other code can be written elsewhere in the module to help him remove it via a spell-hook script (Lilac Soul's Spell-Hooking Tutorial maybe hooking a remove curse spell), or from the ActionTaken of a conversation, or from the OnUsed of some placeable (maybe a fountain or lever or something), or from a trigger/door entered, or even have another tag-based item you can activate to remove it. You can figure that part out on your own.
So we're gonna create some cursed gauntlets with -3 Str penalty that will behave in this way -- sort of a Gauntlets of Ogre Weakness. The item's scripting requirements were pretty much laid out in the previous paragraph. First step is to identify the key events as always. Here we will need to worry about the OnAcquireItem, and OnPlayerUnequipItem events. The gauntlets, once identified, should show up as a cursed item and it should tell the player what negative effects are on it when examined just to be fair to the players. If the strength penalty is placed onto the item as an item property in its blueprint this will happen by default, so the best way to go is to use an item property and not an on the fly effect like we did with the boots in the previous example. Because there will be no effect to get rid of (item property effects are handled automatically by the game already), the OnUnacquireItem event won't need to be hooked like in the previous two examples. That was done to remove the special effect in that particular case where the item got unacquired without being unequipped first. With this cursed item using an item property for the special effect (str loss) the game engine will automatically remove the effect correctly in both cases. Also because of this we won't need to worry about the OnPlayerEquipItem event either. The effect will get automatically applied by the item property handling of the game engine.
Next step for me after formulating a plan is usually to create the blueprint(s). So copy the standard Gauntlets of Ogre Strength blueprint and set its TAG field to "ogre_weakness". Customize the item any way you like but replace the Ability Bonus Strength +3 item property with an Ability Penalty Strength -3 one. Fill in the descriptions for identified and unidentified per your preferences (the identified description should probably say its a cursed item and how to remove it) and set the item name to Gauntlets of Ogre Weakness. Make sure you set a check mark into the box called Undroppable in the item properties panel of the blueprint. This is the cursed flag setting box and should not be confused with the check box in a creature's inventory window called Droppable. That Droppable setting is used to determine if an item carried by a creature is destroyed or dropped when it dies.
Now the scripting. What's needed is a section for the OnAcquireItem event that will make sure the item is identified if possible. If it was already identified or the identification is successful (a Lore check) we will force the player to equip it if he can. Another chunk of code for the OnPlayerUnequipItem event will force him to re-equip the gauntlets if he takes them off.
Show the Complete Gauntlets of Ogre Weakness tag-based Item Script
// Tag-Based Item Script - Gauntlets of
Ogre Weakness
//::////////////////////////////////////////////////////////////////////////
#include "x2_inc_switches"
const int INVENTORY_SLOT_INVALID = -1;
// This function converts a hexidecimal number
represented in a string to an integer. Returns
// the integer value of the converted hex string or 0 if the input string is not
a hex number.
int HexStringToInt( string sHex)
{ sHex = GetStringLowerCase( sHex);
if( GetStringLeft( sHex,
2) == "0x") sHex
= GetStringRight( sHex, GetStringLength( sHex) -2);
if( (sHex ==
"") || (GetStringLength( sHex) >
8)) return
0;
string sConvert =
"0123456789abcdef";
int iValue
= 0;
int iMult
= 1;
while( sHex !=
"")
{
int iDigit = FindSubString( sConvert, GetStringRight(
sHex, 1));
if( iDigit <
0) return
0;
iValue += iMult *iDigit;
iMult *=
16;
sHex =
GetStringLeft( sHex, GetStringLength( sHex) -1);
}
return iValue;
}
// This function is used to force the specified player to
perform an identify on the item given based on his Lore skill.
// If a valid player and item are specified, the skill check is performed, the
item becomes identified if necessary, and the
// function returns TRUE. When the item is already identified no skill check is
performed and the function returns TRUE.
// FALSE is returned if an invalid player or item is specified.. If a valid but
not already identified item is supplied and the
// player specified is valid but fails the Lore test, the item will remain
unidentified and the function returns FALSE.
int CanIdentifyItem( object oOwner,
object oItem)
{
if( !GetIsObjectValid( oOwner) || !GetIsObjectValid( oItem))
return FALSE;
if( GetIdentified( oItem))
return TRUE;
// Compute the maximum cost of items the player can identify based
on his Lore skill.
string sMaxCost =
Get2DAString( "SkillVsItemCost",
"DeviceCostMax", GetSkillRank( SKILL_LORE,
oOwner));
int
nMaxCost = (((sMaxCost == "") || (sMaxCost
== "****")) ?
120000000 : StringToInt(
sMaxCost));
// Compute the actual cost of the item in question.
int bWasPlot =
GetPlotFlag( oItem);
SetPlotFlag( oItem, FALSE);
SetIdentified( oItem, TRUE);
int nItemCost =
GetGoldPieceValue( oItem);
SetPlotFlag( oItem, bWasPlot);
// If the item's cost is more than the max
cost of items the player can identify, set it back to unidentified.
if( nItemCost >
nMaxCost) SetIdentified( oItem, FALSE);
return GetIdentified( oItem);
}
// Given a valid item this function will return TRUE if the item is equippable.
Otherwise it returns FALSE.
int GetIsEquipment( object oItem)
{ if( !GetIsObjectValid( oItem) || (GetObjectType(
oItem) != OBJECT_TYPE_ITEM)) return FALSE;
string sEquipable =
Get2DAString( "baseitems", "EquipableSlots",
GetBaseItemType( oItem));
return ((sEquipable !=
"") && (sEquipable !=
"****") && (sEquipable != "0x00000"));
}
// Given a valid item of equipment, this function returns the inventory slot it
belongs in.
// Otherwise it returns INVENTORY_SLOT_INVALID.
int GetEquipmentSlot( object oItem)
{ if( !GetIsEquipment( oItem))
return INVENTORY_SLOT_INVALID;
int iEquipable = HexStringToInt(
Get2DAString( "baseitems", "EquipableSlots",
GetBaseItemType( oItem)));
if( !iEquipable)
return INVENTORY_SLOT_INVALID;
int iSlot =
0;
int iMask =
1;
while( (iSlot++ <
NUM_INVENTORY_SLOTS) && iMask)
if( (iEquipable
& iMask) == iMask) return (iSlot
-1); else iMask
<<= 1;
return INVENTORY_SLOT_INVALID;
}
// This function is used by the ForceEquipItem
function to re-equip the item he originally had in his inventory
// slot if the cursed one could not be equipped. If there was nothing in the
slot to start with or the specified
// cursed item is invalid or the specified original item is invalid, or if the
specified inventory slot is invalid
// then the slot is left empty, otherwise the original item is returned to the
equipment slot.
void ReEquipSavedItem(
object oSavedItem,
int iSavedSlot,
object oCursedItem)
{ if( !GetIsEquipment( oSavedItem) || !GetIsEquipment(
oCursedItem) ||
(iSavedSlot <
0) || (iSavedSlot >= NUM_INVENTORY_SLOTS)
) return;
// If the slot has the cursed item in it then
do nothing because the force equip worked.
if( GetItemInSlot(
iSavedSlot) == oCursedItem) return;
// Otherwise restore the guy's original item
to its rightful slot
SetCommandable( TRUE);
ClearAllActions( TRUE);
ActionWait( 0.2f);
ActionEquipItem( oSavedItem, iSavedSlot);
ActionDoCommand( SetCommandable( TRUE));
SetCommandable( FALSE);
}
// This function is used to attempt to force the
player to equip an item. If the item is
// invalid or not an equippable item, nothing happens. Otherwise the player is
given
// actions to force him to equip the item. If for some reason (like class or
level restrictions)
// the item cannot be equipped, the player's equipment is not altered.
void ForceEquipItem( object oOwner,
object oItem)
{
if( !GetIsObjectValid( oOwner) ||
(GetObjectType( oOwner) !=
OBJECT_TYPE_CREATURE) ||
!GetIsEquipment( oItem))
return;
// Save his currently equipped item
from the appropriate slot so that if the PC is unable
// to equip the cursed item we will know what item to put back into
the emptied slot.
int iSavedSlot =
GetEquipmentSlot( oItem);
if( iSavedSlot ==
INVENTORY_SLOT_INVALID) return;
object oSavedItem = GetItemInSlot( iSavedSlot,
oOwner);
// Now force him to equip the item
AssignCommand( oOwner, ClearAllActions(
TRUE));
AssignCommand( oOwner, ActionWait(
0.2f));
AssignCommand( oOwner, ActionEquipItem( oItem,
iSavedSlot));
// Reequip the saved item if he could not
equip the cursed one. This action does nothing if the previous one succeeded.
AssignCommand( oOwner, ActionDoCommand(
ReEquipSavedItem( oSavedItem, iSavedSlot, oItem)));
// Lock the player's action queue until the
equipping is completed so the actions cannot be cancelled.
AssignCommand( oOwner, ActionDoCommand(
SetCommandable( TRUE, oOwner)));
AssignCommand( oOwner, SetCommandable(
FALSE, oOwner));
}
// This is the main function for the tag-based script.
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_UNEQUIP:
{ // The item
was just un-equipped.
object oUnequipper
= GetPCItemLastUnequippedBy();
object oItem
= GetPCItemLastUnequipped();
if( !GetIsObjectValid( oUnequipper) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// The cursed
flag check is there to allow the item to be removed later.
// To remove
the item some script should use SetItemCursedFlag to clear the
// cursed
state and then either remove or destroy the item to get rid of it.
if( GetItemCursedFlag( oItem) &&
CanIdentifyItem( oUnequipper, oItem))
{
SendMessageToPC( oUnequipper, "The " +GetName(
oItem) +" is cursed and cannot be removed.");
ForceEquipItem( oUnequipper, oItem);
}
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
case X2_ITEM_EVENT_ACQUIRE:
{ // The item
was just acquired (picked up, purchased, stolen, etc).
object oNewOwner
= GetModuleItemAcquiredBy();
object oItem
= GetModuleItemAcquired();
object oOldOwner
= GetModuleItemAcquiredFrom();
int
iStackSize = GetModuleItemAcquiredStackSize();
if( !GetIsObjectValid( oNewOwner) || !GetIsObjectValid(
oItem) || (iStackSize <= 0))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// If the
player is able to identify the item, force him to equip it if he can.
// The cursed
flag check is there to allow the item to be removed later.
// To remove
the item some script should use SetItemCursedFlag to clear the
// cursed
state and then either remove or destroy the item to get rid of it.
if( GetItemCursedFlag( oItem) &&
CanIdentifyItem( oNewOwner, oItem))
{
SendMessageToPC( oNewOwner, "You have acquired a
cursed item.");
ForceEquipItem( oNewOwner, oItem);
}
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
Last step -- save the tag-based script (above) under the name "ogre_weakness" (or whatever TAG you put in the item's blueprint), drop one of the cursed items on the ground next to the module's start location, rebuild and save the module, then go test it out. Experiment with the additional cost and identified settings to test out the Lore checking and automatic identification features.
One last note about this example. If the player acquires the cursed item while it is not identified and he is unable to identify it, it will go into his inventory but be blue (unidentified). When he examines it he'll get the standard unidentified description. If he then uses a lore potion or identify spell or some other means to identify it, since it is already acquired by him that event won't fire, and it won't auto-equip on him. But it will be in his inventory and he still won't be able to drop it and he will see everything he would for any other identified item when examined. I leave it as an exercise to readers of this to expand the idea and figure a way to auto-equip when the item is identified by its possessor using these other means.
I see this one asked in the scripting forums all the time. This example will show how you can make an item that starts a conversation when you use it. The toolset does not have a way to hook a conversation to an item. Also items have no action queue and so are very passive-inactive objects. They cannot actually participate in a conversation for this reason. So what has to be done is to construct a conversation that a PC can have with himself that appears as though he is talking to the item.
The way a scripter can know if an item is involved in the game is thru the six item related module events that are routed thru the tag-based script. Fortunately, by using the activation related item properties CastSpell Activate Item - 3 of these, CastSpell UniquePower - 2 of these, and OnHitCastSpell UniquePowerOnHit - 1 of these, a tag-based script can respond to in game actions that cause these activations to happen. In the tag-based script, whenever an item gets activated by its possessor it means that the item has one of the CastSpell activation properties on it. As far as I know there is no way to distinguish which activation property was used if the item has more than one of them on it (see the Rod of Targets example below for more about this). In any event, when the item is activated (or Used), the tag-based script will get called from the module's OnActivateItem event handler with the X2_ITEM_EVENT_ACTIVATE event code. This event is the one we will be looking at in this example. Our plan will be to hook this activation event and add some code to make the conversation happen.
The last activation event, OnHitCastSpell, happens when a weapon with that property on it hits something, or when an armor or skin item having the property, worn by a creature, is successfully struck. See the Axe of Mortality and Shield of Cowardice examples below too see how to use that event.
Show the Talking Stone Example
The item we will construct in this example is the talking stone. It will have the Cast Spell Unique Power (Self-Only) item property on it. This allows the PC to use the item without getting a target cursor and it will activate immediately (like the Stone of Recall works). That will make our tag-based script run and we can make a conversation start up. And of course we will be using the X2_ITEM_EVENT_ACTIVATE block in our tag-based script this time so we can catch when the stone is used and respond to the event.
To make the stone itself, copy the blueprint of any item with a base item type of miscellaneous medium. Strip the copy of all item properties and stacking. Set its TAG to "talking_stone", set its name to "Bill", set its resref to "stone_bill", and choose an appearance for it that makes it look like a stone (I like it_midmisc_074 -079). Add the Cast Spell Unique Power (Self-Only) item property to the stone and edit that property so it has Unlimited Uses/Day or 1 Charge and 0 Charges/Use. Save it you're done.
Now we need a conversation to start up. Create one that looks like this:
Show the Conversation
0. [OWNER} - <CUSTOM123> is here
to serve you, my master. What request may I fulfill?
1. Buff me
please.
1a .
[OWNER} - As you wish.
2. I
want to level-up now if you don't mind.
2a.
[OWNER} - Mind? Not at all.
3. Can
you remove my last level-up?
3a.
[OWNER} - I most certainly can!
4. I
have no request. Just wanted to say hello again.
4a.
[OWNER} - Hello back! <CUSTOM123> was getting lonely too. I think I'll glow for an hour or so.
Save the conversation under the name "stone_bill" (same as the resref of our stone, we'll see why later). Since this is not a tutorial on conversations, I'll just give you all the scripts to use. But I will point out that normally when a conversation runs (like with an NPC, placeable, or door) all the ActionTaken, TextAppearsWhen, and Normal/Abort scripts hooked to the conversation will have GetPCSpeaker returning the PC and OBJECT_SELF referring to the NPC/Door/Placeable. When you make a PC have a conversation with himself, OBJECT_SELF will refer to the same guy as GetPCSpeaker does. Therefore your conversation scripts must take this into account when using OBJECT_SELF. Also the message window will show the PC as being the speaker on both sides of the conversation.
Show the Conversation Scripts
// ActionTaken script - Line 1a: As you
wish.
//:://////////////////////////////////////////////////////////
void main()
{ ActionCastSpellAtObject( SPELL_AMPLIFY, OBJECT_SELF,
METAMAGIC_ANY, TRUE, 40,
PROJECTILE_PATH_TYPE_DEFAULT, TRUE);
ActionCastSpellAtObject(
SPELL_AURA_OF_VITALITY, OBJECT_SELF,
METAMAGIC_ANY, TRUE, 40,
PROJECTILE_PATH_TYPE_DEFAULT, TRUE);
ActionCastSpellAtObject( SPELL_CAMOFLAGE,
OBJECT_SELF, METAMAGIC_ANY, TRUE,
40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_CLOAK_OF_CHAOS,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_DEATH_WARD,
OBJECT_SELF, METAMAGIC_ANY, TRUE,
40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_ELEMENTAL_SHIELD,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_ENDURE_ELEMENTS,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_ENERGY_BUFFER,
OBJECT_SELF, METAMAGIC_ANY, TRUE,
40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_ENTROPIC_SHIELD,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_FREEDOM_OF_MOVEMENT,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GLOBE_OF_INVULNERABILITY,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GREATER_BULLS_STRENGTH,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GREATER_CATS_GRACE,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GREATER_EAGLE_SPLENDOR,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GREATER_ENDURANCE,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GREATER_FOXS_CUNNING,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GREATER_MAGIC_WEAPON,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GREATER_OWLS_WISDOM,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GREATER_SPELL_MANTLE,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_GREATER_STONESKIN,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_HASTE,
OBJECT_SELF, METAMAGIC_ANY, TRUE,
40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_IMPROVED_INVISIBILITY,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_KEEN_EDGE,
OBJECT_SELF, METAMAGIC_ANY, TRUE,
40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_LIGHT,
OBJECT_SELF, METAMAGIC_ANY, TRUE,
40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_PROTECTION_FROM_SPELLS,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_RESISTANCE,
OBJECT_SELF, METAMAGIC_ANY, TRUE,
40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionCastSpellAtObject( SPELL_SEE_INVISIBILITY,
OBJECT_SELF, METAMAGIC_ANY,
TRUE, 40, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE);
ActionDoCommand( SetCommandable( TRUE));
SetCommandable( FALSE);
}
// ActionTaken script - Line 2a: Mind? Not at all.
//:://///////////////////////////////////////////////////////////////
void main()
{ int iLevel = GetHitDice(
OBJECT_SELF);
if( iLevel >=
40) return;
SetXP( OBJECT_SELF, ((iLevel +1)
*iLevel *500));
}
// ActionTaken script - Line 3a: I most certainly can.
//::///////////////////////////////////////////////////////////////////
void main()
{ int iLevel = GetHitDice(
OBJECT_SELF);
if( iLevel <=
2) { SetXP( OBJECT_SELF,
0); return;
}
SetXP( OBJECT_SELF, ((iLevel -1)
*(iLevel -2) *500));
}
// ActionTaken script - Line 4a: Hello back! ...
//:://////////////////////////////////////////////////////////
void main()
{ effect eGlow = SupernaturalEffect(
EffectVisualEffect( VFX_DUR_LIGHT_WHITE_20));
ApplyEffectToObject( DURATION_TYPE_TEMPORARY, eGlow,
OBJECT_SELF, HoursToSeconds(
1));
}
Next, on to the tag-based script.. Only one event this time OnActivateItem is needed since there is nothing special that needs to be done when the item is acquired or lost and it cannot be equipped.
Show the Complete Talking Stone tag-based Item Script
// Tag-Based Item Script - Talking Stone
//:://////////////////////////////////////////////////////////
#include "x2_inc_switches"
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_ACTIVATE:
{ // The
item's CastSpell Activate or CastSpell UniquePower was just activated.
object
oItemUser = GetItemActivator();
object
oItem =
GetItemActivated();
object
oTarget = GetItemActivatedTarget();
location
lTarget = (GetIsObjectValid( oTarget) ?
GetLocation( oTarget) : GetItemActivatedTargetLocation());
if( !GetIsObjectValid( oItemUser) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// Make the
stone start up the conversation.
// Save the
item's name into a token we can use in the conversation. Save the talking stone
as an object on the
// PC in case
it is necessary for the conversation scripts to determine which stone was used
or access variables on it.
SetCustomToken( 123, GetName( oItem));
SetLocalObject( oItemUser, "LastTalkingStoneUsed", oItem);
// Figure out the name of the conversation and force the PC to start the
conversation with himself.
string sConversationName = GetResRef( oItem);
AssignCommand(
oItemUser, ActionStartConversation( oItemUser, sConversationName,
TRUE, FALSE));
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
Finally, save the tag-based script under the name "talking_stone" to match the item's TAG, then save everything up and go test it out. When you use it, it runs the "stone_bill" conversation.
Now in the simplest situation you would just have one item and one conversation for it. To accomplish this you could simplify the code in the Activate portion of the tag-based script so that it always uses a specific conversation by setting the variable sConversationName to whatever the name of the conversation is (in this case "stone_bill"). Sometimes its nice to be able to have several different item types or different conversations that could be used. So, for example, you might want to create a Jill stone that uses a different conversation, or a Sam rod.
This is why I chose to make the conversation name match up with the item's resref. By doing it that way, you can create a Jill stone by simply making a copy of the Bill stone's blueprint, setting its resref to "stone_jill", changing its name to Jill, and creating a "stone_jill" conversation. Since the Jill stone will have the same TAG as the Bill stone, they will share the same tag-based script. But since the tag-based script uses the item's resref as the conversation name, the Jill stone will run the stone_jill conversation instead. This allows you to use the one tag-based script to support many different items each having its own special conversation. All you have to do is make sure the item's TAG is "talking_stone" and that it has the Cast Spell Unique Power (Self-Only) item property on it. Make a conversation that matches the item's resref and you're done.
In the last example with the stones, I mentioned that there isn't a way to put two Unique Power item properties on an item and have the tag-based script be able to distinguish which one caused the script to run. One way to solve this is to do like the talking stones example did and make a conversation with a list of things the pc can choose to do and have just one Unique Power property that starts the conversation up. Handy, but not real great in combat since the conversation will probably not appear during combat. Sometimes however you can use the fact that there are item activation methods which allow you to target something (i.e. the Cast Spell Activate Touch, Cast Spell Activate Long-Range, and Cast Spell Unique Power -not self only), to make different unique powers happen depending on what was targeted. This example demonstrates the idea.
Show the Rod of Targets Example
The item will be a rod with CastSpell Activate (long-range) item property
which distinguishes between 4 possible targets as follows:
Target
Function
Ground/Location Sends a 5d6
Fireball to the target spot that is harmful to friends as well as foes.
Self
The rod destroys itself.
Creature
Heals the creature 3d8 points.
Any Other Object Casts a Knock spell
at the object.
Show the Complete Rod of Targets tag-based Item Script
// Tag-Based Item Script - Rod of Targets
//:://////////////////////////////////////////////////////////
#include "x2_inc_switches"
// This function causes a fireball to explode at
the location and damages all living creatures
// in a 5 meter radius with the specified damage.
void IndiscriminateFireballAtLocation( location
lTarget, int nDamage)
{ if( !GetIsObjectValid( GetAreaFromLocation(
lTarget))) return;
// First make the visual effect happen to
make the fireball explosion.
// Try VFX_IMP_FLAME_M also you might like it better.
effect eFireball =
EffectVisualEffect( VFX_FNF_FIREBALL);
ApplyEffectAtLocation( DURATION_TYPE_INSTANT, eFireball,
lTarget);
// Apply fireball damage to all living
creatures in a 5.0 meter blast radius (one tile).
effect eDamage =
EffectLinkEffects( EffectDamage( nDamage, DAMAGE_TYPE_FIRE),
EffectVisualEffect( VFX_COM_HIT_FIRE));
object oAlive
= GetFirstObjectInShape( SHAPE_SPHERE, 5.0f,
lTarget, TRUE);
while( GetIsObjectValid( oAlive))
{ if( (GetCurrentHitPoints( oAlive)
> -10))
ApplyEffectToObject(
DURATION_TYPE_INSTANT, eDamage, oAlive);
oAlive = GetNextObjectInShape(
SHAPE_SPHERE, 5.0f, lTarget,
TRUE);
}
}
// This is the main function for the tag-based script.
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_ACTIVATE:
{ // The
item's CastSpell Activate or CastSpell UniquePower was just activated.
object
oItemUser = GetItemActivator();
object
oItem =
GetItemActivated();
object
oTarget = GetItemActivatedTarget();
location
lTarget = (GetIsObjectValid( oTarget) ?
GetLocation( oTarget) : GetItemActivatedTargetLocation());
if( !GetIsObjectValid( oItemUser) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// Determine which target was chosen
if( !GetIsObjectValid( oTarget))
{
// Must have been a spot on the ground, so put a fireball
there.
AssignCommand( oItemUser, ClearAllActions( TRUE));
AssignCommand( oItemUser, ActionCastFakeSpellAtLocation( SPELL_FIREBALL,
lTarget));
AssignCommand( oItemUser, ActionDoCommand(
IndiscriminateFireballAtLocation( lTarget, d6( 5))));
AssignCommand( oItemUser, ActionDoCommand( SetCommandable(
TRUE, oItemUser)));
AssignCommand( oItemUser, SetCommandable( FALSE,
oItemUser));
}
else if( oTarget == oItemUser)
{
// The fool targeted himself. So destroy the rod with a
fancy visual effect.
effect eCrumble = EffectLinkEffects( EffectVisualEffect(
VFX_FNF_SMOKE_PUFF),
EffectVisualEffect( VFX_COM_CHUNK_STONE_SMALL));
ApplyEffectToObject( DURATION_TYPE_INSTANT, eCrumble, oItemUser);
SetPlotFlag( oItem, FALSE);
SetItemCursedFlag( oItem, FALSE);
DestroyObject( oItem, 0.5f);
SendMessageToPC( oItemUser, "Your rod crumbles
away into dust.");
}
else if( GetObjectType( oTarget) ==
OBJECT_TYPE_CREATURE)
{
// Some creature was selected, so heal him up.
effect eHeal = EffectLinkEffects( EffectHeal( d8( 3)),
EffectVisualEffect( VFX_IMP_HEALING_S));
ApplyEffectToObject( DURATION_TYPE_INSTANT, eHeal, oTarget);
}
else
{
// Must have been a door or placeable or item. Cast a
Knock on it.
AssignCommand( oItemUser, ClearAllActions( TRUE));
AssignCommand( oItemUser, ActionCastSpellAtObject( SPELL_KNOCK,
oTarget, METAMAGIC_ANY, TRUE,
0, PROJECTILE_PATH_TYPE_DEFAULT,
TRUE));
AssignCommand( oItemUser, ActionDoCommand( SetCommandable(
TRUE, oItemUser)));
AssignCommand( oItemUser, SetCommandable( FALSE,
oItemUser));
}
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
Don't forget to save the script under the same name as the TAG of the rod. Not a very complicated technique and it isn't always an appropriate solution but it does often make things easier when you want an item to do multiple things. Of course the way the script distinguishes between targets can be made arbitrarily complex to support lots of different functions. As long as they are all target dependent this technique works well.
This is another frequently asked for item in the scripting forums. It's an item that spawns in some other object when you use it. You can use this idea to give players the ability to summon creatures, create items, or spawn in placeables by using an item. In this example the objective will be to make a wand that players can use to create a temporary portal to teleport them back to town.
Show the Portal Wand Example
Making the wand will be a little more involved than the previous example items because in order to spawn in a portal, you first have to make a blueprint for it. In addition to scripting the wand, the portal has to be scripted to do its part. The plan will be to use the wand's Cast Spell Unique Power property to spawn in a portal (placeable) which teleports players who use it to a waypoint in town. Players will use the wand, target where the portal should appear, and the portal will appear there. They can then use the portal to teleport back to town, and the portal will disappear after a short delay.
Start by creating the town and placing a waypoint in it where the PCs will
arrive when they use the portal. We'll assume the waypoint's TAG is "wp_town_center"
for the purposes of discussion. To make the portal, copy the portal blueprint
from the visual effects category in the placeable's palette. Set it up as
follows:
Name = "Town Portal"
TAG = "town_portal"
Resref = "town_portal
Useable = checked
Plot = checked
OnUsed = "town_portal"
Show the Town Portal OnUsed Event Handler
// OnUsed Event Handler - Town Portal
//::///////////////////////////////////////////////////
// Save under the name "town_portal"
// Put "town_portal" in the OnUsed event slot on the town portal blueprint.
// Mark the portal as Useable and Plot in its blueprint.
// Set the TAG and Resref fields of the portal's blueprint to "town_portal"
// Set the Name field of the portal's blueprint to "Town Portal"
//::///////////////////////////////////////////////////
#include "x0_i0_transport"
void main()
{ // Get the user of the portal and make sure its a valid
PC. The portal won't work for NPCs.
object
oPC = GetLastUsedBy();
if( !GetIsPC( oPC))
return;
// Find and validate the destination
waypoint.
object
oTownCenter = GetWaypointByTag( "wp_town_center");
if( !GetIsObjectValid( oTownCenter))
return;
// Create a visual effect at the destination
using the magic sparks placeable.
object
oVisual = CreateObject( OBJECT_TYPE_PLACEABLE, "plc_magicyellow",
GetLocation( oTownCenter));
// Send the PC to the town center.
TransportToWaypoint( oPC, oTownCenter);
// Make the PC get rid of the magic sparks after he
arrives.
if( GetIsObjectValid( oVisual))
AssignCommand( oPC, ActionDoCommand( DelayCommand(
3.0f, DestroyObject(
oVisual))));
// Lock the PCs action queue to ensure that the action to
destroy the sparks is not interrupted by the player
// clicking his mouse immediately after arriving at the town
center.
AssignCommand( oPC, ActionDoCommand( SetCommandable(
TRUE, oPC)));
AssignCommand( oPC, SetCommandable(
FALSE, oPC));
}
Now that we have a portal all ready to go, its time to focus in on the wand. The only event to be concerned with is the OnActivateItem so the tag-based script should be very simple. The wand needs to have the TAG "town_portal_wand" and the Cast Spell Unique Power item property (not the self-only one). This property allows the user to target something like the long-range one did in the last example with the Rod of Targets. We'll use the location the PC targets and spawn in one of our new Town Portal's there. A timer will also be started so the portal will disappear after a short delay. If the player targets an object, that object's location will be where the portal gets spawned.
Show the Complete Town Portal tag-based Item Script
// Tag-Based Item Script - Portal Wand
//:://////////////////////////////////////////////////
#include "x2_inc_switches"
// This is the main function for the tag-based script.
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_ACTIVATE:
{ // The
item's CastSpell Activate or CastSpell UniquePower was just activated.
object
oItemUser = GetItemActivator();
object
oItem =
GetItemActivated();
object
oTarget = GetItemActivatedTarget();
location
lTarget = (GetIsObjectValid( oTarget) ?
GetLocation( oTarget) : GetItemActivatedTargetLocation());
if( !GetIsObjectValid( oItemUser) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// Make sure a valid location was chosen
if( !GetIsObjectValid( GetAreaFromLocation( lTarget)))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// Spawn in a Town Portal at the location chosen.
object oPortal = CreateObject( OBJECT_TYPE_PLACEABLE,
"town_portal", lTarget);
if( !GetIsObjectValid( oPortal))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// Set up a timer to destroy it after a short delay of 3
rounds.
DelayCommand( RoundsToSeconds( 3), DestroyObject(
oPortal));
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
Save the tag-based script under the name "town_portal_wand" to match the item's TAG and its done. Now your players can get back to town whenever they wish if they can afford the wand. If you're interested in having your portal able to transport people to several destinations, take a look at my Port-Me NPC package in the NWN Vault. You can use it to make an NPC or portal then spawn that in instead of the Town Portal just created in this exercise.
This example illustrates using the OnHit Cast Spell Unique Power (OnHit) item property. It is similar to the activation related item properties from the previous three examples except that instead of being activated by the player "using" the item, it gets activated whenever a hit is scored. When this OnHit property is on a weapon, the tag-based script gets triggered whenever that weapon scores a damaging hit on an opponent (note that the weapon must hit and do damage for the script to fire. If the player misses, or if the opponent has damage reduction or immunity enough to prevent any damage from occurring, then the tag-based script will not be activated due to the OnHit property). If this OnHit property is on an item of armor or helm or shield or skin, the tag-based script gets triggered whenever the player is hit by an opponent and takes damage. When you put the property on other equipment types like say an amulet, I am not sure what the default behavior is (I suspect it works like it does on an armor item in those cases). If you put the property on some other non-equippable item like a gem or a potion or something like that, there will be no effect and the tag-based script will never get activated due to the OnHit property being on that item. So it only makes sense to put this property on an equipment item (one that is equippable).
This item property should be used very sparingly due to its propensity to cause lag. Giving out weapons with this property on them to creatures that have multiple attacks per round (like whirlwind attack), or giving out armor with this property on it to creatures who can be hit multiple times per round, will cause the tag-based script to fire and run many times every round during a battle. This can produce major lag so use it very sparingly.
Show the Axe of Mortality Example
Here's our scenario for this example. We have this boss monster that we want to be particularly difficult to kill. He is going to be set to Immortal. The Immortal flag on a creature makes it so that creature will take damage just like any other, but when he gets down to 1 HP it will be impossible to damage him any more -- thus making him immortal. Our plan is to make a special Axe that will, upon scoring a hit, remove the boss's immortal flag thus allowing him to be destroyed. To ensure that the axe wielder will have to participate in the battle, we will also make our Axe only remove the immortal flag when our boss reaches 90% damage or more, and only after the Axe wielder has scored at least 5 hits on the boss. We don't want our Axe wielder to run up to the boss at the outset of the battle, score one hit, then run away to let the rest of the party take out the boss, we want him to be the major participant in the battle. So he will have to hit the boss at least 5 times and the boss will remain immortal until the 5th (or subsequent) hit from the Axe wielder is scored on the boss when he is near death.
To start off, we will make the boss creature. So pick your favorite monster, give him plenty of hit points, and set his immortal flag on. He also needs to have immunity to death magic. The reason for this is because the Immortal flag does not prevent death magic from killing him. So we don't want some mage to come along, throw one spell, and turn our boss into a pansy. Set the boss's TAG to "immortal_boss" just for discussion purposes.
The next step is to make the Axe. So pick out your favorite axe and give it the OnHit Cast Spell Unique Power (OnHit) item property. Set the Axe's TAG to "mortality_axe" -- remember this is tag-based scripting so our tag-based script will be given the same name "mortality_axe" when it is compiled and saved.
The tag-based script will only have to deal with the OnHitCast event since we have no special stuff that needs to be done when the axe is acquired/unacquired, equipped/unequipped, and it won't have any of the other activation properties on it (see the previous examples for how to use those events). The script will fire whenever the axe scores a hit on any opponent whether it is our special boss or not. So the first thing the script will do is make sure the target is our special boss, otherwise do nothing. Each time the axe scores a hit on our boss, a local integer (stored on the Axe) will be incremented, and if it has hit 5 or more times, the boss's hit points will be checked. If our boss has at least 90% damage at that time, then his immortal flag will be forced off.
The local int used to keep track of Axe hits will be stored on the Axe. This is so that if there are two such axes involved in the battle, each one will have to score 5 hits on its own before the immortality gets removed.. If the local int used to keep track of hits scored were to be saved on the boss instead of the axe, then multiple Axe's of Mortality would share the hits scored making the requirement be that 5 hits scored by any number of axe wielders would be enough to mortalize our boss. For our boss, we will use the former method so that a team of Axe of Mortality wielders will each have to score 5 hits before their axe can mortalize the boss (of course as mentioned previously, you should not allow teams of guys to simultaneously wield OnHitCast weapons due to the lag it will cause).
Whenever our boss is hit by an Axe of Mortality, the axe will be stored as a local object on the boss. When the boss dies we will reset the hit counter on all the axes that have hit him (in his OnDeath script) so that another such boss can be spawned in if desired and the axes won't already have hits attributed to them. Note that this method only allows for one such boss to exist in the module at a time, otherwise an axe wielder could score 3 hits on one boss and 2 on the other and gain his mortalization ability too early. I leave it as an exercise to the reader to work out different methods for supporting multiple axes and multiple bosses. In this example the idea is to have only one axe and one boss -- although it will be coded to handle multiple axes and one boss at a time.
Onto the scripting. We start with the boss's OnDeath script which will reset all the axes that have hit him so that they can be used anew with a respawned boss.
Show the OnDeath Event Handler for the Boss
// Immortal Boss OnDeath Event Handler
//::////////////////////////////////////////////////////
void main()
{ // Clear the hit counters on all axes of mortality that
have scored a hit.
int
nAxesHit = GetLocalInt( OBJECT_SELF,
"NumAxesStored");
// Get the number of axes stored on the boss.
while( --nAxesHit >=
0)
{ object
oStoredAxe = GetLocalObject( OBJECT_SELF,
"AxeThatHit_" +IntToString( nAxesHit +1));
if( GetIsObjectValid(
oStoredAxe )) DeleteLocalInt(
oStoredAxe, "HitsScored");
DeleteLocalInt( OBJECT_SELF,
"AxeThatHit_" +IntToString( nAxesHit +1));
}
DeleteLocalInt( OBJECT_SELF,
"AxesHit");
// Do the default OnDeath processing.
ExecuteScript( "nw_c2_default7",
OBJECT_SELF);
}
Next is the tag-based script. It must verify that the object struck was our special boss and if so, will bump up the axe's hit count, and store the axe on the boss if it isn't already there (so it can be reset later when the boss dies). It also checks to see if the hit count of the axe and the hit points of the boss are such that the immortal flag of the boss should be cleared and if so, it will clear it.
Show the Complete Axe of Mortality tag-based Item Script
// Axe of Mortality tag-based script
//:://////////////////////////////////////////////
#include "x2_inc_switches"
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_ONHITCAST:
{ // The
item's OnHitCastSpell Unique OnHit power was just triggerred.
object oWielder
= OBJECT_SELF;
// weapon wielder/armor wearer
object oTarget
= GetSpellTargetObject(); // weapon target/armor attacker
object oItem
= GetSpellCastItem();
// the item with the OnHitCast property on it.
if( !GetIsObjectValid( oTarget) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// First make sure the target hit was our special immortal boss (TAG = "immortal_boss")
and if not,
// return without doing anything.
if( GetTag( oTarget) !=
"immortal_boss")
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// Bump up the hit count on the axe scoring the hit.
int nHitsScored = GetLocalInt( oItem, "HitsScored")
+1;
SetLocalInt(
oItem, "HitsScored",
nHitsScored);
// Look for the axe object on the boss to see if it isn't already there.
int nNumAxesStored = GetLocalInt( oTarget, "NumAxesStored");
int iStoredAxeNumber =
1;
for( iStoredAxeNumber =
1; (iStoredAxeNumber <= nNumAxesStored);
iStoredAxeNumber++)
{ object
oStoredAxe = GetLocalObject( oTarget,
"AxeThatHit_" +IntToString(
iStoredAxeNumber));
if(
oStoredAxe == oItem) break;
// Found the axe already stored on the boss so break out
of the for loop.
}
// If the axe wasn't found on the boss, then add it to him.
if( iStoredAxeNumber > nNumAxesStored)
{ ++nNumAxesStored;
SetLocalObject( oTarget,
"AxeThatHit_" +IntToString(
nNumAxesStored), oItem);
SetLocalInt( oTarget, "NumAxesStored",
nNumAxesStored);
}
// If the boss has reached 90 percent damage and the axe has scored at least 5
hits on him, clear the boss's immortality.
float fPercentDamage =
1.0f -(IntToFloat( GetCurrentHitPoints( oTarget)) /IntToFloat(
GetMaxHitPoints( oTarget)));
if( (fPercentDamage >=
0.9f) && (nHitsScored >= 5))
SetImmortal( oTarget, FALSE);
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
The way things now stand there is a major exploit with the Axe. An axe wielder can engage in battle with the boss, score his 5 hits and turn the boss mortal, then log out of the module and let his party finish off the boss. Because he takes the axe with him when he logs, it will still have the hit count set on it to 5 or more since it is not available during the boss's OnDeath script and therefore cannot be reset. The player could then re-enter the module and attack the respawned boss as if he had already scored 5 or more hits. To get around this, the module's OnClientEnter script should be modified to reset the hit count on all Axes of Mortality whenever a player joins the module by deleting the local int variable "HitsScored" from the axe. I leave that as an exercise for you to complete. This is only a problem if your boss gets respawned.
Sorry but I never got a chance to test this one out to make sure it works.
This is an item that players will just hate. Builders who often like to say "Muahahaha" will love it. Like the last example it uses the OnHit Cast Spell Unique Power (OnHit) item property. This time, however, it will be placed on a shield in order to illustrate the differences when the OnHit property affects the creature hit instead of the creature doing the hitting (as with a weapon - see the previous example Axe of Mortality). Once again I must remind everyone reading this that the OnHit property can cause some serious lag if overused, so don't go passing out this shield to lots of players or monsters. Every time a creature wielding this shield is hit by any other creature (and takes damage) the tag-based script will fire and run. In a battle against many opponents and/or against opponents with many attacks per round, having this item involved can cause the tag-based script to run numerous times every round -- which will cause lag.
Show the Shield of Cowardice Example
Players wielding the Shield of Cowardice will be forced to make a Will save or become frightened whenever they are hit by any creature and take damage. This item should be interesting to test because if you don't get hit and take damage, then the negative effects programmed on the shield will not affect you. But if you get hit often, then you will have problems if you don't have some sort of immunity from fear given by another source. I suggest making the shield a very good defensive item on its own and give it out to players early. They will assume its a great shield because of all the protection it gives. They will carry it around for awhile and it will work great -- that is until they gain some XP, start meeting tougher opponents that can hit them for damage, and the fear effects start to kick in. It should be a real eye opener for them. The saving throw for the Will save will be vs the creature that scores the hit on the wielder of the shield. Doing this will help preserve the illusion that the shield is a good one because if a lower level opponent gets lucky and scores a hit, the odds are better that the wielder will make his saving throw, not get frightened, and thus not be aware of the negative effects caused by the shield. But when tougher higher level opponents are encountered, the chance of a save diminishes just as the chance of getting hit increases.
Writing the tag-based script for this item will be pretty straight forward. When the script runs (i.e. a hit is scored on the shield bearer), make a Will save and apply a temporary fear effect if they fail. As in the Axe of Mortality example, the only event that will be handled by our tag-based script is the OnHitCast one. To help reduce the lag effects of the item, the wielder will be checked ahead of time to see if he is already frightened and if so, we will avoid the CPU cycles required to make the save and possibly apply another fear effect by not doing them in that situation. If the guy is already frightened we don't want to keep forcing him to make saves and stacking up fear effects on him every time he subsequently gets hit. This would waste precious memory and CPU time to perform something that is already in effect, plus we want to give a frightened guy the chance for the fear to wear off in a reasonable amount of time just to keep things fair for him (or her -- don't wanna be a chauvinist).
Note that in this tag-based script, the part of the script derived from copying the tag-based template script (which is normally just left alone) has been modified slightly (but not changed functionally). The variable names have been altered so that reading the script will make more sense for an armor wearer instead of for a weapon wielder. So the variable called oWielder in the template portion of the script has been changed to oShieldBearer to identify the creature using the shield, and the variable oTarget has been changed to oAttacker in order to more easily identify the creature who scored the hit. This was done only to make the script more readable considering its purpose.
Show the Complete Shield of Cowardice tag-based Item Script
// Shield of Cowardice tag-based script
//:://////////////////////////////////////////////////
#include "x2_inc_switches"
void main()
{ switch( GetUserDefinedItemEventNumber())
{ case X2_ITEM_EVENT_ONHITCAST:
{ // The
item's OnHitCastSpell Unique OnHit power was just triggerred.
object
oShieldBearer
= OBJECT_SELF;
// shield bearer
object oAttacker
= GetSpellTargetObject(); // attacker scoring the
hit
object oItem = GetSpellCastItem();
// the item with the OnHitCast property on it.
if( !GetIsObjectValid( oAttacker) || !GetIsObjectValid(
oItem))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_CONTINUE);
return;
}
// If the shield bearer is already frightened then do nothing.
if( GetHasEffect( EFFECT_TYPE_FRIGHTENED,
oShieldBearer))
{
SetExecutedScriptReturnValue( X2_EXECUTE_SCRIPT_END);
return;
}
// If the shield bearer fails a Will save (DC 12) vs the attacker, then apply a
fear effect to him for 4 rounds.
if( WillSave( oShieldBearer,
12, SAVING_THROW_TYPE_FEAR, oAttacker) ==
0)
{
effect eFear = EffectFrightened();
ApplyEffectToObject( DURATION_TYPE_TEMPORARY, eFear, oShieldBearer,
RoundsToSeconds( 4));
}
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_END);
return;
}
SetExecutedScriptReturnValue(
X2_EXECUTE_SCRIPT_CONTINUE);
}
I'm not real good with scripting saving throws so you might have to tweak the DC of the Will check to balance things out properly. Just as an aside -- this item could very easily be reversed to cause fear against the attacker by making all the references to oShieldBearer (past line 15) say oAttacker and all the oAttacker references (past line 15) say oShieldBearer instead. Reversing those two variables will make the shield cause fear on attackers failing to make a Will save vs the shield bearer when they hit the shield bearer and cause damage.
Still working on this one.
Uses OnAcquire event