Tutorial Lua
Tutorial Lua
1 Introduction 1
References 11
1 Introduction
Hello and welcome to my tutorial for writing your own scripts for FiveM. In this tutorial we
will be starting from setting up a local Artifacts server for FiveM, which will be then later
used to write a base resource script to handle to give you basic database support to handle the
reading and writing of custom user-data; as well as to give you an idea on how to use the NUI,
which is just a browser overlay over the running GTAV game and use the native functions
provided to modify the game to your liking. I will assume that you are running a Microsoft
Windows system.
The development process for the resource will be shown twice, once using Lua as the base
scripting language and once using C# as the base scripting language, hence the two chapters
in the table of contents.
I expect, that you have already downloaded and installed the FiveM-Client[Col18a], if not, do
that immediately by downloading from https://fivem.net.
Furthermore you need an editor to edit the scripts depending on the main language you
want to use. If you are not experienced with any editors that work with the program-
ming languages, I recommend you using Notepad++ which can be downloaded from https:
//notepad-plus-plus.org or Atom which you can download from https://atom.io. Fur-
thermore if you do not want to write your resources using Lua, but instead C# you will need
to download Visual Studio Community from https://www.visualstudio.com/downloads.
While writing scripts there are several sources you should use as reference material.
1
FiveM docs http://docs.fivem.net, this is probably the best resource you can use, as its
content builds the basis of modifing the game.
FiveM Wiki https://wiki.fivem.net, this Wiki is not really completely up-to-date, but it is
good enough to be used.
2
from https://wiki.fivem.net/wiki/Running_FXServer#server.cfg or from the text be-
low.
1 # you p r o b a b l y don ’ t want t o change t h e s e !
# o n l y change them i f you ’ r e u s i n g a s e r v e r with m u l t i p l e network i n t e r f a c e s
3 endpoint_add_tcp ” 0 . 0 . 0 . 0 : 3 0 1 2 0 ”
endpoin t_a dd_ udp ” 0 . 0 . 0 . 0 : 3 0 1 2 0 ”
5
start mapmanager
7 start chat
start spawnmanager
9 start sessionmanager
start fivem
11 start hardcap
start rconlog
13 start scoreboard
start playernames
15
sv_scriptHookAllowed 1
17
# change t h i s
19 # r c o n _ p a s s w o r d yay
21 sv_hostname ” T u t o r i a l S e r v e r ”
23 # nested configs !
# exec s e r v e r _ i n t e r n a l . c f g
25
# l o a d i n g a s e r v e r i c o n ( 9 6 x96 PNG f i l e )
27 # l o a d _ s e r v e r _ i c o n myLogo . png
29 # c o n v a r s f o r use from s c r i p t
s e t temp_convar ” hey world ! ”
31
# d i s a b l e announcing ? c l e a r out t h e master by uncommenting t h i s
33 # s v _ m a s t e r 1 ””
35 # want t o o n l y a l l o w p l a y e r s a u t h e n t i c a t e d with a t h i r d −p a r t y p r o v i d e r l i k e
Steam ?
# sv_authMaxVariance 1
37 # sv_authMinTrust 5
3
# l i c e n s e key f o r s e r v e r ( h t t p s : / / ke ym as ter . f i v e m . n e t )
51 s v _ l i c e n s e K e y changeme
Here you still need to change the changeme of the sv_licenseKey changeme to the license
key you obtained before. If you have done that save the file in your server data folder with
any name you like, e.g. in my case I saved it in D:\myfxserver\data\ as the suggested
server.cfg.
All that is missing now is a shortcut to start the server easily. Navigate to do this to the
D:\myfxserver folder and right click the file run.cmd and select Create shortcut, move
that to somewhere from where you want to start your server, e.g. in my case the Desktop.
Then rename it to something you want. Now right click the shortcut and select properties.
Currently the line Start in should say something like D:\myfxserver, we just change it to
D:\myfxserver\data. Now all is left before we can start the server is to change the Target to
D:\myfxserver\run.cmd +exec server.cfg, if you named your file server.cfg, the same
as me.
It is now time to start up your server and join the game. You can start the server by double
clicking your shortcut. Start up FiveM, enable Dev mode under Settings. Now click Localhost
which you should have in the top menu to join your Vanilla FiveM Server.
Soon after it will be time for you to write you first own resource/script.
3.1 __resource.lua
The __resource.lua is very likely the file you usually only write when you start testing your
scripts, but since I know where we will be going, we can start and write this important file
first. You can orient yourself on the example resource manifest from the FiveM wiki found
at https://wiki.fivem.net/wiki/Resource_manifest.
First of we will be creating a new folder in our data\resources folder, which will be the
name of the resource. Before you go wild, you will need to remember, that the name has to
be in all lower case letters, otherwise certain interactions we are going to use will not work.
4
In my case, I named the folder mybase.
We also need to add to the server.cfg the line start mybase at the appropriate position.
So creating all of our files and folders, I got:
• D:\myfxserver\data\resources\mybase
• __resource.lua, the file defining what file is which for the resource handler.
• server.lua, the script that handles all the server interaction with the clients
• client.lua, the script that handles all the client interaction with the server and a
short interaction with natives.
• cfg.lua, the lua file that just holds all the configuration information for our base
script. Here it is connection information needed for CouchDB and whether the server
is using whitelist mode.
• scoreboard.html, the html file which is overlayed over the entire game screen.
All of these files are empty, and we start writing the __resource.lua. First of all we need to
pick a resource manifest version. which we will put into our file first with
1 resource_manifest_version ’ < resource manifest version > ’
The resource manifests versions which are sensible to choose from are [Col18b]:
5
We would be done here now, but we definately want that other resources can interact with
our mybase resource. So we need to define exports {} and server_exports {}. As clients
interact with the servers via events, we will not actually need any exports, but will rather
specify some server events called by a client. On the other hand we will export a few func-
tion on the server side. They will be called getData, setData, doesDataExist, getUserData,
and setUserData.
Before moving on let us check if you have done everything right. If you have done everything
right, your __resource.lua should look something like this:
1 r e s o u r c e _ m a n i f e s t _ v e r s i o n ’ 44 f e b a b e −d386 −4d18−a f b e −5 e 6 2 7 f 4 a f 9 3 7 ’
3 ui_page ’ scoreboard.html ’
f i l e ’ scoreboard.html ’
5
client_script ’ client.lua ’
7 server_scripts {
’ couchdb.lua ’,
9 ’ auth.lua ’ ,
’ server.lua ’ ,
11 ’ cfg.lua ’
}
13
server_exports {
15 ’ getData ’ ,
’ setData ’ ,
17 ’ doesDataExists ’ ,
’ getUserData ’ ,
19 ’ setUserData ’
}
3.2 couchdb.lua
Now it is time to write our first script file, which will handle the connection to CouchDB. We
access CouchDB via simple http requests, like you do with your webbrowser when browsing
on the internet.
Here we will employ chiefly just three methods of doing so: HEAD to return minimal infor-
mation about a document or database, GET to get data from a document, and PUT to put data
into a document.
But first we need to be able to properly dispatch http requests and get a proper result, to
do that we have to write a wrapper for the PerformHttpRequest function that comes with
FiveM lua implementation. For that look at the following address https://wiki.fivem.net/
wiki/PerformHttpRequest and try to write a function that wraps the function neatly and
awaits it finishing up.
To do this remember, that in lua, you can use variables inside a callback function as long
as they count as defined for the function. So you can check if the function has finished by
setting a boolean and waiting for it to turn to a certain state you want it to be. This is what
I ended up with:
6
f u n c t i o n H t t p R e q u e s t ( u r l , method , data , h e a d e r s )
2 l o c a l c b S t a t u s , cbContent , c b H e a d e r s = 0 , ” ” , { }
i f t y p e ( h e a d e r s ) = = ’ t a b l e ’ then
4 i f t y p e ( d a t a ) = = ’ t a b l e ’ then
data = json.encode ( data )
6 end
local finishedHttpRequest = false
8 PerformHttpRequest ( url ,
fu nc tio n ( status , content , headers )
10 c b S t a t u s , cbContent , c b H e a d e r s = s t a t u s , c o n t e n t , h e a d e r s
finishedHttpRequest = true
12 end ,
method , data , h e a d e r s )
14 repeat C i t i z e n . W a i t ( 0 ) u n t i l f i n i s h e d H t t p R e q u e s t == true
end
16 r e t u r n c b S t a t u s , cbContent , c b H e a d e r s
end
The local variables inside the function can still be set by the callback, so that they can be
returned once they are set. Now that the we can interact via http with websites or with
CouchDB for that matter, we will have to have the ability to send the username and password
to CouchDB, but we will not be using the crude method of sending it via http://username:
password@127.0.0.1:5984/ but by using the Authorization header. For that we need to be
able to base64encode data. For that we pick any of the encoding methods from http://
lua-users.org/wiki/BaseSixtyFour. You should try to understand what is happening in
these methods, but if you don’t it does not really matter. You should just be aware that
nearly all of your problems have been solved already by someone else, and standing on their
shoulders by using their code-snippets will be speeding up your workflow, but you should
always double check their code.
I picked the last method for Lua 5.3 with binary operators.
1 local bs = { [0] =
’A ’ , ’B ’ , ’C ’ , ’D ’ , ’E ’ , ’F ’ , ’G ’ , ’H ’ , ’I ’ , ’J ’ , ’K ’ , ’L ’ , ’M ’ , ’N ’ , ’O ’ , ’P ’ ,
3 ’Q ’ , ’R ’ , ’S ’ , ’T ’ , ’U ’ , ’V ’ , ’W ’ , ’X ’ , ’Y ’ , ’Z ’ , ’a ’ , ’b ’ , ’c ’ , ’d ’ , ’e ’ , ’f ’ ,
’g ’ , ’h ’ , ’i ’,’j ’, ’k ’ , ’l ’ , ’m ’ , ’n ’ , ’o ’ , ’p ’ , ’q ’ , ’r ’ , ’s ’ , ’t ’ , ’u ’ , ’v ’ ,
5 ’w ’ , ’x ’ , ’y ’ , ’z ’ , ’0 ’ , ’1 ’ , ’2 ’ , ’3 ’ , ’4 ’ , ’5 ’ , ’6 ’ , ’7 ’ , ’8 ’ , ’9 ’ , ’+ ’ , ’/ ’ ,
}
7
l o c a l function base64 ( s )
9 l o c a l byte , rep = s t r i n g . b y t e , s t r i n g . r e p
l o c a l pad = 2 − ( ( # s −1) % 3 )
11 s = ( s . . r e p ( ’ \0 ’ , pad ) ) : gsub ( ” . . . ” , f u n c t i o n ( c s )
l o c a l a , b , c = b y t e ( cs , 1 , 3 )
13 r e t u r n bs [ a > > 2 ] . . bs [ ( a &3) < < 4 | b > > 4 ] . . bs [ ( b &15) < < 2 | c > > 6 ] . . bs [ c &63]
end )
15 r e t u r n s : sub ( 1 , # s−pad ) . . rep ( ’ = ’ , pad )
end
7
Now that we can encode our username and password, we can continue. The first thing we
should do is write three wrappers for the methods we are going to employ to communicate
with CouchDB: HEAD, GET, and PUT. Inside the wrappers for the HttpRequest function we set
the methods, headers, and content if applicable. The headers are as follows:
Accept = "application/json"
Authorization = someauthorizationvariable
While GET and PUT can return the entire HttpRequest variables pack, this is definately not
needed for HEAD. As HEAD requests only get very basic information delivered, and can be
used mostly only to check if a document exists. Thus if the status is returned as 200 – which
means everything is ok – we shall return true on a HEAD request and false otherwise. The
code I ended up with looks like this:
f u n c t i o n CouchDB.Get ( u r l )
2 i f not C o u c h D B . i n i t then
repeat C i t i z e n . W a i t ( 0 ) u n t i l CouchDB.init == true
4 end
headers = { }
6 headers.Accept = ” application / json ”
h e a d e r s . A u t h o r i z a t i o n = CouchDB.auth
8 r e t u r n H t t p R e q u e s t ( u r l , ” GET ” , ” ” , h e a d e r s )
end
10
f u n c t i o n CouchDB.Put ( u r l , c o n t e n t )
12 i f not C o u c h D B . i n i t then
repeat C i t i z e n . W a i t ( 0 ) u n t i l CouchDB.init == true
14 end
headers = { }
16 headers.Accept = ” application / json ”
h e a d e r s . A u t h o r i z a t i o n = CouchDB.auth
18 h e a d e r s [ ” Content−Type ” ] = ” a p p l i c a t i o n / j s o n ”
r e t u r n H t t p R e q u e s t ( u r l , ”PUT” , c o n t e n t , h e a d e r s )
20 end
22 f u n c t i o n CouchDB.Head ( u r l )
headers = { }
24 h e a d e r s . A u t h o r i z a t i o n = CouchDB.auth
headers.Accept = ” application / json ”
26 l o c a l s t a t u s , c o n t e n t , h e a d e r s = H t t p R e q u e s t ( u r l , ”HEAD” , ” ” , h e a d e r s )
i f s t a t u s = = 200 then
28 return true
end
30 return f a l s e
end
It furthermore waits until the connection to CouchDB was properly setup, before beginning
to allow queries other than head. Thus we logcally speaking should write the setup of the
8
connection next. For that we first write a simple setup function that just sets the authoriza-
tion variable by base64 encoding a string passed to it, same as the server and the database-
name, which should be stored locally.
Furthermore that function should be automatically called on start of the resource and be
automatically fed the information from the cfg.lua. To do that, in C# we would be using an
EventHandler, but since they are unneccesary in Lua, we just wrap it into a thread for the
server, by using Citizen.CreateThread. Here is what I got:
1 f u n c t i o n Couc hDB .S et up ( s e r v e r , dbname , auth )
CouchDB.auth = ” B a s i c ” . . b a s e 6 4 ( auth )
3 CouchDB.address = s e r v e r . . d b n a m e . . ”/”
i f CouchDB.Head ( C o u c h D B . a d d r e s s ) then
5 return true
end
7 return f a l s e
end
9
Citizen.CreateThread ( function ( )
11 C o u c h D B . i n i t = C ouc hD B. Se tu p ( c f g . d b s e r v e r , cfg.dbname , c f g . a u t h )
i f C o u c h D B . i n i t then
13 p r i n t ( ” C o n n e c t i o n t o CouchDB e s t a b l i s h e d . ” )
else
15 p r i n t ( ” C o n n e c t i o n t o CouchDB f a i l e d . ” )
end
17 end )
All that is left, is to write the functions for our 5 server exports, so external resources can
also access the database in the same way this resource will. For that we will be first needing
to identify a player based on his steamid and license. We ought to take the steamid if it exists,
and if it does not exist, we shall use the license to identify the player internally. For you to
get that working, you might play around with the function GetPlayerIdentifiers(source)
where source is the serverID of a logged in player, or another temporary ID. If you played
around long enoungh and managed to seperate the table, the result can look similar to this:
1 function g e t I d e n t i f i e r s ( source )
l o c a l steamID64 , ip , l i c e n s e = 0 , ” ” , ” ”
3 f o r k , v i n p a i r s ( G e t P l a y e r I d e n t i f i e r s ( s o u r c e ) ) do
i f s t r i n g . s u b ( v , 0 , 6 ) = = ’ steam : ’ then
5 s t e a m I D 6 4 = tonumber ( s t r i n g . s u b ( v , 7 ) , 1 6 )
end
7 i f s t r i n g . s u b ( v , 0 , 3 ) = = ’ i p : ’ then
ip = str i ng . su b ( v , 4 )
9 end
i f s t r i n g . s u b ( v , 0 , 8 ) = = ’ l i c e n s e : ’ then
11 license = string.sub (v ,9)
end
13 end
r e t u r n steamID64 , ip , l i c e n s e
15 end
9
l o c a l steamID64 , ip , l i c e n s e = g e t I d e n t i f i e r s ( s r c )
19 i f s t e a m I D 6 4 = = 0 then
return l i c e n s e
21 end
return steamID64
23 end
Now that we have the identification cleared, we want to access the user relevant data with
getUserData just by specifying a document type and the source. Like if you want a character
document of a player, you would maybe ask for char as the document type, whereas the
documentid will be maybe something like char<steamid>. Here is what I go for our 5 functions,
which concludes our CouchDB connection:
1 f u n c t i o n g e t D a t a ( documentid )
result = {}
3 r e s u l t . s t a t u s , r e s u l t . c o n t e n t , r e s u l t . h e a d e r s = CouchDB.Get (
CouchDB.address..documentid )
result.content = json.decode ( result.content )
5 return r e s u l t
end
7
f u n c t i o n s e t D a t a ( documentid , documentjson )
9 l o c a l s t a t u s , c o n t e n t , h e a d e r s = CouchDB.Put ( C o u c h D B . a d d r e s s . . d o c u m e n t i d ,
documentjson )
i f s t a t u s = = 201 then
11 return true
end
13 return f a l s e
end
15
f u n c t i o n d o e s D a t a E x i s t ( documentid )
17 r e t u r n CouchDB.Head ( C o u c h D B . a d d r e s s . . d o c u m e n t i d )
end
19
function getUserData ( src , documentprefix )
21 l o c a l id = getUserIdentifier ( src )
i f i d ~= ” ” and i d ~= n i l then
23 return getData ( documentprefix..id )
else
25 return n i l
end
27 end
29 f u n c t i o n s e t U s e r D a t a ( s r c , d o c u m e n t p r e f i x , documentjson )
l o c a l id = getUserIdentifier ( src )
31 i f i d ~= ” ” and i d ~= n i l then
r e t u r n s e t D a t a ( d o c u m e n t p r e f i x . . i d , documentjson )
33 else
return f a l s e
35 end
10
end
3.3 cfg.lua
This file will only contain configuration options for other uses who might use the script.
Since we used the variables already in the CouchDB connection, writing this file is straight
forward. Additionally we are going to add another variable which indicates if this is a whitelist-
only server or not.
cfg = {}
2 cfg.dbserver = ” http ://127 . 0 . 0 . 1 :5984/ ”
cfg.dbname = ” t u t o r i a l ”
4 cfg.auth = ” fxserver : tutorial ”
c f g . u s e w h i t e l i s t = true
And this was already the entire file, now let us continue to the auth.lua, in which the user
authenticates himself automatically with our server.
3.4 auth.lua
In this file we will make sure when the player is connecting to the server, that he has access
to the server, so he is neither banned, nor his ip is banned, and if we use a whitelisting
system, he is whitelisted. Furthermore we are going to add a very simple queue system, so
that once the server is full, users do not need to spam the connection to get access.
3.5 server.lua
3.6 client.lua
3.7 scoreboard.html
References
[Col18a] CitizenFX Collective. FiveM - the GTA V multiplayer modification you have dreamt of.
[Online; accessed 20-January-2018]. 2018. URL: https://fivem.net/.
[Col18b] CitizenFX Collective. Manifest versions :: FiveM Documentation. [Online; accessed 21-
January-2018]. 2018. URL: http : / / docs . fivem . net / resources / manifest -
versions/.
[Con17] FiveM Wiki Contributors. Running FXServer — FiveM. [Online; accessed 20-January-
2018]. 2017. URL: https://wiki.fivem.net/w/index.php?title=Running_
FXServer&oldid=1299.
11