Make quickstart options quick, and fix several associated bugs (#7834)

* only strip the legacy M/S suffix from the last pilot when needed

player_select_get_last_pilot_info() chopped any trailing uppercase M or S
from the LastPlayer value, a holdover from the pre-pilot-file era.  Pilots
whose callsigns end in M or S (e.g. BOSS) were never found as the last
pilot.  Now the suffix is only removed when the full callsign is not a
pilot but the shortened one is.  This also avoids indexing before the
string when LastPlayer is empty.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* route all pilot selection through one commit function

player_select_close() and player_finish_select() each had their own copy
of the code that loads a pilot as the active Player, and the copies had
drifted apart.  Both now call player_select_commit_pilot().

This fixes two bugs:
* -pilot did not work with the retail pilot select screen.
  player_finish_select() loaded the requested pilot, but then
  player_select_close() loaded the highlighted pilot (the most recent
  one) over it.  The close handler now skips the load if a pilot has
  already been committed.
* player_finish_select() ignored its is_multi parameter when setting
  player_was_multi, so scripted selection of a multiplayer pilot saved
  it as single-player.

Scripted selection also now saves the pilot file, as the retail screen
already did.  The background bitmap handle starts out as -1 so that the
close handler doesn't release bitmap 0 when the screen was never set up.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* select pilots automatically at game init, through the normal commit path

-startgame, -connect and -almission (via Cmdline_use_last_pilot) used
player_select_get_last_pilot(), which loaded the last pilot directly
without most of the setup that a normal selection does.  It skipped
valid_pilot(), multi_options_init_globals() and saving the pilot file.
Because it never entered the pilot select state, it also turned off the
main hall's fallback for an unavailable campaign.  Meanwhile -pilot and
-benchmark_mode were handled on the pilot select screen itself, which
scripted UIs replace entirely.

All of these now go through player_select_try_auto_select() at
GS_EVENT_GAME_INIT.  It picks the -pilot callsign, else the last pilot
if it still exists, else the most recently saved pilot.  It shows the
startup warning popups, validates the pilot, and commits it through
player_select_commit_pilot().  The pilot is multiplayer if -startgame or
-connect was given, and single-player otherwise.  If there are no
pilots, or validation fails, the pilot select screen is shown as usual.

The main hall now allows the campaign fallback whenever a pilot has just
been committed, not only when coming from the pilot select state.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* select a pilot automatically for -start_mission

-start_mission skips the main hall, but it still waited on the pilot
select screen.  It now uses the same automatic pilot selection as the
other quickstart options, so the screen only appears if there are no
pilots.  The pilot is committed as single-player, consistent with the
main hall forcing single-player mode for -start_mission.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* skip the intro movie for quickstart options

When the command line asks to skip pilot selection (-start_mission,
-pilot, -startgame, -connect, -almission, -benchmark_mode), the intro
movie now doesn't play either.  It could take several minutes before the
game even reached the pilot.  The OnIntroAboutToPlay hook still runs, as
it does when the mod table disables the intro.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Goober5000andClaude Opus 5.5 authored and GitHub committed 2026-10-16 20:57:07 -04:00
1 parent b477dd5ca9
commit c28032a2af
3 files changed
+120 -128

No files matched your search

+100 -121
View File
@@ -128,7 +128,7 @@ UI_BUTTON Player_select_list_region; // button for detecting mouse clicks on th
UI_INPUTBOX Player_select_input_box; // input box for adding new pilot names
// #define PLAYER_SELECT_PALETTE_FNAME NOX("InterfacePalette")
int Player_select_background_bitmap; // bitmap for this screen
int Player_select_background_bitmap = -1; // bitmap for this screen
// int Player_select_palette; // palette bitmap for this screen
int Player_select_autoaccept = 0;
// int Player_select_palette_set = 0;
@@ -156,6 +156,8 @@ SCP_string Player_select_force_main_hall = "";
static int Player_select_no_save_pilot = 0; // to skip save of pilot in pilot_select_close()
bool Player_select_pilot_just_committed = false;
int Player_select_screen_active = 0; // for pilot savefile loading - taylor
// notification text areas
@@ -195,6 +197,8 @@ int player_select_get_last_pilot_info();
void player_select_eval_very_first_pilot();
void player_select_commit();
void player_select_cancel_create();
bool player_select_commit_pilot(const char* callsign, bool is_multi);
void player_select_show_startup_warnings();
bool valid_pilot(const char* callsign, bool no_popup) {
@@ -377,8 +381,8 @@ void player_select_init()
player_select_init_player_stuff(PLAYER_SELECT_MODE_SINGLE);
}
if (Cmdline_benchmark_mode || ((Player_select_num_pilots == 1) && Player_select_input_mode)) {
// When benchmarking, just accept automatically
if ((Player_select_num_pilots == 1) && Player_select_input_mode) {
// accept automatically once the very first pilot is created
Player_select_autoaccept = 1;
}
}
@@ -387,10 +391,8 @@ void player_select_init()
static bool Startup_warning_dialog_displayed = false;
static bool Save_file_warning_displayed = false;
void player_select_do()
void player_select_show_startup_warnings()
{
int k;
// Goober5000 - display a popup warning about problems in the mod
if ((Global_warning_count > 10 || Global_error_count > 0) && !Startup_warning_dialog_displayed) {
char text[512];
@@ -403,7 +405,14 @@ void player_select_do()
popup(PF_BODY_BIG | PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("A new settings file has been created for the current game or mod. You may want to check the options menu to ensure everything is set to your liking.", 1854));
Save_file_warning_displayed = true;
}
}
void player_select_do()
{
int k;
player_select_show_startup_warnings();
// set the input box at the "virtual" line 0 to be active so the player can enter a callsign
if (Player_select_input_mode) {
Player_select_input_box.set_focus();
@@ -460,12 +469,6 @@ void player_select_do()
gr_set_bitmap(Player_select_background_bitmap);
gr_bitmap(0,0,GR_RESIZE_MENU);
//skip this if pilot is given through cmdline, assuming single-player
if (Cmdline_pilot) {
player_finish_select(Cmdline_pilot, false);
return;
}
// press the accept button
if (Player_select_autoaccept) {
Player_select_buttons[gr_screen.res][ACCEPT_BUTTON].button.press_button();
@@ -515,66 +518,29 @@ void player_select_close()
return;
}
// actually set up the Player struct here
if ( (Player_select_pilot == -1) || (Player_select_num_pilots == 0) ) {
nprintf(("General","WARNING! No pilot selected! We should be exiting the game now!\n"));
return;
// a pilot may already have been selected some other way, e.g. by a script
if (!Player_select_pilot_just_committed) {
// actually set up the Player struct here
if ( (Player_select_pilot == -1) || (Player_select_num_pilots == 0) ) {
nprintf(("General","WARNING! No pilot selected! We should be exiting the game now!\n"));
return;
}
if (!player_select_commit_pilot(Pilots[Player_select_pilot], Player_select_mode == PLAYER_SELECT_MODE_MULTI)) {
return;
}
}
// unload all bitmaps
if(Player_select_background_bitmap >= 0) {
bm_release(Player_select_background_bitmap);
Player_select_background_bitmap = -1;
}
}
// if(Player_select_palette >= 0){
// bm_release(Player_select_palette);
//Player_select_palette = -1;GS_EVENT_MAIN_MENU
// }
// setup the player struct
Player_num = 0;
Player = &Players[0];
Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
// New pilot file makes no distinction between multi pilots and regular ones, so let's do this here.
if (Player_select_mode == PLAYER_SELECT_MODE_MULTI) {
Player->flags |= PLAYER_FLAGS_IS_MULTI;
}
// WMC - Set appropriate game mode
if ( Player->flags & PLAYER_FLAGS_IS_MULTI ) {
Game_mode = GM_MULTIPLAYER;
} else {
Game_mode = GM_NORMAL;
}
// now read in a the pilot data
if ( !Pilot.load_player(Pilots[Player_select_pilot], Player) ) {
Error(LOCATION,"Couldn't load pilot file, bailing");
Player = NULL;
return;
}
// set the local multi options from the player flags
multi_options_init_globals();
// read in the current campaign
// NOTE: this may fail if there is no current campaign, it's not fatal
Pilot.load_savefile(Player, Player->current_campaign);
// Set singleplayer/multiplayer mode in Player
if (Player_select_mode == PLAYER_SELECT_MODE_MULTI) {
Player->player_was_multi = 1;
} else {
Player->player_was_multi = 0;
}
// save the pilot file to a version that we work with
Pilot.save_player(Player);
// Update the LastPlayer key in the registry
os_config_write_string(nullptr, "LastPlayer", Player->callsign);
// Maybe use a different main hall (debug console)
if (Player_select_force_main_hall != "") {
main_hall_init(Player_select_force_main_hall);
@@ -870,20 +836,32 @@ void player_select_scroll_list_down()
}
}
// callsign of the last played pilot, or an empty string if there wasn't one
static SCP_string player_select_read_last_pilot()
{
auto callsign = player_get_last_player();
// handle changing from pre-pilot code to post-pilot code, which appended M or S to the callsign;
// but only chop it off if the callsign isn't a valid pilot as-is
if (!callsign.empty() && (callsign.back() == 'M' || callsign.back() == 'S') && !Pilot.verify((callsign + ".json").c_str()))
{
auto chopped = callsign.substr(0, callsign.size() - 1);
if (Pilot.verify((chopped + ".json").c_str()))
callsign = chopped;
}
return callsign;
}
// fill in the data on the last played pilot (callsign and is_multi or not)
int player_select_get_last_pilot_info()
{
const char *last_player = os_config_read_string( NULL, "LastPlayer", NULL);
auto last_pilot = player_select_read_last_pilot();
if (last_player == NULL) {
if (last_pilot.empty()) {
return 0;
} else {
strcpy_s(Player_select_last_pilot, last_player);
}
// handle changing from pre-pilot code to post-pilot code
if (Player_select_last_pilot[strlen(Player_select_last_pilot)-1] == 'M' || Player_select_last_pilot[strlen(Player_select_last_pilot)-1] == 'S') {
Player_select_last_pilot[strlen(Player_select_last_pilot)-1]='\0'; // chop off last char, M|P
strcpy_s(Player_select_last_pilot, last_pilot.c_str());
}
if ( !Pilot.load_player(Player_select_last_pilot, Player) ) {
@@ -895,56 +873,45 @@ int player_select_get_last_pilot_info()
return 1;
}
int player_select_get_last_pilot()
bool player_select_auto_select_requested()
{
// if the player has the Cmdline_use_last_pilot command line option set, try and drop out quickly
if (Cmdline_use_last_pilot) {
int idx;
return Cmdline_pilot || Cmdline_use_last_pilot || Cmdline_benchmark_mode || Cmdline_start_mission;
}
if ( !player_select_get_last_pilot_info() ) {
return 0;
}
bool player_select_try_auto_select()
{
if (!player_select_auto_select_requested())
return false;
SCP_string callsign;
if (Cmdline_pilot)
callsign = Cmdline_pilot;
else
{
// pilots are sorted by file time, so if the last pilot isn't available, this picks the most recently saved one
auto pilots = player_select_enumerate_pilots();
// Copy the enumerated pilots into the appropriate local variables
Player_select_num_pilots = static_cast<int>(pilots.size());
for (auto i = 0; i < MAX_PILOTS; ++i) {
if (i < static_cast<int>(pilots.size())) {
strcpy_s(Pilots_arr[i], pilots[i].c_str());
}
Pilots[i] = Pilots_arr[i];
}
if (pilots.empty())
return false;
Player_select_pilot = -1;
idx = 0;
// pick the last player
for (idx=0;idx<Player_select_num_pilots;idx++) {
if (strcmp(Player_select_last_pilot,Pilots_arr[idx])==0) {
Player_select_pilot = idx;
callsign = pilots.front();
auto last_pilot = player_select_read_last_pilot();
for (const auto &pilot : pilots)
{
if (!stricmp(pilot.c_str(), last_pilot.c_str()))
{
callsign = pilot;
break;
}
}
// set this so that we don't incorrectly create a "blank" pilot - .plr
// in the player_select_close() function
Player_select_num_pilots = 0;
// if we've actually found a valid pilot, load him up
if (Player_select_pilot != -1) {
Player = &Players[0];
Pilot.load_player(Pilots_arr[idx], Player);
Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
// New pilot file makes no distinction between multi pilots and regular ones, so let's do this here.
if (Player->player_was_multi) {
Player->flags |= PLAYER_FLAGS_IS_MULTI;
}
return 1;
}
}
return 0;
player_select_show_startup_warnings();
if (!valid_pilot(callsign.c_str()))
return false;
return player_select_commit_pilot(callsign.c_str(), Cmdline_start_netgame || (Cmdline_connect_addr != nullptr));
}
void player_select_init_player_stuff(int mode)
@@ -1528,7 +1495,9 @@ SCP_string player_get_last_player()
return SCP_string(last_player);
}
void player_finish_select(const char* callsign, bool is_multi) {
bool player_select_commit_pilot(const char* callsign, bool is_multi)
{
// setup the player struct
Player_num = 0;
Player = &Players[0];
Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
@@ -1549,23 +1518,33 @@ void player_finish_select(const char* callsign, bool is_multi) {
if ( !Pilot.load_player(callsign, Player) ) {
Error(LOCATION,"Couldn't load pilot file for pilot \"%s\", bailing", callsign);
Player = nullptr;
} else {
// NOTE: this may fail if there is no current campaign, it's not fatal
Pilot.load_savefile(Player, Player->current_campaign);
}
if (Player_select_mode == PLAYER_SELECT_MODE_MULTI) {
Player->player_was_multi = 1;
} else {
Player->player_was_multi = 0;
return false;
}
// set the local multi options from the player flags
multi_options_init_globals();
// read in the current campaign
// NOTE: this may fail if there is no current campaign, it's not fatal
Pilot.load_savefile(Player, Player->current_campaign);
// Set singleplayer/multiplayer mode in Player
Player->player_was_multi = is_multi ? 1 : 0;
// save the pilot file to a version that we work with
Pilot.save_player(Player);
// Update the LastPlayer key in the registry
os_config_write_string(nullptr, "LastPlayer", Player->callsign);
gameseq_post_event(GS_EVENT_MAIN_MENU);
Player_select_pilot_just_committed = true;
return true;
}
void player_finish_select(const char* callsign, bool is_multi) {
if (player_select_commit_pilot(callsign, is_multi)) {
gameseq_post_event(GS_EVENT_MAIN_MENU);
}
}
bool player_create_new_pilot(const char* callsign, bool is_multi, const char* copy_from_callsign) {
SCP_string buf = callsign;
+8 -2
View File
@@ -35,8 +35,14 @@ void player_select_do();
*/
void player_select_close();
// function to check whether we found a "last pilot". loads this pilot in if possible and returns true, or false otherwise
int player_select_get_last_pilot();
// set when a pilot is loaded as the active Player; cleared when the main menu state is entered
extern bool Player_select_pilot_just_committed;
// whether the command line asks for a pilot to be selected without showing the pilot select screen
bool player_select_auto_select_requested();
// if requested by the command line, selects a pilot without the pilot select screen; returns true if a pilot was selected
bool player_select_try_auto_select();
// tooltips
extern int Player_tips_start_index;