[WIP!!!] X3 Scripting-Tutorial
Moderators: Scripting / Modding Moderators, Moderatoren für Deutsches X-Forum
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
[WIP!!!] X3 Scripting-Tutorial
in Ermangelung der Tatsache, dass es aktuell kein halbwegs umfassendes Scripting-Tutorial gibt, habe ich mich entschlossen eins zu schreiben.
Das Tutorial richtet sich an Scriptanfänger und fortgeschrittene Scripter. Ich befürchte den Profis unter uns wird es nicht viel Neues bescheren.
Das Tutorial untergliedert sich in unterschiedliche Bereiche.
Ich habe versucht die Themen so zu gliedern, dass sie auf einander aufbauen.
Es ist selbstverständlich nicht notwendig das komplette Tutorial durchzuarbeiten, wenn nur Fragen oder Verständnisprobleme in einem bestimmten Bereich bestehen.
Das Tutorial kann ebenfalls als Ideengeber und Nachschlagewerk verstanden werden.
Ich habe versucht Basiswissen des Scriptens zu vermitteln, Zusammenhänge zu erläutern und alles durch praktische Beispiele abzurunden.
Der Spagat Neulinge nicht zu überfordern und Fortgeschrittenen nicht zu viel bereits bekanntes erneut zu erläutern ist mir vermutlich nicht überall gelungen. Ich bitte dies zu entschuldigen.
Ich hoffe dennoch, dass es mir halbwegs gelungen ist das Tutorial so zu gestalten, dass zumindest einige Scripter was davon haben.
IN BEARBEITUNG
Grundlagen
Einführung in das Scripten unter X3
Scriptvariablen, lokale Variablen und globale Variablen
Arrays
Menüs
Erstellung von Menüs
Sprach- und Logdateien
Sprachdatei (t-File)
Logdatei
Scriptaufrufe
Aufrufparameter und Rückgabewerte
Endlosscripte
Das Setup-Script
Versionierung und automatische Scriptupdates
Signale, Kommandos und Events
Sonstiges
Kommentare
Variablennamen
Hotkey
Ship Command
Beispielprojekt
Obwohl alle Erläuterung hier im Forum nachgelesen werden können, habe ich mich entschlossen das komplette Tutorial zusätzlich als PDF-Datei anzubieten.
PDF-Download (folgt bei Fertigstellung des Tutorials)
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
Das Scripten unter X3 entspricht in vielerlei Hinsicht dem Scripten mit anderen Scriptsprachen.
Wer eine vollwertige, moderne Programmiersprache wie JAVA oder C# erwartet wird sich enttäuscht sehen.
Die Scriptsprache von X3 ist vor vielen Jahren entwickelt worden und entspricht daher dem damaligen Entwicklungsstand solcher Sprachen.
Dies ist meines Erachtens nicht negativ zu bewerten, da die X3-Scriptsprache zum Zeitpunkt ihrer Entstehung durchaus "state of the art" war.
Junge Entwickler müssen sich darüber im Klaren sein, dass unter X3 einige Dinge "kompliziert" und "umständlich" umgesetzt werden müssen – verglichen mit z.B. JScript oder VBA.
An diesem Faktum lässt sich nichts ändern.
Wahl des Editors
Der in X3 angebotene Scripteditor (in der Regel SE genannt) eignet sich um schnell ein paar Zeilen Script zu schreiben.
Meine persönliche Abneigung gegen den SE außen vor gelassen, ist der SE bei genauer Betrachtung äußerst unkomfortabel und die Bedienung ist umständlich.
Meiner Erfahrung nach ist ein zügiges Entwickeln von Scripten hiermit kaum möglich.
Aus diesem Grund gibt es einen externen Editor – den Exscriptor. Ich persönlich verwende ausschließlich diesen Editor.
Der Exscriptor wird wie die meisten anderen Scripteditoren anderer Sprachen verwendet und bietet im Vergleich zum SE einigen Komfort.
Ich würde jedem, der viel und komfortabel scripten möchte, die Verwendung dieses Editors empfehlen.
All meine Beispiele wurden mit dem Exscriptor erstellt – daher fehlen auch die Zeilen-Nummerierungen.
Ein weiterer externen Editor ist X-Studio Script Editor von Mr.Bear.
Ich hab keinerlei Erfahrung mit diesem Editor, wollte ihn jeodch der Vollständigkeit halber angeben.
Alle Scripte müssen mit dem SE oder einem der externen X-Editoren bearbeitet werden, damit sie funktionsfähig sind.
Die Verwendung nativer Editoren ist nicht möglich, da die so erstellten/geänderten Scripte von X nicht korrekt geladen werden können!
.pck oder .xml?
Die Scriptsprache unterstützt die beiden Dateitypen .pck und .xml.
Für X macht es keinen Unterschied in welchem Dateiformat ihr eure Scripte schreibt.
Dennoch empfehle ich das XML-Format, da die Scriptdateien dann mit dem Browser oder einem anderen XML-Viewer/Editor betrachtet werden können.
Das .pck-Format lässt sich hingegen nur mit dem SE oder einem anderen externen X-Scripteditor öffnen.
Die Befehls-Bibliothek und das MSCI
IN BEARBEITUNG
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
Es gibt grundlegende Unterschiede bei den unterschiedlichen Variablendeklarationen.
Variablen, die ihr nur innerhalb des aktuellen Scripts verwendet, heißen Scriptvariablen. Die Deklaration und Verwendung wird nachfolgend erläutert.
Scriptvariablen sind nur innerhalb des jeweiligen Scripts existent und können von einem anderen Script nicht verwendet werden.
Diese Variablen werden nach Beendigung des Scripts „gelöscht“. Ein Zugriff von „außen“ ist nicht möglich.
Um eine Variable von unterschiedlichen Scripten aus verwenden und bearbeiten zu können muss sie entweder als „local variable“ oder als „global variable“ deklariert sein.
Die Erläuterungen hierzu erfolgen nach den Erklärungen der Scriptvariablen.
[ external image ]
Scriptvariablen
Ich gehe davon aus, dass jeder der ein Script schreiben möchte weiß, was eine Variable ist. Falls dies nicht der Fall ist kann das Wissen HIER ergänzt werden.
Alle Variablen in X beginnen grundsätzlich mit einem $ (Dollarzeichen). Ihr solltet bei der Benennung der Variablen immer darauf achten sprechende Namen zu finden.
Eine Variable $1234 oder $hahaha zu nennen erschwert euch selbst und anderen das Lesen eures Codes.
Wenn ihr z.B. ein neues Schiff erzeugt, dann nennt es z.B. $NewShip. Damit könnt ihr was anfangen und auch andere Scripter die euren Code lesen und verstehen wollen.
In X gibt es KEINE Typsicherheit! Die Deklaration von Variablen erfolgt ohne jegliche Typangaben!
Die Deklaration
Code: Select all
$Variable1 = 'Test'
Die Deklaration
Code: Select all
$Variable2 = 123
Die fehlende Typsicherheit führt dazu, dass der Scripter selbst dafür sorgen muss, dass bei Rechenoperationen auch rechenbare Werte in den Variablen stehen.
Die Deklaration
Code: Select all
$Variable3 = '123'
So sind auch alle Werte, die aus t-Files gelesen werden Strings!!!
Habt ihr in einem t-File einen Wert mit dem ihr rechnen wollt, so müsst ihr ihn immer zuerst zu einem rechenbaren Wert umwandeln.
Dies erfolgt mittels:
Code: Select all
<RetVar> = string <Var/String> to integer
Code: Select all
$Variable4 = '123'
$Variable4 = string $Variable4 to integer
Beispiel:
Code: Select all
$String1 = 'Das ist '
$String2 = 'ein langer String.'
$String3 = $String1 + $String2
Aus diesem Grund solltet ihr bei Typunsicherheit die Variable vorher immer in einen String wandeln.
Der Code:
Code: Select all
$Var1 = 4
$Var2 = 6
$Text1 = $Var1 + $Var2
Wollt ihr aus den beiden Variablen des Beispiels einen String zusammensetzen, so müsst ihr die Variablen vorher in einen String umwandeln.
Der Befehl hierzu lautet:
Code: Select all
<RetVar> = convert number <Var/Number> to string
Code: Select all
$Var1 = 4
$Var2 = 6
$Var1 = convert number $Var1 to string
$Var2 = convert number $Var2 to string
$Text1 = $Var1 + $Var2
Thema: Berechnungen
X kann nur Ganzzahlen! Es gibt kein Floats oder andere Nachkomma-Typen.
Bei Berechnungen wird alles nach dem Komma abgeschnitten!
Die Berechung
Code: Select all
$Result = 1 / 10
Die Zuweisung eines Kommawertes würde zu einem Fehler führen, weshalb es im Ego-SE auch nicht möglich ist einen solchen Wert einzugeben.
Code: Select all
$Float = 2,2
Der größte Wert einer Anzeigevariable ist 2 Milliarden.
Eine halbwegs schöne Sache gibt es jedoch. Werden Zahlenwerte zu Strings konvertiert, so werden die Tausenderpunkte automatisch gesetzt.
[ external image ]
Lokale Variablen und globale Variablen
Wenden wir uns nun den lokalen und globalen Variablen zu.
Lokale Variablen (lV)
Alle Variablen, die als „local variable“ deklariert sind benötigen ein Objekt, an das sie gebunden sind.
Objekte können Schiffe, Stationen, das Spielerschiff usw. sein. Die grundsätzliche Deklaration sieht folgendermaßen aus:
Code: Select all
<RefObj> -> set local variable: name=<Var/String> value=<Value>
Ihr habt ein Endlosscript geschrieben, welches auf allen Stationen läuft.
Ihr wollt die Version des Scripts auf jeder Station speichern, damit ihr bei einem Update die aktuelle laufende Version ermitteln könnt.
Hierzu speichert ihr die Version folgendermaßen ab:
Code: Select all
[THIS] -> set local variable: name=’lV.scriptxyz.version’ value=$Version
Jedes Script der Station kann die Variable folgendermaßen abfragen:
Code: Select all
$Version = [THIS] -> get local variable: name=’lV.scriptxyz.version’
Wird das Objekt zerstört verschwindet die lV selbstverständlich ebenfalls.
Globale Variablen (gV)
Alle Variablen, die als “global variable” deklariert sind, sind NICHT an ein Objekt gebunden.
Sie sind von allen Scripten überall abrufbar. Sie existieren einfach innerhalb des Spiels.
Die Deklaration sieht folgendermaßen aus:
Code: Select all
set global variable: name=<Var/String> value=<Value>
Ihr solltet euch stets darüber im Klaren sein, dass die gVs nicht einfach so „gelöscht“ werden. Solange sie einen Wert haben werden sie Speicher benötigen!
Aus diesem Grund ist es absolut wichtig, dass ihr nicht mehr benötigte gVs wieder freigebt, in dem ihr ihnen den Wert null zuweist.
Genullte gVs werden von der Engine irgendwann automatisch gelöscht – ohne euer Zutun.
Warum sollte man globale Variablen nutzen?
Ich möchte zwei praktische Beispiele meiner Scripte anführen:
Bei meinem Script SOM wird der aktuelle Auftrag der Bestellung mit allen wichtigen Daten in einer gV gespeichert.
Auf diese gV greifen während des Bestellprozesses 10 Scripte zu und verarbeiten bzw. erweitern diese Daten.
Wenn der Auftrag abgeschlossen ist, wird die gV wieder freigegeben (genullt).
In der SFDB ermittle ich zu Beginn EINMALIG alle möglichen Schiffe, Schiffstypen, Rassen usw.
Diese werden in Arrays als gV abgelegt. Die Ermittlung der Daten ist sehr aufwändig und dauert mehrere Sekunden in denen das System „steht“.
Um diese ganzen (relativ statischen Ermittlungen) nur einmalig durchführen zu müssen werden die Ergebnisse in einer gV gespeichert.
Es gibt einige sinnvolle Möglichkeiten für die Verwendung von gVs.
Nachteil der gVs/lVs
Der größte Vorteil der gVs und lVs ist gleichzeitig auch ihr größter Nachteil.
Sie können von allen möglichen Scripts aus verändert werden, was dazu führen kann, dass mehrere Scripte gleichzeitig auf die selbe Variable zugreifen und den Wert verändern.
Es gibt keinen automatischen Lock, der ein gleichzeitiges Ändern verhindert.
Um sicherzustellen, dass immer nur ein Script auf die Variable zugreift (um sie zu ändern) müsst ihr euch einen eigenen Lock-Mechanismus schreiben.
Hierzu habe ich euch ein kleines Beispiel gemacht:
Code: Select all
* ********************************************************************************************************
* ********************************************************************************************************
* ********************************************************************************************************
* Diese Funktion löscht den ersten Eintrag der Feindesliste.
fct.Delete.Enemy:
* Die lV wird gelockt:
gosub fct.Lock.lV.EnemyShips
* Der erste Eintrag wird entfernt:
remove element from array $anEnemyList at index 0
* Die lV wird zurückgeschrieben:
[THIS]->set local variable: name='lV.enemy.ships' value=$anEnemyList
* Der lock wird wieder entfernt:
[THIS]->set local variable: name='plugin.nw.enemyships.opened' value=[FALSE]
endsub
* ********************************************************************************************************
* ********************************************************************************************************
* ********************************************************************************************************
* Diese Funktion locked die lV der Feindschiffe:
fct.Lock.lV.EnemyShips:
* Da meherere Scripte schreibend auf diese lV zugreifen, muss der jeweilige Schreiber diese vorher locken:
* Sprungmarke:
Jump.Edit.lV:
$tmplVOpen = [THIS]->get local variable: name='plugin.nw.enemyships.opened'
if $tmplVOpen
* In diesem Fall ist die lV gerade in Bearbeitung und es wird 2 Sekunden gewartet
= wait 2000 ms
goto label Jump.Edit.lV
else
* In diesem Fall wird die lV als geöffnet markiert:
[THIS]->set local variable: name='plugin.nw.enemyships.opened' value=[TRUE]
end
endsub
* ********************************************************************************************************
* ********************************************************************************************************
* ********************************************************************************************************
Die erste Funktion löscht einen Eintrag in einem Array, welches in einer lV liegt. Hierzu muss die "Bearbeitung" in einer anderen Variablen "markiert" werden. Ein so genanntes Flag.
Ich hoffe der Code erklärt sich von selbst.
Anmerkung zum Namen
Ich möchte aus gegebenem Anlass noch auf die korrekte Verwendung des Attributs
Code: Select all
... name=<Var/String> ...
Code: Select all
... name='Meine.gV1' ....
Code: Select all
$gVName = 'Meine.gV1'
... name=$gVName ....
Beim SOM werden die Schiffe pro Rasse/Klasse-Kombination in einem Array abgelegt.
Die gV der Split M2 heißt demnach "plugin.ship.order.manager.Split.M2.all".
Im Bespellprozess wird die entsprechende gV - je nach Usereingabe - geladen, um die kaufbaren Schiffe anzuzeigen:
Code: Select all
$tmpgVName = 'plugin.ship.order.manager.' + $anRace + '.' + $anType + '.all'
$tmpShipsAll = get global variable: name=$tmpgVName
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
Arrays werden leider immer wieder falsch verstanden – manchmal sogar überhaupt nicht. Leider sind Arrays nicht ganz so einfach zu erklären wie eine Integer-Variable.
Ich möchte versuchen die Erklärung so einfach wie möglich zu gestalten.
Der grundsätzliche Unterschied zwischen einem Array und einer Variable besteht darin, dass es sich bei einem Array um eine Struktur handelt!
Diese Struktur enthält Variablen, die die eigentlichen Werte enthalten.
Technisch gesehen enthält das Array Zeiger (Pointer) auf die eigentlichen Variablen. Das Array-Objekt existiert hierbei getrennt vom eigentlichen Script.
Ich möchte es an dieser Stelle nicht zu technisch werden lassen. Wer es noch genauer wissen möchte kann sein Wissen über Arrays gerne HIER oder das Wissen über Pointerarithmetik HIER vertiefen.
Es gibt verschiedene Arten von Arrays. Es gibt einfache Arrays, zweidimensionale und multidimensionale Arrays. Aber der Reihenfolge nach!
Alle Arraybefehle befinden sich im Bereich "General Commands" -> "Arrays" der Befehls-Bibliothek.
Link zum MSCI
Das einfache Array
Das einfache Array kann mit einer einfachen Liste verglichen werden. Einem Einkaufszettel zum Beispiel – auf dem untereinander eine beliebige Anzahl von Waren stehen.
Um ein Array anzulegen lautet der Befehl:
Code: Select all
<RetVar> = array alloc: size=<Var/Number>
Ihr wisst, dass es 4 Werte sind die ihr speichern wollt und legt die Größe vorher fest.
Code: Select all
* Werte ermitteln:
$tmpName = [PLAYERSHIP]->get name
$tmpSpeed = [PLAYERSHIP]->get current speed
$tmpShield = [PLAYERSHIP]->get current shield strength
$tmpHull = [PLAYERSHIP]->get hull
* Neues Array erzeugen:
$tmpNewArray = array alloc:size=4
* Werte speichern:
$tmpNewArray[0] = $tmpName
$tmpNewArray[1] = $tmpSpeed
$tmpNewArray[2] = $tmpShield
$tmpNewArray[3] = $tmpHull
Die Größe vorher festzulegen ist in dem meisten Fällen nicht sinnvoll und verursacht nur unnötigen Aufwand. Ihr könnt ein Array jederzeit mit einer Größe von 0 anlegen und die einzelnen Einträge einfach hinzufügen. Der Befehl hierzu lautet:
Code: Select all
append <Value> to array <Var/Array>
Code: Select all
* Neues Array erzeugen:
$tmpNewArray = array alloc:size=0
* Werte speichern:
append $tmpName to array $tmpNewArray
append $tmpSpeed to array $tmpNewArray
append $tmpShield to array $tmpNewArray
append $tmpHull to array $tmpNewArray
Hierzu ein einfaches Codebeispiel. Nachfolgender Code ermittelt alle Sektoren des Universums und speichert diese in einem einfachen Array ab:
Code: Select all
* Ergebnisarray der Sektoren:
$tmpAllSectors = array alloc: size=0
* Maximale Ausdehnung des Unversums:
$tmpMaxSectorsX = get max sectors in x direction
$tmpMaxSectorsY = get max sectors in y direction
* Startsektoren:
$tmpSectorX = 0
$tmpSectorY = 0
while $tmpSectorX < $tmpMaxSectorsX
while $tmpSectorY < $tmpMaxSectorsY
* Einzelner Sektor:
$tmpSector = get sector from universe index: x=$tmpSectorX, y=$tmpSectorY
if $tmpSector != null
* In diesem Fall existiert der Sektor und er wird dem Array hinzugefügt:
append $tmpSector to array $tmpAllSectors
end
inc $tmpSectorY =
end
* Zurücksetzen des Y-Wertes für die nächste Reihe
$tmpSectorY = 0
inc $tmpSectorX =
end
Es gibt noch eine weitere Möglichkeit ein Array zu erzeugen.
Wenn ihr nur ein „kleines“ Array mit maximal 5 Werten benötigt, so könnt ihr dies ebenfalls über folgenden Befehl tun:
Code: Select all
<RetVar> = create new array, arguments=<Value>, <Value>, <Value>, <Value>, <Value>
Code: Select all
* Neues Array erzeugen und Werte speichern:
$tmpNewArray = create new array, arguments=$tmpName, $tmpSpeed, $tmpShield, $tmpHull, null
Achtet jedoch darauf, dass der create immer 5 Argumente haben muss!
Habt ihr weniger als 5 Werte die ihr initialisieren wollt, so müsst ihr die "leeren" Werte mit null angeben.
Das zweidimensionale Array
Kommen wir nun zum zweidimensionalen Array. Diese Art des Arrays könnt ihr am einfachsten mit einer Tabelle vergleichen. Es gibt so zu sagen Zeilen und Spalten. Technisch gesehen besteht der einzelne Eintrag des Arrays aus einem Array. Klingt vielleicht verwirrend – ist es aber nicht.
Hier ein Beispiel dazu:
Ihr habt 2 Freunde und wollt von jedem Freund den Vor- und Nachnamen sowie das Geburtsdatum speichern. Der Code hierzu sähe folgendermaßen aus:
Code: Select all
*Hauparray erzeugen:
$tmpMainArray = array alloc: size=2
* Erster Freund:
$tmpFirstName = 'Fritz'
$tmpLastName = 'Nachname 1'
$tmpBirthday = '12.12.2012'
* Daten in ein Array verpacken:
$tmpFriend = create new array, arguments= $tmpFirstName, $tmpLastName, $tmpBirthday, null, null
* 1. Freund dem Hauptarray hinzufügen:
$tmpMainArray[0] = $tmpFriend
* Zweiter Freund:
$tmpFirstName = 'Hugo'
$tmpLastName = 'Nachname 2'
$tmpBirthday = '10.10.2010'
* Daten in ein Array verpacken:
$tmpFriend = create new array, arguments= $tmpFirstName, $tmpLastName, $tmpBirthday, null, null
* 2. Freund dem Hauptarray hinzufügen:
$tmpMainArray[1] = $tmpFriend
Code: Select all
$tmpLastNameFirstFriend = $tmpMainArray[0][1]
Code: Select all
$tmpBithdaySecondFriend = $tmpMainArray[1][2]
Da die Sektoren in X nicht eindeutig über den Namen ermittelt werden können (es gibt mehrere „Unbekannter Sektor“) erfolgt die Identifizierung der Sektoren über die Position in der Galaxie – sprich die X und Y Werte.
Um nun mit unserer schönen Sektorenliste etwas anfangen zu können erweitern wir das obige Script und speichern neben dem Namen noch die X- und Y-Position des jeweiligen Sektors.
Der neue Mittelteil des Codes hierzu sähe folgendermaßen aus:
Code: Select all
if $tmpSector != null
* In diesem Fall existiert der Sektor:
$tmpNewEntry = create new array, arguments=$tmpSector, $tmpSectorX, $tmpSectorY, null, null
append $tmpNewEntry to array $tmpAllSectors
end
Ihr könntet euch ganz einfach ein Logfile erzeugen und alle bekannten Sektoren ausgeben. Das Ganze sähe folgendermaßen aus:
Code: Select all
* Größe des Sektorarrays:
$tmpAllSectorsCount = size of array $tmpAllSectors
* Ausgabe im Logfile:
$tmpText = 'Es sind insgesamt ' + $tmpAllSectorsCount + ' Sektoren vorhanden. Diese sind:'
write to log file 8888 append=[FALSE] value=$tmpText
* Werte aller Sektoren:
$tmpCounter = 0
while $tmpCounter < $tmpAllSectorsCount
* Einzelne Sektorwerte:
$tmpSector = $tmpAllSectors[$tmpCounter][0]
$tmpX = $tmpAllSectors[$tmpCounter][1]
$tmpY = $tmpAllSectors[$tmpCounter][2]
* String für die Ausgabe im Logfile erzeugen:
$tmpText = $tmpCounter + '. Sektor: ' + $tmpSector + ' (X = ' + $tmpX + ' / Y = ' + $tmpY + ')'
write to log file 8888 append=[TRUE] value=$tmpText
* Nächster Eintrag:
inc $tmpCounter =
end
Das multidimensionale Array
Um das Ganze auf die Spitze treiben zu können gibt es so genannte multidimensionale Arrays.
Hierbei sind vereinfacht gesagt "unendlich" viele Arrays in Arrays "geschachtelt". Es handelt sich dabei um eine Fortführung der zweidimensionalen Arrays.
Der Wert eines Arrays im Array ist hierbei wieder ein Array. Und so weiter und so weiter.
Man nennt so was auch eine Matrix. Ich denke jeder der die Ausführungen zu den Arrays bis hierher verstanden hat kann sich das Prinzip vorstellen.
Ich möchte an dieser Stelle von einem Beispiel absehen, da es meiner Meinung nach nichts bringt.
Wer das Grundprinzip nicht verstanden hat wird auch mit einem Beispiel zur Matrix nicht viel anfangen können.
Array[x][y] = Array[y][x]
Ein gerne gemachter Anfängerfehler ist der Versuch den Wert eines Arrays einem anderen Array zuzuweisen. Beispiel:
Code: Select all
$myArray1[0][0] = $myArray2[1][2]
Wollt ihr Werte eines Arrays einem andern Array zuweisen, so müsst ihr den Umweg über eine Variable wählen.
Um das gewünschte Ergebnis zu erzielen wäre der Weg:
Code: Select all
$ZwischenVariable = $myArray2[1][2]
$myArray1[0][0] = $ZwischenVariable
Fallstrick Array
Wie bereits in der Einleitung erwähnt ist ein Array eine Struktur mit Pointern. Pointer folgen eigenen, ganz speziellen Regeln.
Ein Pointer „zeigt“ lediglich auf einen Wert, ohne diesen selbst zu „besitzen“.
Ich möchte ein - vielleicht blödes – aber hoffentlich anschauliches Beispiel zur Erläuterung geben.
Stellen wir uns das Register eines Katasteramts als Array vor. Dem Katasteramt sind alle Häuser einer Stadt bekannt. Es weiß wo genau sich welches Haus befindet.
Nun sind die Einträge im Register des Amtes vergleichbar mit Pointern, da sie auf das Objekt (Haus) verweisen.
Die Häuser sind in diesem Beispiel unsere Variablen.
Die Eigentümer bzw. Mieter der Häuser sind vergleichbar mit dem Inhalt der Variablen.
Ändert sich nun der Eigentümer oder Mieter des Hauses, so ist dies dem Katasteramt egal. Das Haus bleibt das Haus und steht nach wie vor an derselben Stelle.
Der Eintrag im Register wird sich deshalb nicht ändern.
Das Katasteramt besitzt auch die Häuser nicht, sondern verwaltet lediglich die Standorte.
IN BEARBEITUNG
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
Die Erstellung von Menüs ist eine der einfachsten Aufgaben beim Scripten in X.
Ich möchte das Ganze anhand von zwei Beispielmenüs erklären.
Grundsätzlich ist es bei allen Menüs wichtig, dass alle Texte in einem t-File abgelegt sind, damit sie einfach zu internationalisieren sind.
Änderungen an Texten, die mehrfach verwendet werden, sind hierdurch ebenfalls deutlich schneller gemacht.
Grundsätzliches zu den t-Files kann dem entsprechenden Themenblock dieses Tutorials entnommen werden.
Es ist sehr empfehlenswert sich vor Beginn der Arbeiten an einem Menü Gedanken über den Aufbau zu machen.
Meiner Erfahrung nach eignet sich eine Skizze ziemlich gut dazu. Einfach ins „Blaue hinein“ ein Menü zu erstellen wird in der Regel viel mehr Arbeit bereiten, als sich im Vorfeld Gedanken darüber zu machen.
Alle Menübefehle befinden sich im Bereich "Others" der Befehls-Bibliothek.
Link zum MSCI
[ external image ]
Beispielmenü 1
Entgegen meiner Einführungsempfehlung möchte ich der Einfachheit halber im ersten Schritt der Erklärung die jeweiligen Texte statisch codieren und nicht aus einem t-File lesen.
Das erste Beispielmenü soll folgendermaßen aussehen:
[ external image ]
Ein Menü ist ein Array, welches die einzelnen Menüelemente beinhaltet.
Es ist der "Container", der alles enthält, was angezeigt werden soll.
Um das Menü-Array zu erzeugen wird folgender Befehl verwendet:
Code: Select all
<RetVar> = create custom menu array
Code: Select all
* Menü-Array erzeugen:
$Mainmenu.Menu = create custom menu array
Der Befehl hierzu lautet:
Code: Select all
add custom menu heading to array <Value>: title=<Var/String>
Code: Select all
add custom menu heading to array $Mainmenu.Menu: title='Schiffsdaten'
Code: Select all
add custom menu item to array <Value>: text=<Var/String> returnvalue=<Value>
Da wir unser Menü zu diesem Zeitpunkt mit keiner Funktion versehen geben wird beim Rückgabewert null an.
Unsere ganzen Menüeinträge werden also folgendermaßen erzeugt:
Code: Select all
add custom menu item to array $Mainmenu.Menu: text='Schiff:' returnvalue=null
add custom menu item to array $Mainmenu.Menu: text='Besitzer:' returnvalue=null
add custom menu item to array $Mainmenu.Menu: text='Klasse:' returnvalue=null
add custom menu item to array $Mainmenu.Menu: text='Schildstärke:' returnvalue=null
add custom menu item to array $Mainmenu.Menu: text='Hülle:' returnvalue=null
Dieser ist rein optischer Natur und dient der besseren Strukturierung.
Ein "Trenner" wird mit folgendem Befehl erzeugt:
Code: Select all
add section to custom menu: <Var/Array>
Die Überschrift des Menüs, sowie der Beschreibungstext werden beim Anzeigen des Menüs angegeben.
Um das komplette Menü anzuzeigen wird folgender Befehl verwendet:
Code: Select all
<RetVar/IF> open custom menu: title=<Var/String> description=<Var/String> option array=<Var/Array>
Code: Select all
* Menü-Array erzeugen:
$Mainmenu.Menu = create custom menu array
* Erster Bereich:
add custom menu heading to array $Mainmenu.Menu: title='Schiffsdaten'
* Einträge:
add custom menu item to array $Mainmenu.Menu: text='Schiff:' returnvalue=null
add custom menu item to array $Mainmenu.Menu: text='Besitzer:' returnvalue=null
add custom menu item to array $Mainmenu.Menu: text='Klasse:' returnvalue=null
* Trenner:
add section to custom menu: $Mainmenu.Menu
add custom menu item to array $Mainmenu.Menu: text='Schildstärke:' returnvalue=null
add custom menu item to array $Mainmenu.Menu: text='Hülle:' returnvalue=null
* Zweiter Bereich:
add custom menu heading to array $Mainmenu.Menu: title='Zurück/Abbruch'
* Eintrag:
add custom menu item to array $Mainmenu.Menu: text='Script Tutorial Menü beenden' returnvalue=null
* Menü anzeigen:
$anMenu = open custom menu: title='Script Tutorial' description='Script Tutorial' option array=$Mainmenu.Menu
Ein nachträgliches Einfügen von Menüeinträgen irgendwo in der Mitte ist nicht möglich.
Ebenso ist es nicht möglich einen bereits hinzugefügten Eintrag wieder zu entfernen.
Ich denke die Grundlagen des Menüaufbaus sollten an dieser Stelle jetzt klar sein.
Leider kann unser bisheriges Menü rein gar nichts und die Texte sind statisch codiert.
Alles in allem ist das Menü bis hierher zwar funktionsfähig aber leider sehr unprofessionell gelöst.
Um dies zu ändern möchte ich das aktuelle Menü um folgende Punkte erweitern:
1. Die Texte sollen aus einem t-File gelesen werden.
2. Die Daten des Schiffs, das der Spieler im Ziel hat, sollen angezeigt werden.
Das gewünschte Ergebnismenü soll nach den Änderungen folgendermaßen aussehen:
[ external image ]
Hierzu habe ich ein rudimentäres t-File mit der ID 8600 angelegt, welches folgende Daten beinhaltet:
Code: Select all
<?xml version="1.0" encoding="UTF-8"?>
<language id="49">
<page id="8600" title="ScriptTutorial" descr="ScriptTutorial">
<t id="1">Script Tutorial</t>
<t id="100">Schiffsdaten</t>
<t id="101">Schiff:</t>
<t id="102">Besitzer:</t>
<t id="103">Klasse:</t>
<t id="104">Schildstärke:</t>
<t id="105">Hülle:</t>
<t id="1000">Zurück/Abbruch</t>
<t id="1001">Script Tutorial Menü beenden</t>
<t id="2000">1.0.00</t>
</page>
</language>
Code: Select all
* Ermitteln der Daten:
* --------------------
* Ziel des Spielers:
$tmpTarget = get player tracking aim
* Name des Schiffs:
$tmpName = $tmpTarget -> get name
* Klasse des Schiffs:
$tmpClass = $tmpTarget -> get object class
* Besitzer:
$tmpOwner = $tmpTarget -> get true owner
* aktueller Schildwert:
$tmpShield = $tmpTarget -> get current shield strength
* Zahlenwert in einen String convertieren um die Tausenderpunkte zu setzen:
$tmpShield = convert number $tmpShield to string
* aktueller Hüllenwert:
$tmpHull = $tmpTarget -> get hull
$tmpHull = convert number $tmpHull to string
Um das angelegte t-File im Script verwenden zu können muss es folgendermaßen initialisiert werden:
Code: Select all
* Lesen des t-Files:
* ------------------
$PageID = 8600
load text: id=$PageID
Im Gegensatz zur ersten Version des Menüs besteht im neuen Menü jeder Menüeintrag aus 2 Teilen.
Links steht der beschreibende Text aus dem t-File, rechts der eigentliche Wert, der im Vorfeld ermittelt wurde.
Um zwei Werte in einem Menüeintrag zu verbinden gibt es folgenden Befehl:
Code: Select all
<RetVar> = create text for custom menu, left=<Var/String>, right=<Var/String>
Beginnen wir wieder mit dem ersten Block.
Die Überschrift "Schiffsdaten" wird aus dem t-File gelesen und dem heading als title übergeben. Danach erfolgt die Zuweisung zum Menü-Array:
Code: Select all
$Menutext = read text: page=$PageID id=100
add custom menu heading to array $Mainmenu.Menu: title=$Menutext
Code: Select all
* Text 101: Schiff:
$Text = read text: page=$PageID id=101
$Menutext = create text for custom menu, left=$Text, right=$tmpName
add custom menu item to array $Mainmenu.Menu: text=$Menutext returnvalue=null
Code: Select all
* Menüaufbau:
* ------------
* Menü-Array erzeugen:
$Mainmenu.Menu = create custom menu array
* Neuer Bereich - "Schiffsdaten":
* -------------------------------
$Menutext = read text: page=$PageID id=100
add custom menu heading to array $Mainmenu.Menu: title=$Menutext
* Text 101: Schiff:
$Text = read text: page=$PageID id=101
$Menutext = create text for custom menu, left=$Text, right=$tmpName
add custom menu item to array $Mainmenu.Menu: text=$Menutext returnvalue=null
* Text 102: Besitzer:
$Text = read text: page=$PageID id=102
$Menutext = create text for custom menu, left=$Text, right=$tmpOwner
add custom menu item to array $Mainmenu.Menu: text=$Menutext returnvalue=null
* Text 103: Klasse:
$Text = read text: page=$PageID id=103
$Menutext = create text for custom menu, left=$Text, right=$tmpClass
add custom menu item to array $Mainmenu.Menu: text=$Menutext returnvalue=null
* Trenner:
add section to custom menu: $Mainmenu.Menu
* Text 104: Schildstärke:
$Text = read text: page=$PageID id=104
$Menutext = create text for custom menu, left=$Text, right=$tmpShield
add custom menu item to array $Mainmenu.Menu: text=$Menutext returnvalue=null
* Text 105: Hülle:
$Text = read text: page=$PageID id=105
$Menutext = create text for custom menu, left=$Text, right=$tmpHull
add custom menu item to array $Mainmenu.Menu: text=$Menutext returnvalue=null
* Neuer Bereich - "Zurück/Abbruch":
* ---------------------------------
* Text 1000: Zurück/Abbruch
$Menutext = read text: page=$PageID id=1000
add custom menu heading to array $Mainmenu.Menu: title=$Menutext
* Text 1001: Script Tutorial Menü beenden
$Menutext = read text: page=$PageID id=1001
add custom menu item to array $Mainmenu.Menu: text=$Menutext returnvalue='endall'
* Beschreibungstext:
* ------------------
$tmpDescription = read text: page=$PageID id=1
* Titel:
* ------
$tmpTitle = read text: page=$PageID id=1
* Anzeige des Menüs:
* ------------------
$anMenu = open custom menu: title=$tmpTitle description=$tmpDescription option array=$Mainmenu.Menu
Einen Punkt gäbe es noch zu erledigen, der allerdings nichts mit dem Menü an sich zu tun hat.
Da das Script nur dann ausgeführt werden soll, wenn der Spieler ein Schiff im Ziel hat muss zu Beginn des Scripts eine Abfrage erfolgen, ob ein Schiff gewählt ist.
Ist dies nicht der Fall wird das Script abgebrochen.
Die Überprüfung hierzu sieht folgendermaßen aus:
Code: Select all
* Ermitteln der Daten:
* --------------------
* Ziel des Spielers:
$tmpTarget = get player tracking aim
* Überprüfung, ob der Spieler ein Schiff selektiert hat:
if not $tmpTarget -> is of class {Schiff}
* In diesem Fall wurde kein Schiff selektiert und das Script wird beendet:
return null
end
[ external image ]
Refresh/Reload von Menüs
Entgegen der Architektur moderner Programmiersysteme ist es mittels eines Scripts nicht möglich einen automatischen refresh/reload eines angezeigten Menüs zu erzwingen.
Daher ist ein "automatisches" Reagieren auf Userinteraktionen nicht möglich!
"Dynamische" Menüs müssen aufwändig auscodiert werden. Es gibt keine Events oder Methoden, die ein automatisches Neuladen der Menüs ermöglichen.
Auf jede einzelne Usereingabe muss entsprechend reagiert werden und Änderungen des Menüs, auf Grund von Usereingaben, erzwingen ein erneutes Erzeugen des kompletten Menüs!
Dieser Umstand ist zwar nicht wirklich schlimm, aber er erfordert leider teilweise einen deutlichen Mehraufwand an Programmierlogik.
[ external image ]
Beispielmenü 2
Nachdem nun die ersten Grundlagen der Menüs erklärt wurden möchte ich einige "fortgeschrittene" Techniken der Menüs erläutern.
Hierzu zählen die "value selections", die Verarbeitung von Rückgabewerten und das Erzeugen von Usereingaben.
IN BEARBEITUNG
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
[ external image ]
Sprachdatei (t-File)
Beschreibung folgt.
[ external image ]
Logdatei
Beschreibung folgt.
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
In X gibt es unterschiedliche Möglichkeiten Scripte aufzurufen (zu starten).
IN BEARBEITUNG
Alle Scriptaufruf-Befehle befinden sich im Bereich "General Commands" -> "Script Calls" der Befehls-Bibliothek.
Link zum MSCI
[ external image ]
Aufrufparameter und Rückgabewerte
Beschreibung folgt.
[ external image ]
Endlosscripte
Ein besonderes Augenmerk möchte ich auf Endlosscripte lenken.
In der Regel werden Scripte so programmiert, dass sie etwas tun und sich dann beenden.
Es gibt allerdings auch Situationen, in denen ein Script endlos laufen soll.
Diese Scripte können sowohl globale, als auch lokale Scripte sein.
Beispiel eines endlos laufenden Scripts auf einer Station wäre z.B. das Auffüllen der "Waren" in Schiffswerften.
In diesem Script wird endlos alle x Sekunden geprüft, ob noch kaufbare Schiffe in der Werft vorhanden sind.
Ist dies nicht der Fall, wird die Ware aufgefüllt.
Endlos laufende Scripte sollten immer einen "Kill"-Schalter haben. Vor allem global laufende Scripte.
Global laufende Endlosscripte können ohne einen solchen Schalter nie wieder beendet werden, da nicht ermittelt werden kann "wo" sie laufen.
Bei Start eines globalen Endlosscripts wird dieses "irgendwo" ausgeführt.
Es gibt keine ermittelbare Task-ID und auch sonst keinen Weg an das Script ranzukommen!
Meiner Meinung nach ist der einfachste Weg einen "Kill"-Schalter einzubauen beim Scriptstart eine gV mit der Spielzeit anzulegen.
Ich möchte das Ganze an einem Beispiel verdeutlichen:
Code: Select all
* ********************************************************************************************************
* ********************************************************************************************************
* ********************************************************************************************************
* Da es sich um ein Endlosscript handelt, wird beim ersten Start die Spielzeit in einer gV
* gespeichert. Bei jedem Durchlauf wird geprüft, ob die gV noch identisch ist. Wurde das Script
* erneut gestartet stimmen die Werte nicht überein und das Script beendet sich automatisch selbst.
* Ebenso, wenn von außen der Wert null gesetzt wird.
$anStartTime = playing time
set global variable: name='gV.aos.defence.global.starttime' value=$anStartTime
Code: Select all
Loop:
$tmpStartTimegV = get global variable: name='gV.aos.defence.global.starttime'
if $tmpStartTimegV != $anStartTime
* Wenn die Daten nicht übereistimmen wird das Script beendet:
goto label End
end
Wird das Script neu (erneut) gestartet, wird die neue Version eine neue Startzeit eintragen.
Eine laufende, ältere Version des Scripts wird beim nächsten Durchlauf feststellen, dass die Zeiten nicht mehr übereinstimmen und sich selbst beenden.
Soll das Script komplett beendet werden muss lediglich die gV (von "außen") auf null gesetzt werden und das Script beendet sich beim nächsten Durchlauf selbst.
Analog funktioniert diese Art des "Kill"-Schalters auch bei lokalen Endlosscripten.
Hierbei wird die Startzeit einfach als lokale Variable auf dem jeweiligen Objekt gespeichert.
Code: Select all
$anStartTime = playing time
[THIS] -> set local variable: name='lV.xyz-script.starttime' value=$anStartTime
[ external image ]
Das Setup-Scrtipt
Beschreibung folgt.
[ external image ]
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
Dieser Bereich des Tutorials umfasst all jene Dinge, die nicht eindeutig thematisch zugeordnet werden können.
[ external image ]
Kommentare
Einige werden sich vermutlich fragen, warum ich den Kommentaren einen eigenen Abschnitt spendiere.
Auf Grund meiner längjärigen Erfahrung als Programmierer möchte ich deutlich auf die Wichtigkeit von Kommentaren hinweisen!!
Je mehr ihr kommentiert, desto einfacher wird es für Andere euren Code zu verstehen.
Kommentare sind ebenfalls für euch selbst sehr wichtig. Die Art und Weise wie man programmiert ändert sich im Laufe der Zeit bzw. entwickelt sich.
Wenn ihr euren eigenen Code Monate oder Jahre später lest, werdet ihr euch öfters fragen "warum habe ich das eigentlich so gemacht?" oder "was genau passiert hier?".
Um euch die erneute "Einarbeitung" in euren eigenen Code zu vereinfachen sind möglichst viele sinnvolle Kommentare zu machen.
Wenn ich nun den SOM als Beispiel nehme, so hat dieser über 20.000 Zeilen Code. Niemand kann sich das alles merken. Und das ist nur eines von vielen meiner Projekte.
Ich hab ein den vergangenen 25 Jahren sicherlich mehrere Millionen Zeilen Sourcecode in verschiedenen Sprachen geschrieben.
Ohne Kommentare wäre ich bei meinen eigenen Projekten also aufgeschmissen. Und nun stellt euch vor ein anderer Scripter wollte versuchen euren Code zu verstehen. Ohne Kommentare -> no chance!
[ external image ]
Hotkey
Beschreibung folgt.
[ external image ]
Ship Command
Beschreibung folgt.
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
-
- Posts: 660
- Joined: Sat, 3. Jan 09, 22:57
-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
nachdem ich gestern die ersten Inhalte eingefügt habe, hätte ich gerne ein kleines Feedback.
Geht das Tutorial in die richtige Richtung? Sind die Erklärungen und Beispiele ausreichend? Ist es zu oberflächlich, oder zu detailliert? Ich kann das sehr schwer einschätzen und der Spagat den Neuling nicht zu erschlagen und für den Fortgeschrittenen nicht zu langatmig zu sein ist relativ schwierig.
Die Beschreibungen und Erklärungen sind noch nicht vollständig und an der einen oder anderen Stelle noch nicht 100%ig rund, da es erst der erste Wurf ist. Es wird also noch dran gefeilt

