interesting behavior of OnActivate

Post » Thu Nov 05, 2009 8:50 am

I've been experimenting with the OnActivate test this evening, and found some behavior isn't quite as described in Morrowind Scripting for Dummies 9.

===
1)
What I found really interesting (and useful) is that activations seem to be buffered. Maybe this is known, but I haven't seen it documented anywhere.
My script is rather long, but here's a simplified example of what happened:

set timer to ( timer + 1 ) ;if ( timer >= 256 )   set timer to 0 ;endifif ( timer == 0 )     ;..     ;..     ;..              ;This only runs once every 256 frames.  Yet it works reliably.                           		if ( "indrele rathryon"->OnActivate == 1 )				If ( act_ir == 151 )	;currently sleeping				messagebox, "This person is asleep"			else				"indrele rathryon"->Activate			endif		endif


Even though my script was only testing this every 256 frames, it still worked every single time. There was a noticeable lag of several seconds between hitting spacebar and seeing the dialog box pop up, but it always worked. Of course the lag I allowed here is too long, but the point is, you don't have to test OnActivate every single frame.
The following from MSFD pg 66 is apparently incorrect:
OnActivate
If ( OnActivate == 1 )
This gets set to one for one frame when the object is activated. OnActivate resets itself as soon as the function is called, so only one script can report OnActivate successfully,

The 2nd sentence is correct, but the first is apparently incorrect. The game apparently holds OnActivate==1 indefinitely until your script reads it. This is convenient.



=================================
2)
The 2nd item I noticed is something that might be well-known, depending if this qualifies as a "targeted script". But mine was just a standard global script:
                if ( act_ir = 151 )   ;currently sleeping                       if ( "indrele rathryon"->OnActivate == 1 )	;once this is first called, the game no longer handles ANY activations of this object.                       messagebox, "This person is asleep"                endif

The above broke auto-activation. Initially, activation worked fine. But after the if() test becomes true, and the OnActivate test first runs, then auto-activation of this object permanently dies.
Even after if ( act_ir = 151 ) is no longer true, you still can't activate indrele rathryon anymore.
This seems to be consistent with the documented problem with targeted scripts on pg171:
If OnActivate is used in a targeted script, the object will not be able to be activated by normal means after the script is stopped, unless a targeted script with OnActivate is again added to the object.

However, in my case, the script is still running, but it just isn't checking OnActivate anymore. That might be what the author meant. But the other point is that this isn't really a targeted script, unless I've misunderstood the definition. It's just a regular global script which uses a qualifier on the OnActivate test. So it seems the issue probably applies to any script, not just a targeted one.
Once you call OnActivate, the game expects you to handle all future activations of that object and will begin buffering (OnActivate == 1) until you read the value.

I might be nitpicking on the 2nd item, but thought it worth mentioning. The first item, regarding how it seems to buffer the OnActivate status, is quite useful though.
User avatar
Mélida Brunet
 
Posts: 3440
Joined: Thu Mar 29, 2007 2:45 am

Post » Wed Nov 04, 2009 8:29 pm

This might be something you'll want to PM to the writers of MSFD, assuming they're still trying to improve it. In any case, that's very interesting what you found about OnActivate.
User avatar
Gavin Roberts
 
Posts: 3335
Joined: Fri Jun 08, 2007 8:14 pm

Post » Thu Nov 05, 2009 3:22 am

I think onactivate resets itself as soon as activate is called
User avatar
Cody Banks
 
Posts: 3393
Joined: Thu Nov 22, 2007 9:30 am

Post » Wed Nov 04, 2009 7:39 pm

I think onactivate resets itself as soon as activate is called

Thanks for posting that, I did a quick experiment and it looks like you're right. I tried hitting spacebar on the NPC, then quickly called activate from the console. The activate call invoked the standard dialogue, bypassing the OnActivate script. I then waited for my script but it failed to detect the original activation attempt. So yes, if for any reason your script uses Activate on an item before testing OnActivate, then the OnActivate test will lose it's existing status and return 0.

So both Activate and OnActivate will reset the value when called. I'm not sure if that's what you meant, or if you're saying that only Activate does it.

