luaLFS

Documentation

1. What it is
2. Using
3. Configuring
4. Creating scripts
 4a. Prerequisites
 4b. How luaLFS works internally
 4c. Example: Hello World
 4d. The API, in a nutshell
 4e. Example: 'Welcome to my server'
 4f. Example: More complex and persistent data
 4g. Where you go from here
5. Contributing to luaLFS
6. Troubleshooting
 6a. I can't get luaLFS to connect!
 6b. I'm getting some lua error on luaLFS startup!
 6c. It crashes!

1. What it is
luaLFS is an InSim client (protocol version 4) for Live For Speed, with an embedded Lua interpreter. Realistically this means that it's a scriptable InSim client where people don't have to care about maintaining an InSim connection.

2. Using luaLFS
There are a few caveats to using luaLFS at this stage, depending on the platform;
* There is no GUI - do not expect one
* You must run have the scripts and config.lua file in the location that you are running luaLFS from
* It has been untested on any OS prior to Windows 2000, Debian Etch and any MacOS

All you simply need to do is configure config.lua, and then run luaLFS, making sure you place any user scripts you want to run in ./scripts/, and ensure that contain '.lua'.

Do not delete, rename or edit any files in ./scripts/core/ unless you truely understand what they are for.

3. Configuring 
Config.lua is the hub of all configuration in luaLFS. It's simply a lua table which must contain 2 subtables - luaLFS and LFS. Within each table you will find highly commented sections which describe each section very clearly. All you need to do is simply open it with your favourite text editor, be it anything from notepad or Vim.

It is recommended that any other configuration gets set in the config table, in the same way as the luaLFS or LFS tables, with a unique name. This is, of course, optional for the following reasons;
* The config table could be modified by a third party script upon execution
* There may not be any additional configuration required for third party scripts

4. Creating scripts
Creating scripts is a fairly simple task in luaLFS. Each lua file is a text file that can be edited by any plain old text editor. I'd recommend using notepad for Windows and something like vim, emacs or nano/pico on other OS'.

4a. Prerequisites
Whilst luaLFS is easier to get to grips with, I do assume that you are capable of the following;
* Understanding what InSim is and why you might want to use it
* Reading docs/InSim.txt that comes with LFS
* Able to follow simple scripts, with a little guidance if necessary
* Able to read up on Lua, if necessary

4b. How luaLFS works internally
From a few thousand miles in the sky, this is what luaLFS does on a typical run;
* Creates an InSim instance
* Creates a Lua state
* Loads the core scripts (./scripts/core/core.lua)
* Loads all other files with the substring "lua" in them, from the ./scripts directory
* Loads config.lua
* A created event is sent to the events subsystem, from the core scripts
* Connects to specified InSim "server" (read: any LFS instance with InSim activated), if possible
* A connected event is fired
* Monitors the connection, keeping it alive, and watches for keepalive packets, responding where necessary
* All packets are then passed to the events system
* The events system then distributes the event relevant to the packet to the any function, from any script, which has subscribed to that packet
* When the connection is lost luaLFS quits, or retries to connect, depending on the configuration and the circumstances. The disconnected event is fired.
* luaLFS exits, clearing all memory, after firing the closing event.

So why have I told you this? In order to understand how to write luaLFS scripts you need to know what is happening, as unlike LFSLapper, you're actually doing a lot of the coding yourself.

By now you should understand the following;
* Each InSim packet can be subscribed to by part of a script
* Each InSim packet should be considered an event
* Events get fired (or triggered) when a packet of that type is received

You should now understand why the InSim.txt file distributed with luaLFS is so important. This file holds the key of what functions you should be subscribing to.

4c. Example: Hello World
I'm in some what of a confused state. I don't want to introduce the API without first an example, but the example will use the API in which you use to "decode" the InSim packets. But we'll battle on!

Below is an example of your first script. In traditional style this will print the worlds "Hello World" into the LFS text/chat "console" when luaLFS connects. Simply copy and paste it into your text editor and save it under ./scripts/helloworld.lua.

--- CUT HERE ---
function helloworld_connected()
	local msg = "/echo Hello World!"
	luaLFS:mst(msg)
end

evt_bind(EVT_CONNECTED, helloworld_connected)
--- CUT HERE ---

Now run luaLFS, and if you've got luaLFS configured correctly, it will print "Hello World". Awesome huh?

So lets break it down;

The first 4 lines define your own function, called "helloworld_connected". Line 2 defines your own message, and line 3 makes luaLFS send an IS_MST packet to LFS containing your message.

This function is then bound to the event EVT_CONNECTED, on line 6.

Now what happens is when luaLFS runs, on connection EVT_CONNECTED is sent to the event system, which then works out that helloworld_connected is subscribed to EVT_CONNECTED, and then calls it. helloworld_connected is then called and that sends an IS_MST packet to LFS, via InSim.

