Respawn item in inventory of placeable after X time

Hi guys,
So I’m trying to create a simple interaction with a placeable; a flower that you can collect seed from every X minutes.
I tried looking around in NWN Lexicon or just searching for scripts; no luck. Maybe you can help me. I’d script it like this:
Object_self -> if all items are taken from my inventory, start the clock
Once clock counts X minutes, respawn Y items in inventory

Can anyone help me with this basic script?

Best Regards,
-Val

See here Timers and Timings
@TheBarbarian’s first reply have the code snippets you need.

2 Likes

Are you doing this for the PW your working on. If so you may want this one instead.https://neverwintervault.org/project/nwn1/script/acceraks-simple-spawn-n-clean-pws 2

1 Like

@Val
Skip down to where Shadooow and kevL_s explain timestamps and why skipping unnecessary DelayCommand calls is a good idea, if you look in there.

One way to do this is to check, either in the OnClosed event or in the OnDisturbed event (in the on-item-removed case), whether there are items left in the placeable’s inventory (GetFirstItemInInventory()), and, if not, store the current elapsed game seconds plus the time delay on the placeable.

Then, in the OnOpen event, you can check whether the current elapsed time is higher than the stored time, and spawn the items (CreateItemOnObject()) if it is - creating the new items on the placeable once it gets opened again after enough time passed.

2 Likes

@Greenman6220
Yes, it’s for the PW. At this point I’m trying to spawn inventory items in a placeable as opposed to spawning the placeable itself; but I’ll take a look at it soon because it’s also important; thanks!

@TheBarbarian
I tried using that on the OnUsed handle of the placeable but not only it doesn’t work, I don’t see the logic of it

void main()
{
if (GetLocalInt(OBJECT_SELF, "RECENTLY_USED"))
    return;
SetLocalInt(OBJECT_SELF, "RECENTLY_USED", 1);
DelayCommand(5.0, DeleteLocalInt(OBJECT_SELF, "RECENTLY_USED"));
}

To me it just says: Check if a a variable is true. Set a variable as true. Delay 5 seconds. How does it help respawn items?

@Val It would work if you place your spawning code at the end of the script, right after the DelayCommand line. What all the setting and checking of variables do is ensuring that what is after the DelayCommand line gets run only after 5.0 secs have elapsed.


    void main()
    {
    if (GetLocalInt(OBJECT_SELF, "RECENTLY_USED"))
        return;
    SetLocalInt(OBJECT_SELF, "RECENTLY_USED", 1);
    DelayCommand(5.0, DeleteLocalInt(OBJECT_SELF, "RECENTLY_USED"));

    //  code from here on will only run again after 5.0 secs have elapsed.
    // so all spawning code starts here.

    // Who we're spawning objects on
    object oTarget = GetLastUsedBy();

    // This is standard ale potion, 
    // replace it with the resref of the item you want to give the user
    string sItemTemplate1 = "nw_it_mpotion021";  

    int nStackSize = 5; // How many instances of the item to spawn

    // Now do the spawning
    CreateItemOnObject(sItemTemplate1, oTarget, nStackSize);
    }
1 Like

@Val Lilac Soul’s Script Generator comes in handy for all your other scripting needs. :grinning:

The step that makes it make sense is “return” - abort at this point, return to the point we came from.

It’s “if variable is not zero, abort. Otherwise, set variable as not zero. After time, remove variable.”

As in pscythe’s example, after that, you can put in whatever events you want to only happen again once enough time has passed. GetFirstItemInInventory() can be used to check whether somebody’s inventory is empty (returns OBJECT_INVALID on failure).

But: Use timestamps for the aborts instead! As Shadooow and kevL_s in that linked thread pointed out, unnecessary DelayCommand calls create an avoidable performance drain.

1 Like

Okay, hopefully here is a correct and complete solution to what you need. Since your flower have an inventory, then you need to use the OnOpen and OnClose events rather than the OnUsed.

Create your flower with an empty inventory and let the OnOpen event handler spawn the items. This script will spawn the items onto itself as long as the inventory is empty and we’re not in the DEPLETED state.

