Engine framework

The engine is written mostly in (GNU) C, with some bits of ARM assembler. Execution starts in main() in wimp.c:

  1. stderr buffering is disabled, and the debug spool is initialised.
  2. The input handler is initialised
  3. The WIMP interface is initialised
  4. If an alternate gameconfig file was specified via the command line, its name is now remembered for use
  5. The user's config file is loaded
  6. The main WIMP poll loop is started

On exit from the WIMP poll loop:

  1. An exit message is sent to the debug spool
  2. The sound system is stopped (reset)
  3. The WIMP mode is restored (if previously saved)
  4. main() exits with return value 0

The WIMP poll loop

The WIMP poll loop is minimal, merely continual calls to LimpX_Poll() until the abort flag is set. All WIMP events are propagated to the code via the handlers registered with LimpX.

Screen mode save/restore

Because the WIMP doesn't save and restore extended screen modes properly (i.e. those specified with a mode specifier block instead of mode number), DeathDawn handles this itself, using the wimp_savemode() and wimp_restore() functions. In addition to the screen mode, DeathDawn also saves and restores the Tab key setting (OS_Byte 219) and the cursor editing mode (OS_Byte 4).

Error handling

WIMP errors can be reported via the use of the wimp_error(char *str,int quit) function. If quit is true, the game will exit (via exit()) once the user has confirmed the error. Note that no environment management is performed by wimp_error, so the function is unsafe to call from within the main game loop.

For OK/cancel confirmation 'error' messages, wimp_confirm() can be used instead. This will return 1 for OK or 0 for Cancel. Again, this function should only be called from within the WIMP.

Game code

There are three valid ways of entering the main game loop:

  1. Via playgame() directly, if a valid savegame is in memory
  2. Via newgame(), if no valid savegame is in memory, or you want to discard the current game
  3. Via loadgame(), if a save file is to be loaded

Save game validity is indicated by the gameok flag. Additionally, the gamenotsaved and saveable flags are used to indicate whether the game hasn't been saved, and whether the game is currently saveable (as specified by the mission script).

newgame() operates as follows:

  1. If a valid game is loaded and not saved, it asks for confirmation of starting a new game via wimp_confirm().
  2. reset_engine() is called to reset the various subsystems of the game engine
  3. It attempts to start the graphics system; if this fails, the game returns to the wimp (via return_to_wimp()) and a non-fatal error is reported via wimp_error().
  4. The core game state variables are reset to their initial values: newgameflag and gameok are set to 1, while other variables (gametime, saveable, score, paused, etc.) are initialised to 0.
  5. The gameconfig script is loaded via load_script()
  6. If the gameok flag is still true, gamenotsaved is set to 1 and newgameflag to 0, and playgame() is entered.
  7. Else, return_to_wimp() is called and the function exits immediately.

playgame() operates as follows:

  1. It attempts to start the graphics system; if this fails, the game returns to the wimp (via return_to_wimp()) and a non-fatal error is reported via wimp_error().
  2. It starts the sound system
  3. The rungame and gamenotsaved flags are set to true
  4. A time measurement is taken, for calculation of gametime
  5. While rungame is nonzero, the main loop executes
  6. On exit from the main loop, return_to_wimp() is called

The first half of loadgame() is essentially the same as the content of newgame(), except that newgameflag is set to 0 instead of 1. The second half of the function is a linear set of function calls to load each section of the savegame (in the identical order to which the sections were saved), with occasional error checks for appropriate operations. At the end of the function, playgame() is entered if the load succeeded, else return_to_wimp() is called to ensure the engine is shutdown properly.

The main loop is structured as follows:

  1. If the loading flag is set, it is cleared, and a fresh clock time is taken. This ensures loading screens don't eat game time.
  2. The frame time is calculated, by subtracting the current clock time from the previously stored time. The frame time is capped at 20cs to ensure the game runs sensibly during any patches of low framerate. A new clock measurement is also taken, ready for the next frametime calculation.
  3. The input manager is called, to get the player's input for the current frame.
  4. If the game is paused, the frametime is set to zero; else, per-frame processing is performed:

    1. The force of gravity for this frame is calcuated.
    2. Pedestrians are processed.
    3. Cars are processed.
    4. Objects are processed.
    5. Timed mission script events are processed.
    6. Other, miscellaneous tasks are processed (currently, none).
    7. gametime is increased by frametime.
  5. The camera location is updated.
  6. The main screen content (map, peds, cars, objects) is rendered.
  7. If the game is paused, the pause music is processed, and the pause menu rendering & input function is called (dopausemenu())
  8. Else, the main player controls are processed - radio control, pause, returning to desktop, and viewing the map. The ambient radio track is also processed if the player isn't in a vehicle.
  9. The current sound list is processed via sounds_activate(), and the radio processing is performed via radio_continue().
  10. The screen banks are swapped.

