Skip to end of metadata
Go to start of metadata

Autor X-Freak Cartman Datum 15.08.2007, 14:53 Aufrufe 3262
Beschreibung Prioritäten, Tasks, Interrupts, globale Scripts
Kategorie X3R MSCI Befehlsreferenz Typ

Prioritäten, Signals und Tasks 

 

Prioritäten


Eine Priorität ist in MSCI-Scripts eine Zahl von -2.147.483.647 bis 2.147.483.647, die jedem laufenden Script zugewiesen wird, und die regelt, welche Signals und Interrupts auf das Script wirken.
Logischerweise gilt für Prioritäten:
Je höher die Priorität, desto wichtiger ist der Script. Unterbricht man ein Script, so wird der unterbrechende Script nur gestartet, wenn seine Priorität größer als die des laufenden Scripts ist.
Deshalb sollte man die Priorität nicht zu hoch setzen, damit alle Signals aktiv sind und es keine Komplikationen mit anderen Scripts gibt, die die Aktivität eines Schiffes unterbrechen. Normalerweise benutzt man die Priorität 0. Negative Prioritäten sind nicht üblich, aber auch möglich. Generell sollte man sich gut überlegen, ob man die Priorität größer als 199 setzt, da hier nicht registriert wird, ob ein neuer Leader vorliegt. Auf keinen Fall sollte man jedoch bei NPCs über 299 gehen (Fehler bei Kaperungen) oder generell bei allen Schiffen über 9.999, da hier SIGNAL_KILLED nicht mehr greifen würde.
Man kann die Priorität folgendermaßen auslesen lassen:
<RetVar> == get script priority
Gibt die Priorität des aktuellen Scripts zurück
<RetVar> is script with prio <Var/Number> on stack
Prüft, ob einer der laufenden Scripts auf dem momentanen Task des momentanen Objektes genau die gegebene Priorität <Var/Number> hat
Doch wie Scripter nun mal so sind, wollen sie alles beherrschen, also gibt es natürlich auch Befehle, mit denen sich die Priorität festlegen lässt:set script priority to <Var/Number>
Dieser Befehl setzt die Script-Priorität in einem laufenden Script. Somit kann man beispielsweise gezielt einzelne Signals oder Unterbrechungen deaktivieren oder die Priorität herabsetzen, damit eben diese Signals gestartet werden können.
Es ist der einzige Befehl, mit dem ein Script seine eigene Priorität ändern kann. Alle anderen Befehle legen nur die Priorität mein Start eines Scripts fest:
global script map: set: key=<Object Command/Signal>, class=<Var/Class>, race=<Var/Race>, script=<ScriptName>, prio=<Var/Number>
Setzt die Priorität eines Scripts beim Start desselben per Kommando <Object Command> über die Kommandokonsole oder per Befehl START <RefObj> command <Object Command>: arg1=<Value>, arg2=<Value>, arg3=<Value>, arg4=<Value> zu <Var/Number>.
<RefObj> connect ship command/signal <Object Command/Signal> to script <Script Name> with prio <Var/Number>
Wie vorheriger Befehl, nur auf einzelne Schiffe bezogen, nicht auf Schiffsklassen/-typen
<RefObj> verbinde Flügelmann Kommando/Signal <Object Command/Signal> mit Script <ScriptName> mit Priorität <Var/Number>
Setzt Priorität für ein Flügelmann-Kommando (Aufruf per Hotkey)
<RefObj> -> start task <Var/Number> with script <Script Name> and prio <Var/Number>: arg1=<value> arg2=<value> arg3=<value> arg4=<value> arg5=<value>
Startet einen Script auf einem Task mit der angegebenen Priorität.
<RefObj> -> interrupt task <Var/Number> with script <Script Name> and prio <Var/Number>: arg1=<value> arg2=<value> arg3=<value> arg4=<value>
<RefObj> -> interrupt task <Var> with script <Script> and prio <Var>: arg1=<value> arg2=<value> arg3=<value> arg4=<value>
<RefObj> interrupt with script <ScriptName> and prio <Var/Number>
Diese drei Kommandos unterbrechen ein laufendes Script und starten ein anderes Script mit der festgelegten Priorität.
Die Unterbrechung findet an einem unterbrechbaren Befehl (Ein Befehl mit einem @ davor) statt, und verlangt, dass die Priorität des unterbrechenden Scripts größer als die des zu unterbrechenden Scripts ist. Hier als Priorität null anzugeben, hätte also denselben Effekt, wie in Argon Prime eine weitere Argon Collossus patrouillieren zu lassen: Die Performance geht runter, aber es bringt Nichts.

