Konvertieren von C-Skript-Code nach lite-C

lite-C ist der Syntax von C/C++ sehr viel ähnlicher als C-Skript. Während der Code selbst weitgehend unverändert bleiben kann, sind einige subtile Änderungen in Definitionen und Angaben nötig. Hier finden Sie alle notwendigen Schritte zum Konvertieren Ihres C-Skript-Codes in C .

Bevor Sie anfangen - bereinigen Sie Ihren Code

Bisher konnten Sie beim Schreiben Ihres Codes ruhig ein wenig nachlässig sein und z.B. Gleichheitszeichen (=), Kommata oder Klammern weglassen, ohne daß sich der Kompilierer beschwert hätte. Nicht mehr. Sorgen Sie dafür, daß die Syntax Ihrer Skripte korrekt ist. Sollten Sie immer noch überkommene Schlüsselworte und Anweisungen aus uralten WDL-Skripten verwenden, ist es spätestens jetzt an der Zeit, diesen alten Krempel loszuwerden. Der lite-C-Kompilierer kennt definitiv keine Schlüsselworte, die nicht im Handbuch beschrieben sind, die führen zu Fehlermeldungen. Nur einwandfreier C-Skript-Code läßt sich reibungslos in lite-C übertragen.

Um das Ganze einfacher zu gestalten, können Sie mit Textumwandlungs-Software arbeiten. ReplaceEm (z. B. unter http://www.freeware-archiv.de/BKReplaceEm-Dateien.htm) ist ein kostenloses Werkzeug, welches sich zum fast automatischen Aufräumen Ihres C-Skript-Codes und anschließenden Konvertieren in lite-C verwenden läßt. Hier finden Sie zwei Austauschtabellen für dieses Tool: wdl_to_wdl.txt bereinigt schlampigen Code in Ihrer C-Skript-Datei und wdl_to_c.txt konvertiert ihn anschließend in lite-C. In vielen Fällen wird das Konvertieren dennoch etwas Handarbeit (s. u.) brauchen, die beiden automatischen Austauschoperationen sollten 70% der Aufgabe übernehmen.

Vergewissern Sie sich auch, daß Ihr Code mathematisch korrekt ist. Fehler - z. B. das Vergleichen nicht ganzzahliger Wert mit '==' or '!=' - können in C-Skript und in lite-C zu verschiedenen Ergebnissen führen.

Nehmen Sie '.wdl' oder '#define PRAGMA_' für Projekt-Vorgaben

Anstelle von .wdl haben Skripte nun die Extension .c oder .h. Machen Sie es sich zur Gewohnheit, .c für Skripte, die Variablen, Pointer oder Code enthalten, zu verwenden und .h für Skripte, die nur Definitionen oder Includes beinhalten. Am Anfang des Main-Skripts muss jeweils immer acknex.h eingefügt sein. Die Default-Engine-Keys und Funktionen zur Anzeige des Debugpanels, das Bewegen der Kamera usw. werden nun in default.c eingefügt. Sie können während der Zeit des Entwickelns default.c. einfügen und es zum Publishen entfernen.

In einem Projekt mit lite-C dienen .wdl-Dateien nun lediglich zum Definieren von Pfaden, Resourcen, Ordnern und dem StartUp-Fenster. Für Code, Variablen, Includes etc. lassen sie sich nicht verwenden. Wenn sie denselben Namen wie die .c-Datei hat, wird eine.wdl-Datei beim Hochfahren automatisch gelesen. Hat Ihr Projekt z.B. den Namen "test.c", geben Sie Ihre Satements PATH, RESOURCE, SAVEDIR, PLUGINDIR, WINDOW etc. in der "test.wdl" an. Alternativ benutzen Sie #define PRAGMA_..-Statements für Projekteinstellungen.

Namen: beachten Sie die Schreibweise

Achten Sie darauf, immer dieselbe Schreibweise für Ihre Variablen- und Objektnamen zu verwenden. MyVariable und myvariable sind in C zwei verschiedene Variablen! Als Konvention werden vordefinierte Structs, Flags und Definitionen in Großbuchstaben (wie PANEL oder SHOW) geschrieben, während Engine-Funktionen und -Variablen (wie level_load() oder time_step) in Kleinbuchstaben geschrieben werden.

# Praeprozessor-Definitionen

C-Script hatte keinen Praeprozessor, daher wurden define- oder include-Zeilen vom Kompilierer selbst ausgewertet. In C werden solche Statements vor dem Kompilieren von einem Praeprozessor verarbeitet. Zur Unterscheidung zwischen Compiler-Code und Praeprozessor-Anweisungen beginnen letztere mit '#' und haben kein Semikolon am Ende:
#include "material.c"	 // C-Script: include <material.wdl>;  
#define MYNUMBER 17    // C-Script: define MYNUMBER,17;
#ifdef MYDEF           // C-Script: ifdef MYDEF;
...
#else                  // C-Script: ifelse;
...
#endif                 // C-Script: endif;
Sie haben nun den Vorteil, dass Sie einfache Berechnungen in #define-Statements haben können und sich `Macros´ definieren lassen, die vor dem Kompilieren ausgewertet werden.
#define MYNUMBER  (17+15)
#define NEGATIVE(x)  (-x)

Organisieren Sie Ihre Skripte

Der Kompiler von lite-C handhabt per include eingefügte Skripte wie jeder C/C++-Kompiler. Wir empfehlen, alle .c-Skripte im Work-Ordner zu haben, aber sie können auch Unterordner verwenden. In diesem Fall müssen Sie den entprechenden Pfad angeben, beispielsweise #include "myscripts\script.c". Ordnernamen für Include-Dateien dürfen keine Leerzeichen enthalten. Die Haupt-c.-Datei muss genauso wie vorher im Work-Ordner sein. PATH- oder RESOURCE-Statements aus der .wdl-Datei lassen sich für Includes nicht benutzen. Nun ist Ihr .c-Code vor dem Publishen vollständig als .exe kompiliert, so dass Ihre Skripte im veröffentlichten Spiel nicht enthalten sind.

Ersetzen Sie <> durch""

In C-Skript, vor Version 6.40, kennzeichneten spitze Klammern <> Dateinamen zur Aufnahme ins gepublishte Game. Nicht mehr. In C verursachen Strings in spitzen Klammern Syntaxfehler. Dateinamen werden nun automatisch aufgespürt und einbezogen. Die Dateiextensionen zum Aufnehmen bzw. Ausschließen können in data\options.scr. gesetzt werden. In #include-Statements bedeuten <..> eine Systemdatei (im Unterordner include) und ".." steht für eine Projektdatei (im work-Ordner des betreffenden Projekts oder einem Unterordner).

Verwenden Sie Funktions-Prototypen

In C-Skript könnten Sie eine Funktion, so sie keine Argumente hätte, aufrufen, ehe sie definiert wurde. In lite-C muß jede Funktion - auch wenn sie keine Argumente hat - entweder definiert oder es muß ein Prototyp angemeldet sein, ehe sie aufgerufen werden kann. Beispiel:
function particle_explosion(PARTICLE* p); // function prototype
...
particle_explosion(p); // function call
...
function particle_explosion(PARTICLE* p) // function definition
{
  ...
}

Verwenden Sie stets Pointer

In C-Skript mussten wir zwischen Objektdefinitionen (PANEL mypanel = {... }) und Objekt-Pointern (PANEL* mypanelpointer = mypanel;) unterscheiden. In lite-C sind Objekte immer Pointer, nun heißt es PANEL* mypanel = { ... }.

Initialisieren Sie stets die Variablen

In C-Skript wurden nicht initialisierte lokale Variablen (z.B. var myvar;) automatisch auf Null initialisiert. Nicht mehr. Nun müssen Sie Variablen entweder in der Definition initialisieren (z.B. var myvar = 0;) oder Sie lassen sie, aber nur, wenn ihr Initialwert nicht von Bedeutung ist, uninitialisiert. Da Structs nicht in ihrer Definition definiert werden können, verwenden Sie zum Initialisieren lokaler oder globaler Structs auf Null das Makro zero() (es ist in acknex.h definiert) und zum Initialisieren von Vektoren, die aus 3 vars bestehen, vec_zero():
VECTOR speed;

vec_zero(speed);	// initializes the VECTOR "speed" to x=0,y=0,z=0

Zum Umstellen und Testen kann lite-C mit der Definition PRAGMA_ZERO automatisch alle globalen und lokalen Variablen auf Null initialisieren. Dazu fügen Sie folgendes hinzu:

#define PRAGMA_ZERO   // initialize variables

Am Anfang Ihres Skriptes werden sämtliche nicht-initialisierte globale und lokale Variablen auf Null gesetzt. Dies verlangsamt ein wenig die Funktionsaufrufe und daher ist es vorzuziehen, PRAGMA_ZERO nicht in Ihrer Endversion zu benutzen. Globale Variablen und Structs werden in lite-C immer och automatisch auf Null initialisiert, dennoch sollten Sie es sich zur Gevohnheit machen, ihre Initialwerte anzugeben.

Prüfen Sie Vektoren und Arrays

In C-Skript könnten wir für var-Arrays x-, y-, z-Elemente benutzen. In lite-C gibt es das VECTOR-Struct. Um also x-, y-, z-Elemente anzuwenden und um Engine-Funktionen, die VECTOR* -Pointer erwarten aufzurufen, müssen Sie nun anstelle eines var[3] einen VECTOR* definieren:
var vSpeed[3] = 10,20,30; // C-Script
var vSpeed[3] = { 10,20,30 }; // lite-C - address with [0], [1], [2] VECTOR* vSpeed = {x=10;y=20;z=30;} // lite-C - address with .x, .y, .z

 !!  Ein falsches Anwenden von Arrays kann zu Ärger beim Konvertieren eines Skripts führen. In C-Skript entsprach der Name eines Arrays seinem ersten Element. In C ist er die Adresse des Arras. Der folgende Code nun ist in seiner Syntax korrekt, führt jedoch in C-Skript und lite-C zu unterschiedlichen Ergebnissen.

var my_vector[3];
...
var x = my_vector;    // C-Script: x is set to the first array element
var x = my_vector;    // lite-C - wrong: x is set to the array pointer!
var x = my_vector[0]; // lite-C - correct: x is set to the first array element

Das Verwenden von Funktionen, Starter-Funktionen und DLL-Funktionen

function und action sind nach wie vor gültig und zu var und void per 'typedef' definiert. Dllfunction wird nicht mehr benutzt - definieren Sie den Prototypen der Plugin-Funktion einfach wie jede andere Funktion. Der Kompiler erkennt automatisch ob die Funktion in einem PlugIn sitzt oder nicht. Starter-Funktionen brauchen den Anhang _startup. Näheres siehe function.

In lite-C läuft die main-Funktion vor dem ersten Frame und ermöglicht so Videoeinstellungen bevor die Video-Hardware initialisiert wird. Das bedeutet, dass Sie einen Frame warten müssen - wait(1) - ehe Sie Funktionen, wie beispielsweise video_switch() oder video_set() aufrufen, die auf die Video-Hardware zugreifen.

Setzen Sie Engine-Variablen oder Events in Funktionen

In C-Skript konnten Tastatur- oder Mausevents außerhalb von Funktionen gesetzt oder neu definiert werden. In C müssen sämtliche Anweisungen - einschließlich Events - innerhalb einer Funktion stehen. Um beim Starten Tasten zuzuweisen, nehmen Sie die main- oder eine Startup-Funktion:
function ie_startup() 
{ 

  d3d_autotransparency = ON;


	on_f2 = ie_save;
	on_f3 = ie_load;
	on_f5 = ie_video;
	on_f6 = ie_shot;
	on_f10 = ie_exit;
}

Setzen, Zurücksetzen oder Umschalten von Flags

In C-Skript wurden Flags durch ein flags-Statement in einer Objektdefinition gesetzt, in Funktionen aber als Einzelparameter behandelt. In C wird der flags-Parameter außerdem in Funktionen zum Setzen oder Zurücksetzen von Flags benutzt.
mypanel.flags |= SHOW;  // C-Script: mypanel.SHOW = ON;
mypanel.flags &= ~SHOW; // C-Script: mypanel.SHOW = OFF; 
Das mag Anfängern unheimlich erscheinen und ist in der Tat weniger intuitiv als die Flag-Methode von C-Skript. Daher wurden in acknex.h zum Setzen, Zurücksetzen, Umschalten oder Abfragen von Flags einige Makros definiert:
#define set(obj,flag) obj.flags |= (flag)
#define reset(obj,flag) obj.flags &= ~(flag)
#define toggle(obj,flag) obj.flags ^= (flag)
#define is(obj,flag) (obj.flags & (flag))
...
set(mypanel,SHOW);
reset(mypanel,SHOW);
toggle(mypanel,SHOW);
if is(mypanel,SHOW) { ... }
Sie haben nun den Vorteil, dass Sie verschiedene Flags in einem einzigen Statement setzen oder zurücksetzen können:
mypanel.flags |= (SHOW|LIGHT|TRANSLUCENT);
set(mypanel,SHOW|LIGHT|TRANSLUCENT);

 !!  Bitte beachten Sie: der alte Flag TRANSPARENT wurde in TRANSLUCENT umbenannt (denn TRANSPARENT wird bereits vom Windows-GDI-Header benutzt). Achten Sie ausserdem darauf, den richtigen flags-Parameter zu setzten, denn Entities haben mehr als einen: Es gibt auch eflags, emask, smask, und flags2 -Parameter, die nicht von Makros begleitet werden (obgleich Sie einige definieren könnten):

ENTITY* eSky = { type = "clouds+2.tga"; flags2 = SKY | DOME | SHOW; }

Speichern und Laden

Wenn Sie in den while() { ... wait(); }-Schleifen oder -Funktionen dynamisch veränderte globale oder lokale Pointer verwenden, seien Sie sich der Tatsache bewußt, daß Pointer mit Ausnahme der my- und you-Entity-Pointer unter lite-C nicht gespeichert werden. Sie müssen daher sicher gehen, daß Pointer, wenn sie innerhalb der Schleife benutzt werden, auch auf das richtige Objekt zeigen. Entity-Pointer oder Engine-Objektpointer können, um speichersicher zu sein, in ein handle konvertiert werden.

Execute

Der lite-C-Compiler hat keine Interpreter-Funktion. Die execute-Anweisung kann lediglich zum Aufruf von Engine-Funktionen mit konstanten Parametern verwendet werden. Es gibt die neue Funktion var_for_name, welche den Inhalt einer Variablen oder eines Strings zur Laufzeit anzeigt und verändert. Die [Tab]-Konsole verwendet nun var_for_name. Geben Sie den Namen eines globalen var, int, long, float oder eines STRINGs ein und drücken Sie [Eingabe]. Sein Inhalt wird angezeigt. Indem Sie wieder [Eingabe] drücken, können Sie den Inhalt editieren und verändern. Hier ist der lite-C-Code für [Tab] (in include\default.c):
TEXT* def_ctxt = { string = "Enter var or STRING","#80"; layer = 999; }

void def_console() // Tab
{
  def_ctxt.pos_x = 2;
  def_ctxt.pos_y = screen_size.y-30;
  toggle(def_ctxt,SHOW);
  while is(def_ctxt,SHOW) {
    beep();
    inkey((def_ctxt.string)[1]);
    if (13 == result)
      var_for_name((def_ctxt.string)[1]);
    else
      reset(def_ctxt,SHOW);
  }
}

Proc_late

Wurde ersetzt durch proc_mode = PROC_LATE; vor dem wait() Aufruf.

sleep(x)

Gibt es nicht mehr - verwenden Sie stattdessen wait(-x). Aber Achtung: Sleep(x) (mit großem 'S') gibt es sehr wohl, es ist eine Standard-Windows-Funktion, die sich deutlich anders verhält, als das alte sleep()!

Trigonometrische Funktionen

In C verwenden die trigometrischen float/double-Funktionen (sin, cos, tan, asin, acos, atan) für Winkelwerte Rad anstelle von Grad. Für die Grad-Trigonometrie bietet lite-C spezielle var-Funktionen: (sinv, cosv, tanv, asinv, acosv, atanv). Sie entsprechen ihren Gegenstücken sin, cos, tan, asin, acos, atan in C-Skript. Die 6 trigonometrischen Funktionen sind 'overlaoded', d.h. sie ermitteln anhand der Tatsache, ob ihr Argument ein var oder ein float/double ist, automatisch, ob Sie Grad oder Rad verwenden sollen. Wenn jedoch mögliche Unklarheiten bestehen - z.B. beim Aufrufen einer trigonometrischen Funktion mit einer Konstanten - müssen Sie zwischen den Versionen float/double und var unterscheiden.
x = sinv(45.0);	// C-Script: x = sin(45.0);

var_info, var_nsave

Wurden beide durch var ersetzt. Anstatt var_info zu benutzen, lassen Sie die Variable mit "_i" enden; anstelle von var_nsave , lassen Sie die Variable mit "_n" enden.

Skills

Entity- und Materialskills sind nun ein Array (skill[0]..skill[99]) anstatt einzelne Parameter (skill1..skill100). Damit jedoch nicht sämtliche Skill-Anweisungen geändert werden müssen, lassen sich die alten Skillnamen immer noch verwenden. Dafür gibt es in include\compat.h, das automatisch im Code enthalten ist, viele #defines.

Arrays

In C werden die Anfangswerte von Arrays von geschweiften Klammern {} umschlossen. Beispiel:
var test[3] = { 1,2,3 };	// C-Script: var test[3] = 1,2,3;

STRING

In lite-C muss Strings beim Definieren ein Wert zugewiesen werden. Andernfalls sind sie lediglich leere String-Pointer. Sie sind immer von variabler Länge. Die Syntax hierfür ist nun:
//STRING* str; // this would be just an empty pointer in C
//STRING* str[80]; // this would be just an array of empty pointers in C
STRING* str = "Now this is \n a string!"; // Allocate a string
STRING* str = ""; // Allocate an empty string
STRING* str = "#1000";	// Allocate a 1000 characters string filled with spaces;

 !!  Konstante Schriftzeichen, die Sie (wie "Hier sind ein paar Schriftzeichen") zwischen Anführungszeichen setzen, haben in C den Typus char[], in C-Skript aber STRING*. Da fast alle Engine-Funktionen sowohl char[] als auch STRING* als Argumente annehmen können, brauchen Sie sich in der Regel also gar nicht um diesen Unterschied zu kümmern. Einige Anwender-DLL-Funktionen allerdings könnten explizit einen STRING* erwarten. Um solchen Funktionen Argumente zu übergeben, müssen Sie demzufolge Strings definieren.

TEXT-Strings

TEXT.string ist ein Pointer zu einem Pointer-Array. Daher muß immer ein Array-Index benutzt werden und aufgrund der Vorrangigkeit der Operatoren müssen um TEXT.pstring nun Klammern gesetzt werden.
//str_cpy(mytext.string,"Test!"); // C-Script style
str_cpy((mytext.pstring)[0],"Test!"); // C style 

FONT

Die Schriftzeichengröße wird nun automatisch aus einem FONT-Bild berechnet. Ist die Bildbreite durch 11 teilbar, muss das Bild die Zahlen 0...9 zuzüglich Leerzeichen in einer Reihe beinhalten. Liegt das Verhältnis Breite/Höhe über 4, muss es aus 4 Reihen von je 32 Schriftzeichen bestehen, andernfalls aus 8 Reihen zu 32 Schriftzeichen. Diese Kriterien werden von allen uns bekannten Bitmap-Font erfüllt. Sollten sie auf Ihr Bild nicht zutreffen, muss das Font-Bild umgestaltet werden. Größe und Typus von Truetype-Fonts werden durch ein angefügtes "#nbi" angegeben wobei n = Schriftzeichengröße und optional b = fett (bold) und i = kursiv (italic) ist. Die Defaultgröße ist 12.
//FONT imgfont = "fontimage.pcx", 7,12; // C-Script style
FONT* myfont = "fontimage.pcx"; // C style
//FONT ttffont = "Arial",1,10; // C-Script style
FONT* ttffont = "Arial #10b"; // C style, size 10, bold 

ENTITY, SKY

Layer-Entities lassen sich ähnlich wie in C-Script-Layer definieren, allerdings gibt es kein separates SKY-Objekt. Der Sky ist eine ENTITY mit im Parameter flags2 gesetztem SKY-Flag. Beispiel einer Sky-Entity:
ENTITY* eSky = { type = "blood_gorge+6.tga"; flags2 = SKY|CUBE|SHOW; }

PARTICLE

.. ist ein anderes Struct als ENTITY, daher können Sie den my Pointer nicht mehr für Partikel benutzen. Verwenden Sie stattdessen den PARTICLE* Pointer, der jeder Partikelfunktion übergeben wird:
function particle_effect (PARTICLE *p)
{
   p.size = 50;
   ...
}}

