Back

Developer Network
  File Specs
  Source codes
  Tables
  Mailing lists
  Knowledge Base

Other Games

 

 

Descent Developer Network
Descent 1/2

*.DEM specs

How to use these specs

Here are the complete DEM specs! I have written the DEMo-Editor Descent Manager DEMOEDIT/DOS using this knowledge, with which you should be able, for example, to make a DEMo->AVI renderer... If you need any vartype definitions or constants, used in these specs, you can find them here!

The specs are for Descent 2 Full Version, Descent 1 Full Version and Descent 1 Shareware. Differences in the game format between the games are marked as red C-style "#IFDEF" and "#IFNDEF" commands. So here are the definements:

D1SHARE = Descent 1 Shareware Demo format
D1FULL  = Descent 1 Commercial Demo format
D2FULL  = Descent 2 Commercial Demo format

I don't have specs for Descent 2 Interactive Demo, but according to the version numbering, it seems that D2 Interactive demos have the same structure as D2 Commercial demos (the mission_name is "D2DEMO" then)... But this has to be tested, of course!

In the demo format, Destination Saturn is handled as Descent 1 Commercial, Destination Quartzon as Descent 2 Commercial.

eMail author: Heiko Herrmann
Descent Manager DEMOEDIT/DOS - An editor to cut *.DEM files, written by Heiko Herrmann

 

How the demo system technically works

The demo file contains nothing else than a number of events! Descent has 32-51 (dependant of which version you use) different events. In the demo file is only saved what YOU - the player - actually see from YOUR point of view! That means, if you are PLAYER_1 of a 3 player multiplayer game, and PLAYER_2 kills PLAYER_3 in a different part of the mine, and you cannot see their fight, then the ONLY thing, that is actually saved in the demo file, is a) an event ND_EVENT_MULTI_DEATH, with the increased number of deaths of PLAYER_3, b) an event ND_EVENT_MULTI_KILL, with the increased number of kills of PLAYER_2 and c) an event ND_EVENT_HUD_MESSAGE, that lets the HUD display the message "PLAYER_2 killed PLAYER_3" at the top of the screen! Nothing else is saved here... so you have no chance to reconstruct, which weapon did actually kill PLAYER_3, nor can you check out, how many shield PLAYER_2 had left at the end of fight!

That means: You only know YOUR shield, energy, afterburner, weapons and so on... Well, you might wonder, how does Descent know while the playback of demos, what a killed player drops? The answer is, the demo playback routine doesn't know it! It doesn't even know which powerups are where, even if there are still laying in the mine; not picked up by anyone...

So here is the idea of the demo playback routine: Each frame that was recorded is saved extra. Now each frame starts with the position of PLAYER_1 in the mine, the event ND_EVENT_VIEWER_OBJECT! Now Descent knows, which room has to be displayed. Now every single object, that is in this room at this time is listed seperately using the ND_EVENT_RENDER_OBJECT! So if a frame shows 2 enemies, 4 powerups laying down plus 8 laser shots, then (2+4+8=)14 events of type ND_EVENT_RENDER_OBJECT follow the ND_EVENT_VIEWER_OBJECT! Of course the more fights are happening in one frame, the greater is the amount of bytes used for this frame!

After the ND_EVENT_RENDER_OBJECTs there follow events, that are noting actions happened in this frame: killing, changings of shield/energy/ammo/weapon/afterburner, status changes (e.g. collecting/loosing a flag, invulnerability on/off, cloaking on/off, headlight on/off etc.), scoring, getting messages on the HUD (not only from multiplayer games, also messages like "You already have 20 concussion missiles", "Afterburner!", "Ship destroyed" and "Guide-Bot: Finding blue key") and so on... Even if the screen flashes due to being hit, loading the fushion, being flashed or getting a powerup is an own event.

 

The header

The demo begin with the header, which itself is an own event, ND_EVENT_START_DEMO! It also sets the JustStartedPlayback to 1. After that, the ND_EVENT_NEW_LEVEL event is called, which loads also all walls; after that JustStartedPlayback is set back to 0, and the first ND_EVENT_START_FRAME is fired!

In Descent 1 there is no JustStartedPlayback, so it just goes through: ND_EVENT_START_DEMO, ND_EVENT_NEWLEVEL, ND_EVENT_START_FRAME in that order.

 

The OBJECT datatype

Descent saves the action happening in each frame as OBJECTs. Each of the 2D and 3D objects, that are visible in the current frame including the shots, the items, the robots, the other players and the position of the viewer's eyes (yourself!) are saved in one object. The structure of the object type is NOT a fixed size one, it varies and some flags actually are defining the exact object structure of one specific object. Here is the OBJECT structure, that Descent uses for the DEMo files (The constants can be viewn here!):

BYTE object_render_type;
BYTE object_type;
if ((render_type==RT_NONE) && (object_type!=OBJ_CAMERA))
  return;
BYTE object_id;
BYTE object_flags;
SHORT object_signature;
SHORTPOS object;

switch (object_type) {
  case OBJ_HOSTAGE:
    object_control_type=CT_POWERUP;
    object_movement_type=MT_NONE;
    break;
  case OBJ_ROBOT:
    object_control_type=CT_AI;
    #IFDEF D2FULL
     if (object_id!=SPECIAL_REACTOR_ROBOT)
       object_movement_type=MT_PHYSICS;
      else
       object_movement_type=MT_NONE;
    #ELSE
     object_movement_type=MT_PHYSICS;
    #ENDIF
    object_rtype_pobj_model_num=Robot_info[object_id].model_num;
  case OBJ_POWERUP:
    object_control_type=CT_POWERUP;
    BYTE object_movement_type;
    break;
  case OBJ_PLAYER:
    object_control_type=CT_NONE;
    object_movement_type=MT_PHYSICS;
    object_rtype_pobj_model_num=PLAYER_SHIP_MODEL_NUM;
    break;
  case OBJ_CLUTTER:
    object_control_type=CT_NONE;
    object_movement_type=MT_NONE;
    object_rtype_pobj_model_num=object_id;
    break;
  default:
    BYTE object_control_type;
    BYTE object_movement_type;
    FIX object_size;
    break;
}

VECTOR object_last_pos;
if ((object_type==OBJ_WEAPON) && (object_render_type==RT_WEAPON_VCLIP))
  FIX object_lifeleft;
 else
  BYTE b;
#IFNDEF D1SHARE
 if (object_type==OBJ_ROBOT)
   if Robot_info[object_id].boss_flag
     BYTE cloaked;
#ENDIF

switch (object_movement_type) {
  case MT_PHYSICS:
    VECTOR velocity;
    VECTOR thrust;
    break;
  case MT_SPINNING:
    VECTOR spin_rate;
    break;
  case MT_NONE:
    break;
}

switch (object_control_type) {
  case CT_EXPLOSION:
    FIX spawn_time;
    FIX delete_time;
    SHORT delete_objnum;
    break;
  case CT_LIGHT:
    FIX intensity;
    break;
}

switch (object_render_type) {
  case RT_MORPH:
  case RT_POLYOBJ:
    if ((object_type!=OBJ_ROBOT) && (object_type!=OBJ_PLAYER) && (object_type!=OBJ_CLUTTER)) {
      INT object_rtype_pobj_info_model_num;
      INT object_rtype_pobj_info_subobj_flags;
    }
    if ((object_type!=OBJ_PLAYER) && (object_type!=OBJ_DEBRIS))
      for (i=0; i<Polygon_models[object_rtype_pobj_info_model_num].n_models; i++)
        ANGVEC object_rtype_pobj_info_anim_angels[i];
    INT tmo;
    break;
  case RT_POWERUP:
  case RT_WEAPON_VCLIP:
  case RT_FIREBALL:
  case RT_HOSTAGE:
    INT vclip_num;
    FIX frametime;
    BYTE framenum;
    break;
}

 

Polygon data

The data "Robot_info[object_id].model_num" and "Polygon_models[object_rtype_pobj_info_model_num].n_models; i++)" must be get from the HAM file, or rather -if available- the HXM file for that specific level! View the HAM specs and the HXM specs for reading the info...

You do need to know the data to know how many ANGVEC data the object follows...

 

Notes about the demo system of Descent (and known bugs and limitations)
  • Descent 1 and doors
    When you look into event 28 (ND_EVENT_NEW_LEVEL) you will see that no door states are loaded/saved at the beginning of the DEM file. This is why e.g. when you opened the hostage door before starting the demo recording, it will be closed when looking at it in the demo. You will just fly through a closed door. In Descent 2 this was fixed by adding the additional information and the variable "JustStartedPlayback".

  • Descent 2 and hoard
    Hoard was implemented in V1.2 Vertigo Enhanced, and so was a very late implementation. Unfortunately Parallax forgot to implement hoard handling code into the DEMo file. This is why no orbs are counted in the demo; there would be the need of a new event (lets say ND_EVENT_ORB), which would make the demo format incompatible to earlier version of D2, so we would need a version "16". This wouldn't be that unwise, because earlier versions didn't support hoard nor did they have the textures/sounds needed for the hoard mode.
    But things are even worse: The textures are not displayed when playing hoard demos and the sound is totally disturbed. This is a hard bug and would be really a reason for a V1.21 or V1.3! I talked to Parallax before but they said no, they won't release a fix :((...

  • Descent 1 Shareware and multiplayer games
    The support for playing multiplayer demos in D1 Shareware was in a very early state, as you can see in the missing events 32-42 and the missing data in the event 0 (ND_EVENT_EOF): D1 Shareware saves nothing about the players, not even their nicknames or scores.

  • Descent 1 Shareware and ammo restrictions
    Even worse: D1 Shareware does not save the ammo nor the number of remaining missiles. Looks quite funny: You shoot and your cockpit displays "0 homings"; before and after...

 

