Difference between revisions of "Events (SourceMod Scripting)"

From AlliedModders Wiki
Jump to: navigation, search
m (Post Hooks)
 
(20 intermediate revisions by 10 users not shown)
Line 1: Line 1:
 +
:''To view all the events, click [[Game Events (Source)|here]].''
 +
 
Events are short, named messages sent by the server.  Although they are used for internal message passing, they are also networked to clients.
 
Events are short, named messages sent by the server.  Although they are used for internal message passing, they are also networked to clients.
  
All event natives are found in <tt>plugins/include/events.inc</tt>.
+
Events are used for internal messages (such as when the server starts) and game events (such as when players take damage or spawn). Events are a simple way to hook events that SourceMod does not have dedicated support for, such as player spawns.
 +
However, game events are '''mod-defined''', so you will need to ensure your behavior is correct for every mod you support, or you will have compatibility issues.
 +
 
 +
All event natives are found in <tt>scripting/include/events.inc</tt>.
  
 
=Introduction=
 
=Introduction=
Line 7: Line 12:
  
 
For example, let's look at <tt>player_death</tt> from <tt>hl2/resource/gameevents.res</tt>:
 
For example, let's look at <tt>player_death</tt> from <tt>hl2/resource/gameevents.res</tt>:
<pre> "player_death"
+
<pre>"player_death"
{
+
{
"userid" "short"   // user ID who died
+
    "userid"   "short"     // user ID who died            
"attacker" "short" // user ID who killed
+
    "attacker" "short"     // user ID who killed
}</pre>
+
}</pre>
  
 
Counter-Strike:Source extends this definition in <tt>cstrike/resource/modevents.res</tt>:
 
Counter-Strike:Source extends this definition in <tt>cstrike/resource/modevents.res</tt>:
<pre> "player_death"
+
<pre>"player_death"
{
+
{
"userid" "short"   // user ID who died
+
    "userid"   "short"     // user ID who died            
"attacker" "short" // user ID who killed
+
    "attacker" "short"     // user ID who killed
"weapon" "string" // weapon name killer used  
+
    "weapon"   "string"   // weapon name killer used  
"headshot" "bool" // singals a headshot
+
    "headshot" "bool"     // signals a headshot
}</pre>
+
}</pre>
  
 
Note that the event is structured in the following format:
 
Note that the event is structured in the following format:
<code>"name"
+
<pre>"name"
 
{
 
{
"key1" "valueType1"
+
    "key1" "valueType1"
}</code>
+
    "key2"  "valueType2"
 +
    ...
 +
}</pre>
  
 
=Sending Events=
 
=Sending Events=
 
Events are very easy to send.  For example, let's say we want to send a death message using the <tt>player_death</tt> event from above.  For Counter-Strike:Source, this would look like:
 
Events are very easy to send.  For example, let's say we want to send a death message using the <tt>player_death</tt> event from above.  For Counter-Strike:Source, this would look like:
  
<pawn>SendDeathMessage(attacker, victim, const String:weapon[], bool:headshot)
+
<sourcepawn>void SendDeathMessage(int attacker, int victim, const char[] weapon, bool headshot)
 
{
 
{
new Handle:event = CreateEvent("player_death")
+
    Event event = CreateEvent("player_death");
if (event == INVALID_HANDLE)
+
    if (event == null)
{
+
    {
return
+
        return;
}
+
    }
  
SetEventInt(event, "userid", GetClientUserId(victim))
+
    event.SetInt("userid", GetClientUserId(victim));
SetEventInt(event, "attacker", GetClientUserId(attacker))
+
    event.SetInt("attacker", GetClientUserId(attacker));
SetEventString(event, "weapon", weapon)
+
    event.SetString("weapon", weapon);
SetEventBool(event, "headshot", headshot)
+
    event.SetBool("headshot", headshot);
FireEvent(event)
+
    event.Fire();
}</pawn>
+
}</sourcepawn>
  
 
Notes:
 
Notes:
 
*You don't need to call <tt>CloseHandle()</tt>, <tt>FireEvent()</tt> does this for us.
 
*You don't need to call <tt>CloseHandle()</tt>, <tt>FireEvent()</tt> does this for us.
 
*Even though "userid" and "attacker" are shorts, we set them as ints.  The term "short" is only used to tell the engine how many bytes of the integer are needed to be networked.
 