Der Stack
Der Stack ist theoretisch ein Stapel, auf dem alle Scripts liegen, die unterbrochen wurden und auf ihre weitere Ausführung warten. Eine Unterbrechung kann entweder durch einen Script Call, einen Interrupt oder durch ein Signal hervorgerufen werden. Der unterbrochene Script wird dann an der Unterbrechungsstelle gestoppt und läuft erst nach Beendigung des unterbrechenden Scripts weiter.
Durch diese Unterbrechungen ergibt sich der Stapel von Scripts, der Script Stack.
Es gibt nur zwei Möglichkeiten, Sachen aus dem Stack abzurufen:
Entweder per <RetVar> is script with prio <Var/Number> on stack, das nach einem Script mit der entsprechenden Priorität sucht, oder per <RetVar/IF> <RefObj> is script <ScriptName> on stack of task=<Var/Number>.
Letzteres ist die einzige Möglichkeit, den Script Stack eines Objektes von einem anderen Task oder Objekt aus auf Vorhandensein eines bestimmten Scripts zu prüfen.
Signals
Signals sind von der Spielengine gestartete Kommandos, die bei einem bestimmten Fall eintreten. Einfachstes Beispiel ist SIGNAL_ATTACKED: Es wird gestartet, wenn ein Schiff angegriffen wird. SIGNAL_KILLED hingegen wird nur gestartet, wenn ein Schiff zerstört wurde.
Einige Signals können auch unter bestimmten Umständen mit <RefObj> send signal <Object Signal> gestartet werden. Die genauen Umstände stehen in der Befehlsbeschreibung.
Signals arbeiten wie Interrupts: Sie unterbrechen den momentanen Script und starten einen Neuen. Wenn kein Script auf dem momentanen Objekt läuft, wird das Signal auch nicht gestartet.
Signals haben den Vorteil, dass sie nur bei einem bestimmten eintretenden Fall gesendet werden und nach dem Ablauf des Signal-Scripts den vorherigen Script weiter laufen lassen. Da es sich um Interrupts handelt, haben Signals natürlich auch eine Priorität:
Code:
20 - !ship.signal.leaderjumps
40 - SIGNAL_REQUESTUNDOCK
50 - SIGNAL_ATTACKED - Fluchtreaktion
99 - SIGNAL_RESPONDTOHELPREQUEST - Angriff
99 - SIGNAL_ATTACKED - Gegenangriff
100 - SIGNAL_ATTACKED
150 - SIGNAL_LEADERNEEDSHELP
150 - SIGNAL_FOLLOWERNEEDSHELP
150 - SIGNAL_RESPONDTOHELPREQUEST
200 - SIGNAL_FORMATIONLEADERCHANGED
300 - SIGNAL_CAPTURED
10.000 - SIGNAL_KILLED

