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