GetLocalInt from GetPCItemLastEquipped

I’m trying to get a local int from an item that was last equipped with a specific tag. I’m having a hard time figuring out how to do that!

I’ve tried:
object oItem = GetPCItemLastEquipped();
object oItemx = GetTag(oItem, “item_tag”);
if ( GetLocalInt(oItemx, “INT_STUFF”) == 1 )

Any help would be great, I know I’m overlooking something simple!

:mag::eye:

This is currently trying to define object oItemx via a function that retrieves a string, specifically the tag of object oItem. But it’s also trying to pass a second parameter to GetTag, and GetTag does not have a second parameter, so that’s not actually a check for what the tag is going on, there, either.

Fixed:

    object oItem = GetPCItemLastEquipped();
    if (GetTag(oItem) == "item_tag" && GetLocalInt(oItem, "INT_STUFF") == 1)

It’s basically like a shape puzzle, or a bunch of lego blocks. Each shape only fits into it’s matching hole, and/or each lego piece can only be combined with a matching one. You can see which data type a function returns and which ones it takes as parameters in the toolset, when you highlight it:

image

Lexicon category for data types: https://nwnlexicon.com/index.php?title=Category:Data_Types

3 Likes

Thanks for this!

Here’s what I’m trying to do, the code didn’t work as I was hoping though.

When a player equips a crossbow (which has unlimited ammo), the script is suppose to remove the properties of that ammo and add +1 cold damage instead.

#include “x2_inc_itemprop”
void main()
{
object oItem;
object oItemx = GetPCItemLastEquipped();
object oEquippedBy = GetPCItemLastEquippedBy();

object oPC = oEquippedBy;

itemproperty COLD1 = ItemPropertyDamageBonus(IP_CONST_DAMAGETYPE_COLD,IP_CONST_DAMAGEBONUS_1);

if ( "iron035" == GetTag(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC)) )
{
    if ( "NW_WAMBO001" == GetTag(GetItemInSlot(INVENTORY_SLOT_BOLTS, oPC)) )
    {
        oItem = GetItemPossessedBy(oPC, "NW_WAMBO001");
        if (GetTag(oItemx) == "iron035" && GetLocalInt(oItem, "COLD_AMMO") == 1)
        {
        IPRemoveAllItemProperties(oItem, DURATION_TYPE_PERMANENT);
        IPSafeAddItemProperty(oItem, COLD1, 0.0f, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING);
        }            
    }
}

}

I have it working this way, but if a player has another crossbow in their inventory also with the same int, then the script doesn’t work. It only works if the crossbow being equipped is the only crossbow in the player’s inventory.

#include “x2_inc_itemprop”
void main()
{
object oItem;
object oItemx;
object oEquippedBy = GetPCItemLastEquippedBy();

object oPC = oEquippedBy;

itemproperty COLD1 = ItemPropertyDamageBonus(IP_CONST_DAMAGETYPE_COLD,IP_CONST_DAMAGEBONUS_1);

if ( "iron035" == GetTag(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC)) )
{
    if ( "NW_WAMBO001" == GetTag(GetItemInSlot(INVENTORY_SLOT_BOLTS, oPC)) )
    {
        oItem = GetItemPossessedBy(oPC, "NW_WAMBO001");
        oItemx = GetItemPossessedBy(oPC, "iron035");
        if ( GetLocalInt(oItemx, "COLD_AMMO") == 1 )
        {
        IPRemoveAllItemProperties(oItem, DURATION_TYPE_PERMANENT);
        IPSafeAddItemProperty(oItem, COLD1, 0.0f, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING);
        }
    }
}

}

I need to figure out how to make it check only the crossbow being equipped (oItemx).

1 Like

Try something like this instead:

void main()
{
    object oEquippedBy = GetPCItemLastEquippedBy();
    object oPC = oEquippedBy;
    object oPrimarySlotItem = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC);
    object oBoltSlotItem = GetItemInSlot(INVENTORY_SLOT_BOLTS, oPC);

    itemproperty COLD1 = ItemPropertyDamageBonus(IP_CONST_DAMAGETYPE_COLD,IP_CONST_DAMAGEBONUS_1);

    if ( "iron035" == GetTag(oPrimarySlotItem) )
    {
        if ( "NW_WAMBO001" == GetTag(oBoltSlotItem) )
        {
            if ( GetLocalInt(oPrimarySlotItem, "COLD_AMMO") == 1 )
            {
                IPRemoveAllItemProperties(oBoltSlotItem, DURATION_TYPE_PERMANENT);
                IPSafeAddItemProperty(oBoltSlotItem, COLD1, 0.0f, X2_IP_ADDPROP_POLICY_REPLACE_EXISTING);
            }
        }
    }
}
2 Likes