Bedeutung einzelner Signals (im originalen Spiel)
!ship.signal.leaderjumps
Es ist kein richtiges Signal an sich, sondern wird vom Script !move.jump per Interrupt auf den Followern von [THIS] gestartet.
Es sorgt dafür, dass die Formationsschiffe nach einer kurzen Zeit ebenfalls den Sprungantrieb starten und dem Leader hinterherspringen.
SIGNAL_REQUESTUNDOCK
Versucht ein Schiff, an einer Station anzudocken, an der bereits alle Andockklammern besetzt sind, so wird dieses Signal an das Schiff geschickt, das bereits am längsten angedockt ist. Es startet dann von der Station, wartet ein bisschen im All und dockt wieder an der Station an.
SIGNAL_ATTACKED
Dieses Signal ist für den Realismus des Spiels das wohl wichtigste Signal, da es gestartet wird, wenn ein Schiff angegriffen wird, sodass es ausweichen und zurückschlagen kann. Den Ablauf des Script könnte man wie folgt zusammenfassen:
------
Ausweichen (defensive move) (nur, falls Hülle oder Schild angeschlagen und Schiff kein GKS oder TS)
Starte Kampfdrohnen (falls TS)
Greife Attacker an, falls Waffen vorhanden (setze Priorität 99), ansonsten: Fliehe zu Station (setze Priorität 50)
------
Im Großen und Ganzen ist dieser Script also dazu da, dass Schiffe einen Gegenangriff starten oder fliehen.
Es ist übrigens auch das einzige Signal, das man auf Stationen starten kann. Normalerweise gibt es nur bei Spielerstationen einen Script, der mit diesem Signal auf Stationen verbunden ist. Es startet gelandete, kommandolose Spielerschiffe, die dann die Station verteidigen und wieder andocken.
SIGNAL_LEADERNEEDSHELP
Schiff ist in einer Formation, deren Leader angegriffen wurde.
Der Angreifer wird als attacker der Formationsschiffe gesetzt...
Eigentlich sollten die Formationsschiffe den Angreifer attackieren, jedoch wurden diese Zeilen auskommentiert, sodass dieser Script nur den attacker setzt sowie laufende Bewegungs-Befehle unterbricht.
Es ist zu vermuten, dass der Sinn dieses Signals im Zusammenhang mit Formationsbezogenen Kampfbefehlen erst richtig greift, da diese den attacker des Schiffes abfragen und es attackieren. Wird der Leader nicht nur attackiert, sondern getötet, so wird der Killer des Leaders angegriffen, obwohl der Leader schon tot ist.
SIGNAL_FOLLOWERNEEDSHELP
Schiff ist Formationsanführer und eines der Formationsschiffe wird angegriffen
Der Script ist, abgesehen von zwei auskommentierten Zeilen, mit dem Script von SIGNAL_LEADERNEEDSHELP identisch: Der attacker wird gesetzt.
Der Unterschied ist hier, dass hier SIGNAL_ATTACKED mit dem Angreifer des Formationsschiffes als Argument gestartet wird. Dadurch greift der Leader den Angreifer an oder flieht zu einer Station.
SIGNAL_RESPONDTOHELPREQUEST
Die Bedingungen zum Start dieses Signals sind unbekannt, jedoch ist seine Funktionsweise eindeutig:
Falls das Schiff zum Kampf geeignet ist (kein Transportschiff, Laser installiert, keine andere wichtige Beschäftigung), kommt es einem angegriffenen Schiff zur Hilfe und zerstört die Angreifer. Dafür setzt es, um einen Mehrfachaufruf dieses Signals zu verhindern, die Priorität auf 90.
Da dieses Signal erst seit X3 vorhanden ist, wird angenommen, dass dieses Signal für Militärschiffe und andere NPC-Jobs gültig ist, damit diese rechtzeitig auf den Hilferuf eines anderen Völkerschiffes reagieren können, und nicht nur präventiv das Universum nach Feinden durchsuchen.
Dieses Signal ist auf keinen Fall für Spielerschiffe, sondern ausschließlich für NPCs gültig.
SIGNAL_FORMATIONLEADERCHANGED
Formationsanführer hat sich geändert (beispielsweise bei Tod des bisherigen Leaders, Leader einer Formation kann ausschließlich per <RefObj> give formation leadership to <Var/Ship> geändert werden).
In diesem Fall wird einfach nur das laufende unterbrechbare Kommando unterbrochen (Bewegungskommando, beispielsweise "escort ship"), sodass die Formationsschiffe dem neuen Leader unverzüglich folgen.
SIGNAL_CAPTURED
SIGNAL_KILLED
Diese beiden Kommandos sind ziemlich identisch: Das erste wird bei Kaperung eines Schiffes (Ausstieg des Piloten) gestartet, das zweite bei Zerstörung des Schiffes.
Diese Kommandos legen einfach nur einen neuen Formationsanführer fest und starten SIGNAL_ATTACKED auf dem neuen Formationsanführer, damit dieser den Angreifer des alten Anführers bekämpft.
Einige Scripts nutzen das SIGNAL_KILLED, um beispielsweise eine Schockwelle auszulösen (XTM) oder Kopfgeld auszuzahlen (GAIUS).

