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