diff --git a/data/raw/keybindings.json b/data/raw/keybindings.json index 0f1247a1889a8..31870e708f232 100644 --- a/data/raw/keybindings.json +++ b/data/raw/keybindings.json @@ -3538,6 +3538,39 @@ { "input_method": "keyboard_any", "key": "ESC" } ] }, + { + "type": "keybinding", + "id": "LOAD", + "category": "LOAD_DELETE_CANCEL", + "name": "Load", + "bindings": [ + { "input_method": "keyboard_char", "key": "L" }, + { "input_method": "keyboard_code", "key": "l", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "l" } + ] + }, + { + "type": "keybinding", + "id": "DELETE", + "category": "LOAD_DELETE_CANCEL", + "name": "Delete", + "bindings": [ + { "input_method": "keyboard_char", "key": "D" }, + { "input_method": "keyboard_code", "key": "d", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "d" } + ] + }, + { + "type": "keybinding", + "id": "CANCEL", + "category": "LOAD_DELETE_CANCEL", + "name": "Cancel", + "bindings": [ + { "input_method": "keyboard_char", "key": "C" }, + { "input_method": "keyboard_code", "key": "c", "mod": [ "shift" ] }, + { "input_method": "keyboard_any", "key": "c" } + ] + }, { "type": "keybinding", "id": "YES", diff --git a/src/main_menu.cpp b/src/main_menu.cpp index 12019f7ddd8ae..c9e774c8197e7 100644 --- a/src/main_menu.cpp +++ b/src/main_menu.cpp @@ -36,6 +36,7 @@ #include "output.h" #include "overmapbuffer.h" #include "path_info.h" +#include "popup.h" #include "safemode_ui.h" #include "scenario.h" #include "sdlsound.h" @@ -47,6 +48,24 @@ #include "wcwidth.h" #include "worldfactory.h" +enum class main_menu_opts : int { + MOTD = 0, + NEWCHAR = 1, + LOADCHAR = 2, + WORLD = 3, + SPECIAL = 4, + SETTINGS = 5, + HELP = 6, + CREDITS = 7, + QUIT = 8 +}; +static constexpr int max_menu_opts = 8; + +static int getopt( main_menu_opts o ) +{ + return static_cast( o ); +} + void main_menu::on_move() const { sfx::play_variant_sound( "menu_move", "default", 100 ); @@ -92,22 +111,21 @@ static int utf8_width_notags( const char *s ) return w; } -void main_menu::print_menu_items( const catacurses::window &w_in, - const std::vector &vItems, - size_t iSel, point offset, int spacing ) +std::vector main_menu::print_menu_items( const catacurses::window &w_in, + const std::vector &vItems, + size_t iSel, point offset, int spacing, bool main ) { + const point win_offset( getbegx( w_in ), getbegy( w_in ) ); + std::vector ret; std::string text; for( size_t i = 0; i < vItems.size(); ++i ) { if( i > 0 ) { text += std::string( spacing, ' ' ); } + ret.push_back( utf8_width_notags( text.c_str() ) ); - std::string temp = shortcut_text( c_white, vItems[i] ); - if( iSel == i ) { - text += string_format( "[%s]", colorize( remove_color_tags( temp ), h_white ) ); - } else { - text += string_format( "[%s]", temp ); - } + std::string temp = shortcut_text( iSel == i ? hilite( c_yellow ) : c_yellow, vItems[i] ); + text += string_format( "[%s]", colorize( temp, iSel == i ? hilite( c_white ) : c_white ) ); } int text_width = utf8_width_notags( text.c_str() ); @@ -115,11 +133,134 @@ void main_menu::print_menu_items( const catacurses::window &w_in, offset.y -= std::ceil( text_width / getmaxx( w_in ) ); } - fold_and_print( w_in, offset, getmaxx( w_in ), c_light_gray, text, ']' ); + std::vector menu_txt = foldstring( text, getmaxx( w_in ), ']' ); + + int y_off = 0; + int sel_opt = 0; + for( const std::string &txt : menu_txt ) { + trim_and_print( w_in, offset + point( 0, y_off ), getmaxx( w_in ), c_white, txt ); + if( !main ) { + y_off++; + continue; + } + std::vector tmp_chars = utf8_display_split( remove_color_tags( txt ) ); + for( int x = 0; static_cast( x ) < tmp_chars.size(); x++ ) { + if( tmp_chars.at( x ) == "[" ) { + for( int x2 = x; static_cast( x2 ) < tmp_chars.size(); x2++ ) { + if( tmp_chars.at( x2 ) == "]" ) { + inclusive_rectangle rec( win_offset + offset + point( x, y_off ), + win_offset + offset + point( x2, y_off ) ); + main_menu_button_map.emplace_back( rec, sel_opt++ ); + break; + } + } + } + } + y_off++; + } + + return ret; } -void main_menu::print_menu( const catacurses::window &w_open, int iSel, const point &offset ) +void main_menu::display_sub_menu( int sel, const point &bottom_left, int sel_line ) { + main_menu_sub_button_map.clear(); + std::vector sub_opts; + int xlen = 0; + main_menu_opts sel_o = static_cast( sel ); + switch( sel_o ) { + case main_menu_opts::CREDITS: + display_text( mmenu_credits, _( "Credits" ), sel_line ); + return; + case main_menu_opts::MOTD: + //~ Message Of The Day + display_text( mmenu_motd, _( "MOTD" ), sel_line ); + return; + case main_menu_opts::SPECIAL: + for( int i = 1; i < static_cast( special_game_type::NUM_SPECIAL_GAME_TYPES ); i++ ) { + std::string spec_name = special_game_name( static_cast( i ) ); + nc_color clr = i == sel2 ? hilite( c_yellow ) : c_yellow; + sub_opts.push_back( shortcut_text( clr, spec_name ) ); + int len = utf8_width( shortcut_text( clr, spec_name ), true ); + if( len > xlen ) { + xlen = len; + } + } + break; + case main_menu_opts::SETTINGS: + for( int i = 0; static_cast( i ) < vSettingsSubItems.size(); ++i ) { + nc_color clr = i == sel2 ? hilite( c_yellow ) : c_yellow; + sub_opts.push_back( shortcut_text( clr, vSettingsSubItems[i] ) ); + int len = utf8_width( shortcut_text( clr, vSettingsSubItems[i] ), true ); + if( len > xlen ) { + xlen = len; + } + } + break; + case main_menu_opts::NEWCHAR: + for( int i = 0; static_cast( i ) < vNewGameSubItems.size(); i++ ) { + nc_color clr = i == sel2 ? hilite( c_yellow ) : c_yellow; + sub_opts.push_back( shortcut_text( clr, vNewGameSubItems[i] ) ); + int len = utf8_width( shortcut_text( clr, vNewGameSubItems[i] ), true ); + if( len > xlen ) { + xlen = len; + } + } + break; + case main_menu_opts::LOADCHAR: + case main_menu_opts::WORLD: { + const bool extra_opt = sel == getopt( main_menu_opts::WORLD ); + if( extra_opt ) { + sub_opts.emplace_back( colorize( _( "Create World" ), sel2 == 0 ? hilite( c_yellow ) : c_yellow ) ); + xlen = utf8_width( sub_opts.back(), true ); + } + std::vector all_worldnames = world_generator->all_worldnames(); + for( int i = 0; static_cast( i ) < all_worldnames.size(); i++ ) { + int savegames_count = world_generator->get_world( all_worldnames[i] )->world_saves.size(); + nc_color clr = c_white; + if( all_worldnames[i] == "TUTORIAL" || all_worldnames[i] == "DEFENSE" ) { + clr = c_light_cyan; + } + sub_opts.push_back( colorize( string_format( "%s (%d)", all_worldnames[i], savegames_count ), + ( sel2 == i + ( extra_opt ? 1 : 0 ) ) ? hilite( clr ) : clr ) ); + int len = utf8_width( sub_opts.back(), true ); + if( len > xlen ) { + xlen = len; + } + } + } + break; + case main_menu_opts::HELP: + case main_menu_opts::QUIT: + default: + return; + } + + if( sub_opts.empty() ) { + return; + } + + const point top_left( bottom_left + point( 0, -( sub_opts.size() + 1 ) ) ); + catacurses::window w_sub = catacurses::newwin( sub_opts.size() + 2, xlen + 4, top_left ); + werase( w_sub ); + draw_border( w_sub, c_white ); + for( int y = 0; static_cast( y ) < sub_opts.size(); y++ ) { + std::string opt = ( sel2 == y ? "ยป " : " " ) + sub_opts[y]; + int padding = ( xlen + 2 ) - utf8_width( opt, true ); + opt.append( padding, ' ' ); + nc_color clr = sel2 == y ? hilite( c_white ) : c_white; + trim_and_print( w_sub, point( 1, y + 1 ), xlen + 2, clr, opt ); + inclusive_rectangle rec( top_left + point( 1, y + 1 ), top_left + point( xlen + 2, y + 1 ) ); + main_menu_sub_button_map.emplace_back( rec, std::pair { sel, y } ); + } + wnoutrefresh( w_sub ); +} + +void main_menu::print_menu( const catacurses::window &w_open, int iSel, const point &offset, + int sel_line ) +{ + main_menu_button_map.clear(); + // Clear Lines werase( w_open ); @@ -132,8 +273,12 @@ void main_menu::print_menu( const catacurses::window &w_open, int iSel, const po mvwputch( w_open, point( i, window_height - 4 ), c_white, LINE_OXOX ); } - center_print( w_open, window_height - 2, c_red, - _( "Bugs? Suggestions? Use links in MOTD to report them." ) ); + if( iSel == getopt( main_menu_opts::NEWCHAR ) ) { + center_print( w_open, window_height - 2, c_yellow, vNewGameHints[sel2] ); + } else { + center_print( w_open, window_height - 2, c_red, + _( "Bugs? Suggestions? Use links in MOTD to report them." ) ); + } center_print( w_open, window_height - 1, c_light_cyan, string_format( _( "Tip of the day: %s" ), vdaytip ) ); @@ -187,9 +332,14 @@ void main_menu::print_menu( const catacurses::window &w_open, int iSel, const po const int adj_offset = std::max( 0, ( free_space - width_of_spacing ) / 2 ); const int final_offset = offset.x + adj_offset + spacing; - print_menu_items( w_open, vMenuItems, iSel, point( final_offset, offset.y ), spacing ); + std::vector offsets = + print_menu_items( w_open, vMenuItems, iSel, point( final_offset, offset.y ), spacing, true ); wnoutrefresh( w_open ); + + const point p_offset( catacurses::getbegx( w_open ), catacurses::getbegy( w_open ) ); + + display_sub_menu( iSel, p_offset + point( offsets[iSel], offset.y - 2 ), sel_line ); } std::vector main_menu::load_file( const std::string &path, @@ -283,6 +433,32 @@ void main_menu::init_strings() vMenuItems.emplace_back( pgettext( "Main Menu", "redits" ) ); vMenuItems.emplace_back( pgettext( "Main Menu", "uit" ) ); + // new game menu items + vNewGameSubItems.clear(); + vNewGameSubItems.emplace_back( pgettext( "Main Menu|New Game", "Cstom Character" ) ); + vNewGameSubItems.emplace_back( pgettext( "Main Menu|New Game", "reset Character" ) ); + vNewGameSubItems.emplace_back( pgettext( "Main Menu|New Game", "andom Character" ) ); + if( !MAP_SHARING::isSharing() ) { // "Play Now" function doesn't play well together with shared maps + vNewGameSubItems.emplace_back( pgettext( "Main Menu|New Game", + "Play Now! (efault Scenario)" ) ); + vNewGameSubItems.emplace_back( pgettext( "Main Menu|New Game", "Play Nw!" ) ); + } + vNewGameHints.clear(); + vNewGameHints.emplace_back( + _( "Allows you to fully customize points pool, scenario, and character's profession, stats, traits, skills and other parameters." ) ); + vNewGameHints.emplace_back( _( "Select from one of previously created character templates." ) ); + vNewGameHints.emplace_back( + _( "Creates random character, but lets you preview the generated character and the scenario and change character and/or scenario if needed." ) ); + vNewGameHints.emplace_back( + _( "Puts you right in the game, randomly choosing character's traits, profession, skills and other parameters. Scenario is fixed to Evacuee." ) ); + vNewGameHints.emplace_back( + _( "Puts you right in the game, randomly choosing scenario and character's traits, profession, skills and other parameters." ) ); + vNewGameHotkeys.clear(); + vNewGameHotkeys.reserve( vNewGameSubItems.size() ); + for( const std::string &item : vNewGameSubItems ) { + vNewGameHotkeys.push_back( get_hotkeys( item ) ); + } + // determine hotkeys from translated menu item text vMenuHotkeys.clear(); for( const std::string &item : vMenuItems ) { @@ -292,9 +468,9 @@ void main_menu::init_strings() vWorldSubItems.clear(); vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "elete World" ) ); vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "eset World" ) ); - vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "how World Mods" ) ); - vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "opy World Settings" ) ); - vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Character to emplate" ) ); + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Shw World Mods" ) ); + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Copy World Settngs" ) ); + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Character to Temlate" ) ); vWorldHotkeys.clear(); for( const std::string &item : vWorldSubItems ) { @@ -303,10 +479,10 @@ void main_menu::init_strings() vSettingsSubItems.clear(); vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "ptions" ) ); - vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "Kybindings" ) ); - vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "utopickup" ) ); - vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "afemode" ) ); - vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "olors" ) ); + vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "Kebindings" ) ); + vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "Atopickup" ) ); + vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "Saemode" ) ); + vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "Colos" ) ); vSettingsHotkeys.clear(); for( const std::string &item : vSettingsSubItems ) { @@ -320,18 +496,20 @@ void main_menu::init_strings() void main_menu::display_text( const std::string &text, const std::string &title, int &selected ) { - catacurses::window w_border = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, - point( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0, - TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) ); + const int w_open_height = getmaxy( w_open ); + const int b_height = FULL_SCREEN_HEIGHT - clamp( ( FULL_SCREEN_HEIGHT - w_open_height ) + 4, 0, 4 ); + const int vert_off = clamp( ( w_open_height - FULL_SCREEN_HEIGHT ) / 2, getbegy( w_open ), TERMY ); + + catacurses::window w_border = catacurses::newwin( b_height, FULL_SCREEN_WIDTH, + point( clamp( ( TERMX - FULL_SCREEN_WIDTH ) / 2, 0, TERMX ), vert_off ) ); - catacurses::window w_text = catacurses::newwin( FULL_SCREEN_HEIGHT - 2, FULL_SCREEN_WIDTH - 2, - point( 1 + static_cast( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 ), - 1 + static_cast( TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) ) ); + catacurses::window w_text = catacurses::newwin( b_height - 2, FULL_SCREEN_WIDTH - 2, + point( 1 + clamp( ( TERMX - FULL_SCREEN_WIDTH ) / 2, 0, TERMX ), 1 + vert_off ) ); draw_border( w_border, BORDER_COLOR, title ); int width = FULL_SCREEN_WIDTH - 2; - int height = FULL_SCREEN_HEIGHT - 2; + int height = b_height - 2; const auto vFolded = foldstring( text, width ); int iLines = vFolded.size(); @@ -414,12 +592,16 @@ bool main_menu::opening_screen() ctxt.register_cardinal(); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "PREV_TAB" ); - - ctxt.register_action( "QUIT" ); - ctxt.register_action( "CONFIRM" ); - ctxt.register_action( "DELETE_TEMPLATE" ); ctxt.register_action( "PAGE_UP" ); ctxt.register_action( "PAGE_DOWN" ); + ctxt.register_action( "CONFIRM" ); + ctxt.register_action( "QUIT" ); + + // for mouse selection + ctxt.register_action( "SELECT" ); + ctxt.register_action( "MOUSE_MOVE" ); + ctxt.register_action( "SCROLL_UP" ); + ctxt.register_action( "SCROLL_DOWN" ); // for the menu shortcuts ctxt.register_action( "ANY_INPUT" ); @@ -429,56 +611,25 @@ bool main_menu::opening_screen() player_character = avatar(); int sel_line = 0; + size_t last_world_pos = 0; // Make [Load Game] the default cursor position if there's game save available if( !world_generator->all_worldnames().empty() ) { - sel1 = 2; + std::vector worlds = world_generator->all_worldnames(); + last_world_pos = std::find( worlds.begin(), worlds.end(), + world_generator->last_world_name ) - worlds.begin(); + if( last_world_pos >= worlds.size() ) { + last_world_pos = 0; + } + sel1 = getopt( main_menu_opts::LOADCHAR ); + sel2 = last_world_pos; } background_pane background; ui_adaptor ui; ui.on_redraw( [&]( const ui_adaptor & ) { - print_menu( w_open, sel1, menu_offset ); - - if( layer == 1 ) { - if( sel1 == 0 ) { // Print MOTD. - display_text( mmenu_motd, "MOTD", sel_line ); - } else if( sel1 == 7 ) { // Print Credits. - display_text( mmenu_credits, "Credits", sel_line ); - } - } else if( layer == 2 ) { - if( sel1 == 4 ) { // Special game - std::vector special_names; - int xlen = 0; - for( int i = 1; i < static_cast( special_game_type::NUM_SPECIAL_GAME_TYPES ); i++ ) { - std::string spec_name = special_game_name( static_cast( i ) ); - special_names.push_back( spec_name ); - xlen += utf8_width( shortcut_text( c_white, spec_name ), true ) + 2; - } - xlen += special_names.size() - 1; - point offset( menu_offset + point( -( xlen / 4 ) + 32 + extra_w / 2, -2 ) ); - print_menu_items( w_open, special_names, sel2, offset ); - - wnoutrefresh( w_open ); - } else if( sel1 == 5 ) { // Settings Menu - int settings_subs_to_display = vSettingsSubItems.size(); - std::vector settings_subs; - int xlen = 0; - for( int i = 0; i < settings_subs_to_display; ++i ) { - settings_subs.push_back( vSettingsSubItems[i] ); - // Open and close brackets added - xlen += utf8_width( shortcut_text( c_white, vSettingsSubItems[i] ), true ) + 2; - } - xlen += settings_subs.size() - 1; - point offset = menu_offset + point( 46 + extra_w / 2 - ( xlen / 4 ), -2 ); - if( settings_subs.size() > 1 ) { - offset.x -= 6; - } - print_menu_items( w_open, settings_subs, sel2, offset ); - wnoutrefresh( w_open ); - } - } + print_menu( w_open, sel1, menu_offset, sel_line ); } ); ui.on_screen_resize( [this]( ui_adaptor & ui ) { init_windows(); @@ -488,106 +639,165 @@ bool main_menu::opening_screen() while( !start ) { ui_manager::redraw(); - - if( layer == 1 ) { - std::string action = ctxt.handle_input(); - - std::string sInput = ctxt.get_raw_input().text; - - // check automatic menu shortcuts - for( size_t i = 0; i < vMenuHotkeys.size(); ++i ) { - for( const std::string &hotkey : vMenuHotkeys[i] ) { - if( sInput == hotkey ) { - sel1 = i; + std::string action = ctxt.handle_input(); + input_event sInput = ctxt.get_raw_input(); + + // check automatic menu shortcuts + for( int i = 0; static_cast( i ) < vMenuHotkeys.size(); ++i ) { + for( const std::string &hotkey : vMenuHotkeys[i] ) { + if( sInput.text == hotkey && sel1 != i ) { + sel1 = i; + sel2 = i == getopt( main_menu_opts::LOADCHAR ) ? last_world_pos : 0; + sel_line = 0; + if( i == getopt( main_menu_opts::HELP ) ) { action = "CONFIRM"; + } else if( i == getopt( main_menu_opts::QUIT ) ) { + action = "QUIT"; } } } - // also check special keys - if( action == "QUIT" ) { - if( query_yn( _( "Really quit?" ) ) ) { - sel1 = 8; - action = "CONFIRM"; - } - } else if( action == "LEFT" || action == "PREV_TAB" ) { - sel_line = 0; - if( sel1 > 0 ) { - sel1--; - } else { - sel1 = 8; - } - on_move(); - } else if( action == "RIGHT" || action == "NEXT_TAB" ) { - sel_line = 0; - if( sel1 < 8 ) { - sel1++; - } else { - sel1 = 0; + } + if( sel1 == getopt( main_menu_opts::SETTINGS ) ) { + for( int i = 0; static_cast( i ) < vSettingsSubItems.size(); ++i ) { + for( const std::string &hotkey : vSettingsHotkeys[i] ) { + if( sInput.text == hotkey ) { + sel2 = i; + action = "CONFIRM"; + } } - on_move(); } - - if( ( sel1 == 0 || sel1 == 7 ) && ( action == "UP" || action == "DOWN" || - action == "PAGE_UP" || action == "PAGE_DOWN" ) ) { - if( action == "UP" || action == "PAGE_UP" ) { - sel_line--; - } else if( action == "DOWN" || action == "PAGE_DOWN" ) { - sel_line++; + } + if( sel1 == getopt( main_menu_opts::NEWCHAR ) ) { + for( int i = 0; static_cast( i ) < vNewGameSubItems.size(); ++i ) { + for( const std::string &hotkey : vNewGameHotkeys[i] ) { + if( sInput.text == hotkey ) { + sel2 = i; + action = "CONFIRM"; + } } - } - if( ( action == "UP" || action == "CONFIRM" ) && sel1 != 0 && sel1 != 7 ) { - if( sel1 == 6 ) { - get_help().display_help(); - } else if( sel1 == 8 ) { - return false; - } else { - sel2 = 0; - layer = 2; + } - switch( sel1 ) { - case 1: - start = new_character_tab(); - break; - case 2: - start = load_character_tab(); - break; - case 3: - world_tab(); - break; - default: - break; + // handle mouse click + if( action == "SELECT" || action == "MOUSE_MOVE" ) { + cata::optional coord = ctxt.get_coordinates_text( catacurses::stdscr ); + for( const auto &it : main_menu_button_map ) { + if( coord.has_value() && it.first.contains( coord.value() ) ) { + if( sel1 != it.second ) { + sel1 = it.second; + sel2 = sel1 == getopt( main_menu_opts::LOADCHAR ) ? last_world_pos : 0; + sel_line = 0; + } + if( action == "SELECT" && + ( sel1 == getopt( main_menu_opts::HELP ) || sel1 == getopt( main_menu_opts::QUIT ) ) ) { + action = "CONFIRM"; } + ui_manager::redraw(); + break; } } - } else if( layer == 2 ) { - if( sel1 == 4 ) { // Special game - if( MAP_SHARING::isSharing() ) { // Thee can't save special games, therefore thee can't share them - layer = 1; - popup( _( "Special games don't work with shared maps." ) ); - continue; + for( const auto &it : main_menu_sub_button_map ) { + if( coord.has_value() && it.first.contains( coord.value() ) ) { + sel1 = it.second.first; + sel2 = it.second.second; + sel_line = 0; + if( action == "SELECT" ) { + action = "CONFIRM"; + } + ui_manager::redraw(); + break; } + } + } - std::string action = ctxt.handle_input(); - if( action == "LEFT" || action == "PREV_TAB" ) { - if( sel2 > 0 ) { - sel2--; - } else { - sel2 = static_cast( special_game_type::NUM_SPECIAL_GAME_TYPES ) - 2; + // also check special keys + if( action == "QUIT" ) { + if( query_yn( _( "Really quit?" ) ) ) { + return false; + } + } else if( action == "LEFT" || action == "PREV_TAB" ) { + sel_line = 0; + if( sel1 > 0 ) { + sel1--; + } else { + sel1 = max_menu_opts; + } + sel2 = sel1 == getopt( main_menu_opts::LOADCHAR ) ? last_world_pos : 0; + on_move(); + } else if( action == "RIGHT" || action == "NEXT_TAB" ) { + sel_line = 0; + if( sel1 < max_menu_opts ) { + sel1++; + } else { + sel1 = 0; + } + sel2 = sel1 == getopt( main_menu_opts::LOADCHAR ) ? last_world_pos : 0; + on_move(); + } else if( action == "UP" || action == "DOWN" || + action == "PAGE_UP" || action == "PAGE_DOWN" || + action == "SCROLL_UP" || action == "SCROLL_DOWN" ) { + int max_item_count = 0; + int min_item_val = 0; + switch( static_cast( sel1 ) ) { + case main_menu_opts::MOTD: + case main_menu_opts::CREDITS: + if( action == "UP" || action == "PAGE_UP" || action == "SCROLL_UP" ) { + sel_line--; + if( sel_line < 0 ) { + sel_line = 0; + } + } else if( action == "DOWN" || action == "PAGE_DOWN" || action == "SCROLL_DOWN" ) { + sel_line++; + } + break; + case main_menu_opts::LOADCHAR: + max_item_count = world_generator->all_worldnames().size(); + break; + case main_menu_opts::WORLD: + // extra 1 = "Create New World" + max_item_count = world_generator->all_worldnames().size() + 1; + break; + case main_menu_opts::NEWCHAR: + max_item_count = vNewGameSubItems.size(); + break; + case main_menu_opts::SETTINGS: + max_item_count = vSettingsSubItems.size(); + break; + case main_menu_opts::SPECIAL: + max_item_count = static_cast( special_game_type::NUM_SPECIAL_GAME_TYPES ) - 1; + break; + case main_menu_opts::HELP: + case main_menu_opts::QUIT: + default: + break; + } + if( max_item_count > 0 ) { + if( action == "UP" || action == "PAGE_UP" || action == "SCROLL_UP" ) { + sel2--; + if( sel2 < min_item_val ) { + sel2 = max_item_count - 1; } - on_move(); - } else if( action == "RIGHT" || action == "NEXT_TAB" ) { - if( sel2 < static_cast( special_game_type::NUM_SPECIAL_GAME_TYPES ) - 2 ) { - sel2++; - } else { - sel2 = 0; + } else if( action == "DOWN" || action == "PAGE_DOWN" || action == "SCROLL_DOWN" ) { + sel2++; + if( sel2 >= max_item_count ) { + sel2 = min_item_val; } - on_move(); - } else if( action == "DOWN" || action == "QUIT" ) { - layer = 1; } - if( action == "UP" || action == "CONFIRM" ) { - if( sel2 >= 0 && sel2 < static_cast( special_game_type::NUM_SPECIAL_GAME_TYPES ) - 1 ) { + on_move(); + } + } else if( action == "CONFIRM" ) { + switch( static_cast( sel1 ) ) { + case main_menu_opts::HELP: + get_help().display_help(); + break; + case main_menu_opts::QUIT: + return false; + case main_menu_opts::SPECIAL: + if( MAP_SHARING::isSharing() ) { + on_error(); + popup( _( "Special games don't work with shared maps." ) ); + clear_error(); + } else if( sel2 >= 0 && sel2 < static_cast( special_game_type::NUM_SPECIAL_GAME_TYPES ) - 1 ) { on_out_of_scope cleanup( [&player_character]() { g->gamemode.reset(); player_character = avatar(); @@ -597,268 +807,97 @@ bool main_menu::opening_screen() // check world WORLDPTR world = world_generator->make_new_world( static_cast( sel2 + 1 ) ); if( world == nullptr ) { - continue; + break; } world_generator->set_active_world( world ); try { g->setup(); } catch( const std::exception &err ) { debugmsg( "Error: %s", err.what() ); - continue; + break; } if( !g->gamemode->init() ) { - continue; + break; } cleanup.cancel(); start = true; } - } - } else if( sel1 == 5 ) { // Settings Menu - int settings_subs_to_display = vSettingsSubItems.size(); - std::string action = ctxt.handle_input(); - std::string sInput = ctxt.get_raw_input().text; - for( int i = 0; i < settings_subs_to_display; ++i ) { - for( const std::string &hotkey : vSettingsHotkeys[i] ) { - if( sInput == hotkey ) { - sel2 = i; - action = "CONFIRM"; - } - } - } - - if( action == "LEFT" || action == "PREV_TAB" ) { - if( sel2 > 0 ) { - --sel2; - } else { - sel2 = settings_subs_to_display - 1; - } - on_move(); - } else if( action == "RIGHT" || action == "NEXT_TAB" ) { - if( sel2 < settings_subs_to_display - 1 ) { - ++sel2; - } else { - sel2 = 0; - } - on_move(); - } else if( action == "DOWN" || action == "QUIT" ) { - layer = 1; - } - - if( action == "UP" || action == "CONFIRM" ) { - if( sel2 == 0 ) { + break; + case main_menu_opts::SETTINGS: + if( sel2 == 0 ) { /// Options get_options().show( false ); // The language may have changed- gracefully handle this. init_strings(); - } else if( sel2 == 1 ) { + } else if( sel2 == 1 ) { /// Keybindings input_context ctxt_default = get_default_mode_input_context(); ctxt_default.display_menu(); - } else if( sel2 == 2 ) { + } else if( sel2 == 2 ) { /// Autopickup get_auto_pickup().show(); - } else if( sel2 == 3 ) { + } else if( sel2 == 3 ) { /// Safemode get_safemode().show(); - } else if( sel2 == 4 ) { + } else if( sel2 == 4 ) { /// Colors all_colors.show_gui(); } - } + break; + case main_menu_opts::WORLD: + world_tab( sel2 > 0 ? world_generator->all_worldnames().at( sel2 - 1 ) : "" ); + break; + case main_menu_opts::LOADCHAR: + start = load_character_tab( world_generator->all_worldnames().at( sel2 ) ); + break; + case main_menu_opts::NEWCHAR: + start = new_character_tab(); + break; + case main_menu_opts::MOTD: + case main_menu_opts::CREDITS: + default: + break; } } } - return start; + return true; } bool main_menu::new_character_tab() { - std::vector vSubItems; - vSubItems.emplace_back( pgettext( "Main Menu|New Game", "ustom Character" ) ); - vSubItems.emplace_back( pgettext( "Main Menu|New Game", "reset Character" ) ); - vSubItems.emplace_back( pgettext( "Main Menu|New Game", "andom Character" ) ); - if( !MAP_SHARING::isSharing() ) { // "Play Now" function doesn't play well together with shared maps - vSubItems.emplace_back( pgettext( "Main Menu|New Game", "Play Now! (ixed Scenario)" ) ); - vSubItems.emplace_back( pgettext( "Main Menu|New Game", "Play ow!" ) ); - } - std::vector hints; - hints.emplace_back( - _( "Allows you to fully customize points pool, scenario, and character's profession, stats, traits, skills and other parameters." ) ); - hints.emplace_back( _( "Select from one of previously created character templates." ) ); - hints.emplace_back( - _( "Creates random character, but lets you preview the generated character and the scenario and change character and/or scenario if needed." ) ); - hints.emplace_back( - _( "Puts you right in the game, randomly choosing character's traits, profession, skills and other parameters. Scenario is fixed to Evacuee." ) ); - hints.emplace_back( - _( "Puts you right in the game, randomly choosing scenario and character's traits, profession, skills and other parameters." ) ); - - std::vector> vNewGameHotkeys; - vNewGameHotkeys.reserve( vSubItems.size() ); - for( const std::string &item : vSubItems ) { - vNewGameHotkeys.push_back( get_hotkeys( item ) ); - } - - ui_adaptor ui; - ui.on_redraw( [&]( const ui_adaptor & ) { - print_menu( w_open, 1, menu_offset ); - - if( layer == 2 && sel1 == 1 ) { - center_print( w_open, getmaxy( w_open ) - 7, c_yellow, hints[sel2] ); - - print_menu_items( w_open, vSubItems, sel2, menu_offset + point( 0, -2 ) ); - wnoutrefresh( w_open ); - } else if( layer == 3 && sel1 == 1 ) { - // Then view presets - if( templates.empty() ) { - mvwprintz( w_open, menu_offset + point( 20 + extra_w / 2, -4 ), - c_red, "%s", _( "No templates found!" ) ); - } else { - fold_and_print( w_open, menu_offset + point( 20 + extra_w / 2, -2 ), 0, - c_light_gray, "%s", _( "Press [d] to delete a preset." ) ); - for( int i = 0; i < static_cast( templates.size() ); i++ ) { - int line = menu_offset.y - 4 - i; - mvwprintz( w_open, point( 20 + menu_offset.x + extra_w / 2, line ), - ( sel3 == i ? h_white : c_white ), "%s", - templates[i] ); - } - } - wnoutrefresh( w_open ); + avatar &pc = get_avatar(); + // Preset character templates + if( sel2 == 1 ) { + if( templates.empty() ) { + on_error(); + popup( _( "No templates found!" ) ); + clear_error(); + return false; } - } ); - ui.on_screen_resize( [this]( ui_adaptor & ui ) { - init_windows(); - ui.position_from_window( w_open ); - } ); - ui.position_from_window( w_open ); - avatar &player_character = get_avatar(); - - bool start = false; - while( !start && sel1 == 1 && ( layer == 2 || layer == 3 ) ) { - ui_manager::redraw(); - if( layer == 2 && sel1 == 1 ) { - // Then choose custom character, random character, preset, etc - if( MAP_SHARING::isSharing() && - world_generator->all_worldnames().empty() ) { //don't show anything when there are no worlds (will not work if there are special maps) - layer = 1; - sel1 = 1; - continue; - } - - std::string action = ctxt.handle_input(); - std::string sInput = ctxt.get_raw_input().text; - for( size_t i = 0; i < vNewGameHotkeys.size(); ++i ) { - for( const std::string &hotkey : vNewGameHotkeys[i] ) { - if( sInput == hotkey ) { - sel2 = i; - action = "CONFIRM"; - } - } + while( true ) { + uilist mmenu( _( "Choose a preset character template" ), {} ); + mmenu.border_color = c_white; + int opt_val = 0; + for( const std::string &tmpl : templates ) { + mmenu.entries.emplace_back( opt_val++, true, MENU_AUTOASSIGN, tmpl ); } - if( action == "LEFT" || action == "PREV_TAB" ) { - sel2--; - if( sel2 < 0 ) { - sel2 = vSubItems.size() - 1; - } - on_move(); - } else if( action == "RIGHT" || action == "NEXT_TAB" ) { - sel2++; - if( sel2 >= static_cast( vSubItems.size() ) ) { - sel2 = 0; - } - on_move(); - } else if( action == "DOWN" || action == "QUIT" ) { - layer = 1; - sel1 = 1; + mmenu.query(); + opt_val = mmenu.ret; + if( opt_val < 0 || static_cast( opt_val ) >= templates.size() ) { + return false; } - if( action == "UP" || action == "CONFIRM" ) { - if( sel2 == 0 || sel2 == 2 || sel2 == 3 || sel2 == 4 ) { - on_out_of_scope cleanup( [&player_character]() { - player_character = avatar(); - world_generator->set_active_world( nullptr ); - } ); - g->gamemode = nullptr; - // First load the mods, this is done by - // loading the world. - // Pick a world, suppressing prompts if it's "play now" mode. - WORLDPTR world = world_generator->pick_world( true, sel2 == 3 || sel2 == 4 ); - if( world == nullptr ) { - continue; - } - world_generator->set_active_world( world ); - try { - g->setup(); - } catch( const std::exception &err ) { - debugmsg( "Error: %s", err.what() ); - continue; - } - character_type play_type = character_type::CUSTOM; - switch( sel2 ) { - case 0: - play_type = character_type::CUSTOM; - break; - case 2: - play_type = character_type::RANDOM; - break; - case 3: - play_type = character_type::NOW; - break; - case 4: - play_type = character_type::FULL_RANDOM; - break; - } - if( !player_character.create( play_type ) ) { - load_char_templates(); - MAPBUFFER.clear(); - overmap_buffer.clear(); - continue; - } - if( !g->start_game() ) { - continue; - } - cleanup.cancel(); - start = true; - } else if( sel2 == 1 ) { - layer = 3; - sel3 = 0; - } - } - } else if( layer == 3 && sel1 == 1 ) { - // Then view presets - if( templates.empty() ) { - on_error(); - } - std::string action = ctxt.handle_input(); - if( errflag && action != "TIMEOUT" ) { - clear_error(); - sel1 = 1; - layer = 2; - } else if( action == "DOWN" ) { - if( sel3 > 0 ) { - sel3--; - } else { - sel3 = templates.size() - 1; - } - } else if( action == "UP" ) { - if( sel3 < static_cast( templates.size() ) - 1 ) { - sel3++; + std::string res = query_popup() + .context( "LOAD_DELETE_CANCEL" ).default_color( c_white ) + .message( _( "What to do with template \"%s\"?" ), templates[opt_val] ) + .option( "LOAD" ).option( "DELETE" ).option( "CANCEL" ).cursor( 0 ) + .query().action; + if( res == "DELETE" && + query_yn( _( "Are you sure you want to delete %s?" ), templates[opt_val] ) ) { + const auto path = PATH_INFO::templatedir() + templates[opt_val] + ".template"; + if( !remove_file( path ) ) { + popup( _( "Sorry, something went wrong." ) ); } else { - sel3 = 0; + templates.erase( templates.begin() + opt_val ); } - } else if( action == "LEFT" || action == "PREV_TAB" || action == "QUIT" ) { - sel1 = 1; - layer = 2; - } else if( !templates.empty() && action == "DELETE_TEMPLATE" ) { - if( query_yn( _( "Are you sure you want to delete %s?" ), - templates[sel3].c_str() ) ) { - const auto path = PATH_INFO::templatedir() + templates[sel3] + ".template"; - if( !remove_file( path ) ) { - popup( _( "Sorry, something went wrong." ) ); - } else { - templates.erase( templates.begin() + sel3 ); - if( static_cast( sel3 ) > templates.size() - 1 ) { - sel3--; - } - } - } - } else if( action == "RIGHT" || action == "NEXT_TAB" || action == "CONFIRM" ) { - on_out_of_scope cleanup( [&player_character]() { - player_character = avatar(); + } else if( res == "LOAD" ) { + on_out_of_scope cleanup( [&pc]() { + pc = avatar(); world_generator->set_active_world( nullptr ); } ); g->gamemode = nullptr; @@ -873,419 +912,196 @@ bool main_menu::new_character_tab() debugmsg( "Error: %s", err.what() ); continue; } - if( !player_character.create( character_type::TEMPLATE, templates[sel3] ) ) { + if( !pc.create( character_type::TEMPLATE, templates[opt_val] ) ) { load_char_templates(); MAPBUFFER.clear(); overmap_buffer.clear(); - continue; + return false; } if( !g->start_game() ) { - continue; + return false; } cleanup.cancel(); - start = true; + return true; } - } - } // end while - if( start ) { - add_msg( get_scenario()->description( player_character.male ) ); + if( templates.empty() ) { + return false; + } + } + } else { ///Non-template options + on_out_of_scope cleanup( [&pc]() { + pc = avatar(); + world_generator->set_active_world( nullptr ); + } ); + g->gamemode = nullptr; + // First load the mods, this is done by + // loading the world. + // Pick a world, suppressing prompts if it's "play now" mode. + WORLDPTR world = world_generator->pick_world( true, sel2 == 3 || sel2 == 4 ); + if( world == nullptr ) { + return false; + } + world_generator->set_active_world( world ); + try { + g->setup(); + } catch( const std::exception &err ) { + debugmsg( "Error: %s", err.what() ); + return false; + } + character_type play_type = character_type::CUSTOM; + switch( sel2 ) { + case 0: + play_type = character_type::CUSTOM; + break; + case 2: + play_type = character_type::RANDOM; + break; + case 3: + play_type = character_type::NOW; + break; + case 4: + play_type = character_type::FULL_RANDOM; + break; + } + if( !pc.create( play_type ) ) { + load_char_templates(); + MAPBUFFER.clear(); + overmap_buffer.clear(); + return false; + } - world_generator->last_world_name = world_generator->active_world->world_name; - world_generator->last_character_name = player_character.name; - world_generator->save_last_world_info(); + if( !g->start_game() ) { + return false; + } + cleanup.cancel(); + return true; } - return start; + return false; } -bool main_menu::load_character_tab( bool transfer ) +bool main_menu::load_character_tab( const std::string &worldname ) { - bool start = false; - const auto all_worldnames = world_generator->all_worldnames(); - - if( transfer ) { - layer = 3; - sel1 = 2; - sel2 -= 1; - sel3 = 0; - savegames = world_generator->get_world( all_worldnames[sel2] )->world_saves; - } else { - const size_t last_world_pos = std::find( all_worldnames.begin(), all_worldnames.end(), - world_generator->last_world_name ) - all_worldnames.begin(); - if( last_world_pos < all_worldnames.size() ) { - sel2 = last_world_pos; - savegames = world_generator->get_world( all_worldnames[sel2] )->world_saves; - } - - const size_t last_character_pos = std::find_if( savegames.begin(), savegames.end(), - []( const save_t &it ) { - return it.decoded_name() == world_generator->last_character_name; - } ) - savegames.begin(); - if( last_character_pos < savegames.size() ) { - sel3 = last_character_pos; - } else { - sel3 = 0; - } + savegames = world_generator->get_world( worldname )->world_saves; + if( MAP_SHARING::isSharing() ) { + auto new_end = std::remove_if( savegames.begin(), savegames.end(), []( const save_t &str ) { + return str.decoded_name() != MAP_SHARING::getUsername(); + } ); + savegames.erase( new_end, savegames.end() ); } - ui_adaptor ui; - ui.on_redraw( [&]( const ui_adaptor & ) { - const point offset( transfer ? 25 : 15, transfer ? -1 : 0 ); - - print_menu( w_open, transfer ? 3 : 2, menu_offset ); - - if( layer == 2 && sel1 == 2 ) { - if( all_worldnames.empty() ) { - mvwprintz( w_open, menu_offset + point( offset.x + extra_w / 2, -2 ), - c_red, "%s", _( "No Worlds found!" ) ); - } else { - for( int i = 0; i < static_cast( all_worldnames.size() ); ++i ) { - int line = menu_offset.y - 2 - i; - std::string world_name = all_worldnames[i]; - int savegames_count = world_generator->get_world( world_name )->world_saves.size(); - nc_color color1; - nc_color color2; - if( world_name == "TUTORIAL" || world_name == "DEFENSE" ) { - color1 = c_light_cyan; - color2 = h_light_cyan; - } else { - color1 = c_white; - color2 = h_white; - } - mvwprintz( w_open, offset + point( extra_w / 2 + menu_offset.x, line ), - ( sel2 == i ? color2 : color1 ), "%s (%d)", - world_name, savegames_count ); - } - } - wnoutrefresh( w_open ); - } else if( layer == 3 && sel1 == 2 ) { - savegames = world_generator->get_world( all_worldnames[sel2] )->world_saves; - - const std::string &wn = all_worldnames[sel2]; - - mvwprintz( w_open, menu_offset + offset + point( extra_w / 2, -2 - sel2 ), h_white, - "%s", wn ); + if( savegames.empty() ) { + on_error(); + //~ %s = world name + popup( _( "%s has no characters to load!" ), worldname ); + clear_error(); + return false; + } - if( savegames.empty() ) { - mvwprintz( w_open, menu_offset + point( 40 + extra_w / 2, -2 - sel2 + offset.y ), - c_red, "%s", _( "No save games found!" ) ); - } else { - int line = menu_offset.y - 2; + uilist mmenu( string_format( _( "Load character from \"%s\"" ), worldname ), {} ); + mmenu.border_color = c_white; + int opt_val = 0; + for( const save_t &s : savegames ) { + mmenu.entries.emplace_back( opt_val++, true, MENU_AUTOASSIGN, s.decoded_name() ); + } + mmenu.query(); + opt_val = mmenu.ret; + if( opt_val < 0 || static_cast( opt_val ) >= savegames.size() ) { + return false; + } - for( const auto &savename : savegames ) { - const bool selected = sel3 + line == menu_offset.y - 2; - mvwprintz( w_open, point( ( 16 + wn.length() ) + menu_offset.x + extra_w / 2, line-- + offset.y ), - selected ? h_white : c_white, - "%s", savename.decoded_name() ); - } - } - wnoutrefresh( w_open ); - } + avatar &pc = get_avatar(); + on_out_of_scope cleanup( [&pc]() { + pc = avatar(); + world_generator->set_active_world( nullptr ); } ); - ui.on_screen_resize( [this]( ui_adaptor & ui ) { - init_windows(); - ui.position_from_window( w_open ); - } ); - ui.position_from_window( w_open ); - avatar &player_character = get_avatar(); - - while( !start && sel1 == 2 && ( layer == 2 || layer == 3 ) ) { - ui_manager::redraw(); - if( layer == 2 && sel1 == 2 ) { - if( all_worldnames.empty() ) { - on_error(); - } - std::string action = ctxt.handle_input(); - if( errflag && action != "TIMEOUT" ) { - clear_error(); - layer = 1; - } else if( action == "DOWN" ) { - if( sel2 > 0 ) { - sel2--; - } else { - sel2 = all_worldnames.size() - 1; - } - } else if( action == "UP" ) { - if( sel2 < static_cast( all_worldnames.size() ) - 1 ) { - sel2++; - } else { - sel2 = 0; - } - } else if( action == "LEFT" || action == "PREV_TAB" || action == "QUIT" ) { - layer = 1; - } else if( action == "RIGHT" || action == "NEXT_TAB" || action == "CONFIRM" ) { - if( sel2 >= 0 && sel2 < static_cast( all_worldnames.size() ) ) { - layer = 3; - } - } - } else if( layer == 3 && sel1 == 2 ) { - savegames = world_generator->get_world( all_worldnames[sel2] )->world_saves; - if( MAP_SHARING::isSharing() ) { - auto new_end = std::remove_if( savegames.begin(), savegames.end(), - []( const save_t &str ) { - return str.decoded_name() != MAP_SHARING::getUsername(); - } ); - savegames.erase( new_end, savegames.end() ); - } - - if( savegames.empty() ) { - on_error(); - } - std::string action = ctxt.handle_input(); - if( errflag && action != "TIMEOUT" ) { - clear_error(); - layer = transfer ? 1 : 2; - } else if( action == "DOWN" ) { - if( sel3 > 0 ) { - sel3--; - } else { - sel3 = savegames.size() - 1; - } - } else if( action == "UP" ) { - if( sel3 < static_cast( savegames.size() - 1 ) ) { - sel3++; - } else { - sel3 = 0; - } - } else if( action == "LEFT" || action == "PREV_TAB" || action == "QUIT" ) { - layer = transfer ? 1 : 2; - sel3 = 0; - } - if( action == "RIGHT" || action == "NEXT_TAB" || action == "CONFIRM" ) { - if( sel3 >= 0 && sel3 < static_cast( savegames.size() ) ) { - on_out_of_scope cleanup( [&player_character]() { - player_character = avatar(); - world_generator->set_active_world( nullptr ); - } ); - - g->gamemode = nullptr; - WORLDPTR world = world_generator->get_world( all_worldnames[sel2] ); - world_generator->last_world_name = world->world_name; - world_generator->last_character_name = savegames[sel3].decoded_name(); - world_generator->save_last_world_info(); - world_generator->set_active_world( world ); - - try { - g->setup(); - } catch( const std::exception &err ) { - debugmsg( "Error: %s", err.what() ); - continue; - } - - if( g->load( savegames[sel3] ) ) { - cleanup.cancel(); - start = true; - } - } - } - } - } // end while + g->gamemode = nullptr; + WORLDPTR world = world_generator->get_world( worldname ); + world_generator->last_world_name = world->world_name; + world_generator->last_character_name = savegames[opt_val].decoded_name(); + world_generator->save_last_world_info(); + world_generator->set_active_world( world ); + + try { + g->setup(); + } catch( const std::exception &err ) { + debugmsg( "Error: %s", err.what() ); + return false; + } - if( transfer ) { - layer = 3; - sel1 = 3; - sel2++; - sel3 = vWorldSubItems.size() - 1; + if( g->load( savegames[opt_val] ) ) { + cleanup.cancel(); + return true; } - return start; + return false; } -void main_menu::world_tab() +void main_menu::world_tab( const std::string &worldname ) { - ui_adaptor ui; - ui.on_redraw( [this]( const ui_adaptor & ) { - if( sel1 == 3 ) { // bail out if we're actually in load_character_tab - print_menu( w_open, 3, menu_offset ); - - if( layer == 3 ) { // World Menu - const point offset = menu_offset + point( 40 + extra_w / 2, -2 - sel2 ); - - const auto all_worldnames = world_generator->all_worldnames(); - mvwprintz( w_open, offset + point( -15, 0 ), h_white, "%s", all_worldnames[sel2 - 1] ); - - for( size_t i = 0; i < vWorldSubItems.size(); ++i ) { - nc_color text_color; - nc_color key_color; - if( sel3 == static_cast( i ) ) { - text_color = h_white; - key_color = h_white; - } else { - text_color = c_light_gray; - key_color = c_white; - } - wmove( w_open, offset + point( 0, -i ) ); - wprintz( w_open, c_light_gray, "[" ); - shortcut_print( w_open, text_color, key_color, vWorldSubItems[i] ); - wprintz( w_open, c_light_gray, "]" ); - } + // Create world + if( sel2 == 0 ) { + world_generator->make_new_world(); + return; + } - wnoutrefresh( w_open ); - } else if( layer == 2 ) { // Show world names - mvwprintz( w_open, menu_offset + point( 25 + extra_w / 2, -2 ), - ( sel2 == 0 ? h_white : c_white ), "%s", _( "Create World" ) ); - - int i = 1; - const auto all_worldnames = world_generator->all_worldnames(); - for( auto it = all_worldnames.begin(); it != all_worldnames.end(); ++it, i++ ) { - int savegames_count = world_generator->get_world( *it )->world_saves.size(); - int line = menu_offset.y - 2 - i; - nc_color color1; - nc_color color2; - if( *it == "TUTORIAL" || *it == "DEFENSE" ) { - color1 = c_light_cyan; - color2 = h_light_cyan; - } else { - color1 = c_white; - color2 = h_white; - } - mvwprintz( w_open, point( 25 + menu_offset.x + extra_w / 2, line ), - ( sel2 == i ? color2 : color1 ), "%s (%d)", ( *it ).c_str(), savegames_count ); - } + uilist mmenu( string_format( _( "Manage world \"%s\"" ), worldname ), {} ); + mmenu.border_color = c_white; + int opt_val = 0; + for( const std::string &it : vWorldSubItems ) { + mmenu.entries.emplace_back( opt_val++, true, MENU_AUTOASSIGN, + remove_color_tags( shortcut_text( c_white, it ) ) ); + } + mmenu.query(); + opt_val = mmenu.ret; + if( opt_val < 0 || static_cast( opt_val ) >= vWorldSubItems.size() ) { + return; + } - wnoutrefresh( w_open ); - } + auto clear_world = [this, &worldname]( bool do_delete ) { + world_generator->delete_world( worldname, do_delete ); + savegames.clear(); + MAPBUFFER.clear(); + overmap_buffer.clear(); + if( do_delete ) { + sel2 = 0; // reset to create world selection } - } ); - ui.on_screen_resize( [this]( ui_adaptor & ui ) { - init_windows(); - ui.position_from_window( w_open ); - } ); - ui.position_from_window( w_open ); + }; - avatar &player_character = get_avatar(); - while( sel1 == 3 && ( layer == 2 || layer == 3 || layer == 4 ) ) { - ui_manager::redraw(); - if( layer == 4 ) { //Character to Template - if( load_character_tab( true ) ) { - player_character.setID( character_id(), true ); - player_character.reset_all_missions(); - player_character.character_to_template( player_character.name ); - - player_character = avatar(); + switch( opt_val ) { + case 0: // Delete World + if( query_yn( _( "Delete the world and all saves within?" ) ) ) { + clear_world( true ); + } + break; + case 1: // Reset World + if( query_yn( _( "Remove all saves and regenerate world?" ) ) ) { + clear_world( false ); + } + break; + case 2: // Active World Mods + world_generator->show_active_world_mods( + world_generator->get_world( worldname )->active_mod_order ); + break; + case 3: // Copy World settings + world_generator->make_new_world( true, worldname ); + break; + case 4: // Character to Template + if( load_character_tab( worldname ) ) { + avatar &pc = get_avatar(); + pc.setID( character_id(), true ); + pc.reset_all_missions(); + pc.character_to_template( pc.name ); + pc = avatar(); MAPBUFFER.clear(); overmap_buffer.clear(); - load_char_templates(); - - layer = 3; - } - } else if( layer == 3 ) { // World Menu - // Show options for Destroy, Reset worlds. - // Reset and Destroy ask for world to modify. - // Reset empties world of everything but options, then makes new world within it. - // Destroy asks for confirmation, then destroys everything in world and then removes world folder. - - const auto all_worldnames = world_generator->all_worldnames(); - - std::string action = ctxt.handle_input(); - std::string sInput = ctxt.get_raw_input().text; - for( size_t i = 0; i < vWorldSubItems.size(); ++i ) { - for( const std::string &hotkey : vWorldHotkeys[i] ) { - if( sInput == hotkey ) { - sel3 = i; - action = "CONFIRM"; - } - } - } - - if( action == "DOWN" ) { - if( sel3 > 0 ) { - --sel3; - } else { - sel3 = vWorldSubItems.size() - 1; - } - on_move(); - } else if( action == "UP" ) { - if( sel3 < static_cast( vWorldSubItems.size() ) - 1 ) { - ++sel3; - } else { - sel3 = 0; - } - on_move(); - } else if( action == "LEFT" || action == "PREV_TAB" || action == "QUIT" ) { - layer = 2; - } - - if( action == "RIGHT" || action == "NEXT_TAB" || action == "CONFIRM" ) { - if( sel3 == 2 ) { // Active World Mods - WORLDPTR world = world_generator->get_world( all_worldnames[sel2 - 1] ); - world_generator->show_active_world_mods( world->active_mod_order ); - - } else { - bool query_yes = false; - bool do_delete = false; - if( sel3 == 0 ) { // Delete World - if( query_yn( _( "Delete the world and all saves within?" ) ) ) { - query_yes = true; - do_delete = true; - } - } else if( sel3 == 1 ) { // Reset World - if( query_yn( _( "Remove all saves and regenerate world?" ) ) ) { - query_yes = true; - do_delete = false; - } - } else if( sel3 == 3 ) { // Copy World settings - layer = 2; - world_generator->make_new_world( true, all_worldnames[sel2 - 1] ); - } else if( sel3 == 4 ) { // Character to Template - layer = 4; - sel4 = 0; - } - - if( query_yes ) { - layer = 2; // Go to world submenu, not list of worlds - - world_generator->delete_world( all_worldnames[sel2 - 1], do_delete ); - - savegames.clear(); - MAPBUFFER.clear(); - overmap_buffer.clear(); - - if( do_delete ) { - sel2 = 0; // reset to create world selection - } - } - } - } - } else if( layer == 2 ) { // Show world names - if( MAP_SHARING::isSharing() && !MAP_SHARING::isWorldmenu() && !MAP_SHARING::isAdmin() ) { - layer = 1; - popup( _( "Only the admin can change worlds." ) ); - continue; - } - - const auto all_worldnames = world_generator->all_worldnames(); - - std::string action = ctxt.handle_input(); - - if( action == "DOWN" ) { - if( sel2 > 0 ) { - --sel2; - } else { - sel2 = all_worldnames.size(); - } - } else if( action == "UP" ) { - if( sel2 < static_cast( all_worldnames.size() ) ) { - ++sel2; - } else { - sel2 = 0; - } - } else if( action == "LEFT" || action == "PREV_TAB" || action == "QUIT" ) { - layer = 1; - } - if( action == "RIGHT" || action == "NEXT_TAB" || action == "CONFIRM" ) { - if( sel2 == 0 ) { - world_generator->make_new_world(); - - } else { - layer = 3; - sel3 = 0; - } } - } - } // end while layer == ... + break; + default: + break; + } } std::string main_menu::halloween_spider() diff --git a/src/main_menu.h b/src/main_menu.h index 566221ba53835..5e01e3e60ebda 100644 --- a/src/main_menu.h +++ b/src/main_menu.h @@ -6,6 +6,7 @@ #include #include +#include "cuboid_rectangle.h" #include "cursesdef.h" #include "enums.h" #include "input.h" @@ -26,10 +27,13 @@ class main_menu std::string mmenu_credits; std::vector vMenuItems; // MOTD, New Game, Load Game, etc. std::vector vWorldSubItems; + std::vector vNewGameSubItems; + std::vector vNewGameHints; std::vector< std::vector > vWorldHotkeys; std::vector vSettingsSubItems; std::vector< std::vector > vSettingsHotkeys; std::vector< std::vector > vMenuHotkeys; // hotkeys for the vMenuItems + std::vector< std::vector > vNewGameHotkeys; std::string vdaytip; //tip of the day /** @@ -55,8 +59,8 @@ class main_menu // Tab functions. They return whether a game was started or not. The ones that can never // start a game have a void return type. bool new_character_tab(); - bool load_character_tab( bool transfer = false ); - void world_tab(); + bool load_character_tab( const std::string &worldname ); + void world_tab( const std::string &worldname ); /* * Load character templates from template folder @@ -68,15 +72,14 @@ class main_menu input_context ctxt; int sel1 = 1; int sel2 = 1; - int sel3 = 1; - int sel4 = 1; - int layer = 1; point LAST_TERM; catacurses::window w_open; point menu_offset; std::vector templates; int extra_w = 0; std::vector savegames; + std::vector, std::pair>> main_menu_sub_button_map; + std::vector, int>> main_menu_button_map; /** * Prints a horizontal list of options @@ -87,10 +90,11 @@ class main_menu * make it stand out from the other menu items. * @param offset Offset of menu items * @param spacing: How many spaces to print between each menu item + * @returns A list of horizontal offsets, one for each menu item */ - void print_menu_items( const catacurses::window &w_in, - const std::vector &vItems, size_t iSel, - point offset, int spacing = 1 ); + std::vector print_menu_items( const catacurses::window &w_in, + const std::vector &vItems, size_t iSel, + point offset, int spacing = 1, bool main = false ); /** * Called by @ref opening_screen, this prints all the text that you see on the main menu @@ -99,10 +103,12 @@ class main_menu * @param iSel which index in vMenuItems is selected * @param offset Menu location in window */ - void print_menu( const catacurses::window &w_open, int iSel, const point &offset ); + void print_menu( const catacurses::window &w_open, int iSel, const point &offset, int sel_line ); void display_text( const std::string &text, const std::string &title, int &selected ); + void display_sub_menu( int sel, const point &bottom_left, int sel_line ); + void init_windows(); holiday get_holiday_from_time(); diff --git a/tools/spell_checker/dictionary.txt b/tools/spell_checker/dictionary.txt index d73f8cf9b6c34..79d7848aaa3d3 100644 --- a/tools/spell_checker/dictionary.txt +++ b/tools/spell_checker/dictionary.txt @@ -13,7 +13,6 @@ adze aerarium aerially aeterna -afemode afterbirth afterimage afterlife @@ -509,6 +508,7 @@ editable edmontonia edmontosaurus eeds +efault eidolon elderberries electrochemistry @@ -518,6 +518,7 @@ electroreceptors elete elopements elytra +emode emove emplate encumbering @@ -866,7 +867,6 @@ isassembled issions itemname iterating -ixed j jabberwock jabberwocks @@ -1161,6 +1161,7 @@ neutrite nevah newsbots newsweekly +ngs nibblin nibelung nichrome @@ -1212,7 +1213,6 @@ openable openair opengl openingly -opy orichalcum ornithomimid ornithomimus @@ -1768,6 +1768,7 @@ toolmod toolset toolshelf topazes +topickup toque toques toroids @@ -1872,7 +1873,6 @@ usselves ustom utahraptor utilibot -utopickup uttarasanga v vajra