diff --git a/src/character.cpp b/src/character.cpp index 2d0255e19c382..3081e61ee5098 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -6835,6 +6835,55 @@ units::mass Character::bionics_weight() const return bio_weight; } +int Character::base_age() const +{ + return init_age; +} + +void Character::mod_base_age( int mod ) +{ + init_age += mod; +} + +int Character::age() const +{ + int years_since_cataclysm = to_turns( calendar::turn - calendar::turn_zero ) / + to_turns( calendar::year_length() ); + return init_age + years_since_cataclysm; +} + +std::string Character::age_string() const +{ + //~ how old the character is in years. try to limit number of characters to fit on the screen + std::string unformatted = _( "aged %d" ); + return string_format( unformatted, age() ); +} + +int Character::base_height() const +{ + return init_height; +} + +void Character::mod_base_height( int mod ) +{ + init_height += mod; +} + +std::string Character::height_string() const +{ + const bool metric = get_option( "DISTANCE_UNITS" ) == "metric"; + + if( metric ) { + std::string metric_string = _( "%d cm" ); + return string_format( metric_string, height() ); + } + + int total_inches = std::round( height() / 2.54 ); + int feet = std::floor( total_inches / 12 ); + int remainder_inches = total_inches % 12; + return string_format( "%d\'%d\"", feet, remainder_inches ); +} + int Character::height() const { int height = init_height; @@ -6866,11 +6915,10 @@ int Character::get_bmr() const /** Values are for males, and average! */ - const int age = 25; const int equation_constant = 5; return ceil( metabolic_rate_base() * activity_level * ( units::to_gram ( bodyweight() / 100.0 ) + - ( 6.25 * height() ) - ( 5 * age ) + equation_constant ) ); + ( 6.25 * height() ) - ( 5 * age() ) + equation_constant ) ); } void Character::increase_activity_level( float new_level ) diff --git a/src/character.h b/src/character.h index f7dc62aa46cb8..071f225c9e27e 100644 --- a/src/character.h +++ b/src/character.h @@ -1564,6 +1564,16 @@ class Character : public Creature, public visitable float get_bmi() const; // returns amount of calories burned in a day given various metabolic factors int get_bmr() const; + // age in years, determined at character creation + int base_age() const; + void mod_base_age( int mod ); + // age in years + int age() const; + std::string age_string() const; + // returns the height in cm + int base_height() const; + void mod_base_height( int mod ); + std::string height_string() const; // returns the height of the player character in cm int height() const; // returns bodyweight of the Character @@ -1905,6 +1915,8 @@ class Character : public Creature, public visitable int healthy; int healthy_mod; + /** age in years at character creation */ + int init_age = 25; /**height at character creation*/ int init_height = 175; /** Size class of character. */ diff --git a/src/newcharacter.cpp b/src/newcharacter.cpp index e8d1b626dac8a..1d872e16a3a41 100644 --- a/src/newcharacter.cpp +++ b/src/newcharacter.cpp @@ -175,6 +175,10 @@ void avatar::randomize( const bool random_scenario, points_left &points, bool pl } else { name = MAP_SHARING::getUsername(); } + // if adjusting min and max age from 16 and 55, make sure to see set_description() + init_age = rng( 16, 55 ); + // if adjusting min and max height from 145 and 200, make sure to see set_description() + init_height = rng( 145, 200 ); bool cities_enabled = world_generator->active_world->WORLD_OPTIONS["CITY_SIZE"].getValue() != "0"; if( random_scenario ) { std::vector scenarios; @@ -2217,6 +2221,35 @@ tab_direction set_scenario( avatar &u, points_left &points, return retval; } +namespace char_creation +{ +enum description_selector { + NAME, + HEIGHT, + AGE +}; + +static void draw_height( const catacurses::window &w_height, const avatar &you, + const bool highlight ) +{ + werase( w_height ); + mvwprintz( w_height, point_zero, highlight ? h_light_gray : c_light_gray, _( "Height:" ) ); + unsigned height_pos = 1 + utf8_width( _( "Height:" ) ); + mvwprintz( w_height, point( height_pos, 0 ), c_light_green, string_format( "%d cm", + you.base_height() ) ); + wrefresh( w_height ); +} + +static void draw_age( const catacurses::window &w_age, const avatar &you, const bool highlight ) +{ + werase( w_age ); + mvwprintz( w_age, point_zero, highlight ? h_light_gray : c_light_gray, _( "Age:" ) ); + unsigned age_pos = 1 + utf8_width( _( "Age:" ) ); + mvwprintz( w_age, point( age_pos, 0 ), c_light_green, string_format( "%d", you.base_age() ) ); + wrefresh( w_age ); +} +} // namespace char_creation + tab_direction set_description( avatar &you, const bool allow_reroll, points_left &points ) { @@ -2236,6 +2269,8 @@ tab_direction set_description( avatar &you, const bool allow_reroll, catacurses::window w_profession; catacurses::window w_skills; catacurses::window w_guide; + catacurses::window w_height; + catacurses::window w_age; const auto init_windows = [&]( ui_adaptor & ui ) { w = catacurses::newwin( TERMY, TERMX, point_zero ); w_name = catacurses::newwin( 2, 42, point( 2, 5 ) ); @@ -2247,6 +2282,8 @@ tab_direction set_description( avatar &you, const bool allow_reroll, w_profession = catacurses::newwin( 1, TERMX - 47, point( 46, 10 ) ); w_skills = catacurses::newwin( TERMY - 12, 33, point( 46, 11 ) ); w_guide = catacurses::newwin( 4, TERMX - 3, point( 2, TERMY - 5 ) ); + w_height = catacurses::newwin( 1, 20, point( 80, 5 ) ); + w_age = catacurses::newwin( 1, 10, point( 80, 6 ) ); ui.position_from_window( w ); }; init_windows( ui ); @@ -2257,6 +2294,7 @@ tab_direction set_description( avatar &you, const bool allow_reroll, unsigned female_pos = 2 + male_pos + utf8_width( _( "Male" ) ); input_context ctxt( "NEW_CHAR_DESCRIPTION" ); + ctxt.register_cardinal(); ctxt.register_action( "SAVE_TEMPLATE" ); ctxt.register_action( "PICK_RANDOM_NAME" ); ctxt.register_action( "CHANGE_GENDER" ); @@ -2301,6 +2339,8 @@ tab_direction set_description( avatar &you, const bool allow_reroll, you.name = get_option( "DEF_CHAR_NAME" ); } + char_creation::description_selector current_selector = char_creation::NAME; + bool no_name_entered = false; ui.on_redraw( [&]( const ui_adaptor & ) { draw_character_tabs( w, _( "DESCRIPTION" ) ); @@ -2413,7 +2453,8 @@ tab_direction set_description( avatar &you, const bool allow_reroll, wrefresh( w_guide ); //We draw this stuff every loop because this is user-editable - mvwprintz( w_name, point_zero, c_light_gray, _( "Name:" ) ); + mvwprintz( w_name, point_zero, + current_selector == char_creation::NAME ? h_light_gray : c_light_gray, _( "Name:" ) ); if( no_name_entered ) { mvwprintz( w_name, point( namebar_pos, 0 ), h_light_gray, _( "_______NO NAME ENTERED!_______" ) ); } else { @@ -2440,6 +2481,9 @@ tab_direction set_description( avatar &you, const bool allow_reroll, ctxt.get_desc( "CHANGE_GENDER" ) ); wrefresh( w_gender ); + char_creation::draw_age( w_age, you, current_selector == char_creation::AGE ); + char_creation::draw_height( w_height, you, current_selector == char_creation::HEIGHT ); + const std::string location_prompt = string_format( _( "Press %s to select location." ), ctxt.get_desc( "CHOOSE_LOCATION" ) ); @@ -2470,6 +2514,13 @@ tab_direction set_description( avatar &you, const bool allow_reroll, // do not switch IME mode now, but restore previous mode on return ime_sentry sentry( ime_sentry::keep ); + + int min_allowed_age = 16; + int max_allowed_age = 55; + // in centimeters. 2 std. deviations below average female height + int min_allowed_height = 145; + int max_allowed_height = 200; + do { ui_manager::redraw(); const std::string action = ctxt.handle_input(); @@ -2504,6 +2555,60 @@ tab_direction set_description( avatar &you, const bool allow_reroll, } } else if( action == "PREV_TAB" ) { return tab_direction::BACKWARD; + } else if( action == "RIGHT" ) { + switch( current_selector ) { + case char_creation::NAME: + current_selector = char_creation::HEIGHT; + break; + case char_creation::HEIGHT: + current_selector = char_creation::AGE; + break; + case char_creation::AGE: + current_selector = char_creation::NAME; + break; + } + } else if( action == "LEFT" ) { + switch( current_selector ) { + case char_creation::NAME: + current_selector = char_creation::AGE; + break; + case char_creation::HEIGHT: + current_selector = char_creation::NAME; + break; + case char_creation::AGE: + current_selector = char_creation::HEIGHT; + break; + } + } else if( action == "UP" ) { + switch( current_selector ) { + case char_creation::HEIGHT: + if( you.base_height() < max_allowed_height ) { + you.mod_base_height( 1 ); + } + break; + case char_creation::AGE: + if( you.base_age() < max_allowed_age ) { + you.mod_base_age( 1 ); + } + break; + default: + break; + } + } else if( action == "DOWN" ) { + switch( current_selector ) { + case char_creation::HEIGHT: + if( you.base_height() > min_allowed_height ) { + you.mod_base_height( -1 ); + } + break; + case char_creation::AGE: + if( you.base_age() > min_allowed_age ) { + you.mod_base_age( -1 ); + } + break; + default: + break; + } } else if( action == "REROLL_CHARACTER" && allow_reroll ) { points.init_from_options(); you.randomize( false, points ); @@ -2538,7 +2643,8 @@ tab_direction set_description( avatar &you, const bool allow_reroll, } } } else if( action == "ANY_INPUT" && - !MAP_SHARING::isSharing() ) { // Don't edit names when sharing maps + // Don't edit names when sharing maps + !MAP_SHARING::isSharing() && current_selector == char_creation::NAME ) { const int ch = ctxt.get_raw_input().get_first_input(); utf8_wrapper wrap( you.name ); if( ch == KEY_BACKSPACE ) { diff --git a/src/options.cpp b/src/options.cpp index 0c9280a347c4c..0323302e29f98 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1356,6 +1356,10 @@ void options_manager::add_options_interface() { { "c", translate_marker( "Cup" ) }, { "l", translate_marker( "Liter" ) }, { "qt", translate_marker( "Quart" ) } }, "l" ); + add( "DISTANCE_UNITS", "interface", translate_marker( "Distance units" ), + translate_marker( "Metric or Imperial" ), + { { "metric", translate_marker( "Metric" ) }, { "imperial", translate_marker( "Imperial" ) } }, + "imperial" ); add( "24_HOUR", "interface", translate_marker( "Time format" ), translate_marker( "12h: AM/PM, e.g. 7:31 AM - Military: 24h Military, e.g. 0731 - 24h: Normal 24h, e.g. 7:31" ), diff --git a/src/player_display.cpp b/src/player_display.cpp index d6ab42985c687..8f5da01ffaddc 100644 --- a/src/player_display.cpp +++ b/src/player_display.cpp @@ -1291,16 +1291,19 @@ void player::disp_info() break; } } - //~ player info window: 1s - name, 2s - gender, 3s - Prof or Mutation name - mvwprintw( w_tip, point_zero, _( "%1$s | %2$s | %3$s" ), name, - male ? _( "Male" ) : _( "Female" ), race ); + //~ player info window: 1s - name, 2s - gender, 3s - Prof or Mutation name, 4s age (years), 5s height + mvwprintw( w_tip, point_zero, _( "%1$s | %2$s | %3$s | %4$s | %5$s" ), name, + male ? _( "Male" ) : _( "Female" ), race, age_string(), height_string() ); } else if( prof == nullptr || prof == profession::generic() ) { // Regular person. Nothing interesting. - //~ player info window: 1s - name, 2s - gender, '|' - field separator. - mvwprintw( w_tip, point_zero, _( "%1$s | %2$s" ), name, male ? _( "Male" ) : _( "Female" ) ); + //~ player info window: 1s - name, 2s - gender, 3s - age, 4s - height '|' - field separator. + mvwprintw( w_tip, point_zero, _( "%1$s | %2$s | %3$s | %4$s" ), name, + male ? _( "Male" ) : _( "Female" ), + age_string(), height_string() ); } else { - mvwprintw( w_tip, point_zero, _( "%1$s | %2$s | %3$s" ), name, - male ? _( "Male" ) : _( "Female" ), prof->gender_appropriate_name( male ) ); + mvwprintw( w_tip, point_zero, _( "%1$s | %2$s | %3$s | %4$s | %5$s" ), name, + male ? _( "Male" ) : _( "Female" ), prof->gender_appropriate_name( male ), + age_string(), height_string() ); } input_context ctxt( "PLAYER_INFO" ); diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 4b8b8fd8c2650..cf5f011f9324a 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -381,6 +381,10 @@ void Character::load( const JsonObject &data ) data.read( "per_bonus", per_bonus ); data.read( "int_bonus", int_bonus ); data.read( "omt_path", omt_path ); + + data.read( "base_age", init_age ); + data.read( "base_height", init_height ); + // needs data.read( "thirst", thirst ); data.read( "hunger", hunger ); @@ -637,6 +641,9 @@ void Character::store( JsonOut &json ) const json.member( "per_bonus", per_bonus ); json.member( "int_bonus", int_bonus ); + json.member( "base_age", init_age ); + json.member( "base_height", init_height ); + // health json.member( "healthy", healthy ); json.member( "healthy_mod", healthy_mod );