I know that OnActivate will reset the status because of what happened in an earlier version of my code.
In an earlier version, the code did this:
if ( act_ir == 151 )        if ( "indrele rathryon"->onactivate == 1 )		        messagebox, "This person is asleep"	endifendif

I wasn't calling Activate anywhere at that point, but the messagebox only appeared once, so it did clear itself when checking OnActivate.


...
PS - I'm typing the function name "OnActivate" with capital O and A for clarity, but the board software is changing all of them to lowercase. How annoying...
User avatar
ladyflames
 
Posts: 3355
Joined: Sat Nov 25, 2006 9:45 am

Post » Wed Nov 04, 2009 9:48 pm

I think onactivate resets itself as soon as activate is called

That hypothesis is easy enough to test.

I place this script on a container (normal behavior as a response to Activate is to open)"

short frameCounterif ( OnActivate == 1 )    set frameCounter to ( frameCounter + 1 )    if ( frameCounter < 100 ) a few seconds delay        return    endif    set frameCounter to 0    Activate    messagebox "Container has been activated."endif

I activated the container several times without it opening. When I checked the script through the console frameCounter returned a value of 3. After another activation frameCounter was up to 4. Once again and it is at 5.

Clearly for my situation OnActivate is only true for the one frame that it is activated. It did not remain true until the script reaches Activate.

I cannot explain what is going on in yorgle's original post. I am uncertain as to how the script is running, but it may be similar to my test - triggered by OnActivate. I just do not see it included in the posted code.
User avatar
Matt Bee
 
Posts: 3441
Joined: Tue Jul 10, 2007 5:32 am

Post » Thu Nov 05, 2009 9:16 am

I activated the container several times without it opening. When I checked the script through the console frameCounter returned a value of 3. After another activation frameCounter was up to 4. Once again and it is at 5.

Clearly for my situation OnActivate is only true for the one frame that it is activated. It did not remain true until the script reaches Activate.

It remains true until it is cleared by calling either Onactivate or Activate. frameCounter only incremented once because the onActivate call cleared it. But the status is retained across multiple frames, until you cause it to be cleared.

To see that it isn't just 1 frame, try wrapping your test in a long delay, like:
frame = frame + 1if ( frame > 300 )     set frame to 0endifif ( frame == 0 )          endif


You'll find that when you activate the item, it will wait patiently until frame == 0, when onactivate finally picks it up. The status held by "onactivate" is remembered across multiple frames, until you call the function, at which point it returns true and clears itself.

But apparently, if you call Activate before testing onActivate, then Activate will also clear it, causing onActivate to fail.
User avatar
Bitter End
 
Posts: 3418
Joined: Fri Sep 08, 2006 11:40 am

Post » Thu Nov 05, 2009 4:35 am

I don't know if it may be of use, anyway I tried a variation
begin activateTestScript; usage:; from game console: click the container, so that it is the current object (ID appears on console header); enter in console:; startscript activateTestScript; from  another script:; myUniqueContainerID->startscript activateTestScriptshort frameCounterif ( OnActivate )    set frameCounter to ( frameCounter + 1 )    if ( frameCounter < 100 )   ;  a few seconds delay        return    endif    set frameCounter to 0    Activate    messagebox "Container has been activated."endifif ( GetPCJumping )	stopscript activateTestScriptendifend
of cyran0's test script started as targeted global on a container and had the same results as using it as local script on the container, so this seems confirmed and unrelated to how the script is called...

[EDIT]by the way, i tried also commenting the Activate line, nothing changed

yorgle11 Icon, could you post the entire scripts involved?
User avatar
pinar
 
Posts: 3453
Joined: Thu Apr 19, 2007 1:35 pm

Post » Wed Nov 04, 2009 11:52 pm

Thank you for the clarification, yorgle. I think I was confused by the language. You write of OnActivate being 'called' when I would describe what happens as being 'checked'.

I retested with the following script and did observe what you described originally and predicted more recently:

short frameCounterset frameCounter to ( frameCounter + 1 )if ( frameCounter < 100 ) a few seconds delay    returnendifset frameCounter to 0if ( OnActivate == 1 )    Activate    messagebox "Container has been activated."endif