*Even though "userid" and "attacker" are shorts, we set them as ints.  The term "short" is only used to tell the engine how many bytes of the integer are needed to be networked.
*It is possible for event creation to fail; this can happen if the event does not exist, or nothing is hooking the event.  Thus, you should always make sure <tt>CreateEvent</tt> calls return a valid Handle.
+
*It is possible for event creation to fail; this can happen if the event does not exist, or nothing is hooking the event.  Thus, you should always make sure <tt>CreateEvent</tt> calls return a valid Event handle.
 
*Most events use client userids instead of client indexes.
 
*Most events use client userids instead of client indexes.
 
*By default, <tt>FireEvent()</tt> broadcasts messages to clients.  This can be prevented by passing <tt>dontBroadcast</tt> as true.
 
*By default, <tt>FireEvent()</tt> broadcasts messages to clients.  This can be prevented by passing <tt>dontBroadcast</tt> as true.
Line 59: Line 66:
 
*<tt>Post_NoCopy</tt> - Hook the event, but do not save any of its information (special optimization).
 
*<tt>Post_NoCopy</tt> - Hook the event, but do not save any of its information (special optimization).
  
Hooking an event is usually done for one of the following goals.  The get an idea of which mode to use, see the list below each goal:
+
Hooking an event is usually done for one of the following goals.  To get an idea of which mode to use, see the list below each goal:
 
*Blocking the event (preventing it from being fired)
 
*Blocking the event (preventing it from being fired)
 
**'''Always <tt>Pre</tt>'''
 
**'''Always <tt>Pre</tt>'''
Line 74: Line 81:
 
Blocking events is the easiest thing to do.  Let's say we want to block death events that are headshots:
 
Blocking events is the easiest thing to do.  Let's say we want to block death events that are headshots:
  
<pawn>public OnPluginStart()
+
<sourcepawn>public void OnPluginStart()
 
{
 
{
HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre)
+
    HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre);
 
}
 
}
  
public Action:Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
+
public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
 
{
 
{
if (GetEventBool(event, "headshot"))
+
    if (event.GetBool("headshot"))
{
+
    {
return Plugin_Handled
+
        return Plugin_Handled;
}
+
    }
return Plugin_Continue
+
    return Plugin_Continue;
}</pawn>
+
}</sourcepawn>
 +
 
 +
{{CommonMistake|Blocking an event only blocks other parts of the game from receiving the event; the source of the event may still take action (such as by damaging players)!}}
  
 
==Rewriting Events==
 
==Rewriting Events==
 
Rewriting events is just as easy -- events can be modified in pre hooks.  For example, say we want to remove headshots from all events:
 
Rewriting events is just as easy -- events can be modified in pre hooks.  For example, say we want to remove headshots from all events:
  
<pawn>public OnPluginStart()
+
<sourcepawn>public void OnPluginStart()
 
{
 
{
HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre)
+
    HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre);
 
}
 
}
  
public Action:Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
+
public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
 
{
 
{
SetEventBool(event, "headshot", false)
+
    event.SetBool("headshot", false);
return Plugin_Continue
+
    return Plugin_Continue;
}</pawn>
+
}</sourcepawn>
  
 
==Post Hooks==
 
==Post Hooks==
 +
{{CommonMistake|Post hooks run after the game has handled the event, but the event source may still take action after the event has finished!
 +
This is a potentially sneaky source of recursion and stack overflow crashes, so watch out!
 +
}}
 
Post hooks are default, and will usually be the most common usage.  For example, say we want to print a message to every client that dies:
 
Post hooks are default, and will usually be the most common usage.  For example, say we want to print a message to every client that dies:
  
<pawn>public OnPluginStart()
+
<sourcepawn>public void OnPluginStart()
 
{
 
{
HookEvent("player_death", Event_PlayerDeath)
+
    HookEvent("player_death", Event_PlayerDeath);
 
}
 
}
  
public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
+
public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
 
{
 
{
decl String:weapon[64]
+
    char weapon[64];
new victimId = GetEventInt(event, "userid")
+
    int victimId = event.GetInt("userid");
new attackerId = GetEventInt(event, "attacker")
+
    int attackerId = event.GetInt("attacker");
new bool:headshot = GetEventBool(event, "headshot")
+
    bool headshot = event.GetBool("headshot");
GetEventString(event, "weapon", weapon, sizeof(weapon))
+
    event.GetString("weapon", weapon, sizeof(weapon));
  
decl String:name[64]
+
    char name[64];
new victim = GetClientOfUserId(victimId)
+
    int victim = GetClientOfUserId(victimId);
new attacker = GetClientOfUserId(attackerId)
+
    int attacker = GetClientOfUserId(attackerId);
GetClientName(attacker, name, sizeof(name))
+
    GetClientName(attacker, name, sizeof(name));
  
PrintToConsole(victim,
+
    PrintToConsole(victim,
"You were killed by \"%s\" (weapon \"%s\") (headshot \"%d\")",
+
        "You were killed by \"%s\" (weapon \"%s\") (headshot \"%d\")",
name,
+
        name,
weapon,
+
        weapon,
headshot)
+
        headshot);
}</pawn>
+
}</sourcepawn>
  
 
This will print a message to a player's console telling them who killed them, with what weapon, and whether it was a headshot or not.
 
