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
14 #define POPUPDEAD_NUM_CHOICES 3 // normal
15 #define POPUPDEAD_NUM_CHOICES_RA 4 // red alert
16 #define POPUPDEAD_NUM_CHOICES_SKIP 3 // skip mission menu
17
18 #define POPUPDEAD_NUM_CHOICES_MAX 4
19
20 #include "freespace.h"
21 #include "gamesequence/gamesequence.h"
22 #include "gamesnd/gamesnd.h"
23 #include "globalincs/alphacolors.h"
24 #include "hud/hudmessage.h"
25 #include "io/key.h"
26 #include "io/timer.h"
27 #include "mission/missionparse.h"
28 #include "mission/missioncampaign.h"
29 #include "network/multi.h"
30 #include "network/multiutil.h"
31 #include "playerman/player.h"
32 #include "popup/popup.h"
33 #include "popup/popupdead.h"
34 #include "ui/ui.h"
35
36
37 UI_WINDOW Popupdead_window;
38 UI_BUTTON Popupdead_buttons[POPUPDEAD_NUM_CHOICES_MAX]; // actual lit buttons
39 UI_BUTTON Popupdead_button_regions[POPUPDEAD_NUM_CHOICES_MAX]; // fake buttons used for mouse detection over text
40
41 int Popupdead_region_coords[GR_NUM_RESOLUTIONS][POPUPDEAD_NUM_CHOICES_MAX][4] =
42 {
43 { // GR_640
44 {464, 389, 497, 403}, // upper right pixel of text, lower right pixel of button (for tiny popup)
45 {464, 413, 497, 427},
46 {464, 435, 497, 446},
47 {464, 457, 497, 466},
48 },
49 { // GR_1024
50 {745, 627, 809, 664},
51 {745, 663, 809, 700}, // upper right pixel of text, lower right pixel of button (for tiny popup)
52 {745, 699, 809, 736},
53 {745, 735, 809, 772},
54 },
55 };
56
57 int Popupdead_button_coords[GR_NUM_RESOLUTIONS][POPUPDEAD_NUM_CHOICES_MAX][2] =
58 {
59 { // GR_640
60 {478, 387}, // upper left pixel (tiny popup)
61 {478, 410},
62 {478, 432},
63 {478, 455},
64 },
65 { // GR_1024
66 {760, 620}, // upper left pixel (tiny popup)
67 {760, 656},
68 {760, 692},
69 {760, 728},
70 }
71 };
72
73 const char *Popupdead_background_filename[GR_NUM_RESOLUTIONS] = {
74 "PopDeath", // GR_640
75 "2_PopDeath" // GR-1024
76 };
77
78 int Popupdead_background_coords[GR_NUM_RESOLUTIONS][2] =
79 {
80 { // GR_640
81 131, 363
82 },
83 { // GR_1024
84 205, 581
85 }
86 };
87
88 const char *Popupdead_button_filenames[GR_NUM_RESOLUTIONS][POPUPDEAD_NUM_CHOICES_MAX] =
89 {
90 { // GR_640
91 "PopD_00", // first choice
92 "PopD_01", // second choice
93 "PopD_02", // third choice
94 "PopD_03", // fourth choice
95 },
96 { // GR_1024
97 "2_PopD_00", // first choice
98 "2_PopD_01", // second choice
99 "2_PopD_02", // third choice
100 "2_PopD_03", // fourth choice
101 }
102 };
103
104 int Popupdead_skip_message_y[GR_NUM_RESOLUTIONS] = {
105 96, // GR_640
106 160
107 };
108
109
110 static const char *Popupdead_button_text[POPUPDEAD_NUM_CHOICES_MAX];
111
112 // multiplayer specifics to help with return values since they can vary
113 #define POPUPDEAD_OBS_ONLY 1
114 #define POPUPDEAD_OBS_QUIT 2
115 #define POPUPDEAD_RESPAWN_ONLY 3
116 #define POPUPDEAD_RESPAWN_QUIT 4
117
118 int Popupdead_default_choice; // What the default choice is (ie activated when Enter pressed)
119 int Popupdead_active = 0; // A dead popup is active
120 int Popupdead_choice; // Index for choice picked (-1 if none picked)
121 int Popupdead_num_choices; // number of buttons
122 int Popupdead_multi_type; // what kind of popup is active for muliplayer
123 int Popupdead_skip_active = 0; // The skip-misison popup is active
124 int Popupdead_skip_already_shown = 0;
125
126 int Popupdead_timer;
127
128 extern int Cmdline_mpnoreturn;
129 // Initialize the dead popup data
popupdead_start()130 void popupdead_start()
131 {
132 int i;
133 UI_BUTTON *b;
134
135 if ( Popupdead_active ) {
136 return;
137 }
138
139 // increment number of deaths
140 Player->failures_this_session++;
141
142
143 // create base window
144 Popupdead_window.create(Popupdead_background_coords[gr_screen.res][0], Popupdead_background_coords[gr_screen.res][1], 1, 1, 0);
145 Popupdead_window.set_foreground_bmap(Popupdead_background_filename[gr_screen.res]);
146
147 Popupdead_num_choices = 0;
148 Popupdead_multi_type = -1;
149
150 if ((The_mission.max_respawn_delay >= 0) && ( Game_mode & GM_MULTIPLAYER )) {
151 Popupdead_timer = timestamp(The_mission.max_respawn_delay * 1000);
152 if (Game_mode & GM_MULTIPLAYER) {
153 if(!(Net_player->flags & NETINFO_FLAG_LIMBO)){
154 if (The_mission.max_respawn_delay) {
155 HUD_printf("Player will automatically respawn in %d seconds", The_mission.max_respawn_delay);
156 }
157 else {
158 HUD_printf("Player will automatically respawn now");
159 }
160 }
161 }
162 }
163
164 if ( Game_mode & GM_NORMAL ) {
165 // also do a campaign check here?
166 if (0) { //((Player->show_skip_popup) && (!Popupdead_skip_already_shown) && (Game_mode & GM_CAMPAIGN_MODE) && (Game_mode & GM_NORMAL) && (Player->failures_this_session >= PLAYER_MISSION_FAILURE_LIMIT)) {
167 // init the special preliminary death popup that gives the skip option
168 Popupdead_button_text[0] = XSTR( "Do Not Skip This Mission", 1473);
169 Popupdead_button_text[1] = XSTR( "Advance To The Next Mission", 1474);
170 Popupdead_button_text[2] = XSTR( "Don't Show Me This Again", 1475);
171 Popupdead_num_choices = POPUPDEAD_NUM_CHOICES_SKIP;
172 Popupdead_skip_active = 1;
173 } else if(The_mission.flags[Mission::Mission_Flags::Red_alert]) {
174 // We can't staticly declare these because they are externalized
175 Popupdead_button_text[0] = XSTR( "Quick Start Mission", 105);
176 Popupdead_button_text[1] = XSTR( "Return To Flight Deck", 106);
177 Popupdead_button_text[2] = XSTR( "Return To Briefing", 107);
178 Popupdead_button_text[3] = XSTR( "Replay previous mission", 1432);
179 Popupdead_num_choices = POPUPDEAD_NUM_CHOICES_RA;
180 } else {
181 Popupdead_button_text[0] = XSTR( "Quick Start Mission", 105);
182 Popupdead_button_text[1] = XSTR( "Return To Flight Deck", 106);
183 Popupdead_button_text[2] = XSTR( "Return To Briefing", 107);
184 Popupdead_num_choices = POPUPDEAD_NUM_CHOICES;
185 }
186 } else {
187 // in multiplayer, we have different choices depending on respawn mode, etc.
188
189 // if the player has run out of respawns and must either quit and become an observer
190 if(Net_player->flags & NETINFO_FLAG_LIMBO){
191
192 // the master should not be able to quit the game
193 if( ((Net_player->flags & NETINFO_FLAG_AM_MASTER) && (multi_num_players() > 1)) || (Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN) ) {
194 Popupdead_button_text[0] = XSTR( "Observer Mode", 108);
195 Popupdead_num_choices = 1;
196 Popupdead_multi_type = POPUPDEAD_OBS_ONLY;
197 } else {
198 Popupdead_button_text[0] = XSTR( "Observer Mode", 108);
199 Popupdead_button_text[1] = XSTR( "Return To Flight Deck", 106);
200 Popupdead_num_choices = 2;
201 Popupdead_multi_type = POPUPDEAD_OBS_QUIT;
202 }
203 } else {
204 // the master of the game should not be allowed to quit
205 if ( ((Net_player->flags & NETINFO_FLAG_AM_MASTER) && (multi_num_players() > 1)) || (Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN) ) {
206 Popupdead_button_text[0] = XSTR( "Respawn", 109);
207 Popupdead_num_choices = 1;
208 Popupdead_multi_type = POPUPDEAD_RESPAWN_ONLY;
209 } else {
210 Popupdead_button_text[0] = XSTR( "Respawn", 109);
211 if(!Cmdline_mpnoreturn)
212 {
213 Popupdead_button_text[1] = XSTR( "Return To Flight Deck", 106);
214 Popupdead_num_choices = 2;
215 }
216 else
217 {
218 Popupdead_num_choices = 1;
219 }
220 Popupdead_multi_type = POPUPDEAD_RESPAWN_QUIT;
221 }
222 }
223 }
224
225 // create buttons
226 for (i=0; i < Popupdead_num_choices; i++) {
227 b = &Popupdead_buttons[i];
228 b->create(&Popupdead_window, "", Popupdead_button_coords[gr_screen.res][i][0], Popupdead_button_coords[gr_screen.res][i][1], 30, 20, 0, 1);
229 b->set_bmaps(Popupdead_button_filenames[gr_screen.res][i], 3, 0);
230 b->set_highlight_action(common_play_highlight_sound);
231
232 // create invisible buttons to detect mouse presses... can't use mask since button region is dynamically sized
233 int lx, w, h;
234 gr_get_string_size(&w, &h, Popupdead_button_text[i]);
235 lx = Popupdead_region_coords[gr_screen.res][i][0] - w;
236 b = &Popupdead_button_regions[i];
237 b->create(&Popupdead_window, "", lx, Popupdead_region_coords[gr_screen.res][i][1], Popupdead_region_coords[gr_screen.res][i][2]-lx, Popupdead_region_coords[gr_screen.res][i][3]-Popupdead_region_coords[gr_screen.res][i][1], 0, 1);
238 b->hide();
239 }
240
241 io::mouse::CursorManager::get()->pushStatus();
242 io::mouse::CursorManager::get()->showCursor(true);
243
244 Popupdead_default_choice = 0;
245 Popupdead_choice = -1;
246 Popupdead_active = 1;
247 }
248
249 // maybe play a sound when key up/down is pressed to switch default choice
popupdead_play_default_change_sound()250 void popupdead_play_default_change_sound()
251 {
252 int i, mouse_over=0;
253 UI_BUTTON *br, *b;
254
255 // only play if mouse not currently highlighting a choice
256 for ( i = 0; i < Popupdead_num_choices; i++ ) {
257 br = &Popupdead_button_regions[i];
258 b = &Popupdead_buttons[i];
259 if ( br->button_down() ) {
260 mouse_over=1;
261 break;
262 }
263
264 if ( br->button_hilighted() && !b->button_down() ) {
265 mouse_over=1;
266 break;
267 }
268
269 if ( b->button_hilighted() ) {
270 mouse_over=1;
271 }
272 }
273
274 if (!mouse_over) {
275 gamesnd_play_iface(InterfaceSounds::USER_SELECT);
276 }
277 }
278
279 // do any key processing here
280 // exit: -1 => nothing was done
281 // >=0 => a choice was selected
popupdead_process_keys(int k)282 int popupdead_process_keys(int k)
283 {
284 int masked_k;
285
286 if ( k <= 0 ) {
287 return -1;
288 }
289
290 switch(k) {
291
292 case KEY_ENTER:
293 return Popupdead_default_choice; // select the current default choice
294 break;
295
296 case KEY_ESC:
297 if (Popupdead_skip_active) {
298 return 0; // 0 mimics a "do not skip"
299 } else {
300 return 1; // do nothing here for now - 1 mimics a "return to flight deck"
301 }
302 break;
303
304 case KEY_DOWN:
305 case KEY_PAD2:
306 case KEY_TAB:
307 popupdead_play_default_change_sound();
308 Popupdead_default_choice++;
309 if ( Popupdead_default_choice >= Popupdead_num_choices ) {
310 Popupdead_default_choice=0;
311 }
312 break;
313
314 case KEY_UP:
315 case KEY_PAD8:
316 case KEY_SHIFTED+KEY_TAB:
317 popupdead_play_default_change_sound();
318 Popupdead_default_choice--;
319 if ( Popupdead_default_choice < 0 ) {
320 Popupdead_default_choice=Popupdead_num_choices-1;
321 }
322 break;
323
324 case KEY_PAUSE:
325 game_process_pause_key();
326 break;
327
328 default:
329 break;
330 } // end switch
331
332 // read the dead key set
333 masked_k = k & ~KEY_CTRLED; // take out CTRL modifier only
334 process_set_of_keys(masked_k, Dead_key_set_size, Dead_key_set);
335 button_info_do(&Player->bi); // call functions based on status of button_info bit vectors
336
337 return -1;
338 }
339
340
341 // see if any popup buttons have been pressed
342 // exit: -1 => no buttons pressed
343 // >=0 => button index that was pressed
popupdead_check_buttons()344 int popupdead_check_buttons()
345 {
346 int i;
347 UI_BUTTON *b;
348
349 for ( i = 0; i < Popupdead_num_choices; i++ ) {
350 b = &Popupdead_button_regions[i];
351 if ( b->pressed() ) {
352 return i;
353 }
354
355 b = &Popupdead_buttons[i];
356 if ( b->pressed() ) {
357 return i;
358 }
359 }
360
361 return -1;
362 }
363
364 // See if any of the button should change appearance based on mouse position
popupdead_force_draw_buttons()365 void popupdead_force_draw_buttons()
366 {
367 int i,mouse_is_highlighting=0;
368 UI_BUTTON *br, *b;
369
370 for ( i = 0; i < Popupdead_num_choices; i++ ) {
371 br = &Popupdead_button_regions[i];
372 b = &Popupdead_buttons[i];
373 if ( br->button_down() ) {
374 b->draw_forced(2);
375 mouse_is_highlighting=1;
376 continue;
377 }
378
379 if ( (b->button_hilighted()) || (br->button_hilighted() && !b->button_down()) ) {
380 Popupdead_default_choice=i;
381 mouse_is_highlighting=1;
382 b->draw_forced(1);
383 }
384 }
385
386 // Only if mouse is not highlighting an option, let the default choice be drawn highlighted
387 if ( (!mouse_is_highlighting) && (Popupdead_num_choices>1) ) {
388 for ( i = 0; i < Popupdead_num_choices; i++ ) {
389 b = &Popupdead_buttons[i];
390 // highlight the default choice
391 if ( i == Popupdead_default_choice ) {
392 b->draw_forced(1);
393 }
394 }
395 }
396 }
397
398 // Draw the button text nicely formatted in the popup
popupdead_draw_button_text()399 void popupdead_draw_button_text()
400 {
401 int w,h,i,sx,sy;
402
403 gr_set_color_fast(&Color_bright_blue);
404
405 for ( i=0; i < Popupdead_num_choices; i++ ) {
406 gr_get_string_size(&w, &h, Popupdead_button_text[i]);
407 sx = Popupdead_region_coords[gr_screen.res][i][0]-w;
408 sy = Popupdead_region_coords[gr_screen.res][i][1]+4;
409 gr_string(sx, sy, Popupdead_button_text[i], GR_RESIZE_MENU);
410 }
411 }
412
413 // Called once per frame to run the dead popup
popupdead_do_frame(float)414 int popupdead_do_frame(float /*frametime*/)
415 {
416 int k, choice = -1;
417
418 if ( !Popupdead_active ) {
419 return -1;
420 }
421
422 // maybe show skip mission popup
423 if ((!Popupdead_skip_already_shown) && (Player->show_skip_popup) && (Game_mode & GM_NORMAL) && (Game_mode & GM_CAMPAIGN_MODE) && (Player->failures_this_session >= PLAYER_MISSION_FAILURE_LIMIT)) {
424 int popup_choice = popup(0, 3, XSTR("Do Not Skip This Mission", 1473),
425 XSTR("Advance To The Next Mission", 1474),
426 XSTR("Don't Show Me This Again", 1475),
427 XSTR("You have failed this mission five times. If you like, you may advance to the next mission.", 1472) );
428 switch (popup_choice) {
429 case 0:
430 // stay on this mission, so proceed to normal death popup
431 // in other words, do nothing.
432 break;
433 case 1:
434 // skip this mission
435 Popupdead_active = 0;
436 mission_campaign_skip_to_next();
437 gameseq_post_event(GS_EVENT_START_GAME);
438 return -1;
439 case 2:
440 // don't show this again
441 Player->show_skip_popup = 0;
442 break;
443 }
444
445 Popupdead_skip_already_shown = 1;
446 }
447
448 // don't process keys/buttons if another popup is active
449 if ( !popup_active() ) {
450 k = Popupdead_window.process();
451
452 choice = popupdead_process_keys(k);
453
454 if (choice < 0) {
455 choice = popupdead_check_buttons();
456 }
457
458 if ( choice >= 0 ) {
459 // do something different for single/multiplayer
460 if ( Game_mode & GM_NORMAL ) {
461 Popupdead_choice=choice;
462 } else {
463 Assert( Popupdead_multi_type != -1 );
464 switch ( Popupdead_multi_type ) {
465
466 case POPUPDEAD_OBS_ONLY:
467 case POPUPDEAD_OBS_QUIT:
468 Popupdead_choice = POPUPDEAD_DO_OBSERVER;
469 if ( (Popupdead_multi_type == POPUPDEAD_OBS_QUIT) && (choice == 1) )
470 Popupdead_choice = POPUPDEAD_DO_MAIN_HALL;
471 break;
472
473 case POPUPDEAD_RESPAWN_ONLY:
474 case POPUPDEAD_RESPAWN_QUIT:
475 Popupdead_choice = POPUPDEAD_DO_RESPAWN;
476 if ( (Popupdead_multi_type == POPUPDEAD_RESPAWN_QUIT) && (choice == 1) )
477 Popupdead_choice = POPUPDEAD_DO_MAIN_HALL;
478 break;
479
480 default:
481 UNREACHABLE("Invalid or unknown popupdead multi type!");
482 break;
483 }
484 }
485 }
486 }
487
488 Popupdead_window.draw();
489 popupdead_force_draw_buttons();
490 popupdead_draw_button_text();
491
492 // maybe force the player to respawn if they've taken too long to choose
493 if (( Game_mode & GM_MULTIPLAYER ) && (The_mission.max_respawn_delay >= 0) && (timestamp_elapsed(Popupdead_timer)) && (choice < 0)) {
494 if (( Popupdead_multi_type == POPUPDEAD_RESPAWN_ONLY) || ( Popupdead_multi_type == POPUPDEAD_RESPAWN_QUIT)) {
495 Popupdead_choice = POPUPDEAD_DO_RESPAWN;
496 }
497 }
498
499 return Popupdead_choice;
500 }
501
502 // Close down the dead popup
popupdead_close()503 void popupdead_close()
504 {
505 if ( !Popupdead_active ) {
506 return;
507 }
508
509 gamesnd_play_iface(InterfaceSounds::POPUP_DISAPPEAR);
510 Popupdead_window.destroy();
511 game_flush();
512
513 io::mouse::CursorManager::get()->popStatus();
514
515 Popupdead_active = 0;
516 Popupdead_skip_active = 0;
517 Popupdead_skip_already_shown = 0;
518 }
519
520 // Is there a dead popup active?
popupdead_is_active()521 int popupdead_is_active()
522 {
523 return Popupdead_active;
524 }
525