Jump to content
DoctaSax

Tutorial: NVSE4+ Part 5: Event Handlers & User-Defined Events (UDEs)

Recommended Posts

Req knowledge:
UDFs, blocktypes, forms and refs, advanced use: arrays

Also required:

NVSE 4.6+

 

1. Why?

2. Standard events, handling them, parameters.
3. Using the standard parameters as filters

4. Removing event handlers

5. NVSE events
6. User-defined events

 

1. Why?

 

As a modder there are certain things that you know aren’t impossible, but you’d rather not do them because it causes conflicts and other problems.

How would you go about:

  1. altering a ‘personal hygiene’ variable every time someone activates a sink, tub or toilet?
  2. playing a woozy double vision image space modifier every time the player consumes booze?
  3. playing a wolf whistle sound every time an actor unequips their clothing?
  4. playing the theme from The Good, the Bad and the Ugly every time you equip a revolver?
  5. setting a chance of a booby trap materializing and going off if certain locked doors are opened by the player?
  6. monitoring changes in an actor’s AI packages, so that you can force them to stick to the one they’re supposed to follow?
  7. marking each item that is dropped from player inventory in a particular cell for deletion, to avoid savegame bloat, or to mimic things getting picked up because you’re not around to watch over them?
  8. having people say custom dialog whenever they hit other people with a particular weapon or weapon type?

The only way I can see so far of doing any of those is attaching an object script to an item, with a one-frame blocktype like OnActivate, On(Actor)Equip, OnOpen, OnPackageChange, OnDrop or OnHit(With) signaling that these events happen so you can do something about it.

 

The problem there is you can’t just go around attaching object scripts to vanilla forms without inviting conflicts with other mods changing anything about those forms, and creating a need for separate patch mods resolving those conflicts. FNV already has a significantly reduced maximum number of mods that can be loaded compared to other Beth games, and if a large portion of those are simple patches, that’s just a waste. Not to mention you have to make those patches, and keep repeating to people reporting problems to install them, in the right order.

 

Another issue is that you’ll usually need to attach the same script to a lot of objects if you want it to work correctly. This means all the base forms for sinks, toilets & tubs for your hygiene mod, or all the revolvers if you wanted to have people say "Yippee-ki-yay, motherfucker" if they hit others with revolvers. And what about mod-added items that could just as easily apply? Would you like to make a patch for each and every revolver mod out there? Not me.

 

A lot of times your script will also fire when it’s not needed: you want your item cleanup script to only work on some actors, or only want to have a chance of booby traps on some door refs while other doors sharing the same base form should be safe. The only way of detecting if the necessary conditions apply is by already running the script, and already inviting the conflicts by simply having it on the form.

 

These headaches are enough to make you want to skip adding some cool functionality altogether sometimes. What if...

 

- we could be notified of anything being activated, opened, dropped, hit, consumed etc without attaching scripts to vanilla forms?
 

- we could filter being notified according to what is being activated or who’s doing the activating, what is being opened or who’s doing the opening, etc?

That’d be sweet, huh? It’s exactly what event handlers do.

 

 

 

2. Standard events, handling them, parameters.

 

 

Event handlers are UDFs that you register with NVSE to be called whenever their associated event happens. These handlers are then set for the rest of the game session; even if you load a different save, they are still valid until you actually restart the game.  If I want to be notified whenever anything whatsoever is activated or dropped, for instance, I register an event handler with SetEventHandler every time the game is loaded:

SetEventHandler "OnActivate" myActivateEventHandler
SetEventHandler "OnDrop" myDropEventHandler

and then every time something is about to be activated or dropped, NVSE will call your event handler before proceeding with the activation or dropping. Remember that: certain functions that would work in a regular block will not in their equivalent event handler, because what's about to happen hasn't yet. Using the GetActionRef function in an "OnActivate" handler will probably not return anything - luckily you'll be provided with the same intel via the handler's parameters.

 

In the case of the “OnDrop” event, NVSE will pass along the dropper as first parameter, and the dropped item as second. So your myDropEventHandler needs to be able to capture them, in that order:

Scn myDropEventHandler

ref rDropper
ref rDropped

Begin Function {rDropper, rDropped}
…
End

