1 /*
2 VBL.C
3
4 Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
5 and the "Aleph One" developers.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 This license is contained in the file "COPYING",
18 which is included with this source code; it is available online at
19 http://www.gnu.org/licenses/gpl.html
20
21 Friday, August 21, 1992 7:06:54 PM
22
23 Tuesday, November 17, 1992 3:53:29 PM
24 the new task of the vbl controller is only to move the player. this is necessary for
25 good control of the game. everything else (doors, monsters, projectiles, etc) will
26 be moved immediately before the next frame is drawn, based on delta-time values.
27 collisions (including the player with walls) will also be handled at this time.
28 Thursday, November 19, 1992 1:27:23 AM
29 the enumeration 'turning_head' had to be changed to '_turn_not_rotate' to make this
30 file compile. go figure.
31 Wednesday, December 2, 1992 2:31:05 PM
32 the world doesn�t change while the mouse button is pressed.
33 Friday, January 15, 1993 11:19:11 AM
34 the world doesn�t change after 14 ticks have passed without a screen refresh.
35 Friday, January 22, 1993 3:06:32 PM
36 world_ticks was never being initialized to zero. hmmm.
37 Saturday, March 6, 1993 12:23:48 PM
38 at exit, we remove our vbl task.
39 Sunday, May 16, 1993 4:07:47 PM
40 finally recoding everything
41 Monday, August 16, 1993 10:22:17 AM
42 #ifdef CHARLES added.
43 Saturday, August 21, 1993 12:35:29 PM
44 from pathways VBL_CONTROLLER.C.
45 Sunday, May 22, 1994 8:51:15 PM
46 all the world physics has been moved into PHYSICS.C; all we do now is maintain and
47 distribute a circular queue of keyboard flags (we're the keyboard_controller, not the
48 movement_controller).
49 Thursday, June 2, 1994 12:55:52 PM
50 gee, now we don�t even maintain the queue we just send our actions to PLAYER.C.
51 Tuesday, July 5, 1994 9:27:49 PM
52 nuked most of the shit in here. changed the vbl task to a time
53 manager task. the only functions from the old vbl.c that remain are precalculate_key_information()
54 and parse_keymap().
55 Thursday, July 7, 1994 11:59:32 AM
56 Added recording/replaying
57 Wednesday, August 10, 1994 2:44:57 PM
58 added caching system for FSRead.
59 Friday, January 13, 1995 11:38:51 AM (Jason')
60 fixed the 'a' key getting blacklisted.
61
62 Jan 30, 2000 (Loren Petrich)
63 Did some typecasts
64
65 Jul 7, 2000 (Loren Petrich)
66 Added Ben Thompson's ISp-support changes
67
68 Aug 12, 2000 (Loren Petrich):
69 Using object-oriented file handler
70
71 Aug 26, 2000 (Loren Petrich):
72 Created alternative to SetLength(): delete a file, then re-create it.
73 This should be more stdio-friendly.
74
75 Feb 20, 2002 (Woody Zenfell):
76 Uses GetRealActionQueues()->enqueueActionFlags() rather than queue_action_flags().
77 */
78
79 #include "cseries.h"
80 #include <string.h>
81 #include <stdlib.h>
82
83 #include "map.h"
84 #include "interface.h"
85 #include "shell.h"
86 #include "preferences.h"
87 #include "Logging.h"
88 #include "mouse.h"
89 #include "player.h"
90 #include "key_definitions.h"
91 #include "tags.h"
92 #include "vbl.h"
93 #include "FileHandler.h"
94 #include "Packing.h"
95 #include "ActionQueues.h"
96 #include "computer_interface.h"
97 #include "Console.h"
98 #include "joystick.h"
99 #include "Movie.h"
100 #include "InfoTree.h"
101
102 /* ---------- constants */
103
104 #define RECORD_CHUNK_SIZE (MAXIMUM_QUEUE_SIZE/2)
105 #define END_OF_RECORDING_INDICATOR (RECORD_CHUNK_SIZE+1)
106 #define MAXIMUM_TIME_DIFFERENCE 15 // allowed between heartbeat_count and dynamic_world->tick_count
107 #define MAXIMUM_NET_QUEUE_SIZE 8
108 #define DISK_CACHE_SIZE ((sizeof(int16)+sizeof(uint32))*100)
109 #define MAXIMUM_REPLAY_SPEED 5
110 #define MINIMUM_REPLAY_SPEED -5
111
112 /* ---------- macros */
113
114 #define INCREMENT_QUEUE_COUNTER(c) { (c)++; if ((c)>=MAXIMUM_QUEUE_SIZE) (c) = 0; }
115
116 // LP: fake portable-files stuff
memory_error()117 inline short memory_error() {return 0;}
118
119 /* ---------- structures */
120 #include "vbl_definitions.h"
121
122 /* ---------- globals */
123
124 static int32 heartbeat_count;
125 static bool input_task_active;
126 static timer_task_proc input_task;
127
128 // LP: defined this here so it will work properly
129 static FileSpecifier FilmFileSpec;
130 static OpenedFile FilmFile;
131
132 struct replay_private_data replay;
133
134 #ifdef DEBUG
get_player_recording_queue(short player_index)135 ActionQueue *get_player_recording_queue(
136 short player_index)
137 {
138 assert(replay.recording_queues);
139 assert(player_index>=0 && player_index<MAXIMUM_NUMBER_OF_PLAYERS);
140
141 return (replay.recording_queues+player_index);
142 }
143 #endif
144
145 /* ---------- private prototypes */
146 static void remove_input_controller(void);
147 static void save_recording_queue_chunk(short player_index);
148 static void read_recording_queue_chunks(void);
149 static bool pull_flags_from_recording(short count);
150 // LP modifications for object-oriented file handling; returns a test for end-of-file
151 static bool vblFSRead(OpenedFile& File, int32 *count, void *dest, bool& HitEOF);
152 static void record_action_flags(short player_identifier, const uint32 *action_flags, short count);
153 static short get_recording_queue_size(short which_queue);
154
155 static uint8 *unpack_recording_header(uint8 *Stream, recording_header *Objects, size_t Count);
156 static uint8 *pack_recording_header(uint8 *Stream, recording_header *Objects, size_t Count);
157
158 // #define DEBUG_REPLAY
159
160 #ifdef DEBUG_REPLAY
161 static void open_stream_file(void);
162 static void debug_stream_of_flags(uint32 action_flag, short player_index);
163 static void close_stream_file(void);
164 #endif
165
166 /* ---------- code */
initialize_keyboard_controller(void)167 void initialize_keyboard_controller(
168 void)
169 {
170 ActionQueue *queue;
171 short player_index;
172
173 // vassert(NUMBER_OF_KEYS == NUMBER_OF_STANDARD_KEY_DEFINITIONS,
174 // csprintf(temporary, "NUMBER_OF_KEYS == %d, NUMBER_OF_KEY_DEFS = %d. Not Equal!", NUMBER_OF_KEYS, NUMBER_OF_STANDARD_KEY_DEFINITIONS));
175 assert(NUMBER_OF_STANDARD_KEY_DEFINITIONS==NUMBER_OF_LEFT_HANDED_KEY_DEFINITIONS);
176 assert(NUMBER_OF_LEFT_HANDED_KEY_DEFINITIONS==NUMBER_OF_POWERBOOK_KEY_DEFINITIONS);
177
178 // get globals initialized
179 heartbeat_count= 0;
180 input_task_active= false;
181 obj_clear(replay);
182
183 input_task= install_timer_task(TICKS_PER_SECOND, input_controller);
184 assert(input_task);
185
186 atexit(remove_input_controller);
187
188 /* Allocate the recording queues */
189 replay.recording_queues = new ActionQueue[MAXIMUM_NUMBER_OF_PLAYERS];
190 assert(replay.recording_queues);
191 if(!replay.recording_queues) alert_out_of_memory();
192
193 /* Allocate the individual ones */
194 for (player_index= 0; player_index<MAXIMUM_NUMBER_OF_PLAYERS; player_index++)
195 {
196 queue= get_player_recording_queue(player_index);
197 queue->read_index= queue->write_index = 0;
198 queue->buffer= new uint32[MAXIMUM_QUEUE_SIZE];
199 if(!queue->buffer) alert_out_of_memory();
200 }
201 enter_mouse(0);
202 }
203
set_keyboard_controller_status(bool active)204 void set_keyboard_controller_status(
205 bool active)
206 {
207 input_task_active= active;
208
209 // flush events when changing game state
210 SDL_PumpEvents();
211 SDL_FlushEvents(SDL_KEYDOWN, SDL_KEYUP);
212 SDL_FlushEvents(SDL_MOUSEMOTION, SDL_MOUSEWHEEL);
213 SDL_FlushEvents(SDL_CONTROLLERAXISMOTION, SDL_CONTROLLERBUTTONUP);
214
215 // We enable/disable mouse control here
216 if (active) {
217 enter_mouse(input_preferences->input_device);
218 enter_joystick();
219 } else {
220 exit_mouse(input_preferences->input_device);
221 exit_joystick();
222 }
223
224 /******************************************************************************************/
225 }
226
get_keyboard_controller_status(void)227 bool get_keyboard_controller_status(
228 void)
229 {
230 return input_task_active;
231 }
232
get_heartbeat_count(void)233 int32 get_heartbeat_count(
234 void)
235 {
236 return heartbeat_count;
237 }
238
sync_heartbeat_count(void)239 void sync_heartbeat_count(
240 void)
241 {
242 heartbeat_count= dynamic_world->tick_count;
243 }
244
increment_replay_speed(void)245 void increment_replay_speed(
246 void)
247 {
248 if (replay.replay_speed < MAXIMUM_REPLAY_SPEED) replay.replay_speed++;
249 }
250
decrement_replay_speed(void)251 void decrement_replay_speed(
252 void)
253 {
254 if (replay.replay_speed > MINIMUM_REPLAY_SPEED) replay.replay_speed--;
255 }
256
increment_heartbeat_count(int value)257 void increment_heartbeat_count(int value)
258 {
259 heartbeat_count+=value;
260 }
261
has_recording_file(void)262 bool has_recording_file(void)
263 {
264 FileSpecifier File;
265 return get_recording_filedesc(File);
266 }
267
268 /* Called by the time manager task in vbl_macintosh.c */
input_controller(void)269 bool input_controller(
270 void)
271 {
272 if (input_task_active || Movie::instance()->IsRecording())
273 {
274 if((heartbeat_count-dynamic_world->tick_count) < MAXIMUM_TIME_DIFFERENCE)
275 {
276 if (game_is_networked) // input from network
277 {
278 ; // all handled elsewhere now. (in network.c)
279 }
280 else if (replay.game_is_being_replayed) // input from recorded game file
281 {
282 static short phase= 0; /* When this gets to 0, update the world */
283
284 /* Minimum replay speed is a pause. */
285 if(replay.replay_speed != MINIMUM_REPLAY_SPEED)
286 {
287 if (replay.replay_speed > 0 || (--phase<=0))
288 {
289 short flag_count= MAX(replay.replay_speed, 1);
290
291 if (!pull_flags_from_recording(flag_count)) // oops. silly me.
292 {
293 if (replay.have_read_last_chunk)
294 {
295 assert(get_game_state()==_game_in_progress || get_game_state()==_switch_demo);
296 set_game_state(_switch_demo);
297 }
298 }
299 else
300 {
301 /* Increment the heartbeat.. */
302 heartbeat_count+= flag_count;
303 }
304
305 /* Reset the phase-> doesn't matter if the replay speed is positive */
306 /* +1 so that replay_speed 0 is different from replay_speed 1 */
307 phase= -(replay.replay_speed) + 1;
308 }
309 }
310 }
311 else // then getting input from the keyboard/mouse
312 {
313 uint32 action_flags= parse_keymap();
314
315 process_action_flags(local_player_index, &action_flags, 1);
316 heartbeat_count++; // ba-doom
317 }
318 } else {
319 // dprintf("Out of phase.. (%d);g", heartbeat_count - dynamic_world->tick_count);
320 }
321 }
322
323 return true; // tells the time manager library to reschedule this task
324 }
325
process_action_flags(short player_identifier,const uint32 * action_flags,short count)326 void process_action_flags(
327 short player_identifier,
328 const uint32 *action_flags,
329 short count)
330 {
331 if (replay.game_is_being_recorded)
332 {
333 record_action_flags(player_identifier, action_flags, count);
334 }
335
336 GetRealActionQueues()->enqueueActionFlags(player_identifier, action_flags, count);
337 }
338
record_action_flags(short player_identifier,const uint32 * action_flags,short count)339 static void record_action_flags(
340 short player_identifier,
341 const uint32 *action_flags,
342 short count)
343 {
344 short index;
345 ActionQueue *queue;
346
347 queue= get_player_recording_queue(player_identifier);
348 assert(queue && queue->write_index >= 0 && queue->write_index < MAXIMUM_QUEUE_SIZE);
349 for (index= 0; index<count; index++)
350 {
351 *(queue->buffer + queue->write_index) = *action_flags++;
352 INCREMENT_QUEUE_COUNTER(queue->write_index);
353 if (queue->write_index == queue->read_index)
354 {
355 dprintf("blew recording queue for player %d", player_identifier);
356 }
357 }
358 }
359
360 /*********************************************************************************************
361 *
362 * Function: save_recording_queue_chunk
363 * Purpose: saves one chunk of the queue to the recording file, using run-length encoding.
364 *
365 *********************************************************************************************/
save_recording_queue_chunk(short player_index)366 void save_recording_queue_chunk(
367 short player_index)
368 {
369 uint8 *location;
370 uint32 last_flag, count, flag = 0;
371 int16 i, run_count, num_flags_saved, max_flags;
372 static uint8 *buffer= NULL;
373 ActionQueue *queue;
374
375 // The data format is (run length (int16)) + (action flag (uint32))
376 int DataSize = sizeof(int16) + sizeof(uint32);
377
378 if (buffer == NULL)
379 buffer = new byte[RECORD_CHUNK_SIZE * DataSize];
380
381 location= buffer;
382 count= 0; // keeps track of how many bytes we'll save.
383 last_flag= (uint32)NONE;
384
385 queue= get_player_recording_queue(player_index);
386
387 // don't want to save too much stuff
388 max_flags= MIN(RECORD_CHUNK_SIZE, get_recording_queue_size(player_index));
389
390 // save what's in the queue
391 run_count= num_flags_saved= 0;
392 for (i = 0; i<max_flags; i++)
393 {
394 flag = queue->buffer[queue->read_index];
395 INCREMENT_QUEUE_COUNTER(queue->read_index);
396
397 if (i && flag != last_flag)
398 {
399 ValueToStream(location,run_count);
400 ValueToStream(location,last_flag);
401 count += DataSize;
402 num_flags_saved += run_count;
403 run_count = 1;
404 }
405 else
406 {
407 run_count++;
408 }
409 last_flag = flag;
410 }
411
412 // now save the final run
413 ValueToStream(location,run_count);
414 ValueToStream(location,last_flag);
415 count += DataSize;
416 num_flags_saved += run_count;
417
418 if (max_flags<RECORD_CHUNK_SIZE)
419 {
420 short end_indicator = END_OF_RECORDING_INDICATOR;
421 ValueToStream(location,end_indicator);
422 int32 end_flag = 0;
423 ValueToStream(location,end_flag);
424 count += DataSize;
425 num_flags_saved += RECORD_CHUNK_SIZE-max_flags;
426 }
427
428 FilmFile.Write(count,buffer);
429 replay.header.length+= count;
430
431 vwarn(num_flags_saved == RECORD_CHUNK_SIZE,
432 csprintf(temporary, "bad recording: %d flags, max=%d, count = %u;dm #%p #%u", num_flags_saved, max_flags,
433 count, buffer, count));
434 }
435
436 /*********************************************************************************************
437 *
438 * Function: pull_flags_from_recording
439 * Purpose: remove one flag from each queue from the recording buffer.
440 * Returns: true if it pulled the flags, false if it didn't
441 *
442 *********************************************************************************************/
pull_flags_from_recording(short count)443 static bool pull_flags_from_recording(
444 short count)
445 {
446 short player_index;
447 bool success= true;
448
449 // first check that we can pull something from each player�s queue
450 // (at the beginning of the game, we won�t be able to)
451 // i'm not sure that i really need to do this check. oh well.
452 for (player_index = 0; success && player_index<dynamic_world->player_count; player_index++)
453 {
454 if(get_recording_queue_size(player_index)==0) success= false;
455 }
456
457 if(success)
458 {
459 for (player_index = 0; player_index < dynamic_world->player_count; player_index++)
460 {
461 short index;
462 ActionQueue *queue;
463
464 queue= get_player_recording_queue(player_index);
465 for (index= 0; index<count; index++)
466 {
467 if (queue->read_index != queue->write_index)
468 {
469 #ifdef DEBUG_REPLAY
470 debug_stream_of_flags(*(queue->buffer+queue->read_index), player_index);
471 #endif
472 GetRealActionQueues()->enqueueActionFlags(player_index, queue->buffer + queue->read_index, 1);
473 INCREMENT_QUEUE_COUNTER(queue->read_index);
474 } else {
475 dprintf("Dropping flag?");
476 }
477 }
478 }
479 }
480
481 return success;
482 }
483
get_recording_queue_size(short which_queue)484 static short get_recording_queue_size(
485 short which_queue)
486 {
487 short size;
488 ActionQueue *queue= get_player_recording_queue(which_queue);
489
490 /* Note that this is a circular queue */
491 size= queue->write_index-queue->read_index;
492 if(size<0) size+= MAXIMUM_QUEUE_SIZE;
493
494 return size;
495 }
496
set_recording_header_data(short number_of_players,short level_number,uint32 map_checksum,short version,struct player_start_data * starts,struct game_data * game_information)497 void set_recording_header_data(
498 short number_of_players,
499 short level_number,
500 uint32 map_checksum,
501 short version,
502 struct player_start_data *starts,
503 struct game_data *game_information)
504 {
505 assert(!replay.valid);
506 obj_clear(replay.header);
507 replay.header.num_players= number_of_players;
508 replay.header.level_number= level_number;
509 replay.header.map_checksum= map_checksum;
510 replay.header.version= version;
511 objlist_copy(replay.header.starts, starts, MAXIMUM_NUMBER_OF_PLAYERS);
512 obj_copy(replay.header.game_information, *game_information);
513 // Use the packed size here!!!
514 replay.header.length= SIZEOF_recording_header;
515 }
516
get_recording_header_data(short * number_of_players,short * level_number,uint32 * map_checksum,short * version,struct player_start_data * starts,struct game_data * game_information)517 void get_recording_header_data(
518 short *number_of_players,
519 short *level_number,
520 uint32 *map_checksum,
521 short *version,
522 struct player_start_data *starts,
523 struct game_data *game_information)
524 {
525 assert(replay.valid);
526 *number_of_players= replay.header.num_players;
527 *level_number= replay.header.level_number;
528 *map_checksum= replay.header.map_checksum;
529 *version= replay.header.version;
530 objlist_copy(starts, replay.header.starts, MAXIMUM_NUMBER_OF_PLAYERS);
531 obj_copy(*game_information, replay.header.game_information);
532 }
533
setup_for_replay_from_file(FileSpecifier & File,uint32 map_checksum,bool prompt_to_export)534 bool setup_for_replay_from_file(
535 FileSpecifier& File,
536 uint32 map_checksum,
537 bool prompt_to_export)
538 {
539 bool successful= false;
540
541 (void)(map_checksum);
542
543 FilmFileSpec = File;
544 if (FilmFileSpec.Open(FilmFile))
545 {
546 replay.valid= true;
547 replay.have_read_last_chunk = false;
548 replay.game_is_being_replayed = true;
549 assert(!replay.resource_data);
550 replay.resource_data= NULL;
551 replay.resource_data_size= 0l;
552 replay.film_resource_offset= NONE;
553
554 byte Header[SIZEOF_recording_header];
555 FilmFile.Read(SIZEOF_recording_header,Header);
556 unpack_recording_header(Header,&replay.header,1);
557 replay.header.game_information.cheat_flags = _allow_crosshair | _allow_tunnel_vision | _allow_behindview | _allow_overlay_map;
558
559 /* Set to the mapfile this replay came from.. */
560 if(use_map_file(replay.header.map_checksum))
561 {
562 replay.fsread_buffer= new char[DISK_CACHE_SIZE];
563 assert(replay.fsread_buffer);
564
565 replay.location_in_cache= NULL;
566 replay.bytes_in_cache= 0;
567 replay.replay_speed= 1;
568
569 #ifdef DEBUG_REPLAY
570 open_stream_file();
571 #endif
572 if (prompt_to_export)
573 Movie::instance()->PromptForRecording();
574 successful= true;
575 } else {
576 /* Tell them that this map wasn't found. They lose. */
577 alert_user(infoError, strERRORS, cantFindReplayMap, 0);
578 replay.valid= false;
579 replay.game_is_being_replayed= false;
580 FilmFile.Close();
581 }
582 }
583
584 return successful;
585 }
586
587 /* Note that we _must_ set the header information before we start recording!! */
start_recording(void)588 void start_recording(
589 void)
590 {
591 assert(!replay.valid);
592 replay.valid= true;
593
594 if(get_recording_filedesc(FilmFileSpec))
595 FilmFileSpec.Delete();
596
597 if (FilmFileSpec.Create(_typecode_film))
598 {
599 /* I debate the validity of fsCurPerm here, but Alain had it, and it has been working */
600 if (FilmFileSpec.Open(FilmFile,true))
601 {
602 replay.game_is_being_recorded= true;
603
604 // save a header containing information about the game.
605 byte Header[SIZEOF_recording_header];
606 pack_recording_header(Header,&replay.header,1);
607 FilmFile.Write(SIZEOF_recording_header,Header);
608 }
609 }
610 }
611
stop_recording(void)612 void stop_recording(
613 void)
614 {
615 if (replay.game_is_being_recorded)
616 {
617 replay.game_is_being_recorded = false;
618
619 short player_index;
620 int32 total_length;
621
622 assert(replay.valid);
623 for (player_index= 0; player_index<dynamic_world->player_count; player_index++)
624 {
625 save_recording_queue_chunk(player_index);
626 }
627
628 /* Rewrite the header, since it has the new length */
629 FilmFile.SetPosition(0);
630 byte Header[SIZEOF_recording_header];
631 pack_recording_header(Header,&replay.header,1);
632
633 // ZZZ: removing code that does stuff from assert() argument. BUT...
634 // should we really be asserting on this anyway? I mean, the write could fail
635 // in 'normal operation' too, not just when we screwed something up in writing the program?
636 bool successfulWrite = FilmFile.Write(SIZEOF_recording_header,Header);
637 assert(successfulWrite);
638
639 FilmFile.GetLength(total_length);
640 assert(total_length==replay.header.length);
641
642 FilmFile.Close();
643 }
644
645 replay.valid= false;
646 }
647
rewind_recording(void)648 void rewind_recording(
649 void)
650 {
651 if(replay.game_is_being_recorded)
652 {
653 /* This is unnecessary, because it is called from reset_player_queues, */
654 /* which is always called from revert_game */
655 /*
656 FilmFile.SetLength(sizeof(recording_header));
657 FilmFile.SetPosition(sizeof(recording_header));
658 */
659 // Alternative that does not use "SetLength", but instead creates and re-creates the file.
660 FilmFile.SetPosition(0);
661 byte Header[SIZEOF_recording_header];
662 FilmFile.Read(SIZEOF_recording_header,Header);
663 FilmFile.Close();
664 FilmFileSpec.Delete();
665 FilmFileSpec.Create(_typecode_film);
666 FilmFileSpec.Open(FilmFile,true);
667 FilmFile.Write(SIZEOF_recording_header,Header);
668
669 // Use the packed length here!!!
670 replay.header.length= SIZEOF_recording_header;
671 }
672 }
673
check_recording_replaying(void)674 void check_recording_replaying(
675 void)
676 {
677 short player_index, queue_size;
678
679 if (replay.game_is_being_recorded)
680 {
681 bool enough_data_to_save= true;
682
683 // it's time to save the queues if all of them have >= RECORD_CHUNK_SIZE flags in them.
684 for (player_index= 0; enough_data_to_save && player_index<dynamic_world->player_count; player_index++)
685 {
686 queue_size= get_recording_queue_size(player_index);
687 if (queue_size < RECORD_CHUNK_SIZE) enough_data_to_save= false;
688 }
689
690 if(enough_data_to_save)
691 {
692 bool success;
693 uint32 freespace = 0;
694 FileSpecifier FilmFile_Check;
695
696 get_recording_filedesc(FilmFile_Check);
697
698 success= FilmFile_Check.GetFreeSpace(freespace);
699 if (success && freespace>(RECORD_CHUNK_SIZE*sizeof(int16)*sizeof(uint32)*dynamic_world->player_count))
700 {
701 for (player_index= 0; player_index<dynamic_world->player_count; player_index++)
702 {
703 save_recording_queue_chunk(player_index);
704 }
705 }
706 }
707 }
708 else if (replay.game_is_being_replayed)
709 {
710 bool load_new_data= true;
711
712 // it's time to refill the requeues if they all have < RECORD_CHUNK_SIZE flags in them.
713 for (player_index= 0; load_new_data && player_index<dynamic_world->player_count; player_index++)
714 {
715 queue_size= get_recording_queue_size(player_index);
716 if(queue_size>= RECORD_CHUNK_SIZE) load_new_data= false;
717 }
718
719 if(load_new_data)
720 {
721 // at this point, we've determined that the queues are sufficently empty, so
722 // we'll fill 'em up.
723 read_recording_queue_chunks();
724 }
725 }
726 }
727
reset_recording_and_playback_queues(void)728 void reset_recording_and_playback_queues(
729 void)
730 {
731 short index;
732
733 for(index= 0; index<MAXIMUM_NUMBER_OF_PLAYERS; ++index)
734 {
735 replay.recording_queues[index].read_index= replay.recording_queues[index].write_index= 0;
736 }
737 }
738
stop_replay(void)739 void stop_replay(
740 void)
741 {
742 if (replay.game_is_being_replayed)
743 {
744 assert(replay.valid);
745
746 replay.game_is_being_replayed= false;
747 if (replay.resource_data)
748 {
749 delete []replay.resource_data;
750 replay.resource_data= NULL;
751 }
752 else
753 {
754 FilmFile.Close();
755 assert(replay.fsread_buffer);
756 delete []replay.fsread_buffer;
757 }
758 #ifdef DEBUG_REPLAY
759 close_stream_file();
760 #endif
761 }
762
763 /* Unecessary, because reset_player_queues calls this. */
764 replay.valid= false;
765 }
766
read_recording_queue_chunks(void)767 static void read_recording_queue_chunks(
768 void)
769 {
770 logContext("reading recording queue chunks");
771
772 int32 i, sizeof_read;
773 uint32 action_flags;
774 int16 count, player_index, num_flags;
775 ActionQueue *queue;
776
777 for (player_index = 0; player_index < dynamic_world->player_count; player_index++)
778 {
779 queue= get_player_recording_queue(player_index);
780 for (count = 0; count < RECORD_CHUNK_SIZE; )
781 {
782 if (replay.resource_data)
783 {
784 bool hit_end= false;
785
786 if (replay.film_resource_offset >= replay.resource_data_size)
787 {
788 hit_end = true;
789 }
790 else
791 {
792 uint8* S;
793 S = (uint8 *)(replay.resource_data + replay.film_resource_offset);
794 StreamToValue(S,num_flags);
795 replay.film_resource_offset += sizeof(num_flags);
796 S = (uint8 *)(replay.resource_data + replay.film_resource_offset);
797 StreamToValue(S,action_flags);
798 replay.film_resource_offset+= sizeof(action_flags);
799 }
800
801 if (hit_end || num_flags == END_OF_RECORDING_INDICATOR)
802 {
803 replay.have_read_last_chunk= true;
804 break;
805 }
806 }
807 else
808 {
809 sizeof_read = sizeof(num_flags);
810 uint8 NumFlagsBuffer[sizeof(num_flags)];
811 bool HitEOF = false;
812 if (vblFSRead(FilmFile, &sizeof_read, NumFlagsBuffer, HitEOF))
813 {
814 uint8 *S = NumFlagsBuffer;
815 StreamToValue(S,num_flags);
816 sizeof_read = sizeof(action_flags);
817 uint8 ActionFlagsBuffer[sizeof(action_flags)];
818 bool status = vblFSRead(FilmFile, &sizeof_read, ActionFlagsBuffer, HitEOF);
819 S = ActionFlagsBuffer;
820 StreamToValue(S,action_flags);
821 assert(status || (HitEOF && sizeof_read == sizeof(action_flags)));
822 }
823 else
824 {
825 logError("film file read error");
826 replay.have_read_last_chunk = true;
827 break;
828 }
829
830 if ((HitEOF && sizeof_read != sizeof(action_flags)) || num_flags == END_OF_RECORDING_INDICATOR)
831 {
832 replay.have_read_last_chunk = true;
833 break;
834 }
835 }
836
837 if (!(replay.have_read_last_chunk || num_flags))
838 {
839 logAnomaly("chunk contains no flags");
840 }
841
842 count += num_flags;
843
844 for (i = 0; i < num_flags; i++)
845 {
846 *(queue->buffer + queue->write_index) = action_flags;
847 INCREMENT_QUEUE_COUNTER(queue->write_index);
848 assert(queue->read_index != queue->write_index);
849 }
850 }
851 assert(replay.have_read_last_chunk || count == RECORD_CHUNK_SIZE);
852 }
853 }
854
855 /* This is gross, (Alain wrote it, not me!) but I don't have time to clean it up */
vblFSRead(OpenedFile & File,int32 * count,void * dest,bool & HitEOF)856 static bool vblFSRead(
857 OpenedFile& File,
858 int32 *count,
859 void *dest,
860 bool& HitEOF)
861 {
862 int32 fsread_count;
863 bool status = true;
864
865 assert(replay.fsread_buffer);
866
867 // LP: way for testing whether hitting end-of-file;
868 // doing that by testing for whether a read was complete.
869 HitEOF = false;
870
871 if (replay.bytes_in_cache < *count)
872 {
873 assert(replay.bytes_in_cache + *count < int(DISK_CACHE_SIZE));
874 if (replay.bytes_in_cache)
875 {
876 memcpy(replay.fsread_buffer, replay.location_in_cache, replay.bytes_in_cache);
877 }
878 replay.location_in_cache = replay.fsread_buffer;
879 fsread_count= DISK_CACHE_SIZE - replay.bytes_in_cache;
880 int32 PrevPos;
881 File.GetPosition(PrevPos);
882 int32 replay_left= replay.header.length - PrevPos;
883 if(replay_left < fsread_count)
884 fsread_count= replay_left;
885 if(fsread_count > 0)
886 {
887 assert(fsread_count > 0);
888 // LP: wrapped the routines with some for finding out the file positions;
889 // this finds out how much is read indirectly
890 status = File.Read(fsread_count,replay.fsread_buffer+replay.bytes_in_cache);
891 int32 CurrPos;
892 File.GetPosition(CurrPos);
893 int32 new_fsread_count = CurrPos - PrevPos;
894 int32 FileLen;
895 File.GetLength(FileLen);
896 HitEOF = (new_fsread_count < fsread_count) && (CurrPos == FileLen);
897 fsread_count = new_fsread_count;
898 if(status) replay.bytes_in_cache += fsread_count;
899 }
900 }
901
902 // If we're still low, then we've consumed the disk cache
903 if(replay.bytes_in_cache < *count)
904 {
905 HitEOF = true;
906 }
907
908 // Ignore EOF if we still have cache
909 if (HitEOF && replay.bytes_in_cache < *count)
910 {
911 *count= replay.bytes_in_cache;
912 }
913 else
914 {
915 status = true;
916 HitEOF = false;
917 }
918
919 memcpy(dest, replay.location_in_cache, *count);
920 replay.bytes_in_cache -= *count;
921 replay.location_in_cache += *count;
922
923 return status;
924 }
925
remove_input_controller(void)926 static void remove_input_controller(
927 void)
928 {
929 remove_timer_task(input_task);
930 if (replay.game_is_being_recorded)
931 {
932 stop_recording();
933 }
934 else if (replay.game_is_being_replayed)
935 {
936 if (replay.resource_data)
937 {
938 delete []replay.resource_data;
939 replay.resource_data= NULL;
940 replay.resource_data_size= 0l;
941 replay.film_resource_offset= NONE;
942 }
943 else
944 {
945 FilmFile.Close();
946 }
947 }
948
949 replay.valid= false;
950 }
951
952
reset_mml_keyboard()953 void reset_mml_keyboard()
954 {
955 // no reset
956 }
957
parse_mml_keyboard(const InfoTree & root)958 void parse_mml_keyboard(const InfoTree& root)
959 {
960 int16 which_set;
961 if (!root.read_indexed("set", which_set, NUMBER_OF_KEY_SETUPS))
962 return;
963
964 BOOST_FOREACH(InfoTree ktree, root.children_named("key"))
965 {
966 int16 index;
967 if (!ktree.read_indexed("index", index, NUMBER_OF_STANDARD_KEY_DEFINITIONS))
968 continue;
969
970 int16 keycode;
971 if (!ktree.read_attr("sdl", keycode))
972 continue;
973
974 all_key_definitions[which_set][index].offset = static_cast<SDL_Scancode>(keycode);
975 }
976 }
977
978
StreamToPlayerStart(uint8 * & S,player_start_data & Object)979 static void StreamToPlayerStart(uint8* &S, player_start_data& Object)
980 {
981 StreamToValue(S,Object.team);
982 StreamToValue(S,Object.identifier);
983 StreamToValue(S,Object.color);
984 StreamToBytes(S,Object.name,MAXIMUM_PLAYER_START_NAME_LENGTH+2);
985 }
986
PlayerStartToStream(uint8 * & S,player_start_data & Object)987 static void PlayerStartToStream(uint8* &S, player_start_data& Object)
988 {
989 ValueToStream(S,Object.team);
990 ValueToStream(S,Object.identifier);
991 ValueToStream(S,Object.color);
992 BytesToStream(S,Object.name,MAXIMUM_PLAYER_START_NAME_LENGTH+2);
993 }
994
995
StreamToGameData(uint8 * & S,game_data & Object)996 static void StreamToGameData(uint8* &S, game_data& Object)
997 {
998 StreamToValue(S,Object.game_time_remaining);
999 StreamToValue(S,Object.game_type);
1000 StreamToValue(S,Object.game_options);
1001 StreamToValue(S,Object.kill_limit);
1002 StreamToValue(S,Object.initial_random_seed);
1003 StreamToValue(S,Object.difficulty_level);
1004 StreamToList(S,Object.parameters,2);
1005 }
1006
GameDataToStream(uint8 * & S,game_data & Object)1007 static void GameDataToStream(uint8* &S, game_data& Object)
1008 {
1009 ValueToStream(S,Object.game_time_remaining);
1010 ValueToStream(S,Object.game_type);
1011 ValueToStream(S,Object.game_options);
1012 ValueToStream(S,Object.kill_limit);
1013 ValueToStream(S,Object.initial_random_seed);
1014 ValueToStream(S,Object.difficulty_level);
1015 ListToStream(S,Object.parameters,2);
1016 }
1017
1018
unpack_recording_header(uint8 * Stream,recording_header * Objects,size_t Count)1019 uint8 *unpack_recording_header(uint8 *Stream, recording_header *Objects, size_t Count)
1020 {
1021 uint8* S = Stream;
1022 recording_header* ObjPtr = Objects;
1023
1024 for (size_t k = 0; k < Count; k++, ObjPtr++)
1025 {
1026 StreamToValue(S,ObjPtr->length);
1027 StreamToValue(S,ObjPtr->num_players);
1028 StreamToValue(S,ObjPtr->level_number);
1029 StreamToValue(S,ObjPtr->map_checksum);
1030 StreamToValue(S,ObjPtr->version);
1031 for (int m = 0; m < MAXIMUM_NUMBER_OF_PLAYERS; m++)
1032 StreamToPlayerStart(S,ObjPtr->starts[m]);
1033 StreamToGameData(S,ObjPtr->game_information);
1034 }
1035
1036 assert(static_cast<size_t>(S - Stream) == (Count*SIZEOF_recording_header));
1037 return S;
1038 }
1039
pack_recording_header(uint8 * Stream,recording_header * Objects,size_t Count)1040 uint8 *pack_recording_header(uint8 *Stream, recording_header *Objects, size_t Count)
1041 {
1042 uint8* S = Stream;
1043 recording_header* ObjPtr = Objects;
1044
1045 for (size_t k = 0; k < Count; k++, ObjPtr++)
1046 {
1047 ValueToStream(S,ObjPtr->length);
1048 ValueToStream(S,ObjPtr->num_players);
1049 ValueToStream(S,ObjPtr->level_number);
1050 ValueToStream(S,ObjPtr->map_checksum);
1051 ValueToStream(S,ObjPtr->version);
1052 for (size_t m = 0; m < MAXIMUM_NUMBER_OF_PLAYERS; m++)
1053 PlayerStartToStream(S,ObjPtr->starts[m]);
1054 GameDataToStream(S,ObjPtr->game_information);
1055 }
1056
1057 assert(static_cast<size_t>(S - Stream) == (Count*SIZEOF_recording_header));
1058 return S;
1059 }
1060
1061 // Constants
1062 #define MAXIMUM_FLAG_PERSISTENCE 15
1063 #define DOUBLE_CLICK_PERSISTENCE 10
1064 #define FILM_RESOURCE_TYPE FOUR_CHARS_TO_INT('f', 'i', 'l', 'm')
1065
1066 #define NUMBER_OF_SPECIAL_FLAGS (sizeof(special_flags)/sizeof(struct special_flag_data))
1067 static struct special_flag_data special_flags[]=
1068 {
1069 {_double_flag, _look_dont_turn, _looking_center},
1070 {_double_flag, _run_dont_walk, _action_trigger_state},
1071 {_latched_flag, _action_trigger_state},
1072 {_latched_flag, _cycle_weapons_forward},
1073 {_latched_flag, _cycle_weapons_backward},
1074 {_latched_flag, _toggle_map}
1075 };
1076
1077
1078 /*
1079 * Get FileDesc for replay, ask user if desired
1080 */
1081
find_replay_to_use(bool ask_user,FileSpecifier & file)1082 bool find_replay_to_use(bool ask_user, FileSpecifier &file)
1083 {
1084 if (ask_user) {
1085 return file.ReadDialog(_typecode_film);
1086 } else
1087 return get_recording_filedesc(file);
1088 }
1089
1090
1091 /*
1092 * Get FileDesc for default recording file
1093 */
1094
get_recording_filedesc(FileSpecifier & File)1095 bool get_recording_filedesc(FileSpecifier &File)
1096 {
1097 File.SetToLocalDataDir();
1098 File += getcstr(temporary, strFILENAMES, filenameMARATHON_RECORDING);
1099 return File.Exists();
1100 }
1101
1102
1103 /*
1104 * Save film buffer to user-selected file
1105 */
1106
move_replay(void)1107 void move_replay(void)
1108 {
1109 // Get source file specification
1110 FileSpecifier src_file, dst_file;
1111 if (!get_recording_filedesc(src_file))
1112 return;
1113
1114 // Ask user for destination file
1115 char prompt[256], default_name[256];
1116 if (!dst_file.WriteDialog(_typecode_film, getcstr(prompt, strPROMPTS, _save_replay_prompt), getcstr(default_name, strFILENAMES, filenameMARATHON_RECORDING)))
1117 return;
1118
1119 // Copy file
1120 dst_file.CopyContents(src_file);
1121 int error = dst_file.GetError();
1122 if (error)
1123 alert_user(infoError, strERRORS, fileError, error);
1124 }
1125
1126
1127 /*
1128 * Poll keyboard and return action flags
1129 */
1130
parse_keymap(void)1131 uint32 parse_keymap(void)
1132 {
1133 uint32 flags = 0;
1134
1135 if(get_keyboard_controller_status())
1136 {
1137 Uint8 key_map[SDL_NUM_SCANCODES];
1138 if (Console::instance()->input_active()) {
1139 memset(key_map, 0, sizeof(key_map));
1140 } else {
1141 memcpy(key_map, SDL_GetKeyboardState(NULL), sizeof(key_map));
1142 }
1143
1144 // ZZZ: let mouse code simulate keypresses
1145 mouse_buttons_become_keypresses(key_map);
1146 joystick_buttons_become_keypresses(key_map);
1147
1148 // Parse the keymap
1149 for (int i = 0; i < NUMBER_OF_STANDARD_KEY_DEFINITIONS; ++i)
1150 {
1151 BOOST_FOREACH(const SDL_Scancode& code, input_preferences->key_bindings[i])
1152 {
1153 if (key_map[code])
1154 flags |= standard_key_definitions[i].action_flag;
1155 }
1156 }
1157
1158 // Post-process the keymap
1159 struct special_flag_data *special = special_flags;
1160 for (unsigned i=0; i<NUMBER_OF_SPECIAL_FLAGS; i++, special++) {
1161 if (flags & special->flag) {
1162 switch (special->type) {
1163 case _double_flag:
1164 // If this flag has a double-click flag and has been hit within
1165 // DOUBLE_CLICK_PERSISTENCE (but not at MAXIMUM_FLAG_PERSISTENCE),
1166 // mask on the double-click flag */
1167 if (special->persistence < MAXIMUM_FLAG_PERSISTENCE
1168 && special->persistence > MAXIMUM_FLAG_PERSISTENCE - DOUBLE_CLICK_PERSISTENCE)
1169 flags |= special->alternate_flag;
1170 break;
1171
1172 case _latched_flag:
1173 // If this flag is latched and still being held down, mask it out
1174 if (special->persistence == MAXIMUM_FLAG_PERSISTENCE)
1175 flags &= ~special->flag;
1176 break;
1177
1178 default:
1179 assert(false);
1180 break;
1181 }
1182
1183 special->persistence = MAXIMUM_FLAG_PERSISTENCE;
1184 } else
1185 special->persistence = FLOOR(special->persistence-1, 0);
1186 }
1187
1188
1189 bool do_interchange =
1190 (local_player->variables.flags & _HEAD_BELOW_MEDIA_BIT) ?
1191 (input_preferences->modifiers & _inputmod_interchange_swim_sink) != 0:
1192 (input_preferences->modifiers & _inputmod_interchange_run_walk) != 0;
1193
1194 // Handle the selected input controller
1195 if (input_preferences->input_device == _mouse_yaw_pitch) {
1196 flags = process_aim_input(flags, pull_mouselook_delta());
1197 }
1198 int joyflags = process_joystick_axes(flags, heartbeat_count);
1199 if (joyflags != flags) {
1200 flags = joyflags;
1201 }
1202
1203 // Modify flags with run/walk and swim/sink
1204 if (do_interchange)
1205 flags ^= _run_dont_walk;
1206
1207
1208 if (player_in_terminal_mode(local_player_index))
1209 flags = build_terminal_action_flags((char *)key_map);
1210 } // if(get_keyboard_controller_status())
1211
1212 return flags;
1213 }
1214
1215
1216 /*
1217 * Get random demo replay from map
1218 */
1219
setup_replay_from_random_resource(uint32 map_checksum)1220 bool setup_replay_from_random_resource(uint32 map_checksum)
1221 {
1222 // not supported in SDL version
1223 return false;
1224 }
1225
1226
1227 /*
1228 * Periodic task management
1229 */
1230
1231 typedef bool (*timer_func)(void);
1232
1233 static timer_func tm_func = NULL; // The installed timer task
1234 static uint32 tm_period; // Ticks between two calls of the timer task
1235 static uint32 tm_last = 0, tm_accum = 0;
1236
install_timer_task(short tasks_per_second,timer_func func)1237 timer_task_proc install_timer_task(short tasks_per_second, timer_func func)
1238 {
1239 // We only handle one task, which is enough
1240 tm_period = 1000 / tasks_per_second;
1241 tm_func = func;
1242 tm_last = SDL_GetTicks();
1243 tm_accum = 0;
1244 return (timer_task_proc)tm_func;
1245 }
1246
remove_timer_task(timer_task_proc proc)1247 void remove_timer_task(timer_task_proc proc)
1248 {
1249 tm_func = NULL;
1250 }
1251
execute_timer_tasks(uint32 time)1252 void execute_timer_tasks(uint32 time)
1253 {
1254 if (tm_func) {
1255 if (Movie::instance()->IsRecording()) {
1256 tm_func();
1257 return;
1258 }
1259 uint32 now = time;
1260 tm_accum += now - tm_last;
1261 tm_last = now;
1262 bool first_time = true;
1263 while (tm_accum >= tm_period) {
1264 tm_accum -= tm_period;
1265 if (first_time) {
1266 if(get_keyboard_controller_status())
1267 mouse_idle(input_preferences->input_device);
1268
1269 first_time = false;
1270 }
1271 tm_func();
1272 }
1273 }
1274 }
1275