// // 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: F:\projects\c&c0\vcs\code\queue.cpv 2.24 11 Oct 1995 13:47:40 JOE_BOSTIC $ */ /*************************************************************************** * * * Project Name : Command & Conquer * * * * File Name : QUEUE.CPP * * * * Programmer : Bill R. Randolph * * * * Start Date : 11/28/95 * * * * Last Update : November 28, 1995 [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 * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "function.h" #include "tcpip.h" /********************************** Defines *********************************/ #define SHOW_MONO 1 int tmp_flag = 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 //--------------------------------------------------------------------------- #ifndef DEMO 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[6] = { "Yellow", "Red", "BlueGreen", "Orange", "Green", "Blue", }; #endif //DEMO //........................................................................... // 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 //........................................................................... #ifndef DEMO int NetMonoMode = 1; int NewMonoMode = 1; static int IsMono = 0; #endif //DEMO //--------------------------------------------------------------------------- // 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; /********************************* Prototypes *******************************/ //........................................................................... // Main multiplayer queue logic //........................................................................... static void Queue_AI_Normal(void); #ifndef DEMO 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); 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(CountDownTimerClass *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); #endif //DEMO //........................................................................... // Packet compression/decompression: //........................................................................... #ifndef DEMO static int Build_Send_Packet(void *buf, int bufsize, int frame_delay, int num_cmds, int cap); static int Breakup_Receive_Packet(void *buf, int bufsize ); #endif //DEMO 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); 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, TCountDownTimerClass *skip_crc, long *their_frame, unsigned short *their_sent, unsigned short *their_recv); static void Clean_DoList(ConnManClass *net); #ifndef DEMO static void Queue_Record(void); static void Queue_Playback(void); #endif //DEMO //........................................................................... // Debugging: //........................................................................... #ifndef DEMO static void Compute_Game_CRC(void); 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); #endif //DEMO void Add_CRC(unsigned long *crc, unsigned long val); void Print_CRCs(EventClass *); extern void Keyboard_Process(KeyNumType &input); void Dump_Packet_Too_Late_Stuff(EventClass *event); extern void Register_Game_End_Time(void); extern void Send_Statistics_Packet(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(TARGET whom, MissionType mission, TARGET target, TARGET destination) { if (! OutList.Add(EventClass(whom, mission, target, destination))) { return(false); } else { return(true); } } /* end of Queue_Mission */ /*************************************************************************** * 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) { #ifdef DEMO Queue_AI_Normal(); #else //DEMO if (PlaybackGame) { Queue_Playback(); } else { switch (GameToPlay) { case GAME_NORMAL: Queue_AI_Normal(); break; case GAME_MODEM: case GAME_NULL_MODEM: case GAME_IPX: case GAME_INTERNET: Queue_AI_Multiplayer(); break; } } #endif //DEMO } /* 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())) { ; } OutList.Next(); } //------------------------------------------------------------------------ // Save the DoList to disk, if we're in "Record" mode //------------------------------------------------------------------------ #ifndef DEMO if (RecordGame) { Queue_Record(); } #endif //DEMO //------------------------------------------------------------------------ // Execute the DoList //------------------------------------------------------------------------ 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 */ #ifndef DEMO /*************************************************************************** * 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 * * '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 + 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 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 properites: * * - 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 'MaxAhead' must be * * divisible by 'n'; also, the minimum value for '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 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 * * 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 * * 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) { //........................................................................ // Enums: //........................................................................ enum { MIXFILE_RESEND_DELTA = 120, // ticks b/w resends MIXFILE_TIMEOUT = 3600, // timeout waiting for mixfiles FRAMESYNC_DLG_TIME = (3*60), // time until displaying reconnect dialog FRAMESYNC_TIMEOUT = (25*60), // timeout waiting for frame sync packet }; int timeout_factor = (GameToPlay == 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. Values in these arrays are stored in the // order in which the connections are created. // (ie net->Connection_Index(id)) //........................................................................ 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 //........................................................................ // 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 (GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM){ //PG_TO_FIX #if (0) multi_packet_buf = NullModem.BuildBuf; multi_packet_max = NullModem.MaxLen - sizeof (CommHeaderType); net = &NullModem; #endif } else if (GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) { multi_packet_buf = MetaPacket; multi_packet_max = MetaSize; net = &Ipx; } //------------------------------------------------------------------------ // Debug stuff //------------------------------------------------------------------------ Init_Queue_Mono(net); Update_Queue_Mono (net, 0); //------------------------------------------------------------------------ // 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) { //..................................................................... // Initialize static locals //..................................................................... for (i = 0; i < MAX_PLAYERS - 1; i++) { their_frame[i] = -1; their_sent[i] = 0; their_recv[i] = 0; TheirProcessTime[i] = -1; } my_sent = 0; for (i = 0; i < 32; i++) { CRC[i] = 0; } //..................................................................... // 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) { if (rc == RC_NOT_RESPONDING) { CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); } else if (rc == RC_SCENARIO_MISMATCH) { CCMessageBox().Process (TXT_SCENARIOS_DO_NOT_MATCH); } else if (rc == RC_DOLIST_FULL) { CCMessageBox().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 (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { Process_Send_Period(net); } } // 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 (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 (MPlayerLocalID == MPlayerID[0]) { Generate_Real_Timing_Event(net, my_sent); } } else { // // For the older protocols, do the old broken timing handling. // Generate_Timing_Event(net, my_sent); } } //------------------------------------------------------------------------ // Compute the Game's CRC //------------------------------------------------------------------------ Compute_Game_CRC(); CRC[Frame & 0x001f] = GameCRC; //unsigned long save_crc = GameCRC; //Print_CRCs((EventClass *)NULL); //GameCRC = save_crc; //------------------------------------------------------------------------ // Only process every 'FrameSendRate' frames //------------------------------------------------------------------------ if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { if (!Process_Send_Period(net)) { 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, MPlayerMaxAhead, 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. //------------------------------------------------------------------------ rc = Wait_For_Players (0, net, (MPlayerMaxAhead << 3), MAX ( net->Response_Time() * 3, (unsigned long)FRAMESYNC_DLG_TIME*timeout_factor ), FRAMESYNC_TIMEOUT * (timeout_factor*2), multi_packet_buf, my_sent, their_frame, their_sent, their_recv); if (rc != RC_NORMAL) { if (rc == RC_NOT_RESPONDING) { CCMessageBox().Process (TXT_SYSTEM_NOT_RESPONDING); } else if (rc == RC_SCENARIO_MISMATCH) { CCMessageBox().Process (TXT_SCENARIOS_DO_NOT_MATCH); } else if (rc == RC_DOLIST_FULL) { CCMessageBox().Process(TXT_QUEUE_FULL); } Stop_Game(); return; } //------------------------------------------------------------------------ // Save the DoList to disk, if we're in "Record" mode //------------------------------------------------------------------------ if (RecordGame) { Queue_Record(); } //------------------------------------------------------------------------ // Execute the DoList; if an error occurs, bail out. //------------------------------------------------------------------------ if (!Execute_DoList(MPlayerMax, HOUSE_MULTI1, net, NULL, their_frame, their_sent, their_recv)) { Stop_Game(); return; } //------------------------------------------------------------------------ // Clean out the DoList //------------------------------------------------------------------------ Clean_DoList(net); if (IsMono) { MonoClass::Disable(); } } // 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 //........................................................................ // Variables used only if 'first_time': //........................................................................ int num_ready; // # players signalling ready //........................................................................ // Timing variables //........................................................................ CountDownTimerClass retry_timer; // time between FRAMESYNC packet resends CountDownTimerClass dialog_timer; // time to pop up a dialog CountDownTimerClass 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.Set (resend_delta, true); // time to retry dialog_timer.Set (dialog_time, true); // time to show dlg timeout_timer.Set (timeout, true); // time to bail out while (1) { Update_Queue_Mono (net, 2); //--------------------------------------------------------------------- // Resend a frame-sync packet if longer than one propogation 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.Time()) { retry_timer.Set (resend_delta, true); // 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.Time() && SpecialDialog==SDLG_NONE) { if (Process_Reconnect_Dialog(&timeout_timer, their_frame, net->Num_Connections(), (first_time==0), (reconnect_dlg==0))) { return (RC_CANCEL); } reconnect_dlg = 1; } //--------------------------------------------------------------------- // Exit if too much time goes by (the other system has crashed or // bailed) //--------------------------------------------------------------------- if (!timeout_timer.Time()) { //.................................................................. // 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.Set (resend_delta, true); dialog_timer.Set (dialog_time, true); timeout_timer.Set (timeout, true); } 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. //--------------------------------------------------------------------- messages_this_loop = 0; while ( (messages_this_loop++ < 5) && net->Get_Private_Message (multi_packet_buf, &packetlen, &id) ) { 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 (GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM){ //|| GameToPlay == GAME_INTERNET) { rc = Process_Serial_Packet(multi_packet_buf, first_time); //............................................................... // SERIAL packet received & processed //............................................................... if (rc == RC_SERIAL_PROCESSED) { net->Service(); retry_timer.Set (resend_delta, true); dialog_timer.Set (dialog_time, true); timeout_timer.Set (timeout, true); 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) { return (RC_NOT_RESPONDING); } //............................................................... // 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, MPlayerMaxAhead, 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, MPlayerMaxAhead, 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) { WWMouse->Erase_Mouse(&HidPage, TRUE); 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; //------------------------------------------------------------------------ // 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 (MPlayerLocalID == MPlayerID[0]) { ev.Type = EventClass::RESPONSE_TIME; //.................................................................. // For multi-frame compressed events, the MaxAhead must be an even // multiple of the FrameSendRate. //.................................................................. if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { ev.Data.FrameInfo.Delay = MAX( ((((resp_time / 8) + (FrameSendRate - 1)) / FrameSendRate) * FrameSendRate), (FrameSendRate * 2) ); char flip[128]; sprintf (flip, "C&C95 - Generating timing packet - MaxAhead = %d frames\n", ev.Data.FrameInfo.Delay); CCDebugString (flip); } //.................................................................. // For sending packets every frame, just use the 1-way connection // response time. //.................................................................. else { if (GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM){ //|| GameToPlay == GAME_INTERNET) { ev.Data.FrameInfo.Delay = MAX( (resp_time / 8), (unsigned long)MODEM_MIN_MAX_AHEAD ); } else if (GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) { ev.Data.FrameInfo.Delay = MAX( (resp_time / 8), (unsigned long)NETWORK_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 < MPlayerCount; i++) { // // If we haven't heard from all systems yet, bail out. // if (TheirProcessTime[i] == -1) { return; } if (TheirProcessTime[i] > highest_ticks) { highest_ticks = TheirProcessTime[i]; } } // // 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) { DesiredFrameRate = 60; } else { DesiredFrameRate = 60 / highest_ticks; } if (Options.GameSpeed == 0) { specified_frame_rate = 60; } else { specified_frame_rate = 60 / Options.GameSpeed; } DesiredFrameRate = MIN (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 * 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 + FrameSendRate - 1) / FrameSendRate) * FrameSendRate; maxahead = MAX (maxahead, (int)FrameSendRate * 3); ev.Type = EventClass::TIMING; ev.Data.Timing.DesiredFrameRate = 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); } /*************************************************************************** * 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 (IsMono) { MonoClass::Enable(); Mono_Set_Cursor(0,23); Mono_Printf("Processing Ticks:%03d Frames:%03d\n", ProcessTicks,ProcessFrames); MonoClass::Disable(); } avgticks = ProcessTicks / ProcessFrames; ev.Type = EventClass::PROCESS_TIME; ev.Data.ProcessTime.AverageTicks = avgticks; char flip[128]; sprintf (flip, "C&C95 - Sending PROCESS_TIME packet of %04x ticks\n", ev.Data.ProcessTime.AverageTicks); CCDebugString (flip); OutList.Add(ev); ProcessTicks = 0; 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) { //------------------------------------------------------------------------ // 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 + (FrameSendRate - 1)) / FrameSendRate) * 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 queue'd 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 * 8) - DoList.Count) { cap = (MAX_EVENTS * 8) - DoList.Count; } /* ** No cap for internet game ** ** Or for serial games for that matter ST - 5/31/96 4:00PM */ if (GameToPlay == GAME_INTERNET || GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM){ cap = OutList.Count; do_once = 0; } //------------------------------------------------------------------------ // Build our meta-packet & transmit it. //------------------------------------------------------------------------ while (1) { 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 // MPlayerCount; no ACK is needed if we're just sending to someone // who's left the game. //..................................................................... if (cap == 0 || OutList.Count == 0 || MPlayerCount == 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 (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { packet.Frame = ((Frame + MPlayerMaxAhead + (FrameSendRate - 1)) / FrameSendRate) * FrameSendRate; } else { packet.Frame = Frame + MPlayerMaxAhead; } packet.ID = Houses.ID(PlayerPtr); packet.MPlayerID = MPlayerLocalID; packet.Data.FrameInfo.CRC = ScenarioCRC; packet.Data.FrameInfo.CommandCount = cmd_count; packet.Data.FrameInfo.Delay = MPlayerMaxAhead; //------------------------------------------------------------------------ // 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]) { their_sent[index] = event->Data.FrameInfo.CommandCount; } //------------------------------------------------------------------------ // 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 - 1/2/2019 5:28PM SerialPacketType *serial_packet; // for parsing serial packets int player_gone; EventClass *event; char txt[MAX_MESSAGE_LENGTH+80]; unsigned short magic_number; unsigned short crc; //------------------------------------------------------------------------ // 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->Color, 0); return (RC_PLAYER_LEFT); } //------------------------------------------------------------------------ // Process an incoming message //------------------------------------------------------------------------ if (serial_packet->Command == SERIAL_MESSAGE) { sprintf(txt, Text_String(TXT_FROM), serial_packet->Name, serial_packet->Message); magic_number = *((unsigned short*)(serial_packet->Message + COMPAT_MESSAGE_LENGTH-4)); crc = *((unsigned short*)(serial_packet->Message + COMPAT_MESSAGE_LENGTH-2)); Messages.Add_Message (txt, MPlayerTColors[MPlayerID_To_ColorIndex(serial_packet->ID)], TPF_6PT_GRAD | TPF_USE_GRAD_PAL | TPF_FULLSHADOW, 600, magic_number, crc); //..................................................................... // Save this message in our last-message buffer //..................................................................... if (strlen (serial_packet->Message)) { strcpy (LastMessage, serial_packet->Message); } //..................................................................... // Tell the map to do a partial update (just to force the // messages to redraw). //..................................................................... Map.Flag_To_Redraw(false); 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) || MPlayerCount == 1) { return (RC_SERIAL_PROCESSED); } //........................................................................ // are we getting our own packets back?? //........................................................................ event = (EventClass *)multi_packet_buf; if (event->ID == Houses.ID(PlayerPtr)) { 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 (MPlayerCount == 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 propogation 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(CountDownTimerClass *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->Time() / 60; //-------------------------------------------------------------------------------- // If we have just received input focus again after running in the background then // we need to redraw the whole dialog. //-------------------------------------------------------------------------------- if (AllSurfaces.SurfacesRestored){ AllSurfaces.SurfacesRestored=FALSE; fresh = true; } //------------------------------------------------------------------------ // 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 (Check_Key()) { if (Get_Key_Num()==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 (GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM) { #if (0) //ST - 1/2/2019 5:28PM if ( net->Num_Connections() ) { if (!Reconnect_Modem()) { return (0); } else { return (1); } } #endif } //------------------------------------------------------------------------ // For network, destroy the oldest connection //------------------------------------------------------------------------ else if (GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) { 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); /* ** Send the game statistics packet now if the game is effectivly over */ if (MPlayerCount == 2 && GameToPlay == GAME_INTERNET && !GameStatisticsPacketSent){ Register_Game_End_Time(); ConnectionLost = true; Send_Statistics_Packet(); } 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]; } CCDebugString ("C&C95 = Destroying connection due to time out\n"); Destroy_Connection(id,1); } } 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) { CCDebugString ("C&C95 - In Stop_Game.\n"); GameActive = 0; if (IsMono) { MonoClass::Disable(); } if (GameToPlay == GAME_INTERNET){ ConnectionLost = true; CCDebugString ("C&C95 - About to send statistics packet.\n"); Register_Game_End_Time(); Send_Statistics_Packet(); CCDebugString ("C&C95 - Returned from sending stats packet.\n"); } 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 (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { finfo->Frame = ((Frame + frame_delay + (FrameSendRate - 1)) / FrameSendRate) * FrameSendRate; } else { finfo->Frame = Frame + frame_delay; } //........................................................................ // Fill in the rest of the event //........................................................................ finfo->ID = Houses.ID(PlayerPtr); finfo->MPlayerID = MPlayerLocalID; 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 (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 (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)) { 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 = Houses.ID(PlayerPtr); OutList.First().MPlayerID = MPlayerLocalID; //..................................................................... // 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); } //..................................................................... // Add event to the send packet //..................................................................... 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)); //------------------------------------------------------------------------ // Loop until there are no more events, we've processed our max # of // events, or the buffer is full. //------------------------------------------------------------------------ while (OutList.Count && (num < cap)) { 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. //..................................................................... 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) { 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 { *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) { 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 (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { OutList.First().Frame = ((Frame + frame_delay + (FrameSendRate - 1)) / FrameSendRate) * FrameSendRate; } else { OutList.First().Frame = Frame + frame_delay; } //..................................................................... // Set the event's ID //..................................................................... OutList.First().ID = Houses.ID(PlayerPtr); OutList.First().MPlayerID = MPlayerLocalID; //..................................................................... // 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; } //--------------------------------------------------------------------- // 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; //.................................................................. // 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(); } 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 (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) ) { 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; if (!DoList.Add( *event )) { return (-1); } //.................................................................. // 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 //int lasteventtype=0; //------------------------------------------------------------------------ // 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 ((unsigned)leftover >= (datasize + sizeof(EventClass::EventType)) ) { //..................................................................... // 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; eventdata.MPlayerID = event->MPlayerID; //............................................................... // 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::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) { if ( !DoList.Add( eventdata ) ) { return (-1); } //...................................................... // 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; } char flip[128]; sprintf (flip, "C&C95 - Adding event type %d to queue\n", eventdata.Type); CCDebugString (flip); //if (lasteventtype == 11){ // break; //} //lasteventtype = (int) eventdata.Type; if ( !DoList.Add( eventdata ) ) { return (-1); } //.................................................................. // 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 #endif //DEMO /*************************************************************************** * 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 , HousesType , ConnManClass *net, TCountDownTimerClass *, long *their_frame, unsigned short *their_sent, unsigned short *their_recv) { int i,j,k,wibble; int index; //------------------------------------------------------------------------ // For a single-player game, just execute all events in the queue. //------------------------------------------------------------------------ if (GameToPlay == GAME_NORMAL) { for (i = 0; i < DoList.Count; i++) { if ((unsigned)Frame >= DoList[i].Frame && !DoList[i].IsExecuted) { DoList[i].Execute(); // execute it DoList[i].IsExecuted = true; // mark as having been executed } } return (1); } //#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. // if (CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { 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; } } } //#endif //------------------------------------------------------------------------ // Execute the DoList. Events must be executed in the same order on all // systems; so, execute them in the order of the MPlayerID array. This // array is stored in the same order on all systems. //------------------------------------------------------------------------ for (i = 0; i < MPlayerCount; i++) { HousesType house; HouseClass *housep; house = MPlayerHouses [i]; housep= 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 (!housep){ continue; } if (!housep->IsHuman){ continue; } //..................................................................... // Loop through all events //..................................................................... for (j = 0; j < DoList.Count; j++) { #ifndef DEMO if (net) Update_Queue_Mono (net, 6); #endif //DEMO //.................................................................. // If this event was from the currently-executing player ID, and it's // time to execute it, execute it. //.................................................................. if (DoList[j].MPlayerID == MPlayerID[i] && (unsigned)Frame >= DoList[j].Frame && !DoList[j].IsExecuted) { //............................................................... // Error if it's too late to execute this packet! //............................................................... if ((unsigned)Frame > DoList[j].Frame && DoList[j].Type != EventClass::FRAMEINFO) { #ifndef DEMO Dump_Packet_Too_Late_Stuff(&DoList[j]); #endif //DEMO CCMessageBox().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) { if (DoList[j].Type==EventClass::EXIT){ CCDebugString ("C&C95 - Received EXIT packet\n"); /* ** Flag that this house lost because it quit. ST - 6/5/96 0:29AM */ for (wibble = 0; wibble < MPlayerCount; wibble++) { if (MPlayerID[wibble] == DoList[j].MPlayerID) { house = MPlayerHouses[wibble]; housep = HouseClass::As_Pointer (house); housep->IGaveUp = true; break; } } /* ** Send the game statistics packet now since the game is effectivly over */ if (MPlayerCount == 2 && GameToPlay == GAME_INTERNET && !GameStatisticsPacketSent){ Register_Game_End_Time(); Send_Statistics_Packet(); } } if (DoList[j].ID == Houses.ID(PlayerPtr)) { DoList[j].Execute(); } //............................................................ // If this EXIT event isn't from myself, destroy the connection // for that player. The HousesType for this event is the // connection ID. //............................................................ else if (DoList[j].Type==EventClass::EXIT) { if (GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM){ //|| GameToPlay == GAME_INTERNET) { //ST - 1/2/2019 5:29PM //Destroy_Null_Connection( DoList[j].MPlayerID, 0 ); } else if ((GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) && net) { index = net->Connection_Index (DoList[j].MPlayerID); 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]; } CCDebugString ("C&C95 = Destroying connection due to exit event\n"); #ifndef DEMO Destroy_Connection(DoList[j].MPlayerID,0); #endif //DEMO } } } } //............................................................... // For a FRAMEINFO event, check the CRC value. // This could be an old FRAMEINFO packet that was floating around // for awhile and just arrived; if so, its Frame value will be // old. Ignore these packets. (This created bogus sync bugs on // the Internet, when packets that were 35 frames old arrived.) //............................................................... #ifndef DEMO else if (DoList[j].Type == EventClass::FRAMEINFO) { if (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 (CCMessageBox().Process (TXT_OUT_OF_SYNC, TXT_CONTINUE, TXT_STOP) == 0) { if (GameToPlay == GAME_MODEM || GameToPlay == GAME_NULL_MODEM){ #if (0)//ST - 1/2/2019 5:29PM Destroy_Null_Connection( DoList[j].MPlayerID, -1 ); Shutdown_Modem(); #endif GameToPlay = GAME_NORMAL; } else if ((GameToPlay == GAME_IPX || GameToPlay == GAME_INTERNET) && net) { CCDebugString ("C&C95 = Destroying connections due to bad frame info packet\n"); while (net->Num_Connections()) { Destroy_Connection (net->Connection_ID(0), -1); } } Map.Flag_To_Redraw(true); } else { return (0); } return (1); } } } #endif //DEMO //............................................................... // Execute other commands //............................................................... else { DoList[j].Execute(); } //............................................................... // Mark this event as executed. //............................................................... DoList[j].IsExecuted = 1; } } } 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) { #ifndef DEMO if (net) Update_Queue_Mono (net, 7); #else net = net; #endif //DEMO //..................................................................... // 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(); } else { break; } } } // end of Clean_DoList #ifndef DEMO /*************************************************************************** * 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. //------------------------------------------------------------------------ RecordFile.Write (&j,sizeof(j)); for (i = 0; i < DoList.Count; i++) { if (Frame == DoList[i].Frame && !DoList[i].IsExecuted) { 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 (Check_Key_Num()) { key = Get_Key(); // // If the user hit ESC, end the recording. If this is an Attract-mode // recording, end it no matter what the user does (any key or mouse). // if (key == KA_ESC || AllowAttract) { GameActive = 0; return; } } //------------------------------------------------------------------------ // If we're in "Attact" mode, and the user moves the mouse, stop the // playback. //------------------------------------------------------------------------ if (AllowAttract && 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; //------------------------------------------------------------------------ // 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 && GameToPlay!=GAME_NORMAL) { return; } //------------------------------------------------------------------------ // Only process every 'FrameSendRate' frames //------------------------------------------------------------------------ testframe = ((Frame + (FrameSendRate - 1)) / FrameSendRate) * FrameSendRate; if (GameToPlay != GAME_NORMAL && CommProtocol == COMM_PROTOCOL_MULTI_E_COMP) { if (Frame != testframe) { return; } } //------------------------------------------------------------------------ // Read the DoList from disk //------------------------------------------------------------------------ ok = 1; if (RecordFile.Read (&numevents, sizeof(numevents)) == sizeof(numevents)) { for (i = 0; i < numevents; i++) { if (RecordFile.Read (&event, sizeof(EventClass)) == sizeof(EventClass)) { event.IsExecuted = 0; DoList.Add (event); } else { ok = 0; break; } } } else { ok = 0; } if (!ok) { GameActive = 0; return; } //------------------------------------------------------------------------ // Execute the DoList; if an error occurs, bail out. //------------------------------------------------------------------------ if (GameToPlay == GAME_NORMAL) { max_houses = 1; base_house = PlayerPtr->Class->House; } else { max_houses = MPlayerMax; 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; InfantryClass *infp; UnitClass *unitp; BuildingClass *bldgp; //ObjectClass *objp; 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); } //------------------------------------------------------------------------ // 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); } //------------------------------------------------------------------------ // Buildings //------------------------------------------------------------------------ for (i = 0; i < Buildings.Count(); i++) { bldgp = (BuildingClass *)Buildings.Active_Ptr(i); Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); } #if(0) //------------------------------------------------------------------------ // 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()); } #endif //------------------------------------------------------------------------ // A random # //------------------------------------------------------------------------ Add_CRC(&GameCRC, rand()); } /* 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: * * none. * * * * OUTPUT: * * none * * * * WARNINGS: * * none * * * * HISTORY: * * 05/09/1995 BRR : Created. * *=========================================================================*/ void Print_CRCs(EventClass *ev) { ev=ev; int i; //,j; InfantryClass *infp; UnitClass *unitp; BuildingClass *bldgp; //ObjectClass *objp; FILE *fp; int rnd; HouseClass *housep; //HousesType house; int color; Mono_Clear_Screen(); Mono_Set_Cursor (0,0); char filename[80]; sprintf (filename, "CRC%02d.TXT", Frame & 0x1f); fp = fopen(filename, "wt"); //"OUT.TXT","wt"); if (fp==NULL) { return; } for (i = 0; i < 32; i++) { fprintf(fp,"CRC[%d]=%x\n",i,CRC[i]); } housep = HouseClass::As_Pointer (HOUSE_MULTI1); if (housep) { color = housep->RemapColor; fprintf(fp,"Multi1: IsHuman:%d Color:%s\n",housep->IsHuman, ColorNames[color]); } housep = HouseClass::As_Pointer (HOUSE_MULTI2); if (housep) { color = housep->RemapColor; fprintf(fp,"Multi2: IsHuman:%d Color:%s\n",housep->IsHuman, ColorNames[color]); } housep = HouseClass::As_Pointer (HOUSE_MULTI3); if (housep) { color = housep->RemapColor; fprintf(fp,"Multi3: IsHuman:%d Color:%s\n",housep->IsHuman, ColorNames[color]); } housep = HouseClass::As_Pointer (HOUSE_MULTI4); if (housep) { color = housep->RemapColor; fprintf(fp,"Multi4: IsHuman:%d Color:%s\n",housep->IsHuman, ColorNames[color]); } housep = HouseClass::As_Pointer (HOUSE_MULTI5); if (housep) { color = housep->RemapColor; fprintf(fp,"Multi5: IsHuman:%d Color:%s\n",housep->IsHuman, ColorNames[color]); } housep = HouseClass::As_Pointer (HOUSE_MULTI6); if (housep) { color = housep->RemapColor; fprintf(fp,"Multi6: IsHuman:%d Color:%s\n",housep->IsHuman, ColorNames[color]); } //------------------------------------------------------------------------ // Multi1 Infantry //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI1)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI1 INFANTRY -------------------\n"); for (i = 0; i < Infantry.Count(); i++) { infp = (InfantryClass *)Infantry.Active_Ptr(i); if (infp->Owner()==HOUSE_MULTI1) { Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), infp->Class->Type); } } Mono_Printf("Multi1 Infantry:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi2 Infantry //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI2)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI2 INFANTRY -------------------\n"); for (i = 0; i < Infantry.Count(); i++) { infp = (InfantryClass *)Infantry.Active_Ptr(i); if (infp->Owner()==HOUSE_MULTI2) { Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), infp->Class->Type); } } Mono_Printf("Multi2 Infantry:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi3 Infantry //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI3)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI3 INFANTRY -------------------\n"); for (i = 0; i < Infantry.Count(); i++) { infp = (InfantryClass *)Infantry.Active_Ptr(i); if (infp->Owner()==HOUSE_MULTI3) { Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), infp->Class->Type); } } Mono_Printf("Multi3 Infantry:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi4 Infantry //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI4)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI4 INFANTRY -------------------\n"); for (i = 0; i < Infantry.Count(); i++) { infp = (InfantryClass *)Infantry.Active_Ptr(i); if (infp->Owner()==HOUSE_MULTI4) { Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), infp->Class->Type); } } Mono_Printf("Multi4 Infantry:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi5 Infantry //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI5)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI5 INFANTRY -------------------\n"); for (i = 0; i < Infantry.Count(); i++) { infp = (InfantryClass *)Infantry.Active_Ptr(i); if (infp->Owner()==HOUSE_MULTI5) { Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), infp->Class->Type); } } Mono_Printf("Multi5 Infantry:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi6 Infantry //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI6)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI6 INFANTRY -------------------\n"); for (i = 0; i < Infantry.Count(); i++) { infp = (InfantryClass *)Infantry.Active_Ptr(i); if (infp->Owner()==HOUSE_MULTI6) { Add_CRC (&GameCRC, (int)infp->Coord + (int)infp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", infp->Coord,(int)infp->PrimaryFacing,infp->Get_Mission(), infp->Class->Type); } } Mono_Printf("Multi6 Infantry:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi1 Units //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI1)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI1 UNITS -------------------\n"); for (i = 0; i < Units.Count(); i++) { unitp = (UnitClass *)Units.Active_Ptr(i); if (unitp->Owner()==HOUSE_MULTI1) { Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); fprintf(fp, "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", unitp->Coord,(int)unitp->PrimaryFacing, (int)unitp->SecondaryFacing,unitp->Get_Mission(), unitp->Class->Type); } } Mono_Printf("Multi1 Units:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi2 Units //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI2)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI2 UNITS -------------------\n"); for (i = 0; i < Units.Count(); i++) { unitp = (UnitClass *)Units.Active_Ptr(i); if (unitp->Owner()==HOUSE_MULTI2) { Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); fprintf(fp, "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", unitp->Coord,(int)unitp->PrimaryFacing, (int)unitp->SecondaryFacing,unitp->Get_Mission(), unitp->Class->Type); } } Mono_Printf("Multi2 Units:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi3 Units //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI3)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI3 UNITS -------------------\n"); for (i = 0; i < Units.Count(); i++) { unitp = (UnitClass *)Units.Active_Ptr(i); if (unitp->Owner()==HOUSE_MULTI3) { Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); fprintf(fp, "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", unitp->Coord,(int)unitp->PrimaryFacing, (int)unitp->SecondaryFacing,unitp->Get_Mission(), unitp->Class->Type); } } Mono_Printf("Multi3 Units:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi4 Units //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI4)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI4 UNITS -------------------\n"); for (i = 0; i < Units.Count(); i++) { unitp = (UnitClass *)Units.Active_Ptr(i); if (unitp->Owner()==HOUSE_MULTI4) { Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); fprintf(fp, "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", unitp->Coord,(int)unitp->PrimaryFacing, (int)unitp->SecondaryFacing,unitp->Get_Mission(), unitp->Class->Type); } } Mono_Printf("Multi4 Units:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi5 Units //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI5)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI5 UNITS -------------------\n"); for (i = 0; i < Units.Count(); i++) { unitp = (UnitClass *)Units.Active_Ptr(i); if (unitp->Owner()==HOUSE_MULTI5) { Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); fprintf(fp, "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", unitp->Coord,(int)unitp->PrimaryFacing, (int)unitp->SecondaryFacing,unitp->Get_Mission(), unitp->Class->Type); } } Mono_Printf("Multi5 Units:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi6 Units //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI6)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI6 UNITS -------------------\n"); for (i = 0; i < Units.Count(); i++) { unitp = (UnitClass *)Units.Active_Ptr(i); if (unitp->Owner()==HOUSE_MULTI6) { Add_CRC (&GameCRC, (int)unitp->Coord + (int)unitp->PrimaryFacing); fprintf(fp, "COORD:%x Facing:%d Facing2:%d Mission:%d Type:%d\n", unitp->Coord,(int)unitp->PrimaryFacing, (int)unitp->SecondaryFacing,unitp->Get_Mission(), unitp->Class->Type); } } Mono_Printf("Multi6 Units:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi1 Buildings //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI1)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI1 BUILDINGS -------------------\n"); for (i = 0; i < Buildings.Count(); i++) { bldgp = (BuildingClass *)Buildings.Active_Ptr(i); if (bldgp->Owner()==HOUSE_MULTI1) { Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), bldgp->Class->Type); } } Mono_Printf("Multi1 Buildings:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi2 Buildings //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI2)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI2 BUILDINGS -------------------\n"); for (i = 0; i < Buildings.Count(); i++) { bldgp = (BuildingClass *)Buildings.Active_Ptr(i); if (bldgp->Owner()==HOUSE_MULTI2) { Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), bldgp->Class->Type); } } Mono_Printf("Multi2 Buildings:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi3 Buildings //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI3)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI3 BUILDINGS -------------------\n"); for (i = 0; i < Buildings.Count(); i++) { bldgp = (BuildingClass *)Buildings.Active_Ptr(i); if (bldgp->Owner()==HOUSE_MULTI3) { Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), bldgp->Class->Type); } } Mono_Printf("Multi3 Buildings:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi4 Buildings //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI4)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI4 BUILDINGS -------------------\n"); for (i = 0; i < Buildings.Count(); i++) { bldgp = (BuildingClass *)Buildings.Active_Ptr(i); if (bldgp->Owner()==HOUSE_MULTI4) { Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), bldgp->Class->Type); } } Mono_Printf("Multi4 Buildings:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi5 Buildings //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI5)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI5 BUILDINGS -------------------\n"); for (i = 0; i < Buildings.Count(); i++) { bldgp = (BuildingClass *)Buildings.Active_Ptr(i); if (bldgp->Owner()==HOUSE_MULTI5) { Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), bldgp->Class->Type); } } Mono_Printf("Multi5 Buildings:%d\n",GameCRC); } //------------------------------------------------------------------------ // Multi6 Buildings //------------------------------------------------------------------------ if (HouseClass::As_Pointer(HOUSE_MULTI6)) { GameCRC = 0; fprintf(fp,"-------------------- MULTI6 BUILDINGS -------------------\n"); for (i = 0; i < Buildings.Count(); i++) { bldgp = (BuildingClass *)Buildings.Active_Ptr(i); if (bldgp->Owner()==HOUSE_MULTI6) { Add_CRC (&GameCRC, (int)bldgp->Coord + (int)bldgp->PrimaryFacing); fprintf(fp,"COORD:%x Facing:%d Mission:%d Type:%d\n", bldgp->Coord,(int)bldgp->PrimaryFacing,bldgp->Get_Mission(), bldgp->Class->Type); } } Mono_Printf("Multi6 Buildings:%d\n",GameCRC); } #if (0) //------------------------------------------------------------------------ // 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))); 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:%d\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:%d\n",GameCRC); #endif //------------------------------------------------------------------------ // Random # generator, frame # //------------------------------------------------------------------------ rnd = rand(); Mono_Printf("Random Number:%d\n",rnd); fprintf(fp,"\nRandom Number:%d\n",rnd); Mono_Printf("My Frame:%d\n",Frame); fprintf(fp,"My Frame:%d\n",Frame); #if (0) fprintf(fp,"-------------- Offending event: ----------------\n"); fprintf(fp,"Type: %d\n",ev->Type); fprintf(fp,"Frame: %d\n",ev->Frame); fprintf(fp,"MPlayerID: %04x\n",ev->MPlayerID); if (ev->Type == EventClass::FRAMEINFO) { fprintf(fp,"CRC: %x\n",ev->Data.FrameInfo.CRC); fprintf(fp,"CommandCount: %x\n",ev->Data.FrameInfo.CommandCount); fprintf(fp,"Delay: %x\n",ev->Data.FrameInfo.Delay); } #endif 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 && 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 || NewMonoMode) { net->Configure_Debug (0, sizeof (CommHeaderType), sizeof(EventClass::EventType), EventClass::EventNames, 0); 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(" MPlayerMaxAhead:\n"); Mono_Printf(" their_recv:\n"); Mono_Printf(" their_sent:\n"); Mono_Printf(" my_sent:\n"); Mono_Printf(" DesiredFrameRate:\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); Mono_Set_Cursor(35,14); Mono_Printf("%2d",(int)DesiredFrameRate); } #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) { FILE *fp; int i; 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); fprintf(fp,"MPlayerID: %04x\n",event->MPlayerID); for (i = 0; i < MPlayerCount; i++) { if (event->MPlayerID == MPlayerID[i]) { fprintf(fp,"Player's Name: %s",MPlayerNames[i]); } } fprintf(fp,"----------- My data: ------------------\n"); fprintf(fp,"Frame:%d\n",Frame); fprintf(fp,"MaxAhead:%d\n",MPlayerMaxAhead); fclose(fp); } #endif //DEMO /*************************** end of queue.cpp ******************************/