Authors:

Targeted audience:

Required reading:

Required knowledge:

Explains:

A Bit About the Ecc

Entity Class Compiler (Ecc) is a translator (a compiler-like program) which compiles '.es' with the code you write into '.cpp' sources that are later compiled by ordinary C++ compiler to generate binary code.

It does not do a full C++ syntactic and semantic check on the source. It first performs lexical analysis, breaking it into tokens. The parts of the declaration and implementation that are specific to the '.es' syntax, like events, properties, components and procedures, are analyzed syntactically and semantically and translated to C++ code and declarations. The parts that are purely C++, like function calls, pure C++ loops without waiting, expresions etc., are only checked in global, for matching brackets and similar, and copied verbatim.

Ecc reports its errors in a format that is compatibile with VisualC++ (and should be compatibile with most ANSI compilers) so that Developer Studio can automatically show the line with the error just as it does with the C++ compiler.

It also generates #line directives. That way, when the code you write is syntactically and semantically correct with respect to the .es grammar, but cannot be compiled with C++ compiler (for example, mismatched types or operators in an expression, undeclared variable, etc.), the C++ compiler reports the error inside the .es file instead inside the generated C++ file. Also, when you debug the compiled code, the original .es file is shown.

General Format of Entity Source File

Each entity source file has following elements:

1) the class's unique identifier

First thing in the file must be an integer in the range 1-32767, the identifier of the class. It is used in the generation of identifiers for events, properties, components and states declared in the class. If you make two classes with same identifier, they will work, but might have conficts if you try to derive one from another, or use one another's events etc. The numbers below 100 are reserved by the engine. For the SeriousSam, numbers between 100 and 999 are always used by the Serious Sam entities, while custom MODs can use the numbers above 1000. For come other application, the way of assigning numbers above 100 can be different.

2) an optional C++ block

Here, you can optionally insert a block of completely free C++ declarations/statements/preprocessor directives. The block must be bracketed with '%{' and '%}'. You will usually have to put your #includes and #defines here, and you can also add some module or global functions and variables. The part inside the block is always copied verbatim. You can write any C++ code there.

Example:

%{
#include "StdH.h"
#include <Entities/Common/Flags.h>
#include <Help/SDK/Models/Blob/Blob.h>
%}

3) 'uses' list

Like you #include header files at the start of each C++ file, here you declare that your class will use some other classes or their enums or events. The format of each 'uses' statement is:

uses_statement : 'uses' string ';'

The string is the filename of the class's .es file, without the extension.

If you use the class only in implementation, you don't need to add the 'uses' statement for it. You can just add its generated header file to the #includes in the above C++ block. That can speed up your compilation time. 'Uses' adjusts the generated header so that each other class that references this includes its header file. It is needed if, for example, one of this class's properties is an enum declared in the other class.

Example:

uses "Entities/EnvironmentMarker";
uses "Entities/WatchPlayers";

4) 'enum' and 'event' declarations list

Here, you declare enums and events for this class. The order is arbitrary, and you can mix enums and events any way you like.

Each event is declared like this:

event_declaration : 'event' identifier '{' event_members_list '}' ';'

where the identifier is the name of the event type, like ETimer, EDamage, or EStartRunningAway, etc.

Event members are separated with a comma ',' and each event can have zero or more members in format:

event_member : any_type identifier

Examples:

event ETimer { // timer elapsed
};
event ETouch {
CEntityPointer penOther, // other entity
BOOL bThisMoved, // if this entity has touched other entity
FLOATplane3D plCollision, // plane of collision
};

Each enum is declared very similary to C++ enums, but with the change that each value has its name:

enum_declaration : 'enum' identifier '{' enum_values_list '}' ';'

Enum values are separated with a comma ',' and have following format:

enum_value : int identifier string

The integer is the value of the identifier, and the string is its name as seen by level editor in SeriousEditor.

Example:

enum DamageType {
1 DMT_EXPLOSION "Explosion", // caused by dynamites, rockets and other ordinary explosives
2 DMT_PROJECTILE "Projectile", // caused by projectile (non exploding)
3 DMT_CLOSERANGE "Close range", // caused by close range weapon (chainsaw, head-saw, ...)
};

5) an optional C++ block

Another block same as the one before, just that this one can freely use the above declared events and enums. It is recommended that you use this one for everything else except #includes.

6) class declaration

The last part, and usually largest part of the source is the class declaration itself. The syntax layout of the class declaration is the following:

