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_pause.h"
14 #include "missionui/chatbox.h"
15 #include "io/key.h"
16 #include "popup/popup.h"
17 #include "gamesequence/gamesequence.h"
18 #include "network/stand_gui.h"
19 #include "gamesnd/gamesnd.h"
20 #include "network/multiutil.h"
21 #include "network/multiui.h"
22 #include "network/multimsgs.h"
23 #include "network/multi_endgame.h"
24 #include "network/multi_pmsg.h"
25 #include "playerman/player.h"
26 #include "network/multi.h"
27 #include "globalincs/alphacolors.h"
28 #include "io/timer.h"
29 
30 
31 
32 // ----------------------------------------------------------------------------------
33 // PAUSE DEFINES/VARS
34 //
35 
36 // state of the game (paused or not) on _my_ machine. Obviously this is important for the server
37 // call multi_pause_reset() to reinitialize
38 int Multi_pause_status = 0;
39 
40 // who paused the game
41 net_player *Multi_pause_pauser = NULL;
42 
43 // timestamp for eating keypresses for a while after
44 float Multi_pause_eat = -1.0f;
45 
46 // pause ui screen stuff
47 #define MULTI_PAUSED_NUM_BUTTONS		3
48 
49 // button defs
50 #define MP_SCROLL_UP					0
51 #define MP_SCROLL_DOWN				1
52 #define MP_EXIT_MISSION				2
53 
54 char *Multi_paused_bg_fname[GR_NUM_RESOLUTIONS] = {
55 	"MPPause",
56 	"2_MPPause"
57 };
58 
59 char *Multi_paused_bg_mask[GR_NUM_RESOLUTIONS] = {
60 	"MPPause-m",
61 	"2_MPPause-m"
62 };
63 
64 // where to place the pauser's callsign
65 int Mp_callsign_coords[GR_NUM_RESOLUTIONS][2] = {
66 	{ // GR_640
67 		110, 132
68 	},
69 	{ // GR_1024
70 		171, 218
71 	}
72 };
73 
74 ui_button_info Multi_paused_buttons[GR_NUM_RESOLUTIONS][MULTI_PAUSED_NUM_BUTTONS] = {
75 	{ // GR_640
76 		ui_button_info("PB00",		519,	212,	-1,	-1,	0),
77 		ui_button_info("PB01",		519,	252,	-1,	-1,	1),
78 		ui_button_info("PB02",		488,	321,	-1,	-1,	2),
79 	},
80 	{ // GR_1024
81 		ui_button_info("2_PB00",		831,	339,	-1,	-1,	0),
82 		ui_button_info("2_PB01",		831,	403,	-1,	-1,	1),
83 		ui_button_info("2_PB02",		781,	514,	-1,	-1,	2),
84 	}
85 };
86 
87 // text
88 #define MULTI_PAUSED_NUM_TEXT				3
89 UI_XSTR Multi_paused_text[GR_NUM_RESOLUTIONS][MULTI_PAUSED_NUM_BUTTONS] = {
90 	{ // GR_640
91 		{ "Exit",				1059,	493,	297,	UI_XSTR_COLOR_PINK,	-1, &Multi_paused_buttons[0][MP_EXIT_MISSION].button },
92 		{ "Mission",			1063,	482,	306,	UI_XSTR_COLOR_PINK,	-1, &Multi_paused_buttons[0][MP_EXIT_MISSION].button },
93 		{ "Mission Paused",	1440,	107,	356,	UI_XSTR_COLOR_PINK,	-1, NULL },
94 	},
95 	{ // GR_1024
96 		{ "Exit",				1059,	787,	478,	UI_XSTR_COLOR_PINK,	-1, &Multi_paused_buttons[1][MP_EXIT_MISSION].button },
97 		{ "Mission",			1063,	778,	490,	UI_XSTR_COLOR_PINK,	-1, &Multi_paused_buttons[1][MP_EXIT_MISSION].button },
98 		{ "Mission Paused",	1440,	171,	567,	UI_XSTR_COLOR_PINK,	-1, NULL },
99 	}
100 };
101 
102 
103 UI_WINDOW Multi_paused_window;
104 int Multi_paused_screen_id = -1;		// backed up screen data
105 int Multi_paused_background = -1;		// pause background
106 
107 void multi_pause_check_buttons();
108 void multi_pause_button_pressed(int n);
109 
110 // (server) evaluate a pause request from the given player (should call for himself as well)
111 void multi_pause_server_eval_request(net_player *pl, int pause);
112 
113 // if this player can unpause
114 int multi_pause_can_unpause(net_player *p);
115 
116 // render the callsign of the guy who paused
117 void multi_pause_render_callsign();
118 
119 int Multi_paused = 0;
120 
121 // ----------------------------------------------------------------------------------
122 // EXTERNAL FUNCTIONS/VARIABLES
123 //
124 
125 extern void game_flush();
126 
127 extern void weapon_pause_sounds();
128 extern void weapon_unpause_sounds();
129 
130 extern void audiostream_pause_all();
131 extern void audiostream_unpause_all();
132 
133 // ----------------------------------------------------------------------------------
134 // PAUSE FUNCTIONS
135 //
136 
137 // re-initializes the pause system. call before entering the mission to reset
multi_pause_reset()138 void multi_pause_reset()
139 {
140 	// set the pause status to 0
141 	Multi_pause_status = 0;
142 
143 	// null out the pause pointer
144 	Multi_pause_pauser = NULL;
145 
146 	// eat keys timestamp
147 	Multi_pause_eat = -1.0f;
148 }
149 
150 // (client) call when receiving a packet indicating we should pause
multi_pause_pause()151 void multi_pause_pause()
152 {
153 	int idx;
154 
155 	// if we're already paused, don't do anything
156 	if(Multi_pause_status){
157 		return;
158 	}
159 
160 	// sanity check
161 	Assert(!Multi_pause_status);
162 
163 	// mark the game as being paused
164 	Multi_pause_status = 1;
165 
166 	// if we're not already in the pause state
167 	if(gameseq_get_state() != GS_STATE_MULTI_PAUSED){
168 		// jump into the paused state
169 		gameseq_post_event(GS_EVENT_MULTI_PAUSE);
170 
171 		// mark the netgame state
172 		Netgame.game_state = NETGAME_STATE_PAUSED;
173 	}
174 
175 	// if we're the server of the game, send a packet which will pause the clients in the game now
176 	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
177 		for(idx=0;idx<MAX_PLAYERS;idx++){
178 			if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])){
179 				send_client_update_packet(&Net_players[idx]);
180 			}
181 		}
182 	}
183 }
184 
185 // (client) call when receiving a packet indicating we should unpause
multi_pause_unpause()186 void multi_pause_unpause()
187 {
188 	int idx;
189 
190 	// if we're already unpaused, don't do anything
191 	if(!Multi_pause_status){
192 		return;
193 	}
194 
195 	// sanity check
196 	Assert(Multi_pause_status);
197 
198 	// mark the game as being unpaused
199 	Multi_pause_status = 0;
200 
201 	// pop us out of any necessary states (including the pause state !!)
202 	multi_handle_state_special();
203 
204 	// mark the netgame state
205 	Netgame.game_state = NETGAME_STATE_IN_MISSION;
206 
207 	// if we're the server of the game, send a packet which will unpause the clients in the game now
208 	// if we're the server of the game, send a packet which will pause the clients in the game now
209 	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
210 		for(idx=0;idx<MAX_PLAYERS;idx++){
211 			if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])){
212 				send_client_update_packet(&Net_players[idx]);
213 			}
214 		}
215 	}
216 }
217 
218 // send a request to pause or unpause a game (all players should use this function)
multi_pause_request(int pause)219 void multi_pause_request(int pause)
220 {
221 	// if i'm the server, run it through the eval function right now
222 	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
223 		multi_pause_server_eval_request(Net_player,pause);
224 	}
225 	// otherwise, send a reliable request packet to the server
226 	else {
227 		send_multi_pause_packet(pause);
228 	}
229 }
230 
231 // (server) evaluate a pause request from the given player (should call for himself as well)
multi_pause_server_eval_request(net_player * pl,int pause)232 void multi_pause_server_eval_request(net_player *pl, int pause)
233 {
234 	int cur_state;
235 
236 	// if this is a pause request and we're already in the pause state, do nothing
237 	if(pause && Multi_pause_status){
238 		return;
239 	}
240 
241 	// if this is an unpause request and we're already unpaused, do nothing
242 	if(!pause && !Multi_pause_status){
243 		return;
244 	}
245 
246 	// get the current state (don't allow pausing from certain states
247 	cur_state = gameseq_get_state();
248 	if((cur_state == GS_STATE_DEBRIEF) || (cur_state == GS_STATE_MULTI_MISSION_SYNC) || (cur_state == GS_STATE_BRIEFING) ||
249 		(cur_state == GS_STATE_STANDALONE_POSTGAME) || (cur_state == GS_STATE_MULTI_STD_WAIT) || (cur_state == GS_STATE_WEAPON_SELECT) ||
250 		(cur_state == GS_STATE_TEAM_SELECT) || (cur_state == GS_STATE_MULTI_DOGFIGHT_DEBRIEF)){
251 		return;
252 	}
253 
254 	// if this is a pause request
255 	if(pause){
256 		// record who the pauser is
257 		Multi_pause_pauser = pl;
258 
259 		// pause the game
260 		multi_pause_pause();
261 	}
262 	// if this is an unpause request
263 	else {
264 		// if this guy is allowed to unpause the game, do so
265 		if(multi_pause_can_unpause(pl)){
266 			// unmark the "pauser"
267 			Multi_pause_pauser = NULL;
268 
269 			// unpause the game
270 			multi_pause_unpause();
271 		}
272 	}
273 }
274 
275 // if this player can unpause
multi_pause_can_unpause(net_player * p)276 int multi_pause_can_unpause(net_player *p)
277 {
278 	if(!(p->flags & NETINFO_FLAG_GAME_HOST) && (p != Multi_pause_pauser)){
279 		return 0;
280 	}
281 
282 	return 1;
283 }
284 
285 // if we still want to eat keys
multi_pause_eat_keys()286 int multi_pause_eat_keys()
287 {
288 	// if the eat timestamp is negative, don't eat keys
289 	if(Multi_pause_eat < 0.0f){
290 		return 0;
291 	}
292 
293 	// if less than 1 second has passed, continue eating keys
294 	if((f2fl(timer_get_fixed_seconds()) - Multi_pause_eat) < 1.0f){
295 		nprintf(("Network","PAUSE EATING KEYS\n"));
296 
297 		control_config_clear_used_status();
298 		key_flush();
299 
300 		return 1;
301 	}
302 
303 	// otherwise, disable the timestamp
304 	Multi_pause_eat = -1.0f;
305 
306 	return 0;
307 }
308 
309 
310 // ----------------------------------------------------------------------------------
311 // PAUSE UI FUNCTIONS
312 //
313 
multi_pause_init()314 void multi_pause_init()
315 {
316 	int i;
317 
318 	// if we're already paused. do nothing
319 	if ( Multi_paused ) {
320 		return;
321 	}
322 
323 	Assert( Game_mode & GM_MULTIPLAYER );
324 
325 	if ( !(Game_mode & GM_MULTIPLAYER) )
326 		return;
327 
328 	// pause all beam weapon sounds
329 	weapon_pause_sounds();
330 
331 	// standalone shouldn't be doing any freespace interface stuff
332 	if (Game_mode & GM_STANDALONE_SERVER) {
333 		std_debug_set_standalone_state_string("Multi paused do");
334 	}
335 	// everyone else should be doing UI stuff
336 	else {
337 		// pause all game music
338 		audiostream_pause_all();
339 
340 		// switch off the text messaging system if it is active
341 		multi_msg_text_flush();
342 
343 		if ( Multi_paused_screen_id == -1 )
344 			Multi_paused_screen_id = gr_save_screen();
345 
346 		// create ui window
347 		Multi_paused_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
348 		Multi_paused_window.set_mask_bmap(Multi_paused_bg_mask[gr_screen.res]);
349 		Multi_paused_background = bm_load(Multi_paused_bg_fname[gr_screen.res]);
350 
351 		for (i=0; i<MULTI_PAUSED_NUM_BUTTONS; i++) {
352 			// create the button
353 			Multi_paused_buttons[gr_screen.res][i].button.create(&Multi_paused_window, "", Multi_paused_buttons[gr_screen.res][i].x, Multi_paused_buttons[gr_screen.res][i].y, 1, 1, 0, 1);
354 
355 			// set the highlight action
356 			Multi_paused_buttons[gr_screen.res][i].button.set_highlight_action(common_play_highlight_sound);
357 
358 			// set the ani
359 			Multi_paused_buttons[gr_screen.res][i].button.set_bmaps(Multi_paused_buttons[gr_screen.res][i].filename);
360 
361 			// set the hotspot
362 			Multi_paused_buttons[gr_screen.res][i].button.link_hotspot(Multi_paused_buttons[gr_screen.res][i].hotspot);
363 		}
364 
365 		// add text
366 		for(i=0; i<MULTI_PAUSED_NUM_TEXT; i++){
367 			Multi_paused_window.add_XSTR(&Multi_paused_text[gr_screen.res][i]);
368 		}
369 
370 		// close any instances of a chatbox
371 		chatbox_close();
372 
373 		// intialize our custom chatbox
374 		chatbox_create(CHATBOX_FLAG_MULTI_PAUSED);
375 	}
376 
377 	Multi_paused = 1;
378 
379 	// reset timestamps
380 	multi_reset_timestamps();
381 }
382 
multi_pause_do()383 void multi_pause_do()
384 {
385 	int k;
386 
387 	// make sure we don't enter this state unless we're in the mission itself
388 	Netgame.game_state = NETGAME_STATE_PAUSED;
389 
390 	// server of the game should periodically be sending pause packets for good measure
391 	if (Net_player->flags & NETINFO_FLAG_AM_MASTER) {
392 	}
393 
394 	if (!(Game_mode & GM_STANDALONE_SERVER)) {
395 		// restore saved screen data if any
396 		if (Multi_paused_screen_id >= 0) {
397 			gr_restore_screen(Multi_paused_screen_id);
398 		}
399 
400 		// set the background image
401 		if (Multi_paused_background >= 0) {
402 			gr_set_bitmap(Multi_paused_background);
403 			gr_bitmap(0, 0, GR_RESIZE_MENU);
404 		}
405 
406 		// if we're inside of popup code right now, don't process the window
407 		if(!popup_active()){
408 			// process chatbox and window stuff
409 			k = chatbox_process();
410 			k = Multi_paused_window.process(k);
411 
412 			switch (k) {
413 			case KEY_ESC:
414 			case KEY_PAUSE:
415 				multi_pause_request(0);
416 				break;
417 			}
418 		}
419 
420 		// check for any button presses
421 		multi_pause_check_buttons();
422 
423 		// render the callsign of the guy who paused
424 		multi_pause_render_callsign();
425 
426 		// render the chatbox
427 		chatbox_render();
428 
429 		// draw tooltips
430 		// Multi_paused_window.draw_tooltip();
431 		Multi_paused_window.draw();
432 
433 		// display the voice status indicator
434 		multi_common_voice_display_status();
435 
436 		// don't flip screen if we are in the popup code right now
437 		if (!popup_active()) {
438 			gr_flip();
439 		}
440 	}
441 	// standalone pretty much does nothing here
442 	else {
443 		Sleep(1);
444 	}
445 }
446 
multi_pause_close(int end_mission)447 void multi_pause_close(int end_mission)
448 {
449 	if ( !Multi_paused )
450 		return;
451 
452 	// set the standalonest
453 	if (Game_mode & GM_STANDALONE_SERVER) {
454 		std_debug_set_standalone_state_string("Game play");
455 	} else {
456 		// free the screen up
457 		if ( end_mission && (Multi_paused_screen_id >= 0) ) {
458 			gr_free_screen(Multi_paused_screen_id);
459 			Multi_paused_screen_id = -1;
460 		}
461 
462 		if (Multi_paused_background >= 0) {
463 			bm_release(Multi_paused_background);
464 			Multi_paused_background = -1;
465 		}
466 
467 		Multi_paused_window.destroy();
468 		game_flush();
469 
470 		// unpause all the music
471 		audiostream_unpause_all();
472 	}
473 
474 	// unpause beam weapon sounds
475 	weapon_unpause_sounds();
476 
477 	// eat keys timestamp
478 	Multi_pause_eat = f2fl(timer_get_fixed_seconds());
479 
480 	// reset timestamps
481 	multi_reset_timestamps();
482 
483 	// clear out control config and keypress info
484 	control_config_clear_used_status();
485 	key_flush();
486 
487 	Multi_paused = 0;
488 }
489 
multi_pause_check_buttons()490 void multi_pause_check_buttons()
491 {
492 	int idx;
493 
494 	// process any pause buttons which may have been pressed
495 	for (idx=0; idx<MULTI_PAUSED_NUM_BUTTONS; idx++){
496 		if (Multi_paused_buttons[gr_screen.res][idx].button.pressed()){
497 			multi_pause_button_pressed(idx);
498 		}
499 	}
500 }
501 
multi_pause_button_pressed(int n)502 void multi_pause_button_pressed(int n)
503 {
504 	switch (n) {
505 	// the scroll up button
506 	case MP_SCROLL_UP :
507 		chatbox_scroll_up();
508 		break;
509 
510 	// the scroll down button
511 	case MP_SCROLL_DOWN :
512 		chatbox_scroll_down();
513 		break;
514 
515 	// the exit mission
516 	case MP_EXIT_MISSION :
517 		multi_quit_game(PROMPT_ALL);
518 		break;
519 	}
520 }
521 
522 // render the callsign of the guy who paused
multi_pause_render_callsign()523 void multi_pause_render_callsign()
524 {
525 	char pause_str[100];
526 
527 	// write out the callsign of the player who paused the game
528 	if((Multi_pause_pauser != NULL) && (Multi_pause_pauser->m_player != NULL)){
529 		memset(pause_str,0,100);
530 		strcpy_s(pause_str,Multi_pause_pauser->m_player->callsign);
531 
532 		// blit it
533 		gr_set_color_fast(&Color_bright);
534 		gr_string(Mp_callsign_coords[gr_screen.res][0], Mp_callsign_coords[gr_screen.res][1], pause_str, GR_RESIZE_MENU);
535 	}
536 }
537