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