Skip to content

Using the event system

The event system is a powerful feature of TTT2 that allows content creators to display rich event information in the round end screen, assign scores or add data to the log. In the following article it is explained how it can be used.

Round end screen with events

Icon Padding


If you just plan on modifying the scoring of your role, check out the role scoring variables first.

Registering an event

An event is registered by simply placing an event file in the event folder of your addon. The correct path is lua/terrortown/events/. Any name can be used, but it should be unique. A list of existing TTT2 events can be found in the repository. A documentation of the event base can be found in the API docs.

Basic event

A basic event needs only the Trigger(...) function (which can have an arbitrary amount of parameters). However it is recommended to also define an icon, title and the GetText() function. Serialize() is recommended as well because it logs the event to the log file. CalculateScore() is used if the event should generate score.


The event name is derived from the file name and stored in a global variable that can be accessed with EVENT_<EVENT_NAME>.

Custom defined language strings (title, ...) do not have to use the <event_name> notation, but it is recommended to do so.

if CLIENT then
    EVENT.icon = Material("path/to/material")
    EVENT.title = "title_event_<eventname>"

    function EVENT:GetText()
        -- each returned subtable is rendered as one paragraph
        return {
                string = "desc_event_<eventname>",
                params = {
                    player = self.event.finder.nick,
                translateParams = false
                -- ...

if SERVER then
    -- this function is triggered when the event is triggered
    function EVENT:Trigger(finder)

        return self:Add({
            -- string indexed data can be added here
            finder = {
                nick = finder:Nick(),
                sid64 = finder:SteamID64()

    -- this function is triggered to calculate the score
    function EVENT:CalculateScore()
        self:SetPlayerScore(self.event.finder.sid64, {
            score = 3

-- simple text to store in the log file
function EVENT:Serialize()
    return self.event.finder.nick .. " has done something."

The icon is a simple white on transparent graphic with a resolution of 512x512 and a few empty pixels all the way around. Check out the icon and design guideline to see how to create these icons.


There are a few strings that should be added to the localization file. The following is almost always needed:

title_event_<eventname> = "My cool event"

If the event has a description, the description should be localized as well. In our example it is a single line that has to be added:

desc_event_<eventname> = "{player} has done something."

If the event also grants a score, two more strings are needed. These identifiers are automatically generated:

tooltip_<event_name>_score = "My event: {score}"
<event_name>_score = "My event:"

[Check out this really basic example]


Once you understood the basic principle of these events, you can use inheritance to modify or extend existing events. This might be useful if you plan on overloading the kill event.

By default the event base is always base_event. This does not have to be specified as it is automatically assumed. If you want to inherit from a different existing event, just set the base accordingly. You can even call the parent functions with the BaseClass pointer.

[Check out this example of a modified kill event]

Triggering an event

If you've finnished your event and want to execute it, you have to run a single function: events.Trigger() (docs).

Our previuosly created event would be called like this:

events.Trigger(EVENT_<EVENT_NAME>, somePlayer)


Event data is automatically networked from the server to the client after the round has ended. However not everything is synced. While there are multiple entries that are synced, you should only modify the self.event table if you want to add synced data. Data directly added to the class (e.g. self.myData = xyz) is not synced.


Karma-changes are by default not synced per event to save networking resources. If you still need to know karma changes at certain events use this:

function EVENT:ShouldKarmaChangeSynchronize()
    return true

Synchronization is used in the finish-event to display the latest karma changes on roundendscreen.

Cancel or replace an event

Once an event is triggered, a callback hook is run. If this hook returns false, the event is not added to the queue. This can be used to either remove events or replace them with your own.

-- This hook is called once an event occured, the data is processed
-- and it is about to be added. This hook can be used to modify the data
-- or to cancel the event by returning `false`.
-- @param string type The type of the event as in `EVENT_XXX`
-- @param table eventData The table with the event data
-- @return boolean Return false to cancel the addition of this event
-- @realm server
-- @hook
function GM:TTT2OnTriggeredEvent(type, eventData)


You shouldn't replace EVENT_ROLECHANGE because this event is used to generate the rolechange list in the round end screen.

[Check out this example of a modified kill event that replaces the normal kill event]

React to an event

There's also a secondary hook that is called after the event is successfully added to the event list. It can be used to work with this event or even add more data to the event.

-- This hook is called after the event was successfully added to the
-- eventmanager.
-- @param string type The type of the event as in `EVENT_XXX`
-- @param EVENT event The event that was added with all its functions
-- @realm server
-- @hook
function GM:TTT2AddedEvent(type, event)