class_declaration :
(1) 'class' identifier ':' identifier '{'
(2) 'name' string ';'
(3) 'thumbnail' string ';'
(4) ('features' features_list ';' )opt
(5) 'properties' ':' property_declaration_list
(6) ('{' internal_properties '}')opt
(7) 'components' ':' component_declaration_list
(8) 'functions' ':' function_list
(9) 'procedures' ':' procedure_list
'}' ';'

Class Features

Some special features can be implemented by Ecc without the need for you to write code for it. It usually means that Ecc will automatically add some functions, or variables or adjust some class settings.

Features declaration consists of the 'features' keyword followed by a comma separated list of strings. Each string can be one from the following list:

- AbstractBaseClass - means that the class will be only a base class for deriving other classes from it. It can never be instantiated.

- IsTargetable - makes entities of this class targetable. That means that they will appear on the list of possible targets when changing an CEntityPointer property in the SeriousEditor.

- HasName - implies that the class has a string property named 'm_strName' which is shown as the name of the entity in SeriousEngine. You must declare the property manually.

- HasDescription - implies that the class has a string property named 'm_strDescription' which is shown as the description of the entity in SeriousEngine. You must declare the property manually.

- HasTarget - implies that the class has a CEntityPointer property named 'm_penTarget' which is the default entity pointer property considered if no other is selected. You must declare the property manually.

- ImplementsOnInitClass
  ImplementsOnEndClass
  ImplementsOnPrecache
  ImplementsOnWorldInit
  ImplementsOnWorldEnd
  ImplementsOnWorldTick
  ImplementsOnWorldRender
  - these declare the fact that the class has one of the OnXXX functions implemented in its C++ block.

Class Properties

Entity properties are member variables which have additional support for editing in SeriousEditor, automatic loading/saving and copying etc. They are declared in a comma-separated list, each property with this syntax:

property_declaration :
integer property_type identifier (string (char)opt ('COLOR' '(' color ')') opt )opt ('=' expression)opt

The integer is the property identifier. Each property within one class must have one unique identifier in the range 0-255. These identifiers are used for saving and loading. You should never change the number given to each property. You may rename the property's symbolic name in code, or change its name for SeriousEditor, but if you change the number, the value of that properties in all saved worlds will be lost.

After the property number follows the property type, same as it would be in C++. Following types are supported so far:

enums - any enum declared in this .es file, or those included with 'uses' statements
CTString - a string
CTStringTrans - a string that will be translated during localization
CTFileName - a filename - automatically generates a dependency
CTFileNameNoDep - a filename that does not generate a dependency
BOOL - simple boolean
COLOR - edited using color editing control in SeriousEditor
FLOAT - float typed in edit box
INDEX - integer typed in edit box
RANGE - same as float, but can be visually edited as range in SeriousEditor
CEntityPointer - pointer to some other entity
CModelObject - a model object
CAnimObject - an anim object
CSoundObject - a sound channel for playing sounds
CPlacement3D - placement (cannot be edited)
FLOATaabbox3D - axis aligned bounding box
FLOATquat3D - a quaternion (cannot be edited)
ANGLE - same as float
ANGLE3D - 3 angles, can also be edited as a direction vector
FLOAT3D - 3 floats
FLOATplane3D - a plane - cannot be edited
ILLUMINATIONTYPE - same as index, edited with illumination drop-down
ANIMATION - same as index, edited with animation drop-down

After the type there is property identifier, same as in C++ declaration.

Optional name of the property follows. This name is used to represent the property in SeriousEditor. If the property does not have a name, or if the name is an empty string, the property will not show in editor. If there is a name, you may add a shorcut key (one single-quoted character).

CEntityPointer properties may also have a color assigned. The color is then used to render arrows showing the pointers.

Finally, each property has a default value. Each property must have a default value. Some property types (like CEntityPointer, CSoundObject, CAnimObject, etc.) cannot have default values assigned by the user, but they have implicit defaults that are always same. E.g. CEntityPointer properties are always NULL by default.

Examples:

2 CEntityPointer m_penTarget "Target" 'T' COLOR(C_dGREEN|0xFF),
1 CTString m_strName "Name" 'N' = "Marker",
15 CTString m_strDescription = "",

Internal Class Properties

At the end of the property block, you may insert internal properties. You do so by bracketing them with '{' and '}'. Internal properties are just like a C++ block. They are not handled by the engine, but rather represent member variables that the class will take care of itself. Do not use these unless you are absolutely sure what you are doing.

If you use internal properties, you must be sure that they do not need loading and saving, or you must do it yourself by overriding Read_t() and Write_t() functions.

Internal properties are not visible or editable in SeriousEditor.

Examples:

{
// these are not saved via the property system

CPlacement3D en_plLastPlacement;
CListNode en_lnInMovers;
CBrushPolygon *en_pbpoStandOn;
}

