Lua Scripting Tutorial (CS2D)



Welcome to my little Lua Scripting Tutorial for CS2D. It just handles the basics, if you want to go deeper into Lua, just use this tutorial in the beginning, and then switch to another.
ATTENTION! This tutorial is currently under development, so I'll add things, perhaps I'll delete some things. Stay tuned for future updates!
Content:
Introduction
Creating a scriptfile and using it in CS2D
Variables
Functions and Hooks (Basics)
Queries and Loops
Finding bugs in your scripts
Writing in a way easy to understand
Making a little menu


Introduction

Lua is a scripting language, which is used in many games, for example GARRY'S MOD, or, which you should know now that you're reading this tutorial, for CS2D.
It's easy to learn and you will see first results of your scriptings very fast.


Creating a scriptfile and using it in CS2D


As most of you are asking how to use scripts which you got from others, or which you will make yourself soon, the first thing I want to tell you is how to use scripts.
There are three possibilities:

1. You got a file called "anyname.lua" (The lua in the end is the important thing!)
Then it's really easy. Just open your cs2d folder, go to /sys/lua/ , paste the file in there and open the "server.lua".
Go to the last line of this file and add the following line there:
dofile("sys/lua/anyname.lua")
where anyname is the name of the lua file.
Start CS2D, open a server and it should work.

2. You got the script, but not a file.
Open a text editor of your choice (for windows users, the most common is Editor, or notepad).
Paste the text into the editor.
Click on "Save file as..."
Type in a filename, which describes the script the best. But keep it short (and the best thing is not to put spaces in the filename. Could cause problems).
Add .lua to the end of the filename, erase the *.txt if it's still there.
At "FileType" switch to "All Files". Save it (the best thing would be to save it directly to "YourCS2DFolder"/sys/lua ).
Continue with step one.

3. The Lua Scriptfile is together with a cs2d map. Then it's enough to paste both map and script into the /maps/ folder, as long as the filename (without extension) of the map is the same than the filename of the Lua script (again without extension) everything works fine.
You know only have to start CS2D and open a server with this map.


Variables

This second part is a basic part, where you can't test what you've learned, but it's the most important thing in scripting. Without variables you can't do anything.
To explain what a variable is, I'll use a little metaphor. Just imagine a variable as a box. In this box you can put things, carry the box whereever you like, and fetch it out again.
The best thing this box has is a little label, where you can write on what's in the box!
Imagine an empty box, with an empty label. You now say:
I want to keep these peaces of paper with much text on it. The text is about my friends, so I'll take this box, put my papers in it and write "My Friends" on the label of the box.
Whenever you think you need the content of the box again, you just open the box with the label "My Friends" on it and fetch out the pieces of paper.
This is what a variable does. To put things in a box you just need to use the following:

Code:
NAME-OF-THE-BOX = CONTENT-OF-THE-BOX

for example:

Code:
myFriends = "Tom"

To show Lua that the thing which is in the box is a text, we need to put quotation marks around it.
Now, in the "box" called "myFriends" there is an item called "Tom".
If we now want to use the item of the box, we just need to write the boxes name:

Code:
myFriends

Now the game would automatically use Tom instead of myFriends
Of course you can't only put texts, such called "strings" in your box, but also numbers, or even more than one thing (this type of variable is called "array").
example for a number:

Code:
mymobilephone = 012345667

As you see, the number doesn't have quotation marks around it. That's a difference. If I used

Code:
mymobilephone = "012345667"

The script would have thought that 012345667 isn't a number, but a text. You couldn't have done maths or other things with it.
Doing maths is very easy.

Code:
variable = number1 + number2
variable = number1 - number2
variable = number1 * number2
variable = number1 / number2

an example:

Code:
myAge = 11 + 6

you could also do maths on variables:

Code:
mySistersAge = 11
myBrothersAge = 6
myAge = mySistersAge + myBrothersAge