Since the frame counter runs continuously, I checked its value through the console and only activated the container when frameCounter returned a comparatively low value giving me time to activate the object and notice the delay. I did observe a delay in the activation of the container suggesting that the engine does hold this action in a sort of buffer until the script can be consulted about what to do.


Every object has a default action when activated, but when a script is attached to that object this action is suspended until the script is consulted as to any change in that default action. The change may take the form up updated the journal or displaying a message. Activate may be 'called' by the script to then use the default action, but of course it does not have to be 'called' thereby preventing the looting of corpses or opening of doors, etc. In the latter case, the engine holds the action in buffer forever and the object cannot be 'activated'.

So when the player activates an object with a script on it, the engine suspends execution until the script is consulted. This is the 'buffer' of which you wrote. It holds that action until the script 'checks' OnActivate for the first time. In that one frame of script execution OnActivate will be true. If it is followed by Activate in that same frame then Activate will be called and the object's default action will occur. However OnActivate is not true indefinitely and reset to false when Activate is 'called', it remains true until OnActivate is 'checked'.

As we all understand, if it is desired to have a delay between the 'check' of OnActivate and the 'call' of Activate in the script, a local variable should be flagged to store that the object has been activated and then that variable is 'checked' to see if Activate should be called.
User avatar
Barbequtie
 
Posts: 3410
Joined: Mon Jun 19, 2006 11:34 pm

Post » Thu Nov 05, 2009 2:54 am

Thank you for the clarification, yorgle. I think I was confused by the language. You write of onactivate being 'called' when I would describe what happens as being 'checked'.

There's apparently a stored internal value for the activation status, which I agree is being checked. However I think of OnActivate itself as technically being a function call, not a variable. If it were just a variable, then reading it wouldn't have additional effects besides simply reading what's stored there. There are some system variables in Morrowind which are truly just variables, (OnPCEquip etc), which are simply storage locations and even allow/require you to set their value back to 0 manually. But OnActivate is behaving like a function call with a return value. It both resets itself, and also toggles whether Morrowind will handle activations automatically vs deferring to your script.
It's not terribly important except that referring to it as a function call helps to emphasize the fact that it performs actions, it doesn't only read a value.


So when the player activates an object with a script on it, the engine suspends execution until the script is consulted. This is the 'buffer' of which you wrote. It holds that action until the script 'checks' onactivate for the first time. In that one frame of script execution onactivate will be true. If it is followed by Activate in that same frame then Activate will be called and the object's default action will occur. However onactivate is not true indefinitely and reset to false when Activate is 'called', it remains true until onactivate is 'checked'.

I agree with much of what you're saying, but differ on some details. :) It appears that both Activate and OnActivate will reset the activation status, it isn't only OnActivate which does this. Also, the status is reset immediately, so onActivate is only true until the first time it's read, not for the entire frame.
I put together a demo which I will link in my next post.
User avatar
Madeleine Rose Walsh
 
Posts: 3425
Joined: Wed Oct 04, 2006 2:07 am

Post » Wed Nov 04, 2009 8:33 pm

could you post the entire scripts involved?

Rather than doing that, I put together a more concise example .ESP which I've uploaded here:
http://sharesend.com/r7ej6

This can test more scenarios than my script from the first post. It adds a chest and 2 scrolls into the courtyard area of the Seyda Neen Census Office.
The chest has this script running on it as a local script:

Begin yor_OnActivateDemo; purpose of this script is to demonstrate the behavior of OnActivate; it should be attached as a local script on some object.short	frameshort	testmode		;controls what is tested				;Values:				;0  = default, automatic activation mode				;1  = use scripted OnActivate handler				;10 = call "Activate" before testing OnActivateset frame to frame + 1if ( frame > 500 )	set frame to 0endifif ( frame != 0 )	returnendif;the rest of this script will only run once every 500 frames.	;This demonstrates that calling Activate will clear the return value of OnActivate, causing it to fail.if ( testmode == 10 )	if ( frame == 0 )		activate			;as a result of this call to activate, the OnActivate test below will return false.	endifendif	;When (testmode == 0), this section is bypassed and OnActivate is not called.if ( testmode > 0 )	if ( OnActivate == 1 )		messagebox, "Activation Detected"	endif	;This tests whether the OnActivate return value is cleared immediately, or if it waits until the end of frame.	if ( OnActivate == 1 )		messagebox,  "2nd OnActivate returned true."	endifendifEnd yor_OnActivateDemo



