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 #include <climits>
12 #include <cctype>
13
14 #include "freespace.h"
15 #include "cfile/cfile.h"
16 #include "cmdline/cmdline.h"
17 #include "debugconsole/console.h"
18 #include "gamesequence/gamesequence.h"
19 #include "gamesnd/gamesnd.h"
20 #include "globalincs/alphacolors.h"
21 #include "globalincs/version.h"
22 #include "io/key.h"
23 #include "localization/localize.h"
24 #include "menuui/mainhallmenu.h"
25 #include "menuui/playermenu.h"
26 #include "mission/missioncampaign.h"
27 #include "mod_table/mod_table.h"
28 #include "network/multi.h"
29 #include "osapi/osregistry.h"
30 #include "parse/parselo.h"
31 #include "pilotfile/pilotfile.h"
32 #include "playerman/managepilot.h"
33 #include "playerman/player.h"
34 #include "popup/popup.h"
35 #include "ui/ui.h"
36
37 #include "playermenu.h"
38 #include "graphics/paths/PathRenderer.h"
39
40 // --------------------------------------------------------------------------------------------------------
41 // PLAYER SELECT defines
42 //
43
44 int Max_lines; //Max number of pilots displayed in Window. Gets set in player_select_draw_list()
45
46 // button control defines
47 #define NUM_PLAYER_SELECT_BUTTONS 8 // button control defines
48
49 #define CREATE_PILOT_BUTTON 0 //
50 #define CLONE_BUTTON 1 //
51 #define DELETE_BUTTON 2 //
52 #define SCROLL_LIST_UP_BUTTON 3 //
53 #define SCROLL_LIST_DOWN_BUTTON 4 //
54 #define ACCEPT_BUTTON 5 //
55 #define SINGLE_BUTTON 6 //
56 #define MULTI_BUTTON 7 //
57
58 // list text display area
59 int Choose_list_coords[GR_NUM_RESOLUTIONS][4] = {
60 { // GR_640
61 114, 117, 400, 87
62 },
63 { // GR_1024
64 183, 186, 640, 139
65 }
66 };
67
68 const char *Player_select_background_bitmap_name[GR_NUM_RESOLUTIONS] = {
69 "ChoosePilot",
70 "2_ChoosePilot"
71 };
72 const char *Player_select_background_mask_bitmap[GR_NUM_RESOLUTIONS] = {
73 "ChoosePilot-m",
74 "2_ChoosePilot-m"
75 };
76 // #define PLAYER_SELECT_PALETTE NOX("ChoosePilotPalette") // palette for the screen
77
78 #define PLAYER_SELECT_MAIN_HALL_OVERLAY NOX("MainHall1") // main hall help overlay
79
80 // convenient struct for handling all button controls
81 struct barracks_buttons {
82 const char *filename;
83 int x, y, xt, yt;
84 int hotspot;
85 UI_BUTTON button; // because we have a class inside this struct, we need the constructor below..
86
barracks_buttonsbarracks_buttons87 barracks_buttons(const char *name, int x1, int y1, int xt1, int yt1, int h) : filename(name), x(x1), y(y1), xt(xt1), yt(yt1), hotspot(h) {}
88 };
89
90 static barracks_buttons Player_select_buttons[GR_NUM_RESOLUTIONS][NUM_PLAYER_SELECT_BUTTONS] = {
91 { // GR_640
92 // create, clone and delete (respectively)
93 barracks_buttons("CPB_00", 114, 205, 117, 240, 0),
94 barracks_buttons("CPB_01", 172, 205, 175, 240, 1),
95 barracks_buttons("CPB_02", 226, 205, 229, 240, 2),
96
97 // scroll up, scroll down, and accept (respectively)
98 barracks_buttons("CPB_03", 429, 213, -1, -1, 3),
99 barracks_buttons("CPB_04", 456, 213, -1, -1, 4),
100 barracks_buttons("CPB_05", 481, 207, 484, 246, 5),
101
102 // single player select and multiplayer select, respectively
103 barracks_buttons("CPB_06", 428, 82, 430, 108, 6),
104 barracks_buttons("CPB_07", 477, 82, 481, 108, 7)
105 },
106 { // GR_1024
107 // create, clone and delete (respectively)
108 barracks_buttons("2_CPB_00", 182, 328, 199, 384, 0),
109 barracks_buttons("2_CPB_01", 275, 328, 292, 384, 1),
110 barracks_buttons("2_CPB_02", 361, 328, 379, 384, 2),
111
112 // scroll up, scroll down, and accept (respectively)
113 barracks_buttons("2_CPB_03", 686, 341, -1, -1, 3),
114 barracks_buttons("2_CPB_04", 729, 341, -1, -1, 4),
115 barracks_buttons("2_CPB_05", 770, 332, 787, 394, 5),
116
117 // single player select and multiplayer select, respectively
118 barracks_buttons("2_CPB_06", 685, 132, 700, 173, 6),
119 barracks_buttons("2_CPB_07", 764, 132, 782, 173, 7)
120 }
121 };
122
123 // FIXME add to strings.tbl
124 #define PLAYER_SELECT_NUM_TEXT 1
125 UI_XSTR Player_select_text[GR_NUM_RESOLUTIONS][PLAYER_SELECT_NUM_TEXT] = {
126 { // GR_640
127 { "Choose Pilot", 1436, 122, 90, UI_XSTR_COLOR_GREEN, -1, NULL }
128 },
129 { // GR_1024
130 { "Choose Pilot", 1436, 195, 143, UI_XSTR_COLOR_GREEN, -1, NULL }
131 }
132 };
133
134 UI_WINDOW Player_select_window; // ui window for this screen
135 UI_BUTTON Player_select_list_region; // button for detecting mouse clicks on this screen
136 UI_INPUTBOX Player_select_input_box; // input box for adding new pilot names
137
138 // #define PLAYER_SELECT_PALETTE_FNAME NOX("InterfacePalette")
139 int Player_select_background_bitmap; // bitmap for this screen
140 // int Player_select_palette; // palette bitmap for this screen
141 int Player_select_autoaccept = 0;
142 // int Player_select_palette_set = 0;
143
144 // flag indicating if this is the absolute first pilot created and selected. Used to determine
145 // if the main hall should display the help overlay screen
146 int Player_select_very_first_pilot = 0;
147 int Player_select_initial_count = 0;
148 char Player_select_very_first_pilot_callsign[CALLSIGN_LEN + 2];
149
150 extern int Main_hall_bitmap; // bitmap handle to the main hall bitmap
151
152 int Player_select_mode; // single or multiplayer - never set directly. use player_select_init_player_stuff()
153 int Player_select_num_pilots; // # of pilots on the list
154 int Player_select_list_start; // index of first list item to start displaying in the box
155 int Player_select_pilot; // index into the Pilot array of which is selected as the active pilot
156 int Player_select_input_mode; // 0 if the player _isn't_ typing a callsign, 1 if he is
157 char Pilots_arr[MAX_PILOTS][MAX_FILENAME_LEN];
158 char *Pilots[MAX_PILOTS];
159 int Player_select_clone_flag; // clone the currently selected pilot
160 char Player_select_last_pilot[CALLSIGN_LEN + 10]; // callsign of the last used pilot, or none if there wasn't one
161 int Player_select_last_is_multi;
162
163 SCP_string Player_select_force_main_hall = "";
164
165 static int Player_select_no_save_pilot = 0; // to skip save of pilot in pilot_select_close()
166
167 int Player_select_screen_active = 0; // for pilot savefile loading - taylor
168
169 // notification text areas
170
171 static int Player_select_bottom_text_y[GR_NUM_RESOLUTIONS] = {
172 314, // GR_640
173 502 // GR_1024
174 };
175
176 static int Player_select_middle_text_y[GR_NUM_RESOLUTIONS] = {
177 253, // GR_640
178 404 // GR_1024
179 };
180
181 char Player_select_bottom_text[150] = "";
182 char Player_select_middle_text[150] = "";
183
184
185 // FORWARD DECLARATIONS
186 void player_select_init_player_stuff(int mode); // switch between single and multiplayer modes
187 void player_select_set_input_mode(int n);
188 void player_select_button_pressed(int n);
189 void player_select_scroll_list_up();
190 void player_select_scroll_list_down();
191 int player_select_create_new_pilot();
192 void player_select_delete_pilot();
193 void player_select_display_all_text();
194 void player_select_display_copyright();
195 void player_select_set_bottom_text(const char *txt);
196 void player_select_set_middle_text(const char *txt);
197 void player_select_set_controls(int gray);
198 void player_select_draw_list();
199 void player_select_process_noninput(int k);
200 void player_select_process_input(int k);
201 int player_select_pilot_file_filter(const char *filename);
202 int player_select_get_last_pilot_info();
203 void player_select_eval_very_first_pilot();
204 void player_select_commit();
205 void player_select_cancel_create();
206
207 /*
208 * validate that a pilot/player was created with the same language FSO is currently using
209 *
210 * @param pilots callsign
211 * @note not longer needed if intel entry "primary keys" change to a non-translated value
212 */
valid_pilot_lang(const char * callsign)213 bool valid_pilot_lang(const char *callsign)
214 {
215 char pilot_lang[LCL_LANG_NAME_LEN+1], current_lang[LCL_LANG_NAME_LEN+1];
216 SCP_string filename = callsign;
217
218 filename += ".json";
219 lcl_get_language_name(current_lang);
220
221 if (Pilot.verify(filename.c_str(), NULL, pilot_lang)) {
222 if (!strcmp(current_lang, pilot_lang)) {
223 return true;
224 }
225 }
226 return false;
227 }
228
229 // basically, gray out all controls (gray == 1), or ungray the controls (gray == 0)
player_select_set_controls(int gray)230 void player_select_set_controls(int gray)
231 {
232 int idx;
233
234 for(idx=0;idx<NUM_PLAYER_SELECT_BUTTONS;idx++) {
235 if(gray) {
236 Player_select_buttons[gr_screen.res][idx].button.disable();
237 } else {
238 Player_select_buttons[gr_screen.res][idx].button.enable();
239 }
240 }
241 }
242
243 // functions for selecting single/multiplayer pilots at the very beginning of FreeSpace
player_select_init()244 void player_select_init()
245 {
246 int i;
247 barracks_buttons *b;
248 UI_WINDOW *w;
249
250 // start a looping ambient sound
251 main_hall_start_ambient();
252
253 Player_select_force_main_hall = "";
254
255 Player_select_screen_active = 1;
256
257 // create the UI window
258 Player_select_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
259 Player_select_window.set_mask_bmap(Player_select_background_mask_bitmap[gr_screen.res]);
260
261 // initialize the control buttons
262 for (i=0; i<NUM_PLAYER_SELECT_BUTTONS; i++) {
263 b = &Player_select_buttons[gr_screen.res][i];
264
265 // create the button
266 b->button.create(&Player_select_window, NULL, b->x, b->y, 60, 30, 1, 1);
267
268 // set its highlight action
269 b->button.set_highlight_action(common_play_highlight_sound);
270
271 // set its animation bitmaps
272 b->button.set_bmaps(b->filename);
273
274 // link the mask hotspot
275 b->button.link_hotspot(b->hotspot);
276 }
277
278 // add some text
279 w = &Player_select_window;
280 w->add_XSTR("Create", 1034, Player_select_buttons[gr_screen.res][CREATE_PILOT_BUTTON].xt, Player_select_buttons[gr_screen.res][CREATE_PILOT_BUTTON].yt, &Player_select_buttons[gr_screen.res][CREATE_PILOT_BUTTON].button, UI_XSTR_COLOR_GREEN);
281 w->add_XSTR("Clone", 1040, Player_select_buttons[gr_screen.res][CLONE_BUTTON].xt, Player_select_buttons[gr_screen.res][CLONE_BUTTON].yt, &Player_select_buttons[gr_screen.res][CLONE_BUTTON].button, UI_XSTR_COLOR_GREEN);
282 w->add_XSTR("Remove", 1038, Player_select_buttons[gr_screen.res][DELETE_BUTTON].xt, Player_select_buttons[gr_screen.res][DELETE_BUTTON].yt, &Player_select_buttons[gr_screen.res][DELETE_BUTTON].button, UI_XSTR_COLOR_GREEN);
283
284 w->add_XSTR("Select", 1039, Player_select_buttons[gr_screen.res][ACCEPT_BUTTON].xt, Player_select_buttons[gr_screen.res][ACCEPT_BUTTON].yt, &Player_select_buttons[gr_screen.res][ACCEPT_BUTTON].button, UI_XSTR_COLOR_PINK);
285 w->add_XSTR("Single", 1041, Player_select_buttons[gr_screen.res][SINGLE_BUTTON].xt, Player_select_buttons[gr_screen.res][SINGLE_BUTTON].yt, &Player_select_buttons[gr_screen.res][SINGLE_BUTTON].button, UI_XSTR_COLOR_GREEN);
286 w->add_XSTR("Multi", 1042, Player_select_buttons[gr_screen.res][MULTI_BUTTON].xt, Player_select_buttons[gr_screen.res][MULTI_BUTTON].yt, &Player_select_buttons[gr_screen.res][MULTI_BUTTON].button, UI_XSTR_COLOR_GREEN);
287 for(i=0; i<PLAYER_SELECT_NUM_TEXT; i++) {
288 w->add_XSTR(&Player_select_text[gr_screen.res][i]);
289 }
290
291
292 // create the list button text select region
293 Player_select_list_region.create(&Player_select_window, "", Choose_list_coords[gr_screen.res][0], Choose_list_coords[gr_screen.res][1], Choose_list_coords[gr_screen.res][2], Choose_list_coords[gr_screen.res][3], 0, 1);
294 Player_select_list_region.hide();
295
296 // create the pilot callsign input box
297 Player_select_input_box.create(&Player_select_window, Choose_list_coords[gr_screen.res][0], Choose_list_coords[gr_screen.res][1], Choose_list_coords[gr_screen.res][2] , CALLSIGN_LEN - 1, "", UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_LETTER_FIRST);
298 Player_select_input_box.set_valid_chars(VALID_PILOT_CHARS);
299 Player_select_input_box.hide();
300 Player_select_input_box.disable();
301
302 // not currently entering any text
303 Player_select_input_mode = 0;
304
305 // set up hotkeys for buttons so we draw the correct animation frame when a key is pressed
306 Player_select_buttons[gr_screen.res][SCROLL_LIST_UP_BUTTON].button.set_hotkey(KEY_UP);
307 Player_select_buttons[gr_screen.res][SCROLL_LIST_DOWN_BUTTON].button.set_hotkey(KEY_DOWN);
308 Player_select_buttons[gr_screen.res][ACCEPT_BUTTON].button.set_hotkey(KEY_ENTER);
309 Player_select_buttons[gr_screen.res][CREATE_PILOT_BUTTON].button.set_hotkey(KEY_C);
310
311 // attempt to load in the background bitmap
312 Player_select_background_bitmap = bm_load(Player_select_background_bitmap_name[gr_screen.res]);
313 Assert(Player_select_background_bitmap >= 0);
314
315 // load in the palette for the screen
316 // Player_select_palette = bm_load(PLAYER_SELECT_PALETTE);
317 // Player_select_palette_set = 0;
318
319 // unset the very first pilot data
320 Player_select_very_first_pilot = 0;
321 Player_select_initial_count = -1;
322 memset(Player_select_very_first_pilot_callsign, 0, CALLSIGN_LEN + 2);
323
324 // if(Player_select_num_pilots == 0){
325 // Player_select_autoaccept = 1;
326 // }
327
328 // if we found a pilot
329 if ( player_select_get_last_pilot_info() ) {
330 if (Player_select_last_is_multi) {
331 player_select_init_player_stuff(PLAYER_SELECT_MODE_MULTI);
332 } else {
333 player_select_init_player_stuff(PLAYER_SELECT_MODE_SINGLE);
334 }
335 } else { // otherwise go to the single player mode by default
336 player_select_init_player_stuff(PLAYER_SELECT_MODE_SINGLE);
337 }
338
339 if (Cmdline_benchmark_mode || ((Player_select_num_pilots == 1) && Player_select_input_mode)) {
340 // When benchmarking, just accept automatically
341 Player_select_autoaccept = 1;
342 }
343 }
344
345 // no need to reset this to false because we only ever see player_select once per game run
346 static bool Startup_warning_dialog_displayed = false;
347
player_select_do()348 void player_select_do()
349 {
350 int k;
351
352 // Goober5000 - display a popup warning about problems in the mod
353 if ((Global_warning_count > 10 || Global_error_count > 0) && !Startup_warning_dialog_displayed) {
354 char text[512];
355 sprintf(text, XSTR ("Warning!\n\nThe currently active mod has generated %d warnings and/or errors during program startup. These could have been caused by anything from incorrectly formated table files to corrupt models.\n\nWhile FreeSpace Open will attempt to compensate for these issues, it cannot guarantee a trouble-free gameplay experience.\n\nPlease contact the authors of the mod for assistance.", 1640), Global_warning_count + Global_error_count);
356 popup(PF_TITLE_BIG | PF_TITLE_RED | PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, text);
357 Startup_warning_dialog_displayed = true;
358 }
359
360 // set the input box at the "virtual" line 0 to be active so the player can enter a callsign
361 if (Player_select_input_mode) {
362 Player_select_input_box.set_focus();
363 }
364
365 // process any ui window stuff
366 k = Player_select_window.process();
367
368 if (k) {
369 extern void game_process_cheats(int k);
370 game_process_cheats(k);
371 }
372
373 switch (k) {
374 // switch between single and multiplayer modes
375 case KEY_TAB: {
376 if (Player_select_input_mode) {
377 gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
378 break;
379 }
380
381 // play a little sound
382 gamesnd_play_iface(InterfaceSounds::USER_SELECT);
383
384 if (Player_select_mode == PLAYER_SELECT_MODE_MULTI) {
385 player_select_set_bottom_text(XSTR( "Single-Player Mode", 376));
386
387 // reinitialize as single player mode
388 player_select_init_player_stuff(PLAYER_SELECT_MODE_SINGLE);
389 } else if (Player_select_mode == PLAYER_SELECT_MODE_SINGLE) {
390 player_select_set_bottom_text(XSTR( "Multiplayer Mode", 377));
391
392 // reinitialize as multiplayer mode
393 player_select_init_player_stuff(PLAYER_SELECT_MODE_MULTI);
394 }
395
396 break;
397 }
398
399 case KEY_ESC: {
400 // we can hit ESC to get out of text input mode, and we don't want
401 // to set this var in that case since it will crash on a NULL Player
402 // ptr when going to the mainhall
403 if ( !Player_select_input_mode ) {
404 Player_select_no_save_pilot = 1;
405 }
406
407 break;
408 }
409 }
410
411 // draw the player select pseudo-dialog over it
412 GR_MAYBE_CLEAR_RES(Player_select_background_bitmap);
413 gr_set_bitmap(Player_select_background_bitmap);
414 gr_bitmap(0,0,GR_RESIZE_MENU);
415
416 //skip this if pilot is given through cmdline, assuming single-player
417 if (Cmdline_pilot) {
418 player_finish_select(Cmdline_pilot, false);
419 return;
420 }
421
422 // press the accept button
423 if (Player_select_autoaccept) {
424 Player_select_buttons[gr_screen.res][ACCEPT_BUTTON].button.press_button();
425 }
426
427 // draw any ui window stuf
428 Player_select_window.draw();
429
430 // light up the correct mode button (single or multi)
431 if (Player_select_mode == PLAYER_SELECT_MODE_SINGLE) {
432 Player_select_buttons[gr_screen.res][SINGLE_BUTTON].button.draw_forced(2);
433 } else {
434 Player_select_buttons[gr_screen.res][MULTI_BUTTON].button.draw_forced(2);
435 }
436
437 // draw the pilot list text
438 player_select_draw_list();
439
440 // draw copyright message on the bottom on the screen
441 player_select_display_copyright();
442
443 if (!Player_select_input_mode) {
444 player_select_process_noninput(k);
445 } else {
446 player_select_process_input(k);
447 }
448
449 // draw any pending messages on the bottom or middle of the screen
450 player_select_display_all_text();
451
452 gr_flip();
453 }
454
player_select_close()455 void player_select_close()
456 {
457 // destroy the player select window
458 Player_select_window.destroy();
459
460 // if we're in input mode - we should undo the pilot create reqeust
461 if(Player_select_input_mode) {
462 player_select_cancel_create();
463 }
464
465 // if we are just exiting then don't try to save any pilot files - taylor
466 if (Player_select_no_save_pilot) {
467 Player = NULL;
468 return;
469 }
470
471 // actually set up the Player struct here
472 if ( (Player_select_pilot == -1) || (Player_select_num_pilots == 0) ) {
473 nprintf(("General","WARNING! No pilot selected! We should be exiting the game now!\n"));
474 return;
475 }
476
477 // unload all bitmaps
478 if(Player_select_background_bitmap >= 0) {
479 bm_release(Player_select_background_bitmap);
480 Player_select_background_bitmap = -1;
481 }
482 // if(Player_select_palette >= 0){
483 // bm_release(Player_select_palette);
484 //Player_select_palette = -1;GS_EVENT_MAIN_MENU
485 // }
486
487 // setup the player struct
488 Player_num = 0;
489 Player = &Players[0];
490 Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
491
492 // New pilot file makes no distinction between multi pilots and regular ones, so let's do this here.
493 if (Player_select_mode == PLAYER_SELECT_MODE_MULTI) {
494 Player->flags |= PLAYER_FLAGS_IS_MULTI;
495 }
496
497 // WMC - Set appropriate game mode
498 if ( Player->flags & PLAYER_FLAGS_IS_MULTI ) {
499 Game_mode = GM_MULTIPLAYER;
500 } else {
501 Game_mode = GM_NORMAL;
502 }
503
504 // now read in a the pilot data
505 if ( !Pilot.load_player(Pilots[Player_select_pilot], Player) ) {
506 Error(LOCATION,"Couldn't load pilot file, bailing");
507 Player = NULL;
508 } else {
509 // NOTE: this may fail if there is no current campaign, it's not fatal
510 Pilot.load_savefile(Player, Player->current_campaign);
511 }
512
513 if (Player_select_mode == PLAYER_SELECT_MODE_MULTI) {
514 Player->player_was_multi = 1;
515 } else {
516 Player->player_was_multi = 0;
517 }
518
519 os_config_write_string(nullptr, "LastPlayer", Player->callsign);
520
521 if (Player_select_force_main_hall != "") {
522 main_hall_init(Player_select_force_main_hall);
523 }
524
525 // free memory from all parsing so far, all tbls found during game_init()
526 // and the current campaign which we loaded here
527 stop_parse();
528
529 Player_select_screen_active = 0;
530 }
531
player_select_set_input_mode(int n)532 void player_select_set_input_mode(int n)
533 {
534 int i;
535
536 // set the input mode
537 Player_select_input_mode = n;
538
539 // enable all the player select buttons
540 for (i=0; i<NUM_PLAYER_SELECT_BUTTONS; i++) {
541 Player_select_buttons[gr_screen.res][i].button.enable(!n);
542 }
543
544 Player_select_buttons[gr_screen.res][ACCEPT_BUTTON].button.set_hotkey(n ? -1 : KEY_ENTER);
545 Player_select_buttons[gr_screen.res][CREATE_PILOT_BUTTON].button.set_hotkey(n ? -1 : KEY_C);
546
547 // enable the player select input box
548 if (Player_select_input_mode) {
549 Player_select_input_box.enable();
550 Player_select_input_box.unhide();
551 } else {
552 Player_select_input_box.hide();
553 Player_select_input_box.disable();
554 }
555 }
556
player_select_button_pressed(int n)557 void player_select_button_pressed(int n)
558 {
559 int ret;
560
561 switch (n) {
562 case SCROLL_LIST_UP_BUTTON:
563 player_select_set_bottom_text("");
564
565 player_select_scroll_list_up();
566 break;
567
568 case SCROLL_LIST_DOWN_BUTTON:
569 player_select_set_bottom_text("");
570
571 player_select_scroll_list_down();
572 break;
573
574 case ACCEPT_BUTTON:
575 // make sure he has a valid pilot selected
576 if (Player_select_pilot < 0) {
577 popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR( "You must select a valid pilot first", 378));
578 } else {
579 if (valid_pilot_lang(Pilots[Player_select_pilot])) {
580 player_select_commit();
581 } else {
582 popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR(
583 "Selected pilot was created with a different language\n"
584 "to the currently active language.\n\n"
585 "Please select a different pilot or change the language", 1637));
586 }
587 }
588 break;
589
590 case CLONE_BUTTON:
591 // if we're at max-pilots, don't allow another to be added
592 if (Player_select_num_pilots >= MAX_PILOTS) {
593 player_select_set_bottom_text(XSTR( "You already have the maximum # of pilots!", 379));
594
595 gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
596 break;
597 }
598
599 if (Player_select_pilot >= 0) {
600 // first we have to make sure this guy is actually loaded for when we create the clone
601 if (Player == NULL) {
602 Player = &Players[0];
603 Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
604 }
605
606 // attempt to read in the pilot file of the guy to be cloned
607 if ( !Pilot.load_player(Pilots[Player_select_pilot], Player) ) {
608 Error(LOCATION,"Couldn't load pilot file, bailing");
609 Player = NULL;
610 Int3();
611 }
612
613 // set the clone flag
614 Player_select_clone_flag = 1;
615
616 // create the new pilot (will be cloned with Player_select_clone_flag_set)
617 if ( !player_select_create_new_pilot() ) {
618 player_select_set_bottom_text(XSTR( "Error creating new pilot file!", 380));
619 Player_select_clone_flag = 0;
620 Player->reset();
621 Player = NULL;
622 break;
623 }
624
625 // display some text on the bottom of the dialog
626 player_select_set_bottom_text(XSTR( "Type Callsign and Press Enter", 381));
627
628 // gray out all controls in the dialog
629 player_select_set_controls(1);
630 }
631 break;
632
633 case CREATE_PILOT_BUTTON:
634 // if we're at max-pilots, don't allow another to be added
635 if(Player_select_num_pilots >= MAX_PILOTS) {
636 player_select_set_bottom_text(XSTR( "You already have the maximum # of pilots!", 379));
637
638 gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
639 break;
640 }
641
642 // create a new pilot
643 if ( !player_select_create_new_pilot() ) {
644 player_select_set_bottom_text(XSTR( "Type Callsign and Press Enter", 381));
645 }
646
647 // don't clone anyone
648 Player_select_clone_flag = 0;
649
650 // display some text on the bottom of the dialog
651 player_select_set_bottom_text(XSTR( "Type Callsign and Press Enter", 381));
652
653 // gray out all controls
654 player_select_set_controls(1);
655 break;
656
657 case DELETE_BUTTON:
658 player_select_set_bottom_text("");
659
660 if (Player_select_pilot >= 0) {
661 if (Player_select_mode == PLAYER_SELECT_MODE_MULTI) {
662 popup(PF_TITLE_BIG | PF_TITLE_RED | PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("Disabled!\n\nMulti and single player pilots are now identical. "
663 "Deleting a multi-player pilot will also delete all single-player data for that pilot.\n\nAs a safety precaution, pilots can only be "
664 "deleted from the single-player menu.", 1598));
665 } else {
666 // display a popup requesting confirmation
667 ret = popup(PF_TITLE_BIG | PF_TITLE_RED, 2, POPUP_NO, POPUP_YES, XSTR( "Warning!\n\nAre you sure you wish to delete this pilot?", 382));
668
669 // delete the pilot
670 if (ret == 1) {
671 player_select_delete_pilot();
672 }
673 }
674 }
675 break;
676
677 case SINGLE_BUTTON:
678 Player_select_autoaccept = 0;
679 // switch to single player mode
680 if (Player_select_mode != PLAYER_SELECT_MODE_SINGLE) {
681 // play a little sound
682 gamesnd_play_iface(InterfaceSounds::USER_SELECT);
683
684 // Only do this when changing modes, keeps bottom text from being empty by accident
685 player_select_set_bottom_text("");
686
687 player_select_set_bottom_text(XSTR( "Single-Player Mode", 376));
688
689 // reinitialize as single player mode
690 player_select_init_player_stuff(PLAYER_SELECT_MODE_SINGLE);
691 } else {
692 gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
693 }
694 break;
695
696 case MULTI_BUTTON:
697 Player_select_autoaccept = 0;
698
699 // switch to multiplayer mode
700 if (Player_select_mode != PLAYER_SELECT_MODE_MULTI) {
701 // play a little sound
702 gamesnd_play_iface(InterfaceSounds::USER_SELECT);
703
704 // Only do this when changing modes, keeps bottom text from being empty by accident
705 player_select_set_bottom_text("");
706
707 player_select_set_bottom_text(XSTR( "Multiplayer Mode", 377));
708
709 // reinitialize as multiplayer mode
710 player_select_init_player_stuff(PLAYER_SELECT_MODE_MULTI);
711 } else {
712 gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
713 }
714 break;
715 }
716 }
717
player_select_create_new_pilot()718 int player_select_create_new_pilot()
719 {
720 int idx;
721
722 // make sure we haven't reached the max
723 if (Player_select_num_pilots >= MAX_PILOTS) {
724 gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
725 return 0;
726 }
727
728 int play_scroll_sound = 1;
729
730 if ( play_scroll_sound ) {
731 gamesnd_play_iface(InterfaceSounds::SCROLL);
732 }
733
734 idx = Player_select_num_pilots;
735
736 // move all the pilots in the list up
737 while (idx--) {
738 strcpy(Pilots[idx + 1], Pilots[idx]);
739 }
740
741 // by default, set the default netgame protocol to be VMT
742 Multi_options_g.protocol = NET_TCP;
743
744 // select the beginning of the list
745 Player_select_pilot = 0;
746 Player_select_num_pilots++;
747 Pilots[Player_select_pilot][0] = 0;
748 Player_select_list_start= 0;
749
750 // set us to be in input mode
751 player_select_set_input_mode(1);
752
753 // set the input box to have focus
754 Player_select_input_box.set_focus();
755 Player_select_input_box.set_text("");
756 Player_select_input_box.update_dimensions(Choose_list_coords[gr_screen.res][0], Choose_list_coords[gr_screen.res][1], Choose_list_coords[gr_screen.res][2], gr_get_font_height());
757
758 return 1;
759 }
760
player_select_delete_pilot()761 void player_select_delete_pilot()
762 {
763 char filename[MAX_PATH_LEN + 1];
764 int i;
765
766 // tack on the full path and the pilot file extension
767 // build up the path name length
768 // make sure we do this based upon whether we're in single or multiplayer mode
769 strcpy_s( filename, Pilots[Player_select_pilot] );
770
771 auto del_rval = delete_pilot_file(filename);
772
773 if ( !del_rval ) {
774 popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_BIG | PF_TITLE_RED, 1, POPUP_OK, XSTR("Error\nFailed to delete pilot file. File may be read-only.", 1599));
775 return;
776 }
777
778 // move all the players down
779 for ( i=Player_select_pilot; i<Player_select_num_pilots-1; i++ ) {
780 strcpy(Pilots[i], Pilots[i + 1]);
781 }
782
783 // correcly set the # of pilots and the currently selected pilot
784 Player_select_num_pilots--;
785 if (Player_select_pilot >= Player_select_num_pilots) {
786 Player_select_pilot = Player_select_num_pilots - 1;
787 }
788
789 }
790
791 // scroll the list of players up
player_select_scroll_list_up()792 void player_select_scroll_list_up()
793 {
794 if (Player_select_pilot == -1) {
795 return;
796 }
797
798 // change the pilot selected index and play the appropriate sound
799 if (Player_select_pilot) {
800 Player_select_pilot--;
801 gamesnd_play_iface(InterfaceSounds::SCROLL);
802 } else {
803 gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
804 }
805
806 if (Player_select_pilot < Player_select_list_start) {
807 Player_select_list_start = Player_select_pilot;
808 }
809 }
810
811 // scroll the list of players down
player_select_scroll_list_down()812 void player_select_scroll_list_down()
813 {
814 // change the pilot selected index and play the appropriate sound
815 if ( Player_select_pilot < Player_select_num_pilots - 1 ) {
816 Player_select_pilot++;
817 gamesnd_play_iface(InterfaceSounds::SCROLL);
818 } else {
819 gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
820 }
821
822 if ( Player_select_pilot >= (Player_select_list_start + Max_lines) ) {
823 Player_select_list_start++;
824 }
825 }
826
827 // fill in the data on the last played pilot (callsign and is_multi or not)
player_select_get_last_pilot_info()828 int player_select_get_last_pilot_info()
829 {
830 const char *last_player = os_config_read_string( NULL, "LastPlayer", NULL);
831
832 if (last_player == NULL) {
833 return 0;
834 } else {
835 strcpy_s(Player_select_last_pilot, last_player);
836 }
837
838 // handle changing from pre-pilot code to post-pilot code
839 if (Player_select_last_pilot[strlen(Player_select_last_pilot)-1] == 'M' || Player_select_last_pilot[strlen(Player_select_last_pilot)-1] == 'S') {
840 Player_select_last_pilot[strlen(Player_select_last_pilot)-1]='\0'; // chop off last char, M|P
841 }
842
843 if ( !Pilot.load_player(Player_select_last_pilot, Player) ) {
844 Player_select_last_is_multi = 0;
845 } else {
846 Player_select_last_is_multi = Player->player_was_multi;
847 }
848
849 return 1;
850 }
851
player_select_get_last_pilot()852 int player_select_get_last_pilot()
853 {
854 // if the player has the Cmdline_use_last_pilot command line option set, try and drop out quickly
855 if (Cmdline_use_last_pilot) {
856 int idx;
857
858 if ( !player_select_get_last_pilot_info() ) {
859 return 0;
860 }
861
862 auto pilots = player_select_enumerate_pilots();
863 // Copy the enumerated pilots into the appropriate local variables
864 Player_select_num_pilots = static_cast<int>(pilots.size());
865 for (auto i = 0; i < MAX_PILOTS; ++i) {
866 if (i < static_cast<int>(pilots.size())) {
867 strcpy_s(Pilots_arr[i], pilots[i].c_str());
868 }
869 Pilots[i] = Pilots_arr[i];
870 }
871
872 Player_select_pilot = -1;
873 idx = 0;
874 // pick the last player
875 for (idx=0;idx<Player_select_num_pilots;idx++) {
876 if (strcmp(Player_select_last_pilot,Pilots_arr[idx])==0) {
877 Player_select_pilot = idx;
878 break;
879 }
880 }
881
882 // set this so that we don't incorrectly create a "blank" pilot - .plr
883 // in the player_select_close() function
884 Player_select_num_pilots = 0;
885
886 // if we've actually found a valid pilot, load him up
887 if (Player_select_pilot != -1) {
888 Player = &Players[0];
889 Pilot.load_player(Pilots_arr[idx], Player);
890 Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
891
892 // New pilot file makes no distinction between multi pilots and regular ones, so let's do this here.
893 if (Player->player_was_multi) {
894 Player->flags |= PLAYER_FLAGS_IS_MULTI;
895 }
896
897 return 1;
898 }
899 }
900
901 return 0;
902 }
903
player_select_init_player_stuff(int mode)904 void player_select_init_player_stuff(int mode)
905 {
906 Player_select_list_start = 0;
907
908 // set the select mode to single player for default
909 Player_select_mode = mode;
910
911 auto pilots = player_select_enumerate_pilots();
912 // Copy the enumerated pilots into the appropriate local variables
913 Player_select_num_pilots = static_cast<int>(pilots.size());
914 for (auto i = 0; i < MAX_PILOTS; ++i) {
915 if (i < static_cast<int>(pilots.size())) {
916 strcpy_s(Pilots_arr[i], pilots[i].c_str());
917 }
918 Pilots[i] = Pilots_arr[i];
919 }
920
921 // if we have a "last_player", and they're in the list, bash them to the top of the list
922 if (Player_select_last_pilot[0] != '\0') {
923 int i,j;
924 for (i = 0; i < Player_select_num_pilots; ++i) {
925 if (!stricmp(Player_select_last_pilot,Pilots[i])) {
926 break;
927 }
928 }
929 if (i != Player_select_num_pilots) {
930 for (j = i; j > 0; --j) {
931 strncpy(Pilots[j], Pilots[j-1], strlen(Pilots[j-1])+1);
932 }
933 strncpy(Pilots[0], Player_select_last_pilot, strlen(Player_select_last_pilot)+1);
934 }
935 }
936
937 Player = NULL;
938
939 // if this value is -1, it means we should set it to the num pilots count
940 if (Player_select_initial_count == -1) {
941 Player_select_initial_count = Player_select_num_pilots;
942 }
943
944 // select the first pilot if any exist, otherwise set to -1
945 if (Player_select_num_pilots == 0) {
946 Player_select_pilot = -1;
947 player_select_set_middle_text(XSTR( "Type Callsign and Press Enter", 381));
948 player_select_set_controls(1); // gray out the controls
949 player_select_create_new_pilot();
950 } else {
951 Player_select_pilot = 0;
952 }
953 }
954
player_select_draw_list()955 void player_select_draw_list()
956 {
957 int idx;
958
959 if (gr_screen.res == 1) {
960 Max_lines = 145/gr_get_font_height(); //Make the max number of lines dependent on the font height. 145 and 85 are magic numbers, based on the window size in retail.
961 } else {
962 Max_lines = 85/gr_get_font_height();
963 }
964
965 for (idx=0; idx<Max_lines; idx++) {
966 // only draw as many pilots as we have
967 if ((idx + Player_select_list_start) == Player_select_num_pilots) {
968 break;
969 }
970
971 // if the currently selected pilot is this line, draw it highlighted
972 if ( (idx + Player_select_list_start) == Player_select_pilot) {
973 // if he's the active pilot and is also the current selection, super-highlight him
974 gr_set_color_fast(&Color_text_active);
975 } else { // otherwise draw him normally
976 gr_set_color_fast(&Color_text_normal);
977 }
978 // draw the actual callsign
979 gr_printf_menu(Choose_list_coords[gr_screen.res][0], Choose_list_coords[gr_screen.res][1] + (idx * gr_get_font_height()), "%s", Pilots[idx + Player_select_list_start]);
980 }
981 }
982
player_select_process_noninput(int k)983 void player_select_process_noninput(int k)
984 {
985 int idx;
986
987 // check for pressed buttons
988 for (idx=0; idx<NUM_PLAYER_SELECT_BUTTONS; idx++) {
989 if (Player_select_buttons[gr_screen.res][idx].button.pressed()) {
990 player_select_button_pressed(idx);
991 }
992 }
993
994 // check for keypresses
995 switch (k) {
996 // quit the game entirely
997 case KEY_ESC:
998 gameseq_post_event(GS_EVENT_QUIT_GAME);
999 break;
1000
1001 case KEY_ENTER | KEY_CTRLED:
1002 player_select_button_pressed(ACCEPT_BUTTON);
1003 break;
1004
1005 // delete the currently highlighted pilot
1006 case KEY_DELETE:
1007 player_select_button_pressed(DELETE_BUTTON);
1008 break;
1009 }
1010
1011 // check to see if the user has clicked on the "list region" button
1012 // and change the selected pilot appropriately
1013 if (Player_select_list_region.pressed()) {
1014 int click_y;
1015 // get the mouse position
1016 Player_select_list_region.get_mouse_pos(NULL, &click_y);
1017
1018 // determine what index to select
1019 //idx = (click_y+5) / 10;
1020 idx = click_y / gr_get_font_height();
1021
1022
1023 // if he selected a valid item
1024 if ( ((idx + Player_select_list_start) < Player_select_num_pilots) && (idx >= 0) ) {
1025 Player_select_pilot = idx + Player_select_list_start;
1026 }
1027 }
1028
1029 // if the player has double clicked on a valid pilot, choose it and hit the accept button
1030 if (Player_select_list_region.double_clicked()) {
1031 if ((Player_select_pilot >= 0) && (Player_select_pilot < Player_select_num_pilots)) {
1032 player_select_button_pressed(ACCEPT_BUTTON);
1033 }
1034 }
1035 }
1036
player_select_process_input(int k)1037 void player_select_process_input(int k)
1038 {
1039 char buf[CALLSIGN_LEN + 1];
1040 int idx,z;
1041
1042 // if the player is in the process of typing in a new pilot name...
1043 switch (k) {
1044 // cancel create pilot
1045 case KEY_ESC:
1046 player_select_cancel_create();
1047 break;
1048
1049 // accept a new pilot name
1050 case KEY_ENTER:
1051 Player_select_input_box.get_text(buf);
1052 drop_white_space(buf);
1053 z = 0;
1054 if (!isalpha(*buf)) {
1055 z = 1;
1056 } else {
1057 for (idx=1; buf[idx]; idx++) {
1058 if (!isalpha(buf[idx]) && !isdigit(buf[idx]) && !strchr(VALID_PILOT_CHARS, buf[idx])) {
1059 z = 1;
1060 break;
1061 }
1062 }
1063 }
1064
1065 for (idx=1; idx<Player_select_num_pilots; idx++) {
1066 if (!stricmp(buf, Pilots[idx])) {
1067 // verify if it is ok to overwrite the file
1068 if (pilot_verify_overwrite() == 1) {
1069 // delete the pilot and select the beginning of the list
1070 Player_select_pilot = idx;
1071 player_select_delete_pilot();
1072 Player_select_pilot = 0;
1073 idx = Player_select_num_pilots;
1074 z = 0;
1075
1076 } else
1077 z = 1;
1078
1079 break;
1080 }
1081 }
1082
1083 if (!*buf || (idx < Player_select_num_pilots)) {
1084 z = 1;
1085 }
1086
1087 if (z) {
1088 gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
1089 break;
1090 }
1091
1092 // Create the new pilot, and write out his file
1093 strcpy(Pilots[0], buf);
1094
1095 // if this is the first guy, we should set the Player struct
1096 if (Player == NULL) {
1097 Player = &Players[0];
1098 Player->reset();
1099 Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
1100 }
1101
1102 strcpy_s(Player->callsign, buf);
1103 init_new_pilot(Player, !Player_select_clone_flag);
1104
1105 // set him as being a multiplayer pilot if we're in the correct mode
1106 if (Player_select_mode == PLAYER_SELECT_MODE_MULTI) {
1107 Player->flags |= PLAYER_FLAGS_IS_MULTI;
1108 Player->stats.flags |= STATS_FLAG_MULTIPLAYER;
1109 Player->player_was_multi = 1;
1110 } else {
1111 Player->player_was_multi = 0;
1112 }
1113
1114 // create his pilot file
1115 Pilot.save_player(Player);
1116
1117 // unset the player
1118 Player->reset();
1119 Player = NULL;
1120
1121 // make this guy the selected pilot and put him first on the list
1122 Player_select_pilot = 0;
1123
1124 // unset the input mode
1125 player_select_set_input_mode(0);
1126
1127 // clear any pending bottom text
1128 player_select_set_bottom_text("");
1129
1130 // clear any pending middle text
1131 player_select_set_middle_text("");
1132
1133 // ungray all the controls
1134 player_select_set_controls(0);
1135
1136 // evaluate whether or not this is the very first pilot
1137 player_select_eval_very_first_pilot();
1138 break;
1139
1140 case 0:
1141 break;
1142
1143 // always kill middle text when a char is pressed in input mode
1144 default:
1145 player_select_set_middle_text("");
1146 break;
1147 }
1148 }
1149
1150 // draw copyright message on the bottom on the screen
player_select_display_copyright()1151 void player_select_display_copyright()
1152 {
1153 int sx, sy, w;
1154 char Copyright_msg2[256];
1155
1156 // strcpy_s(Copyright_msg1, XSTR("Descent: FreeSpace - The Great War, Copyright c 1998, Volition, Inc.", -1));
1157 gr_set_color_fast(&Color_white);
1158
1159 // sprintf(Copyright_msg1, NOX("FreeSpace 2"));
1160 auto Copyright_msg1 = gameversion::get_version_string();
1161 if (Unicode_text_mode) {
1162 // Use a Unicode character if we are in unicode mode instead of using special characters
1163 strcpy_s(Copyright_msg2, XSTR("Copyright \xC2\xA9 1999, Volition, Inc. All rights reserved.", 385));
1164 } else {
1165 sprintf(Copyright_msg2, XSTR("Copyright %c 1999, Volition, Inc. All rights reserved.", 385), lcl_get_font_index(font::get_current_fontnum()) + 4);
1166 }
1167
1168 gr_get_string_size(&w, nullptr, Copyright_msg1.c_str());
1169 sx = (int)std::lround((gr_screen.max_w_unscaled / 2) - w/2.0f);
1170 sy = (gr_screen.max_h_unscaled - 2) - 2*gr_get_font_height();
1171 gr_string(sx, sy, Copyright_msg1.c_str(), GR_RESIZE_MENU);
1172
1173 gr_get_string_size(&w, NULL, Copyright_msg2);
1174 sx = (int)std::lround((gr_screen.max_w_unscaled / 2) - w/2.0f);
1175 sy = (gr_screen.max_h_unscaled - 2) - gr_get_font_height();
1176
1177 gr_string(sx, sy, Copyright_msg2, GR_RESIZE_MENU);
1178 }
1179
player_select_display_all_text()1180 void player_select_display_all_text()
1181 {
1182 int w, h;
1183
1184 // only draw if we actually have a valid string
1185 if (strlen(Player_select_bottom_text)) {
1186 gr_get_string_size(&w, &h, Player_select_bottom_text);
1187
1188 w = (gr_screen.max_w_unscaled - w) / 2;
1189 gr_set_color_fast(&Color_bright_white);
1190 gr_printf_menu(w, Player_select_bottom_text_y[gr_screen.res], "%s", Player_select_bottom_text);
1191 }
1192
1193 // only draw if we actually have a valid string
1194 if (strlen(Player_select_middle_text)) {
1195 gr_get_string_size(&w, &h, Player_select_middle_text);
1196
1197 w = (gr_screen.max_w_unscaled - w) / 2;
1198 gr_set_color_fast(&Color_bright_white);
1199 gr_printf_menu(w, Player_select_middle_text_y[gr_screen.res], "%s", Player_select_middle_text);
1200 }
1201 }
1202
player_select_pilot_file_filter(const char * filename)1203 int player_select_pilot_file_filter(const char *filename)
1204 {
1205 return (int)Pilot.verify(filename);
1206 }
1207
player_select_set_bottom_text(const char * txt)1208 void player_select_set_bottom_text(const char *txt)
1209 {
1210 if (txt) {
1211 strncpy(Player_select_bottom_text, txt, 149);
1212 }
1213 }
1214
player_select_set_middle_text(const char * txt)1215 void player_select_set_middle_text(const char *txt)
1216 {
1217 if (txt) {
1218 strncpy(Player_select_middle_text, txt, 149);
1219 }
1220 }
1221
player_select_eval_very_first_pilot()1222 void player_select_eval_very_first_pilot()
1223 {
1224 // never bring up the initial main hall help overlay
1225 // Player_select_very_first_pilot = 0;
1226
1227 // if we already have this flag set, check to see if our callsigns match
1228 if(Player_select_very_first_pilot) {
1229 // if the callsign has changed, unset the flag
1230 if(strcmp(Player_select_very_first_pilot_callsign,Pilots[Player_select_pilot]) != 0){
1231 Player_select_very_first_pilot = 0;
1232 }
1233 } else { // otherwise check to see if there is only 1 pilot
1234 if((Player_select_num_pilots == 1) && (Player_select_initial_count == 0)){
1235 // set up the data
1236 Player_select_very_first_pilot = 1;
1237 strcpy_s(Player_select_very_first_pilot_callsign,Pilots[Player_select_pilot]);
1238 }
1239 }
1240 }
1241
player_select_commit()1242 void player_select_commit()
1243 {
1244 // if we've gotten to this point, we should have ensured this was the case
1245 Assert(Player_select_num_pilots > 0);
1246
1247 gameseq_post_event(GS_EVENT_MAIN_MENU);
1248 gamesnd_play_iface(InterfaceSounds::COMMIT_PRESSED);
1249
1250 // evaluate if this is the _very_ first pilot
1251 player_select_eval_very_first_pilot();
1252 }
1253
player_select_cancel_create()1254 void player_select_cancel_create()
1255 {
1256 int idx;
1257
1258 Player_select_num_pilots--;
1259
1260 // make sure we correct the Selected_pilot index to account for the cancelled action
1261 if (Player_select_num_pilots == 0) {
1262 Player_select_pilot = -1;
1263 }
1264
1265 // move all pilots down
1266 for (idx=0; idx<Player_select_num_pilots; idx++) {
1267 strcpy(Pilots[idx], Pilots[idx + 1]);
1268 }
1269
1270 // unset the input mode
1271 player_select_set_input_mode(0);
1272
1273 // clear any bottom text
1274 player_select_set_bottom_text("");
1275
1276 // clear any middle text
1277 player_select_set_middle_text("");
1278
1279 // ungray all controls
1280 player_select_set_controls(0);
1281
1282 // disable the autoaccept
1283 Player_select_autoaccept = 0;
1284 }
1285
1286 DCF(bastion,"Sets the player to be on the bastion (or any other main hall)")
1287 {
1288 int idx;
1289
1290 if(gameseq_get_state() != GS_STATE_INITIAL_PLAYER_SELECT) {
1291 dc_printf("This command can only be run while in the initial player select screen.\n");
1292 return;
1293 }
1294
1295 if (dc_optional_string_either("help", "--help")) {
1296 dc_printf("Usage: bastion [index]\n");
1297 dc_printf(" [index] -- optional main hall index; if not supplied, defaults to 1\n");
1298 return;
1299 }
1300
1301 if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
1302 dc_printf("Player is on main hall '%s'\n", Player_select_force_main_hall.c_str());
1303 return;
1304 }
1305
1306 if (dc_maybe_stuff_int(&idx)) {
1307 Assert(Main_hall_defines.size() < INT_MAX);
1308 if ((idx < 0) || (idx >= (int) Main_hall_defines.size())) {
1309 dc_printf("Main hall index out of range\n");
1310
1311 } else {
1312 main_hall_get_name(Player_select_force_main_hall, idx);
1313 dc_printf("Player is now on main hall '%s'\n", Player_select_force_main_hall.c_str());
1314 }
1315
1316 } else {
1317 // No argument passed
1318 Player_select_force_main_hall = "1";
1319 dc_printf("Player is now on the Bastion... hopefully\n");
1320 }
1321 }
1322
1323 #define MAX_PLAYER_TIPS 40
1324
1325 char *Player_tips[MAX_PLAYER_TIPS];
1326 int Num_player_tips;
1327 int Player_tips_shown = 0;
1328
1329 // tooltips
player_tips_init()1330 void player_tips_init()
1331 {
1332 Num_player_tips = 0;
1333
1334 try
1335 {
1336 read_file_text("tips.tbl", CF_TYPE_TABLES);
1337 reset_parse();
1338
1339 while (!optional_string("#end")) {
1340 required_string("+Tip:");
1341
1342 if (Num_player_tips >= MAX_PLAYER_TIPS) {
1343 break;
1344 }
1345 Player_tips[Num_player_tips++] = stuff_and_malloc_string(F_NAME, NULL);
1346 }
1347 }
1348 catch (const parse::ParseException& e)
1349 {
1350 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "tips.tbl", e.what()));
1351 return;
1352 }
1353 }
1354
1355 // close out player tips - *only call from game_shutdown()*
player_tips_close()1356 void player_tips_close()
1357 {
1358 int i;
1359
1360 for (i=0; i<MAX_PLAYER_TIPS; i++) {
1361 if (Player_tips[i] != NULL) {
1362 vm_free(Player_tips[i]);
1363 Player_tips[i] = NULL;
1364 }
1365 }
1366 }
1367
player_tips_popup()1368 void player_tips_popup()
1369 {
1370 int tip, ret;
1371
1372 // player has disabled tips
1373 if ( (Player != NULL) && !Player->tips ) {
1374 return;
1375 }
1376 // only show tips once per instance of FreeSpace
1377 if(Player_tips_shown == 1) {
1378 return;
1379 }
1380 Player_tips_shown = 1;
1381
1382 // randomly pick one
1383 tip = (int)frand_range(0.0f, (float)Num_player_tips - 1.0f);
1384
1385 char all_txt[2048];
1386
1387 do {
1388 sprintf(all_txt, XSTR("NEW USER TIP\n\n%s", 1565), Player_tips[tip]);
1389 ret = popup(PF_NO_SPECIAL_BUTTONS | PF_TITLE | PF_TITLE_WHITE, 3, XSTR("&Ok", 669), XSTR("&Next", 1444), XSTR("Don't show me this again", 1443), all_txt);
1390
1391 // now what?
1392 switch(ret){
1393 // next
1394 case 1:
1395 if(tip >= Num_player_tips - 1) {
1396 tip = 0;
1397 } else {
1398 tip++;
1399 }
1400 break;
1401
1402 // don't show me this again
1403 case 2:
1404 ret = 0;
1405 Player->tips = 0;
1406 Pilot.save_player(Player);
1407 Pilot.save_savefile();
1408 break;
1409 }
1410 } while(ret > 0);
1411 }
1412
player_select_enumerate_pilots()1413 SCP_vector<SCP_string> player_select_enumerate_pilots() {
1414 // load up the list of players based upon the Player_select_mode (single or multiplayer)
1415 Get_file_list_filter = player_select_pilot_file_filter;
1416
1417 SCP_vector<SCP_string> pilots;
1418 cf_get_file_list(pilots, CF_TYPE_PLAYERS, "*.json", CF_SORT_TIME, nullptr, CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
1419
1420 return pilots;
1421 }
1422
player_get_last_player()1423 SCP_string player_get_last_player()
1424 {
1425 const char* last_player = os_config_read_string(nullptr, "LastPlayer", nullptr);
1426
1427 if (last_player == nullptr) {
1428 // No last player stored
1429 return SCP_string();
1430 }
1431 return SCP_string(last_player);
1432 }
1433
player_finish_select(const char * callsign,bool is_multi)1434 void player_finish_select(const char* callsign, bool is_multi) {
1435 Player_num = 0;
1436 Player = &Players[0];
1437 Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
1438
1439 // New pilot file makes no distinction between multi pilots and regular ones, so let's do this here.
1440 if (is_multi) {
1441 Player->flags |= PLAYER_FLAGS_IS_MULTI;
1442 }
1443
1444 // WMC - Set appropriate game mode
1445 if ( Player->flags & PLAYER_FLAGS_IS_MULTI ) {
1446 Game_mode = GM_MULTIPLAYER;
1447 } else {
1448 Game_mode = GM_NORMAL;
1449 }
1450
1451 // now read in a the pilot data
1452 if ( !Pilot.load_player(callsign, Player) ) {
1453 Error(LOCATION,"Couldn't load pilot file for pilot \"%s\", bailing", callsign);
1454 Player = nullptr;
1455 } else {
1456 // NOTE: this may fail if there is no current campaign, it's not fatal
1457 Pilot.load_savefile(Player, Player->current_campaign);
1458 }
1459
1460 if (Player_select_mode == PLAYER_SELECT_MODE_MULTI) {
1461 Player->player_was_multi = 1;
1462 } else {
1463 Player->player_was_multi = 0;
1464 }
1465
1466 os_config_write_string(nullptr, "LastPlayer", Player->callsign);
1467
1468 gameseq_post_event(GS_EVENT_MAIN_MENU);
1469 }
player_create_new_pilot(const char * callsign,bool is_multi,const char * copy_from_callsign)1470 bool player_create_new_pilot(const char* callsign, bool is_multi, const char* copy_from_callsign) {
1471 SCP_string buf = callsign;
1472
1473 drop_white_space(buf);
1474 auto invalid_name = false;
1475 if (!isalpha(buf[0])) {
1476 invalid_name = true;
1477 } else {
1478 for (auto idx=1; buf[idx]; idx++) {
1479 if (!isalpha(buf[idx]) && !isdigit(buf[idx]) && !strchr(VALID_PILOT_CHARS, buf[idx])) {
1480 invalid_name = true;
1481 break;
1482 }
1483 }
1484 }
1485
1486 if (buf.empty()) {
1487 invalid_name = true;
1488 }
1489
1490 if (invalid_name) {
1491 return false;
1492 }
1493
1494 // Create the new pilot, and write out his file
1495 // if this is the first guy, we should set the Player struct
1496 if (Player == nullptr) {
1497 Player = &Players[0];
1498 Player->reset();
1499 Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
1500 }
1501
1502 if (copy_from_callsign != nullptr) {
1503 // attempt to read in the pilot file of the guy to be cloned
1504 if (!Pilot.load_player(copy_from_callsign, Player)) {
1505 Error(LOCATION, "Couldn't load pilot file, bailing");
1506 return false;
1507 }
1508 }
1509
1510 strcpy_s(Player->callsign, buf.c_str());
1511 init_new_pilot(Player, copy_from_callsign == nullptr);
1512
1513 // set him as being a multiplayer pilot if we're in the correct mode
1514 if (is_multi) {
1515 Player->flags |= PLAYER_FLAGS_IS_MULTI;
1516 Player->stats.flags |= STATS_FLAG_MULTIPLAYER;
1517 }
1518
1519 // create his pilot file
1520 Pilot.save_player(Player);
1521
1522 // unset the player
1523 Player->reset();
1524 Player = nullptr;
1525
1526 return true;
1527 }
1528