void main()
{

    // Do nothing if Inventory is not empty or we're currently in the DEPLETED state
    if (GetIsObjectValid(GetFirstItemInInventory(OBJECT_SELF)) ||
        GetLocalInt(OBJECT_SELF, "DEPLETED")) return;

    // resref of the thing you want to spawn
    string sItemTemplate1 = "nw_it_mpotion021";

    // How many instances of the item to spawn
    int nStackSize = 5;

    // Spawn into our own inventory
    CreateItemOnObject(sItemTemplate1,  OBJECT_SELF, nStackSize);
}

The OnClosed event handler will check if inventory is empty then enter the DEPLETED state and also issue a DelayCommand to get out of it after a certain fDelay seconds have elapsed.

void main()
{
    float fDelay = 60.0; // in seconds

    // check if Inventory is empty
    if(!GetIsObjectValid(GetFirstItemInInventory(OBJECT_SELF))) {
        // We're empty so enter the DEPLETED STATE
        SetLocalInt(OBJECT_SELF, "DEPLETED", 1);

        // After fDelay been elapsed exit the DEPLETED state
        DelayCommand(fDelay, DeleteLocalInt(OBJECT_SELF, "DEPLETED"));
    }
}

@TheBarbarian
So “return” means “abort”? It just exits the program without running the rest of the script? [Edit: Now that I looked at the script again the answer seems definite yes]

At the moment I’ll use DelayCommand just so I could check the box that “it’s working”; the next stage will obviously be improving it to reduce latency/lag with timestamps.

@pscythe
Great! It did work with the exception that it only creates 1 despite nStackSize being equal to 2 (I tried playing around with the number, still only created 1)

Edit: I just read in NWN Lexicon that “The parameter nStackSize only applies to stackable items (eg. potions). If sItemTemplate refers to a non-stackable item (eg. armor) only 1 item will be created.”
My item is currently non-stackable.
Any alternative commands to creating more than 1?

Thank you for the support, you guys are great!

It exits the current function to return to the calling point, yep.

Put your DelayCommand-based abort into a custom function you use for all instances of it. That way, you can edit them all at once later, by editing the shared function they’re all using.

Something like this (thoughtfully set up for integer time input, since the timestamp route will be using that):

int ReadyCheck(int nDelay=60, object oTarget=OBJECT_SELF)
{
    if (GetLocalInt(oTarget, "NOT_READY"))
        return FALSE;

    float fDelay = IntToFloat(nDelay);

    SetLocalInt(oTarget, "NOT_READY", TRUE);
    DelayCommand(fDelay, DeleteLocalInt(oTarget, "NOT_READY"));

    return TRUE;
}

(edit: IN THE NAME OF THE TRUTH, I dub thee TRUE, local int NOT_READY!)

And in void main:

if (!ReadyCheck())
    return;

Linking TR’s Basics and the functions list of the Lexicon:
https://neverwintervault.org/project/nwn1/other/trs-basics-decisions
https://neverwintervault.org/project/nwn1/other/trs-basics-mostly-operators
https://neverwintervault.org/project/nwn1/other/trs-basics-variables-types-and-functions-tbc
https://neverwintervault.org/project/nwn1/other/trs-basics-boolean-algebra
https://nwnlexicon.com/index.php?title=Category:Functions

For calling a particular function repeatedly, you could use a for loop.
https://nwnlexicon.com/index.php?title=For_Loop

On a serious sidenote, watch out for skipping past the basics while you’ve got your eyes on the big picture. Anything done shoddily in the groundwork can come back to bite you, costing much more time to fix once other things depend on it and need to be changed along with it. #SpeakingFromExperience #NeedToTakeOwnAdvice

1 Like

This code will work for both stackable and non-stackable items.

void main()
{
	if (GetIsObjectValid(GetFirstItemInInventory(OBJECT_SELF)) ||
		GetLocalInt(OBJECT_SELF, "DEPLETED")) return;

	string sItemTemplate1 = "nw_it_mpotion021";
	int iQuantity = 5;

	object oItem  = CreateItemOnObject(sItemTemplate1);
	int iType  = GetBaseItemType(oItem);
	int iStackSize = StringToInt(Get2DAString("BaseItems", "Stacking", iType));

	while (GetIsObjectValid(oItem))
	{
		SetItemStackSize(oItem, iQuantity);

		if (GetItemStackSize(oItem) == iQuantity)
		{
			break;
		}
		else
		{
			oItem = CreateItemOnObject(sItemTemplate1);
			iQuantity -= iStackSize;
		}
	}
}

