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