How to convert C-Script code to lite-C

Lite-C follows the C/C++ syntax more thoroughly than C-Script. While the code itself can remain mostly unchanged, some subtle changes in definitions and declarations are necessary. Here are all steps for getting your C-Script code converted to C:

Before you start - clean up your code

Until now, you could write sloppy code, for instance omit equals signs (=), commas or parentheses without compiler complains. Not anymore. Make sure that the syntax of your scripts is correct. If you're still using abandoned keywords and obsolete instructions from very old WDL scripts, the time is finally come to get rid of that old stuff. Keywords that aren't described in the manual are definitely unknown to the lite-C compiler and will produce error messages. Only proper C-Script code can be smoothly transferred to lite-C.

You can use text conversion software for making this easier. ReplaceEm (f.i. at http://www.freeware-archiv.de/BKReplaceEm-Dateien.htm) is a free tool that can be used to almost automatically cleaning up your C-Script code and then converting it to lite-C. Here are two replace tables for this tool: wdl_to_wdl.txt fixes sloppy code in your C-Script file, and wdl_to_c.txt converts it to lite-C afterwards. Still, in many cases the conversion will require manual steps as described below, but the two automatic replace operations should cover 70% of the task.

Make also sure that your code is mathematically correct. Mistakes - for instance comparing non-integer values with '==' or '!=' - can have different results in C-Script and in lite-C.

Use '.wdl' or '#define PRAGMA_' for project settings

Scripts have now the extension .c or .h instead of .wdl. As a convention, use .c for scripts that contain variables, pointers or code, and use .h for scripts that only contain definitions or includes. At the beginning of the main script, acknex.h must always be included. The default engine keys and functions for displaying the debug panel, moving the camera etc. are now included in default.c. You can include default.c during development, and remove it for publishing.

In a lite-C project, .wdl files are now only for defining paths, resources, folders, the startup window etc. They can not be used for code, variables, includes, etc. A .wdl file is automatically read at startup if it has the same name as the .c file. For instance, if your project is named "test.c", have your PATH, RESOURCE, WINDOW etc. statements in "test.wdl". Alternatively, use #define PRAGMA_.. statements for project settings.

Names are case sensitive

Make sure that you're using the same case for your variable and object names. MyVariable and myvariable are two different variables in C! As a convention, predefined structs, flags and other definitions are written in uppercase (like PANEL or SHOW), while engine variables and functions are written in lowercase (like level_load() or time_step).

# preprocessor definitions

C-Script did not have a preprocessor; thus define or include statements were evaluated by the compiler. In C, such statements are processed by a preprocessor before compiling. To differentiate between compiler code and preprocessor directives, they begin with '#' and don't end with a semicolon:
#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;
As an advantage, you can now have simple calculations in #define statements, and can define 'macros' that are evaluated before compiling:
#define MYNUMBER  (17+15)
#define NEGATIVE(x)  (-x)

Organize your scripts

The lite-C compiler handles included scripts just like any C/C++ compiler. We recommend that you have all your .c scripts in your work folder, but you can also use subfolders. In that case you need to give the relative path, like #include "myscripts\script.c". Folder names for include files must not contain spaces. The main .c file must be in the work folder, just as before. The PATH or RESOURCE statements from the .wdl file can not be used for includes. Your .c code is now completely compiled to an .exe before publishing, so your scripts won't be contained in the published game.

Replace <> by ""

In C-Script before version 6.40, <> brackets indicated file names to be included in the game. Not anymore. Strings in <> brackets produce syntax errors in C. File names are now automatically detected and included. The file extensions to include and exclude can be set in data\options.scr. In #include statements, <..> means a system file (in the include subfolder) and ".." means a project file (in the project's work folder or subfolders).

Use function prototypes

In C-Script you could call a function before it was defined when it had no arguments. In lite-C, any function - even if it has no arguments - must be either defined or a prototype must be declared before it can be called. Example:
function particle_explosion(PARTICLE* p); // function prototype
...
particle_explosion(p); // function call
...
function particle_explosion(PARTICLE* p) // function definition
{
  ...
}

Always use pointers

In C-Script, we had to differentiate between object definitions (PANEL mypanel = {... }) and object pointers (PANEL* mypanelpointer = mypanel;). In lite-C, objects are always pointers. So it's now PANEL* mypanel = { ... }.

Always initialize variables

In C-Script, uninitialized local variables (like var myvar;) were automatically initialized to zero. Not anymore. You now should to either initialize variables in the definition (like var myvar = 0;), or leave them uninitalized only when their inital value does not matter. Because structs can not be initialized in their definition, use the zero() macro (defined in acknex.h) for initalizing local or global structs to zero, and vec_zero() for initializing vectors consisting of 3 vars:
VECTOR speed;

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

For migration and testing, lite-C can automatically initialize all local variables to zero with the PRAGMA_ZERO definition. If you add

#define PRAGMA_ZERO   // initialize variables

at the beginning of your script, all uninitialized global and local variables are set to zero. This slows down function calls a little, so it's preferable not to to use PRAGMA_ZERO in your final version. Global variables and structs are still automatically initialized to zero in lite-C, but you should make it a habit to give their initial values nevertheless.

Check vectors and arrays

In C-Script, we could use x, y, z elements for var arrays. In lite-C there's the VECTOR struct. So for using x, y, z elements, and for calling engine functions that expect VECTOR* pointers, you now need to define a VECTOR* rather than a var[3]:
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
 !!  The wrong use of arrays can lead to trouble when converting a script. In C-Script, the name of an array was equivalent to its first element; in C it's the address of the array. So the following code has correct syntax, but leads to different results in C-Script and lite-C:
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

Using functions, starter functions, and dllfunctions

function and action are still valid, and typedef'd to var and void. Dllfunction is not used anymore - just define the plugin function prototype like any other function. The compiler will automatically detect whether the function sits in a plugin or not. Starter functions need the _startup postfix. See function for details.

In lite-C the main function runs before the first frame, allowing for video settings before the video device is initialized. This means that you have to wait one frame - wait(1) - before calling functions that access the video device, like video_switch() or video_set().

Set engine variables or events in functions

In C-Script, engine variables or keyboard or mouse events could be set or redefined outside functions. In C, all variable assignments - including events - must be inside a function. For assigning keys at startup in lite-C, or for setting engine variables, use the main function or a startup function:
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;
}

Set, reset, or toggle flags

In C-Script, flags were set by a flags statement in an object definition, but were treated as single parameters in functions. In C, the flags parameter is also used in functions for setting or resetting flags:
mypanel.flags |= SHOW;  // C-Script: mypanel.SHOW = ON;
mypanel.flags &= ~SHOW; // C-Script: mypanel.SHOW = OFF; 
This may look scary to beginners, and is indeed less intuitive than the C-Script flag method. Therefore, some macros were defined in acknex.h for setting, resetting, toggling, or testing flags:
#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) { ... }
As an advantage, you can now set or reset several flags in one single statement:
mypanel.flags |= (SHOW|LIGHT|TRANSLUCENT); // note the parentheses
set(mypanel,SHOW|LIGHT|TRANSLUCENT);
 !!  Please note: the old TRANSPARENT flag was renamed to TRANSLUCENT (because TRANSPARENT is already used by the Windows GDI header). Also take care to set the right flags parameter, because entities have more than one: There are also eflags, emask, smask, and flags2 parameters that are unaccompanied by macros (though you could define some):
ENTITY* eSky = { type = "clouds+2.tga"; flags2 = SKY | DOME | SHOW; }

Saving and loading

When you are using dynamically changed global or local pointers in the while() { ... wait(); } loops of functions, be aware that pointers are not saved under lite-C, except for the my and you entity pointers. So you need to make sure that pointers point to the right object when using them within the loop. Entity pointers or engine object pointers can be converted to a handle for being save-proof.

execute

The lite-C compiler has no interpreter function. Therefore, the execute instruction can only be used for calling engine functions with constant parameters. There's a new function var_for_name that displays and modifies the content of a variable or string at runtime. The [Tab] console now uses var_for_name. Enter the name of a global var, int, long, float, or STRING and press [Enter]. Its content will be displayed. You can edit and modify the content by pressing [Enter] again. This is the lite-C code to [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

was replaced by proc_mode = PROC_LATE; before calling wait().

sleep(x)

does not exist anymore - use wait(-x) instead. But take caution: Sleep(x) (with capital 'S') exists and is a standard Windows function that behaves very different than the old sleep()!

Trigonometric functions

In C, the float/double trigonometric functions (sin, cos, tan, asin, acos, atan) use radians instead of degrees for angle values. For degree trigonometry, lite-C offers special var functions: sinv, cosv, tanv, asinv, acosv, atanv. They are equivalent to their sin, cos, tan, asin, acos, atan counterparts in C-Script. The 6 trigonometric functions are overloaded and thus detect automatically whether to should use degrees or radians, depending on whether their argument is a var or a float/double. However, when ambiguities are possible - for instance when calling a trigonometric function with a constant - you have to make a distinction between the float/double and the var versions.
x = sinv(45.0);	// C-Script: x = sin(45.0);

var_info, var_nsave

are both replaced by var. Instead of using var_info, let the variable name end with "_i"; instead of using var_nsave, let the variable name end with "_n".

Skills

Entity and material skills can now use an array (skill[0]..skill[99]) rather than single parameters (skill1..skill100). But the old skill names are still valid. For this there are many #defines in include\compat.h that is automatically included in the code. When using the new array syntax, take care: skill1 is skill[0] and not skill[1]!

Arrays

In C, array inital values are enclosed by {} brackets. Example:
var test[3] = { 1,2,3 };	// C-Script: var test[3] = 1,2,3;

STRING

In lite-C, strings must be given a value when defining them. Otherwise they are only empty string pointers. They have always variable lengths. The syntax for this is now:
//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;
 !!  Character constants that you type between double quotes (like "Here are some characters!") have the type char[] in C, but STRING* in C-Script. Almost all engine functions can take char[] as well as STRING* as arguments, so you normally don't need to care about that difference. However some user DLL functions might explicitely expect a STRING*. Thus you need to define strings for passing arguments to those functions.

TEXT strings

TEXT.pstring is a pointer to a pointer array, so an array index must always be used; and due to the operator precedence, parantheses have now to be set around TEXT.pstring:
//str_cpy(mytext.string,"Test!"); // C-Script style
str_cpy((mytext.pstring)[0],"Test!"); // C style 

FONT

The character size is now automatically calculated from a FONT image. If the image width is divisible by 11, the image is supposed to contain the numbers 0..9 plus space in one row. If the width/height ratio is above 4, it is supposed to consist of 4 rows of 32 characters, otherwise 8 rows of 32 characters. This criteria is met by all bitmap fonts we've seen; otherwise the font image has to be reorganized. The size and type of Truetype fonts are given by an added "#nbi", while n = character size and optionally b = bold and i = italic. The default size is 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 can be defined in a similar way as in C-Script, however there is no separate SKY object. The sky is an ENTITY with the SKY flag set in the flags2 parameter. Example for a sky entity:
ENTITY* eSky = { type = "blood_gorge+6.tga"; flags2 = SKY | CUBE | SHOW; }
For setting view parameters to layer entities, the client_id parameter is now used.

PARTICLE

.. is a different struct than ENTITY, so you can't use the my pointer for particles anymore. Use the PARTICLE* pointer that is passed to any particle function:
function particle_effect (PARTICLE *p)
{
   p.size = 50;
   ...
}

my, you

..are global pointers, so when you call a function to modify them, they are modified on a global scope and not only within the scope of that function. They are still individually preserved during wait().

Debugging

Debugging in SED works the same way as before, but without SED it's a little different:

- Set a breakpoint in the code by placing the comment tag '//!' at the end of a line. Comment breakpoints can only be set in pure lite-C programs (with a main() function, not with WinMain()) and only in the main file, not in an include. They only work after the first frame, so when placing them in the main function, add a wait(1) before.
- Start the engine in window mode with the -debug command line option.

When the breakpoint is hit, the engine stops and the current source line is displayed in the title bar. [Esc] aborts debugging, [F10] steps to the next instruction, [Shift-F10] steps over function calls and [Ctrl-F10] runs to the next breakpoint. Global variables can be observed in a panel or in a SED watch. Local variables can be observed by temporarily replacing them with global variables.

-d command line option

When publishing, lite-C code is compiled to an .exe file that starts a lot faster than a .c script. Thus, #defines can't be changed at runtime anymore because the code can not be modified after compiling. Instead, you can use the command_str for evaluating the command line at run time. The -d command line parameter now directly sets global vars and STRINGs. Examples:
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" 

See also:

Pointers, Structs, Functions ► latest version online