2 * $Logfile: /Freespace2/code/Demo/Demo.cpp $
9 * Revision 1.2 2002/05/07 03:16:43 theoddone33
10 * The Great Newline Fix
12 * Revision 1.1.1.1 2002/05/03 03:28:08 root
16 * 6 8/26/99 8:51p Dave
17 * Gave multiplayer TvT messaging a heavy dose of sanity. Cheat codes.
19 * 5 6/16/99 10:20a Dave
20 * Added send-message-list sexpression.
22 * 4 4/16/99 5:54p Dave
23 * Support for on/off style "stream" weapons. Real early support for
24 * target-painting lasers.
26 * 3 3/29/99 6:17p Dave
27 * More work on demo system. Got just about everything in except for
28 * blowing ships up, secondary weapons and player death/warpout.
30 * 2 3/28/99 5:58p Dave
31 * Added early demo code. Make objects move. Nice and framerate
32 * independant, but not much else. Don't use yet unless you're me :)
39 #include "missionload.h"
42 #include "freespace.h"
45 #include "gamesequence.h"
46 #include "systemvars.h"
48 #include "missionmessage.h"
49 #include "missionparse.h"
53 // -----------------------------------------------------------------------------------------------------------------------------
57 CFILE *Demo_file = NULL;
60 #define DEMO_DEFAULT_FPS 15
61 int Demo_fps = 1000 / DEMO_DEFAULT_FPS;
63 // timestamp for frame dumping
67 float Demo_missiontime = 0.0f;
69 // buffer for reading and writing demo stuff
70 #define DEMO_BUF_SIZE 32768
71 char *Demo_buf = NULL;
74 // # of events posted for this frame
75 int Demo_frame_events = 0;
77 // current offset into the demo file - only used for playback
78 int Demo_cur_offset = -1;
81 #define DEMO_VERSION 2
83 // an error reading or writing the demo file
84 int Demo_error = DEMO_ERROR_NONE;
86 // all strings read out of the demo file must be no longer than this
87 #define DEMO_STRING_LEN 255
90 #define DEMO_DATA_FRAME() do { \
91 if(Demo_file == NULL){\
93 DEMO_ERROR(DEMO_ERROR_GENERAL);\
96 if(Game_mode & GM_DEMO_RECORD){\
97 if(Demo_buf_pos == 0) {\
101 if(Demo_frame_events <= 0){\
104 if(!cfwrite_ushort((ushort)Demo_buf_pos, Demo_file)){\
105 DEMO_ERROR(DEMO_ERROR_DISK_SPACE);\
108 if(!cfwrite(Demo_buf, Demo_buf_pos, 1, Demo_file)){\
109 DEMO_ERROR(DEMO_ERROR_DISK_SPACE);\
112 } else if(Game_mode & GM_DEMO_PLAYBACK){\
113 Demo_buf_pos = (int)cfread_ushort(Demo_file);\
114 if(!cfread(Demo_buf, Demo_buf_pos, 1, Demo_file)){\
115 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);\
120 #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)
121 #define DEMO_INT(vl) do { DEMO_DATA(vl, sizeof(int)); } while(0)
122 #define DEMO_UINT(vl) do { DEMO_DATA(vl, sizeof(uint)); } while(0)
123 #define DEMO_SHORT(vl) do { DEMO_DATA(vl, sizeof(short)); } while(0)
124 #define DEMO_USHORT(vl) do { DEMO_DATA(vl, sizeof(ushort)); } while(0)
125 #define DEMO_BYTE(vl) do { DEMO_DATA(vl, sizeof(char)); } while(0)
126 #define DEMO_UBYTE(vl) do { DEMO_DATA(vl, sizeof(ubyte)); } while(0)
127 #define DEMO_FLOAT(vl) do { DEMO_DATA(vl, sizeof(float)); } while(0)
128 #define DEMO_VECTOR(vl) do { DEMO_DATA(vl, sizeof(vector)); } while(0)
129 #define DEMO_MATRIX(vl) do { DEMO_DATA(vl, sizeof(matrix)); } while(0)
130 #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)
133 #define DE_DUMP 1 // standard object dump
134 #define DE_TRAILER 2 // end of demo trailer
135 #define DE_PRIMARY 3 // primary weapon fired
136 #define DE_UNIQUE_MESSAGE 4 // unique hud message
137 #define DE_BUILTIN_MESSAGE 5 // builtin hud message
138 #define DE_OBJ_CREATE 6 // object create message
139 #define DE_OBJ_WARPIN 7 // ship warpin
140 #define DE_OBJ_WARPOUT 8 // ship warpout
141 #define DE_OBJ_DEPARTED 9 // ship departed
142 #define DE_SHIP_KILL 10 // ship kill
144 // call this when posting an error
145 #define DEMO_ERROR(er) do { Demo_error = er; Int3(); } while(0)
150 Demo_make = !Demo_make;
152 dc_printf("Demo will be recorded\n");
154 dc_printf("Demo will NOT be recorded\n");
159 // -----------------------------------------------------------------------------------------------------------------------------
160 // DEMO FORWARD DECLARATIONS
164 int demo_write_header();
166 // read the demo header
167 int demo_read_header();
169 // write the demo trailer
170 void demo_write_trailer();
172 // do a recording frame
173 void demo_do_recording_frame_start();
175 // do a recording frame
176 void demo_do_recording_frame_end();
178 // do a playback frame
179 void demo_do_playback_frame();
181 // seek through the demo file to the proper location
182 // return 0 on error, 1 on success/continue, 2 if the demo is done
183 int demo_playback_seek();
185 // scan through a read in frame of data and apply everything necessary. returns true if the trailer (end of demo) was found
186 int demo_playback_seek_sub(int frame_size);
189 // -----------------------------------------------------------------------------------------------------------------------------
193 // do frame for the demo - playback and recording, returns 0 if errors were encountered during frame processing
194 int demo_do_frame_start()
199 // if we're not doing any demo stuff
200 if(!(Game_mode & GM_DEMO)){
205 if(Demo_file == NULL){
206 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
210 // make sure we're not trying to record and playback at the same time
211 Assert( ((Game_mode & GM_DEMO_RECORD) && !(Game_mode & GM_DEMO_PLAYBACK)) || (!(Game_mode & GM_DEMO_RECORD) && (Game_mode & GM_DEMO_PLAYBACK)) );
214 if(Game_mode & GM_DEMO_RECORD){
215 demo_do_recording_frame_start();
217 demo_do_playback_frame();
220 // bad bad bad, get mwa
230 // do frame for the demo - playback and recording, returns 0 if errors were encountered during frame processing
231 int demo_do_frame_end()
236 // if we're not doing any demo stuff
237 if(!(Game_mode & GM_DEMO)){
242 if(Demo_file == NULL){
243 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
247 // make sure we're not trying to record and playback at the same time
248 Assert( ((Game_mode & GM_DEMO_RECORD) && !(Game_mode & GM_DEMO_PLAYBACK)) || (!(Game_mode & GM_DEMO_RECORD) && (Game_mode & GM_DEMO_PLAYBACK)) );
250 // recording. there's nothing to do here for playback
251 if(Game_mode & GM_DEMO_RECORD){
252 demo_do_recording_frame_end();
255 // bad bad bad, get mwa
265 // initialize a demo for recording
266 // NOTE : call this after loading the mission and going through the briefing, but _before_ physically moving into the mission
267 int demo_start_record(char *file)
272 char full_name[MAX_FILENAME_LEN] = "";
274 // try and allocate the buffer
275 Demo_buf = (char*)malloc(DEMO_BUF_SIZE);
276 if(Demo_buf == NULL){
277 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
282 strcpy(full_name, file);
283 cf_add_ext(full_name, ".fsd");
284 Demo_file = cfopen(full_name, "wb", CFILE_NORMAL, CF_TYPE_DEMOS);
285 if(Demo_file == NULL){
287 Demo_error = DEMO_ERROR_DISK_ACCESS;
292 Demo_error = DEMO_ERROR_NONE;
295 if(!demo_write_header()){
300 Game_mode |= GM_DEMO_RECORD;
303 Demo_frame_events = 0;
310 // initialize a demo for playback - calling this will load up the demo file and move the player into the playback state
311 int demo_start_playback(char *file)
316 char full_name[MAX_FILENAME_LEN] = "";
318 // try and allocate the buffer
319 Demo_buf = (char*)malloc(DEMO_BUF_SIZE);
320 if(Demo_buf == NULL){
321 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
326 strcpy(full_name, file);
327 cf_add_ext(full_name, ".fsd");
328 Demo_file = cfopen(full_name, "rb", CFILE_NORMAL, CF_TYPE_DEMOS);
329 if(Demo_file == NULL){
330 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
335 Demo_error = DEMO_ERROR_NONE;
338 Demo_cur_offset = -1;
339 if(!demo_read_header()){
344 Game_mode |= GM_DEMO_PLAYBACK;
346 // everything is cool, so jump into the mission
347 gameseq_post_event(GS_EVENT_ENTER_GAME);
356 // if we're recording, write the trailer
357 if(Game_mode & GM_DEMO_RECORD){
358 demo_write_trailer();
361 // close the demo file
362 if(Demo_file != NULL){
368 if(Demo_buf != NULL){
373 // if we're playing back, go back to the main hall
374 if(Game_mode & GM_DEMO_PLAYBACK){
375 gameseq_post_event(GS_EVENT_MAIN_MENU);
379 Game_mode &= ~(GM_DEMO_RECORD | GM_DEMO_PLAYBACK);
383 // if we should run the simulation for this object, or let the demo system handle it
384 int demo_should_sim(object *objp)
389 // always sim stuff in non-demo mode
390 if(!(Game_mode & GM_DEMO)){
394 // don't sim ships or missiles
395 if((objp->type == OBJ_SHIP) || ((objp->type == OBJ_WEAPON) && (objp->instance >= 0) && (Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_MISSILE))){
399 // sim everything else
405 // -----------------------------------------------------------------------------------------------------------------------------
406 // DEMO RECORDING FUNCTIONS
410 int demo_write_header()
415 // write demo version #
416 if(!cfwrite_int(DEMO_VERSION, Demo_file)){
417 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
421 // write mission filename
422 if(!cfwrite_string_len(Game_current_mission_filename, Demo_file)){
423 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
427 // write mission checksum
428 full_name = cf_add_ext(Game_current_mission_filename, FS_MISSION_FILE_EXT);
429 cf_chksum_long(full_name, &chksum);
430 if(!cfwrite_int(chksum, Demo_file)){
431 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
439 // write the demo trailer
440 void demo_write_trailer()
443 demo_do_recording_frame_start();
446 ubyte frame_type = DE_TRAILER;
447 DEMO_UBYTE(frame_type);
452 // write frame data to disk
456 // start recording frame
457 void demo_do_recording_frame_start()
459 // if we're not physically doing the mission
460 if(gameseq_get_state() != GS_STATE_GAME_PLAY){
465 Demo_missiontime += flFrametime;
467 // clear the buffer, set no events, and write the header
469 Demo_frame_events = 0;
472 float fl_time = f2fl(Missiontime);
476 // end recording frame
477 void demo_do_recording_frame_end()
479 // if we're not physically doing the mission
480 if(gameseq_get_state() != GS_STATE_GAME_PLAY){
484 // if its time to dump objects (the last thing we might dump per frame)
485 if((Demo_stamp == -1) || timestamp_elapsed(Demo_stamp)){
486 // post an object dump event
487 demo_POST_object_dump();
490 Demo_stamp = timestamp(Demo_fps);
493 // write all accumulated frame data to disk if necessary
497 // post an object dump event
498 void demo_POST_object_dump()
506 ubyte event_type = DE_DUMP;
507 DEMO_UBYTE(event_type);
509 // go through the ship list and count
511 for ( sobjp = GET_FIRST(&Ship_obj_list); sobjp !=END_OF_LIST(&Ship_obj_list); sobjp = GET_NEXT(sobjp) ){
513 if(sobjp->objnum < 0){
520 // write out the object count
521 DEMO_USHORT(obj_count);
523 // go through the ship list and dump necessary stuff
524 for ( sobjp = GET_FIRST(&Ship_obj_list); sobjp !=END_OF_LIST(&Ship_obj_list); sobjp = GET_NEXT(sobjp) ){
526 if(sobjp->objnum < 0){
529 objp = &Objects[sobjp->objnum];
531 // just ships for now
532 DEMO_INT(objp->signature);
533 DEMO_VECTOR(objp->pos);
534 DEMO_MATRIX(objp->orient);
535 DEMO_FLOAT(objp->phys_info.forward_thrust);
536 team = (ubyte)Ships[objp->instance].team;
538 DEMO_INT(Ships[objp->instance].flags);
541 // up the event count
545 // post a primary fired event
546 void demo_POST_primary_fired(object *objp, int banks, int linked)
551 ubyte event_type = DE_PRIMARY;
552 DEMO_UBYTE(event_type);
555 DEMO_INT(objp->signature);
558 fire_info = (ubyte)banks;
559 fire_info &= ~(1<<7);
563 DEMO_UBYTE(fire_info);
565 // up the event count
569 // post a unique message
570 void demo_POST_unique_message(char *id, char *who_from, int m_source, int priority)
573 if((id == NULL) || (who_from == NULL) || (strlen(id) <= 0) || (strlen(who_from) <= 0)){
578 ubyte event = DE_UNIQUE_MESSAGE;
581 DEMO_STRING(who_from);
585 // up the event count
589 // post a builtin message
590 void demo_POST_builtin_message(int type, ship *shipp, int priority, int timing)
595 ubyte event = DE_BUILTIN_MESSAGE;
600 } else if(shipp->objnum >= 0){
601 sig = Objects[shipp->objnum].signature;
607 // up the event count
611 // post an object create message
612 void demo_POST_obj_create(char *pobj_name, int signature)
615 ubyte event = DE_OBJ_CREATE;
617 DEMO_STRING(pobj_name);
620 // up the event count
624 // post a warpin event
625 void demo_POST_warpin(int signature, int ship_flags)
628 ubyte event = DE_OBJ_WARPIN;
631 DEMO_INT(ship_flags);
633 // up the event count
637 // post a warpout event
638 void demo_POST_warpout(int signature, int ship_flags)
641 ubyte event = DE_OBJ_WARPOUT;
644 DEMO_INT(ship_flags);
646 // up the event count
650 // post a departed event
651 void demo_POST_departed(int signature, int ship_flags)
654 ubyte event = DE_OBJ_DEPARTED;
657 DEMO_INT(ship_flags);
659 // up the event count
663 // post a ship kill event
664 void demo_POST_ship_kill(object *objp)
667 ubyte event = DE_SHIP_KILL;
669 DEMO_INT(objp->signature);
671 // up the event count
676 // -----------------------------------------------------------------------------------------------------------------------------
677 // DEMO PLAYBACK FUNCTIONS
680 // read the demo header
681 int demo_read_header()
684 uint file_checksum, my_checksum;
687 // read the version #
688 version = cfread_int(Demo_file);
689 if(version != DEMO_VERSION){
690 DEMO_ERROR(DEMO_ERROR_VERSION);
694 // read mission filename
695 cfread_string_len(Game_current_mission_filename, MAX_FILENAME_LEN, Demo_file);
697 // get mission checksum
698 file_checksum = cfread_int(Demo_file);
699 full_name = cf_add_ext(Game_current_mission_filename, FS_MISSION_FILE_EXT);
700 if(!cf_chksum_long(full_name, &my_checksum)){
701 DEMO_ERROR(DEMO_ERROR_DISK_ACCESS);
704 if(file_checksum != my_checksum){
705 DEMO_ERROR(DEMO_ERROR_MISSION);
709 // get the file offset of this first frame
710 Demo_cur_offset = cftell(Demo_file);
716 // do a playback frame
717 void demo_do_playback_frame()
719 // seek to the best location in the demo file
720 switch(demo_playback_seek()){
729 // found the trailer - this demo is done
736 // seek through the demo file to the proper location
737 // return 0 on error, 1 on success/continue, 2 if the demo is done
738 int demo_playback_seek()
740 float this_time = 0.0f;
745 // scan until we find something useful
747 // record the beginning of this chunk
748 this_offset = cftell(Demo_file);
750 // read in the data for the next frame
752 frame_size = Demo_buf_pos;
756 DEMO_FLOAT(this_time);
759 if(this_time > f2fl(Missiontime)){
763 // seek back to the beginning of this chunk
764 Demo_cur_offset = this_offset;
769 // we should scan through this chunk
771 // returns true if it finds the demo trailer
772 if(demo_playback_seek_sub(frame_size)){
778 // seek back to the new frame
779 cfseek(Demo_file, Demo_cur_offset, CF_SEEK_SET);
785 // scan through a read in frame of data and apply everything necessary. returns true if the trailer (end of demo) was found
786 int demo_playback_seek_sub(int frame_size)
792 while(Demo_buf_pos < frame_size){
798 // oops, trailer. we're done
804 ushort obj_count = 0;
807 vector obj_pos = vmd_zero_vector;
808 matrix obj_orient = vmd_identity_matrix;
809 float obj_fthrust = 0;
813 // get the object count
814 DEMO_USHORT(obj_count);
816 // read in all the objects
817 for(idx=0; idx<obj_count; idx++){
819 DEMO_VECTOR(obj_pos);
820 DEMO_MATRIX(obj_orient);
821 DEMO_FLOAT(obj_fthrust);
823 DEMO_INT(ship_flags);
825 // find our ship object
826 ship_index = ship_get_by_signature(obj_sig);
827 // Assert(ship_index >= 0);
829 Objects[Ships[ship_index].objnum].pos = obj_pos;
830 Objects[Ships[ship_index].objnum].orient = obj_orient;
831 Objects[Ships[ship_index].objnum].phys_info.forward_thrust = obj_fthrust;
832 Ships[ship_index].team = team;
833 Ships[ship_index].flags = ship_flags;
845 // get the data and ship
847 DEMO_UBYTE(fire_info);
848 ship_index = ship_get_by_signature(obj_sig);
850 Ships[ship_index].weapons.current_primary_bank = (int)(fire_info & ~(1<<7));
851 if(fire_info & (1<<7)){
852 Ships[ship_index].flags |= SF_PRIMARY_LINKED;
854 Ships[ship_index].flags &= ~(SF_PRIMARY_LINKED);
858 ship_fire_primary(&Objects[Ships[ship_index].objnum], 0, 1);
864 case DE_UNIQUE_MESSAGE: {
866 char who_from[255] = "";
872 DEMO_STRING(who_from);
877 message_send_unique_to_player(id, who_from, m_source, priority, 0, 0);
882 case DE_BUILTIN_MESSAGE:{
895 // message_send_builtin_to_player(type, NULL, priority, timing, 0, 0);
897 ship_index = ship_get_by_signature(sig);
899 // message_send_builtin_to_player(type, &Ships[ship_index], priority, timing, 0, 0);
907 char pobj_name[255] = "";
910 p_object *objp = NULL;
912 // try and create the ship
913 DEMO_STRING(pobj_name);
916 objp = mission_parse_get_arrival_ship( pobj_name );
918 objnum = parse_create_object(objp);
920 Objects[objnum].signature = obj_sig;
932 // get the data and ship
934 DEMO_INT(ship_flags);
935 ship_index = ship_get_by_signature(obj_sig);
937 Ships[ship_index].flags = ship_flags;
938 shipfx_warpin_start(&Objects[Ships[ship_index].objnum]);
944 case DE_OBJ_WARPOUT:{
949 // get the data and ship
951 DEMO_INT(ship_flags);
952 ship_index = ship_get_by_signature(obj_sig);
954 Ships[ship_index].flags = ship_flags;
955 shipfx_warpout_start(&Objects[Ships[ship_index].objnum]);
961 case DE_OBJ_DEPARTED:{
966 // get the data and ship
968 DEMO_INT(ship_flags);
969 ship_index = ship_get_by_signature(obj_sig);
971 Ships[ship_index].flags = ship_flags;
972 ship_departed(ship_index);
982 // get the data and ship
984 ship_index = ship_get_by_signature(obj_sig);
986 Objects[Ships[ship_index].objnum].hull_strength = 0.0f;
987 ship_generic_kill_stuff(&Objects[Ships[ship_index].objnum], 1.0f);