DE/MTA Scripting Grundlagen

From Multi Theft Auto: Wiki
Jump to navigation Jump to search

Vorwort

Nachdem ich bereits vor kurzer Zeit mein Lua Tutorial fertiggestellt habe, folgt nun eine Einführung in die Grundlagen des MTA Scriptings, in der diesmal direkter Bezug auf MTA genommen wird.

In Kürze wird es hier im Wiki einen blank Gamemode geben, den man sich herunterladen kann und als Basis für seinen eigenen verwenden kann. Die soll vor allem Anfängern den Einstieg in's MTA Scripting erleichtern.


Grundlagen

In diesem Tutorial wird häufig die Rede von Server und Client sein. Ein Client ist quasi ein Spieler, allgemein ist damit jedoch das Programm gemeint, was der Spieler auf dem Rechner installiert hat und mit dem er MTA spielen kann. Mit Server ist einfach der MTA Server gemeint. Die Hauptaufgabe des Servers besteht darin, Daten von allen Clients bezüglich Position usw. einzusammeln und gleichzeitig an jeweils alle anderen Clients zu verteilen. Im Gegensatz zum Client ist der Server nicht in der Lage physikalische oder graphische Effekte zu berechnen, weswegen viele Funktionen von MTA, die eines dieser beiden Bereiche beeinflussen, clientseitig sind.

MTA unterscheidet zwischen zwei verschiedenen Ausführungsorten. D.h., dass es Scripts gibt, die vom Server ausgeführt und mit allen Clients synchronisiert werden und welche, die von jedem Client selbst ausgeführt werden und nicht mit allen anderen Clients synchronisiert werden. Clientseitig bedeutet, dass etwas nur vom Client ausgeführt wird/werden kann, serverseitig, dass es nur vom Server ausgeführt wird/werden kann. Die meisten Funktionen von MTA können jedoch auf beiden Seiten ausgeführt werden. Ziel ist es, diese Gegebenheit zu nutzen und den Server somit so gut wie möglich zu entlasten, was nicht immer einfach ist.

Bei MTA spricht man übrigens nicht von Scripts oder Gamemodes, sondern allgemein von Ressourcen. Eine Ressource ist ein Verzeichnis mit einem oder mehreren Scripts. Beim Start des MTA Servers wird der ressources Ordner durchsucht und jedes Verzeichnis, dass eine gültige meta.xml enthält, der Liste der verfügbaren Ressourcen hinzugefügt.


Meta.xml

Sinn und Zweck

Die meta.xml ist eine wichtige Datei, die in jedem Ressourcenordner vorhanden sein muss, damit diese vom MTA Server erkannt wird und ausgeführt werden kann. Sie definiert, aus welchen Dateien sich die Ressource zusammensetzt und unter anderem auch, welche dieser Dateien beim Connecten an den Client gesendet werden sollen.

Alle Daten in der meta.xml sind im XML-Format. Ein sogenannter XML-Tag ist ein Datenset, was in etwa so aussieht: <tagname attribut="wert">inhalt</tagname> oder <tagname attribut="wert" />, wenn der Inhalt entfällt. Ein XML-Tag kann beliebig viele Attribute enthalten.

Nähere Informationen zum Thema XML entnehmt ihr aber bitte dem Wikipedia-Artikel darüber.


Mögliche Tags

<info author="Autor" version="Version" name="Name" description="Beschreibung" type="Typ" />
  • Jede Ressource sollte einen info-Tag in der meta.xml enthalten. Werte des info-Tags kann man übrigens mit getResourceInfo auslesen.
    • author: Das ist normalerweise der Name desjenigen, der die Ressource erstellt hat.
    • version: Die Version der vorliegenden Ressource. Für alle, die gerne mit wahllosen Versionsnummern um sich schmeißen.
    • name: Der Name der Ressource.
    • description: Eine kurze Beschreibung der Ressource.
    • type: Typ der Ressource. Kann den Wert "gamemode", "script" oder "map" enthalten.