OK, if the aim is to let you set up custom Unlimited Ammunition types on individual items, I think I’d suggest altering this whole setup a little bit.

I’ve excessively commented this for you for TEH EDUMACASHUNS. Not the solution I recommend ultimately, because (reasons below), but read this:

A script to be hooked into the module OnPlayerEquipItem event.
    // This is a constant integer, turning a number (hard to memorize!) into readable text.
    // A constant cannot be altered from within other functions, but it can be read by them.
    // If you set up constants in a library script, you can #include that library into other scripts, and reuse the constants.
    const int UNLIMITED_AMMO_TYPE_COLD_PLUS_1 = 1;

void main()
{
    object oPC   = GetPCItemLastEquippedBy();  // The player who is equipping the item.
    object oItem = GetPCItemLastEquipped();    // The item that is being equipped.

    // A local int is checked on the item that is being equipped.
    int nAmmoType = GetLocalInt(oItem, "UNLIMITED_AMMUNITION_TYPE");

    // If that local int is not zero, we proceed.
    // Otherwise, the script ends here, since it doesn't contain anything beyond this check at it's root.
    if (nAmmoType)
        {
        // If the item does not have the Unlimited Ammunition item property set on it, abort the script.
        if (!GetItemHasItemProperty(oItem, ITEM_PROPERTY_UNLIMITED_AMMUNITION))
            return;

        // Determine ammunition item depending on baseitemtype of equipped item:
        object oAmmunition;
        switch (GetBaseItemType(oItem))
            {
            // If it's either a light or a heavy crossbow, it uses bolts.
            case BASE_ITEM_LIGHTCROSSBOW:
            case BASE_ITEM_HEAVYCROSSBOW:
                oAmmunition = GetItemInSlot(INVENTORY_SLOT_BOLTS, oPC);
                break;
            // If it's either a short- or a longbow, it uses arrows.
            case BASE_ITEM_LONGBOW:
            case BASE_ITEM_SHORTBOW:
                oAmmunition = GetItemInSlot(INVENTORY_SLOT_ARROWS, oPC);
                break;
            // If it's a sling, it uses bullets.
            case BASE_ITEM_SLING:
                oAmmunition = GetItemInSlot(INVENTORY_SLOT_BULLETS, oPC);
                break;
            }

        // Remove all item properties from the ammunition item.
        // First, check the first item property on the item:
        itemproperty ipRemove = GetFirstItemProperty(oAmmunition);
        // As long as ipRemove is a valid item property, do the following:
        while (GetIsItemPropertyValid(ipRemove))
            {
            // Remove the item property from the target.
            RemoveItemProperty(oAmmunition, ipRemove);

            // Alter ipRemove, to check the next item property on the item.
            ipRemove = GetNextItemProperty(oAmmunition);

            // Once GetNextItemProperty no longer returns a valid item property,
            // the condition of the while loop is no longer being met, so the while loop ends.
            }

        // Determine item properties to add depending on UNLIMITED_AMMUNITION_TYPE value:
        itemproperty ipAdd;
        switch (nAmmoType)
            {
            // Note that the constant we set up at the top of the script is being used here:
            case UNLIMITED_AMMO_TYPE_COLD_PLUS_1:
                ipAdd = ItemPropertyDamageBonus(IP_CONST_DAMAGETYPE_COLD, IP_CONST_DAMAGEBONUS_1);
                break;

            // In theory, more different custom unlimited ammo types should go in here.
            // That's the only thing that justifies the switch case statement here. If there
            // is only a single custom unlimited ammo type, then this entire step is unnecessary.
            }

        // Add the new item property.
        AddItemProperty(DURATION_TYPE_PERMANENT, ipAdd, oAmmunition);
        }
}

