Authors:

Targeted audience:

Required reading:

Required knowledge:

Explains:

Basic Types

Typedef-ed integral and rational types are used instead of ANSI C types for brevity, clarity, symmetry and portability.

SLONG - 32 bit signed integer
SWORD - 16 bit signed integer
SBYTE - 8 bit signed integer

ULONG - 32 bit unsigned integer
UWORD - 16 bit unsigned integer
UBYTE - 8 bit unsigned integer

INDEX - 32 integer (implicitly signed) - used for indices, counters and counts
BOOL - ordinary int used as boolean - TRUE and FALSE are #defined

PIX - same as SLONG, integer coordinates in pixels
TEX - same as SLONG, integer coordinates in texels
MEX - same as SLONG, integer coordinates in mexels (see Graphics)
COLOR - same as ULONG, color in 32 bit RGBA format

FLOAT - 32 bit IEEE float
DOUBLE - 64 bit IEEE float

ANGLE - same as FLOAT, used for angles (in degrees)
TIME - same as FLOAT, used for time values (in seconds)

CTString

A generic string class. Note difference between CTString and CString (as used by MFC). This is to avoid name clashes. Contains a zero terminated string by value and takes care of all the memory allocation/freeing. Standard operators like '+' and '+=' (concatenation), '==' and '!=' (case insensitive comparison), etc. are implemented. Supports PrintF, ScanF, pattern matching, trimming, loading and saving to binary and text files etc. An empty string always contains one character (the null terminator), so it can always be safely cast to (const char*).

CTFileName

Inherits CTString and adds filename management (FileDir(), FileName(), FileExt(), NoExt() and RemoveApplicationPath_t() functions).
IMPORTANT: Although you may cast the CTFileName to (const char *) and pass it to some function that takes (const char *) input (like strlen()), take care that it will not work with var-arg functions like printf(). CTFileName is 8 bytes in size, and passing it directly to printf-style functions will create an extra argument (usually NULL). Always cast CTFileName to (const CTString &) when passing it to printf-style functions.

Double-Linked Lists

CListNode and CListHead are Used for double linked lists. Add one CListNode type member to class that will be added to a list, and one CListHead to a class that will hold the list (or instance a local CListHead if you need a temporary list).
Use CListHead::AddTail() / CListHead::AddHead() to add members to list, and CListNode::Remove() to remove members from list.
For iteration use following macros

FOREACHINLIST(baseclass, member, head, iter)
for normal iteration

FORDELETELIST(baseclass, member, head, iter)
if current element may be destroyed during iteration

Streams

All loading and saving goes through a custom set of stream classes. The base class is CTStream. It supports binary and text reading/writing, chunk ID management, seeking, and filename dependency dictionary.
Use Read_t() to read a block of data from a stream and Write_t() to write it. Standard '>>' and '<<' operators are overloaded for most simple data types including builtin types, strings and filenames.
Dictionary is used when one file contains a lot of dependencies to other files, which are often repeated. This happens for example with textures, models etc. in world files. Then all those filenames are saved in a 'dictionary' only once for each distinct filename. Dictionary must be specially enabled for a certain stream, or one part of stream. If it is not enabled, filenames are saved/loaded directly.
CTFileStream is a CTStream that resembles a file on disk.
CTMemoryStream is a CTStream that resident in memory.
Engine uses memory mapping for fast stream handling. On Win32, memory mapping is handled by the engine itself using a custom 'access violation' exception handler. In order for that to be possible, base of the call stack in each thread must be bracketed with a CTSTREAM_BEGIN/CTSTREAM_END block. If some thread is not bracketed that way, you cannot load/save files in that thread. That is the case with the timer thread.
On Linux, memory mapping is automatically handled by the system.
CTFileStream will refuse to open for any file that is outside of engine dir. To suppress such behaviour and temporarily enable usage of absolute paths, use global functions:

  IgnoreApplicationPath()
UseApplicationPath()

Additional handy global functions:

  FileExists()
GetFileTimeStamp_t()

CSerial and the Stocks

'Stocking' is an automatic system for preventing redundant loads and extra memory usage. A 'Stock' is an object that keeps track of all loaded objects of one class, and takes care that they are not loaded twice. Objects derived from CSerial can be put in stocks. Currently used stocks in the Serious Engine are those of:

  light animations (CAnimData)