File versions

The demo files start with a version number:

  • Descent 1 Shareware: version=5, game_type=1, events=0-31
  • Descent 1 Full Version: version=13, game_type=2, events=0-42
  • Descent 2 Interactive Demo: version=15, game_type=3, events=?
  • Descent 2 Full Version: version=15, game_type=3, events=0-50

 

Main demo structure

The main demo now is a couple of events! Each event is initialized by a single byte, which defines the type of event following! Each number from 0-50 defines one event! This continues until ND_EVENT_EOF is fired! Here are the events together with their parameters (for declarations of the variable types click here):

 

  • Event 0, ND_EVENT_EOF:
    no parameters

  • Event 1, ND_EVENT_START_DEMO
    BYTE version; // see above
    BYTE game_type; // see above
    FIX game_time;
    INT game_mode;
    #IFDEF D1SHARE
     if (game_mode & GM_MULTI)
      BYTE team_vector;
    #ELSE
     if (game_mode & GM_TEAM) {
      BYTE team_vector;
      STRING team_name[0];
      STRING team_name[1];}
     if (game_mode & GM_MULTI) {
      BYTE num_players;
      for (i=0; i<num_players; i++) {
      STRING players[i].callsign;
      BYTE players[i].connected;
      if (game_mode & GM_MULTI_COOP) {
      INT players[i].score;
      } else {
      SHORT players[i].net_killed_total;
      SHORT players[i].net_kills_total;
      }
      }
     } else
      INT player.score;
     for (i=0; i<MAX_PRIMARY_WEAPONS; i++)
      SHORT player.primary_ammo[i];
     for (i=0; i<MAX_SECONDARY_WEAPONS; i++)
      SHORT player.secondary_ammo[i];
     BYTE laser_level;
     STRING current_mission;
    #ENDIF
    BYTE energy;
    BYTE shield;
    INT flags;
    BYTE primary_weapon;
    BYTE secondary_weapon;
    JustStartedPlayback=1;

  • Event 2, ND_EVENT_START_FRAME:
    SHORT last_frame_length;
    INT framecount;
    INT recorded_time;

  • Event 3, ND_EVENT_VIEWER_OBJECT
    #IFDEF D2FULL
     BYTE WhichWindow // If WhichWindow = 0: main window, otherwise camera at the bottom
    #ENDIF
    OBJECT obj;

  • Event 4, ND_EVENT_RENDER_OBJECT
    OBJECT obj;

  • Event 5, ND_EVENT_SOUND
    INT soundno;

  • Event 6, ND_EVENT_SOUND_ONCE
    INT soundno;

  • Event 7, ND_EVENT_SOUND_3D
    INT soundno;
    INT angle;
    INT volume;

  • Event 8, ND_EVENT_WALL_HIT_PROCESS
    INT segnum;
    INT side;
    FIX damage;
    INT player;

  • Event 9, ND_EVENT_TRIGGER
    INT segnum;
    INT side;
    INT objnum;
    #IFDEF D2FULL
     INT shot;
    #ENDIF

  • Event 10, ND_EVENT_HOSTAGE_RESCUED
    INT hostage_number;

  • Event 11, ND_EVENT_SOUND_3D_ONCE
    INT soundno;
    INT angle;
    INT volume;

  • Event 12, ND_EVENT_MORPH_FRAME
    OBJECT obj;

  • Event 13, ND_EVENT_WALL_TOGGLE
    INT segnum;
    INT side;

  • Event 14, ND_EVENT_HUD_MESSAGE
    STRING hud_msg; //null-terminated ###

  • Event 15, ND_EVENT_CONTROL_CENTER_DESTROYED
    INT countdown_seconds_left;

  • Event 16, ND_EVENT_PALETTE_EFFECT
    SHORT red;
    SHORT green;
    SHORT blue;

  • Event 17, ND_EVENT_PLAYER_ENERGY
    #IFNDEF D1SHARE
     BYTE old_energy;
    #ENDIF
    BYTE energy;

  • Event 18, ND_EVENT_PLAYER_SHIELD
    #IFNDEF D1SHARE
     BYTE old_shield;
    #ENDIF
    BYTE shield;

  • Event 19, ND_EVENT_PLAYER_FLAGS
    SHORT old_flags;
    SHORT flags;

  • Event 20, ND_EVENT_PLAYER_WEAPON
    BYTE weapon_type; //weapon_type==0: primary weapon
    BYTE weapon_num;
    #IFNDEF D1SHARE
     BYTE old_weapon;
    #ENDIF

  • Event 21, ND_EVENT_EFFECT_BLOWUP
    SHORT segnum;
    BYTE side;
    VECTOR pnt;

  • Event 22, ND_EVENT_HOMING_DISTANCE
    SHORT distance;

  • Event 23, ND_EVENT_LETTERBOX
    no parameters

  • Event 24, ND_EVENT_RESTORE_COCKPIT
    no parameters

  • Event 25, ND_EVENT_REARVIEW
    no parameters

  • Event 26, ND_EVENT_WALL_SET_TMAP_NUM1
    SHORT seg;
    BYTE side;
    SHORT cseg;
    BYTE cside;
    SHORT tmap;

  • Event 27, ND_EVENT_WALL_SET_TMAP_NUM2
    SHORT seg;
    BYTE side;
    SHORT cseg;
    BYTE cside;
    SHORT tmap;

  • Event 28, ND_EVENT_NEW_LEVEL
    BYTE new_level;
    BYTE old_level;
    #IFDEF D2FULL
     if (JustStartedPlaybak)  {
      INT num_walls;
      for (i=0; i<num_walls; i++)
      {
      BYTE walls[i].type;
      BYTE walls[i].flags;
      BYTE walls[i].state;
      SHORT seg->sides[side].tmap_num1;
      SHORT seg->sides[side].tmap_num2;
      }
      JustStartedPlayback=0;
     }
    #ENDIF

  • Event 29, ND_EVENT_MULTI_CLOAK
    BYTE player_num;

  • Event 30, ND_EVENT_MULTI_DECLOAK
    BYTE player_num;

  • Event 31, ND_EVENT_RESTORE_REARVIEW
    no parameters