<script src="Dateipfad und -name" type="Typ" />
  • Mit dem script-Tag werden Lua-Scriptdateien in die Ressource eingebunden. Das sind die wohl wichtigsten Dateien der meisten Ressourcen.
    • src: Der Pfad zur Scriptdatei relativ zum Odner der jeweiligen Ressource mit dem Dateinamen natürlich.
    • type: Typ des Scripts. Kann entweder "server" oder "client" sein. Bei ersterem wird das Script serverseitig ausgeführt, bei letzterem jedem Client beim Verbindungsaufbau gesendet und clientseitig ausgeführt.
<map src="Dateipfad und -name" />
  • Mit dem map-Tag werden Mapdateien eingebunden, die automatisch beim Ressourcenstart geladen werden. Mit den Mapfunktionen kann man nachträglich noch Maps laden. Eine Map ist übrigens eine Karte, genauer ein Set von ObjektIDs mit Koordinaten und Rotationsangaben.
    • src: Der Pfad zur Mapdatei relativ zum Odner der jeweiligen Ressource mit dem Dateinamen natürlich.
<file src="Dateipfad und -name" />
  • Der file-Tag zeichnet eine beliebige Datei als clientseitig aus. Während Scripts vom Typ "client" automatisch heruntergeladen werden, werden es die darin enthaltenen Bilder, Models oder Texturen nicht. Alle Dateien, die im clientseitigen Script verwendet werden, müssen mit dem file-Tag als clientseitig ausgezeichnet werden.
    • src: Der Pfad zur Datei relativ zum Odner der jeweiligen Ressource mit dem Dateinamen natürlich.
<include ressource="Ressourcenname" />
  • Manchmal möchte man, dass eine Ressource beim Start automatisch andere Ressourcen mit startet. Dies kann man mit dem include-Tag erreichen.
    • ressource: Der Name der Ressource, d.h. der Name des Ordners, in dem die Ressource sich befindet. (Muss sich im resources-Ordner befinden.)
<config src="Dateipfad und -name" type="Typ" />
  • Konfigurationsdateien sind XML-Dateien, in denen bestimmte Einstellungen für einen Ressource vorgenommen werden können, auf die per Script zugegriffen werden kann. Unter den Ressourcenfunktionen finden sich solche zum Auslesen und Ändern der Konfiguration.
    • src: Der Pfad zur Konfigurationsdatei relativ zum Odner der jeweiligen Ressource mit dem Dateinamen natürlich.
    • type: Typ der Konfiguration. Kann entweder "server" oder "client" sein. Bei ersterem wird die Konfigurationsdatei serverseitig eingebunden, bei letzterem jedem Client beim Verbindungsaufbau gesendet und clientseitig eingebunden.
<export function="Funktionsname" type="Typ" http="true/false" />
  • Den export-Tag kann man benutzen, um eine Funktion aus der aktuellen Ressource in anderen laufenden Ressourcen verfügbar zu machen.
    • function: Der Name der Funktion.
    • type: Typ des Exports. Kann entweder "server" oder "client" sein. Bei ersterem wird die Funktion serverseitig exportiert, bei letzterem clientseitig.
    • http: Legt fest, ob die exportierte Funktion auch via HTTP aufgerufen werden kann. Kann demnach entweder "true" (ja) oder "false" (nein) sein.
<html src="Dateipfad und -name" default="true/false" raw="true/false" />
  • Wenn über MTAs eingebauten HTTP-Server Zugang zu einer im Ressourcenordner liegenden Datei ermöglichen will, so muss man diese mit dem html-Tag in der meta.xml einbinden.
    • src: Der Pfad zur Datei relativ zum Odner der jeweiligen Ressource mit dem Dateinamen natürlich.
    • default: Wenn das HTML-Dokument standardmäßig angezeigt werden soll, wenn über den Browser auf den Ressourcenordner zugegriffen wird, dann muss default="true" sein, ansonsten "false".
    • raw: Wenn es sich um eine Datei im binären Format handelt, z.B. einem Bild, dann sollte raw auf "true" gesetzt werden.