entity classes (CEntityClass)
models (CModelData)
sounds (CSoundData)
texture (CTextureData)

You obtain an object from the stock by calling Obtain_t() and mark that you don't need it by calling Release(). The stock then loads the object if it is not already loaded and maintains a used count. Even when an object is not used any more, it is still kept in the stock, until FreeUnusedStock() is called. This way, when a user loads a saved game, or restarts a level, almost nothing is loaded, since all the textures, models, sounds, etc. are already loaded.

Auto Animation Support

Autoanimation means that an animatable object (derived from CAnimObject) is not in fact animated in real time, i.e. no CPU cycles are spent to update its current frame number through time, but rather current frame can be calculated on demand. I.e. when some part of code wants to know the current frame, it is calculated from its current parameters. E.g. when a model is rendered, the rendering system asks the CModelObject (derived from CAnimObject) what is its current frame. If the model is not rendered (because it is not visible), no time is wasted.

Current frame is calculated from the following parameters:

Each CAnimObject is linked to its CAnimData. CAnimData represents a class of animatable objects like e.g. a Beheaded model. It is usually loaded from a file and has all the data regarding the number of animations, all of their parameters and a list of frames for each anim. All CAnimObjects in memory always link to the same CAnimData. (This is accomplished using 'stocking' for all CSerial derived classes - and CAnimData is derived from CSerial).
For manipulation with CAnimObject derived objects, you will usually use following functions:

  SetData()
SetData_t()
PlayAnim()
GetCurrentAnimLength()
GetAnimLength()
IsAnimFinished()
GetPassedTime()

Error Reporting

There are some global functions for reporting an error to user:

ThrowF_t()
formats a string in a printf style and throws an exception with it

FatalError()
formats a string in a printf style, reports a fatal error to the user with that string, prints it to the console, then dumps the console to FatalError.log and quits program.

WarningMessage()
First, formats a string in a printf style.
If the console variable 'con_bNoWarnings' is set, just prints it to the console.
If it is not set, a popup message with the warning message is shown to the user, and the program pauses until the user presses Ok button.

Console

Basic meaning of the word console in Serious Engine is just a buffer where strings are printed. You can use CPrintF() and CPutString() to print to the console. Functions CON_NumberOfLinesAfter(), CON_GetLastLine(), and CON_GetBuffer() are then used in Game dll to actually show that on screen.

Shell

Shell is the part of engine that handles what is commonly refered to as 'console variables', but should more correctly be called 'shell symbols'.
You can declare shell symbols using DeclareSymbol(). To execute statement(s) from the shell, use Execute().
The Game dll itself uses that function to execute the statements that you type in the console in Serious Sam.
Declaration syntax for declaring a new symbol is following:

declaration
: declaration_qualifiers type_specifier identifier pre_func_opt post_func_opt ';'
| declaration_qualifiers type_specifier identifier '[' expression ']' pre_func_opt post_func_opt ';'
| declaration_qualifiers type_specifier identifier '(' type_specifier ')' ';'

declaration_qualifiers
: /* nothing */
| declaration_qualifiers 'const'
| declaration_qualifiers 'user'
| declaration_qualifiers 'persistent'
| declaration_qualifiers 'extern'

type_specifier
: 'FLOAT'
| 'INDEX'
| 'void'

pre_func
: 'pre' ':' identifier

post_func
: 'post' ':' identifier

The first declaration is for scalars, second one is for arrays and the last one for functions.


persistent - denotes that the symbol will be saved/restored when the game exits/restarts
user - denotes that the symbol will be visible in ListSymbols() and accessible through 'tab'-cycling
const - the symbol cannot be changed from the shell
extern - the symbol is declared from a script file, not from executable code

You declare a new symbol by passing a shell syntax declaration as the first parameter to the DeclareSymbol() function, while you give the address of the C++ variable or function as the second parameter.

Pre- and post- functions are functions that are executed before and after a symbol is changed. If pre function returns FALSE, the symbol cannot be changed. You can use same function for more then one symbol, since the address of the symbol is passed as input parameter. Prototypes for pre and post functions are:


BOOL pPreFunc(void *);
void pPostFunc(void *);