Lua tutorial

From Valve Developer Community


This tutorial will show you how to use LuaBind with your mod, great things can be achieved with it. Just look at Garry's Mod.

Table of contents

1 Fixes

What is Lua?

Lua is a powerful, fast, light-weight, embeddable scripting language. Lua combines simple procedural syntax with powerful data description constructs based on associative arrays and extensible semantics. Lua is dynamically typed, runs by interpreting bytecode for a register-based virtual machine, and has automatic memory management with incremental garbage collection, making it ideal for configuration, scripting, and rapid prototyping.

What is Lua Bind?

Luabind is a library that helps you create bindings between C++ and Lua. It has the ability to expose functions and classes, written in C++, to Lua. It will also supply the functionality to define classes in lua and let them derive from other lua classes or C++ classes.

Let's get started

  • Download the files required for my tutorial from one of the following mirrors:
- http://files.filefront.com/packagerar/;9961540;/fileinfo.html
- http://www.sendspace.com/file/ibfucb
- http://www.wikiupload.com/download_page.php?id=26537
- http://www.massmirror.com/be15236056827538704b0c4b3cbd1910.html

Fixes

The link-entity-to-class stuff in the link provided above does not work properly. Please replace it with the following fixed version:


class LuaEntityFactory : public IEntityFactory
{
public:



	const char *ScriptName;
	void SetScript( const char *scr) { ScriptName = scr; }
	CBaseEntity* GetNewInstance( const char *pClassName) 
	{
		RunLuaFile( ScriptName );

		char fstr[ 512 ];
		Q_snprintf( fstr, sizeof( fstr ), "return %s()", pClassName);
		luaL_dostring( luaState, fstr );
		luabind::object obj( luabind::from_stack(luaState, -1) );
        if ( !obj || luabind::type(obj) == LUA_TNIL ) //neo
        {
			cvar->ConsoleColorPrintf( LUA_ERROR_PRINT_COLOUR, "[Lua] LINK_ENTITY_TO_CLASS, Hey put a valid object in it will you!\n");
			return NULL;
        }
        CBaseEntity* pEnt;
        try {
            pEnt = luabind::object_cast<CBaseEntity*>(obj );
        } catch ( const luabind::cast_failed& e ) {
            // deal with it
			cvar->ConsoleColorPrintf( LUA_ERROR_PRINT_COLOUR, "[Lua] LINK_ENTITY_TO_CLASS, Unable to cast your object to its base class! - %s|%s\n", e.what(), e.info());
			return NULL;
        }

		return pEnt;
	}

    IServerNetworkable *Create( const char *pClassName )
    {
		CBaseEntity* pEnt = GetNewInstance( pClassName );
        pEnt->PostConstructor(pClassName);
        return pEnt->NetworkProp();
    }
 
    void Destroy( IServerNetworkable *pNetworkable )
    {
        if ( pNetworkable )
        {
            pNetworkable->Release();
        }
    }
 
    virtual size_t GetEntitySize()
    {
        return 0;  // TODO: we could dispatch this to Lua somehow....
    }
};

static void LUA_LINK_ENTITY_TO_CLASS( const char* pClassName, const char* scriptname )
{
    static LuaEntityFactory lua_entity_factory;
	
	lua_entity_factory.SetScript( scriptname );
	 EntityFactoryDictionary()->InstallFactory( &lua_entity_factory, pClassName );

}


The new syntax for using the command in Lua is:

LINK_ENTITY_TO_CLASS( '''Your class name''', '''Filename please follow the Filename convention as shown below.''' ) 

LINK_ENTITY_TO_CLASS( 'CMyEnt', "lua\\CMyEnt.lua" ) 


Installing

Copying Tutorial Files Over

  • Copy the lua_bind folder from \source_code to somewhere sensible in your mod's code directory
  • Copy the mod\lua folder into your mod somewhere (so: sourcemods\yourmod\lua\sample.lua )

Adding include directories

  • Load Visual Studio
  • Goto Tools - > Options - > Projects and Solutions - > VC++ Directories
  • Change the 'Show directories for combobox to 'Include Directories'
  • Add the luabind\luabind-r507 folder
  • Add the luabind\lua-5.1.3\src folder
  • Add the luabind\boost-1.34.1\include folder
  • Apply the settings

