Events and handlers

From ZDoom Wiki
Jump to: navigation, search
Note: This feature is for ZScript only.

Event handlers are a plugin-like system available since GZDoom 2.4 that allows your ZScript code to receive certain world, client and system events.
There can be multiple event handlers and in most cases all of them will receive the event (note: exceptions exist).

General information

An event handler is a class that inherits either StaticEventHandler or EventHandler.
The main difference between these is that regular EventHandlers only exist inside a level and is saved to the savegames, while a StaticEventHandler will be created on engine startup and persist between any level changes (including savegame loading).
For most of the level scripting you should use an EventHandler; StaticEventHandler is only needed if you need to do something advanced, like storing some non-playsim data locally.

Setting up

In order to make the event handler work, you need to declare it in MAPINFO. For this, two variants are supported: in map definition and in gameinfo definition.

Handling events

Each possible event is done as a virtual method in the StaticEventHandler/EventHandler classes which can be overridden.
Note that you don't need to call the original methods because the default implementation is empty and does nothing.

Method Data available (in the provided event) Description
void OnRegister() Called when the engine registers your handler (adds it to the list).
Initialization can be performed here.
void OnUnregister() Called when the engine removes your handler from the list.
void WorldLoaded(WorldEvent e)
  • bool IsSaveGame
  • bool IsReopen
IsSaveGame only works for StaticEventHandlers. By using this field you can detect that the current level was loaded from a saved game.
IsReopen will be true when the player returns to the same map in a hub, similar to REOPEN ACS script type.
void WorldUnloaded(WorldEvent e)
  • bool IsSaveGame
IsSaveGame only works for StaticEventHandlers. By using this field you can detect that the current level is unloaded to load a saved game.

void WorldThingSpawned(WorldEvent e)
void WorldThingDied(WorldEvent e)
void WorldThingRevived(WorldEvent e)
void WorldThingDestroyed(WorldEvent e)

  • Actor Thing
These events are received just after the specified Thing was spawned or revived, and just before it dies or gets destroyed.

Internally, WorldThingSpawned is called just after PostBeginPlay.

void WorldThingDamaged(WorldEvent e)
  • Actor Thing
  • Actor Inflictor
  • int Damage
  • Actor DamageSource
  • Name DamageType
  • EDmgFlags DamageFlags
  • double DamageAngle
The arguments for this event are the same as the DamageMobj arguments.

DamageAngle can be different from direct angle to Inflictor if portals are involved — beware.
Thing is the actor that got damaged.

void WorldLightning(WorldEvent e) Same as LIGHTNING ACS script type.
void WorldTick() Calls at the beginning of each tick, 35 times per second.

void PlayerEntered(PlayerEvent e)
void PlayerRespawned(PlayerEvent e)
void PlayerDied(PlayerEvent e)
void PlayerDisconnected(PlayerEvent e)

  • int PlayerNumber
  • bool IsReturn
These are generally the same as their ACS counterparts.
PlayerEntered is called when players connect.
PlayerRespawned calls when players respawn (or use the resurrect cheat).
PlayerDied calls when players die (along with WorldThingDied)
PlayerDisconnected calls at the same point as DISCONNECT scripts (this is generally useless with P2P networking).

PlayerNumber is the player that was affected. You can receive the actual player object from the global players array like this:

 PlayerInfo player = players[e.PlayerNumber];

IsReturn will be true if this player returns to this level in a hub.

void RenderOverlay(RenderEvent e)
  • Vector3 ViewPos
  • double ViewAngle
  • double ViewPitch
  • double ViewRoll
  • double FracTic
  • Actor Camera
This event can be used to display something on the screen.

Note that it works locally and in ui context, which means you can't modify actors and have to make sure what player you are drawing it for (using consoleplayer global variable).

bool UiProcess(UiEvent e)
  • EGUIEvent Type
  • String KeyString (only for key events)
  • int KeyChar (only for key events)
  • int MouseX (only for mouse events)
  • int MouseY (only for mouse events)
  • bool IsShift
  • bool IsAlt
  • bool IsCtrl
By using this event you can receive UI input in the event handler. UI input is different from game input in that you can receive absolute screen mouse position instead of mouse deltas, and keyboard events are a bit more suitable for text input.

This event will only be received if you set your EventHandler in UI mode, e.g. by doing this:

 self.IsUiProcessor = true;

