1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 
13 #include "network/multi.h"
14 #include "object/object.h"
15 #include "freespace.h"
16 #include "gamesequence/gamesequence.h"
17 #include "popup/popup.h"
18 #include "popup/popupdead.h"
19 #include "network/multi_endgame.h"
20 #include "playerman/player.h"
21 #include "network/multimsgs.h"
22 #include "network/multiui.h"
23 #include "network/multiutil.h"
24 #include "network/multi_pmsg.h"
25 #include "network/multi_portfwd.h"
26 #include "hud/hudconfig.h"
27 #include "network/multi_fstracker.h"
28 #include "network/multi_mdns.h"
29 
30 
31 // ----------------------------------------------------------------------------------------------------------
32 // Put all functions/data related to leaving a netgame, handling players leaving, handling the server leaving,
33 // and notifying the user of all of these actions, here.
34 //
35 
36 
37 // ----------------------------------------------------------------------------------------------------------
38 // MULTI ENDGAME DEFINES/VARS
39 //
40 
41 
42 // set when the server/client has ended the game on some notification or error and is waiting for clients to leave
43 #define MULTI_ENDGAME_SERVER_WAIT				5.0f
44 int Multi_endgame_server_waiting = 0;
45 float Multi_endgame_server_wait_stamp = -1.0f;
46 int Multi_endgame_client_waiting = 0;
47 
48 // error/notification codes (taken from parameters to multi_quit_game(...)
49 int Multi_endgame_notify_code;
50 int Multi_endgame_error_code;
51 int Multi_endgame_wsa_error;
52 
53 // for reentrancy problems on standalone
54 int Multi_endgame_processing;
55 
56 // ----------------------------------------------------------------------------------------------------------
57 // MULTI ENDGAME FORWARD DECLARATIONS
58 //
59 
60 // called when a given netgame is about to end completely
61 void multi_endgame_cleanup();
62 
63 // throw up a popup with the given notification code and optional winsock code
64 void multi_endgame_popup(int notify_code,int error_code,int wsa_error = -1);
65 
66 // called when server is waiting for clients to disconnect
67 int multi_endgame_server_ok_to_leave();
68 
69 // check to see if we need to be warping out (strange transition possibilities)
70 void multi_endgame_check_for_warpout();
71 
72 
73 // ----------------------------------------------------------------------------------------------------------
74 // MULTI ENDGAME FUNCTIONS
75 //
76 
77 // initialize the endgame processor (call when joining/starting a new netgame)
multi_endgame_init()78 void multi_endgame_init()
79 {
80 	// set this so that the server/client knows he hasn't tried to end the game
81 	Multi_endgame_server_waiting = 0;
82 	Multi_endgame_client_waiting = 0;
83 
84 	// reset the timestamp used when server is waiting for all other clients to leave.
85 	Multi_endgame_server_wait_stamp = -1.0f;
86 
87 	// initialiaze all endgame notify and error codes
88 	Multi_endgame_notify_code = -1;
89 	Multi_endgame_error_code = -1;
90 	Multi_endgame_wsa_error = -1;
91 
92 	// for reentrancy problems in to endgame_process
93 	Multi_endgame_processing = 0;
94 }
95 
96 // process all endgame related events
multi_endgame_process()97 void multi_endgame_process()
98 {
99 	if ( Multi_endgame_processing )
100 		return;
101 
102 	Multi_endgame_processing = 1;
103 
104 	// check to see if we need to be warping out (strange transition possibilities)
105 	multi_endgame_check_for_warpout();
106 
107 	// if we're the server of the game
108 	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
109 		// if we're not waiting for clients to leave, do nothing
110 		if(!Multi_endgame_server_waiting){
111 			Multi_endgame_processing = 0;
112 			return;
113 		}
114 
115 		// if a popup is already active, do nothing
116 		if(popup_active()){
117 			Multi_endgame_processing = 0;
118 			return;
119 		}
120 
121 		// otherwise popup until things are hunky-dory
122 		if(!multi_endgame_server_ok_to_leave()){
123 			if(Game_mode & GM_STANDALONE_SERVER){
124 				while(!multi_endgame_server_ok_to_leave()){
125 					// run networking, etc.
126 					game_set_frametime(-1);
127 					game_do_state_common(gameseq_get_state());
128 				}
129 			} else {
130 				popup_till_condition( multi_endgame_server_ok_to_leave , XSTR("&Cancel",645), XSTR("Waiting for clients to disconnect",646));
131 			}
132 		}
133 
134 		// mark myself as not waiting and get out
135 		multi_endgame_cleanup();
136 	} else {
137 		// if we're not waiting to leave the game, do nothing
138 		if(!Multi_endgame_client_waiting){
139 			Multi_endgame_processing = 0;
140 			return;
141 		}
142 
143 		// otherwise, check to see if there is a popup active
144 		if(popup_active()){
145 			Multi_endgame_processing = 0;
146 			return;
147 		}
148 
149 		// if not, then we are good to leave
150 		multi_endgame_cleanup();
151 	}
152 
153 	Multi_endgame_processing = 0;
154 }
155 
156 // if the game has been flagged as ended (ie, its going to be reset)
multi_endgame_ending()157 int multi_endgame_ending()
158 {
159 	return (Multi_endgame_client_waiting || Multi_endgame_server_waiting);
160 }
161 
162 // reentrancy check
163 int Multi_quit_game = 0;
164 // general quit function, with optional notification, error, and winsock error codes
multi_quit_game(int prompt,int notify_code,int err_code,int wsa_error)165 int multi_quit_game(int prompt, int notify_code, int err_code, int wsa_error)
166 {
167 	int ret_val;
168 
169 	// check for reentrancy
170 	if(Multi_quit_game){
171 		return 0;
172 	}
173 
174 	// if we're not connected or have not net-player
175 	if((Net_player == NULL) || !(Net_player->flags & NETINFO_FLAG_CONNECTED)){
176 		return 1;
177 	}
178 
179 	// reentrancy
180 	Multi_quit_game = 1;
181 
182 	// reset my control info so that I don't continually do whacky stuff.  This is ugly
183 	//player_control_reset_ci( &Player->ci );
184 	if ( Game_mode & GM_IN_MISSION ) {
185 		memset(&Player->ci, 0, sizeof(Player->ci) );
186 		Player->ci.afterburner_stop = 1;
187 		physics_read_flying_controls( &Player_obj->orient, &Player_obj->phys_info, &(Player->ci), flFrametime);
188 	}
189 
190 	// CASE 1 - response to a user request
191 	// if there is no associated notification or error code, don't override the prompt argument
192 	if((err_code == -1) && (notify_code == -1)){
193 		// if we're the server and we're already waiting for clients to leave, don't do anything
194 		if((Net_player->flags & NETINFO_FLAG_AM_MASTER) && Multi_endgame_server_waiting){
195 			Multi_quit_game = 0;
196 			return 0;
197 		}
198 
199 		// if we're the client and we're already waiting to leave, don't do anythin
200 		if(!(Net_player->flags & NETINFO_FLAG_AM_MASTER) && Multi_endgame_client_waiting){
201 			Multi_quit_game = 0;
202 			return 0;
203 		}
204 
205 		// see if we should be prompting for confirmation
206 		if (prompt != PROMPT_NONE) {
207 			int p_flags = PF_USE_AFFIRMATIVE_ICON | PF_USE_NEGATIVE_ICON | PF_BODY_BIG;
208 
209 			if ( (Game_mode & GM_IN_MISSION) && ((Net_player->flags & NETINFO_FLAG_GAME_HOST) || popupdead_is_active()) ) {
210 				p_flags |= PF_RUN_STATE;
211 			}
212 
213 			if (prompt == PROMPT_HOST) {
214 				ret_val = popup(p_flags, 2, POPUP_CANCEL, POPUP_OK, XSTR("Warning - quitting will end the game for all players!", 647));
215 			} else {
216 				ret_val = popup(p_flags, 2, POPUP_NO, POPUP_YES, XSTR("Are you sure you want to quit?", 648));
217 			}
218 
219 			// check for cancel
220 			if ( (ret_val == 0) || (ret_val == -1) ) {
221 				Multi_quit_game = 0;
222 				return 0;
223 			}
224 		}
225 
226 		// if i'm the server of the game, tell all clients that i'm leaving, then wait
227 		if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
228 			send_netgame_end_error_packet(MULTI_END_NOTIFY_SERVER_LEFT,MULTI_END_ERROR_NONE);
229 
230 			// set the waiting flag and the waiting timestamp
231 			Multi_endgame_server_waiting = 1;
232 			Multi_endgame_server_wait_stamp = MULTI_ENDGAME_SERVER_WAIT;
233 		}
234 		// if i'm the client, quit now
235 		else {
236 			multi_endgame_cleanup();
237 		}
238 	}
239 	// CASE 2 - response to an error code or packet from the server
240 	// this is the case where we're being forced to quit the game because of some error or other notification
241 	else {
242 		// if i'm the server, send a packet to the clients telling them that I'm leaving and why
243 		if((Net_player->flags & NETINFO_FLAG_AM_MASTER) && !Multi_endgame_server_waiting){
244 			// if we're in the debrief state, mark down that the server has left the game
245 			if(((gameseq_get_state() == GS_STATE_DEBRIEF) || (gameseq_get_state() == GS_STATE_MULTI_DOGFIGHT_DEBRIEF)) && !(Game_mode & GM_STANDALONE_SERVER)){
246 				multi_debrief_server_left();
247 
248 				// add a message to the chatbox
249 				multi_display_chat_msg(XSTR("<Team captains have left>",649),0,0);
250 
251 				// set ourselves to be "not quitting"
252 				Multi_quit_game = 0;
253 
254 				// tell the users, the game has ended
255 				send_netgame_end_error_packet(notify_code,err_code);
256 				return 0;
257 			}
258 
259 			send_netgame_end_error_packet(notify_code,err_code);
260 
261 			// store the globals
262 			Multi_endgame_notify_code = notify_code;
263 			Multi_endgame_error_code = err_code;
264 			Multi_endgame_wsa_error = wsa_error;
265 
266 			// by setting this, multi_endgame_process() will know to check and see if it is ok for us to leave
267 			Multi_endgame_server_waiting = 1;
268 			Multi_endgame_server_wait_stamp = MULTI_ENDGAME_SERVER_WAIT;
269 		}
270 		// if i'm the client, set the error codes and leave the game now
271 		else if(!Multi_endgame_client_waiting){
272 			// if we're in the debrief state, mark down that the server has left the game
273 			if((gameseq_get_state() == GS_STATE_DEBRIEF) || (gameseq_get_state() == GS_STATE_MULTI_DOGFIGHT_DEBRIEF)){
274 				multi_debrief_server_left();
275 
276 				// add a message to the chatbox
277 				multi_display_chat_msg(XSTR("<The server has ended the game>",650),0,0);
278 
279 				// shut our reliable socket to the server down
280 				psnet_rel_close_socket(Net_player->reliable_socket);
281 				Net_player->reliable_socket = PSNET_INVALID_SOCKET;
282 
283 				// remove our do-networking flag
284 				Net_player->flags &= ~(NETINFO_FLAG_DO_NETWORKING);
285 
286 				Multi_quit_game = 0;
287 				return 0;
288 			}
289 
290 			Multi_endgame_notify_code = notify_code;
291 			Multi_endgame_error_code = err_code;
292 			Multi_endgame_wsa_error = wsa_error;
293 
294 			// by setting this, multi_endgame_process() will know to check and see if it is ok for us to leave
295 			Multi_endgame_client_waiting = 1;
296 		}
297 	}
298 
299 	// unset the reentrancy flag
300 	Multi_quit_game = 0;
301 
302 	return 1;
303 }
304 
305 
306 // ----------------------------------------------------------------------------------------------------------
307 // MULTI ENDGAME FORWARD DEFINITIONS
308 //
309 
310 // called when a given netgame is about to end completely
multi_endgame_cleanup()311 void multi_endgame_cleanup()
312 {
313 	int idx;
314 
315 	hud_config_as_player();
316 
317 	send_leave_game_packet();
318 
319 	// flush all outgoing io, force all packets through
320 	multi_io_send_buffered_packets();
321 
322 	// mark myself as disconnected
323 	if(!(Game_mode & GM_STANDALONE_SERVER)){
324 		Net_player->flags &= ~(NETINFO_FLAG_CONNECTED|NETINFO_FLAG_DO_NETWORKING);
325 	}
326 
327 	/*this is a semi-hack so that if we're the master and we're quitting, we don't get an assert
328 
329     Karajorma - From the looks of things this code actually CAUSES an Int3 and doesn't cause an assert anymore
330 	besides if the game is over why are we setting flags on a Player_obj anyway?
331 
332 	if((Net_player->flags & NETINFO_FLAG_AM_MASTER) && (Player_obj != NULL)){
333 		Player_obj->flags &= ~(OF_PLAYER_SHIP);
334 		obj_set_flags( Player_obj, Player_obj->flags | OF_COULD_BE_PLAYER );
335 	}
336 	*/
337 
338 	// shut my socket down (will also let the server know i've received any notifications/error from him)
339 	// psnet_rel_close_socket( &(Net_player->reliable_socket) );
340 
341 	// 11/18/98 - DB, changed the above to kill all sockets. Its the safest thing to do
342 	for(idx=0; idx<MAX_PLAYERS; idx++){
343 		psnet_rel_close_socket(Net_players[idx].reliable_socket);
344 		Net_players[idx].reliable_socket = PSNET_INVALID_SOCKET;
345 	}
346 
347 	// set the game quitting flag in our local netgame info - this will _insure_ that even if we miss a packet or
348 	// there is some sequencing error, the next time through the multi_do_frame() loop, the game will be ended
349 	// Netgame.flags |= (NG_FLAG_QUITTING | NG_FLAG_ENDED);
350 
351 	// close all open SPX/TCP reliable sockets
352 	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
353 		// do it for all players, since we're leaving anyway.
354 		for(idx=0;idx<MAX_PLAYERS;idx++){
355 			// 6/25/98 -- MWA delete all players from the game
356 
357 			if ( &Net_players[idx] != Net_player ) {
358 				delete_player( idx );
359 			}
360 		}
361 	}
362 
363 	// if we're currently in the pause state, pop back into gameplay first
364 	if(gameseq_get_state() == GS_STATE_MULTI_PAUSED){
365 		gameseq_pop_state();
366 	}
367 
368 	if (Game_mode & GM_STANDALONE_SERVER) {
369 		// multi_standalone_quit_game();
370 		multi_standalone_reset_all();
371 	} else {
372 		Player->flags |= PLAYER_FLAGS_IS_MULTI;
373 
374 		// log game out of tracker
375 		if (Net_player->flags & NETINFO_FLAG_MT_CONNECTED) {
376 			multi_fs_tracker_logout();
377 		}
378 
379 		// stop port forwarding
380 		multi_port_forward_close();
381 
382 		// stop mdns
383 		multi_mdns_service_close();
384 
385 		// if we're in Parallax Online mode, log back in there
386 		if (Multi_options_g.pxo == 1) {
387 			Assert(Multi_options_g.protocol == NET_TCP);
388 			gameseq_post_event(GS_EVENT_PXO);
389 		} else {
390 			gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME);
391 		}
392 
393 		// if we have an error code, bring up the discon popup
394 		if ( ((Multi_endgame_notify_code != -1) || (Multi_endgame_error_code != -1)) && !(Game_mode & GM_STANDALONE_SERVER) ) {
395 			multi_endgame_popup(Multi_endgame_notify_code,Multi_endgame_error_code,Multi_endgame_wsa_error);
396 		}
397 	}
398 
399 	/*
400 	extern CFILE *obj_stream;
401 	if(obj_stream != NULL){
402 		cfclose(obj_stream);
403 		obj_stream = NULL;
404 	}
405 	*/
406 
407 	// unload the multiplayer common interface palette
408 	multi_common_unload_palette();
409 
410 	// reinitialize endgame stuff
411 	// multi_endgame_init();
412 }
413 
414 // throw up a popup with the given notification code and optional winsock code
multi_endgame_popup(int notify_code,int error_code,int wsa_error)415 void multi_endgame_popup(int notify_code,int error_code,int wsa_error)
416 {
417 	char err_msg[255];
418 	int flags = PF_USE_AFFIRMATIVE_ICON;
419 
420 	// if there is a popup already active, just kill it
421 	if(popup_active()){
422 		// if there is already a popup active, kill it
423 		popup_kill_any_active();
424 
425 		Int3();
426 	} else {
427 		// if there is a winsock error code, stick it on the end of the text
428 		if(wsa_error != -1){
429 			sprintf(err_msg,NOX("WSAERROR : %d\n\n"),wsa_error);
430 			flags |= PF_TITLE_RED;
431 		} else {
432 			strcpy_s(err_msg,"");
433 		}
434 
435 		// setup the error message string
436 		if(notify_code != MULTI_END_NOTIFY_NONE){
437 			switch(notify_code){
438 			case MULTI_END_NOTIFY_KICKED :
439 				strcat_s(err_msg,XSTR("You have been kicked",651));
440 				break;
441 			case MULTI_END_NOTIFY_SERVER_LEFT:
442 				strcat_s(err_msg,XSTR("The server has left the game",652));
443 				break;
444 			case MULTI_END_NOTIFY_FILE_REJECTED:
445 				strcat_s(err_msg,XSTR("Your mission file has been rejected by the server",653));
446 				break;
447 			case MULTI_END_NOTIFY_EARLY_END:
448 				strcat_s(err_msg,XSTR("The game has ended while you were ingame joining",654));
449 				break;
450 			case MULTI_END_NOTIFY_INGAME_TIMEOUT:
451 				strcat_s(err_msg,XSTR("You have waited too long to select a ship",655));
452 				break;
453 			case MULTI_END_NOTIFY_KICKED_BAD_XFER:
454 				strcat_s(err_msg,XSTR("You were kicked because mission file xfer failed",998));
455 				break;
456 			case MULTI_END_NOTIFY_KICKED_CANT_XFER:
457 				strcat_s(err_msg,XSTR("You were kicked because you do not have the builtin mission",999));
458 				strcat_s(err_msg, NOX(" "));
459 				strcat_s(err_msg, Game_current_mission_filename);
460 				break;
461 			case MULTI_END_NOTIFY_KICKED_INGAME_ENDED:
462 				strcat_s(err_msg,XSTR("You were kicked because you were ingame joining a game that has ended",1000));
463 				break;
464 			default :
465 				Int3();
466 			}
467 		} else {
468 			switch(error_code){
469 			case MULTI_END_ERROR_CONTACT_LOST :
470 				strcat_s(err_msg,XSTR("Contact with server has been lost",656));
471 				break;
472 			case MULTI_END_ERROR_CONNECT_FAIL :
473 				strcat_s(err_msg,XSTR("Failed to connect to server on reliable socket",657));
474 				break;
475 			case MULTI_END_ERROR_LOAD_FAIL :
476 				strcat_s(err_msg,XSTR("Failed to load mission file properly",658));
477 				break;
478 			case MULTI_END_ERROR_INGAME_SHIP :
479 				strcat_s(err_msg,XSTR("Unable to create ingame join player ship",659));
480 				break;
481 			case MULTI_END_ERROR_INGAME_BOGUS :
482 				strcat_s(err_msg,XSTR("Recevied bogus packet data while ingame joining",660));
483 				break;
484 			case MULTI_END_ERROR_STRANS_FAIL :
485 				strcat_s(err_msg,XSTR("Server transfer failed (obsolete)",661));
486 				break;
487 			case MULTI_END_ERROR_SHIP_ASSIGN:
488 				strcat_s(err_msg,XSTR("Server encountered errors trying to assign players to ships",662));
489 				break;
490 			case MULTI_END_ERROR_HOST_LEFT:
491 				strcat_s(err_msg,XSTR("Host has left the game, aborting...",663));
492 				break;
493 			case MULTI_END_ERROR_XFER_FAIL:
494 				strcat_s(err_msg,XSTR("There was an error receiving the mission file!",665));
495 				break;
496 			case MULTI_END_ERROR_WAVE_COUNT:
497 				strcat_s(err_msg,XSTR("The player wings Alpha, Beta, Gamma, and Zeta must have only 1 wave.  One of these wings currently has more than 1 wave.", 987));
498 				break;
499 			case MULTI_END_ERROR_TEAM0_EMPTY:
500 				strcat_s(err_msg,XSTR("All players from team 1 have left the game", 1645));
501 				break;
502 			case MULTI_END_ERROR_TEAM1_EMPTY:
503 				strcat_s(err_msg,XSTR("All players from team 2 have left the game", 1646));
504 				break;
505 			case MULTI_END_ERROR_CAPTAIN_LEFT:
506 				strcat_s(err_msg,XSTR("Team captain(s) have left the game, aborting...",664));
507 				break;
508 			default :
509 				Int3();
510 			}
511 		}
512 
513 		// show the popup
514 		popup(flags,1,POPUP_OK,err_msg);
515 	}
516 }
517 
518 // called when server is waiting for clients to disconnect
multi_endgame_server_ok_to_leave()519 int multi_endgame_server_ok_to_leave()
520 {
521 	int idx;
522 
523 	// check to see if our client disconnect timestamp has elapsed
524 	if ( Multi_endgame_server_wait_stamp > 0.0f ) {
525 		Multi_endgame_server_wait_stamp -= flFrametime;
526 		if ( Multi_endgame_server_wait_stamp <= 0.0f ) {
527 			return 1;
528 		}
529 	}
530 
531 	// check to see if all clients have disconnected
532 	for(idx=0;idx<MAX_PLAYERS;idx++){
533 		if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])){
534 			return 0;
535 		}
536 	}
537 
538 	// all conditions passed
539 	return 1;
540 }
541 
542 // check to see if we need to be warping out (strange transition possibilities)
multi_endgame_check_for_warpout()543 void multi_endgame_check_for_warpout()
544 {
545 	int need_to_warpout = 0;
546 
547 	// if we're not in the process of warping out - do nothing
548 	if(!(Net_player->flags & NETINFO_FLAG_WARPING_OUT)){
549 		return;
550 	}
551 
552 	// determine if sufficient warping-out conditions exist
553 	if((Game_mode & GM_IN_MISSION) &&										// if i'm still in the mission
554 		((Netgame.game_state == NETGAME_STATE_ENDGAME)	||				// if the netgame ended
555 		 (Netgame.game_state == NETGAME_STATE_DEBRIEF))					// if the netgame is now in the debriefing state
556 	  ) {
557 		need_to_warpout = 1;
558 	}
559 
560 	// if we need to be warping out but are stuck in a dead popup, cancel it
561 	if(need_to_warpout && (popupdead_is_active() || (Net_player->flags & NETINFO_FLAG_RESPAWNING) || (Net_player->flags & NETINFO_FLAG_OBSERVER)) ){
562 		// flush all active pushed state
563 		multi_handle_state_special();
564 
565 		// begin the warpout process
566 		send_debrief_event();
567 
568 		// if text input mode is active, clear it
569 		multi_msg_text_flush();
570 	}
571 }
572