Class Components

Class components are declared in a comma separated list after the 'components' keyword. Each component has this syntax:

component_declaration : integer component_type identifier string

The integer is an ID in the range 0-255. These, you can renumber any way you like, the numbers are never saved/loaded, they are just for some internal reference.

The component type can be: 'model' for a model (.mdl) file, 'texture' for a texture file (.tex), 'sound' for a sample (.wav or .mp3), or 'class' for another class (.ecl).

Examples:

1 model MDL_ROCKET_EXPLOSION "Models\\Effects\\ExplosionRocket\\ExplosionRocket.mdl",
3 texture TXT_ROCKET_EXPLOSION "Models\\Effects\\Explosionrocket\\Texture.tex",
10 sound SOUND_EXPLOSION "Sounds\\Weapons\\_Explosion02.wav",
5 class CLASS_BASIC_EFFECT "Classes\\BasicEffect.ecl",

Class Functions

Functions are just ordinary C++ functions with some minor limitations on declaration (they cannot be declared as pure virtual, they cannot be var-args etc.).

The syntax is following:

function_implementation :
('export')opt ('virtual')opt return_type ('~')opt identifier '(' parameters_list ')' ('const')opt
'{' statements '}'
(';')opt

You can see that this is similar to C++ function declarations. Only extension is the 'export' keyword which declares that the function will be exported from the package DLL.

Note that there is no separation between declaration and the implementation. Ecc generates the header file for you, so you do not need to take care of forward references.

Class Procedures

Procedures are similar to the functions in appearance, but are functionally completely different. See EntitiesBasics for the explanation of procedures and state stack.

The syntax of one procedure is following:

procedure_implementation :
identifier '(' event_specification ')' (':' identifier '::' identifier)opt
'{' statements '}' (';')opt

The declaration line is quite different from the one used for functions. First, there is no return tyep. The procedures can return only events, and they can return events of any type.

The only input parameter specified to a procedure is event. The event specification can be empty, with type only, or with type and variable name. If there is no type, the type of event is assumed to be EVoid. If there is no variable name, it means that the event must be of given type, but that you don't care about its contents. Whether the actual type of event passed when calling the procedure matches the one in the declaration, it is checked on run-time, not on compile time. It the types are not same, an ASSERT will fail.

After input parameters, you may put an optional override. Procedure overriding is an advanced feature explained in more detail in EntitiesAdvanced.

The body of a procedure is similar to the body of a function. Usual C++ constructs like 'if' statements and 'while' loops are supported. Additional syntactical constructs for waiting, calling, jumping etc. are added. These two can be intermixed with only a few limitations.

Procedures that do not have .es specific constructs behave same as functions, just that they have a slightly greater call-overhead, and cannot be called as normal functions.

IMPORTANT: Waiting constructs cannot be nested within one another (altough the can be nested in ordinary C++ constructs and vice versa). Also, if you can nest a 'wait' inside a loop, but not inside a two-level (or more) deep loops.
IMPORTANT: You may not use one-statement blocks without brackets.

Example:
Bad: if (a>b) CPrintF(""a>b);
Good: if (a>b) { CPrintF(""a>b); }

Conditional Statements and Loops

'If' and 'if-else' statements can be used just as in C++, and waiting constructs can be put inside them. If you do a chained 'if-else-if' construct, you cannot end it with an else. Use "} else if (TRUE) {" instead of just "} else {". This is a limitation of the Ecc parser.

'While' and 'do-while' can have waiting constructs within them, but only to level one. 'For' loop cannot have a wait construct inside it.

Waiting Constructs

The foundation of the functionality of a class procedure is the 'wait' construct.

Its syntax is following:

statement_wait
: 'wait' '(' (expression)opt ')' '{' handlers_list '}'

If the expression is not present, the procedure will wait forever, and only trap events. If there is an expression, it must be of the 'float' type (or convertible), and represents the number of seconds to wait. After the timer elapses, an ETimer event is sent to the entity, and one of the handlers should capture it and stop waiting.

When the waiting initally begins, an EBegin event is sent to the entity. You can do initialization or something similar here. It is usually used for 'call' statements. You can later simulate a restart of the wait by sending the EBegin yourself.

IMPORTANT: You should always handle EBegin event if only just to ignore it. Otherwise, it might be cached by some of the callers of this procedure!

Each handler is of the following format:

(1) 'on' '(' event_specification ')' ':' '{' statements '}' (';')opt
or
(2) 'otherwise' '(' (event_specification)opt ')' ':' '{' statements '}' (';')opt

Event specification can contain the event type and the event variable, or only the event type. If the variable is not present, it is assumed that you are not interested in the contents of the event data.

(1) If the event of given type is received during the wait, the statements of the handler are executed. The statements must in the end do either 'call', 'jump', 'stop', 'resume', 'pass' or 'return'.

(2) If none of the handlers before this one handles some event, it is passed to the 'otherwise' handler. This is similar to 'default' in C++ switch construct.

Simplest ways to end an event handler are 'stop', 'resume', or 'pass':

1) 'stop' means that the waiting stops and the part of the procedure after the wait construct continues to execute.
2) 'resume' means that the event has been handled and that the waiting continues on.
3) 'pass' means that you do not want to handle the event, and it will be passed to the next procedure on the call stack (its state is below this one on the state stack).

Example:
// wait for some time
wait(GetWaitingTime()+1.0f) {
on (EBegin) : {
resume;
}
// when damaged
on (EDamage eDamage) : {
// if only minor damage
if (eDamage.fAmount<10) {
// ignore
resume;
// if serious damage
} else {
// let higher level procedure handle it
pass;
}
}
// when time expires
on (ETimer) {
// stop waiting
stop;
}
}

More advanced ways to end an event handler are 'call', 'jump' and 'return';

1) 'call' means that you want to call another procedure (i.e. push its state above this one on the state stack), and the called procedure will continue executing, while this wait can still capture the events which the called procedure misses.

Syntax of a 'call' is this:

'call' jumptarget '(' event_expression ')' ';'

The jumptarget is the name of the called procedure (can be in class::procedure format if overriding is used). Event_expression is the event that will be sent to the called procedure.

2) 'jump' means that you want to jump to another procedure (i.e. make its state replace this one on the state stack). The new procedure continues executing instead this one, and will never return here. All the other procedures below on the stack still remain, and when the new one returns, it will return to the one that called this one.

Syntax of a 'jump' is this:

'jump' jumptarget '(' event_expression ')' ';'

3) 'return' means that you want to return to the procedure below this one on the state stack.

Syntax of a 'return' is this:

'return' (event_expression)opt ';'

The event_expression gives the event that the procedure returned to will execute immediately. If no event is given, EVoid event is sent.

IMPORTANT: While 'jump' and 'return' can be used anywhere inside a procedure, 'call', 'stop', 'resume', and 'pass' can be used only in event handlers.

Example:

// wait forever
wait() {
// initially
on (EBegin) : {
// run around
EStartRunning eStartRunning;
eStartRunning.vDestination = m_vTarget;
call RunAround(eStartRunning);
}
// when damaged
on (EDamage eDamage) : {
// switch to fighting
jump Fight();
}
// when touched
on (ETouch) {
// abort running and return to caller
return EStop();
}
}

Automatized Waiting

The 'wait' construct offers complete control over event handling, but most usually, you will want only to do three different things: either just wait for a specified amount of time, just wait for a specified event, or call another procedure and wait until it returns. To simplify those tasks, there are specific statements: 'autowait', 'waitevent' and 'autocall'.

1) syntax of the autowait is:

'autowait' '(' (expression)opt ')' ';'

If the expression is not given, the wait will be forever, if it is given it will be for the specified ammount of time.

autowait(someexpression);

is same as:

wait(someexpression) {
on (EBegin) { resume; };
on (ETimer) { stop; };
}

Example:

/* This just waits for 5 seconds. Any events occuring in that interval will
be passed to procedures below on the stack. */
autowait(5.0f);

2) syntax of the waitevent is:

'waitevent' '(' ')' identifier (identifier)opt ';'

The identifier after the parentheses defines event type that will break the wait. Optional second identifier defines the variable where the event will be stored. If you are not interested in the event data, you don't have to give a variable name.

waitevent () ETouch eTouch;
somecode...

is similar to:

wait() {
on(EBegin) : { resume; }
on(ETouch eTouch) {
somecode...
stop;
}
};

3) syntax of autocall is:

'autocall' jumptarget '(' event_expression ')' identifier opt_eventvar ';'

The jumptarget is the name of the called procedure (can be in class::procedure format if overriding is used). Event_expression is the event that will be sent to the called procedure.

The identifier after the parentheses defines event type that will break the wait. This is usually the one that is returned from the called procedure. Optional second identifier defines the variable where the event will be stored. If you are not interested in the event data, you don't have to give the variable name.

autocall StartRunning(EStart()) ETouch eTouch;
somecode...

is similar to:

wait() {
on(EBegin) : {
call StartRunning(EStart());
}
on(ETouch eTouch) {
somecode...
stop;
}
};