Adding static library files

  • On your solution explorer select the Server Project and expand the 'Link Libraries' Filter
  • Add luabind\lua.x86.release.lib to the filter
  • Add luabind.x86.release.lib to the filter

Adding the 'binding files' to the server project

IMPORTANT NOTE: Please note this code was written for Orange box Scratch, if you are using episode 1 code you will need to change the cvar->ColourPrintf.... stuff to Msg. And if you are modding HL2DM you will to include the hl2mp_player header file and remove my include for the sdk player.

  • Add luabind\luabind_hl2.cpp to the server project
  • Add luabind\luabind_hl2.h to the server project

Adding initialization code to server

  • Open gameinterface.cpp and add #include "luabind/luabind_hl2.h" to the end of the existing includes, but before #include "tier0/memdbgon.h"
  • At around line 634 after IGameSystem::Add( SoundEmitterSystem() ); add Lua_Startup();

Explanation

Compile your mod to see if everything runs, whilst in-game try the commands: dosample1 and dosample2 you can find their definitions at the bottom of luabind_hl2.cpp

As you can see void Lua_Startup initializes our bindings, I have only included a few classes as an example please feel free to add more if you do I encourage you to post your bigger engine-exposure to me or on a sub-page. :)

If you need further info on binding please take a look at: http://www.rasterbar.com/products/luabind/docs.html. Code comments should be pretty clear if you need to ask me something drop me a mail or ask in #hlcoders on irc.gamesurge.net.

Usage

Calling Lua from C++

  • Firstly add the include to the file you which to use Lua from
 #include "luabind/luabind_hl2.h" 
  • Then when you wish to call your Lua from C++ add the following changing where appropriate, its pretty much self-explanatory.
lua_State *luaState = GetLuaState();

try
{
	if (RunLuaFile("lua\\player.lua"))
		luabind::call_function<void>(luaState, "playerspawn", (CBasePlayer*)this); // THIS HAS TO BE CASTED INTO SOMETHING WHICH LUABIND KNOWS ABOUT
}
catch( luabind::error ex)
{
	lua_State* L = ex.state();
	cvar->ConsoleColorPrintf( LUA_ERROR_PRINT_COLOUR, "[Lua] %s\n", lua_tostring(L, -1));
	lua_pop(L, 1); 
}

That example calls a function playerspawn in \lua\player.lua

function playerspawn ( p )
	p:SetHealth(333)
end

Objects in Lua

Here is a small example to explain this, it's sort of the same as Garrys Mod.

 local ent = CreateEntityByName( "prop_physics_multiplayer" )
 PrecacheModel( "models/airboat.mdl" )
 ent:SetModel( "models/airboat.mdl" )
 ent:SetAbsOrigin( UTIL_EntityByIndex(1):GetAbsOrigin() ) -- 1's always the local player
 ent:Precache()
 ent:Spawn()

Writing classes in Lua and linking them to entities

I have tested this further and it would seem you need to create wrapper classes if the code were to be properly called from c++: http://www.rasterbar.com/products/luabind/docs.html#deriving-in-lua I will post sample code for a wrapper soon.

 class 'CMyEnt' (CBaseEntity)

 function CMyEnt:__init() super(true) -- o_o the true in constructor of cbaseentity means server side only remember!

 end

 function CMyEnt:Spawn() --overwrite the spawn function
	Msg("ZOOORZ")
	--CBaseEntity.Spawn(self) -- this is just like BaseClass::Spawn(). crap isnt working :( commented out for now

 end

 LINK_ENTITY_TO_CLASS( 'CMyEnt' ) -- woot :p


 function sample() -- this is called from my example function

	local ent = CreateEntityByName( "CMyEnt" ) -- create an instance of the entity
	ent:Spawn() -- spawn it demonstration

 end

Extras

You might want to have a command to run a file/string on the go, such as Garry's Mod (lua_run ply:SetHealth(100) and whatnot)

Lua_Runfile console command

Here's the code you need for the runfile concommand:

CON_COMMAND( lua_runfile, "Run a Lua file")
{
	if(UTIL_IsCommandIssuedByServerAdmin())
	{
		char luafile[ MAX_PATH ];
		try
		{
			Q_snprintf( luafile, sizeof( luafile ), "lua\\%s", args[1] );
			RunLuaFile( luafile );
		}
		catch( luabind::error ex)
		{
			lua_State* L = ex.state();
			cvar->ConsoleColorPrintf( LUA_ERROR_PRINT_COLOUR, "[Lua] %s\n", lua_tostring(L, -1));
			lua_pop(L, 1); 
		}
	}
	else
	{
		cvar->ConsoleColorPrintf( LUA_PROHIBIT_PRINT_COLOUR, "[Lua] Admin-only command!\n");
	}
}

This will check if the command was issued by a server admin. If so, it'll append args[1] to lua\ (eg to run mymod\lua\helloworld.lua you'd do lua_runfile helloworld.lua)