If you don’t have those variables ready to capture the parameters, nvse will report an error.

 

In the case of the “OnActivate” event, you need to remember that the terminology with the OnActivate script block and the activate function in vanilla can be confusing. Light switches, sinks and triggers are found under “activators” in the geck although they always seem to be activated by an actor. While it looks like an actor activates something like a container ref, when you force it via script it’s actually the container ref doing the activating:

rContainerRef.activate rActorRef

The “OnActivate” event, then, first expects the activator, the thing that looks to be activated, and then the thing or actor that seems to activate it, which you’d be able to retrieve from a regular OnActivate script block with GetActionRef. So let’s put it this way:

Scn myActivateEventHandler

Ref rActivator
ref rActionRef

Begin Function {rActivator, rActionRef}
…
End

Most of the standard event handlers, the ones that correspond with regular blocktypes, come with standard parameters like that. They will always be sent, be ready to receive them.

 

post-18170-0-16240000-1416100650_thumb.jpg

 

(The params don’t have to be called that, of course, the names are just meant to distinguish what they are.)

 

 

 

3. Using the standard parameters as filters

 

 

These handler parameters can be used as filters, by adding them as parameters to the SetEventHandler function when you register the handler. This makes it so that your event handler is only called in when it’s needed, and you should obviously use such filters as much as possible. Also make sure that whatever they filter on is actually a valid form, otherwise the filtering will bug out and it'll be as if no filter was applied.

 

Because you may choose to only use one filter, or none at all, you need to specifically state on which parameter you’re filtering, the first or the second, and couple them with double colons (like you do with ar_Map):

SetEventHandler "OnDrop" myEventHandler "first"::playerref "second"::myCoolgunForm

In OBSE, the “first” was called “ref”, and the “second” was called “object”, and this is still possible in NVSE:

SetEventHandler "OnDrop" myEventHandler "ref"::playerref "object"::myCoolgunForm

However, unlike OBSE, NVSE allows either of those filters to refer to a ref, base form, or even a formlist (!) containing refs and/or base forms and/or more formlists, when applicable. So using “first” and “second” is much less confusing in that regard.

 

So, give or take some scripting in the actual event handler scripts, the problems from the introduction should be a lot less problematic with the following events and filters set:

1. SetEventHandler "OnActivate" myEventHandler "first"::formlistofallsinkforms "second"::playerref

2. SetEventHandler "OnMagicEffectHit" myEventhandler "first"::playerref "second"::ChemIncCHAlcohol

3. SetEventHandler "OnActorUnequip" myEventhandler ; check what’s unequipped with the handler’s parameter

4. SetEventHandler "OnActorEquip" myEventhandler ; check what’s equipped with the handler’s parameter

5. SetEventHandler "OnOpen" myEventHandler "first"::formlistofdoorrefs "second"::playerref

6. SetEventHandler "OnPackageChange" myEventHandler "first"::rActor "second"::rPackage

7. SetEventHandler "OnDrop" myEventHandler "first"::playerref ; check the cell and the dropped item in the handler

8. SetEventHandler "OnHitWith" myEventHandler "second"::myCoolGunorListofGuns

For the package monitoring problem, it’s also entirely possible to monitor packages starting, changing and ending in one and the same handler:

SetEventHandler "OnPackageStart" myEventHandler "first"::rActor "second"::rPackage
SetEventHandler "OnPackageChange" myEventHandler "first"::rActor "second"::rPackage
SetEventHandler "OnPackageDone" myEventHandler "first"::rActor "second"::rPackage

In this case you can use the GetCurrentEventName function to figure out what event has called your handler:

Scn myEventHandler

Ref rActor
ref rPackage
string_var sv_event

Begin Function {rActor, rPackage}

Let sv_event := GetCurrentEventName
if eval sv_event == "OnPackageStart"
   ; do something
elseif eval sv_event == "OnPackageChange"
   ; do something else
endif

End

4. Removing event handlers

 

 

 

It’s entirely possible that you provide certain settings in your mod that cause an event to no longer apply. Also, event handlers are still valid if you re-load a different save but not re-start the game, so you want to build in some safety by removing a handler if, for instance, that different save hasn't gone through some init yet that the handling needs.

 