Seems that even after reading my guides you’ve picked up a couple of bad coding habits. The first is you’ve got more than one exit point from that function. The other is using the literal 1 instead of TRUE. It doesn’t matter that internally they are the same, it’s the readability of your code that’s important for making it easier to debug. Here is a re-jigged version with both faults removed.

int ReadyCheck(int nDelay = 60, object oTarget = OBJECT_SELF)
{
	int iReturnValue = TRUE;

    if(GetLocalInt(oTarget, "NOT_READY"))
        iReturnValue = FALSE;
    else
    {
        float fDelay = IntToFloat(nDelay);

        SetLocalInt(oTarget, "NOT_READY", TRUE);
        DelayCommand(fDelay, DeleteLocalInt(oTarget, "NOT_READY"));
    }
    return iReturnValue;
}

As an aside. It’s a pity that the version of markdown available in these threads is only the standard one. There is an extension that let’s you specify (and maybe even define) the coding language that is being highlighted. C# is a good approximation for NWScript and is one of the languages available in the extension. Oh, well.

TR

1 Like

Just use a loop with a counter and repeatedly call CreateItemOnObject


void main()
{

    // Do nothing if Inventory is not empty or we're currently in the DEPLETED state
    if (GetIsObjectValid(GetFirstItemInInventory(OBJECT_SELF)) ||
        GetLocalInt(OBJECT_SELF, "DEPLETED")) return;

    // resref of the thing you want to spawn
    string sItemTemplate1 = "nw_it_mpotion021";

    // How many instances of the item to spawn
    int nNeeded = 5;

    // How many have we spawned so far
    int nSpawned = 0;

    // Keep spawning as long until the number spawned matches what we need.
    while (nSpawned != nNeeded) {
       // Spawn the object into our own inventory
       CreateItemOnObject(sItemTemplate1,  OBJECT_SELF, 1);
       nSpawned++;    // increment counter by 1 for every spawn
   }
}
1 Like

@Tarot_Redhand your version is more suitable for beginners but I have to say that @TheBarbarian’s version is more clear. His version have uses less variables and fewer lines of code. Extra variables is almost always bad.

You’re right in general about having too many return statements but in this case it actually makes the code more concise and more readable. When you see return FALSE there is no need to look anywhere else to see what it is returning. But with return iReturnValue you will have to actually look elsewhere to see what have been assigned to iReturnValue and that it haven’t been modified or reassigned while making its way to the final return statement.

And yes always use TRUE and FALSE when you mean True and False rather than 1 and 0. :slight_smile:

1 Like

::throws sand:: :grin:

Don't click it, Tarot.

void ObeyTarot(int itrue=0) {
object oTarot;

if (itrue)
DelayCommand(Twelveandahalfhours, AssignCommand(OBJECT_SELF, ActionDoCommand(ActionObey(oTarot = GetObjectByTag("Tarot_Redhand", 1)))));

}

void ConsiderDoingWhatTarotSays(string sTarotSays);

int nIsDoingWhatTarotSaysAGoodIdea(string sIDEA)
{
return PROBABLY;
}

int GetHasFoodBeenGiven(object oRecipient=OBJECT_SELF) {

if (GetItemPossessedBy(oRecipient, "willaltercodeformattinghabitsinreturnforfood") == OBJECT_INVALID) return FALSE;
else return TRUE;}

void ConsiderDoingWhatTarotSays(string sTarotSays)
{
   int nDoIt = FALSE;
if (GetHasFoodBeenGiven(OBJECT_SELF) && (nIsDoingWhatTarotSaysAGoodIdea(sTarotSays) != FALSE))
nDoIt = TRUE;

else
nDoIt = FALSE;

if (nDoIt == TRUE)
     ObeyTarot(TRUE);
else if (nDoIt == FALSE)
    return;

}

Switched the 1 to a TRUE, aye aye, sir. Seconding @pscythe on the readability in cases like this. Let’s not hijack @Val’s thread to get into a full-on style debate, though - NWScript newbies will develop utter-lack-of-code-standardization-trauma soon enough without the lot of us pushing it. :grin:

1 Like

Thanks everyone for the replies, you’re all amazingly helpful and it’s rare to experience that level of extensive sharing of knowledge with helpful attitudes all around.

For now I decided to stick with the script as is, spawning 1 object every X time using the poor DelayCommand function. If I’ll ever require improved functionality I might revisit this thread to bask in the vast techno-knowledge provided herein :slight_smile: