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