DE/Lua Tutorial
Vorwort
Da es bis auf einige knappe und teils unprofessionelle Tutorials kein deutsches Lua Tutorial gibt, habe ich beschlossen, nun ein eigenes zu schreiben, welches alle für MTA notwendigen Grundlagen einfach und übersichtlich erklärt. Weiterführende Möglichkeiten und Funktionen können der Offiziellen Lua Dokumentation entnommen werden, die es jedoch nicht auf Deutsch gibt.
Ein weiterer Grund ist, dass ich schon von einigen gehört habe und auch so schon den Verdacht hatte, dass MTA einfach so wenige deutsche Spieler und vor allem Server hat (gibt es überhaupt einen deutschen?), weil die meisten Probleme mit den englischen Dokumentationen haben oder der Meinung sind, dass für MTA ja sowieso kein deutscher Support vorhanden ist.
Soviel nun dazu, wie es zu der Idee des deutschen Lua Tutorials kam. Fangen wir an mit dem Erlernen einer neuen Sprache!
Grundlagen
Lua, eine Skriptsprache
Lua (portugiesisch für Mond) ist eine Skriptsprache zum Einbinden in Programme, um diese leichter weiterentwickeln und warten zu können. Insbesondere die geringe Größe des Interpreters von 120 KB und die hohe Geschwindigkeit verglichen mit anderen Skriptsprachen überzeugen viele Entwickler davon, Lua einzusetzen.
Die Syntax lehnt sich an die von Pascal an, was besonders Anfängern den Einstieg in Lua erleichtert. Im Gegensatz zu von Pascal abgeleiteten Sprachen nutzt Lua jedoch „==“ und nicht „=“ als Vergleichsoperator.
(Quelle: Wikipedia)
Einige werden sich bei dieser Einführung vielleicht gefragt haben, was der Unterschied zwischen Skript- und Programmiersprache ist und was ein Interpreter sein soll. Im folgenden will ich diese beiden Fragen so kurz und einfach wie möglich klären.
Eine Programmiersprache (z.B. C, C++, Pascal, Delphi) wird - wie der Name schon sagt - verwendet, um Programme zu schreiben. Ein Programm liegt bekannterweise in Form einer Executable (*.exe) vor. Diese kann weitgehend unabhängig von irgendwelchen Betriebssystemen oder der Umgebung selbstständig ausgeführt werden (z.B. durch Doppelklick). Weitgehend deswegen, da einige Programme aufgrund von Speicherersparnissen sogenannte Programmbibliotheken (original Dynamic Link Library, kurz dll) benötigen, um zu funktionieren. Erklärungen dazu findet ihr hier.
Um einen Programmquellcode, d.h. einen Text, der in einer Programmiersprache geschrieben wurde, aus dem Textdokumentformat in eine Anwendung (original Executable, kurz exe) umzuwandeln, benötigt man nun einen Compiler (zu Deutsch Kompilierer oder Übersetzer), der die Programmiersprache aus der menschenlesbaren Form in die maschinenlesbare Form umwandelt. Neben dieser Hauptaufgabe übernimmt der Compiler natürlich noch weitere Aufgaben, auf die ich jetzt aber nicht genauer eingehen werde. Infos dazu findet ihr hier.
Die maschinenlesbare Form ist für Menschen unlesbar.
Eine Skriptsprache (z.B. JavaScript, DOS-Batch(Win) bzw. Shell-Scripts(UNIX)) hingegen liegt standardmäßig in der menschenlesbaren Form vor. Skriptsprachen werden - wie im ersten Satz bereits erwähnt - vor allem von Programmen benutzt, um benutzerdefinierte Abläufe verwenden zu können. Zur Ausführung von Scripts wird ein sogenannter Interpreter benötigt, der das Skript liest und es Schritt für Schritt in die Maschinensprache umwandelt und ausführt. Manche Skriptsprachen kann man mit einem Precompiler (zu Deutsch Vorkompilierer oder Vorübersetzer) vorkompilieren. Diesen könnte man auch den Vorkauer vom Interpreter nennen, da er das Script schon zum Teil in die Maschinensprache umwandelt, jedoch nicht so extrem wie der Compiler der Programmiersprachen. Das Ergebnis davon ist logischerweise die schnellere Ausführung des Skripts.
Genug der langweiligen Definitionen. Klären wir lieber, was man denn alles braucht, um ein Lua Script zu schreiben.
Materialien
Lua Skripte werden üblicherweise als Textdateien mit der Endung .lua gespeichert. Die Endung ist jedoch nicht von belang. Es kann jede beliebige Endung gewählt werden.
Wie jede normale Textdatei kann man also auch Lua Skripte mit dem standard Texteditor des vorhandenen Betriebssystems schreiben, editieren und lesen. Ich persönlich empfehle jedoch das Programm Notepad++, welches hier heruntergeladen werden kann. Das Programm ist Freeware und wird regelmäßig aktualisiert und erweitert. Der Vorteil daran ist, dass es ein professionelles Syntax-Highlighting eingebaut hat, welches Schlagwörter und Standardfunktionen farblich oder anderweitig hervorhebt und somit die Übersichtlichkeit erheblich verbessert.
Standardmäßig wird die Sprache Lua automatisch an der Dateiendung .lua erkannt, solltest Du eine andere Endung verwenden und trotzdem das Lua Syntax-Highlighting verwenden wollen, kannst Du es oben im Menü „Sprachen“ einstellen.
Wer ohne MTA seine kleinen Lua Skripte testen oder ausführen möchte, kann sich hier einen kostenlosen standalone Interpreter herunterladen. Wenn Du Dir nicht sicher bist, welche Windows Edition Du hast, dann hast Du wahrscheinlich eine 32 Bit Version, welche auf dieser Seite der „Windows x86 Executables“ entspricht. Die aktuelle Version (Win 32 Bit) dafür gibt es bei einem Klick auf lua5_1_3_Win32_bin.zip.
Nach dem Download kann der Standalone Interpreter mit einem Doppelklick auf die „lua5.1.exe“ gestartet werden. Dort kann man dann direkt Lua Befehle eingeben oder ein Script laden. Dies geht wie folgt:
dofile("dateiname.lua")
Kommentare
Um sein Skript übersichtlicher zu gestalten, kann man mit Kommentaren bestimmte Zeilen eräutern, Bereiche abgrenzen, alten Code deaktivieren, ohne ihn zu entfernen und generell das Skript übersichtlicher gestalten.
Kommentare werden vom Interpreter vollkommen ignoriert und sind nur für den Menschen lesbar und von Belang.
Einzeilig
-- Dies ist ein einzeiliger Kommentar. Er gilt von -- bis zum Ende dieser Zeile. -- Automatische Zeilenumbrüche, die eingefügt werden, wenn die Zeilenlänge überschritten wurde, zählen nicht.
Mehrzeilig
--[[ Dieser Kommentar geht über beliebig viele Zeilen und endet mit ]]
Variablen
Einführung
Eine Variable kann man sich wie eine kleine Kiste vorstellen, auf der außen ein Bezeichner (oder Name) steht und die einen Wert enthält. Während der Bezeichner immer gleich ist, kann der Inhalt variieren. Jede Variable hat einen Variablentypen, der bei Programmiersprachen normalerweise zu Beginn des Programmes fest definiert wird und sich nicht ändern kann. Da Lua aber ja keine Programmiersprache ist, muss der Variablentyp zu Beginn und auch sonst nie festgelegt werden und kann sich beliebig oft verändern. Der Interpreter erkennt Variablentypen automatisch und nimmt dem Skripter somit eine Menge Arbeit (vor allem bzgl. der Planung) ab.
Typen
Im Folgenden will ich die wichtigsten Variablentypen auflisten und kurz beschreiben.
- Integer (ganze Zahlen)
- Umfasst alle Ganzzahlen, sowohl negative als auch positive.
- Beispiele: -8, 0, 12, 2
- Float (Komma- bzw. gebrochene Zahlen)
- Umfasst alle Fließkommazahlen, sowohl negative als auch positive. Wichtig dabei ist, dass anstelle des Kommas (,) ein Dezimalpunkt (.) zum Trennen des ganzen vom gebrochenen Teil verwendet werden muss.
- Beispiele: -77.2, 0.0, 3.14159, 9.81
- String (Zeichenkette)
- Kann jedes beliebige Zeichen enthalten. Eine maximale Länge ist mir nicht bekannt. Strings müssen immer in Anführungszeichen(" oder ') eingschlossen werden.
- Beispiele: "hallo", 'Lua ist toll!', "", " :D "
- Boolean (Schalter)
- Kann zwei verschiedene Werte enthalten: true (dt. wahr bzw. an) oder false (dt. falsch bzw. aus). true entspricht dabei nicht dem Wert 1 und false nicht dem Wert 0.
- Beispiele: true, false
- Nil (Leer oder Nichts)
- nil ist sowohl ein Wert als auch ein Typ. Eine Variable vom Typ nil enthält automatisch den Wert nil. nil bedeutet ganz einfach nichts. Wenn eine Variable nil ist, existiert sie nicht. nil entspricht weder dem Wert 0 noch dem Wert false noch dem Wert ""!
- Beispiele: nil
Ich werde auf jeden Variablentypen noch detaillierter eingehen. Außerdem wurden hier die Variablentypen function und table nicht aufgeführt, da diese jeweils ein eigenes Kapitel bekommen. Zwei weitere Typen, die diese Liste vervollständigen, sind userdata und thread, auf die ich jedoch in diesem Tutorial nicht weiter eingehen werde, da vor allem letzteres vollkommen irrelevant für MTA ist.
Definition
Eine Variable definieren bedeutet im Grunde genommen eine Variable anlegen. In Lua muss einer Variable bei der Definition ein Wert zugewiesen werden. nil ist dabei als Wert auch möglich, macht jedoch wenig Sinn, da jede beliebige Variablenbezeichnung automatisch den Wert nil enthält.
Man unterscheidet zwischen lokalen und globalen Variablen. Um eine Variable als lokal zu definieren muss das Schlüsselwort local davor geschrieben werden. Lokal bedeutet für uns im Moment noch ganz einfach, dass die Variable nur im aktuellen Script verfügbar ist. Sollten also z.B. von einem Programm zwei verschiedene Lua Skripte geladen sein und benutzt werden, können diese untereinander auf die globalen Variablen des jeweils anderen Skripts zugreifen, auf die lokalen jedoch nicht. Generell empfiehlt es sich, immer lokale Variablen zu nehmen, falls man nicht explizit eine globale benötigt, da so vermieden wird, dass mehrere parallel laufende Skripte aufgrund von gleichen Variablenbezeichnern durcheinander kommen.
Einzeln
Definieren wir uns also nun unsere erste lokale Variable und geben ihr einen ganzzahligen Inhalt:
local zahl = 7 -- weist der lokalen Variable zahl den Wert 7 zu
Eine globale Variable erhält man, indem man einfach das local weglässt:
gZahl = 13 -- weist der globalen Variable gZahl den Wert 13 zu
Das selbe geht natürlich nun auch mit allen anderen Variablentypen:
local ganzzahl = -5 local kommazahl = 0.008 local zeichenkette = "Hello World" local schalter = true local nichts = nil -- diese Zeile kann man sich im Grunde genommen sparen, da jede nicht definierte Variable automatisch den Wert nil hat
Längere Strings mit mehreren Zeilenumbrüchen können in einer vereinfachten Weise zugewiesen werden. Dies funktioniert so ähnlich wie bei Kommentaren. Anstelle von Anführungszeichen verwendet man als Anfangs- und als Endmarkierung des Strings.
local text = [[Willkommen auf unserem Server. Bitte halte Dich an die Regeln. MfG die Administration]]
Alternativ kann in einem String, der in doppelten Anführungszeichen (") eingeschlossen ist, folgende Steuerzeichen enthalten:
- \n
- Entspricht einem Zeilenumbruch.
- \t
- Entspricht einem Tabulator.
- \"
- Wird verwendet, um doppelte Anführungszeichen in einem String, der von doppelten Anführungszeichen eingeschlossen ist, darzustellen, ohne dass der Interpreter diese fälschlicherweise als Stringende auffasst.
- \'
- Wie \", nur gilt dies in Strings, die von einfachen Anführungszeichen eingeschlossen sind.
- \\
- Entspricht einem Backslash (\)
Mehrere
Man kann in einer Zeile mehrere Variablen definieren, indem man sie mit Kommas voneinander trennt. Die Variablentypen können dabei alle unterschiedlich sein. Hier mal ein Beispiel:
local frucht, anzahl, reif = "apfel", 4, true
local darf dabei nur einmal am Zeilenanfang stehen und bewirkt, dass alle dahinter aufgelisteten Variablen lokal werden.
Sollten links neben dem Gleichheitszeichen mehr Variablenbezeichner stehen als rechts Werte, so bleiben die überschüssigen Variablen nil. Im umgekehrten Fall werden überschüssige Werte rechts einfach ignoriert.
Operatoren
Arithmetische
Arithmetische Operatoren werden verwendet, um Berechnungen durchzuführen. Somit kann man entweder mit Variablen, festen Zahlen oder einer Mischung neue Werte berechnen oder verändern.
Bei Berechnungen gilt stets: Punkt- vor Strichrechnung.
- Plus (+)
- Addiert zwei Werte miteinander.
local a, b = 3, 8 local c = a + b -- a + b entspricht hier der Rechnung 3 + 8, c erhält nun also den Wert 11
- Minus (-)
- Subtrahiert den zweiten vom ersten Wert.
local a, b = 3, 8 local c = a - b -- a - b entspricht hier der Rechnung 3 - 8, c erhält nun also den Wert -5
- Mal (*)
- Multipliziert zwei Werte miteinander.
local a, b = 3, 8 local c = a * b -- a * b entspricht hier der Rechnung 3 * 8, c erhält nun also den Wert 24
- Geteilt (/)
- Dividiert den ersten durch den zweiten Wert. Hierbei entsteht in den meisten Fällen eine Kommazahl!
local a, b = 3, 8 local c = a / b -- a / b entspricht hier der Rechnung 3 / 8, c erhält nun also den Wert 0.375
- Modulo (%)
- Ermittelt den Rest, der übrig bleibt, wenn man die erste Zahl durch die zweite teilt.
- Der Modulo-Operator gehört zur Punktrechnung.
local a, b = 14, 3 local c = a % b -- a % b entspricht hier der Rechnung 14 % 3, c erhält nun also den Wert 2, da 14 / 3 = 4 REST 2
- Minus (-) (als Vorzeichen)
- Dient zur Darstellung einer negativen Zahl, also einer Zahl unter 0.
local a = -3 -- entspricht der Rechnung a = 0 - 3
- Hoch (^)
- Potenziert die erste Zahl mit der zweiten.
- Potenzen werden noch vor der Punktrechnung ausgerechnet.
local a, b = 3, 8 local c = a ^ b -- a ^ b entspricht hier der Rechnung 3 ^ 8 (3<sup>8</sup>), c erhält nun also den Wert 6561
Logische
Logische Operatoren werden Benutzt, um Werte miteinander zu vergleichen und mehrere Bedingungen zu verknüpfen. Wir werden genauer auf sie zurückkommen, wenn es um Bedingungen geht. Bis dahin reicht es, wenn man sich merkt, dass ein logischer Vergleich entweder true (wenn die Aussage wahr ist) oder false (wenn die Aussage falsch ist) ergibt.
- Gleich (==)
- Prüft, ob zwei Werte gleich sind.
local a = (5 == 5) -- a ist true, da die Aussage wahr ist local b = (5 == 8) -- b ist false, da die Aussage falsch ist
- Ungleich (~=)
- Prüft, ob zwei Werte ungleich sind.
local a = (5 ~= 5) -- a ist false, da die Aussage falsch ist local b = (5 ~= 8) -- b ist true, da die Aussage wahr ist
- Größer als (>)
- Prüft, ob der erste Wert größer als der zweite ist.
local a = (5 > 5) -- a ist false, da die Aussage falsch ist local b = (5 > 8) -- b ist false, da die Aussage falsch ist local c = (8 > 5) -- c ist true, da die Aussage wahr ist
- Größer als oder gleich (>=)
- Prüft, ob der erste Wert größer als der zweite ist oder dem zweiten gleicht.
local a = (5 >= 5) -- a ist true, da die Aussage wahr ist local b = (5 >= 8) -- b ist false, da die Aussage falsch ist local c = (8 >= 5) -- c ist true, da die Aussage wahr ist
- Kleiner als (<)
- Prüft, ob der erste Wert kleiner als der zweite ist.
local a = (5 < 5) -- a ist false, da die Aussage falsch ist local b = (5 < 8) -- b ist true, da die Aussage wahr ist local c = (8 < 5) -- c ist false, da die Aussage falsch ist
- Kleiner als oder gleich (<=)
- Prüft, ob der erste Wert kleiner als der zweite ist oder dem zweiten gleicht.
local a = (5 <= 5) -- a ist true, da die Aussage wahr ist local b = (5 <= 8) -- b ist true, da die Aussage wahr ist local c = (8 <= 5) -- c ist false, da die Aussage falsch ist
- Und (and)
- Verknüpft zwei Bedingungen miteinander.
local a = (true and false) -- a ist false, da nicht beide Aussagen wahr sind local b = (5 < 8) and (7 == 7) -- b ist true, da beide Aussagen wahr sind local c = (8 > 5) and (1 ~= 2) and (0 <= -7) -- c ist false, da die dritte Aussage falsch ist
- Oder (or)
- Verknüpft zwei Bedingungen miteinander.
local a = (true or false) -- a ist true, da die erste Aussage wahr ist local b = (5 < 8) or (7 == 7) -- b ist true, da mindestens eine der Aussagen wahr ist local c = (8 > 5) or (1 ~= 2) or (0 <= -7) -- c ist true, da mindestens eine der Aussagen wahr ist
- Nicht (not)
- Kehrt einen Booleanwert um.
local a = not true -- a ist false local b = not false -- b ist true local c = not (0 == 8) -- c ist true, da die Aussage falsch ist
Sonstige
Lua hat noch zwei weitere Operatoren, die sich jedoch nicht in die oberen Gruppen eingliedern lassen.
- Länge (#)
- Steht für die Länge eines Strings oder einer Table.
local sprache = "deutsch" local laenge = #sprache -- laenge wird hier auf 7 gesetzt, da das Wort "deutsch" 7 Buchstaben hat
- Verknüpfung (..)
- Verknüpft zwei Strings miteinander.
local geschlecht, alter = "männlich", "18" local satz = "Ich bin "..geschlecht.." und "..alter.." Jahre alt." -- weist satz den Wert "Ich bin männlich und 18 Jahre alt." zu
Natürlich können auch alle anderen Variablentypen mit- und untereinander verglichen werden.
Der Längenoperator (#) sowie der Verknüpfungsoperator (..) sind nur für Strings gedacht, wobei der erste später noch bei Tables eine Rolle spielen wird und der zweite es nicht so genau nimmt mit den Variablentypen. Bei Booleans und nil jedoch führt er in jedem Fall zu einem Fehler. Wir kommen später noch darauf zu sprechen, wie man soetwas umgeht.
Für Integer und Floats sind alle Vergleichsoperatoren und die arithmetischen verfügbar. Strings unterstützen nur die Vergleichsoperatoren, wobei man mit den größer/kleiner Operatoren zwei Strings nach dem Alphabet ordnen kann.
"a" < "b" -- ergibt true, da a vor b im Alphabet kommt
Strings, Booleans und nil kann man nur untereinander vergleichen, nicht miteinander. Miteinander führt in jedem Fall zu false.
Bedingungen
Einführung
Bedingungen sind dazu da, um bestimmte Anweisungen nur auszuführen, wenn ein bestimmter Sachverhalt gegeben ist. Ein Beispiel wäre, wenn ein Spieler einem anderen Spieler eine private Nachricht senden möchte. Dann kann er das natürlich nur tun, wenn der adressierte Spieler auch online ist. Also soll die Nachricht in einem solchen Fall gesendet werden, andernfalls dem Sender jedoch eine Fehlermeldung ausgegeben werden.
Syntax
Die Syntax einer Bedingung, die man übrigens auch „if-Statement“ (if = engl. für wenn) oder im Englischen „condition“ nennt, ist recht simpel.
if <bedingung> then -- Anweisung(en), die ausgeführt werden soll(en), wenn die Bedingung wahr (=true) ist end
Für <bedingung> darf hier jeder Audruck eingesetzt werden, der letztendlich irgendeinen Wert ergibt. Sollte <bedingung> nil oder false sein, so wird die Anweisung zwischen then und end nicht ausgeführt, andernfalls wird sie ausgeführt.
Else-Statement
Auf jedes if-Statement darf ein else-Statement (else = engl. für sonst) folgen. Dieses benötigt kein then und auch keine Bedingung, sondern wird ausgeführt, wenn die Bedingung des if-Statements nil oder false ist. Das else-Statement ist optional!
if <bedingung> then -- Anweisung(en), die ausgeführt werden soll(en), wenn die Bedingung erfüllt wird else -- Anweisung(en), die ausgeführt werden soll(en), wenn die Bedingung nicht erfüllt wird end
Elseif-Statement
Das elseif-Statement ist ein spezielles Statement, was es erlaubt, einer unübersichtlichen Verschachtelung zu entgehen, indem es die Möglichkeit bietet, einem else-Statement auch eine Bedingung zu geben. Ein if-Statement darf beliebig viele elseif-Statements enthalten. Diese müssen jedoch zwischen dem if- und dem else-Statement (falls vorhanden) stehen. Für die Bedingung gilt hier das selbe, wie für das if-Statement. Wichtig ist, dass demnach auch hier ein then erforderlich ist. Genauso wie das else-Statement ist jedoch auch das elseif-Statement optional!
if <bedingung1> then -- Anweisung(en), die ausgeführt werden soll(en), wenn die Bedingung 1 erfüllt wird elseif <bedingung2> then -- Anweisung(en), die ausgeführt werden soll(en), wenn die Bedingung 2 erfüllt wird else -- Anweisung(en), die ausgeführt werden soll(en), wenn weder Bedingung 1 noch Bedingung 2 erfüllt werden end
Bedingungen formulieren
Bei der Bedingung eines if-Statements muss nicht zwangsläufig ein Boolean herauskommen. Wie bereits erwähnt, ist die Bedingung immer dann wahr, wenn sie weder nil noch false ist. D.h., dass auch Werte wie 0, "Hallo" und 3.14159 als „wahr“ gelten. Natürlich ist das auch bei true der Fall.
Um komplexere Bedingungen zu formulieren, werden auf jeden Fall die bereits aufgelisteten logischen Operatoren benötigt. Mit diesen kann man für jeden nur erdenklichen Fall eine Bedingung formulieren.
Wenn logische (Vergleichs-)Operatoren (==, ~=, <, >, <=, >=) angewandt werden, kommt dabei immer ein Boolean raus, wie man auch schon an den gegebenen Beispielen erkennen kann. Verknüfungen (and, or) sind dazu gedacht, um z.B. zu überprüfen, ob mehrere oder wenigstens eine Bedingung von vielen erfüllt werden oder nicht. Der not-Operator ermöglicht es, zu überprüfen, ob eine Bedingung nicht erfüllt wird.
Zu dem and- und dem or-Operator möchte ich an dieser Stelle noch etwas ergänzen. Der and-Operator wird zunächst einmal dazu verwendet, um herauszufinden, ob zwei Bedingungen gleichzeitig erfüllt sind. Der or-Operator wird dazu benutzt, um herauszufinden, ob eine von zweien oder beide Bedingungen erfüllt sind.
Weder der and- noch der or-Operator geben aber zwangsläufig einen Boolean zurück.
local foo = wert1 and wert2
Sollte wert1 nil oder false sein, so entspricht dieser Ausdruck dem folgenden:
local foo = wert1
Ist wert1 weder nil noch false, so entspricht der Ausdruck dem hier:
local foo = wert2
Bei or ist es ähnlich, nur umgekehrt.
local foo = wert1 or wert2
Sollte wert1 nil oder false sein, so entspricht dieser Ausdruck dem folgenden:
local foo = wert2
Ist wert1 weder nil noch false, so entspricht der Ausdruck dem hier:
local foo = wert1
Beispiele
50-50-Chance
Dieses Beispiel simuliert einen Münzwurf. math.random() und print() sind standard Lua-Funktionen. Zum Thema Funktionen komme ich später. Für dieses Beispiel reicht es, wenn ihr wisst, dass math.random() eine Zufallszahl (float) zwischen 0 und 1 zurückgibt und print("text") den in den Klammern eingeschlossenen String in die Konsole ausgibt.
local zufall = math.random() if (zufall < 0.5) then print("Kopf") else print("Zahl") end
Der Nutzen von elseif
Im Folgenden wollen wir herausfinden, ob eine Zahl positiv, negativ oder 0 ist. Dazu möchte ich zuerst ein Beispiel ohne und danach eines mit elseif zeigen, um meine Aussage, dass elseif Verschachtelungen spart, zu verifizieren.
local zahl = 14 --ohne elseif if (zahl < 0) then print("Die Zahl ist negativ.") else if (zahl == 0) then print("Die Zahl entspricht 0.") else print("Die Zahl ist positiv.") end end --mit elseif if (zahl < 0) then print("Die Zahl ist negativ.") elseif (zahl == 0) then print("Die Zahl entspricht 0.") else print("Die Zahl ist positiv.") end
Je komplexer das ganze if-Statement, desto nützlicher wird elseif sein.
Eingebettetes Pseudo-if-Statement
In vielen Script- und Programmiersprachen gibt es ein gekürztes if-Statement. In Javascript z.B. geht das wie folgt:
var nachricht = "Du benutzt zurzeit " + (ie ? "den Internet Explorer" : "einen guten Browser") + ".";
Wir gehen dabei davon aus, dass in der Variable ie ein Boolean gespeichert ist, der true ist, wenn der Benutzer den Internet Explorer benutzt, ansonsten false. Diese Art von eingebetteten if-Statements gibt es noch in vielen anderen Sprachen, unter anderen in PHP, Java und C(++). In meinem Beispiel kann man sich das ? als then und den : als else vorstellen. Dann sollte es eigentlich nicht allzu schwer sein, diese Art von if-Statement zu verstehen.
So direkt gibt es das bei Lua nicht, aber mit and und or kann man sich ein Konstrukt bauen, welches auf die gleiche Weise funktioniert. Dazu werde ich einfach mal das obere Beispiel in Lua umwandeln, auch, wenn Lua eigentlich nicht viel mit Browsern am Hut hat.
local nachricht = "Du benutzt zurzeit "..(ie and "den Internet Explorer" or "einen guten Browser").."."
Zuerst ist das and an der Reihe. Es schmeißt den String "den Internet Explorer" raus, wenn ie false (oder nil) ist und zurück bleibt auf jeden Fall false (oder nil). Das wird dann vom or rausgeschmissen und zurück bleibt "einen guten Browser". Wenn ie weder false noch nil ist, schmeißt and die Vairable ie raus und zurück bleibt "den Internet Explorer". Da dieser String weder false noch nil ist, wird er vom or beibelassen und "einen guten Browser" rausgeschmissen. Entschuldigt meine Ausdrucksweise mit dem rausschmeißen, aber das schien mir die beste Möglichkeit, diese Methode verständlich zu erklären.
Tabellen
Einführung
Tabellen sind spezielle Variablen, die mehrere Werte enthalten können. Damit wir Tabellen verwenden können, führe ich nun erst einmal den Variablentyp „table“ ein. Mit einer table-Variable kann man ersteinmal kaum Operationen durchführen. Dazu benötigt man Funktionen, zu denen wir aber erst später kommen.
Definition
Um eine Variable als (leere) table zu definieren, benutzt man diesen Ausdruck:
local tabelle = {}
Ohne Funktionen macht das noch recht wenig Sinn. Um einer Tabelle direkt mehrere Werte zuzuweisen, schreibt man diese einfach durch Kommata getrennt zwischen die geschweiften Klammern.
local tabelle1 = { "Apfel", 14, 3.14159, true }
Eine table kann alle möglichen Typen von Variablen enthalten. So zum Beispiel auch andere Tabellen.
local tabelle2 = { 1, { 1.25, 1.5, 1.75 }, 2, { 2.25, 2.5, 2.75 } }
Um nun auf die Elemente einer table zugreifen zu können, benutzt man Indizes (Indizes ist der Plural von Index). Jedes Element in einer table hat einen Index. Wenn man die Tabellen so definiert, wie ich es gemacht habe, so wird jedem Element ein integer als Index zugewiesen, wobei das erste Element die 1 als Index bekommt, das zweite die 2 usw. Der ungefähre Wert von Pi (3.14159) in der oberen Tabelle hat den Index 3. Um anhand eines Indizes ein Element einer Tabelle abzufragen, schreibt man den Index einfach in eckigen Klammern hinter den Tabellennamen. tabelle1[3] entspricht also dem Wert 3.14159. Wenn das Element der Tabelle selbst noch eine Tabelle ist, wie z.B. das Element mit dem index 2 aus tabelle2, dann setzt man die Indizes einfach hintereinander. Der Wert 2.25 ist demnach in tabelle2[4][1] gespeichert.
Ganzzahlige Indizes
Um gezielt bestimmten Indizes einen Wert zuzuweisen, kann man folgende Schreibweise verwenden:
local auskunft = { [11] = "elf", [88] = "achtundachtzig", [0] = "null" }
Dabei spielt es keine Rolle, in welcher Reihenfolge die Indizes einen Wert zugewiesen bekommen und es spiel auch keine Rolle, ob die Indizes positiv oder negativ sind. Es muss jedoch beachtet werden, dass etwas wie { [1] = "eins", "zwei" } dem Index 1 den Wert "zwei" zuweisen würde, da Lua immer bei 1 anfängt zu zählen, egal, ob der Scripter den Index 1 bereits definiert hat.
Zeichenketten als Indizes
Ein Index kann auch ein String sein. Dazu verwendet man folgende Schreibweise:
lua konstanten = { ["pi"] = 3.14159, ["e"] = 2.71828 }
Ansprechen kann man diese Werte genauso wie bei integer-Indizes. konstanten["pi"] ist also 3.14159.
Eine Besonderheit bei Zeichenketten als Index ist, dass es eine vereinfachte Schreibweise gibt. Sowohl bei der Definition als auch bei der Abfrage der Elemente. Das obige Beispiel kann auch so realisiert werden:
lua konstanten = { pi = 3.14159, e = 2.71828 }
Zum Abfragen der Elemente kann man die Schreibweise konstanten.pi bzw. konstanten.e verwenden. Man sollte aber bedenken, dass Schlüsselwörter wie z.B. if oder local als Indizes besser mit ["if"] und ["local"] definiert werden.
Beliebige Indizes
Ein Index muss nicht unbedingt ein integer oder ein string sein. Genauer gesagt kann ein Index ein beliebiger Wert (außer nil) sein. Somit wäre z.B. auch das möglich:
local tabelle = { [true] = "wahr", [3.14159] = "pi", [0] = 1, 123 }
Die Zahl 123 hätte hier übrigens den Index 1.
Einzelzuweisungen
Man muss nicht immer alle Werte einer table auf einmal definieren. Jedes Tabellenelement lässt sich wie eine normale Variable behandeln.
local tabelle = {} tabelle[true] = "wahr" tabelle.pi = 3.14159 tabelle[2] = { 1, 2, 3 } tabelle[2][0] = 0
Jedes undefinierte Element hat - genauso wie jede undefinierte Variable - den Wert nil.
Verlinkungen
local tabelle1 = { 1, 2, 3 } local tabelle2 = tabelle1 tabelle1[1] = 4
In der zweiten Zeile dieses Beispiels wird die tabelle1 nicht in die Variable tabelle2 kopiert, sondern tabelle2 verweist ab sofort auf tabelle1. Am Ende des Codes wäre also tabelle2[1] auch 4, obwohl es nicht explizit zugewiesen wurde.
Um eine Tabelle zu kopieren, könnt ihr euch die Useful Function table.copy aus der Wiki kopieren und diese dazu verwenden.
Schleifen
Einführung
Schleifen verwendet man immer dann, wenn man eine oder mehrere Anweisungen mehr als einmal direkt hintereinander ausführen möchte oder wenn man eine oder mehrere Anweisungen für jedes Element einer table ausführen möchte. Es gibt drei verschiedene Arten von Schleifen. Diese werde ich jetzt hier einzeln vorstellen.
While-Schleife
Die while-Schleife wiederholt eine oder mehrere Anweisungen solange, bis eine bestimmte Bedingung nicht mehr erfüllt wird. Die Syntax ist die folgende:
while [bedingung] do -- Anweisung(en) end
Beispiel:
local zahl = 10 while (zahl >= 0) do print(zahl) zahl = zahl - 1 end
Das Beispiel gibt nacheinander alle Zahlen von 10 bis 0 aus. Nach der Schleife hat zahl dann den Wert -1.
Repeat-Until-Schleife
Die repeat-until-Schleife wiederholt eine oder mehrere Anweisungen solange, bis eine bestimmte Bedingung erfüllt ist. Sie führt diese Anweisung(en) aber in jedem Fall einmal aus. Die Syntax lautet:
repeat -- Anweisung(en) until [bedingung]
Das obere Beispiel lässt sich hiermit wie folgt realisieren:
local zahl = 10 repeat print(zahl) zahl = zahl - 1 until (zahl < 0)
For-Schleife
Es gibt zwei verschiedene for-Schleifen. Einmal die einfache numerische for-Schleife und zum Anderen die generische for-Schleife, mit der man u.a. alle Elemente einer tabelle durchlaufen kann. Mit der generischen for-Schleife kann man noch ganz andere wesentlich komplexere Operationen durchführen, die ich hier aber nicht erklären werde, da sie auch - zumindest beim Scripten für MTA - nicht wirklich benötigt werden.
Numerisch
Syntax:
for [laufvariable], [maximum], [schrittweite] do -- Anweisung(en) end
Die Laufvariable wird nach jedem Durchlauf der Schleife um die Schrittweite erhöht, bis sie größer oder gleich dem Maximum ist. Alle drei Werte müssen numerisch sein, d.h. ein integer oder ein float. Die Schrittweite kann auch weggelassen werden. Sie ist dann automatisch 1. Unser Beispiel von oben sähe mit einer for-Schleife so aus:
for i=10, 0, -1 do print(i) end
Die Variable i ist nur innerhalb der Schleife verfügbar. Damit sie auch nach der Schleife noch verfügbar ist, muss man sie schon vor der Schleife deklarieren.
Generisch
Die generische for-Schleife wird hauptsächlich dazu verwendet, um tables zu durchlaufen. Ich werde sie daher auch nur in diesem Zusammenhang erläutern. Die Syntax für den Fall, dass man eine table durchlaufen will, ist:
for [index], [wert] in pairs([tabelle]) do -- Anweisung(en) end
Diese Schleife geht durch alle Elemente der Tabelle und führt die Anweisungen somit für alle Elemente der Tabelle aus, wobei Index jedes Mal dem aktuellen Index des Tabellenelements und Wert jedes Mal dem aktuellen Wert des Tabellenelements entspricht. Anstelle von pairs() kann man auch ipairs() nehmen, wenn man nur alle ganzzahligen Indizes durchlaufen möchte. Hier mal ein Beispiel:
local tabelle = { eins = 1, "hallo", pi = 3.14159 } -- mit pairs for index, wert in pairs(tabelle) do print(tostring(index).."="..tostring(wert)..", ") end -- Ausgabe: "eins=1, 1=hallo, pi=3.14159, " -- mit ipairs for index, wert in ipairs(tabelle) do print(tostring(index).."="..tostring(wert)..", ") end -- Ausgabe: "1=hallo, "
tostring() ist übrigens eine Funktion, die eine beliebige Variable in einen string umwandelt. Wenn man Variablen mit einem string verknüpfen will und sich nicht sicher ist, ob diese Variablen string sind oder nicht, sollte man auf jeden Fall diese Funktion verwenden, da ansonsten Fehler auftreten können. (Lua gibt einen Error aus, wenn man z.B. soetwas versucht: "Hallo "..nil..".")
Schleifenabbruch
Mit dem Schlüsselwort break kann man eine Schleife sofort abbrechen. Hier ein kurzes Beispiel dazu:
local zahl = 10 while (true) do print(zahl) zahl = zahl - 1 if (zahl < 0) then break end end
Diese Schleife würde auch alle Zahlen von 10 bis 0 nacheinander ausgeben, nur dass der Abbruch manuell erfolgt, wenn zahl negativ wird. Ohne diese Bedingung würde die Schleife endlos laufen.
Funktionen
Einführung
Funktionen sind im Grunde genommen Anweisungsblöcke, die - meist in Abhängigkeit von übergebenen Werten - mehrmals an unterschiedlichen Orten im Script bzw. zu unterschiedlichen Zeiten ausgeführt werden sollen. Da eine Funktion - sofern sie mehr als einmal ausgeführt werden soll bzw. mehr als einmal benötigt wird - auch in einer Variable gespeichert werden kann, kann man den Variablentyp function einführen, der in Lua auch tatsächlich existiert. Funktionen können Werte übergeben bekommen und auch Werte zurückgeben.
Definition
Die allgemeine Syntax für das Definieren einer Funktion ist die folgende:
function [funktionsname]([parameterliste]) -- Anweisung(en) end
Ein Beispiel für eine Funktion, die das Quadrat einer Zahl zurückgibt, wäre:
function quadrat(zahl) return zahl * zahl end
Alternativ kann eine Funktion übrigens auch so einer Variable zugewiesen werden:
quadrat = function (zahl) return zahl * zahl end
Rückgabewerte
return wird verwendet, um die Funktion einen Wert zurückgeben zu lassen. Nach einem return-Befehl bricht die Funktion alle Tätigkeiten ab und ist beendet. Um einen Rückgabewert verwenden zu können, müssen wir aber ersteinmal wissen, wie man eine Funktion dazu bringt, die Anweisungen, die sie enthält, auszuführen. Das geht allgemein mit funktionsname(parameterliste). Als Beispiel für unsere quadrat-Funktion:
local vier = quadrat(2)
Wenn wir quadrat(2) ausführen, dann wird der obrige Code ausgeführt, wobei die Variable zahl den Wert 2 zugewiesen bekommt. zahl ist dabei jedoch nur innerhalb der Funktion definiert. Die Variable vier bekommt nun den Wert 2 * 2, also 4 zugewiesen.
local vier = quadrat
würde die Funktion quadrat in die Variable vier kopieren. Man könnte nun also auch vier(2) aufrufen, um 4 zu bekommen.
Eine Funktion kann auch mehrere Funktionswerte zurückgeben. Das ist vor allem dann sinnvoll, wenn es sich um Positionen handelt, was bei MTA ja nicht selten vorkommt. Beispiel:
function berechnungen(zahl1, zahl2) return zahl1 + zahl2, zahl1 - zahl2, zahl1 * zahl2, zahl1 / zahl2 end local summe, differenz, produkt, quotient = berechnungen(5, 10)
Damit wird summe = 15, differenz = -5, produkt = 50 und quotient = 0.5.
Lua Standardfunktionen
Funktionen wie tostring, print und math.random sind Standardfunktionen von Lua. Eine komplette Liste aller Lua Standardfunktionen bekommt ihr auf der offiziellen Lua Webseite (Englisch).
Die Standard-string-Funktionen erlauben eine besondere Syntax, die ich hier kurz erläutern möchte, da sie von vielen erfahrenen Scriptern genutzt wird und es hilfreich ist, wenn man etwas damit anfangen kann.
local name = "Firzen Polas" local laenge = string.len(name)
Dieser Code weist der Variable laenge die Länge des strings "Firzen Polas", also 12, zu. Alternativ kann man auch den folgenden Code verwenden:
local name = "Firzen Polas" local laenge = name:len()
Vorraussetzung ist, dass die Variable vor dem Doppelpunkt ein string ist. Bei den math- und table-Funktionen funktioniert dies nicht.
Rekursive Funktionen
Rekursive Funktionen sind eine effektive Alternative zu Schleifen - zumindest in manchen seltenen Fällen. Das berühmteste Beispiel ist das Berechnen einer Fakultät. Zur Information: n! (sprich: n Fakultät) bezeichnet das Produkt aller Ganzzahlen von n bis 1, also quasi 1*2*3*4*...*n. Beispiel: 3! = 1 * 2 * 3 = 6
function fakultaet(zahl) if (zahl > 0) then return zahl * fakultaet(zahl - 1) else return 1 end end
Mit Schleife sähe das so aus:
function fakultaet(zahl) if (zahl == 0) then return 1 end for i=zahl-1, 1, -1 do zahl = zahl * i end return zahl end
Variable Parameterzahl
Manchmal benötigt eine Funktion nicht zwingend eine bestimmte Anzahl von Parametern oder soll eine variable Anzahl an Parametern akzeptieren. Dies lässt sich realisieren mit drei Punkten am Ende der Parameterliste.
function aufsummieren(...) local parameter = {...} local summe = 0 for _, zahl in pairs(parameter) summe = summe + tonumber(zahl) end return summe end
... entspricht dabei innerhalb der Funktion der Parameterliste als normale Liste. D.h., wenn man aufsummieren(1, 2, 3, 4) aufruft, dann wird parameter = {1, 2, 3, 4}.
Fragen, Anregungen, Kritik
Sollten noch irgendwelche Fragen offen sein, so könnt ihr jederzeit in unserem IRC Channel oder in unserem Forum vorbeischauen und eure Fragen dort loswerden. Außerdem haben wir in unserem IRC Channel einen Lua Bot, an dem ihr Funktionen, die auf den standard Lua Funktionen basieren, ausprobieren könnt.
Anregungen und Kritik nehme ich dort auch entgegen. Alternativ könnt ihr diese hier in die Diskussion schreiben. Falls ihr Fehler findet, behebt diese bitte oder gebt mir Bescheid.
Ich hoffe, das Tutorial konnte euch weiterhelfen. Wenn ihr nun Hilfe braucht bei eurem ersten Gamemode in MTA, dann solltet ihr euch die MTA Scripting Grundlagen durchlesen. Viel Erfolg.