my, you

..sind globale Pointer, wenn Sie also eine Funktion, um diese zu verändern aufrufen, werden sie auf einem globalen Gebiet verändert und nicht nur innerhalb des Bereiches dieser Funktion. Sie werden während wait() noch individuell beibehalten.

Debugging

Das Debuggen in SED funktioniert genause wie vorher, ohne SED ist es aber ein wenig anders:

-Setzen Sie, indem sie den Kommentar-Tag '//!' ans Ende der Zeile stellen, einen Breakpoint in den Code. Breakpoints können nur in reinen lite-C-Programmen (mit einer main() -Funktion, nicht mit WinMain()) gesetzt werden und dies auch nur in der Haupt-Datei (main), nicht in einer eingefügten (include). Sie funktionieren nur nach dem ersten Frame, wenn Sie sie also in die Main-Funktion setzen, fügen Sie davor ein wait(1) ein.
- Starten Sie die Engine im Fenstermodus und mit der Kommandozeilenoption -debug.

Sobald der Breakpoint getroffen wird, hält die Engine an und es wird die gegenwärtige Quellzeile im Titelbalken angezeigt. [Esc] bricht das Debuggen ab, [F10] geht zur nächsten Anweisung und [Shift-F10] geht über Funktionsaufrufe hinweg und [Ctrl-F10] läuft zum nächsten Breakpoint. Globale Variable können in einem Panel oder einem SED-Watch beobachtet werden. Lokale Variable lassen sich überprüfen, indem man sie vorübergehend durch globale ersetzt.

-d Kommandozeilenoptionen

Zum Veröffentlichen wird der lite-C-Code in eine.exe-Datei kompiliert. Diese startet um einiges schneller als ein .c-Skript. Daher können können #defines nicht mehr zur Laufzeit verändert werden, denn der Code lässt sich nun mal nach dem Kompilieren nicht mehr ändern. Zum Auswerten der Kommandozeile zur Laufzeit können Sie stattdessen command_str benutzen. Der -d-Kommandozeilenparameter setzt globale vars und STRINGs nun direkt. Beispiele:
var Test1 = 0;	// is set to 1 by "-d Test1" 
var Test2 = 77;	// is set to 78 by "-d Test2,78" 
STRING* sTest3 = "old";	// is set to "new" by "-d sTest3,new" 

Siehe auch:

Pointers, Structs, Functions

► Aktuelle Version Online