Auxiliary functions

These other functions form part of the game core and are integral to its operation:

reset_engine: Resets the various subsystems of the engine. 'reset' typically means to make the subsystem dormant; i.e. stopping any interrupt-driven callbacks, freeing any allocated memory, and resetting state variables.
graphics_start: Calls wimp_savemode() followed by mode_enter() to switch from the WIMP to the configured gameplay mode. Does nothing if already in the gameplay mode.
return_to_wimp: The only safe way to immediately return to the WIMP once the graphics system has been initialised. Flushes the keyboard, stops sound interrupts, exits fullscreen mode, restores the WIMP mode, and stops the radio. Radio playback is stopped last, as the CDFS CD stop SWI can block for a second or two. Stopping it last gives the user some confirmation that something has happened, as the screen mode will have changed beforehand. Function does nothing if the graphics subsystem isn't active.
showmap: A blocking function to display and manipulate the ingame fullscreen map.
dopausemenu: Rendering and input handling for the pause menu.
drawhud: Renders the HUD.
loading, loading_update: Draws the loading screen, or updates the message if the screen is currently active.
loadgame, savegame: Linear functions to load and save the game state from a savefile.
drawscreen: Main rendering function. Draws the map, cars, peds, objects, messages (and processing), and HUD.

TODO:
DOCS: UNKNOWN: Add links to definitions of stuff
CODE: UNKNOWN: Fill in/remove remaining stub functions in game.c
CODE: UNKNOWN: Error handling when loading stuff is dodgy? Game may crash when trying to load again
DOCS: LONG: Design highscores system
MUST-CODE: MED: Loading bar on loading screen? Will probably need some form of progress indicator for map generation, etc. LOADING command can specify number of LOAD* commands that will follow, and bar can be split into appropriate number of sections. Each LOAD command will first count how many files match the wildcard, thus splitting bar further and advancing one stage per file
CODE: UNKNOWN: Remove UI path, as suggested in initial design doc?
CODE: MED: Cause errors for mistakes in ped/car type files, simpscript failing to find an INCLUDE, etc.
MUST-CODE: QUICK: Config UI for altering message_speed value
MUST-CODE: MED: Better error handling. Return to wimp, display error message (just generic or actual error text?), etc. Maybe move the error handler out of sndbuf and make it handle everything.
DOCS: UNKNOWN: Work out stderr solution. Leaving it unredirected fools game into thinking it's not in the wimp; disabling startup message will produce screen spam due to debug messages. Redirecting to file/null causes WIMP error boxes to be supressed when an uncaught error occurs. So just disable debug output for all builds that don't redirect stderr? Maybe introduce command line switch to enable debug output? Or use improved error handler to trigger error message for us? etc.
DOCS: UNKNOWN: Possible to use AppAcc for something? http://homepage.ntlworld.com/rik.griffin/appacc.html
DOCS: UNKNOWN: There's a lot of definition files (cars, peds, zones, etc.) that all share similar code for loading & storing. Maybe write generic manager code that handles all that stuff? Can have one big structure containing a cache of all the files, a type flag for each file, and getter functions that return the file of the right type & name, optionally loading it or calling a callback function, callback functions to deal with loading the file in the first place, etc. i.e.:

#ifndef _RES_H
#define _RES_H

typedef struct {
	int flags; /* Can force load, etc. */
	int path; /* Path type number */
	int (*canflush)(void *ptr); /* True if we can flush this */
	void (*free)(void *ptr); /* Delete this */
	void (*load)(char *path,char *leaf); /* Load file */
} restype;

void res_reset(); /* Get rid of everything */
void res_flush(); /* Get rid of what can be gotten rid of */
void red_load(int type,char *path,char *leaf); /* Load a file */
void *res_get(int type,char *name); /* Get ptr to loaded file (possibly loading it if not loaded already) Possibly increasing refs, if supported */
void res_free(int type,void *ptr); /* Decrease refs for file */

void res_add(int type,char *name,void *ptr); /* Callback for use by resource type code; adds an entry to the res list */

#endif

MUST-CODE: MED: Cardefs aren't getting reloaded when starting a new game? Possible resource leak? Game was trying to use bad sound effects one time, so probably cardefs to blame.
MUST-CODE: UNKNOWN: Memory leak somewhere in gameplay?
MUST-CODE: MED: Check for any instances where chars are used to store map coords - there's no reason to limit the engine to 256x256!
MUST-CODE: MED: Fix bugs to do with starting new games
MUST-DOCS: LONG: Improve stronghelp doc pages to have better layouts (add more sub-pages to list cardef/peddef/etc. attribute details instead of listing them all on the main pages?)
MUST-CODE: MED: Code to handle running out of memory!
???
Profit!