Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Categories

Dark Dreams of Furiae - a new module for NWN:EE! Buy now
Attention, new and old users! Please read the new rules of conduct for the forums, and we hope you enjoy your stay!

Script Weird Insanity

HawkwinterHawkwinter Member Posts: 14
edited April 2019 in Builders - Scripting
So, I'm trying to set up a fairly basic "On Enter" script. It should strip out everything the player is carrying, give them 3000gp, 10 potions, a torch, a set of commoner's clothes, and then set their level to 3.

I'm getting all kinds of weirdness though, and it's proving very difficult to debug. From what's showing up in the console, commands are often being run out of order, and it throws errors when I start trying to add "DelayCommand" instructions to some of the functions I'm calling.

Frequently, it would give the gold before removing the gold they walked in with, and then remove everything. I'm not new to programming (but I am somewhat rusty), and it's been like 15 years since I've touched NWScript, but I can't understand why I'm getting all of this weirdness.

What could be causing this weirdness?

{Edit: After a bunch of fiddling around I have made partial progress. But still. It's only creating 3 potions, not 10 (tried it as 1 command and as a for loop), and it's not running "CraftSupplies(oPC);", even though that function works fine when I call it on a rest.}
//::///////////////////////////////////////////////
//:: Default On Enter for Module
//:: x3_mod_def_enter
//:: Copyright (c) 2008 Bioware Corp.
//:://////////////////////////////////////////////
/*
     This script adds the horse menus to the PCs.
*/
//:://////////////////////////////////////////////
//:: Created By: Deva B. Winblood
//:: Created On: Dec 30th, 2007
//:: Last Update: April 21th, 2008

//:: Updated By: Valas
//:: First Updated: 2019-04-27
//:: Last Update: 2019-04-27
//:://////////////////////////////////////////////

#include "x3_inc_horse"
#include "1x_craftsupplies"

int BaseGear(object oPC){
/******************************
 * This script clears the inventory of the entering player character.
 * It is suitable for use in an OnEnter or OnClientEnter event.
 *******************************/
    int nSlot, nCount, nVentory = 0;
    // Make sure oPC is a PC (not necessary in a module's OnClientEnter).
    if ( !GetIsPC(oPC) )
        return 0;

    // Destroy the items in the main inventory.
    object oItem = GetFirstItemInInventory(oPC);
    while ( oItem != OBJECT_INVALID ) {
        nVentory = 1;
        DestroyObject(oItem);
        oItem = GetNextItemInInventory(oPC);
    }
    // Destroy equipped items.
    for ( nSlot = 0; nSlot < NUM_INVENTORY_SLOTS; ++nSlot ){
        DestroyObject(GetItemInSlot(nSlot, oPC));
    }

    // Remove all gold.
    AssignCommand(oPC, TakeGoldFromCreature(GetGold(oPC), oPC, TRUE));

    //Give 3000 Gold to Spend.
    DelayCommand(4.0, GiveGoldToCreature(oPC, 3000));


    object oCloth = CreateItemOnObject("nw_cloth022", oPC, 1);
    DelayCommand(0.5, AssignCommand(oPC, ActionEquipItem(oCloth, INVENTORY_SLOT_CHEST)));

    //Give 3 healing potions, a torch, and some rags.
    CreateItemOnObject("nw_it_torch001", oPC, 1);
    //DelayCommand(0.2, AssignCommand(oPC, ActionSpeakString("Torch")));

    for (nCount = 0; nCount < 10; ++nCount) {
        CreateItemOnObject("nw_it_mpotion001", oPC, 1);
    }
    //DelayCommand(0.2, AssignCommand(oPC, ActionSpeakString("10 Healing Potions")));

    CraftSupplies(oPC);

    return 1;
}

void main()
{
    object oPC=GetEnteringObject();
    ExecuteScript("x3_mod_pre_enter",OBJECT_SELF); // Override for other skin systems
    if ((GetIsPC(oPC)||GetIsDM(oPC))&&!GetHasFeat(FEAT_HORSE_MENU,oPC))
    { // add horse menu
        HorseAddHorseMenu(oPC);
        if (GetLocalInt(GetModule(),"X3_ENABLE_MOUNT_DB"))
        { // restore PC horse status from database
            DelayCommand(2.0,HorseReloadFromDatabase(oPC,X3_HORSE_DATABASE));
        } // restore PC horse status from database
    } // add horse menu
    if (GetIsPC(oPC))
    { // more details
        // restore appearance in case you export your character in mounted form, etc.
        if (!GetSkinInt(oPC,"bX3_IS_MOUNTED")) HorseIfNotDefaultAppearanceChange(oPC);
        // pre-cache horse animations for player as attaching a tail to the model
        HorsePreloadAnimations(oPC);
        DelayCommand(3.0,HorseRestoreHenchmenLocations(oPC));
        // Strip PC Gear
        BaseGear(oPC);

        //Raise Level to 3
        int nGoalXP = 5 * (5 - 1) * 500;
        //SetXP(oPC, 0);
        SetXP(oPC, nGoalXP);
    } // more details
}