Some things to note;
* Each function name must be unique, as each script is read into a global lua state. This means if you have 2 functions called the same thing, one will overwrite the other.
* Variables also must be unique - unless you use the keyword "local" in front of them. I recommend only doing that within each applicable scope (i.e. within a function, or within a loop).
* EVT_CONNECTED is just one event. This is a special one. Normally you'd use the name of the packet you want. i.e. ISP_MSO. A full list of packets can be found in docs/InSim.txt of LFS, or under "InSim packet types" within ./scripts/core/event_id.lua.

4d. The API, in a nutshell
In the "Hello World" example you will have seen the magical variable "luaLFS" (on line 3). luaLFS is a special variable which always exists and contains a number of very important functions and methods from which you can call to talk to, and receive data from, LFS.

The following are methods, which cannot be changed without editing the C source code of luaLFS itself;
* luaLFS.sendmsg: Sends a binary message directly to LFS. Unless you're implementing a packet you will never use this directly.
* luaLFS.quit: This forces luaLFS to quit.
* luaLFS.rehash: This makes luaLFS reload all script files.
* luaLFS.sleep: This makes luaLFS sleep for X seconds, where X is an argument.
* luaLFS.version: This returns the version number.
* luaLFS.verbose: This returns whether luaLFS is in verbose mode or not.

All other contained with the luaLFS object are implmented directly in the ./scripts/core/api.lua file. These are the functions are you most likely to use. I won't explain each one as there is one for almost each InSim packet. What you do is either pass in a single variable to read the data from InSim, which then returns a table describing the packet, or you pass in multiple variables to send the packet.

Complicated perhaps, but its more obvious once you start becoming familiar with the InSim packets and a few examples.

4e. Example: 'Welcome to my server'
This is a more complex example, designed to welcome people to a multiplayer server.

--- CUT HERE ---
function welcometo_newconn(imsg)
	local t = luaLFS:ncn(imsg)
	local topic = string.gsub(":Welcome $p", "$p", luaLFS:stripctrlchars(t.pname))
	local rules = string.gsub(":No wrecking, cheers $p", "$p", luaLFS:stripctrlchars(t.pname))

	print(os.date('%x %X').." : Player joined ("..t.uname..")")
	
	luaLFS:mtc(t.ucid, 0, topic)

	if (t.admin == 1) then
		luaLFS:mtc(t.ucid, 0, ":You have real admin rights")
	end

	luaLFS:mtc(t.ucid, 0, rules)
end

evt_bind(ISP_NCN, welcometo_newconn)
--- CUT HERE ---

What we've done is create our own function, welcometo_newconn, which has an argument of imsg. imsg will contain any data that luaLFS passes to our function. In this case it will be the packet that we've subscribed to on the final line (ISP_NCN - a new connection). 

We then decode that binary packet into a table, using the code "local t = luaLFS:ncn(imsg)". "t" is now a table which contains the useful contents of that IS_NCN packet. In this case, the following;
ucid, uname, pname, admin, total, flags
or in laymans terms
Unique connection id, licence username, player name, whether this player is an admin, the total number of players and the flags for this connection

You can now see how this helps us! Using this information we replace $p with the playername in both topic and rules, we also print the date and time of when the player joined to the console output of luaLFS. We then end them the topic (and only them, because we send an IS_MTC - a message to a single connection). If they're an admin we then say that they're an admin, and finally we send them the rules.

Fairly simple, but it might take some getting used to, and a little dig through ./scripts/core/api.lua.

4f. Example: More complex and persistent data
By now you should recognise the power of luaLFS and how quick it can be to create simple scripts. You may be wondering how we can make data persistant across multiple events, or InSim packets.

The easiest way is to simply create a global variable, outside of any functions and then make it a table. Filling it as necessary. The following example is a comprehensive "racer white list";

--- CUT HERE ---
function undercontrol_init()
	luaLFS:mst("Undercontrol v"..config.undercontrol.version.." active.")
	if ( srv == nil ) then
		srv = {
			connections = { number = 0, },
		}
	end
	luaLFS:tiny(1, TINY_NCN)
	luaLFS:tiny(1, TINY_NPL)
end

function undercontrol_deinit()
	srv = nil
end

