Four days ago I introduced you to the idea of SourceScript, now I will tell you about the progress I’ve made the past few days.
Hooks
All the global hooks have been added, but not fully implemented yet, but I’ll tell you about that later. What do I mean by ‘global’ hooks? Well, when writing a server plugin you can create a game event ‘listener’ that tells the plugin when something happens, like a player connecting or the map changing. So basically Source’s built in hook system.
In the previous post I told you that SourceScript will run on any Source Engine game or mod, but obviously Team Fortress 2 has some events that doesn’t exist in Counter-Strike: Source and vice versa. So, global hooks are things like PlayerConnect and PlayerSpawn that exist in any multiplayer game.
Now, these are the added hooks:
- Frame - Pretty much Garry’s Mod’s Think hook
- MapInit - When the map has been loaded
- MapShutdown - When the map is being changed or the server is shutdown
- PlayerActivate - Right before a player spawns
- ServerActivate - The map has completed loading
- PlayerDisconnect - Duh
- PlayerInServer - Also a prespawn hook
- PlayerSettingsChanged - Someone’s nick or other setting changed
- PlayerConnect - Doh
- PlayerCommand - When a player runs a console command
- GameEvent - This is for all game specific events like ‘hostage_follow’
The last hook is an interesting one. Soon that one will be divided into actual hooks, but for now you’ll have to deal with it in Lua:
function Event( eventname )
Msg( "A game event has fired: " .. eventname .. "\n" )
end
hook.Add( "GameEvent", Event )
Players
Now, all the above hooks lack one simple thing. That’s passing a usable player object like seen in Garry’s Mod. I started off by just passing the entity index of players, but this started to get boring quickly.
Then I spend two days learning about metatables and calling them from Lua. Metatables are basically tables defining how other tables should be handled. If you want to know more about them, read this excellent tutorial by Deco Da Man. Yesterday I rewrote the PlayerActivate hook, so it uses the player object. The C++ code so far is really messy, because I just learned how to do it:
void hook_clientactivate(int entid)
{
// First get the player table using the entity index
lua_getfield( lua, LUA_GLOBALSINDEX, "Player" ); // Look up the table 'Player'
lua_getfield( lua, -1, "GetByIndex" ); // Get the member 'GetByIndex'
lua_remove( lua, -2 ); // Remove the table 'Player' from the stack
lua_getfield( lua, LUA_GLOBALSINDEX, "Player" ); // Push the table 'Player' as the first argument
lua_pushnumber( lua, entid ); // Push the entity index
lua_call( lua, 2, 1 ); // Call the function Player:GetByIndex()
lua_setfield( lua, LUA_GLOBALSINDEX, "tplayer" );
// Now, call the hook with the achieved player table
lua_getfield( lua, LUA_GLOBALSINDEX, "hook" );
lua_getfield( lua, -1, "Call" );
lua_pushstring( lua, "PlayerActivate" );
lua_getfield( lua, LUA_GLOBALSINDEX, "tplayer" );
lua_call( lua, 2, 0 );
}
I will probably rewrite it tomorrow, so it’ll work as easy as other hooks work now:
void hook_clientconnect(string nick, string ip)
{
hook_start( "PlayerConnect" );
lua_pushstring( lua, nick.c_str() );
lua_pushstring( lua, ip.c_str() );
hook_finish(2);
}
Eventually I wrote the following script (Which actually works
):
function Act( ply )
Msg( "Player activated: " .. ply:Nick() .. "\n" )
end
hook.Add( "PlayerActivate", Act )
Doesn’t seem so exciting, but it’s pretty cool to see this in the console, knowing that it’s powered by a Lua script :3:

Stay tuned for more updates on the player object and don’t forget to let me know of your thoughts, since you’re a potential scripter who’s going to use this