<settings />


Beispiel

<meta>
    <info author="Slothman" type="gamemode" name="Stealth" />
    <config src="help.xml" type="client"/>

    <script src="stealthmain_server.lua" />
    <script src="noiseblip.lua" />
    <script src="mission_timer.lua" />
    <script src="gadgets_server.lua" />
    <script src="gadgets_client.lua" type="client"/>
    <script src="stealthmain_client.lua" type="client"/>
    <script src="noisebar.lua" type="client"/>
    <script src="spycam.lua" type="client"/>

    <file src="riot_shield.txd" />
    <file src="riot_shield.dff" />
    <file src="riot_shield.col" />
    <file src="armor.png" />
    <file src="camera.png" />
    <file src="cloak.png" />
    <file src="goggles.png" />
    <file src="mine.png" />
    <file src="radar.png" />
    <file src="shield.png" />

    <include resource="scoreboard" />
    <include resource="killmessages" />
    <include resource="maplimits" />

    <settings>
     <setting name="roundlimit" value="[6]" /> <!-- round length in minutes -->
	 <setting name="teamdamage" value="[1]" /> <!-- 0 for team protection off, 1 for team protection on -->
	 <setting name="teambalance" value="[1]" /> <!--  difference limit of amount of players between teams -->
	 <setting name="spazammo" value="[25]" /> <!-- ammo amounts -->
	 <setting name="m4ammo" value="[100]" />
	 <setting name="shotgunammo" value="[25]" />
	 <setting name="sniperammo" value="[20]" />
	 <setting name="ak47ammo" value="[120]" />
	 <setting name="rifleammo" value="[40]" />
	 <setting name="deserteagleammo" value="[45]" />
	 <setting name="pistolammo" value="[132]" />
	 <setting name="uziammo" value="[150]" />
	 <setting name="tec9ammo" value="[150]" />
	 <setting name="silencedammo" value="[65]" />
	 <setting name="grenadeammo" value="[4]" />
	 <setting name="satchelammo" value="[4]" />
	 <setting name="teargasammo" value="[4]" />
	 <setting name="molatovammo" value="[4]" />
     </settings>
</meta>


Elemente

Was sind Elemente?

Ein Element in MTA kann alles sein. Sei es ein Spieler, ein Fahrzeug, ein Objekt, ein Marker, ein GUI-Element oder aber auch eine Ressource. Und das waren nicht mal alle möglichen Elementtypen.

Im Script sind Elemente durch Userdata-Pointer vertreten. Man kann mit ihnen eigentlich nichts anderes machen, als sie speichern oder an MTA-Funktionen übergeben und sie haben auch keinen darstellbaren Wert. Wendet man tostring auf einen Userdata-Pointer an, so erhält man einen String in der Form "userdata: 0x090A34D". Die type-Funktion gibt "userdata" zurück, wenn man ihr ein Element übergibt.


Der Elementbaum

Den Elementbaum kann man sich wie einen Stammbaum vorstellen, nur dass der Ursprung bekannt ist. Der Ursprung des Elementbaumes ist die sogenannte root (engl. für Wurzel). Aus ihr entspringen alle Elemente des ganzen Servers.

Bevor ich nun versuche, ein Bild zu beschreiben, wie man sich das vorstellen soll, zeige ich lieber ein Beispiel für einen MTA Elementbaum im XML-Format. Dieser ist jedoch an einigen Stellen stark vereinfacht, da er sonst den Rahmen sprengen würde, jedoch verschlechtert das die Verbildlichung nicht.

