/* VBL.C Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc. and the "Aleph One" developers. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This license is contained in the file "COPYING", which is included with this source code; it is available online at http://www.gnu.org/licenses/gpl.html Friday, August 21, 1992 7:06:54 PM Tuesday, November 17, 1992 3:53:29 PM the new task of the vbl controller is only to move the player. this is necessary for good control of the game. everything else (doors, monsters, projectiles, etc) will be moved immediately before the next frame is drawn, based on delta-time values. collisions (including the player with walls) will also be handled at this time. Thursday, November 19, 1992 1:27:23 AM the enumeration 'turning_head' had to be changed to '_turn_not_rotate' to make this file compile. go figure. Wednesday, December 2, 1992 2:31:05 PM the world doesnÕt change while the mouse button is pressed. Friday, January 15, 1993 11:19:11 AM the world doesnÕt change after 14 ticks have passed without a screen refresh. Friday, January 22, 1993 3:06:32 PM world_ticks was never being initialized to zero. hmmm. Saturday, March 6, 1993 12:23:48 PM at exit, we remove our vbl task. Sunday, May 16, 1993 4:07:47 PM finally recoding everything Monday, August 16, 1993 10:22:17 AM #ifdef CHARLES added. Saturday, August 21, 1993 12:35:29 PM from pathways VBL_CONTROLLER.C. Sunday, May 22, 1994 8:51:15 PM all the world physics has been moved into PHYSICS.C; all we do now is maintain and distribute a circular queue of keyboard flags (we're the keyboard_controller, not the movement_controller). Thursday, June 2, 1994 12:55:52 PM gee, now we donÕt even maintain the queue we just send our actions to PLAYER.C. Tuesday, July 5, 1994 9:27:49 PM nuked most of the shit in here. changed the vbl task to a time manager task. the only functions from the old vbl.c that remain are precalculate_key_information() and parse_keymap(). Thursday, July 7, 1994 11:59:32 AM Added recording/replaying Wednesday, August 10, 1994 2:44:57 PM added caching system for FSRead. Friday, January 13, 1995 11:38:51 AM (Jason') fixed the 'a' key getting blacklisted. Jan 30, 2000 (Loren Petrich) Did some typecasts Jul 7, 2000 (Loren Petrich) Added Ben Thompson's ISp-support changes Aug 12, 2000 (Loren Petrich): Using object-oriented file handler Aug 26, 2000 (Loren Petrich): Created alternative to SetLength(): delete a file, then re-create it. This should be more stdio-friendly. Feb 20, 2002 (Woody Zenfell): Uses GetRealActionQueues()->enqueueActionFlags() rather than queue_action_flags(). */ #include "cseries.h" #include #include #include "map.h" #include "interface.h" #include "shell.h" #include "preferences.h" #include "mouse.h" #include "player.h" #include "key_definitions.h" #include "tags.h" #include "vbl.h" #include "ISp_Support.h" /* BT: Added April 16, 2000 for Input Sprocket Support */ #include "FileHandler.h" #include "Packing.h" #include "ActionQueues.h" #include "computer_interface.h" #include "Console.h" #ifdef env68k #pragma segment input #endif /* ---------- constants */ #define RECORD_CHUNK_SIZE (MAXIMUM_QUEUE_SIZE/2) #define END_OF_RECORDING_INDICATOR (RECORD_CHUNK_SIZE+1) #define MAXIMUM_TIME_DIFFERENCE 15 // allowed between heartbeat_count and dynamic_world->tick_count #define MAXIMUM_NET_QUEUE_SIZE 8 #define DISK_CACHE_SIZE ((sizeof(int16)+sizeof(uint32))*100) #define MAXIMUM_REPLAY_SPEED 5 #define MINIMUM_REPLAY_SPEED -5 /* ---------- macros */ #define INCREMENT_QUEUE_COUNTER(c) { (c)++; if ((c)>=MAXIMUM_QUEUE_SIZE) (c) = 0; } // LP: fake portable-files stuff #ifdef mac inline short memory_error() {return MemError();} #else inline short memory_error() {return 0;} #endif /* ---------- structures */ #include "vbl_definitions.h" /* ---------- globals */ static long heartbeat_count; static bool input_task_active; static timer_task_proc input_task; // LP: defined this here so it will work properly static FileSpecifier FilmFileSpec; static OpenedFile FilmFile; /* Not static because vbl_macintosh.c uses this.. */ struct key_definition current_key_definitions[NUMBER_OF_STANDARD_KEY_DEFINITIONS]; struct replay_private_data replay; #ifdef DEBUG ActionQueue *get_player_recording_queue( short player_index) { assert(replay.recording_queues); assert(player_index>=0 && player_indexread_index= queue->write_index = 0; queue->buffer= new uint32[MAXIMUM_QUEUE_SIZE]; if(!queue->buffer) alert_user(fatalError, strERRORS, outOfMemory, memory_error()); } enter_mouse(0); } void set_keyboard_controller_status( bool active) { input_task_active= active; /******************************************************************************************/ /* BT: Added April 16, 2000 ISp: De-activate Input Sprockets while the keyboard is not active. This also means that the game is currently not recieving keystrokes */ #ifdef mac if(active) Start_ISp(); else Stop_ISp(); #endif #if TARGET_API_MAC_CARBON if (active) enter_mouse(0); else exit_mouse(0); #endif #ifdef SDL // We enable/disable mouse control here if (active) enter_mouse(input_preferences->input_device); else exit_mouse(input_preferences->input_device); #endif /******************************************************************************************/ } bool get_keyboard_controller_status( void) { return input_task_active; } long get_heartbeat_count( void) { return heartbeat_count; } void sync_heartbeat_count( void) { heartbeat_count= dynamic_world->tick_count; } void increment_replay_speed( void) { if (replay.replay_speed < MAXIMUM_REPLAY_SPEED) replay.replay_speed++; } void decrement_replay_speed( void) { if (replay.replay_speed > MINIMUM_REPLAY_SPEED) replay.replay_speed--; } void increment_heartbeat_count(int value) { heartbeat_count+=value; } /* Returns NONE if it is custom.. */ short find_key_setup( short *keycodes) { short key_setup= NONE; for (unsigned index= 0; key_setup==NONE && index= 0 && which_default < NUMBER_OF_KEY_SETUPS); definitions= all_key_definitions[which_default]; for (unsigned i= 0; i < NUMBER_OF_STANDARD_KEY_DEFINITIONS; i++) { keycodes[i] = definitions[i].offset; } } void set_keys( short *keycodes) { struct key_definition *definitions; /* all of them have the same ordering, so which one we pick is moot. */ definitions = all_key_definitions[_standard_keyboard_setup]; for (unsigned index= 0; indextick_count) < MAXIMUM_TIME_DIFFERENCE) { if (game_is_networked) // input from network { ; // all handled elsewhere now. (in network.c) } else if (replay.game_is_being_replayed) // input from recorded game file { static short phase= 0; /* When this gets to 0, update the world */ /* Minimum replay speed is a pause. */ if(replay.replay_speed != MINIMUM_REPLAY_SPEED) { if (replay.replay_speed > 0 || (--phase<=0)) { short flag_count= MAX(replay.replay_speed, 1); if (!pull_flags_from_recording(flag_count)) // oops. silly me. { if (replay.have_read_last_chunk) { assert(get_game_state()==_game_in_progress || get_game_state()==_switch_demo); set_game_state(_switch_demo); } } else { /* Increment the heartbeat.. */ heartbeat_count+= flag_count; } /* Reset the phase-> doesn't matter if the replay speed is positive */ /* +1 so that replay_speed 0 is different from replay_speed 1 */ phase= -(replay.replay_speed) + 1; } } } else // then getting input from the keyboard/mouse { uint32 action_flags= parse_keymap(); process_action_flags(local_player_index, &action_flags, 1); heartbeat_count++; // ba-doom } } else { // dprintf("Out of phase.. (%d);g", heartbeat_count - dynamic_world->tick_count); } } return true; // tells the time manager library to reschedule this task } void process_action_flags( short player_identifier, const uint32 *action_flags, short count) { if (replay.game_is_being_recorded) { record_action_flags(player_identifier, action_flags, count); } GetRealActionQueues()->enqueueActionFlags(player_identifier, action_flags, count); } static void record_action_flags( short player_identifier, const uint32 *action_flags, short count) { short index; ActionQueue *queue; queue= get_player_recording_queue(player_identifier); assert(queue && queue->write_index >= 0 && queue->write_index < MAXIMUM_QUEUE_SIZE); for (index= 0; indexbuffer + queue->write_index) = *action_flags++; INCREMENT_QUEUE_COUNTER(queue->write_index); if (queue->write_index == queue->read_index) { dprintf("blew recording queue for player %d", player_identifier); } } } /********************************************************************************************* * * Function: save_recording_queue_chunk * Purpose: saves one chunk of the queue to the recording file, using run-length encoding. * *********************************************************************************************/ void save_recording_queue_chunk( short player_index) { uint8 *location; uint32 last_flag, count, flag = 0; int16 i, run_count, num_flags_saved, max_flags; static uint8 *buffer= NULL; ActionQueue *queue; // The data format is (run length (int16)) + (action flag (uint32)) int DataSize = sizeof(int16) + sizeof(uint32); if (buffer == NULL) buffer = new byte[RECORD_CHUNK_SIZE * DataSize]; location= buffer; count= 0; // keeps track of how many bytes we'll save. last_flag= (uint32)NONE; queue= get_player_recording_queue(player_index); // don't want to save too much stuff max_flags= MIN(RECORD_CHUNK_SIZE, get_recording_queue_size(player_index)); // save what's in the queue run_count= num_flags_saved= 0; for (i = 0; ibuffer[queue->read_index]; INCREMENT_QUEUE_COUNTER(queue->read_index); if (i && flag != last_flag) { ValueToStream(location,run_count); ValueToStream(location,last_flag); count += DataSize; num_flags_saved += run_count; run_count = 1; } else { run_count++; } last_flag = flag; } // now save the final run ValueToStream(location,run_count); ValueToStream(location,last_flag); count += DataSize; num_flags_saved += run_count; if (max_flagsplayer_count; player_index++) { if(get_recording_queue_size(player_index)==0) success= false; } if(success) { for (player_index = 0; player_index < dynamic_world->player_count; player_index++) { short index; ActionQueue *queue; queue= get_player_recording_queue(player_index); for (index= 0; indexread_index != queue->write_index) { #ifdef DEBUG_REPLAY debug_stream_of_flags(*(queue->buffer+queue->read_index), player_index); #endif GetRealActionQueues()->enqueueActionFlags(player_index, queue->buffer + queue->read_index, 1); INCREMENT_QUEUE_COUNTER(queue->read_index); } else { dprintf("Dropping flag?"); } } } } return success; } static short get_recording_queue_size( short which_queue) { short size; ActionQueue *queue= get_player_recording_queue(which_queue); /* Note that this is a circular queue */ size= queue->write_index-queue->read_index; if(size<0) size+= MAXIMUM_QUEUE_SIZE; return size; } static void precalculate_key_information( void) { #ifndef SDL /* convert raw key codes to offets and masks */ for (size_t i = 0; i < NUMBER_OF_STANDARD_KEY_DEFINITIONS; ++i) { current_key_definitions[i].mask = 1 << (current_key_definitions[i].offset&7); current_key_definitions[i].offset >>= 3; } #endif } void 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) { assert(!replay.valid); obj_clear(replay.header); replay.header.num_players= number_of_players; replay.header.level_number= level_number; replay.header.map_checksum= map_checksum; replay.header.version= version; objlist_copy(replay.header.starts, starts, MAXIMUM_NUMBER_OF_PLAYERS); obj_copy(replay.header.game_information, *game_information); // Use the packed size here!!! replay.header.length= SIZEOF_recording_header; } void 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) { assert(replay.valid); *number_of_players= replay.header.num_players; *level_number= replay.header.level_number; *map_checksum= replay.header.map_checksum; *version= replay.header.version; objlist_copy(starts, replay.header.starts, MAXIMUM_NUMBER_OF_PLAYERS); obj_copy(*game_information, replay.header.game_information); } bool setup_for_replay_from_file( FileSpecifier& File, uint32 map_checksum) { bool successful= false; (void)(map_checksum); FilmFileSpec = File; if (FilmFileSpec.Open(FilmFile)) { replay.valid= true; replay.have_read_last_chunk = false; replay.game_is_being_replayed = true; assert(!replay.resource_data); replay.resource_data= NULL; replay.resource_data_size= 0l; replay.film_resource_offset= NONE; byte Header[SIZEOF_recording_header]; FilmFile.Read(SIZEOF_recording_header,Header); unpack_recording_header(Header,&replay.header,1); /* Set to the mapfile this replay came from.. */ if(use_map_file(replay.header.map_checksum)) { replay.fsread_buffer= new char[DISK_CACHE_SIZE]; assert(replay.fsread_buffer); replay.location_in_cache= NULL; replay.bytes_in_cache= 0; replay.replay_speed= 1; #ifdef DEBUG_REPLAY open_stream_file(); #endif successful= true; } else { /* Tell them that this map wasn't found. They lose. */ alert_user(infoError, strERRORS, cantFindReplayMap, 0); } } return successful; } /* Note that we _must_ set the header information before we start recording!! */ void start_recording( void) { assert(!replay.valid); replay.valid= true; if(get_recording_filedesc(FilmFileSpec)) FilmFileSpec.Delete(); if (FilmFileSpec.Create(_typecode_film)) { /* I debate the validity of fsCurPerm here, but Alain had it, and it has been working */ if (FilmFileSpec.Open(FilmFile,true)) { replay.game_is_being_recorded= true; // save a header containing information about the game. byte Header[SIZEOF_recording_header]; pack_recording_header(Header,&replay.header,1); FilmFile.Write(SIZEOF_recording_header,Header); } } } void stop_recording( void) { if (replay.game_is_being_recorded) { replay.game_is_being_recorded = false; short player_index; long total_length; assert(replay.valid); for (player_index= 0; player_indexplayer_count; player_index++) { save_recording_queue_chunk(player_index); } /* Rewrite the header, since it has the new length */ FilmFile.SetPosition(0); byte Header[SIZEOF_recording_header]; pack_recording_header(Header,&replay.header,1); // ZZZ: removing code that does stuff from assert() argument. BUT... // should we really be asserting on this anyway? I mean, the write could fail // in 'normal operation' too, not just when we screwed something up in writing the program? bool successfulWrite = FilmFile.Write(SIZEOF_recording_header,Header); assert(successfulWrite); FilmFile.GetLength(total_length); assert(total_length==replay.header.length); FilmFile.Close(); } replay.valid= false; } void rewind_recording( void) { if(replay.game_is_being_recorded) { /* This is unnecessary, because it is called from reset_player_queues, */ /* which is always called from revert_game */ /* FilmFile.SetLength(sizeof(recording_header)); FilmFile.SetPosition(sizeof(recording_header)); */ // Alternative that does not use "SetLength", but instead creates and re-creates the file. FilmFile.SetPosition(0); byte Header[SIZEOF_recording_header]; FilmFile.Read(SIZEOF_recording_header,Header); FilmFile.Close(); FilmFileSpec.Delete(); FilmFileSpec.Create(_typecode_film); FilmFileSpec.Open(FilmFile,true); FilmFile.Write(SIZEOF_recording_header,Header); // Use the packed length here!!! replay.header.length= SIZEOF_recording_header; } } void check_recording_replaying( void) { short player_index, queue_size; if (replay.game_is_being_recorded) { bool enough_data_to_save= true; // it's time to save the queues if all of them have >= RECORD_CHUNK_SIZE flags in them. for (player_index= 0; enough_data_to_save && player_indexplayer_count; player_index++) { queue_size= get_recording_queue_size(player_index); if (queue_size < RECORD_CHUNK_SIZE) enough_data_to_save= false; } if(enough_data_to_save) { bool success; unsigned long freespace = 0; FileSpecifier FilmFile_Check; get_recording_filedesc(FilmFile_Check); success= FilmFile_Check.GetFreeSpace(freespace); if (success && freespace>(RECORD_CHUNK_SIZE*sizeof(int16)*sizeof(uint32)*dynamic_world->player_count)) { for (player_index= 0; player_indexplayer_count; player_index++) { save_recording_queue_chunk(player_index); } } } } else if (replay.game_is_being_replayed) { bool load_new_data= true; // it's time to refill the requeues if they all have < RECORD_CHUNK_SIZE flags in them. for (player_index= 0; load_new_data && player_indexplayer_count; player_index++) { queue_size= get_recording_queue_size(player_index); if(queue_size>= RECORD_CHUNK_SIZE) load_new_data= false; } if(load_new_data) { // at this point, we've determined that the queues are sufficently empty, so // we'll fill 'em up. read_recording_queue_chunks(); } } } void reset_recording_and_playback_queues( void) { short index; for(index= 0; indexplayer_count; player_index++) { queue= get_player_recording_queue(player_index); for (count = 0; count < RECORD_CHUNK_SIZE; ) { if (replay.resource_data) { bool hit_end= false; if (replay.film_resource_offset >= replay.resource_data_size) { hit_end = true; } else { uint8* S; S = (uint8 *)(replay.resource_data + replay.film_resource_offset); StreamToValue(S,num_flags); replay.film_resource_offset += sizeof(num_flags); S = (uint8 *)(replay.resource_data + replay.film_resource_offset); StreamToValue(S,action_flags); replay.film_resource_offset+= sizeof(action_flags); } if (hit_end || num_flags == END_OF_RECORDING_INDICATOR) { replay.have_read_last_chunk= true; break; } } else { sizeof_read = sizeof(num_flags); uint8 NumFlagsBuffer[sizeof(num_flags)]; bool HitEOF = false; if (vblFSRead(FilmFile, &sizeof_read, NumFlagsBuffer, HitEOF)) { uint8 *S = NumFlagsBuffer; StreamToValue(S,num_flags); sizeof_read = sizeof(action_flags); uint8 ActionFlagsBuffer[sizeof(action_flags)]; bool status = vblFSRead(FilmFile, &sizeof_read, ActionFlagsBuffer, HitEOF); S = ActionFlagsBuffer; StreamToValue(S,action_flags); assert(status || (HitEOF && sizeof_read == sizeof(action_flags))); } if ((HitEOF && sizeof_read != sizeof(action_flags)) || num_flags == END_OF_RECORDING_INDICATOR) { replay.have_read_last_chunk = true; break; } } assert(replay.have_read_last_chunk || num_flags); count += num_flags; vassert((num_flags != 0 && count <= RECORD_CHUNK_SIZE) || replay.have_read_last_chunk, csprintf(temporary, "num_flags = %d, count = %d", num_flags, count)); for (i = 0; i < num_flags; i++) { *(queue->buffer + queue->write_index) = action_flags; INCREMENT_QUEUE_COUNTER(queue->write_index); assert(queue->read_index != queue->write_index); } } assert(replay.have_read_last_chunk || count == RECORD_CHUNK_SIZE); } } /* This is gross, (Alain wrote it, not me!) but I don't have time to clean it up */ static bool vblFSRead( OpenedFile& File, long *count, void *dest, bool& HitEOF) { long fsread_count; bool status = true; assert(replay.fsread_buffer); // LP: way for testing whether hitting end-of-file; // doing that by testing for whether a read was complete. HitEOF = false; if (replay.bytes_in_cache < *count) { assert(replay.bytes_in_cache + *count < int(DISK_CACHE_SIZE)); if (replay.bytes_in_cache) { memcpy(replay.fsread_buffer, replay.location_in_cache, replay.bytes_in_cache); } replay.location_in_cache = replay.fsread_buffer; fsread_count= DISK_CACHE_SIZE - replay.bytes_in_cache; long PrevPos; File.GetPosition(PrevPos); long replay_left= replay.header.length - PrevPos; if(replay_left < fsread_count) fsread_count= replay_left; if(fsread_count > 0) { assert(fsread_count > 0); // LP: wrapped the routines with some for finding out the file positions; // this finds out how much is read indirectly status = File.Read(fsread_count,replay.fsread_buffer+replay.bytes_in_cache); long CurrPos; File.GetPosition(CurrPos); long new_fsread_count = CurrPos - PrevPos; long FileLen; File.GetLength(FileLen); HitEOF = (new_fsread_count < fsread_count) && (CurrPos == FileLen); fsread_count = new_fsread_count; if(status) replay.bytes_in_cache += fsread_count; } } // If we're still low, then we've consumed the disk cache if(replay.bytes_in_cache < *count) { HitEOF = true; } // Ignore EOF if we still have cache if (HitEOF && replay.bytes_in_cache < *count) { *count= replay.bytes_in_cache; } else { status = true; HitEOF = false; } memcpy(dest, replay.location_in_cache, *count); replay.bytes_in_cache -= *count; replay.location_in_cache += *count; return status; } static void remove_input_controller( void) { remove_timer_task(input_task); if (replay.game_is_being_recorded) { stop_recording(); } else if (replay.game_is_being_replayed) { if (replay.resource_data) { delete []replay.resource_data; replay.resource_data= NULL; replay.resource_data_size= 0l; replay.film_resource_offset= NONE; } else { FilmFile.Close(); } } replay.valid= false; } // Which key-value set to change key definitions of static key_definition *XML_KeySet = all_key_definitions[0]; class XML_KeyParser: public XML_ElementParser { bool IsPresent[2]; short Index, Key; public: bool Start(); bool HandleAttribute(const char *Tag, const char *Value); bool AttributesDone(); XML_KeyParser(): XML_ElementParser("key") {} }; bool XML_KeyParser::Start() { IsPresent[0] = IsPresent[1] = false; return true; } bool XML_KeyParser::HandleAttribute(const char *Tag, const char *Value) { if (StringsEqual(Tag,"index")) { if (ReadBoundedInt16Value(Value,Index,0,NUMBER_OF_STANDARD_KEY_DEFINITIONS-1)) { IsPresent[0] = true; return true; } else return false; } // Select whether to accept a SDL or a Macintosh key definition #ifdef SDL else if (StringsEqual(Tag,"sdl")) #else else if (StringsEqual(Tag,"mac")) #endif { if (ReadInt16Value(Value,Key)) { IsPresent[1] = true; return true; } else return false; } // Do nothing in case of the opposite sort of key #ifdef SDL else if (StringsEqual(Tag,"mac")) #else else if (StringsEqual(Tag,"sdl")) #endif { // OK no matter what return true; } UnrecognizedTag(); return false; } bool XML_KeyParser::AttributesDone() { if (!(IsPresent[0]&&IsPresent[1])) return false; // Set the key value! #ifdef SDL XML_KeySet[Index].offset = (SDLKey)Key; #else XML_KeySet[Index].offset = Key; #endif return true; } static XML_KeyParser KeyParser; class XML_KeyboardParser: public XML_ElementParser { bool IsPresent; public: bool Start(); bool HandleAttribute(const char *Tag, const char *Value); bool AttributesDone(); XML_KeyboardParser(): XML_ElementParser("keyboard") {} }; bool XML_KeyboardParser::Start() { IsPresent = false; return true; } bool XML_KeyboardParser::HandleAttribute(const char *Tag, const char *Value) { if (StringsEqual(Tag,"set")) { short WhichSet = 0; if (ReadBoundedInt16Value(Value,WhichSet,0,NUMBER_OF_KEY_SETUPS-1)) { XML_KeySet = all_key_definitions[WhichSet]; IsPresent = true; return true; } else return false; } UnrecognizedTag(); return false; } bool XML_KeyboardParser::AttributesDone() { return IsPresent; } static XML_KeyboardParser KeyboardParser; // LP change: added keyboard export XML_ElementParser *Keyboard_GetParser() { KeyboardParser.AddChild(&KeyParser); return &KeyboardParser; } static void StreamToPlayerStart(uint8* &S, player_start_data& Object) { StreamToValue(S,Object.team); StreamToValue(S,Object.identifier); StreamToValue(S,Object.color); StreamToBytes(S,Object.name,MAXIMUM_PLAYER_START_NAME_LENGTH+2); } static void PlayerStartToStream(uint8* &S, player_start_data& Object) { ValueToStream(S,Object.team); ValueToStream(S,Object.identifier); ValueToStream(S,Object.color); BytesToStream(S,Object.name,MAXIMUM_PLAYER_START_NAME_LENGTH+2); } static void StreamToGameData(uint8* &S, game_data& Object) { StreamToValue(S,Object.game_time_remaining); StreamToValue(S,Object.game_type); StreamToValue(S,Object.game_options); StreamToValue(S,Object.kill_limit); StreamToValue(S,Object.initial_random_seed); StreamToValue(S,Object.difficulty_level); StreamToList(S,Object.parameters,2); } static void GameDataToStream(uint8* &S, game_data& Object) { ValueToStream(S,Object.game_time_remaining); ValueToStream(S,Object.game_type); ValueToStream(S,Object.game_options); ValueToStream(S,Object.kill_limit); ValueToStream(S,Object.initial_random_seed); ValueToStream(S,Object.difficulty_level); ListToStream(S,Object.parameters,2); } uint8 *unpack_recording_header(uint8 *Stream, recording_header *Objects, size_t Count) { uint8* S = Stream; recording_header* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { StreamToValue(S,ObjPtr->length); StreamToValue(S,ObjPtr->num_players); StreamToValue(S,ObjPtr->level_number); StreamToValue(S,ObjPtr->map_checksum); StreamToValue(S,ObjPtr->version); for (int m = 0; m < MAXIMUM_NUMBER_OF_PLAYERS; m++) StreamToPlayerStart(S,ObjPtr->starts[m]); StreamToGameData(S,ObjPtr->game_information); } assert(static_cast(S - Stream) == (Count*SIZEOF_recording_header)); return S; } uint8 *pack_recording_header(uint8 *Stream, recording_header *Objects, size_t Count) { uint8* S = Stream; recording_header* ObjPtr = Objects; for (size_t k = 0; k < Count; k++, ObjPtr++) { ValueToStream(S,ObjPtr->length); ValueToStream(S,ObjPtr->num_players); ValueToStream(S,ObjPtr->level_number); ValueToStream(S,ObjPtr->map_checksum); ValueToStream(S,ObjPtr->version); for (size_t m = 0; m < MAXIMUM_NUMBER_OF_PLAYERS; m++) PlayerStartToStream(S,ObjPtr->starts[m]); GameDataToStream(S,ObjPtr->game_information); } assert(static_cast(S - Stream) == (Count*SIZEOF_recording_header)); return S; } // Constants #define MAXIMUM_FLAG_PERSISTENCE 15 #define DOUBLE_CLICK_PERSISTENCE 10 #define FILM_RESOURCE_TYPE FOUR_CHARS_TO_INT('f', 'i', 'l', 'm') #define NUMBER_OF_SPECIAL_FLAGS (sizeof(special_flags)/sizeof(struct special_flag_data)) static struct special_flag_data special_flags[]= { {_double_flag, _look_dont_turn, _looking_center}, {_double_flag, _run_dont_walk, _action_trigger_state}, {_latched_flag, _action_trigger_state}, {_latched_flag, _cycle_weapons_forward}, {_latched_flag, _cycle_weapons_backward}, {_latched_flag, _toggle_map} }; /* * Get FileDesc for replay, ask user if desired */ bool find_replay_to_use(bool ask_user, FileSpecifier &file) { if (ask_user) { return file.ReadDialog(_typecode_film); } else return get_recording_filedesc(file); } /* * Get FileDesc for default recording file */ bool get_recording_filedesc(FileSpecifier &File) { File.SetToLocalDataDir(); File += getcstr(temporary, strFILENAMES, filenameMARATHON_RECORDING); return File.Exists(); } /* * Save film buffer to user-selected file */ void move_replay(void) { // Get source file specification FileSpecifier src_file, dst_file; if (!get_recording_filedesc(src_file)) return; // Ask user for destination file char prompt[256], default_name[256]; if (!dst_file.WriteDialog(_typecode_film, getcstr(prompt, strPROMPTS, _save_replay_prompt), getcstr(default_name, strFILENAMES, filenameMARATHON_RECORDING))) return; // Copy file dst_file.CopyContents(src_file); int error = dst_file.GetError(); if (error) alert_user(infoError, strERRORS, fileError, error); } /* * Poll keyboard and return action flags */ uint32 parse_keymap(void) { uint32 flags = 0; if(get_keyboard_controller_status()) { Uint8 *key_map; if (Console::instance()->input_active()) { static Uint8 chat_input_mode_keymap[SDLK_LAST]; memset(&chat_input_mode_keymap, 0, sizeof(chat_input_mode_keymap)); key_map = chat_input_mode_keymap; } else { key_map = SDL_GetKeyState(NULL); } // ZZZ: let mouse code simulate keypresses mouse_buttons_become_keypresses(key_map); // Parse the keymap key_definition *key = current_key_definitions; for (unsigned i=0; ioffset]) flags |= key->action_flag; // Post-process the keymap struct special_flag_data *special = special_flags; for (unsigned i=0; iflag) { switch (special->type) { case _double_flag: // If this flag has a double-click flag and has been hit within // DOUBLE_CLICK_PERSISTENCE (but not at MAXIMUM_FLAG_PERSISTENCE), // mask on the double-click flag */ if (special->persistence < MAXIMUM_FLAG_PERSISTENCE && special->persistence > MAXIMUM_FLAG_PERSISTENCE - DOUBLE_CLICK_PERSISTENCE) flags |= special->alternate_flag; break; case _latched_flag: // If this flag is latched and still being held down, mask it out if (special->persistence == MAXIMUM_FLAG_PERSISTENCE) flags &= ~special->flag; break; default: assert(false); break; } special->persistence = MAXIMUM_FLAG_PERSISTENCE; } else special->persistence = FLOOR(special->persistence-1, 0); } // Handle the selected input controller if (input_preferences->input_device != _keyboard_or_game_pad) { _fixed delta_yaw, delta_pitch, delta_velocity; test_mouse(input_preferences->input_device, &flags, &delta_yaw, &delta_pitch, &delta_velocity); flags = mask_in_absolute_positioning_information(flags, delta_yaw, delta_pitch, delta_velocity); } // Modify flags with run/walk and swim/sink bool do_interchange = (local_player->variables.flags & _HEAD_BELOW_MEDIA_BIT) ? (input_preferences->modifiers & _inputmod_interchange_swim_sink) != 0: (input_preferences->modifiers & _inputmod_interchange_run_walk) != 0; if (do_interchange) flags ^= _run_dont_walk; if (player_in_terminal_mode(local_player_index)) flags = build_terminal_action_flags((char *)key_map); } // if(get_keyboard_controller_status()) return flags; } /* * Get random demo replay from map */ bool setup_replay_from_random_resource(uint32 map_checksum) { // not supported in SDL version return false; } /* * Set keys to match preferences */ void set_keys_to_match_preferences(void) { set_keys(input_preferences->keycodes); } /* * Periodic task management */ typedef bool (*timer_func)(void); static timer_func tm_func = NULL; // The installed timer task static uint32 tm_period; // Ticks between two calls of the timer task static uint32 tm_last = 0, tm_accum = 0; timer_task_proc install_timer_task(short tasks_per_second, timer_func func) { // We only handle one task, which is enough tm_period = 1000 / tasks_per_second; tm_func = func; tm_last = SDL_GetTicks(); tm_accum = 0; return (timer_task_proc)tm_func; } void remove_timer_task(timer_task_proc proc) { tm_func = NULL; } void execute_timer_tasks(void) { if (tm_func) { uint32 now = SDL_GetTicks(); tm_accum += now - tm_last; tm_last = now; bool first_time = true; while (tm_accum >= tm_period) { tm_accum -= tm_period; if (first_time) { if(get_keyboard_controller_status()) mouse_idle(input_preferences->input_device); first_time = false; } tm_func(); } } }