Wie man sehen kann, regeln Signals eigentlich ausnahmslos den Kampfablauf. Es gibt zwei unbenutzte Signals: SIGNAL_ABORTED und SIGNAL_COLLISONWARN. über Ersteres ist nur der Name selbst bekannt, Letzteres sollte ursprünglich das Schiff davor bewahren, mit einer Station oder einem sonstigen Objekt öfter als nötig zu kollidieren.
Tasks
Da oft mehrere Scripts auf einem Schiff laufen sollen, muss eine Regelung her, mit der man verschiedene Scripts eindeutig voneinander unterscheiden kann. Dafür sind die Tasks zuständig. Ein Task ist eine Art Scriptslot eines Objektes, auf dem jeweils nur ein Script gestartet werden kann. Tasks werden per Zahlen zugeordnet, die man per <RetVar/IF> get task ID auslesen kann. Es sind jeweils nur Task IDs von 0 bis 2.147.483.647 möglich.
Im normalen Spiel werden die Tasks wie folgt benutzt:
Code:
Schiffe Stationen
0 Hauptkommando Haupttask
1 Kanzel 1
2 Kanzel 2
3 Kanzel 3
4 Kanzel 4
5 Kanzel 5
6 Kanzel 6
10 zusätzliches Schiffskom. 1 Stationskommando 1
11 zusätzliches Schiffskom. 2 Stationskommando 2
12-19 Stationskommando 3-10