<root>
    <console/>
    <player dontRespawn="false"/>
    <player dontRespawn="false" lastSpawnarea=""/>
    <resource id="resourcebrowser"/>
    <resource id="ajax"/>
    <resource id="resourcemanager"/>
    <resource id="spawnmanager"/>
    <resource id="mapmanager"/>
    <resource id="runcode"/>
    <resource id="fr">
        <map id="dynamic">
            <vehicle/>
        </map>
    </resource>
    <resource id="elementbrowser"/>
    <resource id="assault">
        <map id="dynamic">
            <team/>
            <team/>
            <blip/>
            <marker/>
            <colshape/>
            <blip/>
            <blip/>
        </map>
    </resource>
    <resource id="as-farm">
        <map id="dynamic"/>
        <map id="as-farm.map">
            <meta>
                <author/>
                <version/>
                <name/>
                <description/>
            </meta>
            <spawngroup req="" type="attacker">
                <spawnarea posY="-8.3976354598999" posX="20.182683944702" skins="9" ... />
            </spawngroup>
            <spawngroup req="" type="attacker">
                <spawnarea posY="32.166355133057" posX="-46.90763092041" skins="9" ... />
            </spawngroup>
            <spawngroup req="" type="attacker">
                <spawnarea posY="35.214984893799" posX="-33.486911773682" skins="9" ... />
            </spawngroup>
            <spawngroup req="" type="attacker">
                <spawnarea posY="35.214984893799" posX="-33.486911773682" skins="9" ... />
            </spawngroup>
            <objective id="first" type="checkpoint" description="Breach into the farm" ... />
            <pickup type="weapon" ... />
        </map>
    </resource>
</root>

Wie man in diesem Beispiel sehr schön sehen kann, sind alle Spieler, Ressourcen und die Konsole (das schwarze Serverfenster) direkte Kindelemente vom root-Element. Letzteres könnt ihr im Script übrigens mit getRootElement bekommen. Maps und Fahrzeuge wiederrum sind Kindelemente der jeweiligen Ressourcen und so weiter.


Elemente in MTA-Funktionen

Die meisten MTA-Funktionen verlangen ein oder mehrere Elemente als Parameter oder geben eines zurück. Wenn man ein Element übergibt, was Kindelemente enthält, z.B. das root-Element, dann wendet MTA die Funktion auf alle Kindelemente des übergebenen Elements an, die dem Elementtypen entsprechen, der von der Funktion erwartet wird. Der Typ eines Elements lässt sich mit getElementType herausfinden.


Events

Beschreibung

Ein Event ist ein Ereignis. Das kann z.B. der Tod eines Spielers sein, aber auch das Beenden einer Ressource oder das Klicken auf einen Button.

Ein Eventhandler ist eine Funktion, in unserem Fall eine Lua-Funktion, die aufgerufen wird, wenn ein bestimmtes Event stattfindet. Generell ist alles in MTA eventgesteuert und was nicht eventgesteuert ist, ist zeitgesteuert.

Mit der Funktion addEventHandler kann man in MTA eine Eventhandlerfunktion hinzufügen. Im Wiki finden sich Listen mit vorhandenen serverseitigen Events und auch clientseitigen Events.

Wenn ein Event eintritt, dann werden alle dazugehörigen Eventhandlerfunktionen in einer Umgebung aufgerufen, die die Variable source definiert als den Pointer zu dem Element, welches das Event hervorgerufen hat. Das heißt im Klartext, dass in jeder Eventhandlerfunktion die Variable source das Element enthält, was das Ereignis herbeigeführt hat.

Die Variablen root und resourceRoot sind in jeder Ressource vordefiniert, wobei root dem Rückgabewert von getRootElement und resourceRoot dem von getResourceRootElement entsprechen.

Serverseitiges Beispiel

local gSpawnPosition = { 0, 0, 3 } -- Spawnposition

function onPlayerJoin()
    spawnPlayer(source, unpack(gSpawnPosition)) -- spawne den Spieler
    setCameraTarget(source) -- setze das Ziel seiner Kamera auf sich selbst
    fadeCamera(source, true) -- blende die Umgebung ein
    
    outputChatBox(getPlayerNametagText(source).." hat den Server betreten.") -- sende Nachricht an alle Spieler
end

function onPlayerQuit(quitType)
    outputChatBox(getPlayerNametagText(source).." hat den Server verlassen. (Grund: "..quitType..")") -- sende Nachricht an alle Spieler