^- This variant goes with the “Modify the entire OnPlayerEquipItem event modulewide”-route that’s currently going on. In this case, you have a single local variable set on the item, which should work for any ranged weapon that has the Unlimited Ammunition item property on it. Every item that is being equipped by a player is checked for whether it possesses this local variable. If it doesn’t have the variable, nothing further happens, so that’s relatively unobtrusive.

Reasons why this may not be a good idea: Local vars on items aren’t persistent past character export in singleplayer, though. :thinking: Also, it opens up potential for a console cheat in singleplayer, since players can add local vars to items via the console, there. So, I don’t think this is actually the best route to go.


If you very specifically want to set up a special item, identified by it’s unique tag, that has a custom unlimited ammunition type, then I vote for tag-based scripting:

Set up a script named to match the tag of the custom crossbow.
#include "x2_inc_switches"

void main()
{
    // If the event that has caused this tag-based item event to trigger is
    // the equip-item event:
    if (GetUserDefinedItemEventNumber() == X2_ITEM_EVENT_EQUIP)
        {
        // Safety check in case the crossbow loses it's Unlimited Ammunition item property by some means:
        // If the item does not have the Unlimited Ammunition item property set on it, abort the script.
        if (!GetItemHasItemProperty(GetPCItemLastEquipped(), ITEM_PROPERTY_UNLIMITED_AMMUNITION))
            return;

        // Find the object that equipped the item, and their bolt ammunition (which will have been automatically created via the Unlimited Ammunition itemproperty):
        object oPC = GetPCItemLastEquippedBy();
        object oBolts = GetItemInSlot(INVENTORY_SLOT_BOLTS, oPC);

        // Remove the current itemproperties from the ammo.
        itemproperty ipRemove = GetFirstItemProperty(oBolts);
        while (GetIsItemPropertyValid(ipRemove))
            {
            RemoveItemProperty(oBolts, ipRemove);
            ipRemove = GetNextItemProperty(oBolts);
            }

        // Add the new itemproperty to the ammo.
        itemproperty ipAdd = ItemPropertyDamageBonus(IP_CONST_DAMAGETYPE_COLD, IP_CONST_DAMAGEBONUS_1);
        AddItemProperty(DURATION_TYPE_PERMANENT, ipAdd, oBolts);
        }
}

Tag-based scripting is activated by default, these days. If you have an item with a custom tag and want it to behave in special ways, you just need to set up a script for that tag, include x2_inc_switches, check for which event is being triggered, and then put in the code for that event.

This route will be persistent across singleplayer character export. I think it might even be persistent across different modules if the tagbased item script is exported and installed via the override folder - if only within modules in which tagbased scripting is activated.


Also, to try to explain what was happening with the “It only works if there is only a single crossbow in the player’s inventory”-thing: https://nwnlexicon.com/index.php/GetItemPossessedBy

GetPCItemEquippedBy() is the one if you want to reliably identify the item that has just been equipped, thereby triggering the OnPlayerEquipItem event. Trusting the Lexicon on this one - GetItemPossessedBy() apparently scans through the player’s inventory before it scans through the player’s equipment, so it should never be able to find the crossbow you just equipped if the player has another item with the same tag sitting in their inventory.

It’s odd that the script doesn’t work with the second crossbow in the inventory, if the second crossbow has the local int set on it too, though; the if (GetLocalInt(oItemx, “COLD_AMMO”) == 1)-check that leads to the item properties of the ammunition being altered should also equal TRUE if the local int is set on the crossbow in the inventory, as opposed to on the crossbow that is being equipped. Wild guess: Are you reusing an exported character in singleplayer? That way around, you might have one crossbow that has the variable set on it, and one that doesn’t - and if you equip the one that has the variable set on it, the script finds the one that doesn’t have the variable set on it, and then the check equals FALSE.

That’s my guess for what’s going on there, anyway. :thinking:

@Orth also had a very good thought there that warrants being put into words explicitly, so it doesn’t go overlooked: Descriptive variable names are your friend! Try to stick to descriptive variable names when you can, especially if you’re still learning the ropes. You’ll thank yourself for building up that habit later on, if you have to backtrack and edit your own code once some time has passed and you’ve forgotten what exactly is going on in it. x_X

1 Like

I’ll try this tonight!

And thank you! This is some good reading and gives me a lot to think about!

Update, I got everything working as needed. Also had to put another script together for when players enter the module.

Thank you both again!