Lua_Run console command

For dostring, the command is fairly easy. You need to add a new helper function like runfile, however:

bool RunLuaString( const char* string )
{
	if (int error = luaL_dostring(luaState, string) != 0) 
	{
		cvar->ConsoleColorPrintf( LUA_ERROR_PRINT_COLOUR, "[LUA] Error while doing 'dostring': %s\n", string);
		return false;
	}
	return true;
}

After adding this, you only have to add the concommand:

CON_COMMAND( lua_run, "Run a Lua string")
{
	if(UTIL_IsCommandIssuedByServerAdmin())
	{
		try
		{
			RunLuaString( args.ArgS() );
		}
		catch( luabind::error ex)
		{
			lua_State* L = ex.state();
			cvar->ConsoleColorPrintf( LUA_ERROR_PRINT_COLOUR, "[Lua] %s\n", lua_tostring(L, -1));
			lua_pop(L, 1); 
		}
	}
	else
	{
		cvar->ConsoleColorPrintf( LUA_PROHIBIT_PRINT_COLOUR, "[Lua] Admin-only command!\n");
	}
}

It will not work if you put the command in "" (like lua_run "ply:SetHealth(100)"). args.ArgS() takes the entire argument string minus the actual lua_run command.

Also, you may see the new define: LUA_PROHIBIT_PRINT_COLOUR. Drop this into your header file to add it:

#define LUA_PROHIBIT_PRINT_COLOUR Color( 40,160,255,255 )

Example of usage

You start up your mod, and you want to test a new Lua file you made and a new function you added. Now, it would be impractical to make a new Lua file just for the new command, so what you would do is:

Start a new game. Now, let's test the new Lua file first. Drop the file into the lua directory in your mod and give it a name of amazinglua.lua. Now, ingame, type in the console:

lua_runfile amazinglua.lua

It will run. Let's say now that this had a different name, and was in a folder called Amazing in the Lua folder.

lua_runfile amazing/newname.lua

Now, to test the new command. Let's assume this new command is Ignite the player.

lua_run ply = GetPlayerByID(1)
(I'm assuming you have a GetPlayerByID function)

Alright, now we have the player we want to ignite in our grasps. Now to ignite them!

lua_run ply:Ignite(30)

They're on fire! This will work for generally anything. You cannot split lua commands using ; (eg lua_run ply = GetPlayerByID(1);ply:Ignite(30)) because the Source Engine will think "Oh, it's a new command." and it'll think anything after the ; is a different command.


Visual Studio 2003

If you require Visual Studio 2003 Static Libraries they are here: http://www.sendspace.com/file/94di59


Visual Studio 2005

If you require Visual Studio 2005 Static Libraries they are here: http://www.mediafire.com/?ixzhgmscyyl


Notes

If you use this code please show some sort of credit, I'm releasing this under GPL so any mods you do make could you please release your extended Lua code :) Share the open source spirit.


I have only included a few classes as an example please feel free to add more if you do I encourage you to post your bigger engine-exposure to me or on a sub-page. :)

Thanks go to Neomantra, and Marine.

LuaBind to Half-Life 2 example and starter code

do-not-email-me &AT*% gmail.com

Copyright (C) 2008 John6000

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.