To remove a handler you use RemoveEventHandler, with the same syntax as the one you used when you set it:

RemoveEventHandler "OnActivate" myEventHandler "first"::rActivator "second"::rActionRef

The above will remove the event handler being called when those filters apply. If those are the only ones you set in the first place, you might as well skip the filters:

RemoveEventHandler "OnActivate" myEventHandler

So yes, you can set and remove an event handler being called under some filters and not others.

 

 

 

5. NVSE events

 

Aside from events related to vanilla blocktypes, you can also register handlers to be called when certain meta-game events happen. This is especially of use for mods that write some type of co-save files etc.

 

post-18170-0-90564300-1416100657_thumb.jpg

 

 

6. User-defined events

 

As if all of that wasn’t cool enough, you can fire off custom events that other people can register to in the same way as they would a standard one, and receive information that you send along with it.

 

This is more naturally of use to modders working on some type of general functionality, wishing to let other modders know that something happened that is of note and non-continuous, something that can classify as an ‘event’.

 

Perhaps you’ve overhauled sneaking and wish to advertise that according to your sneaking mod, the player’s been detected or has successfully hid, whether or not vanilla functions agree.

Perhaps your mod needs to go through a series of init scripts before it’s functional and you wish to let dependent mods know that yours is ready for action when it’s done.

Perhaps you want your inventory sorter to not only notify others that sorting took place, but also send along information on where everything ended up.

Perhaps even you’ve been slaving over a naughty mod that monitors naughty things happening until one of the naughty people involved encounters some type of climactic event and you wish other naughty mods to know this so they can do cool stuff like play appropriate sounds and what not. ;)

 

In each of those cases, all you really need to do is dispatch an event with the DispatchEvent function, which should at least be followed by an event name, optionally a stringmap with intel, and optionally another string overriding the default sender name:

DispatchEvent "eventName":string args:stringmap sender:string
DispatchEvent "SomeEventName"
Array_var ar_mystringmap
; populate ar_mystringmap
DispatchEvent "SomeEventName", ar_mystringmap
DispatchEvent "SomeEventName", ar_mystringmap, "somestring"

And the mods wishing to register a handler to it will do it as normal, with the following limitations:
- because there are no standard parameters to use, you can’t use filters when setting handlers for custom events
- the only parameter that the handler UDF needs and has to have is an array var that can capture the stringmap that’s always received, even if none was dispatched:

SetEventHandler "SomeEventName" myEventHandler
Scn MyEventHandler

Array_var args

Begin Function {args}
…
End

If no stringmap was dispatched, it’ll only contain the 2 standard elements:
       [eventName] == "SomeEventName"
       [eventSender] == "SomeSenderString" (usually == "DispatchingMod.esp")

The dispatcher can determine “SomeSenderString” by explicitly adding the third optional parameter in the DispatchEvent line. If it’s left blank it’ll be the dispatching mod’s name. If a stringmap was dispatched, the standard elements are added to it.

 

That’s it. Go forward and modify.

Share this post


Link to post

Nice job again.

 

From what I have experienced, script extender compilers are a bit wonky. You know, they slip a lot of mistakes away and sometimes refuse to compile correct things.

 

For example, as opposed to OBSE documentation,

SetEventHandler "OnSomeEvent" SomeUDF "first"::PlayerRef
If I don't enclose the 'first' with double quotes they give me a warning that I have an unquoted thing and they will consider it a string. Presumably because "::" is just a dictionary mapping operator, so first/second/object/ref must be a string.

 

Also, recently I've experienced a strange malfunction.

For every OBSE v21 user, both of these will work.

SetEventHandler "OnDeath" a3hfdOnDeathXp "second"::PlayerRef
;or
SetEventHandler "OnDeath" a3hfdOnDeathXp "object"::PlayerRef
However the first line ("second") doesn't seem to be working for some lower version OBSE users. The handler function was triggered just fine, but OBSE refused to filter out actors. I only changed every "second" part to "object" and it started working fine for that user.

Maybe at least some events are/were malfunctioning. This was a problem because I had advertised that mod requiring obse v20 or higher, and that user actually decided to use v20 instead of the latest one for some reason I don't want to understand.

 