Post edited by Hawkwinter on

Comments

  • badstrrefbadstrref Member Posts: 124
    edited April 2019
    TakeGold from EnterArea or Module events will fail, use AssignCommand(object, action) in that case, or use a Generic Trigger

    You can also change
    for (nCount = 0; nCount < 10; ++nCount) {
            CreateItemOnObject("nw_it_mpotion001", oPC, 1);
        }
    

    into
    CreateItemOnObject("nw_it_mpotion001", oPC, 10);
    


    Also you can use
    AssignCommand(oPC, ClearAllActions(TRUE));
    
    To guarentee that
    AssignCommand(oPC, ActionEquipItem(....
    
    will work




    #include "1x_craftsupplies"
    
    void WipePCInventory(object oPC)
    {
        int i = 0; int n = NUM_INVENTORY_SLOTS;
        for(i; i <= n; i++)
        {
            DestroyObject(GetItemInSlot(i, oPC));
        }
    
        object o = GetFirstItemInInventory(oPC);
        while(GetIsObjectValid(o))
        {
        DestroyObject(o);
        o = GetNextItemInInventory(oPC);
        }
    
        AssignCommand(GetModule(), TakeGoldFromCreature(GetGold(oPC), oPC, TRUE));
    }
    
    void CreateMiscItems(object oPC)
    {
        object oCloth = CreateItemOnObject("nw_cloth022", oPC, 1);
        AssignCommand(oPC, ClearAllActions(TRUE));
        AssignCommand(oPC, ActionEquipItem(oCloth, INVENTORY_SLOT_CHEST));
        CreateItemOnObject("nw_it_torch001", oPC, 1);
        CreateItemOnObject("nw_it_mpotion001", oPC, 10);
        CraftSupplies(oPC);
        GiveGoldToCreature(oPC, 3000);
    }
    
    void InitCharacter(object oPC)
    {
        WipePCInventory(oPC);
        DelayCommand(1.0, CreateMiscItems(oPC));
        int nGoalXP = 5 * (5 - 1) * 500;
        SetXP(oPC, nGoalXP);
    }
    
    void main()
    {
        object oPC = GetEnteringObject();
    
        if (GetIsPC(oPC))
        {
            InitCharacter(oPC);
        }
    }
    


    I don't whats in your CraftSupplies(oPC);
    since its implemented in the include.
    You also may want to avoid init characters if they're DM or DM possessed creatures...

    Post edited by badstrref on
  • HawkwinterHawkwinter Member Posts: 14
    Here's what currently happens after my most recent monkeying around with it (Total current script included at the bottom).
    1. Everything is stripped off them when they enter (including PC_Properties, which gets respawned and reequipped as usual when they rest).
    2. It takes all of their money.
    3. It gives them a set of commoner clothes, and equips it.
    4. It creates a torch in their inventory... usually.
    5. It tries to create 10 potions, but only creates 3.
    6. It gives them 3000 gold.
    7. It does not create any of the crafting items, even if you have the feats.
    8. It sets you to level 5.

    >"TakeGold from EnterArea or Module events will fail, use AssignCommand(object, action) in that case."
    Isn't that what I'm already doing? I'm confused.
    "AssignCommand(oPC, TakeGoldFromCreature(GetGold(oPC), oPC, TRUE));"

    >"CreateItemOnObject("nw_it_mpotion001", oPC, 10);"
    That's what I had first. It doesn't work. The loop doesn't work. Both give you 3 potions and then stop.

    >I don't whats in your CraftSupplies(oPC);
    Right. fair.
    void CraftSupplies(object oPC)
    {
        object oItem = OBJECT_INVALID;
        //Can the Player Craft Wands?
        if(GetHasFeat(FEAT_CRAFT_WAND, oPC)){
            oItem = GetItemPossessedBy(oPC, "x2_it_cfm_wand");
            if(oItem == OBJECT_INVALID)
            CreateItemOnObject("x2_it_cfm_wand", oPC, 1);
        }
    
        //Can the Player Craft Potions?
        if(GetHasFeat(FEAT_BREW_POTION, oPC)){
            oItem = GetItemPossessedBy(oPC, "x2_it_cfm_pbottl");
            if(oItem==OBJECT_INVALID)
            CreateItemOnObject("x2_it_cfm_pbottl", oPC, 1);
        }
    
        //Can the Player Craft Scrolls?
        if(GetHasFeat(FEAT_SCRIBE_SCROLL, oPC)){
            oItem = GetItemPossessedBy(oPC, "x2_it_cfm_bscrl");
            if(oItem==OBJECT_INVALID)
            CreateItemOnObject("x2_it_cfm_bscrl", oPC, 1);
        }
    }
    

    Checks if you have the crafting feats, and whichever ones you have, it should give you a blank crafting item for it if you don't have any. The idea being that then people won't be dependent on the module having proper support of it for their crafting feats to work (modules which I am not making - we're probably gonna start with SOU, do a bridging module, then play HOTU, we may grab others to play online after that, and my plan once I have it ironed out is to toss the other scripts in my override and then make sure I'm the one to host, but I will check the modules to see if I need to do any additional tweaking to them on top of that just in case. As for this character builder module, I'll use it to make sure everyone is on the same level for gear before we start playing, but I'm also using it to test my override scripts.

    >You also may want to avoid init characters if they're DM or DM possessed creatures...
    If I ever go to publish it I will keep that in mind (or if the check for it is simple I'll throw it in), but it won't come up in our usecase, it's just gonna be a co-op romp against the AI in a premade adventure.
    int BaseGear(object oPC){
    /******************************
     * This script clears the inventory of the entering player character.
     * It is suitable for use in an OnEnter or OnClientEnter event.
     *******************************/
        int nSlot, nCount, nVentory = 0;
        // Make sure oPC is a PC (not necessary in a module's OnClientEnter).
        if ( !GetIsPC(oPC) )
            return 0;
    
        // Destroy the items in the main inventory.
        object oItem = GetFirstItemInInventory(oPC);
        while ( oItem != OBJECT_INVALID ) {
            nVentory = 1;
            DestroyObject(oItem);
            oItem = GetNextItemInInventory(oPC);
        }
        // Destroy equipped items.
        for ( nSlot = 0; nSlot < NUM_INVENTORY_SLOTS; ++nSlot ){
            DestroyObject(GetItemInSlot(nSlot, oPC));
        }
    
        // Remove all gold.
        AssignCommand(oPC, TakeGoldFromCreature(GetGold(oPC), oPC, TRUE));
    
        //Give 3000 Gold to Spend.
        DelayCommand(4.0, GiveGoldToCreature(oPC, 3000));
    
    
        object oCloth = CreateItemOnObject("nw_cloth022", oPC, 1);
        AssignCommand(oPC, ClearAllActions(TRUE));
        DelayCommand(0.5, AssignCommand(oPC, ActionEquipItem(oCloth, INVENTORY_SLOT_CHEST)));
    
        //Give 3 healing potions, a torch, and some rags.
        CreateItemOnObject("nw_it_torch001", oPC, 1);
        //DelayCommand(0.2, AssignCommand(oPC, ActionSpeakString("Torch")));
    
        CreateItemOnObject("nw_it_mpotion001", oPC, 10);
        //DelayCommand(0.2, AssignCommand(oPC, ActionSpeakString("10 Healing Potions")));
    
        return 1;
    }
    
    void main()
    {
        object oPC=GetEnteringObject();
        ExecuteScript("x3_mod_pre_enter",OBJECT_SELF); // Override for other skin systems
        if ((GetIsPC(oPC)||GetIsDM(oPC))&&!GetHasFeat(FEAT_HORSE_MENU,oPC))
        { // add horse menu
            HorseAddHorseMenu(oPC);
            if (GetLocalInt(GetModule(),"X3_ENABLE_MOUNT_DB"))
            { // restore PC horse status from database
                DelayCommand(2.0,HorseReloadFromDatabase(oPC,X3_HORSE_DATABASE));
            } // restore PC horse status from database
        } // add horse menu
        if (GetIsPC(oPC))
        { // more details
            // restore appearance in case you export your character in mounted form, etc.
            if (!GetSkinInt(oPC,"bX3_IS_MOUNTED")) HorseIfNotDefaultAppearanceChange(oPC);
            // pre-cache horse animations for player as attaching a tail to the model
            HorsePreloadAnimations(oPC);
            DelayCommand(3.0,HorseRestoreHenchmenLocations(oPC));
            // Strip PC Gear
            BaseGear(oPC);
    
            //Raise Level to 3
            int nGoalXP = 5 * (5 - 1) * 500;
            //SetXP(oPC, 0);
            SetXP(oPC, nGoalXP);
    
            CraftSupplies(oPC);
        } // more details
    }
    

  • meaglynmeaglyn Member Posts: 58
    I'd avoid deleting the skin item since you just went through the trouble of adding the horse menu to it. You can just skip that slot in the loop.

    As to the potions. It's creating 10. The first 7 are getting added to the stack of 3 that's already there and marked for delete. Then the three that go over the stack size make a new item with stack size 3. Objects are not actually deleted in scripts until the script ends. You can fix that by delaying the potion creation. Better might be to split your basegear() into two functions. One to remove stuff, which you can just call. And then another to give the new stuff. You can then delay that call a bit.

    As to the level... you are saying you want level 3 but explicitly setting to level 5.

    For the crafting items, make sure the resref is correct. The GetItemPossessedBy takes a tag. The createItem call takes a resref. They are not guaranteed to be the same.

  • HawkwinterHawkwinter Member Posts: 14
    edited May 2019
    >It's creating 10. The first 7 are getting added to the stack of 3 that's already there and marked for delete.
    >Objects are not actually deleted in scripts until the script ends.
    Thats the part that was screwing me up. And I had no idea. I thought object targeted for deletion was deleted before running the next command in the script. What if I put the delete in a separate script and then run an executescript from inside the 'onenter'?
    >And then another to give the new stuff. You can then delay that call a bit.
    Can you explain why I would need to delay it? Shouldn't it be executing the commands in order?

    >As to the level... you are saying you want level 3 but explicitly setting to level 5.
    3 is the final target, but 5 allows me to easily test it with a wizard with all three crafting feats - whose script I've also been tweaking. That part works great,

    >avoid deleting the skin item since you just went through the trouble of adding the horse menu to it. You can just skip that slot in the loop.
    Fair enough. I'll figure out what slot that is and then add an exception to skip it.

    >For the crafting items, make sure the resref is correct. The GetItemPossessedBy takes a tag. The createItem call takes a resref. They are not guaranteed to be the same.
    I'll doublecheck those. They work reliably when I execute that function from onRest, but often not from onEnter. Probably because the weirdness of commands executing out of order you have been describing.

    Is there documentation on these commands happening out of order so I can avoid future issues like this coming up again?

  • meaglynmeaglyn Member Posts: 58
    edited May 2019
    It's not that commands in general are being done out of order. It is specifically and uniquely destroyobject that is done when the script engine returns. I.e. when the script ends. I believe that includes executescript calls since they run in the same script engine instance.

    DelayCommand would allow the script to end before the delayed commands run. That way the potions would be deleted before you did the creation of the new ones. DelayCommand can be a function. So DelayCommand(0.1, createStartingItems(oPC)); would work. That will run after the onenter script finishes and thus the potions will be gone.

    You know about the lexicon right?

    If the crafting script works on rest then the resrefs are not the issue. You might try delaying that call too and see what it does. Not sure why that isn't working unless the PC is not fully formed because this is module enter. In PWs people often run this sort of code in the on area enter script of the default starting location (or a trigger painter around such location) so that the PC is sure to be fully functional.

    Anyway, keep at it. There are some quirks but it's not madness :)

    Edit: "often" is often spelled "often" not "of".

    Post edited by meaglyn on
  • HawkwinterHawkwinter Member Posts: 14
    edited May 2019
    I know about the Lexicon, yeah, I've been using it to check on what functions do, I remembered it from way back in the mid 2000s when I was actively into NWN creation stuff. Evidently I didn't look up Destroy Object.

    >DelayCommand(0.1, createStartingItems(oPC)); would work.
    Then that's how I'll do it.

    >In PWs people of run this sort of code in the on area enter script of the default starting location (or a trigger painter around such location) so that the PC is sure to be fully functional.
    Hmm. This particular script isn't going to be an override to use with other modules, it'll be fired from a character building module that's a single room, so that could work too. I'll play around with it and see where I get.

    >DelayCommand would allow the script to end before the delayed commands run.
    >DelayCommand is not a delay before executing the next instruction, still in orde, it pulls the command out of sequence to be executed after the script finishes
    I really need to read the lexicon more closely.

    Thanks!

Sign In or Register to comment.