end

function onPlayerWasted()
    spawnPlayer(source, getElementPosition(source)) -- respawne den Spieler
end

function onResourceStart()
    for _, player in next, getElementsByType("player") do -- für jedes Spielerelement...
        spawnPlayer(player, unpack(gSpawnPosition)) -- ...spawne den Spieler
        fadeCamera(player, true) -- ...blende die Umgebung ein
    end
end

function onResourceStop()
    for _, player in next, getElementsByType("player") do-- für jedes Spielerelement...
        fadeCamera(player, false) -- ...blende die Umgebung aus
    end
end

-- füge die Eventhandler hinzu
addEventHandler("onPlayerJoin", root, onPlayerJoin)
addEventHandler("onPlayerQuit", root, onPlayerQuit)
addEventHandler("onPlayerWasted", root, onPlayerWasted)
addEventHandler("onResourceStart", resourceRoot, onResourceStart)
addEventHandler("onResourceStop", resourceRoot, onResourceStop)

Man sollte immer bedenken, dass Spielerelemente auf dem gleichen Level sind wie Ressourcenelemente, d.h., dass Spieler auch ohne dass Ressourcen laufen auf dem Server sein können, also auch schon, wenn eine Ressource startet.


Commands

Beschreibung

Commands sind Befehle, die der Spieler beginnend mit einem Schrägstrich (/) im Chat benutzen kann, um bestimmte Funktionen auszuführen. Aufgrund der großen Bandbreite von alternativen Eingabemethoden wie Klicken auf Elemente in der Spielwelt oder GUI-Elemente zählen Commands eher zu den veralteten und unbequemen Eingabemethoden.

Der Ablauf ist ganz einfach: Der Spieler gibt einen Command ein, eventuell mit einem oder mehreren Parametern und MTA ruft eine Lua Funktion auf, der das Element des Spielers, der eingegebene Command sowie die übergebenen Parameter übermittelt werden. Bei der Eingabe werden die Parameter einfach durch Leerzeichen getrennt. Beispiel: /command param1 param2 param3

Commandhandlerfunktionen werden in MTA mit der Funktion addCommandHandler hinzugefügt. Dazu nun einfach mal ein Beispiel für ein einfaches PM-System.


Serverseitiges Beispiel

function cmdPrivateMessage(source, cmd, targetname, ...)
    --[[ da auch die eigentliche Nachricht an den Leerzeichen gespalten und als einzelne Parameter übergeben wird,
         muss man mit dem ...-Parameter eine nicht festgelegte Anzahl Parameter (jeder in diesem Fall ein Wort)
         entgegennehmen und diese wieder zusammenfügen ]]
    local message = table.concat({...}, " ")
    
    if (message == "") then -- wenn die Nachricht leer ist, wenn also kein Spielername und/oder keine Nachricht übergeben wurde
        outputChatBox("Syntax: /pm [str:Spielername] [str:Nachricht]", source, 240, 0, 0) -- Ausgabe der korrekten Syntax
        return -- Abbruch der Funktion
    end
    
    local target = getPlayerFromNick(targetname) -- suche nach einem Spielerelement mit dem gegebenen Namen
    if (not target) then -- wenn kein Spielerelement gefunden wurde
        outputChatBox(targetname.." existiert nicht oder ist offline.", source, 240, 0 , 0) -- Ausgabe einer Fehlermeldung
        return -- Abbruch der Funktion
    end
    
    -- ab hier ist gewährleistet, dass alle benötigten Daten vorhanden und gültig sind
    outputChatBox("PM an "..targetname..": "..message, source, 200, 60, 0) -- Ausgabe einer Nachricht an den Sender
    outputChatBox("PM von "..getPlayerNametagText(source)..": "..message, target, 240, 100, 0) -- Ausgabe einer Nachricht an den Empfänger
end

-- Hinzufügen des Commandhandlers
addCommandHandler("pm", cmdPrivateMessage, false, false)

Commandparameter werden immer als strings übergeben.