These issues are of course about OBSE, but NVSE *may* have similar issues as well imo. After all it's quite common they don't do what they are supposed to do.

Share this post


Link to post
Guest tomm434

Very nice tutorial - thanks!

 

I've been looking for opportunity to atach even "SayToDone" blocks to actors - that's awesome!

What version of NVSE does this function require?

 

 

about vanilla ways: there is one way to replace "OnActivate" block for player - add him a perk with "Activate" entry point

Share this post


Link to post

yesterday's beta and there's already a tutorial? wow...

you all thank you a lot!

Share this post


Link to post

Nice stuff Doc, have to look into these event handlers, I had a use for this at some point, just have to remember what it was now :)

Share this post


Link to post

Maybe you can sweet-talk jaam into detecting the event of having an idea so you can store it for later. :idea:

Share this post


Link to post

- An event handler OnDrop, is it called when you're still in menumode or when you close the pipboy and go in gamemode?

- Could I have a full example of how to script some spell detection? more specifically, I'm interested in "playing a woozy double vision image space modifier every time the player consumes booze", it's something I thought a long time ago and never realized for compatibility reasons.

- Is there an AddEventHandler too?

Share this post


Link to post

1. Good question; I'm not sure. But considering that events are 'handled' before they really happen, and that the thing you drop is already gone from inventory while you're still in menumode, I guess it's already done in menumode. Tbh, I haven't tested events as extensively as strings and arrays, and even those were never completely tested; my focus was always on learning how to drive these things, not how the engine works, if you know what I mean.

 

2. I wrote the example as

SetEventHandler OnMagicEffectHit myEventhandler "first"::playerref "second"::ChemIncCHAlcohol

'magic effects' are oblivion's version of fallout's 'base effects'. ChemIncCHalcohol is a base effect attached to vanilla booze. It is possible some mod-added/altered booze doesn't have it though. You could populate a formlist with base effects instead. Perhaps an "OnActorEquip" event would work too, on the actual ingestible, although some mods (like sofo) cast booze on people like spells instead of equipping them, so the magiceffect one should catch them better.

So you use the event handler to be notified, and probably apply and monitor the image space modifier in some spell or token script you start from there.

 

3. I'm not sure why you'd need one. You have standard events, for which you add handlers with SetEventHandler. And custom, user-defined events, for which you do the same.

Share this post


Link to post

3 - I was meaning something like the opposite of RemoveEventHandler. You can decide to stop an event handler, but you can't decide to start it again, right?

 

2 - It's that I ... uhm... understood the event handler itself, but not how to practically script the whole thing. I understood it's some sort of UDF with forced parameters, but then I don't get the rest. Do I always need to register it inside a GameMode and under a GameRestarted / GameLoaded condition?

 

And I would add, also: you told once UDFs will always be executed (at the cost to stutter the rest...), so I started using UDFs for those scraps of scripts I really want reliability. Is there the same reliability for an Event Handler? or there's a risk it somewhat can be skipped? I'm pretty interested for the example you made with packages, since we all know they're a lil wonky sometimes... maybe this could be the definitive solution for them to not be broken

Share this post


Link to post

3. I'd say there's nothing wrong with adding a handler with SetEventHandler again after you've removed it.

 

2. Apart from the SetEventHandler code line, I'd write a handler that casts a spell once it's triggered:

scn myHandler

 

ref rTarget

ref rBaseEffect

 

Begin Function {rTarget, rBaseEffect}

 

rTarget.cios mySpell

 

End

 

And in the spell, I'd start the image space modifier with "imod" in the scripteffectstart block, and keep checking whether the player's still under the influence in the scripteffectupdate block so you can remove it with rimod at some point.

 

About UDFs and stutter, it's not necessarily the fault of the UDF, it's more a question of the combined load of the script calling the udf and the udf itself, combined with other scripts possibly doing the same at around the same time. Other script types may simple stop the script if they can't complete during the frame but the udf really, really wants to complete. I have no idea whether event handlers behave any differently; I assume not, but that's really more jaam's area. I suggest using handlers primarily as a method to be notified of things happening and who it's happening to etc, and then using other scripts to do something with that.

Share this post


Link to post

