Skip to content

Chance that a special item drops in creatures inventory, chosen randomly from the list of items

If I got a list of 50 or so items, how would I make one of them, randomly drop into a creatures inventory after it is killed? So:
1.) Kill creature
2.) "Possible" chance that a special item drops in its inventory, chosen randomly from the list of items.

Comments

  • ProlericProleric Member Posts: 1,281
    There is a ready-made system for this:
    https://nwnlexicon.com/index.php?title=X0_i0_treasure
  • 4BOLTMAIN4BOLTMAIN Member Posts: 90
    This is the best option.
    Proleric wrote: »
    There is a ready-made system for this:
    https://nwnlexicon.com/index.php?title=X0_i0_treasure


    If you are like myself and have a hard time figuring out how to get the NWN treasure scripts to work with custom items you could create a merchant with all 50 of your items and then use the on death script to pick a random item from the merchant. Axe Murderer helped me with this feature on original NWN and I still use it today so the least I can do is pay it forward. You could use any object with inventory but I use merchants for ease of inventory management and they are not visible to other players.


    Here is an example...

    First, create an object with inventory, add your items and then set the tag to "Treasure_Chest_01".
    Second, modify the on death script of the creature with the lines listed at the bottom of the on death script posted below.
    Third, create a new script using the second script posted below and save it as "treasure_01".

    The name of the script called from ExecuteScript in the ondeath code has to match the name of the second script.
    The tag of the object with inventory has to match the tag in the second script.

    This is a modified nw_c2_default7 with the added lines needed at the bottom of the script.
    //:://////////////////////////////////////////////////
    //:: NW_C2_DEFAULT7
    /*
      Default OnDeath event handler for NPCs.
    
      Adjusts killer's alignment if appropriate and
      alerts allies to our death.
     */
    //:://////////////////////////////////////////////////
    //:: Copyright (c) 2002 Floodgate Entertainment
    //:: Created By: Naomi Novik
    //:: Created On: 12/22/2002
    //:://////////////////////////////////////////////////
    //:://////////////////////////////////////////////////
    //:: Modified By: Deva Winblood
    //:: Modified On: April 1st, 2008
    //:: Added Support for Dying Wile Mounted
    //:://///////////////////////////////////////////////
    
    #include "x2_inc_compon"
    #include "x0_i0_spawncond"
    #include "x3_inc_horse"
    
    void main()
    {
        int nClass = GetLevelByClass(CLASS_TYPE_COMMONER);
        int nAlign = GetAlignmentGoodEvil(OBJECT_SELF);
        object oKiller = GetLastKiller();
    
        if (GetLocalInt(GetModule(),"X3_ENABLE_MOUNT_DB")&&GetIsObjectValid(GetMaster(OBJECT_SELF))) SetLocalInt(GetMaster(OBJECT_SELF),"bX3_STORE_MOUNT_INFO",TRUE);
    
    
        // If we're a good/neutral commoner,
        // adjust the killer's alignment evil
        if(nClass > 0 && (nAlign == ALIGNMENT_GOOD || nAlign == ALIGNMENT_NEUTRAL))
        {
            AdjustAlignment(oKiller, ALIGNMENT_EVIL, 5);
        }
    
        // Call to allies to let them know we're dead
        SpeakString("NW_I_AM_DEAD", TALKVOLUME_SILENT_TALK);
    
        //Shout Attack my target, only works with the On Spawn In setup
        SpeakString("NW_ATTACK_MY_TARGET", TALKVOLUME_SILENT_TALK);
    
        // NOTE: the OnDeath user-defined event does not
        // trigger reliably and should probably be removed
        if(GetSpawnInCondition(NW_FLAG_DEATH_EVENT))
        {
             SignalEvent(OBJECT_SELF, EventUserDefined(1007));
        }
        craft_drop_items(oKiller);
    
    //:://////////////////////////////////////////////////
    /*
        Custom Treasure
    
        Change 100 to any number that will work for your goals.
        You can leave it at 100 for testing.
    
        100 = 100% drop rate
        75 = 75% drop rate
        35 = 35% drop rate
    */
    //:://////////////////////////////////////////////////
    if (d100() <= 100) ExecuteScript("treasure_01", OBJECT_SELF);
    }
    


    Save this new script as "treasure_01".
    void main()
    {
    object oItem, oCopy;
    object oChest = GetObjectByTag("Treasure_Chest_01");
    
    int nCount = GetLocalInt(oChest, "itemcount");
    if (!nCount)
        {
        oItem = GetFirstItemInInventory(oChest);
        do
            {
            nCount++;
            oItem = GetNextItemInInventory(oChest);
            }
        while (GetIsObjectValid(oItem));
        SetLocalInt(oChest, "itemcount", nCount);
        }
    
    int nPick = Random(nCount);
    oItem = GetFirstItemInInventory(oChest);
    while (nPick)
       {
       nPick--;
       oItem = GetNextItemInInventory(oChest);
       }
    oCopy = CopyItem(oItem, OBJECT_SELF);
    }
    
  • Ugly_DuckUgly_Duck Member Posts: 182
    Thanks a load 4BOLTMAIN !! This worked great!
  • 4BOLTMAIN4BOLTMAIN Member Posts: 90
    Credit goes to Axe Murderer. Not sure if he is still around but I have several levels of loot and it is easily controlled with this system. You can add more checks than just the % like players level, creatures area and even multiple chests.

    Glad it worked out for you.
  • Ugly_DuckUgly_Duck Member Posts: 182
    edited April 2022
    How could I do all that? I think I'd need to make several custom OnDeath scripts for each creature and follow the same pattern as the originals you showed me?

    Any ideas?
  • 4BOLTMAIN4BOLTMAIN Member Posts: 90
    I only use one ondeath script. I check for the area that the creature is in to determine treasure level. I check for the killers level to control the treasure level.

    For my areas I use the tag to simplify things.
    'Goblin_Dungeon_1'
    'Bugbear_Dungeon_2'
    'Dragon_Dungeon_4'
    I then get the right string of the tag and do a switch statement

    case 1: //player is in level 1 dungeon etc.

    I have maybe 7 or 8 levels of treasure on my module so obviously that many containers and scripts but yet only one ondeath script.

    So my containers for my system are tagged Drop_Chest_1, Drop_Chest_2, etc. then I have a script for each chest, drop_chest_01, drop_chest_02, etc. I then just execute the script for the treasure intended. I don't need to check the creatures tag since I know what creatures are in what area.

    My ondeath script does check the tag of the creature to see if it is a boss or if a certain key needs to be created but that's something I can explain later if needed.

    Here are a couple examples from my module.
            switch(iNum)
                {
                case 1:  // player is in dungeon 1   25% total item drop rate
                if (Random (1000) <= 120) ExecuteScript ("drop_chest_randm",oPC);
                if (Random (1000) <= 110) ExecuteScript ("drop_chest_01",oPC);
                if (Random (1000) <= 20) ExecuteScript ("drop_chest_02",oPC);
                break;
    

    and then an example of a higher dungeon check, iHD is the players level
                case 4:  // player is in dungeon 4   18% total item drop rate
                if (Random (1000) <= 25) ExecuteScript ("drop_chest_randm",oPC);
                if (Random (1000) <= 25) ExecuteScript ("drop_chest_02",oPC);
                if (Random (1000) <= 35) ExecuteScript ("drop_chest_03",oPC);
                if (Random (1000) <= 50 && iHD >= 7) ExecuteScript ("drop_chest_04",oPC);
                if (Random (1000) <= 15 && iHD >= 14) ExecuteScript ("drop_chest_uniqu",oPC);
                if (Random (1000) <= 15 && iHD >= 14) ExecuteScript ("drop_chest_skt",oPC);
                if (Random (1000) <= 15 && iHD >= 14) ExecuteScript ("drop_chest_set",oPC);
                break;
    

    As you can see I do things a little different in my module than the example I posted, a drop on my module will go directly into the killers inventory so they don't have to click on a body or bag, they don't have to worry about someone else taking their loot and it just keeps the module fast paced as intended. With so many different levels of loot I use random(1000) rather than d(100) I have a spreadsheet with all the area rates and then it also lists each areas total rate for easy reference. Those numbers look pretty low for the checks in dungeon 4 but the items actually drop a lot more than you would think.

    If a players level isn't 7 than the dungeon 4 rate isn't exactly 18%... then again a level 6 player probably wont be in dungeon 4 so I don't worry about it.

    Let me know if you have any more questions,
  • Ugly_DuckUgly_Duck Member Posts: 182
    Wow thats cool stuff. You gave me the idea of having the special items drop in the same randomness that you originally showed me a post or so back. How do I get that same randomness, but have the special items drop in the players inventory?
  • 4BOLTMAIN4BOLTMAIN Member Posts: 90
    edited April 2022
    Most of my items come from the drop chests. I do have 2 crafting items that I want to be specific to 2 bosses so I check for their tag and if d10() > 5 then I just do CreateItemOnObject.

    Most of my dungeons have locked doors to prevent players running straight to the boss. All of the locked doors take the same key so all the creatures I want to drop a key have the tag set to DKEY_creaturename. The script checks the right string of the tag and creates the key when needed.

    I understand I could just put the key in their inventory but there was some issues with players going invisible and pickpocketing the key. What happens then is the next player or party running the dungeon doesn't get the key and it causes issues... "Hey, this dungeon is broken, this creature isn't dropping the key" and then on top of that they say it in shout for everyone to see... you get the point, lol.
  • 4BOLTMAIN4BOLTMAIN Member Posts: 90
    edited April 2022
    Ugly_Duck wrote: »
    Wow thats cool stuff. You gave me the idea of having the special items drop in the same randomness that you originally showed me a post or so back. How do I get that same randomness, but have the special items drop in the players inventory?

    // Get the object that killed the caller.
    object GetLastKiller()

    object oPC = GetLastKiller();

    Just change OBJECT_SELF to oPC where the ondeath calls the ExecuteScript.
    Post edited by 4BOLTMAIN on
  • Ugly_DuckUgly_Duck Member Posts: 182
    This worked good, thank you. But when I added, the "object GetLastKiller()", it wouldn't compile. I didn't know where to add it, so I put it right above, "if (d100() <= 100) ExecuteScript("treasure_01", OBJECT_SELF);" in the ondeath script. I don't know if there will be bugs with it left the way I have it though. Here is a copy of the modified OnDeath script. Just something to look at, thats all.
    //:://////////////////////////////////////////////////
    //::
    // MODIFIED: NW_C2_DEFAULT7
    // Random Loot Generator Addition
    /*
      Default OnDeath event handler for NPCs.
    
      Adjusts killer's alignment if appropriate and
      alerts allies to our death.
     */
    //:://////////////////////////////////////////////////
    //:: Copyright (c) 2002 Floodgate Entertainment
    //:: Created By: Naomi Novik
    //:: Created On: 12/22/2002
    //:://////////////////////////////////////////////////
    //:://////////////////////////////////////////////////
    //:: Modified By: Deva Winblood
    //:: Modified On: April 1st, 2008
    //:: Added Support for Dying Wile Mounted
    //:://///////////////////////////////////////////////
    
    #include "x2_inc_compon"
    #include "x0_i0_spawncond"
    #include "x3_inc_horse"
        #include "_kb_loot_corpse"
    void main()
    {
        int nClass = GetLevelByClass(CLASS_TYPE_COMMONER);
        int nAlign = GetAlignmentGoodEvil(OBJECT_SELF);
        object oKiller = GetLastKiller();
    
        if (GetLocalInt(GetModule(),"X3_ENABLE_MOUNT_DB")&&GetIsObjectValid(GetMaster(OBJECT_SELF))) SetLocalInt(GetMaster(OBJECT_SELF),"bX3_STORE_MOUNT_INFO",TRUE);
    
    
        // If we're a good/neutral commoner,
        // adjust the killer's alignment evil
        if(nClass > 0 && (nAlign == ALIGNMENT_GOOD || nAlign == ALIGNMENT_NEUTRAL))
        {
            AdjustAlignment(oKiller, ALIGNMENT_EVIL, 5);
        }
    
        // Call to allies to let them know we're dead
        SpeakString("NW_I_AM_DEAD", TALKVOLUME_SILENT_TALK);
    
        //Shout Attack my target, only works with the On Spawn In setup
        SpeakString("NW_ATTACK_MY_TARGET", TALKVOLUME_SILENT_TALK);
    
        // NOTE: the OnDeath user-defined event does not
        // trigger reliably and should probably be removed
        if(GetSpawnInCondition(NW_FLAG_DEATH_EVENT))
        {
             SignalEvent(OBJECT_SELF, EventUserDefined(1007));
        }
        craft_drop_items(oKiller);
         LeaveCorpse();
     // below test
    //:://////////////////////////////////////////////////
    /*
        Custom Treasure
    
        Change 100 to any number that will work for your goals.
        You can leave it at 100 for testing.
    
        100 = 100% drop rate
        75 = 75% drop rate
        35 = 35% drop rate
    */
    //:://////////////////////////////////////////////////
    
    //
    // Get the object that killed the caller.
    
    
    object oPC = GetLastKiller();  //TEST CODE for dropping stuff in the players inventory. Comment out this  line to revert back to dropping in the creatures inventory.
    if (d100() <= 4) ExecuteScript("treasure_01", oPC);  // Change "oPC" back to "OBJECT_SELF" to reset the TEST CODE.
    }
    
    As is, it still works fine. But I'm not sure if there will be negative setbacks due to the lack of said lines of code.
  • meaglynmeaglyn Member Posts: 149
    You've already got the last killer in oKiller. You can remove the object oPC =... line and put oKiller in place of oPC in the ExecuteScript line.

    The "missing" lins from 4BOLTMAIN was not part of the code you needed to add. That was showing the name and short description of the function to use. You don't need that in the script.
  • 4BOLTMAIN4BOLTMAIN Member Posts: 90
    Sorry guys, it was late when I responded and I should have looked at the script again.

    meaglyn is right. oKiller is already called out in the original script so just change OBJECT_SELF to oKiller where ExecuteScript is.

    [code]
    //:://////////////////////////////////////////////////
    //::
    // MODIFIED: NW_C2_DEFAULT7
    // Random Loot Generator Addition
    /*
    Default OnDeath event handler for NPCs.

    Adjusts killer's alignment if appropriate and
    alerts allies to our death.
    */
    //:://////////////////////////////////////////////////
    //:: Copyright (c) 2002 Floodgate Entertainment
    //:: Created By: Naomi Novik
    //:: Created On: 12/22/2002
    //:://////////////////////////////////////////////////
    //:://////////////////////////////////////////////////
    //:: Modified By: Deva Winblood
    //:: Modified On: April 1st, 2008
    //:: Added Support for Dying Wile Mounted
    //:://///////////////////////////////////////////////

    #include "x2_inc_compon"
    #include "x0_i0_spawncond"
    #include "x3_inc_horse"
    #include "_kb_loot_corpse"

    void main()
    {
    int nClass = GetLevelByClass(CLASS_TYPE_COMMONER);
    int nAlign = GetAlignmentGoodEvil(OBJECT_SELF);
    object oKiller = GetLastKiller();

    if (GetLocalInt(GetModule(),"X3_ENABLE_MOUNT_DB")&&GetIsObjectValid(GetMaster(OBJECT_SELF))) SetLocalInt(GetMaster(OBJECT_SELF),"bX3_STORE_MOUNT_INFO",TRUE);


    // If we're a good/neutral commoner,
    // adjust the killer's alignment evil
    if(nClass > 0 && (nAlign == ALIGNMENT_GOOD || nAlign == ALIGNMENT_NEUTRAL))
    {
    AdjustAlignment(oKiller, ALIGNMENT_EVIL, 5);
    }

    // Call to allies to let them know we're dead
    SpeakString("NW_I_AM_DEAD", TALKVOLUME_SILENT_TALK);

    //Shout Attack my target, only works with the On Spawn In setup
    SpeakString("NW_ATTACK_MY_TARGET", TALKVOLUME_SILENT_TALK);

    // NOTE: the OnDeath user-defined event does not
    // trigger reliably and should probably be removed
    if(GetSpawnInCondition(NW_FLAG_DEATH_EVENT))
    {
    SignalEvent(OBJECT_SELF, EventUserDefined(1007));
    }
    craft_drop_items(oKiller);
    LeaveCorpse();

    //:://////////////////////////////////////////////////
    /*
    Custom Treasure

    Change 100 to any number that will work for your goals.
    You can leave it at 100 for testing.

    100 = 100% drop rate
    75 = 75% drop rate
    35 = 35% drop rate
    */

    if (d100() <= 4) ExecuteScript("treasure_01", oKiller);
    }
    [\code]
  • Ugly_DuckUgly_Duck Member Posts: 182
    Any chance you could help me with another issue I'm having please? Didn't wanna clutter up the forum with another post so I'll ask in this one.

    Goal: Have a few particular items drop into the players inventory when logging into the starting area. Only give them these items 1 time. This is the script I got so far:
    // ** Gives a Stone of Recall to player on entering this area
    //Put this script OnEnter
    //** #include "nw_i0_tool" is for giving gold to player on enter of starting area.
    #include "nw_i0_tool"
    void main()
    {
        object oPC = GetEnteringObject();
        if(!GetIsPC(oPC))
        {
            return;
        }
        if(GetItemPossessedBy(oPC, "NW_IT_RECALL") == OBJECT_INVALID) // Making recall stone only drop once.
        {
            CreateItemOnObject("nw_it_recall", oPC);
            CreateItemOnObject("intro_book_001_c", oPC);
            CreateItemOnObject("_recall_book_con", oPC);
            //RewardPartyGP(50, oPC, TRUE);
    
    
     //** ETHER STONE STUFF IS BELOW. Gives an Ether Stone to select caster classes.
        if ((GetLevelByClass(CLASS_TYPE_BARD, oPC)==0)&&
    
        (GetLevelByClass(CLASS_TYPE_DRUID, oPC)==0)&&
        (GetLevelByClass(CLASS_TYPE_SORCERER, oPC)==0)&&
        (GetLevelByClass(CLASS_TYPE_WIZARD, oPC)==0))
             return;
        CreateItemOnObject("_ether_stone_01", oPC); //ETHER STONE STUFF
    
        }
    
    
    }
    

    The issue:
    I made all the items undroppable, other than the recall stone (nw_it_recall). I couldn't make the recall stone undroppable because I'd have to make a copy of it and the recall function seems to not work anymore with a copy of the original (can't figure out how to do this also). So, if a player keeps all the items in their inventory, when they log in again, everything is fine. But, if they drop the recall stone, when they log back in, they get the recall stone back, but also more copies of the other items.

  • ProlericProleric Member Posts: 1,281
    edited April 2022
    You need to make each item creation conditional on not possessing that item, e.g.
    if(GetItemPossessedBy(oPC, "intro_book_001_c")  == OBJECT_INVALID) 
      CreateItemOnObject("intro_book_001_c", oPC);
    

    There is a simpler wrapper function HasItem() in nw_i0_tool which does the same thing.

    See also https://nwnlexicon.com/index.php?title=Grimlar_-_Guide_To_The_Stone_Of_Recall to get the stone working.
  • Ugly_DuckUgly_Duck Member Posts: 182
    Thanks Proleric, this worked great! Here is my new script, with a new Recall Stone that is flagged as undroppable (which fixed the whole item replication stuff:
    // ** Gives a Stone of Recall to player on entering this area
    //Put this script OnEnter
    //** #include "nw_i0_tool" is for giving gold to player on enter of starting area.
    #include "nw_i0_tool"
    void main()
    {
        object oPC = GetEnteringObject();
        if(!GetIsPC(oPC))
        {
            return;
        }
        //if(GetItemPossessedBy(oPC, "NW_IT_RECALL") == OBJECT_INVALID) // Making recall stone only drop once.
        if(GetItemPossessedBy(oPC, "intro_book_001_c")  == OBJECT_INVALID) //Drops Intro Book once.
        if(GetItemPossessedBy(oPC, "_nw_it_recall_02")  == OBJECT_INVALID) //Drops NEW Recall Stone once.
        if(GetItemPossessedBy(oPC, "_recall_book_con")  == OBJECT_INVALID) //Drops Recall Book once.
        {
            CreateItemOnObject("_nw_it_recall_02", oPC);
            CreateItemOnObject("intro_book_001_c", oPC);
            CreateItemOnObject("_recall_book_con", oPC);
            //RewardPartyGP(50, oPC, TRUE);
    
    
     //** ETHER STONE STUFF IS BELOW. Gives an Ether Stone to select caster classes.
        if ((GetLevelByClass(CLASS_TYPE_BARD, oPC)==0)&&
    
        (GetLevelByClass(CLASS_TYPE_DRUID, oPC)==0)&&
        (GetLevelByClass(CLASS_TYPE_SORCERER, oPC)==0)&&
        (GetLevelByClass(CLASS_TYPE_WIZARD, oPC)==0))
             return;
        CreateItemOnObject("_ether_stone_01", oPC); //ETHER STONE STUFF
    
        }
    
    
    }
    
    
  • ProlericProleric Member Posts: 1,281
    Not quite what I meant. In pseudocode,
    If I don't have X create X.
    If I don't have Y create Y.
    If I don't have Z create Z.
    

    There's no need for "if X if Y if Z then create X Y Z" which will only work if all the items are missing.
Sign In or Register to comment.