|
 |
Descent Developer Network Descent 1/2
*.DEM 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 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.
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;
} |
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...
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
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
|
|
|