2 - sorry but I want to be sure to have understood: myHandler is a simple Object script like an UDF, but then you put your SetEventHandler line inside a gamemode in some GameRestarted / GameLoaded condition? like when you make your first initialization?

 

(however, just wondering... for compatilibity with other booze mods, maybe an event handler like that could be applied more to alcohol withdrawal effects, maybe some custom alcoholics have different effects but they all have a withdrawal)

Share this post


Link to post

Ah yes, you can't register your event handler with the event handler. Because that script would never be executed. So you do that in a different script somewhere - most of us already have a first-time init and a GGL/GGR check somewhere in our mods, so that's the logical place for it to be.

Share this post


Link to post

Thank you, that what I was missing, I wasn't sure if the GGL / GGR was a requisite or a choice

Share this post


Link to post

Speaking of GGL/GGR, it might be worth noting that once registered event handlers won't go away untill restart the game or explicitly remove them. So it is very important to clean them up on game load, and also you should make 100% sure that the filter argument form is valid because if invalid it's as good as no filter. One bad thing in script extender is that most errors are silently ignored. (or good thing, maybe)

Share this post


Link to post
Guest tomm434

Okay - 2 questions:

 

1)

 

What might be the reason that none of these codes don't work?

SetEventHandler “OnActivate” MyFunction "first"::LunetteRef "second"::PlayerRef
SetEventHandler “OnActivate” MyFunction
SetEventHandler “OnActivate” MyFunction "first"::FormListOfChairs "second"::PlayerRef

2)

What happens if

 

1) My mod sets "SetEventHandler “OnActivate” MyFunction"

2) Then someone from another mod sets "SetEventHandler “OnActivate” OtherModFunction"

 

Who wins?

 

Thanks!
 

 

Share this post


Link to post

1) no idea, I did test the OnActivate one and they worked for me.  You do have 4.6, right?

 

2) Both should trigger.

Share this post


Link to post

 

no idea, I did test the OnActivate one and they worked for me.  You do have 4.6, right?

Yes! I downloaded it from here

 

http://forums.bethsoft.com/topic/1482519-beta-new-vegas-script-extender-nvse-v4/page-7

 

Tried to move code from object script - same result. Do you think I should report it to jaam?

 

 

Probably. Before you do, can I have a look at the handler and the script setting it?

Share this post


Link to post
Guest tomm434

Line from object script which executes on every game load:

SetEventHandler “OnActivate” LunettemyActivateEventHandler "first"::LunetteRef "second"::PlayerRef

Function

Scn LunettemyActivateEventHandler

Ref rActivator
ref rActionRef

Begin Function {rActivator, rActionRef}
Player.Kill
End

To be honest earlier I inserted one of my previous functions (without function parameters) instead of "LunetteMyActivateEventHandler". You said that NVSE should throw an error but it didn't.

Then I tried it with this function  - I get the same result.

Also I don't know if it's important but GECK throws an error every time that "OnActivate" block is treated as "string".

 

What do you think?

 

 

Share this post


Link to post

The error that it throws when the parameters don't match is in-game, in the console. It'll always compile fine.

The fact that it complains about "OnActivate" being a string though... I only ever get that when I don't mark something as a string with quotation marks when I should. I just installed the official version & re-compiled my own handler-setting script to make sure, but don't get that complaint.

 

Other than suggesting you re-download the NVSE package, make sure you install all dlls & exes and load up both geck and game with the nvse loader, there's little I can do except refer you to jaam.

Share this post


Link to post
Guest tomm434

Everything works fine now.

 

You know what was causing this?

I copied the line from your tutorial

SetEventHandler “OnActivate” myEventHandler "first"::formlistofallsinkforms "second"::playerref

Quotation marks in " “OnActivate”  " are messed up. That's why GECK was throwing me an error. You might want to edit that for people who copy text from tutorial :)

 

 

And now error is thrown in console if I call functions without parameters.

 

 

Thanks a lot for the tut! I love that new event detection. I hope jaam will add more events later if possible.

Share this post


Link to post

Oh wow... I guess that's because I wrote the bulk of the text in Word & it has this annoying habit of changing quotation marks into curly ones. We can solidly blame Microsoft once again (as well as the idiot who used it out of force of habit).

Changed all quotation marks in the OP to be proper ones.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...