Additionally, mouse events will only be received if you also set RequireMouse to true:

 self.RequireMouse = true;

KeyChar is the ASCII value for the key, while KeyString is a single-character string that contains the character provided for convenience, as ZScript doesn't provide a char type.

Note: this is one of the few non-void methods in the event system. By returning true here you will block any processing of this event by the other event handlers if their Order is lower than the current EventHandler.
Also, if you need to interact with the world upon receiving an event, you have to use EventHandler.SendNetworkEvent (see: networking).

bool InputProcess(InputEvent e)
  • EGenericEvent Type
  • int KeyScan
  • int KeyChar
  • String KeyString
  • int MouseX (only for mouse event)
  • int MouseY (only for mouse event)
This event provides direct interface to the commonly used player input. You don't need any special steps in order to use it.

MouseX and MouseY are delta values (offsets from the last mouse position). These are internally used for player aiming.
KeyScan is the internal key value. Note that, while a bit counter-intuitive (for people unfamiliar with the console bind system), mouse buttons are considered keys for this event.
For example, a left mouse click is registered as KeyDown+InputEvent.Key_Mouse1.

Note: this is one of the few non-void methods in the event system. By returning true here you will block any processing of this event by the other event handlers if their Order is lower than the current EventHandler.
In case of InputEvent, returning true will also mean that the generic game input handler will NOT receive the event (player will be locked from moving).
Also, if you need to interact with the world upon receiving an event, you have to use EventHandler.SendNetworkEvent (see: networking).

void UiTick() This is the same as WorldTick, except it also runs outside of the level (only matters for StaticEventHandlers) and runs in the ui context.
void ConsoleProcess(ConsoleEvent e)
  • String Name
  • int Args[3]
This event is called when the player uses the "event" console command. It runs in the ui context.

For example, when the player runs this command:

 event testevent 1 2 3

The event handler will receive Name as "testevent" and Args[0]...Args[2] as {1,2,3}.

void NetworkProcess(ConsoleEvent e)
  • int Player
  • String Name
  • int Args[3]
  • bool IsManual
This event is called either when the player uses the "netevent" console command, or when EventHandler.SendNetworkEvent is used.

To distinguish between these two cases, you can use IsManual. This field will be true if the event was produced manually through the console.
Player is the number of the player that activated the event.
This is generally similar to using the "puke" command for ACS.

Event handler order

Event handler order defines the order in which user interaction-related events are received by the handlers.
It also defines the order in which RenderOverlay events are received.

For input events, the higher order receives the event first, while for render events the higher order receives the event last (thus drawing on top of everything).

You can set the order only in OnRegister callback, like this:

 override void OnRegister()

The value is arbitrary and it only matters in relation to other event handler order. Default order is 0.


Since GZDoom 2.4, scripts that are allowed to directly interact with the user and scripts that are actively involved in the world processing are separated. World scripts are called "play scope" and user interaction scripts are called "ui scope". Altering the world from ui context is not possible; for details see object scopes and versions.

In order to perform some world action when the user clicks in a menu or presses some key in an InputEvent handler, you need to use the combination of EventHandler.SendNetworkEvent and an event handler that has an override of NetworkProcess.

In your ui, you execute it as follows:

 EventHandler.SendNetworkEvent("myevent", 666, 23, -1);

Then any event handlers that handle NetworkProcess will receive an event with Name equal to "myevent" and Args equal to {666,23,-1}.

Passing strings to network events

Any event handler can handle any name, so if you need to send a string you can simply check for Name starting with a value, for example (space does not work for this when typing directly into the console - you need an alias or SendNetworkEvent, or put the thing in quotes):


where the event handler in question will find the strings on either side of the colon and deal with them accordingly:

 class ItemGiveEventHandler : EventHandler
   override void NetworkProcess (ConsoleEvent e)
     if (IsManual || e.Player < 0 || !(players [e.Player]) || !(players [e.Player].mo))
     let player = players [e.Player].mo;
     Array<string> command;
     e.Name.Split (command, ":");
     if(command.Size() != 2 || !(command [0] ~== "giveitem"))
     player.GiveInventory (command [1], 1);

Without the IsManual check, this event handler as written would let any player type "event giveitem:BFG9000" ingame to get a BFG and the engine would not recognize it as a cheat.