#IFNDEF D1SHARE
//the following events are only used in D1FULL and D2FULL

  • Event 32, ND_EVENT_MULTI_DEATH
    BYTE player_num;

  • Event 33, ND_EVENT_MULTI_KILL
    BYTE player_num;
    BYTE kills; //either 1 (kill) or 255 (suicide)

  • Event 34, ND_EVENT_MULTI_CONNECT
    BYTE player_num;
    BYTE new_player;
    if (!new_player)
    {
     STRING old_callsign;
     INT killed_total;
     INT kills_total;
    }
    STRING new_callsign;

  • Event 35, ND_EVENT_MULTI_RECONNECT
    BYTE player_num;

  • Event 36, ND_EVENT_MULTI_DISCONNECT
    BYTE player_num;

  • Event 37, ND_EVENT_MULTI_SCORE
    BYTE player_num;
    INT score;

  • Event 38, ND_EVENT_PLAYER_SCORE
    INT score;

  • Event 39, ND_EVENT_PRIMARY_AMMO
    SHORT old_ammo;
    SHORT new_ammo;

  • Event 40, ND_EVENT_SECONDARY_AMMO
    SHORT old_ammo;
    SHORT new_ammo;

  • Event 41, ND_EVENT_DOOR_OPENING
    SHORT segnum;
    BYTE side;

  • Event 42, ND_EVENT_LASER_LEVEL
    SHORT old_level;
    SHORT new_level;

#ENDIF


#IFDEF D2FULL
//the following events are only used in D2FULL

  • Event 43, ND_EVENT_PLAYER_AFTERBURNER
    SHORT old_afterburner;
    SHORT afterburner;

  • Event 44, ND_EVENT_CLOAKING_WALL
    BYTE front_wall_num;
    BYTE back_wall_num;
    BYTE type;
    BYTE state;
    BYTE cloak_value;
    SHORT l0;
    SHORT l1;
    SHORT l2;
    SHORT l3;

  • Event 45, ND_EVENT_CHANGE_COCKPIT
    INT cockpit;

  • Event 46, ND_EVENT_START_GUIDED
    no parameters

  • Event 47, ND_EVENT_END_GUIDED
    no parameters

  • Event 48, ND_EVENT_SECRET_THINGY
    INT truth;

  • Event 49, ND_EVENT_LINK_SOUND_TO_OBJECT
    INT soundno;
    INT signature;
    INT max_volume;
    INT max_distance;
    INT loop_start;
    INT loop_end;

  • Event 50, ND_EVENT_KILL_SOUND_TO_OBJECT
    INT signature;

#ENDIF

 

 

All pages (C) 1996-2000 Descent Network Team
Everything taken from the Descent, FreeSpace, Red Faction and Summoner series games are
Copyright Interplay Productions , THQ Inc. , Parallax Software , Volition Inc. and/or Outrage Entertainment