function undercontrol_newconn(imsg)
	local t = luaLFS:ncn(imsg)
	local topic = string.gsub(config.undercontrol.welcome, "$p", luaLFS:stripctrlchars(t.pname))

	srv.connections.number = t.total - 1

	-- print(os.date('%x %X').." : Player joined ("..t.uname.."). "..srv.connections.number.." in server.")

	if (t.reqi == 0) then
		luaLFS:mtc(t.ucid, 0, topic)
	end

	if (t.admin == 1) then
		luaLFS:mtc(t.ucid, 0, ":You are currently logged in as an administrator.")
	else
		if (config.undercontrol.canspectate ~= true) then
			local k, v
			for k, v in pairs(config.undercontrol.racers) do
				if (string.lower(t.uname) == string.lower(v)) then
					i = 1
				end
			end
			if (i ~= 1) then
				luaLFS:mtc(t.ucid, 0, config.undercontrol.deniedmsg)
				luaLFS_sleep(1)
				luaLFS:mst("/kick "..t.uname)
				srv.connections.number = srv.connections.number - 1
				return
			end		
		end
	end

	if (srv.connections[t.ucid] ~= nil) then
		table.remove(srv.connections, t.ucid)
	end
	table.insert(srv.connections, t.ucid, t)
	return
end

function undercontrol_connleave(imsg)
	local t = luaLFS:cnl(imsg)
	srv.connections.number = t.total-1
	-- print(os.date('%x %X').." : Player left ("..srv.connections[t.ucid].uname.."). "..srv.connections.number.." in server.")

	if (srv.connections[t.ucid] ~= nil) then
		table.remove(srv.connections, t.ucid)
	end
end

function undercontrol_playerjoins(imsg)
	print("Player joins")
	local o = luaLFS:npl(imsg)
	local k, v
	i = 0
	for k, v in pairs(config.undercontrol.racers) do
		if (srv.connections[o.ucid].uname == v) then
			i = 1
		end
	end
print(i)
	if ((i ~= 1) and (srv.connections[o.ucid].admin ~= 1)) then
		luaLFS:mst("/spectate "..o.pname)
		luaLFS:mtc(o.ucid, 0, config.undercontrol.deniedmsg)
	end
end

evt_bind(EVT_CONNECTED, undercontrol_init)
evt_bind(EVT_DISCONNECTED, undercontrol_deinit)

evt_bind(ISP_NCN, undercontrol_newconn)
evt_bind(ISP_CNL, undercontrol_connleave)

evt_bind(ISP_NPL, undercontrol_playerjoins)
--- CUT HERE ---

If you then add the following to config.lua, after LFS = { ... }
--- CUT HERE ---
	undercontrol =
	{
		version = 0.1,
		-- Welcome banner
		-- $p == playername
		-- $u == licence username
		welcome = ":Welcome $p!",
		-- Do we allow them to spectate?
		canspectate = true,
		-- Denied message
		deniedmsg = ":You have no known licence to race on this server",
		-- Users allowed to race, by licence name
		racers = { "the_angry_angel", "birder", },
	},
--- CUT HERE ---

In a nut shell when players join, if they are in the "racers" table, within config.lua, or they are an admin, they will be allowed to race. If they are not in the list they will be forced to spectate mode. If "canspectate" is set to false, they will be kicked and not allowed to spectate.

So, how exactly is this different from before? To be honest, not much. The major difference can be found under the "undercontrol_init" and "undercontrol_deinit" functions. undercontrol_init creates a global variable, called srv. This is a large table which will contain the each connection to the server, referenced by their ucid (unique connection id). i.e. srv[1] would contain a table describing connection id 1 to the server, 2 describing connection 2, and so on.

Simply because it's a global (there was no local keyword before it), it is globally accessible. 

Now here is the important thing;
undercontrol_init and undercontrol_deinit both assume that srv does not exist and recreates it if necessary. This is due to the ability for luaLFS to rehash (not demonstrated in any example, but you simply need to call luaLFS.rehash()) and so that the contents remain persistent.

4g. Where you go from here
The sky is pretty much the limit. There are a number of features I'm still to implement and bundle, but you should find that it's fairly feature complete. Check out the thread for more information on the project goals.

5. Contributing to luaLFS
I'm happy to accept patches but;
* Make them cross platform if you can, no worries if not, but it would help
* Diff your changes against that release and attach them to an email or post on the forums
* I may not accept them
* Make sure that it is your own code, and you are happy to contribute under the terms of the licence

6. Troubleshooting
6a. I can't get luaLFS to connect!
* Make sure that you have the correct port and password in config.lua
* Make sure you have opened InSim in LFS (either enter /insim=29999 into a text window, or your setup / config file of your dedicated server - you can of course use a different port, but please ensure that it matches the config file)
* If you're not running as a server and you're getting a password error go into Multiplayer > Start A New MultiPlayer Game and clear or copy the password from there
* Make sure your firewall(s) allow the connection

6b. I'm getting some lua error on luaLFS startup!
* We have to deal with this on a case-by-case basis. It's probably a broken script though. Copy and paste the error into the relevant thread on the forums

6c. It crashes
I hope not, but please do the following and help us by
* Letting us know what scripts you are running
* What you were doing at the time
* The crash address
* The version
* If it's reproducable, run luaLFS under verbose mode by editing config.lua and setting verbose = 1, then run debug.bat (or debug.sh) and send the output file to us.