Der Startpost ist selbstverständlich noch weit entfernt von seiner Fertigstellung

Es wäre trotzdem super wenn ein paar Leute mal kurz drüberfliegen könnten um mir ein grundsätzliches Feedback zu geben, ob es in die richtige Richtung geht.
Danke
Reflexer
-
- Posts: 66
- Joined: Fri, 9. Dec 11, 15:43
also für mich ist das ganze durchaus nachvollziehbar und verständlich - genau richtig würde ich sagen und mit scripten hab ich selbst ja nicht wirklich was am hut. aber jeder der den script editor schon mal aktiviert hat und sich ein paar der scripte angeschaut hat, wird damit was anfangen können.
vielen dank für deinen einsatz!
grüße
south
-
- Posts: 5366
- Joined: Sun, 3. Sep 06, 18:19
X-Tended TC Mod Team Veteran.
Modeller of X3AP Split Acinonyx, Split Drake, Argon Lotan, Teladi Tern. My current work:

-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
ich hatte es aber bereits auf der todo-liste
-
- Posts: 5366
- Joined: Sun, 3. Sep 06, 18:19
X-Tended TC Mod Team Veteran.
Modeller of X3AP Split Acinonyx, Split Drake, Argon Lotan, Teladi Tern. My current work:

-
- Moderator (Script&Mod)
- Posts: 14571
- Joined: Sun, 20. Nov 05, 22:45
Code: Select all
$Array2D=array alloc: size=0
$tmpArray = create new array, arguments='00', '01', '02', '03', '04'
append $tmpArray to array $Array2D
$tmpArray[0] = '10'
$tmpArray[0] = '11'
$tmpArray[0] = '12'
$tmpArray[0] = '13'
$tmpArray[0] = '14'
$WasKommtHierRaus = $Array2D[0][1]
ARRAY(
ARRAY( '10' , '11' , '12' , '13' , '14' ) ,
ARRAY( '10' , '11' , '12' , '13' , '14' )
)
um genau zu sein handelt es sich beim 2. und 3. ARRAY um DASSELBE ARRAY, sprich wenn man
$ARRAY2D[1][2] = '44'
ausführt bekomt man jetzt sowohl für $ARRAY2D[0][2] als auch [1][2]44 raus. korrekt funktionieren würde der Code so:
$Array2D=array alloc: size=0
$tmpArray = create new array, arguments='00', '01', '02', '03', '04'
append $tmpArray to array $Array2D
$tmpArray=array alloc: size=5
$tmpArray[0] = '10'
$tmpArray[0] = '11'
$tmpArray[0] = '12'
$tmpArray[0] = '13'
$tmpArray[0] = '14'
$WasKommtHierRaus = $Array2D[0][1]
*nun wie erwartet '01'

zum Thema Menüs: man kann meines Wissens Menüs durchaus dynamisch machen - es sind schließlich einfach nur Arrays und Arrays sind Pointer (s.o.)



-
- Posts: 863
- Joined: Sat, 21. Feb 09, 12:54
*hust*UniTrader wrote:zum Teil über Arrays solltest du evtl. die Eigenheit von Pointern etwas besser hervorheben, z.B. anhand eines kleinen Code-Beispiels, das nicht funktioniert wie erwartet, denn sowas kommt ziemlich häufig als fehler vor
lasst mich doch erstmal meinen ersten Wurf des Tutorials fertig machen



trotzdem danke, dass du dir die mühe gemacht hast - zumindest liest es überhaupt jemand

beim punkt der menüs möchte ich dir widersprechen. einen work-around für alles mögliche zu finden geht meistens... meine aussage war, dass der refresh des menüs nicht wie bei modernen programmiersprachen funktioniert... und ich denke dem wird wohl jeder zustimmen....
ob ich noch einen bereich "best practises" aufnehme, in den sowas reinkommt, weiß ich noch nicht.... mal sehen