Startet man also per Kommandokonsole eines Schiffes ein Kommando, so wird es auf Task 0 gestartet.
Die Tasks für Kanzeln entsprechen der jeweiligen Turret ID, sodass man bei einem Turret-Script zur Identifizierung der zu steuernden Kanzel einfach nur die Task ID abfragen muss.
Die Zusätzlichen Schiffskommandos 1 und 2 belegen dann noch Task 10 und 11.
Bei Stationen ist die ganze Sache etwas anders: Wird eine Station gebaut, so wird ein idle-Script auf Task 0 gestartet:
Code:
Script !station.cmd.idle.pl
001 while [TRUE]
002 @ = wait 999999 ms
003 end
004 return null
Dieser Script ist ausschließlich dazu da, bei SIGNAL_ATTACKED unterbrochen zu werden.
Die eigentlichen Stationskommandos 1 bis 10 sind dann auf den Tasks 10 bis 19 gestartet.
Alle anderen Tasks kann der Spieler nicht über die Kommandokonsole einsehen, sodass sie unsichtbar auf dem Objekt laufen.
Wie auch bei den t-files und Kommandos gibt es einen für Egosoft reservierten Bereich. Dieser reicht von 0 bis 39. Der Bereich ab 20 ist für das Bonuspaket reserviert, Task 33 wird von den Kanzelscripts zum automatischen Waffenwechsel verwendet (im normalen Spiel bloß von NPCs genutzt)
Es ist also zu empfehlen, nur Task IDs zu wählen, die größer als 40 sind. Aus persönlichem Anlass bitte ich darum, Task IDs ab 65 zu wählen, da der Bereich zwischen 50 und 60 hauptsächlich von meinen Projekten eingenommen ist.
Im Egoforum gibt es eine Übersicht über verwendete feste Tasks, sodass es keine Konflikte mit anderen Scripts bezüglich der Tasks gibt.
Sind die auf dem Task zu startenden Scripte temporär und laufen nur wenige Millisekunden anstatt in Dauerschleife, so empfiehlt es sich, einen beweglichen Task zu verwenden. Der Script dafür sieht dann wie folgt aus:
Code:
$Task = 65
while <RefObj> -> is task $Task in use
inc $Task =
end
<RefObj> -> start task $Task with script <Script Name> and prio <Var/Number>: arg1=<value> arg2=<value> arg3=<value> arg4=<value> arg5=<value>
Somit wird eine Task ID gesucht, die größer oder gleich 65 ist und zur Zeit nicht benutzt wird. Somit ist sicher gestellt, dass kein anderer Script gestoppt wird.
Globale Scripts
Globale Scripts werden gestartet, wenn man versucht, ein Script auf dem Objekt null zu starten. Dies kann auch aus Versehen passieren, wenn ein Objekt zerstört wurde, SIGNAL_KILLED durchgelaufen ist und der Interruptpunkt ein Start-Script Call war.
Da dann ein Objekt-Script global läuft, ist es empfehlenswert, in jedem Objekt-Script am Anfang die folgende Prüfung einzubauen:
Code:
skip if [THIS]
return null
Dadurch wird verhindert, dass ein Script ungewollt global läuft.
Laufende globale Scripts kann man im ScriptEditor unter "Global Script Tasks" einsehen (direkt unter dem Script Debugging Menu). Normalerweise läuft hier nur plugin.autotrade.lockmaster, der für Universumshändler regelt, an welcher Station sie andocken dürfen. Besonders in der XTendedMod kommen weitere globale Scripts hinzu, doch auch einzelne Projekte können Globale Scripts starten.
Globale Scripts sind generell hilfreich, wenn sich der Script nicht eindeutig auf ein Objekt bezieht, sondern stattdessen mehrere Objekte prüft oder den Wechsel des Spielerschiffs vernachlässigen soll und keine AL-Option verfügbar sein soll.
Bei Globalen Scripts sollte man immer zuerst prüfen, ob schon ein derartiger Script läuft. Dafür empfiehlt sich folgender Code am Anfang und am Ende des Scripts:
Code für einen Globalen Script, der nur ein einziges Mal durchlaufen und sich dann beenden soll
Code:
Script globaler.script
001 $GlobVarName.Active = <eindeutiger GV-Name, der nur von diesem Script genutzt wird>
002 skip if not get global variable: name=$GlobVarName.Active
003 return null
004 set global variable: name=$GlobVarName.Active value=[TRUE]
<eigentlicher Script Code>
050 set global variable: name=$GlobVarName.Active value=null
051 return null
Läuft der Script noch nicht, ist die GlobVar null. Somit wird Zeile 003 übersprungen und die Globale Variable auf [TRUE] gesetzt.
Versucht jetzt ein Script erneut, den globalen Script zu starten, so wird dieser in Zeile 003 beendet.
Ist der erste Script schließich fertig mit seiner Arbeit, so wird die Globale Variable wieder zurückgesetzt und beim nächsten Start dieses Scripts beginnt das Ganze wieder von vorn.
Aber es gibt natürlich auch Scripts, die in Endlosschleife laufen sollen. Dafür empfehle ich folgenden Code, der dem für einmaligen Ablauf ähnlich ist:
Code:
Script globaler.script
001 $GlobVarName.Active = <eindeutiger GV-Name, der nur von diesem Script genutzt wird>
002 skip if not get global variable: name=$GlobVarName.Active
003 return null
004 set global variable: name=$GlobVarName.Active value=[TRUE]
<eigentlicher Script Code>
050 set global variable: name=$GlobVarName.Active value=null
051 $null = null
052 @ START $null -> call script 'globaler.script' :
053
054 return null
Hier wird ebenfalls überprüft, jedoch startet sich das Script in Zeile 52 als neuer globaler Script neu.
Würde man einfach eine while-Schleife benutzen, so müsste man für eine Aktualisierung des Scripts eine Sonderfunktion einbauen, die den Script neu startet, wenn eine neue Version verfügbar ist. Bei dieser Variante regelt sich das Ganze automatisch, da sich der Script unabhängig von seiner Version regelmäßig selbst neu startet.
Dieser Script muss nur ein einziges Mal im Setup-Script des Scriptpakets gestartet werden, danach läuft er unendlich lang weiter, bis er aus dem scripts-Ordner entfernt wird. Natürlich kann man auch für eine Deinstallation eine Sonderroutine einbauen, das bleibt jedoch dem Scripter überlassen.
btw: AL-Scripts und BBS-Scripts werden ebenfalls global gestartet. Bei der XTendedMod gab es ein Problem mit einem AL-Script, das bei jedem Laden des Speicherstands neu gestartet wird. Speichert und lädt man regelmäßig, so erhöht sich die Anzahl der Instanzen je ein mal, bis die Performance schließlich im Keller ist. Es handelte sich um den Script al..XTM.St.Xtra, der besonders lange läuft (bis zu mehreren Minuten). Benutzt ihr die Mod schon seit 0.7.1, so könnt ihr in ihn sicher mehrfach unter euren Global Script Tasks finden.
Damit so ein peinlicher Fehler bei euren Scripts nicht auftritt, empfehle ich, immer zu prüfen, ob ein globaler Script bereits läuft, vor Allem, wenn er in Dauerschleife agiert.

 


Dieser Artikel wurde ursprünglich von X-Freak Cartman geschrieben und war bis zu deren Deaktivierung auf www.madxhawk.com zu finden. X2

Write a comment…