// // Copyright 2020 Electronic Arts Inc. // // TiberianDawn.DLL and RedAlert.dll and corresponding source code 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 3 of the License, or (at your option) any later version. // TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed // in the hope that it will be useful, but with permitted additional restrictions // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT // distributed with this program. You should have received a copy of the // GNU General Public License along with permitted additional restrictions // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection /* $Header: /counterstrike/QUEUE.CPP 6 3/14/97 5:12p Steve_tall $ */ /*************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** * * * Project Name : Command & Conquer * * * * File Name : QUEUE.CPP * * * * Programmer : Bill R. Randolph * * * * Start Date : 11/28/95 * * * * Last Update : October 14, 1996 [BRR] * * * *-------------------------------------------------------------------------* * Functions for Queueing Events: * * Queue_Mission -- Queue a mega mission event. * * Queue_Options -- Queue the options event. * * Queue_Exit -- Add the exit game event to the queue. * * * * Functions for processing Queued Events: * * Queue_AI -- Process all queued events. * * Queue_AI_Normal -- Process all queued events. * * Queue_AI_Multiplayer -- Process all queued events. * * * * Main Multiplayer Queue Logic: * * Wait_For_Players -- Waits for other systems to come on-line * * Generate_Timing_Event -- computes & queues a RESPONSE_TIME event * * Process_Send_Period -- timing for sending packets every 'n' frames * * Send_Packets -- sends out events from the OutList * * Send_FrameSync -- Sends a FRAMESYNC packet * * Process_Receive_Packet -- processes an incoming packet * * Process_Serial_Packet -- Handles an incoming serial packet * * Can_Advance -- determines if it's OK to advance to the next frame * * Process_Reconnect_Dialog -- processes the reconnection dialog * * Handle_Timeout -- attempts to reconnect; if fails, bails. * * Stop_Game -- stops the game * * * * Packet Compression / Decompression: * * Build_Send_Packet -- Builds a big packet from a bunch of little ones. * * Add_Uncompressed_Events -- adds uncompressed events to a packet * * Add_Compressed_Events -- adds compressed events to a packet * * Breakup_Receive_Packet -- Splits a big packet into little ones. * * Extract_Uncompressed_Events -- extracts events from a packet * * Extract_Compressed_Events -- extracts events from a packet * * * * DoList Management: * * Execute_DoList -- Executes commands from the DoList * * Clean_DoList -- Cleans out old events from the DoList * * Queue_Record -- Records the DoList to disk * * Queue_Playback -- plays back queue entries from a record file * * * * Debugging: * * Compute_Game_CRC -- Computes a CRC value of the entire game. * * Add_CRC -- Adds a value to a CRC * * Print_CRCs -- Prints a data file for finding Sync Bugs * * Init_Queue_Mono -- inits mono display * * Update_Queue_Mono -- updates mono display * * Print_Framesync_Values -- displays frame-sync variables * * Check_Mirror -- Checks mirror memory * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" #ifdef WOLAPI_INTEGRATION //#include "WolDebug.h" #include "WolapiOb.h" extern WolapiObject* pWolapi; bool bReconnectDialogCancelled; #endif /********************************** Defines *********************************/ #define SHOW_MONO 0 /********************************** Globals *********************************/ //--------------------------------------------------------------------------- // GameCRC is the current computed CRC value for this frame. // CRC[] is a record of our last 32 game CRC's. // ColorNames is for debug output in Print_CRCs //--------------------------------------------------------------------------- static unsigned long GameCRC; static unsigned long CRC[32] = {0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0}; static char *ColorNames[8] = { "Yellow", "LtBlue", "Red", "Green", "Orange", "Grey", "Blue", "Brown" }; //........................................................................... // Mono debugging variables: // NetMonoMode: 0 = show connection output, 1 = flowcount output // NewMonoMode: set by anything that toggles NetMonoMode; re-inits screen // IsMono: used for taking control of Mono screen away from the engine //........................................................................... int NetMonoMode = 1; int NewMonoMode = 1; static int IsMono = 0; //--------------------------------------------------------------------------- // Several routines return various codes; here's an enum for all of them. //--------------------------------------------------------------------------- typedef enum RetcodeEnum { RC_NORMAL, // no news is good news RC_PLAYER_READY, // a new player has been heard from RC_SCENARIO_MISMATCH, // scenario mismatch RC_DOLIST_FULL, // DoList is full RC_SERIAL_PROCESSED, // modem: SERIAL packet was processed RC_PLAYER_LEFT, // modem: other player left the game RC_HUNG_UP, // modem has hung up RC_NOT_RESPONDING, // other player not responding (timeout/hung up) RC_CANCEL, // user cancelled } RetcodeType; #ifdef FIXIT_CSII // checked - ajw 9/28/98 extern void Enable_Secret_Units(void); #endif /********************************* Prototypes *******************************/ //........................................................................... // Main multiplayer queue logic //........................................................................... static void Queue_AI_Normal(void); static void Queue_AI_Multiplayer(void); static RetcodeType Wait_For_Players(int first_time, ConnManClass *net, int resend_delta, int dialog_time, int timeout, char *multi_packet_buf, int my_sent, long *their_frame, unsigned short *their_sent, unsigned short *their_recv); static void Generate_Timing_Event(ConnManClass *net, int my_sent); static void Generate_Real_Timing_Event(ConnManClass *net, int my_sent); static void Generate_Process_Time_Event(ConnManClass *net); static int Process_Send_Period(ConnManClass *net); //, int init); static int Send_Packets(ConnManClass *net, char *multi_packet_buf, int multi_packet_max, int max_ahead, int my_sent); static void Send_FrameSync(ConnManClass *net, int cmd_count); static RetcodeType Process_Receive_Packet(ConnManClass *net, char *multi_packet_buf, int id, int packetlen, long *their_frame, unsigned short *their_sent, unsigned short *their_recv); static RetcodeType Process_Serial_Packet(char *multi_packet_buf, int first_time); static int Can_Advance(ConnManClass *net, int max_ahead, long *their_frame, unsigned short *their_sent, unsigned short *their_recv); static int Process_Reconnect_Dialog(CDTimerClass *timeout_timer, long *their_frame, int num_conn, int reconn, int fresh); static int Handle_Timeout(ConnManClass *net, long *their_frame, unsigned short *their_sent, unsigned short *their_recv); static void Stop_Game(void); //........................................................................... // Packet compression/decompression: //........................................................................... static int Build_Send_Packet(void *buf, int bufsize, int frame_delay, int num_cmds, int cap); int Add_Uncompressed_Events(void *buf, int bufsize, int frame_delay, int size, int cap); int Add_Compressed_Events(void *buf, int bufsize, int frame_delay, int size, int cap); static int Breakup_Receive_Packet(void *buf, int bufsize ); int Extract_Uncompressed_Events(void *buf, int bufsize); int Extract_Compressed_Events(void *buf, int bufsize); //........................................................................... // DoList management: //........................................................................... static int Execute_DoList(int max_houses, HousesType base_house, ConnManClass *net, CDTimerClass *skip_crc, // ConnManClass *net, TCountDownTimerClass *skip_crc, long *their_frame, unsigned short *their_sent, unsigned short *their_recv); static void Clean_DoList(ConnManClass *net); static void Queue_Record(void); static void Queue_Playback(void); //........................................................................... // Debugging: //........................................................................... static void Compute_Game_CRC(void); void Add_CRC(unsigned long *crc, unsigned long val); static void Print_CRCs(EventClass *ev); static void Init_Queue_Mono(ConnManClass *net); static void Update_Queue_Mono(ConnManClass *net, int flow_index); static void Print_Framesync_Values(long curframe, unsigned long max_ahead, int num_connections, unsigned short *their_recv, unsigned short *their_sent, unsigned short my_sent); extern void Keyboard_Process(KeyNumType &input); void Dump_Packet_Too_Late_Stuff(EventClass *event, ConnManClass *net, long *their_frame, unsigned short *their_sent, unsigned short *their_recv); void Check_Mirror(void); /*************************************************************************** * Queue_Mission -- Queue a mega mission event. * * * * This routine is called when the player causes a change to a game unit. * * The event that initiates the change is queued to as a result of a call * * to this routine. * * * * INPUT: * * whom Whom this mission request applies to (a friendly unit). * * mission The mission to assign to this object. * * target The target of this mission (if any). * * dest The movement destination for this mission (if any). * * * * OUTPUT: * * Was the mission request queued successfully? * * * * WARNINGS: * * none. * * * * HISTORY: * * 09/21/1995 JLB : Created. * *=========================================================================*/ bool Queue_Mission(TargetClass whom, MissionType mission, TARGET target, TARGET destination) { if (! OutList.Add(EventClass(whom, mission, TargetClass(target), TargetClass(destination)))) { return(false); } else { return(true); } } /*********************************************************************************************** * Queue_Mission -- Queue a mega mission event, formation override for common speed. * * * * This routine is called when the player causes a change to a game unit. The event that * * initiates the change is queued to as a result of a call to this routine. * * * * INPUT: whom -- Whom this mission request applies to (a friendly unit). * * * * mission -- The mission to assign to this object. * * * * target -- The target of this mission (if any). * * * * dest -- The movement destination for this mission (if any). * * * * speed -- The override speed for this unit. * * * * maxspeed -- The override maximum speed for this unit. * * * * OUTPUT: Was the mission request queued successfully? * * * * WARNINGS: none * * * * HISTORY: * * 09/21/1995 JLB : Created. * *=============================================================================================*/ bool Queue_Mission(TargetClass whom, MissionType mission, TARGET target, TARGET destination, SpeedType speed, MPHType maxspeed) { if (! OutList.Add(EventClass(whom, mission, TargetClass(target), TargetClass(destination), speed, maxspeed))) { return(false); } else { return(true); } } /*************************************************************************** * Queue_Options -- Queue the options event. * * * * INPUT: * * none. * * * * OUTPUT: * * Was the options screen event queued successfully? * * * * WARNINGS: * * none. * * * * HISTORY: * * 09/21/1995 JLB : Created. * *=========================================================================*/ bool Queue_Options(void) { if (! OutList.Add(EventClass(EventClass::OPTIONS))) { return(false); } else { return(true); } } /* end of Queue_Options */ /*************************************************************************** * Queue_Exit -- Add the exit game event to the queue. * * * * INPUT: * * none. * * * * OUTPUT: * * Was the exit event queued successfully? * * * * WARNINGS: * * none. * * * * HISTORY: * * 09/21/1995 JLB : Created. * *=========================================================================*/ bool Queue_Exit(void) { if (! OutList.Add(EventClass(EventClass::EXIT))) { return(false); } else { return(true); } } /* end of Queue_Exit */ /*************************************************************************** * Queue_AI -- Process all queued events. * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 09/21/1995 JLB : Created. * *=========================================================================*/ void Queue_AI(void) { if (Session.Play) { Queue_Playback(); } else { switch (Session.Type) { case GAME_SKIRMISH: case GAME_NORMAL: Queue_AI_Normal(); break; case GAME_MODEM: case GAME_NULL_MODEM: case GAME_IPX: case GAME_INTERNET: case GAME_TEN: case GAME_MPATH: Queue_AI_Multiplayer(); break; } } } /* end of Queue_AI */ /*************************************************************************** * Queue_AI_Normal -- Process all queued events. * * * * This is the "normal" version of the queue management routine. It does * * the following: * * - Transfers items in the OutList to the DoList * * - Executes any commands in the DoList that are supposed to be done on * * this frame # * * - Cleans out the DoList * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 09/21/1995 JLB : Created. * *=========================================================================*/ static void Queue_AI_Normal(void) { //------------------------------------------------------------------------ // Move events from the OutList (events generated by this player) into the // DoList (the list of events to execute). //------------------------------------------------------------------------ while (OutList.Count) { OutList.First().IsExecuted = false; if (!DoList.Add(OutList.First())) { ; } #ifdef MIRROR_QUEUE MirrorList.Add(OutList.First()); #endif OutList.Next(); } //------------------------------------------------------------------------ // Save the DoList to disk, if we're in "Record" mode //------------------------------------------------------------------------ if (Session.Record) { Queue_Record(); } //------------------------------------------------------------------------ // Execute the DoList; if an error occurs, bail out. //------------------------------------------------------------------------ if (!Execute_DoList(1, PlayerPtr->Class->House, NULL, NULL, NULL, NULL, NULL)) { GameActive = 0; return; } //------------------------------------------------------------------------ // Clean out the DoList //------------------------------------------------------------------------ Clean_DoList(NULL); } /* end of Queue_AI_Normal */ /*************************************************************************** * Queue_AI_Multiplayer -- Process all queued events. * * * * This is the network version of the queue management routine. It does * * the following: * * - If this is the 1st frame, waits for other systems to signal ready * * - Generates a timing event, to allow the connection time to be dynamic * * - Handles timing related to sending packets every 'n' frames * * - Sends outgoing events * * - Frame-syncs to the other systems (see below) * * - Executes & cleans out the DoList * * * * The Frame-Sync'ing logic is the heart & soul of network play. It works * * by ensuring that any system won't out-run the other system by more than * * 'Session.MaxAhead' frames; this in turn ensures that a packet's * * execution frame # won't have been passed by the time that packet is * * received by all systems. * * * * To achieve this, the system must keep track of all other system's * * current frame #'s; these are stored in an array called 'their_frame[]'. * * However, because current frame #'s are sent in FRAMEINFO packets, which * * don't require an ACK, and command packets are sent in packets requiring * * an ACK, it's possible for a command packet to get lost, and the next * * frame's FRAMEINFO packet to not get lost; the other system may then * * advance past the frame # the command is to execute on! So, to prevent * * this, all FRAMEINFO packets include a CommandCount field. This value * * tells the other system how many events it should have received by this * * time. This system can therefore keep track of how many commands it's * * actually received, and compare it to the CommandCount field, to see if * * it's missed an event packet. The # of events we've received from each * * system is stored in 'their_recv[]', and the # events they say they've * * sent is stored in 'their_sent[]'. * * * * Thus, two conditions must be met in order to advance to the next frame: * * - Our current frame # must be < their_frame + Session.MaxAhead * * - their_recv[i] must be >= their_sent[i] * * * * 'their_frame[] is updated by Process_Receive_Packet() * * 'their_recv[] is updated by Process_Receive_Packet() * * 'their_sent[] is updated by Process_Receive_Packet() * * 'my_sent' is updated by this routine. * * * * The order of the arrays their_frame[] etc is the same order the * * connections are created in. The Sender's ID is passed to * * Connection_Index() to obtain the array index. * * * * The only routines allowed to pop up dialogs are: * * Wait_For_Players() (only pops up the reconnect dialog) * * Execute_DoList() (tells if out of sync, or packet recv'd too late) * * * * Sign-off's are detected by: * * - Timing out while waiting for a packet * * - Detecting that the other player is now at the score screen or * * connection dialog (serial) * * - If we see an EventClass::EXIT event on the private channel * * * * The current communications protocol, COMM_PROTOCOL_MULTI_E_COMP, has * * the following properties: * * - It compresses packets, so that the minimum number of bytes are * * transmitted. Packets are compressed by extracting all info common to * * the events into the packet header, and then sending only the bytes * * relevant to each type of event. For instance, if 100 infantry guys * * are told to move to the same location, the command itself & the * * location will be included in the 1st movement command only; after * * that, there will be a rep count then 99 infantry TARGET numbers, * * identifying all the infantry told to move. * * - The protocol also only sends packets out every 'n' frames. This cuts * * the data rate dramatically. It means that 'Session.MaxAhead' must be * * divisible by 'n'; also, the minimum value for 'Session.MaxAhead' is * * 'n * 2', to give both sides some "breathing" room in case a FRAMEINFO * * packet gets missed. * * * * Note: For synchronization-waiting loops (like waiting to hear from all * * other players, waiting to advance to the next frame, etc), use * * Net.Num_Connections() rather than Session.NumPlayers; this reflects the * * actual # of connections, and can be "faked" into playing even when * * there aren't any other players actually there. A typical example of * * this is playing back a recorded game. For command-execution loops, use * * Session.NumPlayers. This ensures all commands get executed, even if * * there isn't a human generating those commands. * * * * The modem works a little differently from the network in this respect: * * - The connection has to stay "alive" even if the other player exits to * * the join dialog. This prevents each system from timing out & hanging * * the modem up. Thus, packets are sent back & forth & just thrown away,* * but each system knows the other is still there. Messages may be sent * * between systems, though. * * - Destroy_Null_Connection doesn't hang up the modem, so * * Num_Connections() still reports a value of 1 even though the other * * player has left. * * - Any waits on Num_Connections() must also check for * * Session.NumPlayers > 1, to keep from waiting forever if the other * * guy has left * * - Packets sent to a player who's left require no ACK * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static void Queue_AI_Multiplayer(void) { if(Session.Type == GAME_SKIRMISH) return; return; #if (0)//PG //........................................................................ // Enums: //........................................................................ enum { MIXFILE_RESEND_DELTA = 120, // ticks b/w resends MIXFILE_TIMEOUT = 3600*2, // timeout waiting for mixfiles. FRAMESYNC_DLG_TIME = (3*60), // time until displaying reconnect dialog FRAMESYNC_TIMEOUT = (15*60), // timeout waiting for frame sync packet }; int timeout_factor = (Session.Type == GAME_INTERNET) ? 6 : 1; //........................................................................ // Variables for sending, receiving & parsing packets: //........................................................................ ConnManClass *net; // ptr to access all multiplayer functions EventClass packet; // for sending single frame-sync's char *multi_packet_buf; // buffer for sending/receiving int multi_packet_max; // max length of multi_packet_buf //........................................................................ // Frame-sync'ing variables //........................................................................ static long their_frame[MAX_PLAYERS - 1]; // other players' frame #'s static unsigned short their_sent[MAX_PLAYERS - 1]; // # cmds other player claims to have sent static unsigned short their_recv[MAX_PLAYERS - 1]; // # cmds actually received from others static unsigned short my_sent; // # cmds I've sent out //........................................................................ // Timing variables //........................................................................ static CDTimerClass skip_crc; // to delay the CRC check // static TCountDownTimerClass skip_crc; // to delay the CRC check //........................................................................ // Other misc variables //........................................................................ int i; RetcodeType rc; int reconnect_dlg = 0; // 1 = the reconnect dialog is displayed //------------------------------------------------------------------------ // Initialize the packet buffer pointer & its max size //------------------------------------------------------------------------ #if (0)//PG if (Session.Type == GAME_MODEM || Session.Type == GAME_NULL_MODEM) { multi_packet_buf = NullModem.BuildBuf; multi_packet_max = NullModem.MaxLen - sizeof (CommHeaderType); net = &NullModem; } else if (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET) { multi_packet_buf = Session.MetaPacket; multi_packet_max = Session.MetaSize; net = &Ipx; } #endif #if(TEN) else if (Session.Type == GAME_TEN) { multi_packet_buf = Session.TenPacket; multi_packet_max = Session.TenSize; net = Ten; } #endif #if(MPATH) else if (Session.Type == GAME_MPATH) { multi_packet_buf = Session.MPathPacket; multi_packet_max = Session.MPathSize; net = MPath; } #endif //------------------------------------------------------------------------ // Debug stuff //------------------------------------------------------------------------ Init_Queue_Mono(net); Update_Queue_Mono (net, 0); //------------------------------------------------------------------------ // Compute the Game's CRC //------------------------------------------------------------------------ Compute_Game_CRC(); CRC[Frame & 0x001f] = GameCRC; //------------------------------------------------------------------------ // If we've just started a game, or loaded a multiplayer game, we must // wait for all other systems to signal ready. //------------------------------------------------------------------------ if (Frame==0 || Session.LoadGame) { //..................................................................... // Initialize static locals //..................................................................... for (i = 0; i < MAX_PLAYERS - 1; i++) { their_frame[i] = -1; their_sent[i] = 0; their_recv[i] = 0; } my_sent = 0; #ifdef FIXIT_MULTI_SAVE skip_crc = 32; #else skip_crc = Frame + 32; #endif // FIXIT_MULTI_SAVE for (i = 0; i < 32; i++) CRC[i] = 0; //..................................................................... // If we've loaded a saved game: // - If this game was saved as the result of a lost connection, clear // the CRC value so it will always match the other system's // - Otherwise, use the GameCRC value, so we'll compare save-game files // rather than scenario INI files //..................................................................... if (Session.LoadGame) { if (Session.EmergencySave) ScenarioCRC = 0; else ScenarioCRC = GameCRC; } //..................................................................... // Send our initial FRAMESYNC packet //..................................................................... Send_FrameSync(net, my_sent); //..................................................................... // Wait for the other guys //..................................................................... rc = Wait_For_Players (1, net, MIXFILE_RESEND_DELTA, FRAMESYNC_DLG_TIME*timeout_factor, MIXFILE_TIMEOUT, multi_packet_buf, my_sent, their_frame, their_sent, their_recv); if (rc != RC_NORMAL) { #ifdef WIN32 if (Session.Type == GAME_INTERNET){ Register_Game_End_Time(); } #endif //WIN32 if (rc == RC_NOT_RESPONDING) { WWMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); } else if (rc == RC_SCENARIO_MISMATCH) { WWMessageBox().Process (TXT_SCENARIOS_DO_NOT_MATCH); } else if (rc == RC_DOLIST_FULL) { WWMessageBox().Process(TXT_QUEUE_FULL); } Stop_Game(); return; } //..................................................................... // Re-initialize frame numbers (in case somebody signed off while I was // waiting for MIX files to load; we would have fallen through, but // their frame # would still be -1). //..................................................................... for (i = 0; i < MAX_PLAYERS - 1; i++) their_frame[i] = 0; //..................................................................... // Reset the network response time computation, now that we're both // sending data again (loading MIX files will have introduced // deceptively large values). //..................................................................... net->Reset_Response_Time(); //..................................................................... // Initialize the frame timers //..................................................................... if (Session.CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { Process_Send_Period(net);//, 1); } //..................................................................... // Turn off our special load-game flags //..................................................................... if (Session.LoadGame) { Session.EmergencySave = false; Session.LoadGame = false; } } // end of Frame 0 wait //------------------------------------------------------------------------ // Adjust connection timing parameters every 128 frames. //------------------------------------------------------------------------ else if ( (Frame & 0x007f) == 0) { // // If we're using the new spiffy protocol, do proper timing handling. // If we're the net "master", compute our desired frame rate & new // 'MaxAhead' value. // if (Session.CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { // // All systems will transmit their required process time. // Generate_Process_Time_Event(net); // // The game "host" will transmit timing adjustment events. // if (Session.Am_I_Master()) { Generate_Real_Timing_Event(net, my_sent); } } else { // // For the older protocols, do the old broken timing handling. // Generate_Timing_Event(net, my_sent); } } //------------------------------------------------------------------------ // Only process every 'FrameSendRate' frames //------------------------------------------------------------------------ if (Session.CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { if (!Process_Send_Period(net)) { //, 0)) { if (IsMono) { MonoClass::Disable(); } return; } } //------------------------------------------------------------------------ // Send our data packet(s); update my command-sent counter //------------------------------------------------------------------------ my_sent += Send_Packets(net, multi_packet_buf, multi_packet_max, Session.MaxAhead, my_sent); //------------------------------------------------------------------------ // If this is our first time through, we're done. //------------------------------------------------------------------------ if (Frame==0) { if (IsMono) { MonoClass::Disable(); } return; } //------------------------------------------------------------------------ // Frame-sync'ing: wait until it's OK to advance to the next frame. //------------------------------------------------------------------------ #ifdef FIXIT_VERSION_3 int iFramesyncTimeout; if( Session.Type == GAME_INTERNET && pWolapi && pWolapi->GameInfoCurrent.iPlayerCount > 2 ) // Shortened resync timeout for non-2 player games. iFramesyncTimeout = 5 * 60; // One minute. else iFramesyncTimeout = FRAMESYNC_TIMEOUT; rc = Wait_For_Players (0, net, (Session.MaxAhead << 3), MAX ( net->Response_Time() * 3, FRAMESYNC_DLG_TIME*timeout_factor ), iFramesyncTimeout * (2*timeout_factor), multi_packet_buf, my_sent, their_frame, their_sent, their_recv); #else rc = Wait_For_Players (0, net, (Session.MaxAhead << 3), MAX ( net->Response_Time() * 3, FRAMESYNC_DLG_TIME*timeout_factor ), FRAMESYNC_TIMEOUT* (2*timeout_factor), multi_packet_buf, my_sent, their_frame, their_sent, their_recv); #endif if (rc != RC_NORMAL) { #ifdef WIN32 if (Session.Type == GAME_INTERNET){ Register_Game_End_Time(); #ifdef WOLAPI_INTEGRATION // New rule - if you cancel a waiting to reconnect dialog, you lose. bReconnectDialogCancelled = ( rc == RC_CANCEL ); #endif } #endif //WIN32 if (rc == RC_NOT_RESPONDING) { WWMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); } else if (rc == RC_SCENARIO_MISMATCH) { WWMessageBox().Process (TXT_SCENARIOS_DO_NOT_MATCH); } else if (rc == RC_DOLIST_FULL) { WWMessageBox().Process(TXT_QUEUE_FULL); } Stop_Game(); return; } //------------------------------------------------------------------------ // Save the DoList to disk, if we're in "Record" mode //------------------------------------------------------------------------ if (Session.Record) { Queue_Record(); } //------------------------------------------------------------------------ // Execute the DoList; if an error occurs, bail out. //------------------------------------------------------------------------ if (!Execute_DoList(Session.MaxPlayers, HOUSE_MULTI1, net, &skip_crc, their_frame, their_sent, their_recv)) { #ifdef WIN32 if (Session.Type == GAME_INTERNET){ Register_Game_End_Time(); } #endif //WIN32 Stop_Game(); return; } //------------------------------------------------------------------------ // Clean out the DoList //------------------------------------------------------------------------ Clean_DoList(net); if (IsMono) { MonoClass::Disable(); } #endif } // end of Queue_AI_Multiplayer /*************************************************************************** * Wait_For_Players -- Waits for other systems to come on-line * * * * This routine performs the most critical logic in multiplayer; that of * * synchronizing my frame number with those of the other systems. * * * * INPUT: * * first_time 1 = 1st time this routine is called * * net ptr to connection manager * * resend_delta time (ticks) between FRAMESYNC resends * * dialog_time time (ticks) until pop up a reconnect dialog * * timeout time (ticks) until we give up the ghost * * multi_packet_buf buffer to store packets in * * my_sent # commands I've sent so far * * their_frame array of their frame #'s * * their_sent array of their CommandCount values * * their_recv array of # cmds I've received from them * * * * OUTPUT: * * RC_NORMAL OK to advance to the next frame * * RC_CANCEL user hit 'Cancel' at the timeout countdown dlg * * RC_NOT_RESPONDING other player(s) not responding * * RC_SCENARIO_MISMATCH scenario's don't match (first_time only) * * RC_DOLIST_FULL DoList was full * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static RetcodeType Wait_For_Players(int first_time, ConnManClass *net, int resend_delta, int dialog_time, int timeout, char *multi_packet_buf, int my_sent, long *their_frame, unsigned short *their_sent, unsigned short *their_recv) { //........................................................................ // Variables for sending, receiving & parsing packets: //........................................................................ EventClass *event; // event ptr for parsing incoming packets int packetlen; // size of meta-packet sent, & received int id; // id of other player int messages_this_loop; // to limit # messages processed each loop int message_limit; // max # messages we'll read each frame //........................................................................ // Variables used only if 'first_time': //........................................................................ int num_ready; // # players signalling ready //........................................................................ // Timing variables //........................................................................ CDTimerClass retry_timer; // time between FRAMESYNC packet resends CDTimerClass dialog_timer; // time to pop up a dialog CDTimerClass timeout_timer; // general-purpose timeout //........................................................................ // Dialog variables //........................................................................ int reconnect_dlg = 0; // 1 = the reconnect dialog is displayed //........................................................................ // Other misc variables //........................................................................ KeyNumType input; // for user input int x,y; // for map input RetcodeType rc; //------------------------------------------------------------------------ // Wait to hear from all other players //------------------------------------------------------------------------ num_ready = 0; retry_timer = resend_delta; // time to retry dialog_timer = dialog_time; // time to show dlg timeout_timer = timeout; // time to bail out while (1) { Keyboard->Check(); Update_Queue_Mono (net, 2); //--------------------------------------------------------------------- // Resend a frame-sync packet if longer than one propagation delay goes // by; this prevents a "deadlock". If he's waiting for me to advance, // but has missed my last few FRAMEINFO packets, I may be waiting for // him to advance. Resending a FRAMESYNC ensures he knows what frame // number I'm on. //--------------------------------------------------------------------- if (!retry_timer) { retry_timer = resend_delta; // time to retry Update_Queue_Mono (net, 3); Send_FrameSync(net, my_sent); } //--------------------------------------------------------------------- // Service the connections //--------------------------------------------------------------------- net->Service(); //--------------------------------------------------------------------- // Pop up a reconnect dialog if enough time goes by //--------------------------------------------------------------------- if (!dialog_timer && SpecialDialog==SDLG_NONE) { if (reconnect_dlg == 0 && first_time == 0) { FILE *fp; int i; HouseClass *housep; fp = fopen("recon.txt","wt"); if (fp) { fprintf(fp,"# Connections: %d\n",net->Num_Connections()); fprintf(fp," My Frame #: %d\n",Frame); for (i = 0; i < net->Num_Connections(); i++) { housep = HouseClass::As_Pointer((HousesType)(net->Connection_ID(i))); fprintf(fp,"%15s: Their Sent:%d Their Recv:%d Their Frame:%d\n", housep->IniName, their_sent[i], their_recv[i], their_frame[i]); } fclose(fp); } #ifdef WOLAPI_INTEGRATION // "Reconnecting" dialog is about to be shown. // At this point, begin wolapi "disconnect pinging", if appropriate. if( Session.Type == GAME_INTERNET && pWolapi && pWolapi->GameInfoCurrent.bTournament ) pWolapi->Init_DisconnectPinging(); #endif } if (Process_Reconnect_Dialog(&timeout_timer, their_frame, // (Returns immediately.) net->Num_Connections(), (first_time==0), (reconnect_dlg==0))) { return (RC_CANCEL); } reconnect_dlg = 1; #ifdef WOLAPI_INTEGRATION // Continue wolapi "disconnect pinging", if appropriate. if( Session.Type == GAME_INTERNET && pWolapi && pWolapi->bDoingDisconnectPinging ) pWolapi->Pump_DisconnectPinging(); #endif } //--------------------------------------------------------------------- // Exit if too much time goes by (the other system has crashed or // bailed) //--------------------------------------------------------------------- if (!timeout_timer) { //.................................................................. // For the first-time run, just give up; something's wrong. //.................................................................. if (first_time) { return (RC_NOT_RESPONDING); } //.................................................................. // Otherwise, we're in the middle of a game; so, the modem & // network must deal with a timeout differently. //.................................................................. else { Update_Queue_Mono (net, 4); if (Handle_Timeout(net, their_frame, their_sent, their_recv)) { Map.Flag_To_Redraw(true); // erase modem reconnect dialog Map.Render(); retry_timer = resend_delta; dialog_timer = dialog_time; timeout_timer = timeout; } #ifdef FIXIT_MULTI_SAVE #ifdef FIXIT_VERSION_3 else if ((Session.Type == GAME_MODEM || Session.Type == GAME_NULL_MODEM) ) { #else else if ((Session.Type == GAME_MODEM || Session.Type == GAME_NULL_MODEM) && PlayingAgainstVersion != VERSION_RED_ALERT_104) { #endif if (WWMessageBox().Process (TXT_ASK_EMERGENCY_SAVE_NOT_RESPONDING, TXT_YES, TXT_NO, TXT_NONE) == 0) { Session.EmergencySave = 1; //printf("Saving emergency game; frame:%d, CRC:%d\n",Frame,GameCRC); //Print_CRCs(NULL); //printf("Before Save: Count1:%d, Count2:%d, Seed:%d\n", // Scen.RandomNumber.Count1, // Scen.RandomNumber.Count2, // Scen.RandomNumber.Seed); Save_Game(-1, (char *)Text_String(TXT_MULTIPLAYER_GAME)); //printf("After Save: Count1:%d, Count2:%d, Seed:%d\n", // Scen.RandomNumber.Count1, // Scen.RandomNumber.Count2, // Scen.RandomNumber.Seed); Session.EmergencySave = 0; } return (RC_CANCEL); } #endif // FIXIT_MULTI_SAVE else { return (RC_NOT_RESPONDING); } } } //--------------------------------------------------------------------- // Check for an incoming message. We must still process commands // even if 'first_time' is set, in case the other system got my 1st // FRAMESYNC, but I didn't get his; he'll be at the next frame, and // may be sending commands. // We have to limit the number of incoming messages we handle; it's // possible to go into an infinite loop processing modem messages. // (This feature is disabled for Ten; we need to keep the TCP buffers // clear, so we read all the packets we can every time.) //--------------------------------------------------------------------- messages_this_loop = 0; message_limit = 5; if (Session.Type == GAME_TEN || Session.Type == GAME_MPATH) { message_limit = 9999; } while ( (messages_this_loop++ < message_limit) && net->Get_Private_Message (multi_packet_buf, &packetlen, &id) ) { Keyboard->Check(); Update_Queue_Mono (net, 5); /*.................................................................. Get an event ptr to the incoming message ..................................................................*/ event = (EventClass *)multi_packet_buf; //------------------------------------------------------------------ // Special processing for a modem game: process SERIAL packets //------------------------------------------------------------------ if (Session.Type == GAME_MODEM || Session.Type == GAME_NULL_MODEM) { rc = Process_Serial_Packet(multi_packet_buf, first_time); //............................................................... // SERIAL packet received & processed //............................................................... if (rc == RC_SERIAL_PROCESSED) { net->Service(); retry_timer = resend_delta; dialog_timer = dialog_time; timeout_timer = timeout; continue; } //............................................................... // other player has left the game //............................................................... else if (rc == RC_PLAYER_LEFT) { if (first_time) { num_ready++; } break; } //............................................................... // Connection was lost //............................................................... else if (rc == RC_HUNG_UP) { #ifdef FIXIT_MULTI_SAVE #ifndef FIXIT_VERSION_3 if (PlayingAgainstVersion != VERSION_RED_ALERT_104){ #endif if (WWMessageBox().Process (TXT_ASK_EMERGENCY_SAVE_HUNG_UP, TXT_YES, TXT_NO, TXT_NONE) == 0) { Session.EmergencySave = 1; //printf("Saving emergency game; frame:%d, CRC:%d\n",Frame,GameCRC); //Print_CRCs(NULL); //printf("Before Save: Count1:%d, Count2:%d, Seed:%d\n", // Scen.RandomNumber.Count1, // Scen.RandomNumber.Count2, // Scen.RandomNumber.Seed); Save_Game (-1, (char *)Text_String(TXT_MULTIPLAYER_GAME)); //printf("After Save: Count1:%d, Count2:%d, Seed:%d\n", // Scen.RandomNumber.Count1, // Scen.RandomNumber.Count2, // Scen.RandomNumber.Seed); Session.EmergencySave = 0; } return (RC_CANCEL); #ifndef FIXIT_VERSION_3 }else{ return (RC_NOT_RESPONDING); } #endif #else return (RC_NOT_RESPONDING); #endif // FIXIT_MULTI_SAVE } //............................................................... // If it was any other type of serial packet, break //............................................................... else if (rc != RC_NORMAL) { break; } } //------------------------------------------------------------------ // Process the incoming packet //------------------------------------------------------------------ rc = Process_Receive_Packet(net, multi_packet_buf, id, packetlen, their_frame, their_sent, their_recv); //.................................................................. // New player heard from //.................................................................. if (rc == RC_PLAYER_READY) { num_ready++; } //.................................................................. // Scenario's don't match //.................................................................. else if (rc == RC_SCENARIO_MISMATCH) { return (RC_SCENARIO_MISMATCH); } //.................................................................. // DoList was full //.................................................................. else if (rc == RC_DOLIST_FULL) { return (RC_DOLIST_FULL); } //.................................................................. // Service the connection, to clean out the receive queues //.................................................................. net->Service(); } //--------------------------------------------------------------------- // Debug output //--------------------------------------------------------------------- Print_Framesync_Values(Frame, Session.MaxAhead, net->Num_Connections(), their_recv, their_sent, my_sent); //--------------------------------------------------------------------- // Attempt to advance to the next frame. //--------------------------------------------------------------------- //..................................................................... // For the first-time run, just check to see if we've heard from // everyone. //..................................................................... if (first_time) { if (num_ready >= net->Num_Connections()) { break; } } //..................................................................... // For in-game processing, we have to check their_sent, their_recv, // their_frame, etc. //..................................................................... else { if (Can_Advance(net, Session.MaxAhead, their_frame, their_sent, their_recv)) { break; } } //--------------------------------------------------------------------- // Service game stuff. Servicing the map's input, and rendering the // map, allows the map to scroll even though we're hung up waiting for // packets. Don't do this if 'first_time' is set, since users could be // waiting a very long time for all systems to load the scenario, and // it gets frustrating being able to scroll around without doing // anything. //--------------------------------------------------------------------- Call_Back(); if (!first_time && SpecialDialog == SDLG_NONE && reconnect_dlg==0) { #ifdef WIN32 WWMouse->Erase_Mouse(&HidPage, TRUE); #endif //WIN32 Map.Input(input, x, y); if (input) Keyboard_Process(input); Map.Render(); } } /* end of while */ //------------------------------------------------------------------------ // If the reconnect dialog was shown, force the map to redraw. //------------------------------------------------------------------------ if (reconnect_dlg) { Map.Flag_To_Redraw(true); Map.Render(); } return (RC_NORMAL); } // end of Wait_For_Players /*************************************************************************** * Generate_Timing_Event -- computes & queues a RESPONSE_TIME event * * * * This routine adjusts the connection timing on the local system; it also * * optionally generates a RESPONSE_TIME event, to tell all systems to * * dynamically adjust the current MaxAhead value. This allows both the * * MaxAhead & the connection retry logic to have dynamic timing, to adjust * * to varying line conditions. * * * * INPUT: * * net ptr to connection manager * * my_sent # commands I've sent out so far * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static void Generate_Timing_Event(ConnManClass *net, int my_sent) { unsigned long resp_time; // connection response time, in ticks EventClass ev; // // For now, TEN & MPATH don't measure the net's response time, so there's // no point in adjusting our timing. Do nothing. // if (Session.Type == GAME_TEN || Session.Type == GAME_MPATH) { return; } //------------------------------------------------------------------------ // Measure the current connection response time. This time will be in // 60ths of a second, and represents full round-trip time of a packet. // To convert to one-way packet time, divide by 2; to convert to game // frames, divide again by 4, assuming a game rate of 15 fps. //------------------------------------------------------------------------ resp_time = net->Response_Time(); //------------------------------------------------------------------------ // Adjust my connection retry timing; only do this if I've sent out more // than 5 commands, so I know I have a measure of the response time. //------------------------------------------------------------------------ if (my_sent > 5) { net->Set_Timing (resp_time + 10, -1, (resp_time * 4)+15); //..................................................................... // If I'm the network "master", I'm also responsible for updating the // MaxAhead value on all systems, so do that here too. //..................................................................... if (Session.Am_I_Master()) { ev.Type = EventClass::RESPONSE_TIME; //.................................................................. // For multi-frame compressed events, the MaxAhead must be an even // multiple of the FrameSendRate. //.................................................................. if (Session.CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { ev.Data.FrameInfo.Delay = max( ((((resp_time / 8) + (Session.FrameSendRate - 1)) / Session.FrameSendRate) * Session.FrameSendRate), (Session.FrameSendRate * 2) ); } //.................................................................. // For sending packets every frame, just use the 1-way connection // response time. //.................................................................. else { if (Session.Type == GAME_MODEM || Session.Type == GAME_NULL_MODEM) { ev.Data.FrameInfo.Delay = max( (resp_time / 8), MODEM_MIN_MAX_AHEAD ); } else if (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET) { ev.Data.FrameInfo.Delay = max( (resp_time / 8), NETWORK_MIN_MAX_AHEAD ); } else if (Session.Type == GAME_TEN || Session.Type == GAME_MPATH) { ev.Data.FrameInfo.Delay = max( (resp_time / 8), MODEM_MIN_MAX_AHEAD ); } } OutList.Add(ev); } } } // end of Generate_Timing_Event /*************************************************************************** * Generate_Real_Timing_Event -- Generates a TIMING event * * * * INPUT: * * net ptr to connection manager * * my_sent # commands I've sent out so far * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 07/02/1996 BRR : Created. * *=========================================================================*/ static void Generate_Real_Timing_Event(ConnManClass *net, int my_sent) { unsigned long resp_time; // connection response time, in ticks EventClass ev; int highest_ticks; int i; int specified_frame_rate; int maxahead; // // If we haven't sent out at least 5 guaranteed-delivery packets, don't // bother trying to measure our connection response time; just return. // if (my_sent < 5) { return; } // // Find the highest processing time we have stored // highest_ticks = 0; for (i = 0; i < Session.Players.Count(); i++) { // // If we haven't heard from all systems yet, bail out. // if (Session.Players[i]->Player.ProcessTime == -1) { return; } if (Session.Players[i]->Player.ProcessTime > highest_ticks) { highest_ticks = Session.Players[i]->Player.ProcessTime; } } // // Compute our "desired" frame rate as the lower of: // - What the user has dialed into the options screen // - What we're really able to run at // if (highest_ticks == 0) { Session.DesiredFrameRate = 60; } else { Session.DesiredFrameRate = 60 / highest_ticks; } if (Options.GameSpeed == 0) { specified_frame_rate = 60; } else { specified_frame_rate = 60 / Options.GameSpeed; } Session.DesiredFrameRate = MIN (Session.DesiredFrameRate, specified_frame_rate); // // Measure the current connection response time. This time will be in // 60ths of a second, and represents full round-trip time of a packet. // To convert to one-way packet time, divide by 2; to convert to game // frames, ....uh.... // resp_time = net->Response_Time(); // // Compute our new 'MaxAhead' value, based upon the response time of our // connection and our desired frame rate. // 'MaxAhead' in frames is: // // (resp_time / 2 ticks) * (1 sec/60 ticks) * (n Frames / sec) // // resp_time is divided by 2 because, as reported, it represents a round- // trip, and we only want to use a one-way trip. // maxahead = (resp_time * Session.DesiredFrameRate) / (2 * 60); // // Now, we have to round 'maxahead' so it's an even multiple of our // send rate. It also must be at least thrice the FrameSendRate. // (Isn't "thrice" a cool word?) // maxahead = ((maxahead + Session.FrameSendRate - 1) / Session.FrameSendRate) * Session.FrameSendRate; maxahead = MAX (maxahead, (int)Session.FrameSendRate * 3); ev.Type = EventClass::TIMING; ev.Data.Timing.DesiredFrameRate = Session.DesiredFrameRate; ev.Data.Timing.MaxAhead = maxahead; OutList.Add(ev); // // Adjust my connection retry timing. These values set the retry timeout // to just over one round-trip time, the 'maxretries' to -1, and the // connection timeout to allow for about 4 retries. // //net->Set_Timing (resp_time + 10, -1, (resp_time * 4)+15); if (Session.Type == GAME_INTERNET) { net->Set_Timing (resp_time + 10, -1, ((resp_time + 10)* 8)+15); }else{ net->Set_Timing (resp_time + 10, -1, (resp_time * 4)+15); } } /*************************************************************************** * Generate_Process_Time_Event -- Generates a PROCESS_TIME event * * * * INPUT: * * net ptr to connection manager * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 07/02/1996 BRR : Created. * *=========================================================================*/ static void Generate_Process_Time_Event(ConnManClass *net) { EventClass ev; int avgticks; unsigned long resp_time; // connection response time, in ticks // // Measure the current connection response time. This time will be in // 60ths of a second, and represents full round-trip time of a packet. // To convert to one-way packet time, divide by 2; to convert to game // frames, ....uh.... // resp_time = net->Response_Time(); // // Adjust my connection retry timing. These values set the retry timeout // to just over one round-trip time, the 'maxretries' to -1, and the // connection timeout to allow for about 4 retries. // //net->Set_Timing (resp_time + 10, -1, (resp_time * 4)+15); if (Session.Type == GAME_INTERNET) { net->Set_Timing (resp_time + 10, -1, ((resp_time + 10)* 8)+15); }else{ net->Set_Timing (resp_time + 10, -1, (resp_time * 4)+15); } if (IsMono) { MonoClass::Enable(); Mono_Set_Cursor(0,23); Mono_Printf("Processing Ticks:%03d Frames:%03d\n", Session.ProcessTicks,Session.ProcessFrames); MonoClass::Disable(); } avgticks = Session.ProcessTicks / Session.ProcessFrames; ev.Type = EventClass::PROCESS_TIME; ev.Data.ProcessTime.AverageTicks = avgticks; OutList.Add(ev); Session.ProcessTicks = 0; Session.ProcessFrames = 0; } /*************************************************************************** * Process_Send_Period -- timing for sending packets every 'n' frames * * * * This function is for a CommProtocol of COMM_PROTOCOL_MULTI_E_COMP only. * * It determines if it's time to send a packet or not. * * * * INPUT: * * net ptr to connection manager * * * * OUTPUT: * * 1 = it's time to send a packet; 0 = don't send a packet this frame. * * * * WARNINGS: * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static int Process_Send_Period(ConnManClass *net) //, int init) { //------------------------------------------------------------------------ // If the current frame # is not an even multiple of 'FrameSendRate', then // it's not time to send a packet; just return. //------------------------------------------------------------------------ if (Frame != (((Frame + (Session.FrameSendRate - 1)) / Session.FrameSendRate) * Session.FrameSendRate) ) { net->Service(); if (IsMono) { MonoClass::Disable(); } return (0); } return (1); } // end of Process_Send_Period /*************************************************************************** * Send_Packets -- sends out events from the OutList * * * * This routine computes how many events can be sent this frame, and then * * builds the "meta-packet" & sends it. * * * * The 'cap' value is the max # of events we can send. Ideally, it should * * be based upon the bandwidth of our connection. Currently, it's just * * hardcoded to prevent the modem from having to resend "too much" data, * * which is about 200 bytes per frame. * * * * INPUT: * * net ptr to connection manager * * multi_packet_buf buffer to store packets in * * multi_packet_max max size of multi_packet_buf * * max_ahead current game MaxAhead value * * my_sent # commands I've sent this game * * * * OUTPUT: * * # events sent, NOT including the FRAMEINFO event * * * * WARNINGS: * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static int Send_Packets(ConnManClass *net, char *multi_packet_buf, int multi_packet_max, int max_ahead, int my_sent) { int cap; // max # events to send, NOT including FRAMEINFO event int do_once; // true: only go through packet loop once int ack_req; // 0 = no ack required on outgoing packet int packetlen; // size of meta-packet sent //------------------------------------------------------------------------ // Determine how many events it's OK to send this frame. //------------------------------------------------------------------------ //........................................................................ // If we have 4 or more packets queued for sending, don't add any more // this frame. //........................................................................ if (net->Private_Num_Send() >= 4) { cap = 0; do_once = 1; } //........................................................................ // If there are 2 or more packets queued, the entire packet we send must // fit within a single ComQueue buffer, so limit # events to 5. // (The Modem connection manager has a max buffer size of 200 bytes, which // is large enough for 6 uncompressed events, which leaves room for 5 // events plus a FRAMEINFO.) //........................................................................ else if (net->Private_Num_Send() >= 2) { cap = 5; do_once = 1; } //........................................................................ // Otherwise, just send all events in the OutList //........................................................................ else { cap = OutList.Count; do_once = 0; } //........................................................................ // Make sure we aren't sending more events than are in the OutList //........................................................................ if (cap > OutList.Count) { cap = OutList.Count; } //........................................................................ // Make sure we don't send so many events that our DoList fills up //........................................................................ if (cap > (MAX_EVENTS * 64) - DoList.Count) { cap = (MAX_EVENTS * 64) - DoList.Count; } // // 10/21/96 5:12PM - ST // if (Session.Type == GAME_INTERNET || Session.Type == GAME_MODEM || Session.Type == GAME_NULL_MODEM){ cap = OutList.Count; do_once = 0; } //------------------------------------------------------------------------ // Build our meta-packet & transmit it. //------------------------------------------------------------------------ while (1) { Keyboard->Check(); Update_Queue_Mono (net, 1); //..................................................................... // If there are no commands this frame, we'll just be sending a FRAMEINFO // packet; no ack is required. For the modem's sake, check // Session.NumPlayers; no ACK is needed if we're just sending to someone // who's left the game. //..................................................................... if (cap == 0 || OutList.Count == 0 || Session.NumPlayers == 1) { ack_req = 0; } else { ack_req = 1; } //..................................................................... // Build & send out our message //..................................................................... packetlen = Build_Send_Packet (multi_packet_buf, multi_packet_max, max_ahead, my_sent, cap); net->Send_Private_Message (multi_packet_buf, packetlen, ack_req); //..................................................................... // Call Service() to actually send the packet //..................................................................... net->Service(); //..................................................................... // Stop if there's no more data to send, or if our send queue is // filling up. //..................................................................... if (OutList.Count == 0 || do_once) { break; } } return (cap); } // end of Send_Packets /*************************************************************************** * Send_FrameSync -- Sends a FRAMESYNC packet * * * * This routine is used to periodically remind the other systems that * * we're still here, and to tell them what frame # we're on, in case * * they've missed my FRAMEINFO packets. * * * * INPUT: * * net ptr to connection manager * * cmd_count # commands I've sent so far * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static void Send_FrameSync(ConnManClass *net, int cmd_count) { EventClass packet; //------------------------------------------------------------------------ // Build a frame-sync event to send. FRAMESYNC packets contain a // scenario-based CRC rather than a game-state-based CRC, to let the // games compare scenario CRC's on startup. //------------------------------------------------------------------------ memset (&packet, 0, sizeof(EventClass)); packet.Type = EventClass::FRAMESYNC; if (Session.CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { packet.Frame = ((Frame + Session.MaxAhead + (Session.FrameSendRate - 1)) / Session.FrameSendRate) * Session.FrameSendRate; } else { packet.Frame = Frame + Session.MaxAhead; } packet.ID = PlayerPtr->ID; packet.Data.FrameInfo.CRC = ScenarioCRC; packet.Data.FrameInfo.CommandCount = cmd_count; packet.Data.FrameInfo.Delay = Session.MaxAhead; //------------------------------------------------------------------------ // Send the event. For modem, this just sends to the other player; // for network, it sends to everyone we're connected to. //------------------------------------------------------------------------ net->Send_Private_Message (&packet, (offsetof(EventClass, Data) + size_of(EventClass, Data.FrameInfo)), 0 ); return; } // end of Send_FrameSync /*************************************************************************** * Process_Receive_Packet -- processes an incoming packet * * * * This routine receives a packet from another system, adds it to our * * execution queue (the DoList), and updates my arrays of their frame #, * * their commands-sent, and their commands-received. * * * * INPUT: * * net ptr to connection manager * * multi_packet_buf buffer containing packet(s) to parse * * id id of sender * * their_frame array containing frame #'s of other players * * their_sent array containing command count of other players * * their_recv array containing # recv'd cmds from other players * * * * OUTPUT: * * RC_NORMAL: nothing unusual happened, although * * their_sent or their_recv may have been * * altered * * RC_PLAYER_READY: player has been heard from for the 1st time; * * this presumes that his original * * 'their_frame[]' value was -1 when this * * routine was called * * RC_SCENARIO_MISMATCH: FRAMEINFO scenario CRC doesn't match; * * normally only applies after loading a new * * scenario or save-game * * RC_DOLIST_FULL: fatal error; unable to add events to DoList * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static RetcodeType Process_Receive_Packet(ConnManClass *net, char *multi_packet_buf, int id, int packetlen, long *their_frame, unsigned short *their_sent, unsigned short *their_recv) { EventClass *event; int index; RetcodeType retcode = RC_NORMAL; int i; //------------------------------------------------------------------------ // Get an event ptr to the incoming message //------------------------------------------------------------------------ event = (EventClass *)multi_packet_buf; //------------------------------------------------------------------------ // Get the index of the sender //------------------------------------------------------------------------ index = net->Connection_Index(id); //------------------------------------------------------------------------ // Compute the other player's frame # (at the time this packet was sent) //------------------------------------------------------------------------ if (their_frame[index] < (int)(event->Frame - event->Data.FrameInfo.Delay)) { //..................................................................... // If the original frame # for this player is -1, it means we've heard // from this player for the 1st time; return the appropriate value. //..................................................................... if (their_frame[index]==-1) { retcode = RC_PLAYER_READY; } their_frame[index] = event->Frame - event->Data.FrameInfo.Delay; } //------------------------------------------------------------------------ // Extract the other player's CommandCount. This count will include // the commands in this packet, if there are any. //------------------------------------------------------------------------ if (event->Data.FrameInfo.CommandCount > their_sent[index]) { if ( abs(their_sent[index] - event->Data.FrameInfo.CommandCount) > 500) { FILE *fp; fp = fopen("badcount.txt","wt"); if (fp) { fprintf(fp,"Event Type:%s\n",EventClass::EventNames[event->Type]); fprintf(fp,"Frame:%d ID:%d IsExec:%d\n", event->Frame, event->ID, event->IsExecuted); if (event->Type != EventClass::FRAMEINFO) { fprintf(fp,"Wrong Event Type!\n"); } else { fprintf(fp,"CRC:%x CommandCount:%d Delay:%d\n", event->Data.FrameInfo.CRC, event->Data.FrameInfo.CommandCount, event->Data.FrameInfo.Delay); } } } their_sent[index] = event->Data.FrameInfo.CommandCount; } if (Debug_Print_Events) { if (event->Type == EventClass::FRAMESYNC) { printf("(%d) Received FRAMESYNC: ", Frame); } else { printf("(%d) Received FRAMEINFO: ", Frame); } printf("EvFrame:%d ID:%d CRC:%x CmdCount:%d Delay:%d\n", event->Frame, event->ID, event->Data.FrameInfo.CRC, event->Data.FrameInfo.CommandCount, event->Data.FrameInfo.Delay); } //------------------------------------------------------------------------ // If this packet was not a FRAMESYNC packet: // - Add the events in it to our DoList // - Increment our commands-received counter by the number of non- // FRAMEINFO packets received //------------------------------------------------------------------------ if (event->Type != EventClass::FRAMESYNC) { //..................................................................... // Break up the packet into its component events. A returned packet // count of -1 indicates a fatal queue-full error. //..................................................................... i = Breakup_Receive_Packet( multi_packet_buf, packetlen); if (i==-1) { return (RC_DOLIST_FULL); } //..................................................................... // Compute the actual # commands in the packet by subtracting off the // FRAMEINFO event //..................................................................... if ( (event->Type==EventClass::FRAMEINFO) && (i > 0)) { i--; } their_recv[index] += i; } //------------------------------------------------------------------------ // If the event was a FRAMESYNC packet, there will be no commands to add, // but we must check the ScenarioCRC value. //------------------------------------------------------------------------ else if (event->Data.FrameInfo.CRC != ScenarioCRC) { return (RC_SCENARIO_MISMATCH); } return (retcode); } // end of Process_Receive_Packet /*************************************************************************** * Process_Serial_Packet -- Handles an incoming serial packet * * * * This routine is needed because the modem classes don't support a * * "global channel" like the network classes do, but that functionality is * * still needed for modem communications. Specifically, the modem dialogs * * transmit their own special packets back & forth, and messages are sent * * using a special packet type. Thus, we have to call this routine when * * we receive a modem packet, to allow it to process messages & dialog * * packets. * * * * INPUT: * * multi_packet_buf packet buffer to process * * first_time 1 = this is the 1st game frame * * * * OUTPUT: * * RC_NORMAL: this wasn't a SERIAL-type packet * * RC_SERIAL_PROCESSED: this was a SERIAL-type packet, and was * * processed; the other player is still connected, * * even if he's not in the game. * * RC_PLAYER_LEFT: other player has left the game * * RC_HUNG_UP: we're getting our own packets back; thus, the * * modem is mirroring our packets, which means the * * modem hung up! * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static RetcodeType Process_Serial_Packet(char *multi_packet_buf, int first_time) { multi_packet_buf; first_time; return (RC_NORMAL); #if (0) // ST - 5/13/2019 SerialPacketType *serial_packet; // for parsing serial packets int player_gone; EventClass *event; //------------------------------------------------------------------------ // Determine if this packet means that the other player has left the game //------------------------------------------------------------------------ serial_packet = (SerialPacketType *)multi_packet_buf; player_gone = 0; //........................................................................ // On Frame 0, only a SIGN_OFF means the other player left; the other // packet types may be left over from a previous session. //........................................................................ if (first_time) { if (serial_packet->Command == SERIAL_SIGN_OFF) { player_gone = 1; } } //........................................................................ // On subsequent frames, any of SIGN_OFF, TIMING, or SCORE_SCREEN means // the other player is gone. //........................................................................ else { if (serial_packet->Command == SERIAL_SIGN_OFF || serial_packet->Command == SERIAL_TIMING || serial_packet->Command == SERIAL_SCORE_SCREEN ) { player_gone = 1; } } if (player_gone) { Destroy_Null_Connection(serial_packet->ScenarioInfo.Color, 0); return (RC_PLAYER_LEFT); } //------------------------------------------------------------------------ // Process an incoming message //------------------------------------------------------------------------ if (serial_packet->Command == SERIAL_MESSAGE) { if (!Session.Messages.Concat_Message(serial_packet->Name, serial_packet->ID, serial_packet->Message.Message, Rule.MessageDelay * TICKS_PER_MINUTE)) { #ifdef FIXIT_CSII // checked - ajw 9/28/98 - Appears to do nothing char *ptr = &serial_packet->Message.Message[0]; if (!strncmp(ptr,"SECRET UNITS ON ",15) && NewUnitsEnabled) { Enable_Secret_Units(); } #endif Session.Messages.Add_Message (serial_packet->Name, serial_packet->ID, serial_packet->Message.Message, (PlayerColorType)serial_packet->ID, TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, Rule.MessageDelay * TICKS_PER_MINUTE); Sound_Effect(VOC_INCOMING_MESSAGE); } //..................................................................... // Save this message in our last-message buffer //..................................................................... if (strlen (serial_packet->Message.Message)) { strcpy (Session.LastMessage, serial_packet->Message.Message); } //..................................................................... // Tell the map to do a partial update (just to force the // messages to redraw). //..................................................................... //Map.Flag_To_Redraw(false); Map.Flag_To_Redraw(true); return (RC_SERIAL_PROCESSED); } //------------------------------------------------------------------------ // Any other SERIAL-type packet means the other player is still there; // throw them away, but let the caller know the connection is OK. //------------------------------------------------------------------------ if ( (serial_packet->Command >= SERIAL_CONNECT && serial_packet->Command < SERIAL_LAST_COMMAND) || (serial_packet->Command >= SERIAL_REQ_SCENARIO && serial_packet->Command <= SERIAL_NO_SCENARIO) || Session.NumPlayers == 1) { return (RC_SERIAL_PROCESSED); } //........................................................................ // are we getting our own packets back?? //........................................................................ event = (EventClass *)multi_packet_buf; if (event->Type <= EventClass::EMPTY || event->Type >= EventClass::LAST_EVENT) return (RC_SERIAL_PROCESSED); if (event->ID == PlayerPtr->ID) { return (RC_HUNG_UP); } return (RC_NORMAL); #endif } // end of Process_Serial_Packet /*************************************************************************** * Can_Advance -- determines if it's OK to advance to the next frame * * * * This routine uses the current values stored in their_frame[], * * their_send[], and their_recv[] to see if it's OK to advance to the next * * game frame. We must not advance if: * * - If our frame # would be too far ahead of the slowest player (the * * lowest their_frame[] value). "Too far" means * * (Frame >= their_frame + MaxAhead). * * - our current command count doesn't match the sent command count of one * * other player (meaning that we've missed a command packet from that * * player, and thus the frame # we're receiving from him may be due to a * * FRAMEINFO packet sent later than the command, so we shouldn't use * * this frame # to see if we should advance; we should wait until we * * have all the commands before we advance. * * * * Of course, this routine assumes the values in their_frame[] etc are * * kept current by the caller. * * * * INPUT: * * net ptr to connection manager * * max_ahead max frames ahead * * their_frame array of their frame #'s * * their_sent array of their sent command count * * their_recv array of their # received commands * * * * OUTPUT: * * 1 = OK to advance; 0 = not OK * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static int Can_Advance(ConnManClass *net, int max_ahead, long *their_frame, unsigned short *their_sent, unsigned short *their_recv) { long their_oldest_frame; // other players' oldest frame # int count_ok; // true = my cmd count matches theirs int i; //------------------------------------------------------------------------ // Special case for modem: if the other player has left, go ahead and // advance to the next frame; don't wait on him. //------------------------------------------------------------------------ if (Session.NumPlayers == 1) { return (1); } //------------------------------------------------------------------------ // Find the oldest frame # in 'their_frame' //------------------------------------------------------------------------ their_oldest_frame = Frame + 1000; for (i = 0; i < net->Num_Connections(); i++) { if (their_frame[i] < their_oldest_frame) their_oldest_frame = their_frame[i]; } //------------------------------------------------------------------------ // I can advance to the next frame IF: // 1) I'm less than a one-way propagation delay ahead of the other // players' frame numbers, AND // 2) their_recv[i] >= their_sent[i] (ie I've received all the commands // the other players have sent so far). //------------------------------------------------------------------------ count_ok = 1; for (i = 0; i < net->Num_Connections(); i++) { if (their_recv[i] < their_sent[i]) { count_ok = 0; break; } } if (count_ok && (Frame < (their_oldest_frame + max_ahead))) { return (1); } return (0); } // end of Can_Advance /*************************************************************************** * Process_Reconnect_Dialog -- processes the reconnection dialog * * * * This routine [re]draws the reconnection dialog; if 'reconn' is set, * * it tells the user who we're trying to reconnect to; otherwise, is just * * says something generic like "Waiting for connections". * * * * INPUT: * * timeout_timer ptr to count down timer, showing time remaining * * their_frame array of other players' frame #'s * * num_conn # connections in 'their_frame' * * reconn 1 = reconnect, 0 = waiting for first-time connection * * fresh 1 = draw from scratch, 0 = only update time counter * * * * OUTPUT: * * 1 = user wants to cancel, 0 = not * * * * WARNINGS: * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static int Process_Reconnect_Dialog(CDTimerClass *timeout_timer, long *their_frame, int num_conn, int reconn, int fresh) { static int displayed_time = 0; // time value currently displayed int new_time; int oldest_index; // index of person requiring a reconnect int i,j; //------------------------------------------------------------------------ // Convert the timer to seconds //------------------------------------------------------------------------ new_time = *timeout_timer / 60; //------------------------------------------------------------------------ // If the timer has changed, or 'fresh' is set, redraw the dialog //------------------------------------------------------------------------ if (fresh || (new_time != displayed_time)) { //..................................................................... // Find the index of the person we're trying to reconnect to //..................................................................... if (reconn) { j = 0x7fffffff; oldest_index = 0; for (i = 0; i < num_conn; i++) { if (their_frame[i] < j) { j = their_frame[i]; oldest_index = i; } } } Net_Reconnect_Dialog(reconn, fresh, oldest_index, new_time); } displayed_time = new_time; //........................................................................ // If user hits ESC, bail out //........................................................................ if (Keyboard->Check()) { if (Keyboard->Get() == KN_ESC) { return (1); } } return (0); } // end of Process_Reconnect_Dialog /*************************************************************************** * Handle_Timeout -- handles a timeout in the wait-for-players loop * * * * This routine "gracefully" handles a timeout in the frame-sync loop. * * The timeout must be handled differently by a modem game or network * * game. * * * * The modem game must detect if the other player is still connected * * physically, even if he's not playing the game any more; if so, this * * routine returns an OK status. If the other player isn't even * * physically connected, an error is returned. * * * * The network game must find the connection that's causing the timeout, * * and destroy it. The game continues, even if there are no more human * * players left. * * * * INPUT: * * net ptr to connection manager * * their_frame array containing frame #'s of other players * * their_sent array containing command count of other players * * their_recv array containing # recv'd cmds from other players * * * * OUTPUT: * * 1 = it's OK; reset timeout timers & keep processing * * 0 = game over, man * * * * WARNINGS: * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static int Handle_Timeout(ConnManClass *net, long *their_frame, unsigned short *their_sent, unsigned short *their_recv) { int oldest_index; // index of person requiring a reconnect int i,j; int id; //------------------------------------------------------------------------ // For modem, attempt to reconnect; if that fails, save the game & bail. //------------------------------------------------------------------------ if (Session.Type == GAME_MODEM || Session.Type == GAME_NULL_MODEM) { if ( net->Num_Connections() ) { if (!Reconnect_Modem()) { #ifndef FIXIT_MULTI_SAVE //............................................................... // Set 'Session.EmergencySave', so when this game is loaded, we // won't check the CRC of the game state (this system & the // other may be on different frames, in which case the CRC // won't match). //............................................................... Session.EmergencySave = 1; //Save_Game (-1, (char *)Text_String(TXT_MULTIPLAYER_GAME)); Session.EmergencySave = 0; #endif // FIXIT_MULTI_SAVE return (0); } else { return (1); } } } //------------------------------------------------------------------------ // For network, destroy the oldest connection //------------------------------------------------------------------------ else if (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET || Session.Type == GAME_TEN || Session.Type == GAME_MPATH) { j = 0x7fffffff; oldest_index = 0; for (i = 0; i < net->Num_Connections(); i++) { if (their_frame[i] < j) { j = their_frame[i]; oldest_index = i; } } id = net->Connection_ID(oldest_index); #ifdef WIN32 /* ** Send the game statistics packet now if the game is effectivly over */ if (Session.Players.Count() == 2 && Session.Type == GAME_INTERNET && !GameStatisticsPacketSent) { Register_Game_End_Time(); ConnectionLost = true; Send_Statistics_Packet(); // Disconnect, and I'll be the only one left. } #endif //WIN32 if (id != ConnManClass::CONNECTION_NONE) { for (i = oldest_index; i < net->Num_Connections() - 1; i++) { their_frame[i] = their_frame[i+1]; their_sent[i] = their_sent[i+1]; their_recv[i] = their_recv[i+1]; } if (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET) { Destroy_Connection(id,1); } #if(TEN) else if (Session.Type == GAME_TEN) { Destroy_TEN_Connection(id,1); } #endif #if(MPATH) else if (Session.Type == GAME_MPATH) { Destroy_MPATH_Connection(id,1); } #endif } } return (1); } // end of Handle_Timeout /*************************************************************************** * Stop_Game -- stops the game * * * * This routine clears any global flags that need it, in preparation for * * halting the game prematurely. * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/22/1995 BRR : Created. * *=========================================================================*/ static void Stop_Game(void) { Session.LoadGame = false; Session.EmergencySave = false; GameActive = 0; if (IsMono) { MonoClass::Disable(); } #ifdef WIN32 if (Session.Type == GAME_INTERNET){ ConnectionLost = true; Send_Statistics_Packet(); // Stop_Game() } #endif //WIN32 return; } // end of Stop_Game /*************************************************************************** * Build_Send_Packet -- Builds a big packet from a bunch of little ones. * * * * This routine takes events from the OutList, and puts them into a * * "meta-packet", which is transmitted to all systems we're connected to. * * Also, these events are added to our own DoList. * * * * Every Meta-Packet we send uses a FRAMEINFO packet as a header; this * * tells the other systems what frame we're on, as well as serving as a * * standard packet header. * * * * INPUT: * * buf buffer to store packet in * * bufsize max size of buffer * * frame_delay desired frame delay to attach to all outgoing packets * * num_cmds value to use for the CommandCount field * * cap max # events to send * * * * OUTPUT: * * new size of packet * * * * WARNINGS: * * 'num_cmds' should be the total of of commands, including all those sent * * this frame! * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static int Build_Send_Packet(void *buf, int bufsize, int frame_delay, int num_cmds, int cap) { int size = 0; EventClass *finfo; //------------------------------------------------------------------------ // All events start with a FRAMEINFO event; fill this part in. //------------------------------------------------------------------------ //........................................................................ // Set the event type //........................................................................ finfo = (EventClass *)buf; finfo->Type = EventClass::FRAMEINFO; //........................................................................ // Set the frame to execute this event on; this is protocol-specific //........................................................................ if (Session.CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { finfo->Frame = ((Frame + frame_delay + (Session.FrameSendRate - 1)) / Session.FrameSendRate) * Session.FrameSendRate; } else { finfo->Frame = Frame + frame_delay; } //........................................................................ // Fill in the rest of the event //........................................................................ finfo->ID = PlayerPtr->ID; finfo->Data.FrameInfo.CRC = GameCRC; finfo->Data.FrameInfo.CommandCount = num_cmds; finfo->Data.FrameInfo.Delay = frame_delay; //------------------------------------------------------------------------ // Initialize the # of bytes processed; this is protocol-specific //------------------------------------------------------------------------ if (Session.CommProtocol==COMM_PROTOCOL_SINGLE_NO_COMP) { size += sizeof(EventClass); } else { size += (offsetof(EventClass, Data) + size_of(EventClass, Data.FrameInfo)); } //------------------------------------------------------------------------ // Transfer all events from the OutList into the DoList, building our // packet while we go. //------------------------------------------------------------------------ switch (Session.CommProtocol) { //..................................................................... // COMM_PROTOCOL_SINGLE_NO_COMP: // We'll send at least a FRAMEINFO every single frame, no compression //..................................................................... case (COMM_PROTOCOL_SINGLE_NO_COMP): size = Add_Uncompressed_Events(buf, bufsize, frame_delay, size, cap); break; //..................................................................... // COMM_PROTOCOL_SINGLE_E_COMP: // Compress a group of packets into our send buffer; send out // compressed packets every frame. // COMM_PROTOCOL_MULTI_E_COMP: // Compress a group of packets into our send buffer; send out // compressed packets every 'n' frames. //..................................................................... case (COMM_PROTOCOL_SINGLE_E_COMP): case (COMM_PROTOCOL_MULTI_E_COMP): size = Add_Compressed_Events(buf, bufsize, frame_delay, size, cap); break; //..................................................................... // Default: We have no idea what to do, so do nothing. //..................................................................... default: size = 0; break; } return( size ); } /* end of Build_Send_Packet */ /*************************************************************************** * Add_Uncompressed_Events -- adds uncompressed events to a packet * * * * INPUT: * * buf buffer to store packet in * * bufsize max size of buffer * * frame_delay desired frame delay to attach to all outgoing packets * * size current packet size * * cap max # events to process * * * * OUTPUT: * * new size value * * * * WARNINGS: * * This routine MUST check to be sure it doesn't overflow the buffer. * * * * HISTORY: * * 11/21/1995 DRD : Created. * *=========================================================================*/ static int Add_Uncompressed_Events(void *buf, int bufsize, int frame_delay, int size, int cap) { int num = 0; // # of events processed int ev_size; // size of event we're adding //------------------------------------------------------------------------ // Loop until there are no more events, or we've processed our max # of // events, or the buffer is full. //------------------------------------------------------------------------ while (OutList.Count && (num < cap)) { Keyboard->Check(); if (OutList.First().Type==EventClass::ADDPLAYER) { ev_size = sizeof(EventClass) + OutList.First().Data.Variable.Size; } else { ev_size = sizeof(EventClass); } //..................................................................... // Will the next event exceed the size of the buffer? If so, break. //..................................................................... if ( (size + ev_size) > bufsize ) { return (size); } //..................................................................... // Set the event's frame delay //..................................................................... OutList.First().Frame = Frame + frame_delay; //..................................................................... // Set the event's ID //..................................................................... OutList.First().ID = PlayerPtr->ID; //..................................................................... // Transfer the event in OutList to DoList, un-queue the OutList // event. If the DoList is full, stop transferring immediately. //..................................................................... OutList.First().IsExecuted = 0; if (!DoList.Add(OutList.First())) { return (size); } #ifdef MIRROR_QUEUE MirrorList.Add(OutList.First()); #endif //..................................................................... // Add event to the send packet //..................................................................... if (OutList.First().Type==EventClass::ADDPLAYER) { memcpy ( ((char *)buf) + size, &OutList.First(), sizeof(EventClass) ); size += sizeof(EventClass); memcpy ( ((char *)buf) + size, OutList.First().Data.Variable.Pointer, OutList.First().Data.Variable.Size); size += OutList.First().Data.Variable.Size; } else { memcpy ( ((char *)buf) + size, &OutList.First(), sizeof(EventClass) ); size += sizeof(EventClass); } //..................................................................... // Increment our event counter; delete the last event from the queue //..................................................................... num++; OutList.Next(); } return (size); } // end of Add_Uncompressed_Events /*************************************************************************** * Add_Compressed_Events -- adds an compressed events to a packet * * * * INPUT: * * buf buffer to store packet in * * bufsize max size of buffer * * frame_delay desired frame delay to attach to all outgoing packets * * size reference to current packet size * * cap max # events to process * * * * OUTPUT: * * new size value * * * * WARNINGS: * * This routine MUST check to be sure it doesn't overflow the buffer. * * * * HISTORY: * * 11/21/1995 DRD : Created. * *=========================================================================*/ static int Add_Compressed_Events(void *buf, int bufsize, int frame_delay, int size, int cap) { int num = 0; // # of events processed EventClass::EventType eventtype; // type of event being compressed EventClass prevevent; // last event processed int datasize; // size of element plucked from event union int storedsize; // actual # bytes stored from event unsigned char *unitsptr = NULL; // ptr to buffer pos to store mega. rep count unsigned char numunits = 0; // megamission rep count value bool missiondup = false; // flag: is this event a megamission repeat? //------------------------------------------------------------------------ // clear previous event //------------------------------------------------------------------------ memset (&prevevent, 0, sizeof(EventClass)); if (Debug_Print_Events) { printf("\n(%d) Building Send Packet\n", Frame); } //------------------------------------------------------------------------ // Loop until there are no more events, we've processed our max # of // events, or the buffer is full. //------------------------------------------------------------------------ while (OutList.Count && (num < cap)) { Keyboard->Check(); eventtype = OutList.First().Type; datasize = EventClass::EventLength[ eventtype ]; //..................................................................... // For a variable-sized event, pull the size from the event; otherwise, // the size will be the data element size plus the event type value. // (The other data elements in the event, Frame, ID, etc, are stored // in the packet header.) //..................................................................... if (eventtype==EventClass::ADDPLAYER) { storedsize = datasize + sizeof (EventClass::EventType) + OutList.First().Data.Variable.Size; } else { storedsize = datasize + sizeof (EventClass::EventType); } //..................................................................... // MegaMission compression: MegaMissions are stored as: // EventType // Rep Count // MegaMission structure (event # 1 only) // Whom #2 // Whom #3 // Whom #4 // ... // Whom #n //..................................................................... if (prevevent.Type == EventClass::MEGAMISSION) { //.................................................................. // If previous & current events are both MegaMissions: //.................................................................. if (eventtype == EventClass::MEGAMISSION) { //............................................................... // If the Mission, Target, & Destination are the same, compress // the events into one: // - Change datasize to the size of the 'Whom' field only // - set total # bytes to store to the size of the 'Whom' only // - increment the MegaMission rep count // - set the MegaMission rep flag //............................................................... if (OutList.First().Data.MegaMission.Mission == prevevent.Data.MegaMission.Mission && OutList.First().Data.MegaMission.Target == prevevent.Data.MegaMission.Target && OutList.First().Data.MegaMission.Destination == prevevent.Data.MegaMission.Destination) { #if (0)//PG if (Debug_Print_Events) { printf(" adding Whom:%x (%x) Mission:%s Target:%x (%x) Dest:%x (%x)\n", OutList.First().Data.MegaMission.Whom.As_TARGET(), OutList.First().Data.MegaMission.Whom, MissionClass::Mission_Name(OutList.First().Data.MegaMission.Mission), OutList.First().Data.MegaMission.Target.As_TARGET(), OutList.First().Data.MegaMission.Target, OutList.First().Data.MegaMission.Destination.As_TARGET(), OutList.First().Data.MegaMission.Destination); } #endif datasize = sizeof(prevevent.Data.MegaMission.Whom); storedsize = datasize; numunits++; missiondup = true; } //............................................................... // Data doesn't match; start a new run of MegaMissions: // - Store previous MegaMission rep count // - Init 'unitsptr' to buffer pos after next EventType // - set total # bytes to store to 'datasize' + sizeof(EventType) + // sizeof (numunits) // - init the MegaMission rep count to 1 // - clear the MegaMission rep flag //............................................................... else { if (Debug_Print_Events) { printf(" New MEGAMISSION run:\n"); } *unitsptr = numunits; unitsptr = ((unsigned char *)buf) + size + sizeof(EventClass::EventType); storedsize += sizeof(numunits); numunits = 1; missiondup = false; } } //.................................................................. // Previous event was a MegaMission, but this one isn't: end the // run of MegaMissions: // - Store previous MegaMission rep count // - Clear variables //.................................................................. else { *unitsptr = numunits; // save # events in our run unitsptr = NULL; // init other values numunits = 0; missiondup = false; } } //..................................................................... // The previous event is not a MEGAMISSION but the current event is: // Set up a new run of MegaMissions: // - Init 'unitsptr' to buffer pos after next EventType // - set total # bytes to store to 'datasize' + sizeof(EventType) + // sizeof (numunits) // - init the MegaMission rep count to 1 // - clear the MegaMission rep flag //..................................................................... else if (eventtype == EventClass::MEGAMISSION) { if (Debug_Print_Events) { printf(" New MEGAMISSION run:\n"); } unitsptr = ((unsigned char *)buf) + size + sizeof(EventClass::EventType); storedsize += sizeof(numunits); numunits = 1; missiondup = false; } //..................................................................... // Will the next event exceed the size of the buffer? If so, // stop compressing. //..................................................................... if ( (size + storedsize) > bufsize ) break; //..................................................................... // Set the event's frame delay (this is protocol-dependent) //..................................................................... if (Session.CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { OutList.First().Frame = ((Frame + frame_delay + (Session.FrameSendRate - 1)) / Session.FrameSendRate) * Session.FrameSendRate; } else { OutList.First().Frame = Frame + frame_delay; } //..................................................................... // Set the event's ID //..................................................................... OutList.First().ID = PlayerPtr->ID; //..................................................................... // Transfer the event in OutList to DoList, un-queue the OutList event. // If the DoList is full, stop transferring immediately. //..................................................................... OutList.First().IsExecuted = 0; if ( !DoList.Add( OutList.First() ) ) { break; } #ifdef MIRROR_QUEUE MirrorList.Add(OutList.First()); #endif //--------------------------------------------------------------------- // Compress the event into the send packet buffer //--------------------------------------------------------------------- switch ( eventtype ) { //.................................................................. // RESPONSE_TIME: just use the Delay field of the FrameInfo union //.................................................................. case (EventClass::RESPONSE_TIME): *(EventClass::EventType *)( ((char *)buf) + size) = eventtype; memcpy ( ((char *)buf) + size + sizeof(EventClass::EventType), &OutList.First().Data.FrameInfo.Delay, datasize ); size += (datasize + sizeof(EventClass::EventType)); break; //.................................................................. // MEGAMISSION: //.................................................................. case (EventClass::MEGAMISSION): //............................................................... // Repeated mission in a run: // - Update the rep count (in case we break out) // - Copy the Whom field only //............................................................... if (missiondup) { *unitsptr = numunits; memcpy ( ((char *)buf) + size, &OutList.First().Data.MegaMission.Whom, datasize ); size += datasize; } //............................................................... // 1st mission in a run: // - Init the rep count (in case we break out) // - Set the EventType // - Copy the MegaMission structure, leaving room for 'numunits' //............................................................... else { *unitsptr = numunits; *(EventClass::EventType *)( ((char *)buf) + size) = eventtype; memcpy ( ((char *)buf) + size + sizeof(EventClass::EventType) + sizeof(numunits), &OutList.First().Data.MegaMission, datasize ); size += (datasize + sizeof(EventClass::EventType) + sizeof(numunits)); } break; //.................................................................. // Variable-sized packets: Copy the packet Size & the buffer //.................................................................. case (EventClass::ADDPLAYER): *(EventClass::EventType *)( ((char *)buf) + size) = eventtype; memcpy ( ((char *)buf) + size + sizeof(EventClass::EventType), &OutList.First().Data.Variable.Size, datasize ); size += (datasize + sizeof(EventClass::EventType)); memcpy ( ((char *)buf) + size, OutList.First().Data.Variable.Pointer, OutList.First().Data.Variable.Size); size += OutList.First().Data.Variable.Size; break; //.................................................................. // Default case: Just copy over the data field from the union //.................................................................. default: *(EventClass::EventType *)( ((char *)buf) + size) = eventtype; memcpy ( ((char *)buf) + size + sizeof(EventClass::EventType), &OutList.First().Data, datasize ); size += (datasize + sizeof(EventClass::EventType)); break; } //--------------------------------------------------------------------- // update # events processed //--------------------------------------------------------------------- num++; //--------------------------------------------------------------------- // Update 'prevevent' //--------------------------------------------------------------------- memcpy ( &prevevent, &OutList.First(), sizeof(EventClass) ); //--------------------------------------------------------------------- // Go to the next event to process //--------------------------------------------------------------------- OutList.Next(); } if (Debug_Print_Events) { printf("\n"); } return (size); } // end of Add_Compressed_Events /*************************************************************************** * Breakup_Receive_Packet -- Splits a big packet into little ones. * * * * INPUT: * * buf buffer to break up * * bufsize length of buffer * * * * OUTPUT: * * # events added to queue, -1 if fatal error (queue is full) * * (return value includes any FRAMEINFO packets encountered; * * FRAMESYNC's are ignored) * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static int Breakup_Receive_Packet(void *buf, int bufsize ) { int count = 0; /* ** is there enough leftover for another record */ switch (Session.CommProtocol) { case (COMM_PROTOCOL_SINGLE_NO_COMP): count = Extract_Uncompressed_Events(buf, bufsize); break; default: count = Extract_Compressed_Events(buf, bufsize); break; } return (count); } /* end of Breakup_Receive_Packet */ /*************************************************************************** * Extract_Uncompressed_Events -- extracts events from a packet * * * * INPUT: * * buf buffer containing events to extract * * bufsize length of 'buf' * * * * OUTPUT: * * # events extracted * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 DRD : Created. * *=========================================================================*/ static int Extract_Uncompressed_Events(void *buf, int bufsize) { int count = 0; int pos = 0; int leftover = bufsize; EventClass *event; //------------------------------------------------------------------------ // Loop until there are no more events in the packet //------------------------------------------------------------------------ while (leftover >= sizeof(EventClass) ) { Keyboard->Check(); event = (EventClass *)(((char *)buf) + pos); //..................................................................... // add event to the DoList, only if it's not a FRAMESYNC // (but FRAMEINFO's do get added.) //..................................................................... if (event->Type != EventClass::FRAMESYNC) { event->IsExecuted = 0; //.................................................................. // Special processing for variable-sized events //.................................................................. if (event->Type == EventClass::ADDPLAYER) { event->Data.Variable.Pointer = new char[event->Data.Variable.Size]; memcpy (event->Data.Variable.Pointer, ((char *)buf) + sizeof(EventClass), event->Data.Variable.Size); pos += event->Data.Variable.Size; leftover -= event->Data.Variable.Size; } if (!DoList.Add( *event )) { if (event->Type == EventClass::ADDPLAYER) { delete [] event->Data.Variable.Pointer; } return (-1); } #ifdef MIRROR_QUEUE MirrorList.Add(*event); #endif //.................................................................. // Keep count of how many events we add to the queue //.................................................................. count++; } //..................................................................... // Point to the next position in the buffer; decrement our 'leftover' //..................................................................... pos += sizeof(EventClass); leftover -= sizeof(EventClass); } return (count); } // end of Extract_Uncompressed_Events /*************************************************************************** * Extract_Compressed_Events -- extracts events from a packet * * * * INPUT: * * buf buffer containing events to extract * * bufsize length of 'buf' * * * * OUTPUT: * * # events extracted * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 DRD : Created. * *=========================================================================*/ static int Extract_Compressed_Events(void *buf, int bufsize) { int pos = 0; // current buffer parsing position int leftover = bufsize; // # bytes left to process EventClass *event; // event ptr for parsing buffer int count = 0; // # events processed int datasize = 0; // size of data to copy EventClass eventdata; // stores Frame, ID, etc unsigned char numunits = 0; // # units stored in compressed MegaMissions //------------------------------------------------------------------------ // Clear work event structure //------------------------------------------------------------------------ memset (&eventdata, 0, sizeof(EventClass)); //------------------------------------------------------------------------ // Assume the first event is a FRAMEINFO event // Init 'datasize' to the amount of data to copy, minus the EventType value // For the 1st packet only, this will include all info before the Data // union, plus the size of the FrameInfo structure, minus the EventType size. //------------------------------------------------------------------------ datasize = (offsetof(EventClass, Data) + size_of(EventClass, Data.FrameInfo)) - sizeof(EventClass::EventType); event = (EventClass *)(((char *)buf) + pos); while (leftover >= (datasize + (int)sizeof(EventClass::EventType)) ) { Keyboard->Check(); //..................................................................... // add event to the DoList, only if it's not a FRAMESYNC // (but FRAMEINFO's do get added.) //..................................................................... if (event->Type != EventClass::FRAMESYNC) { //.................................................................. // initialize the common data from the FRAMEINFO event // keeping IsExecuted 0 //.................................................................. if (event->Type == EventClass::FRAMEINFO) { eventdata.Frame = event->Frame; eventdata.ID = event->ID; //............................................................... // Adjust position past the common data //............................................................... pos += (offsetof(EventClass, Data) - sizeof(EventClass::EventType)); leftover -= (offsetof(EventClass, Data) - sizeof(EventClass::EventType)); } //.................................................................. // if MEGAMISSION event get the number of units (events to generate) //.................................................................. else if (event->Type == EventClass::MEGAMISSION) { numunits = *(((unsigned char *)buf) + pos + sizeof(eventdata.Type)); pos += sizeof(numunits); leftover -= sizeof(numunits); } //.................................................................. // clear the union data portion of the event //.................................................................. memset (&eventdata.Data, 0, sizeof(eventdata.Data)); eventdata.Type = event->Type; datasize = EventClass::EventLength[ eventdata.Type ]; switch (eventdata.Type) { case (EventClass::RESPONSE_TIME): memcpy ( &eventdata.Data.FrameInfo.Delay, ((char *)buf) + pos + sizeof(EventClass::EventType), datasize ); break; case (EventClass::ADDPLAYER): memcpy ( &eventdata.Data.Variable.Size, ((char *)buf) + pos + sizeof(EventClass::EventType), datasize ); eventdata.Data.Variable.Pointer = new char[eventdata.Data.Variable.Size]; memcpy (eventdata.Data.Variable.Pointer, ((char *)buf) + pos + sizeof(EventClass::EventType) + datasize, eventdata.Data.Variable.Size); pos += eventdata.Data.Variable.Size; leftover -= eventdata.Data.Variable.Size; break; case (EventClass::MEGAMISSION): memcpy ( &eventdata.Data.MegaMission, ((char *)buf) + pos + sizeof(EventClass::EventType), datasize ); if (numunits > 1) { pos += (datasize + sizeof(EventClass::EventType)); leftover -= (datasize + sizeof(EventClass::EventType)); datasize = sizeof(eventdata.Data.MegaMission.Whom); while (numunits) { Keyboard->Check(); if ( !DoList.Add( eventdata ) ) { return (-1); } #ifdef MIRROR_QUEUE MirrorList.Add( eventdata ); #endif //...................................................... // Keep count of how many events we add to the queue //...................................................... count++; numunits--; memcpy ( &eventdata.Data.MegaMission.Whom, ((char *)buf) + pos, datasize ); //...................................................... // if one unit left fall thru to normal code //...................................................... if (numunits == 1) { datasize -= sizeof(EventClass::EventType); break; } else { pos += datasize; leftover -= datasize; } } } break; default: memcpy ( &eventdata.Data, ((char *)buf) + pos + sizeof(EventClass::EventType), datasize ); break; } if ( !DoList.Add( eventdata ) ) { if (eventdata.Type == EventClass::ADDPLAYER) { delete [] eventdata.Data.Variable.Pointer; } return (-1); } #ifdef MIRROR_QUEUE MirrorList.Add( eventdata ); #endif //.................................................................. // Keep count of how many events we add to the queue //.................................................................. count++; pos += (datasize + sizeof(EventClass::EventType)); leftover -= (datasize + sizeof(EventClass::EventType)); if (leftover) { event = (EventClass *)(((char *)buf) + pos); datasize = EventClass::EventLength[ event->Type ]; if (event->Type == EventClass::MEGAMISSION) { datasize += sizeof(numunits); } } } //..................................................................... // FRAMESYNC event: This >should< be the only event in the buffer, // and it will be uncompressed. //..................................................................... else { pos += (datasize + sizeof(EventClass::EventType)); leftover -= (datasize + sizeof(EventClass::EventType)); event = (EventClass *)(((char *)buf) + pos); //.................................................................. // size of FRAMESYNC event - EventType size //.................................................................. datasize = (offsetof(EventClass, Data) + size_of(EventClass, Data.FrameInfo)) - sizeof(EventClass::EventType); } } return (count); } // end of Extract_Compressed_Events /*************************************************************************** * Execute_DoList -- Executes commands from the DoList * * * * This routine executes any events in the DoList that need to be executed * * on the current game frame. The events must be executed in a special * * order, so that all systems execute all events in exactly the same * * order. * * * * This routine also handles checking the Game CRC sent by other systems * * against my own, to be sure we're still in sync. * * * * INPUT: * * max_houses # houses to execute commands for * * base_house HousesType to start with * * net ptr to connection manager; NULL if none * * skip_crc a frame-based countdown timer; if it's non-zero, the * * CRC check will be skipped. Ignored if NULL. * * their_frame array of their frame #'s * * their_sent array of # commands they've sent * * their_recv array of # commands I've received from them * * * * (their_xxx are ignored if 'net' is NULL.) * * * * OUTPUT: * * 1 = OK, 0 = some error occurred (CRC error, packet rcv'd too late.) * * * * WARNINGS: * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static int Execute_DoList(int max_houses, HousesType base_house, ConnManClass *net, CDTimerClass *skip_crc, long *their_frame, unsigned short *their_sent, unsigned short *their_recv) { HousesType house; HouseClass *hptr; int i,j,k; int index; int check_crc; Check_Mirror(); #if(TIMING_FIX) // // If MPlayerMaxAhead is recomputed such that it increases, the systems // may try to free-run to the new MaxAhead value. If so, they may miss // an event that was generated after the TIMING event was created, but // before it executed; this event will be scheduled with the older, // shorter MaxAhead value. If a system doesn't receive this event, it // may execute past the frame it's scheduled to execute on, creating // a Packet-Recieved-Too-Late error. To prevent this, find any events // that are scheduled to execute during this "period of vulnerability", // and re-schedule for the end of that period. // for (j = 0; j < DoList.Count; j++) { if (DoList[j].Type != EventClass::FRAMEINFO && DoList[j].Frame > (unsigned)NewMaxAheadFrame1 && DoList[j].Frame < (unsigned)NewMaxAheadFrame2) { DoList[j].Frame = (unsigned)NewMaxAheadFrame2; #ifdef MIRROR_QUEUE MirrorList[j].Frame = NewMaxAheadFrame2; #endif } } #endif //------------------------------------------------------------------------ // Execute the DoList. Events must be executed in the same order on all // systems; so, execute them in the order of the HouseClass array. This // array is stored in the same order on all systems. //------------------------------------------------------------------------ for (i = 0; i < max_houses; i++) { //..................................................................... // Convert our index into a HousesType value //..................................................................... house = (HousesType)(i + base_house); hptr = HouseClass::As_Pointer(house); //..................................................................... // If for some reason this house doesn't exist, skip it. // Also, if this house has exited the game, skip it. (The user can // generate events after he exits, because the exit event is scheduled // at least FrameSendRate*3 frames ahead. If one system gets these // packets & another system doesn't, they'll go out of sync because // they aren't checking the CommandCount for that house, since that // house isn't connected any more.) //..................................................................... if (!hptr) { continue; } if (!hptr->IsHuman) { continue; } //..................................................................... // Loop through all events //..................................................................... for (j = 0; j < DoList.Count; j++) { if (net) Update_Queue_Mono (net, 6); //.................................................................. // If this event was from the currently-executing player ID, and it's // time to execute it, execute it. //.................................................................. if (DoList[j].ID == hptr->ID && (unsigned) Frame >= DoList[j].Frame && !DoList[j].IsExecuted) { //............................................................... // Error if it's too late to execute this packet! // (Hack: disable this check for solo or skirmish mode.) //............................................................... if ((unsigned)Frame > DoList[j].Frame && DoList[j].Type != EventClass::FRAMEINFO && Session.Type != GAME_NORMAL && Session.Type != GAME_SKIRMISH) { #if(TEN) Send_TEN_Packet_Too_Late(); #endif // TEN #if(MPATH) //Send_MPATH_Packet_Too_Late(); #endif // MPATH Dump_Packet_Too_Late_Stuff(&DoList[j], net, their_frame, their_sent, their_recv); WWMessageBox().Process (TXT_PACKET_TOO_LATE); return (0); } //............................................................... // Only execute EXIT & OPTIONS commands if they're from myself. //............................................................... if (DoList[j].Type==EventClass::EXIT || DoList[j].Type==EventClass::OPTIONS) { #ifdef WIN32 if (DoList[j].Type==EventClass::EXIT) { /* ** Flag that this house lost because it quit. */ HousesType quithouse; HouseClass *quithptr; for (int player = 0; player < max_houses ; player++) { quithouse = (HousesType)(player + base_house); quithptr = HouseClass::As_Pointer(quithouse); if (!quithptr) { continue; } if (quithptr->ID == DoList[j].ID) { quithptr->IsGiverUpper = true; break; } } /* ** Send the game statistics packet now since the game is effectivly over */ if (Session.Players.Count() == 2 && Session.Type == GAME_INTERNET && !GameStatisticsPacketSent) { Register_Game_End_Time(); Send_Statistics_Packet(); // Event - player aborted, and there were only 2 left. } } #endif //WIN32 if (Debug_Print_Events) { if (DoList[j].Type==EventClass::EXIT) { printf("(%d) Executing EXIT, ID:%d (%s), EvFrame:%d\n", Frame, DoList[j].ID, (HouseClass::As_Pointer((HousesType)(DoList[j].ID)))->IniName, DoList[j].Frame); } } if (DoList[j].ID == PlayerPtr->ID) { DoList[j].Execute(); } else if (DoList[j].Type==EventClass::EXIT) { //............................................................ // If this EXIT event isn't from myself, destroy the connection // for that player. The HousesType for this event is the // connection ID. //............................................................ if (Session.Type == GAME_MODEM || Session.Type == GAME_NULL_MODEM) { //PG Destroy_Null_Connection( house, 0 ); } else if ((Session.Type == GAME_IPX || Session.Type == GAME_INTERNET || Session.Type == GAME_TEN || Session.Type == GAME_MPATH) && net) { index = net->Connection_Index (house); if (index != -1) { for (k = index; k < net->Num_Connections() - 1; k++) { their_frame[k] = their_frame[k+1]; their_sent[k] = their_sent[k+1]; their_recv[k] = their_recv[k+1]; } if (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET) { Destroy_Connection(house,0); } #if(TEN) else if (Session.Type == GAME_TEN) { Destroy_TEN_Connection(house,0); } #endif // TEN #if(MPATH) else if (Session.Type == GAME_MPATH) { Destroy_MPATH_Connection(house,0); } #endif // MPATH } } // // Special case for recording playback: turn the house over // to the computer. // if (Session.Play && DoList[j].Type==EventClass::EXIT) { hptr->IsHuman = false; hptr->IQ = Rule.MaxIQ; hptr->Computer_Paranoid(); strcpy (hptr->IniName,Text_String(TXT_COMPUTER)); Session.NumPlayers--; } } } //............................................................... // For a FRAMEINFO event, check the CRC value. //............................................................... else if (DoList[j].Type == EventClass::FRAMEINFO) { //............................................................ // Skip the CRC check if we're less than 32 frames into the game; // this will prevent a newly-loaded modem game from instantly // going out of sync, if the games were saved at different // frame numbers. //............................................................ if (!skip_crc || *skip_crc == 0) { check_crc = 1; } else { check_crc = 0; } if (check_crc && DoList[j].Frame == Frame && DoList[j].Data.FrameInfo.Delay < 32) { index = ((DoList[j].Frame - DoList[j].Data.FrameInfo.Delay) & 0x001f); if (CRC[index] != DoList[j].Data.FrameInfo.CRC) { Print_CRCs(&DoList[j]); #if(TEN) Send_TEN_Out_Of_Sync(); #endif // TEN #if(MPATH) //Send_MPATH_Out_Of_Sync(); #endif // MPATH #if (0)//PG if (WWMessageBox().Process (TXT_OUT_OF_SYNC, TXT_CONTINUE, TXT_STOP) == 0) { if (Session.Type == GAME_MODEM || Session.Type == GAME_NULL_MODEM) { //PG Destroy_Null_Connection( house, -1 ); Shutdown_Modem(); Session.Type = GAME_NORMAL; } else if ((Session.Type == GAME_IPX || Session.Type == GAME_INTERNET) && net) { while (net->Num_Connections()) { Keyboard->Check(); Destroy_Connection (net->Connection_ID(0), -1); } } #if(TEN) else if (Session.Type == GAME_TEN && net) { while (net->Num_Connections()) { Destroy_TEN_Connection (net->Connection_ID(0), -1); } } #endif #if(MPATH) else if (Session.Type == GAME_MPATH && net) { while (net->Num_Connections()) { Destroy_MPATH_Connection (net->Connection_ID(0), -1); } } #endif Map.Flag_To_Redraw(true); } else { return (0); } #endif return (1); } } } //............................................................... // Execute other commands //............................................................... else { DoList[j].Execute(); } //............................................................... // Mark this event as executed. //............................................................... DoList[j].IsExecuted = 1; #ifdef MIRROR_QUEUE MirrorList[j].IsExecuted = 1; #endif } } } return (1); } // end of Execute_DoList /*************************************************************************** * Clean_DoList -- Cleans out old events from the DoList * * * * Currently, an event can only be removed from the DoList if it's at the * * head of the list; and event can't be removed from the middle. So, * * this routine loops as long as the next event in the DoList has been * * executed, it's removed. * * * * INPUT: * * net ptr to connection manager; ignored if NULL * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static void Clean_DoList(ConnManClass *net) { while (DoList.Count) { Keyboard->Check(); if (net) Update_Queue_Mono (net, 7); //..................................................................... // Discard events that have been executed, OR it's too late to execute. // (This happens if another player exits the game; he'll leave FRAMEINFO // events lying around in my queue. They won't have been "executed", // because his IPX connection was destroyed.) //..................................................................... if ( (DoList.First().IsExecuted) || ((unsigned)Frame > DoList.First().Frame) ) { DoList.Next(); #ifdef MIRROR_QUEUE MirrorList.Next(); #endif } else { break; } } } // end of Clean_DoList /*************************************************************************** * Queue_Record -- Records the DoList to disk * * * * This routine just saves any events in the DoList to disk; we can later * * "play back" the recording just be pulling events from disk rather than * * from the network! * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 08/14/1995 BRR : Created. * *=========================================================================*/ static void Queue_Record(void) { int i,j; //------------------------------------------------------------------------ // Compute # of events to save this frame //------------------------------------------------------------------------ j = 0; for (i = 0; i < DoList.Count; i++) { if (Frame == DoList[i].Frame && !DoList[i].IsExecuted) { j++; } } //------------------------------------------------------------------------ // Save the # of events, then all events. //------------------------------------------------------------------------ Session.RecordFile.Write (&j,sizeof(j)); for (i = 0; i < DoList.Count; i++) { if (Frame == DoList[i].Frame && !DoList[i].IsExecuted) { Session.RecordFile.Write (&DoList[i],sizeof (EventClass)); j--; } } } /* end of Queue_Record */ /*************************************************************************** * Queue_Playback -- plays back queue entries from a record file * * * * This routine reads events from disk, putting them into the DoList; * * it then executes the DoList just like the network version does. The * * result is that the game "plays back" like a recording. * * * * This routine detects mouse motion and stops playback, so it can work * * like an "attract" mode, showing a demo of the game itself. * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/15/1995 BRR : Created. * *=========================================================================*/ static void Queue_Playback(void) { int numevents; EventClass event; int i; int ok; static int mx,my; int max_houses; HousesType base_house; int key; int testframe; //------------------------------------------------------------------------ // If the user hits ESC, stop the playback //------------------------------------------------------------------------ if (Keyboard->Check()) { key = Keyboard->Get(); if (key == KA_ESC || Session.Attract) { GameActive = 0; return; } } //------------------------------------------------------------------------ // If we're in "Attract" mode, and the user moves the mouse, stop the // playback. //------------------------------------------------------------------------ if (Session.Attract && Frame > 0 && (mx != Get_Mouse_X() || my != Get_Mouse_Y())) { GameActive = 0; return; } mx = Get_Mouse_X(); my = Get_Mouse_Y(); //------------------------------------------------------------------------ // Compute the Game's CRC //------------------------------------------------------------------------ Compute_Game_CRC(); CRC[Frame & 0x001f] = GameCRC; //------------------------------------------------------------------------ // If we've reached the CRC print frame, do so & exit //------------------------------------------------------------------------ if (Frame >= Session.TrapPrintCRC) { Print_CRCs(NULL); Prog_End("Queue_Playback reached CRC print frame", true); Emergency_Exit(0); } //------------------------------------------------------------------------ // Don't read anything the first time through (since the Queue_AI_Network // routine didn't write anything the first time through); do this after the // CRC is computed, since we'll still need a CRC for Frame 0. //------------------------------------------------------------------------ if (Frame==0 && Session.Type!=GAME_NORMAL) { return; } //------------------------------------------------------------------------ // Only process every 'FrameSendRate' frames //------------------------------------------------------------------------ testframe = ((Frame + (Session.FrameSendRate - 1)) / Session.FrameSendRate) * Session.FrameSendRate; if ( (Session.Type != GAME_NORMAL && Session.Type != GAME_SKIRMISH) && Session.CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { if (Frame != testframe) { return; } } //------------------------------------------------------------------------ // Read the DoList from disk //------------------------------------------------------------------------ ok = 1; if (Session.RecordFile.Read (&numevents, sizeof(numevents)) == sizeof(numevents)) { for (i = 0; i < numevents; i++) { if (Session.RecordFile.Read (&event, sizeof(EventClass)) == sizeof(EventClass)) { event.IsExecuted = 0; DoList.Add (event); #ifdef MIRROR_QUEUE MirrorList.Add(event); #endif } else { ok = 0; break; } } } else { ok = 0; } if (!ok) { GameActive = 0; return; } //------------------------------------------------------------------------ // Execute the DoList; if an error occurs, bail out. //------------------------------------------------------------------------ if (Session.Type == GAME_NORMAL) { max_houses = 1; base_house = PlayerPtr->Class->House; } else { max_houses = Session.MaxPlayers; base_house = HOUSE_MULTI1; } if (!Execute_DoList(max_houses, base_house, NULL, NULL, NULL, NULL, NULL)) { GameActive = 0; return; } //------------------------------------------------------------------------ // Clean out the DoList //------------------------------------------------------------------------ Clean_DoList(NULL); } /* end of Queue_Playback */ /*************************************************************************** * Compute_Game_CRC -- Computes a CRC value of the entire game. * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 05/09/1995 BRR : Created. * *=========================================================================*/ static void Compute_Game_CRC(void) { int i,j; VesselClass *vessp; InfantryClass *infp; UnitClass *unitp; BuildingClass *bldgp; ObjectClass *objp; HouseClass *housep; GameCRC = 0; //------------------------------------------------------------------------ // Infantry //------------------------------------------------------------------------ for (i = 0; i < Infantry.Count(); i++) { infp = (InfantryClass *)Infantry.Active_Ptr(i); Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); Add_CRC (&GameCRC, (int)infp->Speed + (int)infp->NavCom); Add_CRC (&GameCRC, (int)infp->Mission + (int)infp->TarCom); } //------------------------------------------------------------------------ // Units //------------------------------------------------------------------------ for (i = 0; i < Units.Count(); i++) { unitp = (UnitClass *)Units.Active_Ptr(i); Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing + (int)unitp->SecondaryFacing); } //------------------------------------------------------------------------ // Shippies //------------------------------------------------------------------------ for (i = 0; i < Vessels.Count(); i++) { vessp = (VesselClass *)Vessels.Active_Ptr(i); Add_CRC (&GameCRC, (int)vessp->Coord + (int)vessp->PrimaryFacing); Add_CRC (&GameCRC, (int)vessp->Speed + (int)vessp->NavCom); Add_CRC (&GameCRC, (int)vessp->Strength); Add_CRC (&GameCRC, (int)vessp->Mission + (int)vessp->TarCom); } //------------------------------------------------------------------------ // Buildings //------------------------------------------------------------------------ for (i = 0; i < Buildings.Count(); i++) { bldgp = (BuildingClass *)Buildings.Active_Ptr(i); Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); } //------------------------------------------------------------------------ // Houses //------------------------------------------------------------------------ for (i = 0; i < Houses.Count(); i++) { housep = (HouseClass *)Houses.Active_Ptr(i); Add_CRC (&GameCRC, (int)housep->Credits + (int)housep->Power + (int)housep->Drain); } //------------------------------------------------------------------------ // Map Layers //------------------------------------------------------------------------ for (i = 0; i < LAYER_COUNT; i++) { for (j = 0; j < Map.Layer[i].Count(); j++) { objp = Map.Layer[i][j]; Add_CRC (&GameCRC, (int)objp->Coord + (int)objp->What_Am_I()); } } //------------------------------------------------------------------------ // Logic Layers //------------------------------------------------------------------------ for (i = 0; i < Logic.Count(); i++) { objp = Logic[i]; Add_CRC (&GameCRC, (int)objp->Coord + (int)objp->What_Am_I()); } //------------------------------------------------------------------------ // A random # //------------------------------------------------------------------------ // Add_CRC(&GameCRC, Scen.RandomNumber.Seed); Add_CRC(&GameCRC, Scen.RandomNumber); } /* end of Compute_Game_CRC */ /*************************************************************************** * Add_CRC -- Adds a value to a CRC * * * * INPUT: * * crc ptr to crc * * val value to add * * * * OUTPUT: * * none * * * * WARNINGS: * * none * * * * HISTORY: * * 05/09/1995 BRR : Created. * *=========================================================================*/ void Add_CRC(unsigned long *crc, unsigned long val) { int hibit; if ( (*crc) & 0x80000000) { hibit = 1; } else { hibit = 0; } (*crc) <<= 1; (*crc) += val; (*crc) += hibit; } /* end of Add_CRC */ /*************************************************************************** * Print_CRCs -- Prints a data file for finding Sync Bugs * * * * INPUT: * * ev -- event to display * * * * OUTPUT: * * none * * * * WARNINGS: * * none * * * * HISTORY: * * 05/09/1995 BRR : Created. * *=========================================================================*/ static void Print_CRCs(EventClass *ev) { int i,j; InfantryClass *infp; UnitClass *unitp; VesselClass *vesselp; BuildingClass *bldgp; ObjectClass *objp; FILE *fp; HouseClass *housep; HousesType house; int color; Mono_Clear_Screen(); Mono_Set_Cursor (0,0); fp = fopen("OUT.TXT","wt"); if (fp==NULL) { return; } for (i = 0; i < 32; i++) { fprintf(fp,"CRC[%d]=%x\n",i,CRC[i]); } // // Houses // for (house = HOUSE_MULTI1; house <= HOUSE_MULTI8; house++) { GameCRC = 0; housep = HouseClass::As_Pointer (house); if (housep) { HousesType actlike = housep->ActLike; color = housep->RemapColor; fprintf(fp,"%s: IsHuman:%d Color:%s ID:%d ActLike:%s\n", housep->IniName, housep->IsHuman, ColorNames[color], housep->ID, HouseClass::As_Pointer(actlike)->Class->Name()); Add_CRC (&GameCRC, (int)housep->Credits + (int)housep->Power + (int)housep->Drain); Mono_Printf("House %s:%x\n",housep->Class->Name(),GameCRC); } } // // Infantry // for (house = HOUSE_MULTI1; house <= HOUSE_MULTI8; house++) { housep = HouseClass::As_Pointer (house); if (housep) { GameCRC = 0; fprintf(fp,"-------------------- %s Infantry -------------------\n", housep->Class->Name()); for (i = 0; i < Infantry.Count(); i++) { infp = (InfantryClass *)Infantry.Active_Ptr(i); if (infp->Owner()==house) { Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); Add_CRC (&GameCRC, (int)infp->Speed + (int)infp->NavCom); Add_CRC (&GameCRC, (int)infp->Mission + (int)infp->TarCom); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d Tgt:%x Speed:%d NavCom:%x\n", infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), infp->Class->Type, infp->As_Target(), infp->Speed, infp->NavCom); } } Mono_Printf("%s Infantry:%x\n",housep->Class->Name(),GameCRC); } } // // Units // for (house = HOUSE_MULTI1; house <= HOUSE_MULTI8; house++) { housep = HouseClass::As_Pointer (house); if (housep) { GameCRC = 0; fprintf(fp,"-------------------- %s Units -------------------\n", housep->Class->Name()); for (i = 0; i < Units.Count(); i++) { unitp = (UnitClass *)Units.Active_Ptr(i); if (unitp->Owner()==house) { Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing + (int)unitp->SecondaryFacing); fprintf(fp, "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d Tgt:%x\n", unitp->Coord,(int)unitp->PrimaryFacing, (int)unitp->SecondaryFacing,unitp->Get_Mission(), unitp->Class->Type, unitp->As_Target()); } } Mono_Printf("%s Units:%x\n",housep->Class->Name(),GameCRC); } } // // Vessels // for (house = HOUSE_MULTI1; house <= HOUSE_MULTI8; house++) { housep = HouseClass::As_Pointer (house); if (housep) { GameCRC = 0; fprintf(fp,"-------------------- %s Vessels -------------------\n", housep->Class->Name()); for (i = 0; i < Vessels.Count(); i++) { vesselp = (VesselClass *)Vessels.Active_Ptr(i); if (vesselp->Owner()==house) { Add_CRC (&GameCRC, (int)vesselp->Coord + (int)vesselp->PrimaryFacing); Add_CRC (&GameCRC, (int)vesselp->Speed + (int)vesselp->NavCom); Add_CRC (&GameCRC, (int)vesselp->Strength); Add_CRC (&GameCRC, (int)vesselp->Mission + (int)vesselp->TarCom); fprintf(fp, "COORD:%x Facing:%d Mission:%d Strength:%d Type:%d Tgt:%x\n", vesselp->Coord,(int)vesselp->PrimaryFacing, vesselp->Get_Mission(), vesselp->Strength, vesselp->Class->Type, vesselp->As_Target()); } } Mono_Printf("%s Vessels:%x\n",housep->Class->Name(),GameCRC); } } // // Buildings // for (house = HOUSE_MULTI1; house <= HOUSE_MULTI8; house++) { housep = HouseClass::As_Pointer (house); if (housep) { GameCRC = 0; fprintf(fp,"-------------------- %s Buildings -------------------\n", housep->Class->Name()); for (i = 0; i < Buildings.Count(); i++) { bldgp = (BuildingClass *)Buildings.Active_Ptr(i); if (bldgp->Owner()==house) { Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d Tgt:%x\n", bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), bldgp->Class->Type, bldgp->As_Target()); } } Mono_Printf("%s Buildings:%x\n",housep->Class->Name(),GameCRC); } } // // Animations // AnimClass *animp; fprintf(fp,"-------------------- Animations -------------------\n"); for (i = 0; i < Anims.Count(); i++) { animp = (AnimClass *)Anims.Active_Ptr(i); fprintf(fp,"Target:%x OwnerHouse:%d Loops:%d\n", animp->xObject, animp->OwnerHouse, animp->Loops); } //------------------------------------------------------------------------ // Map Layers //------------------------------------------------------------------------ GameCRC = 0; for (i = 0; i < LAYER_COUNT; i++) { fprintf(fp,">>>> MAP LAYER %d <<<<\n",i); for (j = 0; j < Map.Layer[i].Count(); j++) { objp = Map.Layer[i][j]; Add_CRC (&GameCRC, (int)objp->Coord + (int)objp->What_Am_I()); fprintf(fp,"Object %d: %x ",j,objp->Coord); if (objp->What_Am_I() == RTTI_AIRCRAFT) fprintf(fp,"Aircraft (Type:%d) ", (AircraftType)(*((AircraftClass *)objp))); else if (objp->What_Am_I() == RTTI_ANIM) fprintf(fp,"Anim (Type:%d) ", (AnimType)(*((AnimClass *)objp))); else if (objp->What_Am_I() == RTTI_BUILDING) fprintf(fp,"Building (Type:%d) ", (StructType)(*((BuildingClass *)objp))); else if (objp->What_Am_I() == RTTI_BULLET) fprintf(fp,"Bullet (Type:%d) ", (BulletType)(*((BulletClass *)objp))); else if (objp->What_Am_I() == RTTI_INFANTRY) fprintf(fp,"Infantry (Type:%d) ", (InfantryType)(*((InfantryClass *)objp))); else if (objp->What_Am_I() == RTTI_OVERLAY) fprintf(fp,"Overlay (Type:%d) ", (OverlayType)(*((OverlayClass *)objp))); else if (objp->What_Am_I() == RTTI_SMUDGE) fprintf(fp,"Smudge (Type:%d) ", (SmudgeType)(*((SmudgeClass *)objp))); else if (objp->What_Am_I() == RTTI_TEMPLATE) fprintf(fp,"Template (Type:%d) ", (TemplateType)(*((TemplateClass *)objp))); else if (objp->What_Am_I() == RTTI_TERRAIN) fprintf(fp,"Terrain (Type:%d) ", (TerrainType)(*((TerrainClass *)objp))); else if (objp->What_Am_I() == RTTI_UNIT) fprintf(fp,"Unit (Type:%d) ", (UnitType)(*((UnitClass *)objp))); else if (objp->What_Am_I() == RTTI_VESSEL) fprintf(fp,"Vessel (Type:%d) ", (VesselType)(*((VesselClass *)objp))); house = objp->Owner(); if (house!=HOUSE_NONE) { housep = HouseClass::As_Pointer (house); fprintf(fp,"Owner: %s\n",housep->Class->IniName); } else { fprintf(fp,"Owner: NONE\n"); } } } Mono_Printf("Map Layers:%x \n",GameCRC); //------------------------------------------------------------------------ // Logic Layers //------------------------------------------------------------------------ GameCRC = 0; fprintf(fp,">>>> LOGIC LAYER <<<<\n"); for (i = 0; i < Logic.Count(); i++) { objp = Logic[i]; Add_CRC (&GameCRC, (int)objp->Coord + (int)objp->What_Am_I()); fprintf(fp,"Object %d: %x ",i,objp->Coord); if (objp->What_Am_I() == RTTI_AIRCRAFT) fprintf(fp,"Aircraft (Type:%d) ", (AircraftType)(*((AircraftClass *)objp))); else if (objp->What_Am_I() == RTTI_ANIM) fprintf(fp,"Anim (Type:%d) ", (AnimType)(*((AnimClass *)objp))); else if (objp->What_Am_I() == RTTI_BUILDING) fprintf(fp,"Building (Type:%d) ", (StructType)(*((BuildingClass *)objp))); else if (objp->What_Am_I() == RTTI_BULLET) fprintf(fp,"Bullet (Type:%d) ", (BulletType)(*((BulletClass *)objp))); else if (objp->What_Am_I() == RTTI_INFANTRY) fprintf(fp,"Infantry (Type:%d) ", (InfantryType)(*((InfantryClass *)objp))); else if (objp->What_Am_I() == RTTI_OVERLAY) fprintf(fp,"Overlay (Type:%d) ", (OverlayType)(*((OverlayClass *)objp))); else if (objp->What_Am_I() == RTTI_SMUDGE) fprintf(fp,"Smudge (Type:%d) ", (SmudgeType)(*((SmudgeClass *)objp))); else if (objp->What_Am_I() == RTTI_TEMPLATE) fprintf(fp,"Template (Type:%d) ", (TemplateType)(*((TemplateClass *)objp))); else if (objp->What_Am_I() == RTTI_TERRAIN) fprintf(fp,"Terrain (Type:%d) ", (TerrainType)(*((TerrainClass *)objp))); else if (objp->What_Am_I() == RTTI_UNIT) fprintf(fp,"Unit (Type:%d) ", (UnitType)(*((UnitClass *)objp))); house = objp->Owner(); if (house!=HOUSE_NONE) { housep = HouseClass::As_Pointer (house); fprintf(fp,"Owner: %s\n",housep->Class->IniName); } else { fprintf(fp,"Owner: NONE\n"); } } Mono_Printf("Logic:%x \n",GameCRC); //------------------------------------------------------------------------ // Random # generator, frame # //------------------------------------------------------------------------ Mono_Printf("Random Number:%x \n",Scen.RandomNumber.Seed); #ifdef RANDOM_COUNT fprintf(fp,"\nRandom Number:%x (Count1:%d, Count2:%d)\n", Scen.RandomNumber.Seed, Scen.RandomNumber.Count1, Scen.RandomNumber.Count2); #else fprintf(fp,"\nRandom Number:%x\n",Scen.RandomNumber.Seed); #endif Mono_Printf("My Frame:%d \n",Frame); fprintf(fp,"My Frame:%d\n",Frame); if (ev) { fprintf(fp,"\n"); fprintf(fp,"Offending event:\n"); fprintf(fp," Type: %d\n",ev->Type); fprintf(fp," Frame: %d\n",ev->Frame); fprintf(fp," ID: %x\n",ev->ID); fprintf(fp," CRC: %x\n",ev->Data.FrameInfo.CRC); fprintf(fp," CommandCount: %d\n",ev->Data.FrameInfo.CommandCount); fprintf(fp," Delay: %d\n",ev->Data.FrameInfo.Delay); } fclose(fp); } /* end of Print_CRCs */ /*************************************************************************** * Init_Queue_Mono -- inits mono display * * * * This routine steals control of the mono screen away from the rest of * * the engine, by setting the global IsMono; if IsMono is set, the other * * routines in this module turn off the Mono display when they're done * * with it, so the rest of the engine won't over-write what we're writing. * * * * INPUT: * * net ptr to connection manager * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static void Init_Queue_Mono(ConnManClass *net) { #if(SHOW_MONO) //------------------------------------------------------------------------ // Set 'IsMono' so we can steal the mono screen from the engine //------------------------------------------------------------------------ if ((Frame==0 || Session.LoadGame) && MonoClass::Is_Enabled()) { IsMono = true; } //------------------------------------------------------------------------ // Enable mono output for our stuff; we must Disable it before we return // control to the engine. //------------------------------------------------------------------------ if (IsMono) MonoClass::Enable(); if (net->Num_Connections() > 0) { //..................................................................... // Network mono debugging screen //..................................................................... if (NetMonoMode==0) { if (Frame==0 || Session.LoadGame || NewMonoMode) { net->Configure_Debug (0, sizeof (CommHeaderType), sizeof(EventClass::EventType), EventClass::EventNames, 0, 27); net->Mono_Debug_Print (0,1); NewMonoMode = 0; } else { net->Mono_Debug_Print (0,0); } } //..................................................................... // Flow control debugging output //..................................................................... else { if (NewMonoMode) { Mono_Clear_Screen(); Mono_Printf(" Queue AI:\n"); // flowcount[0] Mono_Printf(" Build Packet Loop:\n"); // flowcount[1] Mono_Printf(" Frame Sync:\n"); // flowcount[2] Mono_Printf(" Frame Sync Resend:\n"); // flowcount[3] Mono_Printf(" Frame Sync Timeout:\n"); // flowcount[4] Mono_Printf(" Frame Sync New Message:\n"); // flowcount[5] Mono_Printf(" DoList Execution:\n"); // flowcount[6] Mono_Printf(" DoList Cleaning:\n"); // flowcount[7] Mono_Printf("\n"); Mono_Printf(" Frame:\n"); Mono_Printf(" Session.MaxAhead:\n"); Mono_Printf(" their_recv:\n"); Mono_Printf(" their_sent:\n"); Mono_Printf(" my_sent:\n"); NewMonoMode = 0; } } } #else net = net; #endif } // end of Init_Queue_Mono /*************************************************************************** * Update_Queue_Mono -- updates mono display * * * * INPUT: * * net ptr to connection manager * * flow_index index # for flow-count updates * * -1: display * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static void Update_Queue_Mono(ConnManClass *net, int flow_index) { #if(SHOW_MONO) static int flowcount[20] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //------------------------------------------------------------------------ // If 'NetMonoMode' is 1, display flowcount info //------------------------------------------------------------------------ if (NetMonoMode==1) { if (flow_index >= 0 && flow_index < 20) { Mono_Set_Cursor(35,flow_index); flowcount[flow_index]++; Mono_Printf("%d",flowcount[flow_index]); } } //------------------------------------------------------------------------ // Otherwise, display the connection debug screen //------------------------------------------------------------------------ else { net->Mono_Debug_Print (0,0); } #else flow_index = flow_index; net = net; #endif } // end of Update_Queue_Mono /*************************************************************************** * Print_Framesync_Values -- displays frame-sync variables * * * * INPUT: * * curframe current game Frame # * * max_ahead max-ahead value * * num_connections # connections * * their_recv # commands I've received from my connections * * their_sent # commands each connection claims to have sent * * my_sent # commands I've sent * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 11/21/1995 BRR : Created. * *=========================================================================*/ static void Print_Framesync_Values(long curframe, unsigned long max_ahead, int num_connections, unsigned short *their_recv, unsigned short *their_sent, unsigned short my_sent) { #if(SHOW_MONO) int i; if (NetMonoMode==1) { Mono_Set_Cursor(35,9); Mono_Printf("%d",curframe); Mono_Set_Cursor(35,10); Mono_Printf("%d",max_ahead); for (i = 0; i < num_connections; i++) { Mono_Set_Cursor(35 + i*5,11); Mono_Printf("%4d",(int)their_recv[i]); } for (i = 0; i < num_connections; i++) { Mono_Set_Cursor(35 + i*5,12); Mono_Printf("%4d",(int)their_sent[i]); } Mono_Set_Cursor(35,13); Mono_Printf("%4d",(int)my_sent); } #else curframe = curframe; max_ahead = max_ahead; num_connections = num_connections; their_recv = their_recv; their_sent = their_sent; my_sent = my_sent; #endif } // end of Print_Framesync_Values /*************************************************************************** * Dump_Packet_Too_Late_Stuff -- Dumps a debug file to disk * * * * INPUT: * * event ptr to event to print * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 06/28/1996 BRR : Created. * *=========================================================================*/ void Dump_Packet_Too_Late_Stuff(EventClass *event, ConnManClass *net, long *their_frame, unsigned short *their_sent, unsigned short *their_recv) { FILE *fp; int i; HousesType house; fp = fopen("toolate.txt", "wt"); if (!fp) { return; } fprintf(fp,"----------------- Event data: ----------------------\n"); fprintf(fp,"Type: %s\n",EventClass::EventNames[event->Type]); fprintf(fp,"Frame: %d\n",event->Frame); fprintf(fp,"ID: %d\n",event->ID); for (i = 0; i < Session.Players.Count(); i++) { if (event->ID == Session.Players[i]->Player.ID) { fprintf(fp,"Player's Name: %s",Session.Players[i]->Name); } } fprintf(fp,"\n"); fprintf(fp,"--------------------- My data: ---------------------\n"); fprintf(fp,"My Frame:%d\n",Frame); fprintf(fp,"My MaxAhead:%d\n",Session.MaxAhead); if (net) { fprintf(fp,"-------------------- Frame Stats: ------------------\n"); fprintf(fp,"Name ID TheirFrame TheirSent TheirRecv\n"); for (i = 0; i < net->Num_Connections(); i++) { house = (HousesType)(net->Connection_ID(i)); fprintf(fp,"%12s %2d %6d %6d %6d\n", (HouseClass::As_Pointer(house))->IniName, net->Connection_ID(i), their_frame[i], their_sent[i], their_recv[i]); } } fclose(fp); } /*************************************************************************** * Check_Mirror -- Checks mirror memory * * * * INPUT: * * none. * * * * OUTPUT: * * none. * * * * WARNINGS: * * none. * * * * HISTORY: * * 10/14/1996 BRR : Created. * *=========================================================================*/ void Check_Mirror(void) { #ifdef MIRROR_QUEUE int i; char txt[80]; unsigned long *ptr; int found_5s = 0; ptr = (unsigned long *)(DoList.Get_Array()); for (i = 0; i < (MAX_EVENTS * 64 * sizeof(EventClass)) / sizeof(unsigned long); i++) { if (ptr[i] == 0x55555555) { sprintf(txt,"55555555 found in DoList! Addr:%p", &(ptr[i])); WWMessageBox().Process (txt); found_5s = 1; } } ptr = (unsigned long *)(MirrorList.Get_Array()); for (i = 0; i < (MAX_EVENTS * 64 * sizeof(EventClass)) / sizeof(unsigned long); i++) { if (ptr[i] == 0x55555555) { sprintf(txt,"55555555 found in MirrorList! Addr:%p", &(ptr[i])); WWMessageBox().Process (txt); found_5s = 1; } } ptr = (unsigned long *)(DoList.Get_Array()); for (i = 0; i < (MAX_EVENTS * 64 * sizeof(EventClass)) / sizeof(unsigned long); i++) { if (ptr[i] == 0xAAAAAAAA) { sprintf(txt,"AAAAAAAA found in DoList! Addr:%p", &(ptr[i])); WWMessageBox().Process (txt); found_5s = 1; } } ptr = (unsigned long *)(MirrorList.Get_Array()); for (i = 0; i < (MAX_EVENTS * 64 * sizeof(EventClass)) / sizeof(unsigned long); i++) { if (ptr[i] == 0xAAAAAAAA) { sprintf(txt,"AAAAAAAA found in MirrorList! Addr:%p", &(ptr[i])); WWMessageBox().Process (txt); found_5s = 1; } } for (i = 0; i < DoList.Count; i++) { if (memcmp(&DoList[i], &MirrorList[i], sizeof(EventClass)) != 0) { sprintf(txt,"Queue Memory Trashed! Head:%d Tail:%d, Addr:%p or %p", DoList.Get_Head(), DoList.Get_Tail(), DoList.Get_Array() + i, MirrorList.Get_Array() + i); WWMessageBox().Process (txt); Prog_End("Check_Mirror", true); Emergency_Exit(0); } } if (found_5s) { //Prog_End(); Emergency_Exit(0); } #endif } // end of Check_Mirror /*************************** end of queue.cpp ******************************/