Listening to game events in CS:GO

From Valve Developer Community
Jump to: navigation, search

CS:GO vscript API is missing crucial functions for event listening. However, you can still use the event data fetched from logic_eventlisteners in your custom maps. Fetching event data option was added to CS:GO in 2016.

There are multiple ways of acessing the script scope of an event listener, which is where the event data are dumped. Only one method will be demonstrated here.

You can find event data contents in /resources/*events.res files.

Setting up

Create your event listener in Hammer Editor, add targetname and enable the FetchEventData keyvalue. Add an output that executes an OnGameEvent_ function in itself with the event_data parameter. Note that the targetname and function names are arbitrary, but it is always good practice to be consistent.

Example event player_say

logic_eventlistener keyvalues:

targetname: player_say
EventName:  player_say
FetchEventData: Yes

logic_eventlistener output:

OnEventFired > player_say > RunScriptCode > ::OnGameEvent_player_say(event_data)

In your script, create your event callback function and bind it using .bindenv(this):

::OnGameEvent_player_say <- function( event )
{
	ScriptPrintMessageChatAll( event.userid + " says " + event.text )
}.bindenv(this) // environment binding

Alternatively you can bind named functions. This would also let you dynamically change the event callback function back and forth in runtime.

// Your event function
function OnPlayerSay( event )
{
	ScriptPrintMessageChatAll( event.userid + " says " + event.text )
}

::OnGameEvent_player_say <- OnPlayerSay.bindenv(this)

// these can be done anywhere in the code
// ::OnGameEvent_player_say = OnPlayerSay2.bindenv(this)
// ::OnGameEvent_player_say = OnPlayerSay3.bindenv(this)

Getting player userid, SteamID and Steam names

As mentioned before, the API to get these information is not available. The workaround to getting userids is listening to an event that dumps a userid which was triggered by a known player, then getting SteamIDs from the player_connect event data using these associated userids.

You can simplify all of this by using vs_library, a third party vscript library that handles it for you.

After following instructions on installation and setting up required event listeners, you can get player info from their script scope, and get player script handles from their userids using VS.GetPlayerByUserid.

Example code that sets the health of the player that types "!hp" in chat:

::OnGameEvent_player_say <- function( event )
{
	// get the chat message
	local msg = event.text

	// require all chat commands to be prepended with a symbol (!)
	// if the message isn't a command, leave
	if ( msg[0] != '!' )
		return

	local player = VS.GetPlayerByUserid( event.userid )
	SayCommand( player, msg )

}.bindenv(this)

function SayCommand( player, msg )
{
	// pass the text after the command symbol (slice '!'), split the message by spaces
	local argv = ::split( msg.slice(1), " " )
	local argc = argv.len()

	// 'argv[0]' is the command
	// values separated with " " can be accessed with 'argv[1]', 'argv[2]'...

	local value
	if ( argc > 1 )
		value = argv[1]

	// Your chat commands are string cases in this switch statement.
	// Strings are case sensitive.
	// If you'd like to make them insensitive, you can add 'tolower' to the command string
	// In this case, every case string needs to be lower case.
	switch ( argv[0].tolower() )
	{
		// multiple chat messages can execute the same code
		case "hp":
		case "health":
		{
			CommandSetHealth( player, value )
			break
		}
	//	default:
	//		Msg("Invalid command.\n")
	}
}

function CommandSetHealth( player, health )
{
	// if health is null, the message did not have a value
	// if player is null, the player was not found. Player disconnected, or unexpected error
	if ( !health || !player )
		return

	// 'value' is string, convert to int
	// if a character exists before the number, cannot convert - invalid input
	// example invalid message: "!hp m26"
	// but this will work: "!hp 26m"
	try( health = health.tointeger() )

	// invalid value
	catch(e){ return }

	// clamp the value
	if ( health < 1 )
		health = 1

	player.SetHealth( health )

	local sc = player.GetScriptScope()

	ScriptPrintMessageChatAll( sc.name + " (" + sc.networkid + ") set their health to " + health )
}

Use on dedicated servers

The player_connect event is fired only once when a player connects to the server. For this reason, it is not possible to get the Steam name and SteamIDs of players that were connected to the server prior to a map change. This data will only be available for players that connect to the server while your map is running.

This is not an issue for singleplayer and coop maps that are locally hosted (unless the map is changed while another is loaded).

This also breaks automatic userid validation, requiring manual work. To manually validate every player, execute VS.ValidateUseridAll() on the round_start event (or round_freeze_end, this is dependant on your map and how the data is used). Note that this validation is asynchronous, meaning you cannot access player userids in the same frame as validating them.

External links

See also