2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Demo/Demo.cpp $
17 * Revision 1.3 2002/06/09 04:41:16 relnev
18 * added copyright header
20 * Revision 1.2 2002/05/07 03:16:43 theoddone33
21 * The Great Newline Fix
23 * Revision 1.1.1.1 2002/05/03 03:28:08 root
27 * 6 8/26/99 8:51p Dave
28 * Gave multiplayer TvT messaging a heavy dose of sanity. Cheat codes.
30 * 5 6/16/99 10:20a Dave
31 * Added send-message-list sexpression.
33 * 4 4/16/99 5:54p Dave
34 * Support for on/off style "stream" weapons. Real early support for
35 * target-painting lasers.
37 * 3 3/29/99 6:17p Dave
38 * More work on demo system. Got just about everything in except for
39 * blowing ships up, secondary weapons and player death/warpout.
41 * 2 3/28/99 5:58p Dave
42 * Added early demo code. Make objects move. Nice and framerate
43 * independant, but not much else. Don't use yet unless you're me :)
50 #include "missionload.h"
53 #include "freespace.h"
56 #include "gamesequence.h"
57 #include "systemvars.h"
59 #include "missionmessage.h"
60 #include "missionparse.h"
64 // -----------------------------------------------------------------------------------------------------------------------------
68 CFILE *Demo_file = NULL;
71 #define DEMO_DEFAULT_FPS 15
72 int Demo_fps = 1000 / DEMO_DEFAULT_FPS;
74 // timestamp for frame dumping
78 float Demo_missiontime = 0.0f;
80 // buffer for reading and writing demo stuff
81 #define DEMO_BUF_SIZE 32768
82 char *Demo_buf = NULL;
85 // # of events posted for this frame
86 int Demo_frame_events = 0;
88 // current offset into the demo file - only used for playback
89 int Demo_cur_offset = -1;
92 #define DEMO_VERSION 2
94 // an error reading or writing the demo file
95 int Demo_error = DEMO_ERROR_NONE;
97 // all strings read out of the demo file must be no longer than this
98 #define DEMO_STRING_LEN 255
101 #define DEMO_DATA_FRAME() do { \
102 if(Demo_file == NULL){\
104 DEMO_ERROR(DEMO_ERROR_GENERAL);\
107 if(Game_mode & GM_DEMO_RECORD){\
108 if(Demo_buf_pos == 0) {\
112 if(Demo_frame_events <= 0){\
115 if(!cfwrite_ushort((ushort)Demo_buf_pos, Demo_file)){\
116 DEMO_ERROR(DEMO_ERROR_DISK_SPACE);\
119 if(!cfwrite(Demo_buf, Demo_buf_pos, 1, Demo_file)){\
120 DEMO_ERROR(DEMO_ERROR_DISK_SPACE);\
123 } else if(Game_mode & GM_DEMO_PLAYBACK){\
124 Demo_buf_pos = (int)cfread_ushort(Demo_file);\
125 if(!cfread(Demo_buf, Demo_buf_pos, 1, Demo_file)){\
126 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);\
131 #define DEMO_DATA(vl, vl_size) do { if(Demo_buf == NULL){ DEMO_ERROR(DEMO_ERROR_GENERAL); break; } if((Demo_buf_pos + vl_size) >= DEMO_BUF_SIZE){ Int3(); DEMO_ERROR(DEMO_ERROR_FRAMESIZE); break; } if(Game_mode & GM_DEMO_RECORD){ memcpy(Demo_buf + Demo_buf_pos, &vl, vl_size); } else if(Game_mode & GM_DEMO_PLAYBACK){ memcpy(&vl, Demo_buf + Demo_buf_pos, vl_size); } Demo_buf_pos += vl_size; } while(0)
132 #define DEMO_INT(vl) do { DEMO_DATA(vl, sizeof(int)); } while(0)
133 #define DEMO_UINT(vl) do { DEMO_DATA(vl, sizeof(uint)); } while(0)
134 #define DEMO_SHORT(vl) do { DEMO_DATA(vl, sizeof(short)); } while(0)
135 #define DEMO_USHORT(vl) do { DEMO_DATA(vl, sizeof(ushort)); } while(0)
136 #define DEMO_BYTE(vl) do { DEMO_DATA(vl, sizeof(char)); } while(0)
137 #define DEMO_UBYTE(vl) do { DEMO_DATA(vl, sizeof(ubyte)); } while(0)
138 #define DEMO_FLOAT(vl) do { DEMO_DATA(vl, sizeof(float)); } while(0)
139 #define DEMO_VECTOR(vl) do { DEMO_DATA(vl, sizeof(vector)); } while(0)
140 #define DEMO_MATRIX(vl) do { DEMO_DATA(vl, sizeof(matrix)); } while(0)
141 #define DEMO_STRING(vl) do { /*int stlen; if(Game_mode & GM_DEMO_RECORD){ stlen = strlen(vl); if(stlen <= 0){ break; } DEMO_DATA(stlen, sizeof(ushort)); DEMO_DATA(*vl, strlen(vl)); } else { ushort len = 0; DEMO_USHORT(len); DEMO_DATA(*vl, len); vl[len] = '\0'; }*/ } while(0)
144 #define DE_DUMP 1 // standard object dump
145 #define DE_TRAILER 2 // end of demo trailer
146 #define DE_PRIMARY 3 // primary weapon fired
147 #define DE_UNIQUE_MESSAGE 4 // unique hud message
148 #define DE_BUILTIN_MESSAGE 5 // builtin hud message
149 #define DE_OBJ_CREATE 6 // object create message
150 #define DE_OBJ_WARPIN 7 // ship warpin
151 #define DE_OBJ_WARPOUT 8 // ship warpout
152 #define DE_OBJ_DEPARTED 9 // ship departed
153 #define DE_SHIP_KILL 10 // ship kill
155 // call this when posting an error
156 #define DEMO_ERROR(er) do { Demo_error = er; Int3(); } while(0)
161 Demo_make = !Demo_make;
163 dc_printf("Demo will be recorded\n");
165 dc_printf("Demo will NOT be recorded\n");
170 // -----------------------------------------------------------------------------------------------------------------------------
171 // DEMO FORWARD DECLARATIONS
175 int demo_write_header();
177 // read the demo header
178 int demo_read_header();
180 // write the demo trailer
181 void demo_write_trailer();
183 // do a recording frame
184 void demo_do_recording_frame_start();
186 // do a recording frame
187 void demo_do_recording_frame_end();
189 // do a playback frame
190 void demo_do_playback_frame();
192 // seek through the demo file to the proper location
193 // return 0 on error, 1 on success/continue, 2 if the demo is done
194 int demo_playback_seek();
196 // scan through a read in frame of data and apply everything necessary. returns true if the trailer (end of demo) was found
197 int demo_playback_seek_sub(int frame_size);
200 // -----------------------------------------------------------------------------------------------------------------------------
204 // do frame for the demo - playback and recording, returns 0 if errors were encountered during frame processing
205 int demo_do_frame_start()
210 // if we're not doing any demo stuff
211 if(!(Game_mode & GM_DEMO)){
216 if(Demo_file == NULL){
217 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
221 // make sure we're not trying to record and playback at the same time
222 SDL_assert( ((Game_mode & GM_DEMO_RECORD) && !(Game_mode & GM_DEMO_PLAYBACK)) || (!(Game_mode & GM_DEMO_RECORD) && (Game_mode & GM_DEMO_PLAYBACK)) );
225 if(Game_mode & GM_DEMO_RECORD){
226 demo_do_recording_frame_start();
228 demo_do_playback_frame();
231 // bad bad bad, get mwa
241 // do frame for the demo - playback and recording, returns 0 if errors were encountered during frame processing
242 int demo_do_frame_end()
247 // if we're not doing any demo stuff
248 if(!(Game_mode & GM_DEMO)){
253 if(Demo_file == NULL){
254 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
258 // make sure we're not trying to record and playback at the same time
259 SDL_assert( ((Game_mode & GM_DEMO_RECORD) && !(Game_mode & GM_DEMO_PLAYBACK)) || (!(Game_mode & GM_DEMO_RECORD) && (Game_mode & GM_DEMO_PLAYBACK)) );
261 // recording. there's nothing to do here for playback
262 if(Game_mode & GM_DEMO_RECORD){
263 demo_do_recording_frame_end();
266 // bad bad bad, get mwa
276 // initialize a demo for recording
277 // NOTE : call this after loading the mission and going through the briefing, but _before_ physically moving into the mission
278 int demo_start_record(const char *file)
283 char full_name[MAX_FILENAME_LEN] = "";
285 // try and allocate the buffer
286 Demo_buf = (char*)malloc(DEMO_BUF_SIZE);
287 if(Demo_buf == NULL){
288 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
293 SDL_strlcpy(full_name, file, sizeof(full_name));
294 cf_add_ext(full_name, ".fsd");
295 Demo_file = cfopen(full_name, "wb", CFILE_NORMAL, CF_TYPE_DEMOS);
296 if(Demo_file == NULL){
298 Demo_error = DEMO_ERROR_DISK_ACCESS;
303 Demo_error = DEMO_ERROR_NONE;
306 if(!demo_write_header()){
311 Game_mode |= GM_DEMO_RECORD;
314 Demo_frame_events = 0;
321 // initialize a demo for playback - calling this will load up the demo file and move the player into the playback state
322 int demo_start_playback(const char *file)
327 char full_name[MAX_FILENAME_LEN] = "";
329 // try and allocate the buffer
330 Demo_buf = (char*)malloc(DEMO_BUF_SIZE);
331 if(Demo_buf == NULL){
332 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
337 SDL_strlcpy(full_name, file, sizeof(full_name));
338 cf_add_ext(full_name, ".fsd");
339 Demo_file = cfopen(full_name, "rb", CFILE_NORMAL, CF_TYPE_DEMOS);
340 if(Demo_file == NULL){
341 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
346 Demo_error = DEMO_ERROR_NONE;
349 Demo_cur_offset = -1;
350 if(!demo_read_header()){
355 Game_mode |= GM_DEMO_PLAYBACK;
357 // everything is cool, so jump into the mission
358 gameseq_post_event(GS_EVENT_ENTER_GAME);
367 // if we're recording, write the trailer
368 if(Game_mode & GM_DEMO_RECORD){
369 demo_write_trailer();
372 // close the demo file
373 if(Demo_file != NULL){
379 if(Demo_buf != NULL){
384 // if we're playing back, go back to the main hall
385 if(Game_mode & GM_DEMO_PLAYBACK){
386 gameseq_post_event(GS_EVENT_MAIN_MENU);
390 Game_mode &= ~(GM_DEMO_RECORD | GM_DEMO_PLAYBACK);
394 // if we should run the simulation for this object, or let the demo system handle it
395 int demo_should_sim(object *objp)
400 // always sim stuff in non-demo mode
401 if(!(Game_mode & GM_DEMO)){
405 // don't sim ships or missiles
406 if((objp->type == OBJ_SHIP) || ((objp->type == OBJ_WEAPON) && (objp->instance >= 0) && (Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_MISSILE))){
410 // sim everything else
416 // -----------------------------------------------------------------------------------------------------------------------------
417 // DEMO RECORDING FUNCTIONS
421 int demo_write_header()
426 // write demo version #
427 if(!cfwrite_int(DEMO_VERSION, Demo_file)){
428 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
432 // write mission filename
433 if(!cfwrite_string_len(Game_current_mission_filename, Demo_file)){
434 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
438 // write mission checksum
439 full_name = cf_add_ext(Game_current_mission_filename, FS_MISSION_FILE_EXT);
440 cf_chksum_long(full_name, &chksum);
441 if(!cfwrite_int(chksum, Demo_file)){
442 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
450 // write the demo trailer
451 void demo_write_trailer()
454 demo_do_recording_frame_start();
457 ubyte frame_type = DE_TRAILER;
458 DEMO_UBYTE(frame_type);
463 // write frame data to disk
467 // start recording frame
468 void demo_do_recording_frame_start()
470 // if we're not physically doing the mission
471 if(gameseq_get_state() != GS_STATE_GAME_PLAY){
476 Demo_missiontime += flFrametime;
478 // clear the buffer, set no events, and write the header
480 Demo_frame_events = 0;
483 float fl_time = f2fl(Missiontime);
487 // end recording frame
488 void demo_do_recording_frame_end()
490 // if we're not physically doing the mission
491 if(gameseq_get_state() != GS_STATE_GAME_PLAY){
495 // if its time to dump objects (the last thing we might dump per frame)
496 if((Demo_stamp == -1) || timestamp_elapsed(Demo_stamp)){
497 // post an object dump event
498 demo_POST_object_dump();
501 Demo_stamp = timestamp(Demo_fps);
504 // write all accumulated frame data to disk if necessary
508 // post an object dump event
509 void demo_POST_object_dump()
517 ubyte event_type = DE_DUMP;
518 DEMO_UBYTE(event_type);
520 // go through the ship list and count
522 for ( sobjp = GET_FIRST(&Ship_obj_list); sobjp !=END_OF_LIST(&Ship_obj_list); sobjp = GET_NEXT(sobjp) ){
524 if(sobjp->objnum < 0){
531 // write out the object count
532 DEMO_USHORT(obj_count);
534 // go through the ship list and dump necessary stuff
535 for ( sobjp = GET_FIRST(&Ship_obj_list); sobjp !=END_OF_LIST(&Ship_obj_list); sobjp = GET_NEXT(sobjp) ){
537 if(sobjp->objnum < 0){
540 objp = &Objects[sobjp->objnum];
542 // just ships for now
543 DEMO_INT(objp->signature);
544 DEMO_VECTOR(objp->pos);
545 DEMO_MATRIX(objp->orient);
546 DEMO_FLOAT(objp->phys_info.forward_thrust);
547 team = (ubyte)Ships[objp->instance].team;
549 DEMO_INT(Ships[objp->instance].flags);
552 // up the event count
556 // post a primary fired event
557 void demo_POST_primary_fired(object *objp, int banks, int linked)
562 ubyte event_type = DE_PRIMARY;
563 DEMO_UBYTE(event_type);
566 DEMO_INT(objp->signature);
569 fire_info = (ubyte)banks;
570 fire_info &= ~(1<<7);
574 DEMO_UBYTE(fire_info);
576 // up the event count
580 // post a unique message
581 void demo_POST_unique_message(const char *id, const char *who_from, int m_source, int priority)
584 if((id == NULL) || (who_from == NULL) || (strlen(id) <= 0) || (strlen(who_from) <= 0)){
589 ubyte event = DE_UNIQUE_MESSAGE;
592 DEMO_STRING(who_from);
596 // up the event count
600 // post a builtin message
601 void demo_POST_builtin_message(int type, ship *shipp, int priority, int timing)
606 ubyte event = DE_BUILTIN_MESSAGE;
611 } else if(shipp->objnum >= 0){
612 sig = Objects[shipp->objnum].signature;
618 // up the event count
622 // post an object create message
623 void demo_POST_obj_create(const char *pobj_name, int signature)
626 ubyte event = DE_OBJ_CREATE;
628 DEMO_STRING(pobj_name);
631 // up the event count
635 // post a warpin event
636 void demo_POST_warpin(int signature, int ship_flags)
639 ubyte event = DE_OBJ_WARPIN;
642 DEMO_INT(ship_flags);
644 // up the event count
648 // post a warpout event
649 void demo_POST_warpout(int signature, int ship_flags)
652 ubyte event = DE_OBJ_WARPOUT;
655 DEMO_INT(ship_flags);
657 // up the event count
661 // post a departed event
662 void demo_POST_departed(int signature, int ship_flags)
665 ubyte event = DE_OBJ_DEPARTED;
668 DEMO_INT(ship_flags);
670 // up the event count
674 // post a ship kill event
675 void demo_POST_ship_kill(object *objp)
678 ubyte event = DE_SHIP_KILL;
680 DEMO_INT(objp->signature);
682 // up the event count
687 // -----------------------------------------------------------------------------------------------------------------------------
688 // DEMO PLAYBACK FUNCTIONS
691 // read the demo header
692 int demo_read_header()
695 uint file_checksum, my_checksum;
698 // read the version #
699 version = cfread_int(Demo_file);
700 if(version != DEMO_VERSION){
701 DEMO_ERROR(DEMO_ERROR_VERSION);
705 // read mission filename
706 cfread_string_len(Game_current_mission_filename, MAX_FILENAME_LEN, Demo_file);
708 // get mission checksum
709 file_checksum = cfread_int(Demo_file);
710 full_name = cf_add_ext(Game_current_mission_filename, FS_MISSION_FILE_EXT);
711 if(!cf_chksum_long(full_name, &my_checksum)){
712 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
715 if(file_checksum != my_checksum){
716 DEMO_ERROR(DEMO_ERROR_MISSION);
720 // get the file offset of this first frame
721 Demo_cur_offset = cftell(Demo_file);
727 // do a playback frame
728 void demo_do_playback_frame()
730 // seek to the best location in the demo file
731 switch(demo_playback_seek()){
740 // found the trailer - this demo is done
747 // seek through the demo file to the proper location
748 // return 0 on error, 1 on success/continue, 2 if the demo is done
749 int demo_playback_seek()
751 float this_time = 0.0f;
756 // scan until we find something useful
758 // record the beginning of this chunk
759 this_offset = cftell(Demo_file);
761 // read in the data for the next frame
763 frame_size = Demo_buf_pos;
767 DEMO_FLOAT(this_time);
770 if(this_time > f2fl(Missiontime)){
774 // seek back to the beginning of this chunk
775 Demo_cur_offset = this_offset;
780 // we should scan through this chunk
782 // returns true if it finds the demo trailer
783 if(demo_playback_seek_sub(frame_size)){
789 // seek back to the new frame
790 cfseek(Demo_file, Demo_cur_offset, CF_SEEK_SET);
796 // scan through a read in frame of data and apply everything necessary. returns true if the trailer (end of demo) was found
797 int demo_playback_seek_sub(int frame_size)
803 while(Demo_buf_pos < frame_size){
809 // oops, trailer. we're done
815 ushort obj_count = 0;
818 vector obj_pos = vmd_zero_vector;
819 matrix obj_orient = vmd_identity_matrix;
820 float obj_fthrust = 0;
824 // get the object count
825 DEMO_USHORT(obj_count);
827 // read in all the objects
828 for(idx=0; idx<obj_count; idx++){
830 DEMO_VECTOR(obj_pos);
831 DEMO_MATRIX(obj_orient);
832 DEMO_FLOAT(obj_fthrust);
834 DEMO_INT(ship_flags);
836 // find our ship object
837 ship_index = ship_get_by_signature(obj_sig);
838 // SDL_assert(ship_index >= 0);
840 Objects[Ships[ship_index].objnum].pos = obj_pos;
841 Objects[Ships[ship_index].objnum].orient = obj_orient;
842 Objects[Ships[ship_index].objnum].phys_info.forward_thrust = obj_fthrust;
843 Ships[ship_index].team = team;
844 Ships[ship_index].flags = ship_flags;
856 // get the data and ship
858 DEMO_UBYTE(fire_info);
859 ship_index = ship_get_by_signature(obj_sig);
861 Ships[ship_index].weapons.current_primary_bank = (int)(fire_info & ~(1<<7));
862 if(fire_info & (1<<7)){
863 Ships[ship_index].flags |= SF_PRIMARY_LINKED;
865 Ships[ship_index].flags &= ~(SF_PRIMARY_LINKED);
869 ship_fire_primary(&Objects[Ships[ship_index].objnum], 0, 1);
875 case DE_UNIQUE_MESSAGE: {
877 char who_from[255] = "";
883 DEMO_STRING(who_from);
888 message_send_unique_to_player(id, who_from, m_source, priority, 0, 0);
893 case DE_BUILTIN_MESSAGE:{
906 // message_send_builtin_to_player(type, NULL, priority, timing, 0, 0);
908 ship_index = ship_get_by_signature(sig);
910 // message_send_builtin_to_player(type, &Ships[ship_index], priority, timing, 0, 0);
918 char pobj_name[255] = "";
921 p_object *objp = NULL;
923 // try and create the ship
924 DEMO_STRING(pobj_name);
927 objp = mission_parse_get_arrival_ship( pobj_name );
929 objnum = parse_create_object(objp);
931 Objects[objnum].signature = obj_sig;
943 // get the data and ship
945 DEMO_INT(ship_flags);
946 ship_index = ship_get_by_signature(obj_sig);
948 Ships[ship_index].flags = ship_flags;
949 shipfx_warpin_start(&Objects[Ships[ship_index].objnum]);
955 case DE_OBJ_WARPOUT:{
960 // get the data and ship
962 DEMO_INT(ship_flags);
963 ship_index = ship_get_by_signature(obj_sig);
965 Ships[ship_index].flags = ship_flags;
966 shipfx_warpout_start(&Objects[Ships[ship_index].objnum]);
972 case DE_OBJ_DEPARTED:{
977 // get the data and ship
979 DEMO_INT(ship_flags);
980 ship_index = ship_get_by_signature(obj_sig);
982 Ships[ship_index].flags = ship_flags;
983 ship_departed(ship_index);
993 // get the data and ship
995 ship_index = ship_get_by_signature(obj_sig);
997 Objects[Ships[ship_index].objnum].hull_strength = 0.0f;
998 ship_generic_kill_stuff(&Objects[Ships[ship_index].objnum], 1.0f);