This will print a message to a player's console telling them who killed them, with what weapon, and whether it was a headshot or not.
Line 139: Line 151:
 
For example, let's say we want to find when a certain sequence of events is called.
 
For example, let's say we want to find when a certain sequence of events is called.
  
<pawn>public OnPluginStart()
+
<sourcepawn>public void OnPluginStart()
 
{
 
{
HookEvent("game_newmap", GameEvents, EventHookMode_PostNoCopy)
+
    HookEvent("game_newmap", GameEvents, EventHookMode_PostNoCopy);
HookEvent("game_start", GameEvents, EventHookMode_PostNoCopy)
+
    HookEvent("game_start", GameEvents, EventHookMode_PostNoCopy);
HookEvent("game_end", GameEvents, EventHookMode_PostNoCopy)
+
    HookEvent("game_end", GameEvents, EventHookMode_PostNoCopy);
HookEvent("game_message", GameEvents, EventHookMode_PostNoCopy)
+
    HookEvent("game_message", GameEvents, EventHookMode_PostNoCopy);
 
}
 
}
  
public GameEvents(Handle:event, const String:name[], bool:dontBroadcast)
+
public void GameEvents(Event event, const char[] name, bool dontBroadcast)
 
{
 
{
PrintToServer("Event has been fired (event \"%s\") (nobcast \"%d\")", name, dontBroadcast)
+
    PrintToServer("Event has been fired (event \"%s\") (nobcast \"%d\")", name, dontBroadcast);
}</pawn>
+
}</sourcepawn>
  
Note that like normal <tt>Post</tt> hooks, there is no return value needed.  However, the <tt>event</tt> parameter for <tt>PostNoCopy</tt> will '''always''' be equal to <tt>INVALID_HANDLE</tt>.  Thus, the <tt>name</tt> parameter must be used instead of <tt>GetEventName</tt>.
+
Note that like normal <tt>Post</tt> hooks, there is no return value needed.  However, the <tt>event</tt> parameter for <tt>PostNoCopy</tt> will '''always''' be equal to <tt>null</tt>.  Thus, the <tt>name</tt> parameter must be used instead of <tt>event.GetName</tt>.
  
 
[[Category:SourceMod Scripting]]
 
[[Category:SourceMod Scripting]]
[[Category:SourceMod Development]]
+
 
 +
{{LanguageSwitch}}

Latest revision as of 13:57, 19 May 2024

To view all the events, click here.

Events are short, named messages sent by the server. Although they are used for internal message passing, they are also networked to clients.

Events are used for internal messages (such as when the server starts) and game events (such as when players take damage or spawn). Events are a simple way to hook events that SourceMod does not have dedicated support for, such as player spawns. However, game events are mod-defined, so you will need to ensure your behavior is correct for every mod you support, or you will have compatibility issues.

All event natives are found in scripting/include/events.inc.

Introduction

Events are documented in .res files under a mod's resource folder. The "default" events are located in hl2/resource/gameevents.res and hl2/resource/serverevents.res. Mods can extend these events with their own.

For example, let's look at player_death from hl2/resource/gameevents.res:

"player_death"
{
    "userid"    "short"     // user ID who died             
    "attacker"  "short"     // user ID who killed
}

Counter-Strike:Source extends this definition in cstrike/resource/modevents.res:

"player_death"
{
    "userid"    "short"     // user ID who died             
    "attacker"  "short"     // user ID who killed
    "weapon"    "string"    // weapon name killer used 
    "headshot"  "bool"      // signals a headshot
}

Note that the event is structured in the following format:

"name"
{
    "key1"  "valueType1"
    "key2"  "valueType2"
    ...
}

Sending Events

Events are very easy to send. For example, let's say we want to send a death message using the player_death event from above. For Counter-Strike:Source, this would look like:

void SendDeathMessage(int attacker, int victim, const char[] weapon, bool headshot)
{
    Event event = CreateEvent("player_death");
    if (event == null)
    {
        return;
    }
 
    event.SetInt("userid", GetClientUserId(victim));
    event.SetInt("attacker", GetClientUserId(attacker));
    event.SetString("weapon", weapon);
    event.SetBool("headshot", headshot);
    event.Fire();
}

Notes:

  • You don't need to call CloseHandle(), FireEvent() does this for us.
  • Even though "userid" and "attacker" are shorts, we set them as ints. The term "short" is only used to tell the engine how many bytes of the integer are needed to be networked.
  • It is possible for event creation to fail; this can happen if the event does not exist, or nothing is hooking the event. Thus, you should always make sure CreateEvent calls return a valid Event handle.
  • Most events use client userids instead of client indexes.
  • By default, FireEvent() broadcasts messages to clients. This can be prevented by passing dontBroadcast as true.

Hooking Events

When hooking an event, there are three modes to choose from:

  • Pre - Hook the event before it is fired.
  • Post - Hook the event after it is fired.
  • Post_NoCopy - Hook the event, but do not save any of its information (special optimization).

Hooking an event is usually done for one of the following goals. To get an idea of which mode to use, see the list below each goal:

  • Blocking the event (preventing it from being fired)
    • Always Pre
  • Rewriting the event (changing its parameters)
    • Always Pre
  • Acting upon the event (doing something once the event is completed)
    • Pre if your action must come before the mod's action.
    • Post if your action must come after the mod's action.
    • PostNoCopy if your action is Post and only requires the event name.

As always, you do not need to unhook events when your plugin unloads. They are automatically removed.

Blocking Events

Blocking events is the easiest thing to do. Let's say we want to block death events that are headshots:

public void OnPluginStart()
{
    HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre);
}
 
public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
    if (event.GetBool("headshot"))
    {
        return Plugin_Handled;
    }
    return Plugin_Continue;
}

Blocking an event only blocks other parts of the game from receiving the event; the source of the event may still take action (such as by damaging players)!

Rewriting Events

Rewriting events is just as easy -- events can be modified in pre hooks. For example, say we want to remove headshots from all events:

public void OnPluginStart()
{
    HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre);
}
 
public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
    event.SetBool("headshot", false);
    return Plugin_Continue;
}

Post Hooks

Post hooks run after the game has handled the event, but the event source may still take action after the event has finished! This is a potentially sneaky source of recursion and stack overflow crashes, so watch out!

Post hooks are default, and will usually be the most common usage. For example, say we want to print a message to every client that dies:

public void OnPluginStart()
{
    HookEvent("player_death", Event_PlayerDeath);
}
 
public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
    char weapon[64];
    int victimId = event.GetInt("userid");
    int attackerId = event.GetInt("attacker");
    bool headshot = event.GetBool("headshot");
    event.GetString("weapon", weapon, sizeof(weapon));
 
    char name[64];
    int victim = GetClientOfUserId(victimId);
    int attacker = GetClientOfUserId(attackerId);
    GetClientName(attacker, name, sizeof(name));
 
    PrintToConsole(victim,
        "You were killed by \"%s\" (weapon \"%s\") (headshot \"%d\")",
        name,
        weapon,
        headshot);
}

This will print a message to a player's console telling them who killed them, with what weapon, and whether it was a headshot or not.

Note that the return value for post hooks is ignored, so the Action tag is not needed.

PostNoCopy Hooks

Lastly, there are some hooks where the only piece of information needed is the name of the event. PostNoCopy is a special optimization for this case. When transitioning from Pre to Post, SourceMod must duplicate the event and all of its key/value pairs. PostNoCopy prevents that sequence from happening.

For example, let's say we want to find when a certain sequence of events is called.

public void OnPluginStart()
{
    HookEvent("game_newmap", GameEvents, EventHookMode_PostNoCopy);
    HookEvent("game_start", GameEvents, EventHookMode_PostNoCopy);
    HookEvent("game_end", GameEvents, EventHookMode_PostNoCopy);
    HookEvent("game_message", GameEvents, EventHookMode_PostNoCopy);
}
 
public void GameEvents(Event event, const char[] name, bool dontBroadcast)
{
    PrintToServer("Event has been fired (event \"%s\") (nobcast \"%d\")", name, dontBroadcast);
}

Note that like normal Post hooks, there is no return value needed. However, the event parameter for PostNoCopy will always be equal to null. Thus, the name parameter must be used instead of event.GetName.

Warning: This template (and by extension, language format) should not be used, any pages using it should be switched to Template:Languages

View this page in:  English  Russian  简体中文(Simplified Chinese)