An array (like I mentioned before) is a box where you can put in more than one thing at once! Incredible!

Code:
myfriends = {"Tom", "Ray", "Kim"}

Now, in this "array" called "myfriends" there are 3 friends of mine! Every item in an array is seperated with a comma from the next one. How to show the script which friend I want to use in the text? It's very easy, just watch the example:
myfriends[1]
If I asked CS2D for "myfriends[1]" it would tell me "Tom". If I wrote myfriends[2] the script would tell me "Ray" (which we'll do later, I just want to show you some basics about variables now).
These were the basic things about variables, some additional info will follow in the following parts of this tutorial.



Functions and Hooks

At this point, I'll just give you an example of a little script, and explain to you how it works with this script.
Everything which begins with -- is a comment, so CS2D won't care about it.

addhook("say","tutorialsay") -- First, we add a hook (this you just have to do everytime.). In the brackets, there are 2 different parts now. The first one is "say", which is a special hook. I'll explain it later. The second thing is "tutorialsay" which is a name, which you can choose yourself. Just make sure that it's the only hook in every running script with this name.

function tutorialsay(player, text) --If the hook with "say" is called, so if somebody says something in game, the function will start. As you can see the functions name, "tutorialsay", was used before in addhook, so addhook calls for the function "tutorialsay" to start. Now, every hook with "say" gives you two predefined variables. The id of the player who says something and the text the player says.
-- The id is now saved in a variable called "player", you can give it any other name too, the only important thing is the order, in which the two variables appear (in the hook "say", it's always at first the id of the player who says something, at second the text).
-- The text is saved in a variable called "text". Let's see the next line of the script:

msg("Player "..player(player, "name").." said: "..text)
-- I just explain every little peace, one after another:
-- "msg" is just the command for CS2D to write a message on everybodys screen.
-- Now there are the brackets. Everything in the breakets belongs to the command in front of the bracket (in our case "msg")
-- "Player " This is just the first part which will be printed on your screen. You could have also written "asdfh " , but it wouldn't have made much sense ;-)
-- .. Two dots show cs2d that some strings are connected together. The first string here is "Player ", the second is explained in the following line
-- player(player, "name") player() is a cs2d function which gives you information about a player. The first parameter is the id of the player (in our case it's saved in the variable player), the second what you want to know about the player.
-- player(player, "name") just gives you the players nick, without this you would just have had an ID, but ID's aren't very comfortable.
-- .. again double dot, connects two strings (this time it's the string player(player, "name") and the following line)
-- " said: " well, this is again just a string, where you could have also written "asdfh ". But it would just not have made sense
-- ..text connects the " said: " and the variable text together. Let's see the next line of our script.

end -- Well. To show Lua that the function just ended, you ned to write end . Not very fanciful, but it does its job ;-)

The script ended. Without all the comments it would have just looked like this:

Code:
addhook("say","tutorialsay")
function tutorialsay(player, text)
   msg("Player "..player(player, "name").." said: "..text)
end


Doesn't look very long-winded, and it also doesn't do that much. If you would save this script (and do what's described in step Creating a scriptfile and using it in CS2D) the script would just do one simple thing.
Imagine player Carl says I'm leaving now, cya
The script would just add a little line to the chatfenster where the following is shown to you:
Player Carl said: I'm leaving now, cya
I don't think this script is very useful, but it helps a lot to explain how hooks are working.
To see which hooks exist, DC made this wonderful list (which is also in your cs2d/sys/lua folder, called info.txt)
As you can see, there is for example the line

Code:
join(id)

If you wanted to make a function which is called when somebody joins, you would have to write:

Code:
addhook("join", "myfirstjoinscript")
function myfirstjoinscript(id)
   YOUR SCRIPT HERE
end

Easy, isn't it? Just a little exercise for you, write a script which says "Player NICKNAME HERE joined" when a player joined. The solution can be found here


Queries and Loops

First question in this topic is "What is a Query? What is a loop?". Easy to answer:
A query compares a value to another, a loop continues doing something until you tell it to stop.
The easiest examples are if (this is a query!) and while (this is a loop!)
I'll start with IF:
Imagine you have two variables, and you know want to know if the first has the same content than the second. If IF wouldn't exist this would be nearly impossible (perhaps even impossible) but with if it's really easy:

Code:
if (variable1 == variable2) then
   YOUR SCRIPT
end

As you can see, there is again this END . It now shows Lua that the if query we started with THEN ends now.
The example from before:

Code:
addhook("say","tutorialsay")
function tutorialsay(player, text)
   if (text == "bla")then
      msg("Player "..player(player, "name").." said: bla")
   end
end
Now, the script will only add the line with "Player NAME said: bla" if the player really says bla. If not, nothing will happen. Of course, you can nest if-queries:

Code:
addhook("say","tutorialsay")
function tutorialsay(player, text)
   if (text == "bla")then
      if (player(player, "name") == "Tom") then
         msg("Player Tom said: bla")
      end
   end
end
But pay attention, if you nist if-queries, you always need to close EVERY if-query, or your script won't work.
Now you can see why I always indent the code. With an indented code, you can easily see where your if-query starts, and where it has to stop.
So my suggestion to you is, use your tab key (the one with the two arrows in different directions!) and make your code look nicer and easier to understand.

while-loops:
A while loop is simple. It just compares two values, and as long as the comparison isn't false, the code inside the loop will be repeated.
Code:
number = 1
while (number < 11) do
   SOME CODE HERE
end

This code would now go on forever, because we didn't increase the variable number.
As you can imagine, a loop which will be done everytime when the Computer does something will slow down your games speed drastically and will perhaps even stop the game (because it all the time has to go through the loop)
So we have to set the variable higher everytime we go through the loop:
Code:
number = 1
while (number < 11) do
   number = number + 1
end

This loop would now do nothing, but going on 11 times.
To make the while loop easier, there is another loop. This loop is the "for"-loop.
The good thing with the for loop is that the for loop doesn't need the line where you increase the variable.
Code:
for (i = 1, 11, 1) do
   SOME CODE HERE
end

There are three things in the brackets now. At first the variable i will be set to 1. The next number is how high the variable should be increased, till 11.
The last number is how much the variable should be increased every time the loop is done. If I wrote 0.5 there, the loop would be done 20 times, instead of 10. If I wrote 2 there, the loop would have just been done 5 times.
We can now do a little script, which sends every player on the server a message every minute:
Code:
addhook ("minute", "tutorialadvertise") --The hook which is called every minute is called "minute"
function tutorialadvertise() --Because a minute can't tell you anything about a player or a weapon, the brackets are just empty
   for (i = 1, 32, 1) do -- We use 32 here, because the maximum number of players on a server is 32
      if (player(i, "exists")) then --If the player with the id we are currently looping exists then
         msg2(i, "Hello, this script is a tutorial") --msg2 has two parameters. The first is the id of the guy you want to send something, the second is the text.
      end
   end
end

Exercise: Write a script which will send the message "Player NICK is Admin here" when you (as an admin) say "!admin".
Hints:
1. You need an array with the USGN ids of the admins in it.
2. To get a players usgn id just use "player(id, "usgn")"
3. You can get the number of the items of an array with # in front of the arrays name. (e.g. an array called "admins" has 3 items. #admins is the variable for 3 now)
4. You can either use while or for !
The solutions can be found here


Finding Bugs

Now, a very important thing. Finding Bugs. Of course, until now, there was not much which you can make wrong, but as soon as your scripts get longer, there will be problems.
First, open your console and enter debuglua 1 there. If you now open a server with your script and then open the console there should be something like this:
Console:
LUA: Adding function 'mySayScript' to hook 'say'

In my script it looks like this:

If this is the output, your script runs well, and errors can't be found by cs2d. This doesn't mean that there are no errors, but the syntax is right.
There could also be a red line, which looks like this:
Console:
LUA ERROR: sys/lua/myscript.lua:36: ')' expected near '='

In my script it looks like this:

The text after LUA ERROR: shows you in which file the bug is, the number after it in which line.
Then there is the real problem. In my case, the script expected a ) near a = . Often, the message doesn't really help, because the bug is another.
In my case, where the error line was "')' expected near '='", there was a = missing in an if-query. So you see, the debug message doesn't really tell you what's wrong.
But at least, it tells you in which line you have to search for the error! (Or in which lines, I would EVERYTIME check the previous line too!)
Common errors are missing "=" in if queries, missing "end" to end a function or a query (or a loop of course).
A missing end is a good example for what can be a problem too:
Console:
LUA ERROR: sys/lua/myscript.lua:202: 'end' expected (to close 'function' at line 31) near '<.eof>'

An attentive reader can see the problem. The debug message shows that the error is in line 202 (in <.eof> which means END OF FILE, the last line) but the function was started in line 31.
It doesn't happen very often that I write functions which are about 170 lines, so you can see, that the debug expects the end at the end of the file, and not where it should be.
If you get a "'end' expected" message, it would be better to look for it in the environement of the function than at the end of the file.


Writing in a way easy to understand

There are many people who want to show their scripts to others, or who want to understand their own scripts after some time without having to reread it 20 times
So, their are some possibilities to write in a way easy to understand.
Most important thing is: Use tabs. There's a key on your keyboard for that which should look like this:
tabkey
Using tabs to make your script clearly represented is very easy. As you already know from the other parts functions, if querys and loops are in blocks beginning with function, if (...) then....
Using tabs to insert these blocks makes it more clear to see where the block started and ended.
An Example:
Code:
addhook("join", "Join")
function Join(id)
if (id == 1) then
msg("Player with ID 1 joined)
end
msg("Player "..player(id, "name").." joined")
end

This one was without tabs. Let's take a look at the same script WITH tabs:
Code:
addhook("join", "Join")
function Join(id) --block 1 starts here
   if (id == 1) then --block 2 starts here
      msg("Player with ID 1 joined)
   end --block 2 ends here
   msg("Player "..player(id, "name").." joined")
end--block 1 ends here

It's incredible how much it helps to just use some tabs.

Second Method: COMMENT. Use comments whereever you can to make clear what this part of the script does.
If you equip a player an item with parse(equip 1 64) then use comments (with -- if you've already forgotten) to write which weapon is equiped.
It's much faster for you to reread it when you can just read the weaponname in comments instead of searching for it.


Solutions

First exercise, writing a script which tells you "Player NICKNAME HERE joined" when a player joined:

Code:
addhook("join", "exerciseJoin") --of course the "exerciseJoin" doesn't need to have the same name at your script, the important thing is that you take the same name in the next line
function exerciseJoin(id)
   msg("Player "..player(id, "name").." joined")
end


Second exercise, writing a script which says "Player NICK is admin" when a player who is admin says "!admin"
First, with a while-loop:
Code:
admins = {1234, 2345, 3546}
addhook("say", "adminsay")
function adminsay(id, text)
   i = 1
   while (i <= #admins) do
      i = i + 1
      if (text == "!admin") then
         if (player(id, "usgn") == admins[i]) then
            msg("Player "..player(id, "name").." is admin!")
         end
      end
   end
end

Or, with for:
Code:
admins = {1234, 2345, 3546}
addhook("say", "adminsay")
function adminsay(id, text)
   for (i = 0, #admins, 1) do
      if (text == "!admin") then
         if (player(id, "usgn") == admins[i]) then
            msg("Player "..player(id, "name").." is admin!")
         end
      end
   end
end



CS2D Tutorial Copyright by
TheKilledDeath
Richard Baumann