diff --git a/.clang-tidy b/.clang-tidy index 1f050940983f..d8a4e7dc8571 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -24,7 +24,6 @@ Checks: > -bugprone-infinite-loop, -bugprone-misplaced-widening-cast, -bugprone-narrowing-conversions, - -bugprone-unused-return-value, -bugprone-redundant-branch-condition, -bugprone-reserved-identifier, -bugprone-signed-char-misuse, diff --git a/data/raw/keybindings.json b/data/raw/keybindings.json index 5bc2f1db30d8..59cd9d5a2c67 100644 --- a/data/raw/keybindings.json +++ b/data/raw/keybindings.json @@ -2922,6 +2922,27 @@ { "input_method": "keyboard", "key": "ESC" } ] }, + { + "type": "keybinding", + "id": "LOAD", + "category": "LOAD_DELETE_CANCEL", + "name": "Load", + "bindings": [ { "input_method": "keyboard", "key": "L" } ] + }, + { + "type": "keybinding", + "id": "DELETE", + "category": "LOAD_DELETE_CANCEL", + "name": "Delete", + "bindings": [ { "input_method": "keyboard", "key": "D" } ] + }, + { + "type": "keybinding", + "id": "CANCEL", + "category": "LOAD_DELETE_CANCEL", + "name": "Cancel", + "bindings": [ { "input_method": "keyboard", "key": "C" } ] + }, { "type": "keybinding", "id": "YES", diff --git a/src/avatar.cpp b/src/avatar.cpp index 084f3ced9be9..85af21ac754d 100644 --- a/src/avatar.cpp +++ b/src/avatar.cpp @@ -195,7 +195,7 @@ mission *avatar::get_active_mission() const return active_mission; } -void avatar::reset_all_misions() +void avatar::reset_all_missions() { active_mission = nullptr; active_missions.clear(); diff --git a/src/avatar.h b/src/avatar.h index 3574635d0648..5d74f7f2f884 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -73,6 +73,7 @@ class avatar : public player void randomize( bool random_scenario, points_left &points, bool play_now = false ); bool load_template( const std::string &template_name, points_left &points ); void save_template( const std::string &name, const points_left &points ); + void character_to_template( const std::string &name ); bool is_avatar() const override { return true; @@ -84,6 +85,13 @@ class avatar : public player return this; } + std::string get_save_id() const { + return save_id.empty() ? name : save_id; + } + void set_save_id( const std::string &id ) { + save_id = id; + } + void toggle_map_memory(); bool should_show_map_memory(); void prepare_map_memory_region( const tripoint &p1, const tripoint &p2 ); @@ -103,7 +111,7 @@ class avatar : public player /** Provides the window and detailed morale data */ void disp_morale(); /** Resets all missions before saving character to template */ - void reset_all_misions(); + void reset_all_missions(); std::vector get_active_missions() const; std::vector get_completed_missions() const; @@ -220,6 +228,9 @@ class avatar : public player } private: + // The name used to generate save filenames for this avatar. Not serialized in json. + std::string save_id; + std::unique_ptr player_map_memory; bool show_map_memory = true; diff --git a/src/cata_utility.cpp b/src/cata_utility.cpp index 780068dd9b23..75740c915082 100644 --- a/src/cata_utility.cpp +++ b/src/cata_utility.cpp @@ -13,6 +13,7 @@ #include #include "debug.h" +#include "enum_conversions.h" #include "filesystem.h" #include "json.h" #include "options.h" @@ -670,3 +671,104 @@ void deserialize_wrapper( const std::function &callback, const JsonIn jsin( buffer ); callback( jsin ); } + +/* compare against table of easter dates */ +static bool is_easter( int day, int month, int year ) +{ + if( month == 3 ) { + switch( year ) { + // *INDENT-OFF* + case 2024: return day == 31; + case 2027: return day == 28; + default: break; + // *INDENT-ON* + } + } else if( month == 4 ) { + switch( year ) { + // *INDENT-OFF* + case 2021: return day == 4; + case 2022: return day == 17; + case 2023: return day == 9; + case 2025: return day == 20; + case 2026: return day == 5; + case 2028: return day == 16; + case 2029: return day == 1; + case 2030: return day == 21; + default: break; + // *INDENT-ON* + } + } + return false; +} + +holiday get_holiday_from_time( std::time_t time, bool force_refresh ) +{ + static holiday cached_holiday = holiday::none; + static bool is_cached = false; + + if( force_refresh ) { + is_cached = false; + } + if( is_cached ) { + return cached_holiday; + } + + is_cached = true; + + bool success = false; + + std::tm local_time; + std::time_t current_time = time == 0 ? std::time( nullptr ) : time; + + /* necessary to pass LGTM, as threadsafe version of localtime differs by platform */ +#if defined(_WIN32) + + errno_t err = localtime_s( &local_time, ¤t_time ); + if( err == 0 ) { + success = true; + } + +#else + + success = !!localtime_r( ¤t_time, &local_time ); + +#endif + + if( success ) { + + const int month = local_time.tm_mon + 1; + const int day = local_time.tm_mday; + const int wday = local_time.tm_wday; + const int year = local_time.tm_year + 1900; + + /* check date against holidays */ + if( month == 1 && day == 1 ) { + cached_holiday = holiday::new_year; + return cached_holiday; + } + // only run easter date calculation if currently March or April + else if( ( month == 3 || month == 4 ) && is_easter( day, month, year ) ) { + cached_holiday = holiday::easter; + return cached_holiday; + } else if( month == 7 && day == 4 ) { + cached_holiday = holiday::independence_day; + return cached_holiday; + } + // 13 days seems appropriate for Halloween + else if( month == 10 && day >= 19 ) { + cached_holiday = holiday::halloween; + return cached_holiday; + } else if( month == 11 && ( day >= 22 && day <= 28 ) && wday == 4 ) { + cached_holiday = holiday::thanksgiving; + return cached_holiday; + } + // For the 12 days of Christmas, my true love gave to me... + else if( month == 12 && ( day >= 14 && day <= 25 ) ) { + cached_holiday = holiday::christmas; + return cached_holiday; + } + } + // fall through to here if localtime fails, or none of the day tests hit + cached_holiday = holiday::none; + return cached_holiday; +} diff --git a/src/cata_utility.h b/src/cata_utility.h index 51232fca8648..edf6e499d4bc 100644 --- a/src/cata_utility.h +++ b/src/cata_utility.h @@ -2,14 +2,18 @@ #ifndef CATA_SRC_CATA_UTILITY_H #define CATA_SRC_CATA_UTILITY_H +#include +#include +#include #include #include #include #include -#include #include #include +#include "enums.h" + /** * Greater-than comparison operator; required by the sort interface */ @@ -285,4 +289,11 @@ class restore_on_out_of_scope // *INDENT-ON* }; +/** + * Get the current holiday based on the given time, or based on current time if time = 0 + * @param time The timestampt to assess + * @param force_refresh Force recalculation of current holiday, otherwise use cached value +*/ +holiday get_holiday_from_time( std::time_t time = 0, bool force_refresh = false ); + #endif // CATA_SRC_CATA_UTILITY_H diff --git a/src/character.h b/src/character.h index 80a4655002b8..b00b84e090fb 100644 --- a/src/character.h +++ b/src/character.h @@ -1526,7 +1526,7 @@ class Character : public Creature, public visitable bool crossed_threshold() const; // --------------- Values --------------- - std::string name; + std::string name; // Pre-cataclysm name, invariable bool male = true; std::list worn; diff --git a/src/debug_menu.cpp b/src/debug_menu.cpp index 894106cbef22..33af84abada1 100644 --- a/src/debug_menu.cpp +++ b/src/debug_menu.cpp @@ -803,16 +803,31 @@ void character_edit_menu( Character &c ) case edit_character::desc: { uilist smenu; smenu.text = _( "Select a value and press enter to change it." ); - smenu.addentry( 0, true, 'n', "%s: %s", _( "Current name" ), p.get_name() ); - smenu.addentry( 1, true, 'a', "%s: %d", _( "Current age" ), p.base_age() ); - smenu.addentry( 2, true, 'h', "%s: %d", _( "Current height in cm" ), p.base_height() ); + if( p.is_avatar() ) { + smenu.addentry( 0, true, 's', "%s: %s", _( "Current save file name" ), get_avatar().get_save_id() ); + } + smenu.addentry( 1, true, 'n', "%s: %s", _( "Current pre-Cataclysm name" ), p.name ); + smenu.addentry( 2, true, 'a', "%s: %d", _( "Current age" ), p.base_age() ); + smenu.addentry( 3, true, 'h', "%s: %d", _( "Current height in cm" ), p.base_height() ); smenu.query(); switch( smenu.ret ) { case 0: { + std::string buf = get_avatar().get_save_id(); + string_input_popup popup; + popup + .title( _( "Rename save file (WARNING: this will duplicate the save):" ) ) + .width( 85 ) + .edit( buf ); + if( popup.confirmed() ) { + get_avatar().set_save_id( buf ); + } + } + break; + case 1: { std::string buf = p.name; string_input_popup popup; popup - .title( _( "Rename:" ) ) + .title( _( "Rename character:" ) ) .width( 85 ) .edit( buf ); if( popup.confirmed() ) { @@ -820,7 +835,7 @@ void character_edit_menu( Character &c ) } } break; - case 1: { + case 2: { string_input_popup popup; popup .title( _( "Enter age in years. Minimum 16, maximum 55" ) ) @@ -832,7 +847,7 @@ void character_edit_menu( Character &c ) } } break; - case 2: { + case 3: { string_input_popup popup; popup .title( _( "Enter height in centimeters. Minimum 145, maximum 200" ) ) diff --git a/src/enums.h b/src/enums.h index 0a0716c6d33e..d489e7aadf38 100644 --- a/src/enums.h +++ b/src/enums.h @@ -64,7 +64,7 @@ enum visibility_type { }; // Matching rules for comparing a string to an overmap terrain id. -enum ot_match_type { +enum class ot_match_type { // The provided string must completely match the overmap terrain id, including // linear direction suffixes for linear terrain types or rotation suffixes // for rotated terrain types. @@ -89,11 +89,11 @@ struct enum_traits { static constexpr ot_match_type last = ot_match_type::num_ot_match_type; }; -enum special_game_id : int { - SGAME_NULL = 0, - SGAME_TUTORIAL, - SGAME_DEFENSE, - NUM_SPECIAL_GAMES +enum class special_game_type : int { + NONE = 0, + TUTORIAL, + DEFENSE, + NUM_SPECIAL_GAME_TYPES }; enum art_effect_passive : int { diff --git a/src/game.cpp b/src/game.cpp index fa88554fd10a..956938580f23 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -529,12 +529,12 @@ void game::setup() bool game::has_gametype() const { - return gamemode && gamemode->id() != SGAME_NULL; + return gamemode && gamemode->id() != special_game_type::NONE; } -special_game_id game::gametype() const +special_game_type game::gametype() const { - return gamemode ? gamemode->id() : SGAME_NULL; + return gamemode ? gamemode->id() : special_game_type::NONE; } void game::load_map( const tripoint &pos_sm, const bool pump_events ) @@ -585,7 +585,7 @@ bool game::start_game() if( omtstart == overmap::invalid_tripoint ) { if( query_yn( _( "Try again?\n\nIt may require several attempts until the game finds a valid starting location." ) ) ) { - MAPBUFFER.reset(); + MAPBUFFER.clear(); overmap_buffer.clear(); } else { return false; @@ -1137,10 +1137,10 @@ bool game::cleanup_at_end() move_save_to_graveyard( char_filename ); write_memorial_file( char_filename, sLastWords ); memorial().clear(); - std::vector characters = list_active_characters(); + std::vector characters = list_active_saves(); // remove current player from the active characters list, as they are dead std::vector::iterator curchar = std::find( characters.begin(), - characters.end(), u.name ); + characters.end(), u.get_save_id() ); if( curchar != characters.end() ) { characters.erase( curchar ); } @@ -1211,7 +1211,7 @@ bool game::cleanup_at_end() sfx::fade_audio_group( sfx::group::context_themes, 300 ); sfx::fade_audio_group( sfx::group::fatigue, 300 ); - MAPBUFFER.reset(); + MAPBUFFER.clear(); overmap_buffer.clear(); #if defined(__ANDROID__) @@ -2465,7 +2465,7 @@ void game::move_save_to_graveyard( const std::string &dirname ) const std::string save_dir = get_world_base_save_path(); const std::string graveyard_dir = PATH_INFO::graveyarddir() + "/"; const std::string graveyard_save_dir = graveyard_dir + dirname + "/"; - const std::string prefix = base64_encode( u.name ) + "."; + const std::string &prefix = base64_encode( u.get_save_id() ) + "."; if( !assure_dir_exist( graveyard_dir ) ) { debugmsg( "could not create graveyard path '%s'", graveyard_dir ); @@ -2545,12 +2545,10 @@ bool game::load( const save_t &name ) // Now load up the master game data; factions (and more?) load_master(); u = avatar(); - u.name = name.player_name(); - // This should be initialized more globally (in player/Character constructor) - u.weapon = item( "null", calendar::start_of_cataclysm ); if( !read_from_file( playerpath + SAVE_EXTENSION, std::bind( &game::unserialize, this, _1 ) ) ) { return false; } + u.set_save_id( name.decoded_name() ); u.load_map_memory(); u.get_avatar_diary()->load(); @@ -2735,7 +2733,7 @@ bool game::save() }, _( "uistate data" ) ) ) { return false; } else { - world_generator->active_world->add_save( save_t::from_player_name( u.name ) ); + world_generator->active_world->add_save( save_t::from_save_id( u.get_save_id() ) ); return true; } } catch( std::ios::failure &err ) { @@ -2744,11 +2742,11 @@ bool game::save() } } -std::vector game::list_active_characters() +std::vector game::list_active_saves() { std::vector saves; for( auto &worldsave : world_generator->active_world->world_saves ) { - saves.push_back( worldsave.player_name() ); + saves.push_back( worldsave.decoded_name() ); } return saves; } @@ -11256,19 +11254,19 @@ void game::quickload() return; } - if( active_world->save_exists( save_t::from_player_name( u.name ) ) ) { + if( active_world->save_exists( save_t::from_save_id( u.get_save_id() ) ) ) { if( moves_since_last_save != 0 ) { // See if we need to reload anything - MAPBUFFER.reset(); + MAPBUFFER.clear(); overmap_buffer.clear(); try { setup(); } catch( const std::exception &err ) { debugmsg( "Error: %s", err.what() ); } - load( save_t::from_player_name( u.name ) ); + load( save_t::from_save_id( u.get_save_id() ) ); } } else { - popup_getkey( _( "No saves for %s yet." ), u.name ); + popup_getkey( _( "No saves for current character yet." ) ); } } @@ -11968,7 +11966,7 @@ Creature *game::get_creature_if( const std::function & std::string game::get_player_base_save_path() const { - return get_world_base_save_path() + "/" + base64_encode( u.name ); + return get_world_base_save_path() + "/" + base64_encode( get_avatar().get_save_id() ); } std::string game::get_world_base_save_path() const @@ -12025,3 +12023,12 @@ distribution_grid_tracker &get_distribution_grid_tracker() { return *g->grid_tracker_ptr; } + +const scenario *get_scenario() +{ + return g->scen; +} +void set_scenario( const scenario *new_scenario ) +{ + g->scen = new_scenario; +} diff --git a/src/game.h b/src/game.h index 44a9a21cf4a4..a3e394b43c6b 100644 --- a/src/game.h +++ b/src/game.h @@ -188,7 +188,7 @@ class game bool save(); /** Returns a list of currently active character saves. */ - std::vector list_active_characters(); + std::vector list_active_saves(); void write_memorial_file( const std::string &filename, std::string sLastWords ); bool cleanup_at_end(); void start_calendar(); @@ -587,7 +587,7 @@ class game const std::string &none_message = "" ); bool has_gametype() const; - special_game_id gametype() const; + special_game_type gametype() const; void toggle_fullscreen(); void toggle_pixel_minimap(); @@ -979,7 +979,7 @@ class game /** True if the game has just started or loaded, else false. */ bool new_game = false; - const scenario *scen; + const scenario *scen = nullptr; std::vector coming_to_stairs; int monstairz = 0; diff --git a/src/gamemode.cpp b/src/gamemode.cpp index 23492dc510ac..bb55d5d0e4cc 100644 --- a/src/gamemode.cpp +++ b/src/gamemode.cpp @@ -5,32 +5,32 @@ #include "debug.h" #include "translations.h" -std::string special_game_name( special_game_id id ) +std::string special_game_name( special_game_type id ) { switch( id ) { - case SGAME_NULL: - case NUM_SPECIAL_GAMES: + case special_game_type::NONE: + case special_game_type::NUM_SPECIAL_GAME_TYPES: return "Bug (gamemode.cpp:special_game_name)"; - case SGAME_TUTORIAL: + case special_game_type::TUTORIAL: return _( "Tutorial" ); - case SGAME_DEFENSE: + case special_game_type::DEFENSE: return _( "Defense" ); default: return "Uncoded (gamemode.cpp:special_game_name)"; } } -std::unique_ptr get_special_game( special_game_id id ) +std::unique_ptr get_special_game( special_game_type id ) { std::unique_ptr ret; switch( id ) { - case SGAME_NULL: + case special_game_type::NONE: ret = std::make_unique(); break; - case SGAME_TUTORIAL: + case special_game_type::TUTORIAL: ret = std::make_unique(); break; - case SGAME_DEFENSE: + case special_game_type::DEFENSE: ret = std::make_unique(); break; default: diff --git a/src/gamemode.h b/src/gamemode.h index a1d37c2b1f7e..aa55d593372c 100644 --- a/src/gamemode.h +++ b/src/gamemode.h @@ -10,13 +10,13 @@ enum action_id : int; struct special_game; -std::string special_game_name( special_game_id id ); -std::unique_ptr get_special_game( special_game_id id ); +std::string special_game_name( special_game_type id ); +std::unique_ptr get_special_game( special_game_type id ); struct special_game { virtual ~special_game() = default; - virtual special_game_id id() { - return SGAME_NULL; + virtual special_game_type id() { + return special_game_type::NONE; } // Run when the game begins virtual bool init() { diff --git a/src/gamemode_defense.h b/src/gamemode_defense.h index 605cad11d09d..ef93a1148f04 100644 --- a/src/gamemode_defense.h +++ b/src/gamemode_defense.h @@ -54,8 +54,8 @@ enum caravan_category { struct defense_game : public special_game { defense_game(); - special_game_id id() override { - return SGAME_DEFENSE; + special_game_type id() override { + return special_game_type::DEFENSE; } bool init() override; void per_turn() override; diff --git a/src/gamemode_tutorial.h b/src/gamemode_tutorial.h index 2e1646d939ac..dc931e5ab4aa 100644 --- a/src/gamemode_tutorial.h +++ b/src/gamemode_tutorial.h @@ -11,7 +11,6 @@ template struct enum_traits; -enum special_game_id : int; enum action_id : int; enum class tut_lesson : int { @@ -57,8 +56,8 @@ struct hash { } // namespace std struct tutorial_game : public special_game { - special_game_id id() override { - return SGAME_TUTORIAL; + special_game_type id() override { + return special_game_type::TUTORIAL; } bool init() override; void per_turn() override; diff --git a/src/init.cpp b/src/init.cpp index 4538e0b411c5..06d4a0fb189d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -973,7 +973,7 @@ bool init::check_mods_for_errors( loading_ui &ui, const std::vector &opt world_generator->delete_world( world_name, true ); // TODO: Why would we need these calls? - MAPBUFFER.reset(); + MAPBUFFER.clear(); overmap_buffer.clear(); } diff --git a/src/main_menu.cpp b/src/main_menu.cpp index 79ebc8553f06..7e677e9dfb5e 100644 --- a/src/main_menu.cpp +++ b/src/main_menu.cpp @@ -26,10 +26,10 @@ #include "gamemode.h" #include "get_version.h" #include "help.h" -#include "ime.h" #include "loading_ui.h" #include "mapbuffer.h" #include "mapsharing.h" +#include "messages.h" #include "newcharacter.h" #include "optional.h" #include "options.h" @@ -37,6 +37,7 @@ #include "overmapbuffer.h" #include "path_info.h" #include "pldata.h" +#include "popup.h" #include "safemode_ui.h" #include "scenario.h" #include "sdlsound.h" @@ -45,9 +46,27 @@ #include "text_snippets.h" #include "translations.h" #include "ui_manager.h" +#include "ui.h" #include "wcwidth.h" #include "worldfactory.h" +enum class main_menu_opts : int { + MOTD = 0, + NEWCHAR = 1, + LOADCHAR = 2, + WORLD = 3, + SETTINGS = 4, + HELP = 5, + CREDITS = 6, + QUIT = 7 +}; +static constexpr int max_menu_opts = 7; + +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 ); @@ -55,17 +74,27 @@ void main_menu::on_move() const void main_menu::on_error() { - if( errflag ) { - return; - } sfx::play_variant_sound( "menu_error", "default", 100 ); - errflag = true; } -void main_menu::clear_error() +class sound_on_move_uilist_callback : public uilist_callback { - errflag = false; -} + private: + main_menu *mmenu; + bool first = true; + + public: + sound_on_move_uilist_callback( main_menu *mmenu ) : mmenu( mmenu ) { } + + void select( uilist * ) override { + if( first ) { + // Don't emit sound when menu is opened + first = false; + return; + } + mmenu->on_move(); + } +}; //CJK characters have a width of 2, etc static int utf8_width_notags( const char *s ) @@ -93,22 +122,22 @@ 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_light_gray ) : c_light_gray ) ); } int text_width = utf8_width_notags( text.c_str() ); @@ -116,11 +145,123 @@ 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_light_gray, 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, 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::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_light_gray ); + 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_light_gray ) : c_light_gray; + 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 ); @@ -130,21 +271,34 @@ void main_menu::print_menu( const catacurses::window &w_open, int iSel, point of // Draw horizontal line for( int i = 1; i < window_width - 1; ++i ) { - mvwputch( w_open, point( i, window_height - 4 ), c_white, LINE_OXOX ); + mvwputch( w_open, point( i, window_height - 5 ), c_white, LINE_OXOX ); } - 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 ) ); + if( iSel == getopt( main_menu_opts::NEWCHAR ) ) { + std::vector lines = foldstring( vNewGameHints[sel2], window_width - 2 ); + center_print( w_open, window_height - 3, c_yellow, lines[0] ); + if( lines.size() > 1 ) { + center_print( w_open, window_height - 2, c_yellow, lines[1] ); + } + if( lines.size() > 2 ) { + center_print( w_open, window_height - 1, c_yellow, lines[2] ); + } + } else { + center_print( w_open, window_height - 3, c_red, + _( "Bugs? Suggestions? Use links in MOTD to report them." ) ); + std::vector lines = foldstring( string_format( _( "Tip of the day: %s" ), + vdaytip ), window_width - 2 ); + center_print( w_open, window_height - 2, c_light_cyan, lines[0] ); + if( lines.size() > 1 ) { + center_print( w_open, window_height - 1, c_light_cyan, lines[1] ); + } + } int iLine = 0; const int iOffsetX = ( window_width - FULL_SCREEN_WIDTH ) / 2; switch( current_holiday ) { case holiday::new_year: - break; case holiday::easter: break; case holiday::halloween: @@ -153,9 +307,7 @@ void main_menu::print_menu( const catacurses::window &w_open, int iSel, point of 25, 0, c_white, halloween_graves() ); break; case holiday::thanksgiving: - break; case holiday::christmas: - break; case holiday::none: case holiday::num_holiday: default: @@ -163,10 +315,10 @@ void main_menu::print_menu( const catacurses::window &w_open, int iSel, point of } if( mmenu_title.size() > 1 ) { - for( const std::string &line : mmenu_title ) { + for( const std::string &i_title : mmenu_title ) { nc_color cur_color = c_white; nc_color base_color = c_white; - print_colored_text( w_open, point( iOffsetX, iLine++ ), cur_color, base_color, line ); + print_colored_text( w_open, point( iOffsetX, iLine++ ), cur_color, base_color, i_title ); } } else { center_print( w_open, iLine++, c_light_cyan, mmenu_title[0] ); @@ -189,9 +341,14 @@ void main_menu::print_menu( const catacurses::window &w_open, int iSel, point of 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 - 1 ), 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 - 3 ), sel_line ); } std::vector main_menu::load_file( const std::string &path, @@ -213,86 +370,9 @@ std::vector main_menu::load_file( const std::string &path, return result; } -/* compare against table of easter dates */ -bool main_menu::is_easter( int day, int month, int year ) -{ - if( month == 3 ) { - switch( year ) { - // *INDENT-OFF* - case 2024: return day == 31; - case 2027: return day == 28; - default: break; - // *INDENT-ON* - } - } else if( month == 4 ) { - switch( year ) { - // *INDENT-OFF* - case 2021: return day == 4; - case 2022: return day == 17; - case 2023: return day == 9; - case 2025: return day == 20; - case 2026: return day == 5; - case 2028: return day == 16; - case 2029: return day == 1; - case 2030: return day == 21; - default: break; - // *INDENT-ON* - } - } - return false; -} - holiday main_menu::get_holiday_from_time() { - bool success = false; - - std::tm local_time; - std::time_t current_time = std::time( nullptr ); - - /* necessary to pass LGTM, as threadsafe version of localtime differs by platform */ -#if defined(_WIN32) - - errno_t err = localtime_s( &local_time, ¤t_time ); - if( err == 0 ) { - success = true; - } - -#else - - success = !!localtime_r( ¤t_time, &local_time ); - -#endif - - if( success ) { - - const int month = local_time.tm_mon + 1; - const int day = local_time.tm_mday; - const int wday = local_time.tm_wday; - const int year = local_time.tm_year + 1900; - - /* check date against holidays */ - if( month == 1 && day == 1 ) { - return holiday::new_year; - } - // only run easter date calculation if currently March or April - else if( ( month == 3 || month == 4 ) && is_easter( day, month, year ) ) { - return holiday::easter; - } else if( month == 7 && day == 4 ) { - return holiday::independence_day; - } - // 13 days seems appropriate for Halloween - else if( month == 10 && day >= 19 ) { - return holiday::halloween; - } else if( month == 11 && ( day >= 22 && day <= 28 ) && wday == 4 ) { - return holiday::thanksgiving; - } - // For the 12 days of Christmas, my true love gave to me... - else if( month == 12 && ( day >= 14 && day <= 25 ) ) { - return holiday::christmas; - } - } - // fall through to here if localtime fails, or none of the day tests hit - return holiday::none; + return ::get_holiday_from_time( 0, true ); } void main_menu::init_windows() @@ -311,10 +391,9 @@ void main_menu::init_windows() const int total_h = FULL_SCREEN_HEIGHT + extra_h; // position of window within main display - const int x0 = ( TERMX - total_w ) / 2; - const int y0 = ( TERMY - total_h ) / 2; + const point p0( ( TERMX - total_w ) / 2, ( TERMY - total_h ) / 2 ); - w_open = catacurses::newwin( total_h, total_w, point( x0, y0 ) ); + w_open = catacurses::newwin( total_h, total_w, p0 ); menu_offset.y = total_h - 3; // note: if iMenuOffset is changed, @@ -335,6 +414,7 @@ void main_menu::init_strings() mmenu_motd += ( line.empty() ? " " : line ) + "\n"; } mmenu_motd = colorize( mmenu_motd, c_light_red ); + mmenu_motd_len = foldstring( mmenu_motd, FULL_SCREEN_WIDTH - 2 ).size(); // Credits mmenu_credits.clear(); @@ -350,18 +430,53 @@ void main_menu::init_strings() if( mmenu_credits.empty() ) { mmenu_credits = _( "No credits information found." ); } + mmenu_credits_len = foldstring( mmenu_credits, FULL_SCREEN_WIDTH - 2 ).size(); // fill menu with translated menu items vMenuItems.clear(); - vMenuItems.push_back( pgettext( "Main Menu", "OTD" ) ); - vMenuItems.push_back( pgettext( "Main Menu", "ew Game" ) ); - vMenuItems.push_back( pgettext( "Main Menu", "Lod" ) ); - vMenuItems.push_back( pgettext( "Main Menu", "orld" ) ); - vMenuItems.push_back( pgettext( "Main Menu", "pecial" ) ); - vMenuItems.push_back( pgettext( "Main Menu", "Setings" ) ); - vMenuItems.push_back( pgettext( "Main Menu", "Hlp" ) ); - vMenuItems.push_back( pgettext( "Main Menu", "redits" ) ); - vMenuItems.push_back( pgettext( "Main Menu", "uit" ) ); + vMenuItems.emplace_back( pgettext( "Main Menu", "OTD" ) ); + vMenuItems.emplace_back( pgettext( "Main Menu", "ew Game" ) ); + vMenuItems.emplace_back( pgettext( "Main Menu", "Lod" ) ); + vMenuItems.emplace_back( pgettext( "Main Menu", "orld" ) ); + vMenuItems.emplace_back( pgettext( "Main Menu", "Setings" ) ); + vMenuItems.emplace_back( pgettext( "Main Menu", "Hlp" ) ); + 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!" ) ); + + // Special games don't play well together with shared maps + vNewGameSubItems.emplace_back( pgettext( "Main Menu|New Game", "utorial" ) ); + vNewGameSubItems.emplace_back( pgettext( "Main Menu|New Game", "efence mode" ) ); + } + 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." ) ); + vNewGameHints.emplace_back( + _( "Learn controls and basic game mechanics while exploring a small underground complex." ) ); + vNewGameHints.emplace_back( + _( "Defend against waves of incoming enemies. This game mode hasn't been updated in a while and may contain bugs." ) ); + 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(); @@ -370,25 +485,23 @@ void main_menu::init_strings() } vWorldSubItems.clear(); - vWorldSubItems.push_back( pgettext( "Main Menu|World", "elete World" ) ); - vWorldSubItems.push_back( pgettext( "Main Menu|World", "eset World" ) ); - vWorldSubItems.push_back( pgettext( "Main Menu|World", "how World Mods" ) ); - vWorldSubItems.push_back( pgettext( "Main Menu|World", "dit World Mods" ) ); - vWorldSubItems.push_back( pgettext( "Main Menu|World", "opy World Settings" ) ); - vWorldSubItems.push_back( pgettext( "Main Menu|World", "Character to emplate" ) ); - - vWorldHotkeys.clear(); - for( const std::string &item : vWorldSubItems ) { - vWorldHotkeys.push_back( get_hotkeys( item ) ); - } + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Show World Mods" ) ); + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Edit World Mods" ) ); + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Copy World Settings" ) ); + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Character to Template" ) ); + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Reset World" ) ); + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "Delete World" ) ); + vWorldSubItems.emplace_back( pgettext( "Main Menu|World", "<= Return" ) ); + + vWorldHotkeys = { 'm', 'e', 's', 't', 'r', 'd', 'q' }; vSettingsSubItems.clear(); - vSettingsSubItems.push_back( pgettext( "Main Menu|Settings", "ptions" ) ); - vSettingsSubItems.push_back( pgettext( "Main Menu|Settings", "Kybindings" ) ); - vSettingsSubItems.push_back( pgettext( "Main Menu|Settings", "utopickup" ) ); - vSettingsSubItems.push_back( pgettext( "Main Menu|Settings", "afemode" ) ); - vSettingsSubItems.push_back( pgettext( "Main Menu|Settings", "istractions" ) ); - vSettingsSubItems.push_back( pgettext( "Main Menu|Settings", "olors" ) ); + vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "ptions" ) ); + 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", "istractions" ) ); + vSettingsSubItems.emplace_back( pgettext( "Main Menu|Settings", "Colos" ) ); vSettingsHotkeys.clear(); for( const std::string &item : vSettingsSubItems ) { @@ -400,29 +513,23 @@ 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_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_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( 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(); - if( selected < 0 ) { - selected = 0; - } else if( iLines < height ) { - selected = 0; - } else if( selected >= iLines - height ) { - selected = iLines - height; - } - fold_and_print_from( w_text, point_zero, width, selected, c_light_gray, text ); draw_scrollbar( w_border, selected, height, iLines, point_south, BORDER_COLOR, true ); @@ -491,68 +598,40 @@ bool main_menu::opening_screen() load_char_templates(); ctxt.register_cardinal(); - ctxt.register_action( "QUIT" ); - ctxt.register_action( "CONFIRM" ); - ctxt.register_action( "DELETE_TEMPLATE" ); + ctxt.register_action( "NEXT_TAB" ); + ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "PAGE_UP" ); ctxt.register_action( "PAGE_DOWN" ); + ctxt.register_action( "CONFIRM" ); + ctxt.register_action( "QUIT" ); + // for the menu shortcuts ctxt.register_action( "ANY_INPUT" ); bool start = false; - g->u = avatar(); + avatar &player_character = get_avatar(); + 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 < NUM_SPECIAL_GAMES; 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(); @@ -562,826 +641,438 @@ 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; - - // switch off ime at program start - if( ctxt.get_raw_input().sequence.empty() ) { - // FIXME: disable_ime only seems to work after receiving an input event - // with empty input sequence. (empty input event is also fired when the - // window loses focus, might be related?) - disable_ime(); - continue; - } - - // 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" ) { - sel_line = 0; - if( sel1 > 0 ) { - sel1--; - } else { - sel1 = 8; - } - on_move(); - } else if( action == "RIGHT" ) { - sel_line = 0; - if( sel1 < 8 ) { - sel1++; - } else { - sel1 = 0; - } - 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::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"; + } } - } - 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; + } + 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"; } } } - } 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; - } + } - std::string action = ctxt.handle_input(); - if( action == "LEFT" ) { - if( sel2 > 0 ) { - sel2--; - } else { - sel2 = NUM_SPECIAL_GAMES - 2; - } - on_move(); - } else if( action == "RIGHT" ) { - if( sel2 < NUM_SPECIAL_GAMES - 2 ) { - sel2++; - } else { - sel2 = 0; - } - on_move(); - } else if( action == "DOWN" || action == "QUIT" ) { - layer = 1; - } - if( action == "UP" || action == "CONFIRM" ) { - if( sel2 >= 0 && sel2 < NUM_SPECIAL_GAMES - 1 ) { - on_out_of_scope cleanup( []() { - g->gamemode.reset(); - g->u = avatar(); - world_generator->set_active_world( nullptr ); - } ); - g->gamemode = get_special_game( static_cast( sel2 + 1 ) ); - // check world - WORLDPTR world = world_generator->make_new_world( static_cast( sel2 + 1 ) ); - if( world == nullptr ) { - continue; - } - world_generator->set_active_world( world ); - try { - g->setup(); - } catch( const std::exception &err ) { - debugmsg( "Error: %s", err.what() ); - continue; - } - if( !g->gamemode->init() ) { - continue; + // also check special keys + if( action == "QUIT" ) { + ui_manager::redraw(); + 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; + main_menu_opts opt = static_cast( sel1 ); + switch( opt ) { + case main_menu_opts::MOTD: + case main_menu_opts::CREDITS: + if( action == "UP" || action == "PAGE_UP" || action == "SCROLL_UP" ) { + if( sel_line > 0 ) { + sel_line--; } - 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"; + } else if( action == "DOWN" || action == "PAGE_DOWN" || action == "SCROLL_DOWN" ) { + int effective_height = sel_line + FULL_SCREEN_HEIGHT - 2; + if( ( opt == main_menu_opts::CREDITS && effective_height < mmenu_credits_len ) || + ( opt == main_menu_opts::MOTD && effective_height < mmenu_motd_len ) ) { + sel_line++; } } - } - - if( action == "LEFT" ) { - if( sel2 > 0 ) { - --sel2; - } else { - sel2 = settings_subs_to_display - 1; + 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::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" ) { - if( sel2 < settings_subs_to_display - 1 ) { - ++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 ) { + 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::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 ) { get_distraction_manager().show(); } else if( sel2 == 5 ) { 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: + if( static_cast( sel2 ) < world_generator->all_worldnames().size() ) { + start = load_character_tab( world_generator->all_worldnames().at( sel2 ) ); + } else { + on_error(); + popup( _( "No world to load." ) ); + } + 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; + if( start && get_scenario() ) { + add_msg( get_scenario()->description( player_character.male ) ); + } + return true; } bool main_menu::new_character_tab() { - std::vector vSubItems; - vSubItems.push_back( pgettext( "Main Menu|New Game", "ustom Character" ) ); - vSubItems.push_back( pgettext( "Main Menu|New Game", "reset Character" ) ); - vSubItems.push_back( pgettext( "Main Menu|New Game", "andom Character" ) ); - if( !MAP_SHARING::isSharing() ) { // "Play Now" function doesn't play well together with shared maps - vSubItems.push_back( pgettext( "Main Menu|New Game", "Play Now! (ixed Scenario)" ) ); - vSubItems.push_back( pgettext( "Main Menu|New Game", "Play ow!" ) ); - } - std::vector hints; - hints.push_back( - _( "Allows you to fully customize points pool, scenario, and character's profession, stats, traits, skills and other parameters." ) ); - hints.push_back( - _( "Select from one of previously created character templates." ) ); - hints.push_back( - _( "Creates random character, but lets you preview the generated character and the scenario and change character and/or scenario if needed." ) ); - hints.push_back( - _( "Puts you right in the game, randomly choosing character's traits, profession, skills and other parameters. Scenario is fixed to Evacuee." ) ); - hints.push_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 ); + std::string selected_template; + + avatar &pc = get_avatar(); + // Preset character templates + if( sel2 == 1 ) { + if( templates.empty() ) { + on_error(); + popup( _( "No templates found!" ) ); + return false; } - } ); - ui.on_screen_resize( [this]( ui_adaptor & ui ) { - init_windows(); - ui.position_from_window( w_open ); - } ); - ui.position_from_window( w_open ); - - 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; + while( true ) { + uilist mmenu( _( "Choose a preset character template" ), {} ); + mmenu.border_color = c_light_gray; + mmenu.hotkey_color = c_yellow; + sound_on_move_uilist_callback cb( this ); + mmenu.callback = &cb; + + int opt_val = 0; + for( const std::string &tmpl : templates ) { + mmenu.entries.emplace_back( opt_val++, true, MENU_AUTOASSIGN, tmpl ); } - - 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"; - } - } + mmenu.entries.emplace_back( opt_val++, true, 'q', "<= Return" ); + mmenu.query(); + opt_val = mmenu.ret; + if( opt_val < 0 || static_cast( opt_val ) >= templates.size() ) { + return false; } - if( action == "LEFT" ) { - sel2--; - if( sel2 < 0 ) { - sel2 = vSubItems.size() - 1; - } - on_move(); - } else if( action == "RIGHT" ) { - sel2++; - if( sel2 >= static_cast( vSubItems.size() ) ) { - sel2 = 0; - } - on_move(); - } else if( action == "DOWN" || action == "QUIT" ) { - layer = 1; - sel1 = 1; - } - if( action == "UP" || action == "CONFIRM" ) { - if( sel2 == 0 || sel2 == 2 || sel2 == 3 || sel2 == 4 ) { - on_out_of_scope cleanup( []() { - g->u = 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( 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( !g->u.create( play_type ) ) { - load_char_templates(); - MAPBUFFER.reset(); - overmap_buffer.clear(); - continue; - } - if( !g->start_game() ) { - continue; - } - cleanup.cancel(); - start = true; - } else if( sel2 == 1 ) { - layer = 3; - sel3 = 0; + std::string res = query_popup() + .context( "LOAD_DELETE_CANCEL" ).default_color( c_light_gray ) + .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 { + templates.erase( templates.begin() + opt_val ); } + } else if( res == "LOAD" ) { + selected_template = templates[opt_val]; + break; } - } 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++; - } else { - sel3 = 0; - } - } else if( action == "LEFT" || 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 == "CONFIRM" ) { - on_out_of_scope cleanup( []() { - g->u = avatar(); - world_generator->set_active_world( nullptr ); - } ); - g->gamemode = nullptr; - WORLDPTR world = world_generator->pick_world(); - if( world == nullptr ) { - continue; - } - world_generator->set_active_world( world ); - try { - g->setup(); - } catch( const std::exception &err ) { - debugmsg( "Error: %s", err.what() ); - continue; - } - if( !g->u.create( character_type::TEMPLATE, templates[sel3] ) ) { - load_char_templates(); - MAPBUFFER.reset(); - overmap_buffer.clear(); - continue; - } - if( !g->start_game() ) { - continue; - } - cleanup.cancel(); - start = true; + return false; } } - } // end while - - if( start ) { - g->u.add_msg_if_player( g->scen->description( g->u.male ) ); - - world_generator->last_world_name = world_generator->active_world->world_name; - world_generator->last_character_name = g->u.name; - world_generator->save_last_world_info(); } - return start; -} -bool main_menu::load_character_tab( bool transfer ) -{ - 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; + on_out_of_scope cleanup( [&pc]() { + g->gamemode.reset(); + pc = avatar(); + world_generator->set_active_world( nullptr ); + } ); + g->gamemode.reset(); + + WORLDPTR world; + if( sel2 == 5 ) { + g->gamemode = get_special_game( special_game_type::TUTORIAL ); + world = world_generator->make_new_world( special_game_type::TUTORIAL ); + } else if( sel2 == 6 ) { + g->gamemode = get_special_game( special_game_type::DEFENSE ); + world = world_generator->make_new_world( special_game_type::DEFENSE ); } 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; - } + // Pick a world, suppressing prompts if it's "play now" mode. + bool empty_only = sel2 == 3 || sel2 == 4; + bool show_prompt = !empty_only; + world = world_generator->pick_world( show_prompt, empty_only ); + } - const size_t last_character_pos = std::find_if( savegames.begin(), savegames.end(), - []( const save_t &it ) { - return it.player_name() == world_generator->last_character_name; - } ) - savegames.begin(); - if( last_character_pos < savegames.size() ) { - sel3 = last_character_pos; - } else { - sel3 = 0; - } + 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; } - ui_adaptor ui; - ui.on_redraw( [&]( const ui_adaptor & ) { - const point offset( transfer ? 25 : 15, transfer ? -1 : 0 ); + if( g->gamemode ) { + bool success = g->gamemode->init(); + if( success ) { + cleanup.cancel(); + } + return success; + } - print_menu( w_open, transfer ? 3 : 2, menu_offset ); + character_type play_type = character_type::CUSTOM; + switch( sel2 ) { + case 0: + play_type = character_type::CUSTOM; + break; + case 1: + play_type = character_type::TEMPLATE; + 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, selected_template ) ) { + load_char_templates(); + MAPBUFFER.clear(); + overmap_buffer.clear(); + return false; + } - 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, 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; + if( !g->start_game() ) { + return false; + } + cleanup.cancel(); + return true; +} - const std::string &wn = all_worldnames[sel2]; +bool main_menu::load_character_tab( const std::string &worldname ) +{ + 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() ); + } - 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 ); + 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_light_gray; + mmenu.hotkey_color = c_yellow; + sound_on_move_uilist_callback cb( this ); + mmenu.callback = &cb; + int opt_val = 0; + for( const save_t &s : savegames ) { + mmenu.entries.emplace_back( opt_val++, true, MENU_AUTOASSIGN, + colorize( s.decoded_name(), c_white ) ); + } + mmenu.entries.emplace_back( opt_val++, true, 'q', "<= Return" ); + 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( 40 + menu_offset.x + extra_w / 2, line-- + offset.y ), - selected ? h_white : c_white, - "%s", savename.player_name() ); - } - } - wnoutrefresh( w_open ); - } - } ); - ui.on_screen_resize( [this]( ui_adaptor & ui ) { - init_windows(); - ui.position_from_window( w_open ); + avatar &pc = get_avatar(); + on_out_of_scope cleanup( [&pc]() { + pc = avatar(); + world_generator->set_active_world( nullptr ); } ); - ui.position_from_window( w_open ); - - 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 == "QUIT" ) { - layer = 1; - } else if( action == "RIGHT" || 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.player_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 == "QUIT" ) { - layer = transfer ? 1 : 2; - sel3 = 0; - } - if( action == "RIGHT" || action == "CONFIRM" ) { - if( sel3 >= 0 && sel3 < static_cast( savegames.size() ) ) { - on_out_of_scope cleanup( []() { - g->u = 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].player_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, 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_light_gray; + mmenu.hotkey_color = c_yellow; + sound_on_move_uilist_callback cb( this ); + mmenu.callback = &cb; + for( size_t i = 0; i < vWorldSubItems.size(); i++ ) { + mmenu.entries.emplace_back( static_cast( i ), true, vWorldHotkeys[i], vWorldSubItems[i] ); + } + mmenu.query(); + int 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 ); - - while( sel1 == 3 && ( layer == 2 || layer == 3 || layer == 4 ) ) { - ui_manager::redraw(); - if( layer == 4 ) { //Character to Template - if( load_character_tab( true ) ) { - points_left points; - points.stat_points = 0; - points.trait_points = 0; - points.skill_points = 0; - points.limit = points_left::TRANSFER; - - g->u.setID( character_id(), true ); - g->u.reset_all_misions(); - g->u.save_template( g->u.name, points ); - - g->u = avatar(); - MAPBUFFER.reset(); - 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 == "QUIT" ) { - layer = 2; + switch( opt_val ) { + case 5: // Delete World + if( query_yn( _( "Delete the world and all saves within?" ) ) ) { + clear_world( true ); } - - if( action == "RIGHT" || 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 if( sel3 == 3 ) { // Edit World Mods - if( query_yn( _( - "Editing mod list or mod load order may render the world unstable or completely unplayable. " - "It is advised to manually back up world files before proceeding. " - "If you have just started playing, consider creating new world instead.\n" - "Proceed?" - ) ) ) { - WORLDPTR world = world_generator->get_world( all_worldnames[sel2 - 1] ); - world_generator->edit_active_world_mods( world ); - } - } else { - bool query_yes = false; - bool do_delete = false; - if( sel3 == 0 ) { // Delete World - if( query_yn( _( "Delete the world and all saves?" ) ) ) { - 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 == 4 ) { // Copy World settings - layer = 2; - world_generator->make_new_world( true, all_worldnames[sel2 - 1] ); - } else if( sel3 == 5 ) { // 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.reset(); - 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; + break; + case 4: // Reset World + if( query_yn( _( "Remove all saves and regenerate world?" ) ) ) { + clear_world( false ); } - - 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 == "QUIT" ) { - layer = 1; + break; + case 0: // Active World Mods + world_generator->show_active_world_mods( + world_generator->get_world( worldname )->active_mod_order ); + break; + case 1: // Edit World Mods + if( query_yn( _( + "Editing mod list or mod load order may render the world unstable or completely unplayable. " + "It is advised to manually back up world files before proceeding. " + "If you have just started playing, consider creating new world instead.\n" + "Proceed?" + ) ) ) { + WORLDPTR world = world_generator->get_world( worldname ); + world_generator->edit_active_world_mods( world ); } - if( action == "RIGHT" || action == "CONFIRM" ) { - if( sel2 == 0 ) { - world_generator->make_new_world(); - - } else { - layer = 3; - sel3 = 0; - } + break; + case 2: // Copy World settings + world_generator->make_new_world( true, worldname ); + break; + case 3: // 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(); } - } - } // 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 5fe82920fc82..12eada5537bb 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 "input.h" #include "point.h" @@ -14,6 +15,7 @@ class main_menu { + friend class sound_on_move_uilist_callback; public: main_menu() : ctxt( "MAIN_MENU" ) { } // Shows the main menu and returns whether a game was started or not @@ -24,12 +26,17 @@ class main_menu std::vector mmenu_title; std::string mmenu_motd; std::string mmenu_credits; + int mmenu_motd_len; + int mmenu_credits_len; std::vector vMenuItems; // MOTD, New Game, Load Game, etc. std::vector vWorldSubItems; - std::vector< std::vector > vWorldHotkeys; + std::vector vNewGameSubItems; + std::vector vNewGameHints; + 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 /** @@ -44,19 +51,14 @@ class main_menu // Play a sound whenever the user moves left or right in the main menu or its tabs void on_move() const; - // Flag to be set when first entering an error condition, cleared when leaving it - // Used to prevent error sound from playing repeatedly at input polling rate - bool errflag = false; // Play a sound *once* when an error occurs in the main menu or its tabs; sets errflag void on_error(); - // Clears errflag - void clear_error(); // 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 +70,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 +88,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,14 +101,14 @@ 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, 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 functions and member variables*/ - static bool is_easter( int day, int month, int year ); holiday get_holiday_from_time(); holiday current_holiday = holiday::none; diff --git a/src/mapbuffer.cpp b/src/mapbuffer.cpp index eac63b5d7073..46ae6f9cb5bb 100644 --- a/src/mapbuffer.cpp +++ b/src/mapbuffer.cpp @@ -40,36 +40,32 @@ static std::string find_dirname( const tripoint &om_addr ) mapbuffer MAPBUFFER; mapbuffer::mapbuffer() = default; +mapbuffer::~mapbuffer() = default; -mapbuffer::~mapbuffer() +void mapbuffer::clear() { - reset(); -} - -void mapbuffer::reset() -{ - for( auto &elem : submaps ) { - delete elem.second; - } submaps.clear(); } -bool mapbuffer::add_submap( const tripoint &p, submap *sm ) +bool mapbuffer::add_submap( const tripoint &p, std::unique_ptr &sm ) { - if( submaps.count( p ) != 0 ) { + if( submaps.count( p ) ) { return false; } - submaps[p] = sm; + submaps[p] = std::move( sm ); return true; } -bool mapbuffer::add_submap( const tripoint &p, std::unique_ptr &sm ) +bool mapbuffer::add_submap( const tripoint &p, submap *sm ) { - const bool result = add_submap( p, sm.get() ); - if( result ) { - sm.release(); + // FIXME: get rid of this overload and make submap ownership semantics sane. + std::unique_ptr temp( sm ); + bool result = add_submap( p, temp ); + if( !result ) { + // NOLINTNEXTLINE( bugprone-unused-return-value ) + temp.release(); } return result; } @@ -81,7 +77,6 @@ void mapbuffer::remove_submap( tripoint addr ) debugmsg( "Tried to remove non-existing submap %s", addr.to_string() ); return; } - delete m_target->second; submaps.erase( m_target ); } @@ -97,7 +92,7 @@ submap *mapbuffer::lookup_submap( const tripoint &p ) return nullptr; } - return iter->second; + return iter->second.get(); } void mapbuffer::save( bool delete_after_save ) @@ -180,7 +175,7 @@ void mapbuffer::save_quad( const std::string &dirname, const std::string &filena submap_addr.x += offsets_offset.x; submap_addr.y += offsets_offset.y; submap_addrs.push_back( submap_addr ); - submap *sm = submaps[submap_addr]; + submap *sm = submaps[submap_addr].get(); if( sm != nullptr && !sm->is_uniform ) { all_uniform = false; } @@ -213,7 +208,7 @@ void mapbuffer::save_quad( const std::string &dirname, const std::string &filena continue; } - submap *sm = submaps[submap_addr]; + submap *sm = submaps[submap_addr].get(); if( sm == nullptr ) { continue; @@ -274,7 +269,7 @@ submap *mapbuffer::unserialize_submaps( const tripoint &p ) quad_path, p.x, p.y, p.z ); return nullptr; } - return submaps[ p ]; + return submaps[ p ].get(); } void mapbuffer::deserialize( JsonIn &jsin ) diff --git a/src/mapbuffer.h b/src/mapbuffer.h index 9a505fa232ca..c3e8062d1928 100644 --- a/src/mapbuffer.h +++ b/src/mapbuffer.h @@ -29,7 +29,7 @@ class mapbuffer void save( bool delete_after_save = false ); /** Delete all buffered submaps. **/ - void reset(); + void clear(); /** Add a new submap to the buffer. * @@ -39,10 +39,10 @@ class mapbuffer * is released (set to NULL). * @return true if the submap has been stored here. False if there * is already a submap with the specified coordinates. The submap - * is not stored than and the caller must take of the submap object - * on their own (and properly delete it). + * is not stored and the given unique_ptr retains ownsership. */ bool add_submap( const tripoint &p, std::unique_ptr &sm ); + // Old overload that we should stop using, but it's complicated bool add_submap( const tripoint &p, submap *sm ); /** Get a submap stored in this buffer. @@ -59,7 +59,7 @@ class mapbuffer } private: - using submap_map_t = std::map; + using submap_map_t = std::map>; public: inline submap_map_t::iterator begin() { diff --git a/src/newcharacter.cpp b/src/newcharacter.cpp index 4f9628bfd4eb..5284a881751a 100644 --- a/src/newcharacter.cpp +++ b/src/newcharacter.cpp @@ -441,9 +441,9 @@ bool avatar::create( character_type type, const std::string &tempname ) } auto nameExists = [&]( const std::string & name ) { - return world_generator->active_world->save_exists( save_t::from_player_name( name ) ) && - !query_yn( _( "A character with the name '%s' already exists in this world.\n" - "Saving will override the already existing character.\n\n" + return world_generator->active_world->save_exists( save_t::from_save_id( name ) ) && + !query_yn( _( "A save with the name '%s' already exists in this world.\n" + "Saving will overwrite the already existing character.\n\n" "Continue anyways?" ), name ); }; set_body(); @@ -2979,6 +2979,16 @@ cata::optional query_for_template_name() } } +void avatar::character_to_template( const std::string &name ) +{ + points_left points; + points.stat_points = 0; + points.trait_points = 0; + points.skill_points = 0; + points.limit = points_left::TRANSFER; + save_template( name, points ); +} + void avatar::save_template( const std::string &name, const points_left &points ) { std::string name_san = ensure_valid_file_name( name ); @@ -2992,6 +3002,7 @@ void avatar::save_template( const std::string &name, const points_left &points ) jsout.member( "trait_points", points.trait_points ); jsout.member( "skill_points", points.skill_points ); jsout.member( "limit", points.limit ); + jsout.member( "starting_vehicle", starting_vehicle ); jsout.member( "random_start_location", random_start_location ); if( !random_start_location ) { jsout.member( "start_location", start_location ); @@ -3024,6 +3035,11 @@ bool avatar::load_template( const std::string &template_name, points_left &point points.skill_points = jobj.get_int( "skill_points" ); points.limit = static_cast( jobj.get_int( "limit" ) ); + if( jobj.has_member( "starting_vehicle" ) ) { + starting_vehicle = vproto_id( jobj.get_string( "starting_vehicle" ) ); + } else { + starting_vehicle = vproto_id::NULL_ID(); + } random_start_location = jobj.get_bool( "random_start_location", true ); const std::string jobj_start_location = jobj.get_string( "start_location", "" ); diff --git a/src/overmap.cpp b/src/overmap.cpp index 7f43f12e3f1d..f1ed119266a7 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -1623,7 +1623,7 @@ void overmap::generate( const overmap *north, const overmap *east, const overmap *south, const overmap *west, overmap_special_batch &enabled_specials ) { - if( g->gametype() == SGAME_DEFENSE ) { + if( g->gametype() == special_game_type::DEFENSE ) { dbg( DL::Info ) << "overmap::generate skipped in Defense special game mode!"; return; } @@ -4992,12 +4992,12 @@ std::string enum_to_string( ot_match_type data ) { switch( data ) { // *INDENT-OFF* - case exact: return "EXACT"; - case type: return "TYPE"; - case prefix: return "PREFIX"; - case contains: return "CONTAINS"; + case ot_match_type::exact: return "EXACT"; + case ot_match_type::type: return "TYPE"; + case ot_match_type::prefix: return "PREFIX"; + case ot_match_type::contains: return "CONTAINS"; // *INDENT-ON* - case num_ot_match_type: + case ot_match_type::num_ot_match_type: break; } debugmsg( "Invalid ot_match_type" ); diff --git a/src/player.h b/src/player.h index ff657e06709b..5d048719c8a4 100644 --- a/src/player.h +++ b/src/player.h @@ -247,7 +247,7 @@ class player : public Character int movecounter = 0; bool manual_examine = false; - vproto_id starting_vehicle; + vproto_id starting_vehicle = vproto_id::NULL_ID(); std::vector starting_pets; void make_craft_with_command( const recipe_id &id_to_make, int batch_size, bool is_long = false, diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 81cc63f654ff..4a88d644d27b 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -412,6 +412,7 @@ void Character::load( const JsonObject &data ) data.read( "int_bonus", int_bonus ); data.read( "omt_path", omt_path ); + data.read( "name", name ); data.read( "base_age", init_age ); data.read( "base_height", init_height ); @@ -668,6 +669,8 @@ void Character::store( JsonOut &json ) const json.member( "per_bonus", per_bonus ); json.member( "int_bonus", int_bonus ); + json.member( "name", name ); + json.member( "base_age", init_age ); json.member( "base_height", init_height ); @@ -1423,7 +1426,6 @@ void npc::load( const JsonObject &data ) time_point companion_mission_t_r = calendar::start_of_cataclysm; std::string act_id; - data.read( "name", name ); data.read( "marked_for_death", marked_for_death ); data.read( "dead", dead ); data.read( "patience", patience ); @@ -1636,7 +1638,6 @@ void npc::store( JsonOut &json ) const { player::store( json ); - json.member( "name", name ); json.member( "marked_for_death", marked_for_death ); json.member( "dead", dead ); json.member( "patience", patience ); diff --git a/src/scenario.h b/src/scenario.h index a06e146788a3..b6c231d2b627 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -126,4 +126,7 @@ struct scen_blacklist { void reset_scenarios_blacklist(); +const scenario *get_scenario(); +void set_scenario( const scenario *new_scenario ); + #endif // CATA_SRC_SCENARIO_H diff --git a/src/trait_group.cpp b/src/trait_group.cpp index 87b2c1b03773..d5106a3c1adb 100644 --- a/src/trait_group.cpp +++ b/src/trait_group.cpp @@ -274,7 +274,6 @@ void Trait_group_collection::add_entry( std::unique_ptr ptr ptr->probability = std::min( 100, ptr->probability ); creators.push_back( std::move( ptr ) ); - ptr.release(); } void Trait_group_distribution::add_entry( std::unique_ptr ptr ) @@ -287,7 +286,6 @@ void Trait_group_distribution::add_entry( std::unique_ptr p sum_prob += ptr->probability; creators.push_back( std::move( ptr ) ); - ptr.release(); } Trait_list Trait_group_distribution::create( RecursionList &rec ) const diff --git a/src/worldfactory.cpp b/src/worldfactory.cpp index f1f33741d4da..66c53995de71 100644 --- a/src/worldfactory.cpp +++ b/src/worldfactory.cpp @@ -41,12 +41,9 @@ using namespace std::placeholders; // single instance of world generator std::unique_ptr world_generator; -save_t::save_t( const std::string &name ) - : name( name ) -{ -} +save_t::save_t( const std::string &name ): name( name ) {} -std::string save_t::player_name() const +std::string save_t::decoded_name() const { return name; } @@ -56,9 +53,9 @@ std::string save_t::base_path() const return base64_encode( name ); } -save_t save_t::from_player_name( const std::string &name ) +save_t save_t::from_save_id( const std::string &save_id ) { - return save_t( name ); + return save_t( save_id ); } save_t save_t::from_base_path( const std::string &base_path ) @@ -178,14 +175,14 @@ WORLDPTR worldfactory::make_new_world( bool show_prompt, const std::string &worl return add_world( std::move( retworld ) ); } -WORLDPTR worldfactory::make_new_world( special_game_id special_type ) +WORLDPTR worldfactory::make_new_world( special_game_type special_type ) { std::string worldname; switch( special_type ) { - case SGAME_TUTORIAL: + case special_game_type::TUTORIAL: worldname = "TUTORIAL"; break; - case SGAME_DEFENSE: + case special_game_type::DEFENSE: worldname = "DEFENSE"; break; default: @@ -348,13 +345,14 @@ std::vector worldfactory::all_worldnames() const return result; } -WORLDPTR worldfactory::pick_world( bool show_prompt ) +WORLDPTR worldfactory::pick_world( bool show_prompt, bool empty_only ) { std::vector world_names = all_worldnames(); // Filter out special worlds (TUTORIAL | DEFENSE) from world_names. for( std::vector::iterator it = world_names.begin(); it != world_names.end(); ) { - if( *it == "TUTORIAL" || *it == "DEFENSE" ) { + if( *it == "TUTORIAL" || *it == "DEFENSE" || + ( empty_only && !get_world( *it )->world_saves.empty() ) ) { it = world_names.erase( it ); } else { ++it; diff --git a/src/worldfactory.h b/src/worldfactory.h index 8b0182eefb86..5654d041242a 100644 --- a/src/worldfactory.h +++ b/src/worldfactory.h @@ -14,10 +14,11 @@ #include "pimpl.h" #include "type_id.h" +enum class special_game_type; + class JsonIn; class JsonObject; -enum special_game_id : int; namespace catacurses { class window; @@ -31,10 +32,10 @@ class save_t save_t( const std::string &name ); public: - std::string player_name() const; + std::string decoded_name() const; std::string base_path() const; - static save_t from_player_name( const std::string &name ); + static save_t from_save_id( const std::string &save_id ); static save_t from_base_path( const std::string &base_path ); bool operator==( const save_t &rhs ) const { @@ -92,7 +93,7 @@ class worldfactory // Generate a world WORLDPTR make_new_world( bool show_prompt = true, const std::string &world_to_copy = "" ); - WORLDPTR make_new_world( special_game_id special_type ); + WORLDPTR make_new_world( special_game_type special_type ); // Used for unit tests - does NOT verify if the mods can be loaded WORLDPTR make_new_world( const std::vector &mods ); /// Returns the *existing* world of given name. @@ -103,7 +104,7 @@ class worldfactory void init(); - WORLDPTR pick_world( bool show_prompt = true ); + WORLDPTR pick_world( bool show_prompt = true, bool empty_only = false ); WORLDPTR active_world; diff --git a/tests/map_helpers.cpp b/tests/map_helpers.cpp index 738287c586a8..f4fab6673f3c 100644 --- a/tests/map_helpers.cpp +++ b/tests/map_helpers.cpp @@ -92,7 +92,7 @@ void clear_items( const int zlevel ) void clear_overmap() { - MAPBUFFER.reset(); + MAPBUFFER.clear(); overmap_buffer.clear(); }