By using the console to change the value of 'testmode' on the chest, you can test different scenarios with the script. There is a text document included which is meant to demonstrate how OnActivate apparently behaves. There is also another text document which contains the script.
For this demo to be convenient to look at, I'd recommend opening both text files, and Morrowind in a window.


Here is the demonstration procedure which I put in one of the text files, and also appears on one of the in-game scrolls (my conclusions are at the bottom). Sorry for the width, I'm not sure how to post a text box with word-wrap. But it's probably more convenient to read it in the text file rather than here:
This chest demonstrates the behavior of the OnActivate function.For this to make sense, please refer to the script on the chest.  The script is named 'yor_OnActivateDemo'First, hit the spacebar to activate the chest.  Notice that it activates normally.  Close it.Open the console and click on the chest.  Type 'show testmode'.  Notice that the value of testmode is 0.  This means the chest is still using the game's built-in default activation behavior.Now change the value of testmode to 1 by typing 'set testmode to 1'This means the chest will now use the OnActivate command to detect when you have activated it.  Wait about 10 seconds, then activate the chest using spacebar (not the 'activate' console command).You should notice a delay between hitting spacebar and seeing a messagebox appear.  Try it a few more times.  The delay will vary but may take up to several seconds.  This is because the script only checks OnActivate once every 500 frames.This demonstrates that the return value of OnActivate is retained until the script has read it.  It does NOT have to be read on the same frame as when the object was activated by the player.Notice also that only 1 messagebox is displayed for each time you press spacebar.  Refer to the script code, and notice that it actually reads OnActivate TWICE, and attempts to display a messagebox both times.  But only the first messagebox ever appears.
This demonstrates that OnActivate only returns '1' once, and after it is called, the return value of OnActivate is immediately reset to 0.Open the console, make sure you are operating on the chest, and change the value of testmode back to 0 by typing 'set testmode to 0' . Close the console and attempt to open the chest. Notice that it won't open and there is no messagebox. This is because in testmode 0, the script no longer calls the OnActivate function. However, because OnActivate has been previously called, the game no longer handles the activations automatically. So they are left completely unhandled.This demonstrates that once OnActivate is called on an object, the game no longer handles any future activations of that object, even if your script has stopped calling OnActivate. The same behavior occurs regardless whether it is a local script, or a global script (this chest uses a local script).Open the console, make sure you're operating on the chest, and change the value of testmode to 10. Close the console. The chest will begin automatically opening itself every few seconds. This is because in testmode 10, the script calls the "Activate" function every 500 frames, immediately before it reads OnActivate.Try to activate the chest by using the spacebar. Notice that the chest will still open at 500 frame intervals, but the messagebox never appears. Your attempt to activate the chest has not been detected. This is because once the script calls "Activate" on the object, the status of OnActivate is reset.Please refer to the script code for this chest to see that with testmode == 10, it does *attempt* to display the messagebox, but fails only because the scripted "Activate" call is resetting the stored activation status.This demonstrates that calling "Activate" on an object will reset the return value of OnActivate to 0.Conclusions:1) By default, Morrowind will handle activations automatically.2) Once a script calls OnActivate, Morrowind will no longer handle activations for the associated object. This occurs regardless whether OnActivate is invoked from a local script, or a global script.3) When activated, the return value of OnActivate will become 1, and this does NOT expire at the end of a frame. The '1' value is retained INDEFINITELY until either OnActivate is called, OR Activate is called. Calling either of those functions will immediately reset the return value of OnActivate back to 0.

User avatar
Jordyn Youngman
 
Posts: 3396
Joined: Thu Mar 01, 2007 7:54 am


Return to III - Morrowind