diff --git a/src/character.cpp b/src/character.cpp index 85195ef92ea3c..0ac06bf4203c0 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -6091,17 +6091,18 @@ float Character::rest_quality() const return rest; } -std::string Character::extended_description() const +std::vector Character::extended_description() const { - std::string ss; + std::vector tmp; + std::string name_str = colorize( get_name(), basic_symbol_color() ); if( is_avatar() ) { // This is me, . - ss += string_format( _( "This is you - %s." ), get_name() ); + tmp.emplace_back( string_format( _( "This is you - %s." ), name_str ) ); } else { - ss += string_format( _( "This is %s." ), get_name() ); + tmp.emplace_back( string_format( _( "This is %s." ), name_str ) ); } - ss += "\n--\n"; + tmp.emplace_back( "--" ); const std::vector &bps = get_all_body_parts( get_body_part_flags::only_main ); // Find length of bp names, to align @@ -6121,33 +6122,46 @@ std::string Character::extended_description() const std::pair hp_bar = get_hp_bar( get_part_hp_cur( bp ), get_part_hp_max( bp ), false ); - ss += colorize( left_justify( bp_heading, longest ), name_color ); - ss += colorize( hp_bar.first, hp_bar.second ); + std::string bp_stat = colorize( left_justify( bp_heading, longest ), name_color ); + bp_stat += colorize( hp_bar.first, hp_bar.second ); // Trailing bars. UGLY! // TODO: Integrate into get_hp_bar somehow - ss += colorize( std::string( 5 - utf8_width( hp_bar.first ), '.' ), c_white ); - ss += "\n"; + bp_stat += colorize( std::string( 5 - utf8_width( hp_bar.first ), '.' ), c_white ); + tmp.emplace_back( bp_stat ); } - ss += "--\n"; - ss += _( "Wielding:" ) + std::string( " " ); + tmp.emplace_back( "--" ); + std::string wielding; if( weapon.is_null() ) { - ss += _( "Nothing" ); + wielding = _( "Nothing" ); } else { - ss += weapon.tname(); + wielding = weapon.tname(); } - ss += "\n"; - ss += _( "Wearing:" ) + std::string( " " ); + tmp.emplace_back( string_format( _( "Wielding: %s" ), colorize( wielding, c_red ) ) ); + std::string wearing = _( "Wearing:" ) + std::string( " " ); const std::list visible_worn_items = get_visible_worn_items(); std::string worn_string = enumerate_as_string( visible_worn_items.begin(), visible_worn_items.end(), []( const item_location & it ) { return it.get_item()->tname(); } ); - ss += !worn_string.empty() ? worn_string : _( "Nothing" ); + wearing += !worn_string.empty() ? worn_string : _( "Nothing" ); + tmp.emplace_back( wearing ); - return replace_colors( ss ); + int visibility_cap = get_player_character().get_mutation_visibility_cap( this ); + const std::string trait_str = visible_mutations( visibility_cap ); + if( !trait_str.empty() ) { + tmp.emplace_back( string_format( _( "Traits: %s" ), trait_str ) ); + } + + std::vector ret; + ret.reserve( tmp.size() ); + for( const std::string &s : tmp ) { + ret.emplace_back( replace_colors( s ) ); + } + + return ret; } social_modifiers Character::get_mutation_bionic_social_mods() const diff --git a/src/character.h b/src/character.h index ff60a7317d1b2..c738b6d45b67e 100644 --- a/src/character.h +++ b/src/character.h @@ -33,7 +33,6 @@ #include "city.h" // IWYU pragma: keep #include "compatibility.h" #include "coords_fwd.h" -#include "craft_command.h" #include "creature.h" #include "damage.h" #include "enums.h" @@ -68,6 +67,7 @@ class activity_actor; class basecamp; class bionic_collection; class character_martial_arts; +class craft_command; class dispersion_sources; class effect; class effect_source; @@ -2642,7 +2642,7 @@ class Character : public Creature, public visitable nc_color symbol_color() const override; const std::string &symbol() const override; - std::string extended_description() const override; + std::vector extended_description() const override; std::string mutation_name( const trait_id &mut ) const; std::string mutation_desc( const trait_id &mut ) const; diff --git a/src/creature.h b/src/creature.h index 13d89c261cb95..38b264ee7fbd6 100644 --- a/src/creature.h +++ b/src/creature.h @@ -1181,7 +1181,7 @@ class Creature : public viewer string_format( npc_speech, std::forward( args )... ) ); } - virtual std::string extended_description() const = 0; + virtual std::vector extended_description() const = 0; /** Creature symbol background color */ virtual nc_color symbol_color() const = 0; diff --git a/src/descriptions.cpp b/src/descriptions.cpp deleted file mode 100644 index 0b7ae1fa91f4b..0000000000000 --- a/src/descriptions.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include "game.h" // IWYU pragma: associated - -#include -#include -#include -#include -#include -#include - -#include "avatar.h" -#include "calendar.h" -#include "character.h" -#include "color.h" -#include "creature_tracker.h" -#include "harvest.h" -#include "input_context.h" -#include "map.h" -#include "mapdata.h" -#include "mod_manager.h" -#include "output.h" -#include "string_formatter.h" -#include "translation.h" -#include "translations.h" -#include "ui_manager.h" -#include "viewer.h" -#include "vpart_position.h" - -static const skill_id skill_survival( "survival" ); - -static const trait_id trait_ILLITERATE( "ILLITERATE" ); - -enum class description_target : int { - creature, - furniture, - terrain, - vehicle -}; - -static const Creature *seen_critter( const tripoint &p ) -{ - const Creature *critter = get_creature_tracker().creature_at( p, true ); - if( critter != nullptr && get_player_view().sees( *critter ) ) { - return critter; - } - - return nullptr; -} - -void game::extended_description( const tripoint &p ) -{ - ui_adaptor ui; - const int top = 3; - int width = 0; - catacurses::window w_head; - catacurses::window w_main; - ui.on_screen_resize( [&]( ui_adaptor & ui ) { - const int left = 0; - const int right = TERMX; - const int bottom = TERMY; - width = right - left; - const int height = bottom - top; - w_head = catacurses::newwin( top, TERMX, point_zero ); - w_main = catacurses::newwin( height, width, point( left, top ) ); - ui.position( point_zero, point( TERMX, TERMY ) ); - } ); - ui.mark_resize(); - - // Default to critter (if any), furniture (if any), then terrain. - description_target cur_target = description_target::terrain; - if( seen_critter( p ) != nullptr ) { - cur_target = description_target::creature; - } else if( get_map().has_furn( p ) ) { - cur_target = description_target::furniture; - } - - std::string action; - input_context ctxt( "EXTENDED_DESCRIPTION" ); - ctxt.register_action( "CREATURE" ); - ctxt.register_action( "FURNITURE" ); - ctxt.register_action( "TERRAIN" ); - ctxt.register_action( "VEHICLE" ); - ctxt.register_action( "CONFIRM" ); - ctxt.register_action( "QUIT" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - - ui.on_redraw( [&]( const ui_adaptor & ) { - werase( w_head ); - mvwprintz( w_head, point_zero, c_white, - _( "[%s] describe creatures, [%s] describe furniture, " - "[%s] describe terrain, [%s] describe vehicle/appliance, [%s] close." ), - ctxt.get_desc( "CREATURE" ), ctxt.get_desc( "FURNITURE" ), - ctxt.get_desc( "TERRAIN" ), ctxt.get_desc( "VEHICLE" ), ctxt.get_desc( "QUIT" ) ); - - // Set up line drawings - for( int i = 0; i < TERMX; i++ ) { - mvwputch( w_head, point( i, top - 1 ), c_white, LINE_OXOX ); - } - - wnoutrefresh( w_head ); - - std::string desc; - // Allow looking at invisible tiles - player may want to examine hallucinations etc. - switch( cur_target ) { - case description_target::creature: { - const Creature *critter = seen_critter( p ); - if( critter != nullptr ) { - desc = critter->extended_description(); - } else { - desc = _( "You do not see any creature here." ); - } - } - break; - case description_target::furniture: - if( !u.sees( p ) || !m.has_furn( p ) ) { - desc = _( "You do not see any furniture here." ); - } else { - const furn_id fid = m.furn( p ); - const std::string mod_src = enumerate_as_string( fid->src.begin(), - fid->src.end(), []( const std::pair &source ) { - return string_format( "'%s'", source.second->name() ); - }, enumeration_conjunction::arrow ); - desc = string_format( _( "Origin: %s\n%s" ), mod_src, fid->extended_description() ); - } - break; - case description_target::terrain: - if( !u.sees( p ) ) { - desc = _( "You can't see the terrain here." ); - } else { - const ter_id tid = m.ter( p ); - const std::string mod_src = enumerate_as_string( tid->src.begin(), - tid->src.end(), []( const std::pair &source ) { - return string_format( "'%s'", source.second->name() ); - }, enumeration_conjunction::arrow ); - desc = string_format( _( "Origin: %s\n%s" ), mod_src, tid->extended_description() ); - } - break; - case description_target::vehicle: - const optional_vpart_position vp = m.veh_at( p ); - if( !u.sees( p ) || !vp ) { - desc = _( "You can't see vehicles or appliances here." ); - } else { - desc = vp.extended_description(); - } - break; - } - - std::string signage = m.get_signage( p ); - if( !signage.empty() ) { - // NOLINTNEXTLINE(cata-text-style): the question mark does not end a sentence - desc += u.has_trait( trait_ILLITERATE ) ? _( "\nSign: ???" ) : string_format( _( "\nSign: %s" ), - signage ); - } - - werase( w_main ); - fold_and_print_from( w_main, point_zero, width, 0, c_light_gray, desc ); - wnoutrefresh( w_main ); - } ); - - do { - ui_manager::redraw(); - action = ctxt.handle_input(); - if( action == "CREATURE" ) { - cur_target = description_target::creature; - } else if( action == "FURNITURE" ) { - cur_target = description_target::furniture; - } else if( action == "TERRAIN" ) { - cur_target = description_target::terrain; - } else if( action == "VEHICLE" ) { - cur_target = description_target::vehicle; - } - } while( action != "CONFIRM" && action != "QUIT" ); -} - -std::string map_data_common_t::extended_description() const -{ - std::stringstream ss; - ss << "
" << string_format( _( "That is a %s." ), name() ) << "
" << '\n'; - ss << description << std::endl; - bool has_any_harvest = std::any_of( harvest_by_season.begin(), harvest_by_season.end(), - []( const harvest_id & hv ) { - return !hv.obj().empty(); - } ); - - if( has_any_harvest ) { - ss << "--" << std::endl; - int player_skill = get_player_character().get_greater_skill_or_knowledge_level( skill_survival ); - ss << _( "You could harvest the following things from it:" ) << std::endl; - // Group them by identical ids to avoid repeating same blocks of data - // First, invert the mapping: season->id to id->seasons - std::multimap identical_harvest; - for( size_t season = SPRING; season <= WINTER; season++ ) { - const auto &hv = harvest_by_season[ season ]; - if( hv.obj().empty() ) { - continue; - } - - identical_harvest.insert( std::make_pair( hv, static_cast( season ) ) ); - } - // Now print them in order of seasons - // TODO: Highlight current season - for( size_t season = SPRING; season <= WINTER; season++ ) { - const auto range = identical_harvest.equal_range( harvest_by_season[ season ] ); - if( range.first == range.second ) { - continue; - } - - // List the seasons first - ss << enumerate_as_string( range.first, range.second, - []( const std::pair &pr ) { - if( pr.second == season_of_year( calendar::turn ) ) { - return "" + calendar::name_season( pr.second ) + ""; - } - - return "" + calendar::name_season( pr.second ) + ""; - } ); - ss << ":" << std::endl; - // List the drops - // They actually describe what player can get from it now, so it isn't spoily - // TODO: Allow spoily listing of everything - ss << range.first->first.obj().describe( player_skill ) << std::endl; - // Remove the range from the multimap so that it isn't listed twice - identical_harvest.erase( range.first, range.second ); - } - - ss << std::endl; - } - - return replace_colors( ss.str() ); -} diff --git a/src/game.cpp b/src/game.cpp index df681b8e2bba5..3710b2003e692 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -195,6 +195,7 @@ #include "translations.h" #include "trap.h" #include "ui.h" +#include "ui_extended_description.h" #include "ui_manager.h" #include "uistate.h" #include "units.h" @@ -7639,8 +7640,8 @@ look_around_result game::look_around( } else if( action == "debug_hour_timer" ) { toggle_debug_hour_timer(); } else if( action == "EXTENDED_DESCRIPTION" ) { - // TODO: fix point types - extended_description( lp.raw() ); + extended_description_window ext_desc( lp ); + ext_desc.show(); } else if( action == "CHANGE_MONSTER_NAME" ) { creature_tracker &creatures = get_creature_tracker(); monster *const mon = creatures.creature_at( lp, true ); diff --git a/src/iexamine_actors.cpp b/src/iexamine_actors.cpp index 6819117df36ea..e0bfb5aaddde6 100644 --- a/src/iexamine_actors.cpp +++ b/src/iexamine_actors.cpp @@ -7,6 +7,7 @@ #include "itype.h" #include "map.h" #include "mapgen_functions.h" +#include "mapgendata.h" #include "map_iterator.h" #include "messages.h" #include "mtype.h" diff --git a/src/mapdata.cpp b/src/mapdata.cpp index f93b89fda3315..5ea21968363ec 100644 --- a/src/mapdata.cpp +++ b/src/mapdata.cpp @@ -10,6 +10,7 @@ #include "assign.h" #include "calendar.h" +#include "character.h" #include "color.h" #include "debug.h" #include "enum_conversions.h" @@ -18,7 +19,9 @@ #include "iexamine.h" #include "iexamine_actors.h" #include "item_group.h" +#include "iteminfo_query.h" #include "json.h" +#include "mod_manager.h" #include "output.h" #include "rng.h" #include "string_formatter.h" @@ -30,6 +33,8 @@ static furn_id f_null; static const item_group_id Item_spawn_data_EMPTY_GROUP( "EMPTY_GROUP" ); +static const skill_id skill_survival( "survival" ); + namespace { @@ -661,6 +666,144 @@ const std::set &map_data_common_t::get_harvest_names() const return hid.is_null() ? null_names : hid->names(); } +std::vector ter_t::extended_description() const +{ + std::vector ret; + ret.emplace_back( get_origin( src ) ); + ret.emplace_back( "--" ); + + std::vector tmp = map_data_common_t::extended_description(); + ret.insert( ret.end(), tmp.begin(), tmp.end() ); + + return ret; +} + +std::vector furn_t::extended_description() const +{ + std::vector ret; + ret.emplace_back( get_origin( src ) ); + ret.emplace_back( "--" ); + + std::vector tmp = map_data_common_t::extended_description(); + ret.insert( ret.end(), tmp.begin(), tmp.end() ); + + // If this furniture has a crafting pseudo item, check for tool qualities and print them + if( !crafting_pseudo_item.is_empty() ) { + const item pseudo( crafting_pseudo_item ); + std::vector quality_part = { iteminfo_parts::QUALITIES }; + const iteminfo_query quality_query( quality_part ); + std::vector info_vec; + pseudo.qualities_info( info_vec, &quality_query, 1, false ); + // A bit of cargo-culting here, pre-imgui printing code was adapted + // to split string with line breaks into single-line strings + std::string quality_string = format_item_info( info_vec, {} ); + size_t strpos = 0; + while( ( strpos = quality_string.find( '\n' ) ) != std::string::npos ) { + // \n character is skipped + ret.emplace_back( quality_string.substr( 0, strpos ) ); + quality_string.erase( 0, strpos + 1 ); + } + } + + return ret; +} + +std::vector map_data_common_t::extended_description() const +{ + std::vector tmp; + + tmp.emplace_back( string_format( _( "
That is a %s.
" ), name() ) ); + tmp.emplace_back( description.translated() ); + bool has_any_harvest = std::any_of( harvest_by_season.begin(), harvest_by_season.end(), + []( const harvest_id & hv ) { + return !hv.obj().empty(); + } ); + + if( has_any_harvest ) { + tmp.emplace_back( "--" ); + int player_skill = get_player_character().get_greater_skill_or_knowledge_level( skill_survival ); + tmp.emplace_back( _( "You could harvest the following things from it:" ) ); + // Group them by identical ids to avoid repeating same blocks of data + // First, invert the mapping: season->id to id->seasons + std::multimap identical_harvest; + for( size_t season = SPRING; season <= WINTER; season++ ) { + const auto &hv = harvest_by_season[season]; + if( hv.obj().empty() ) { + continue; + } + + identical_harvest.insert( std::make_pair( hv, static_cast( season ) ) ); + } + // Now print them in order of seasons + // TODO: Highlight current season + for( size_t season = SPRING; season <= WINTER; season++ ) { + const auto range = identical_harvest.equal_range( harvest_by_season[season] ); + if( range.first == range.second ) { + continue; + } + + // List the seasons first + std::string seasons = enumerate_as_string( range.first, range.second, + []( const std::pair &pr ) { + if( pr.second == season_of_year( calendar::turn ) ) { + return "" + calendar::name_season( pr.second ) + ""; + } + + return "" + calendar::name_season( pr.second ) + ""; + } ); + seasons += ":"; + tmp.emplace_back( seasons ); + // List the drops + // They actually describe what player can get from it now, so it isn't spoily + // TODO: Allow spoily listing of everything + tmp.emplace_back( range.first->first.obj().describe( player_skill ) ); + // Remove the range from the multimap so that it isn't listed twice + identical_harvest.erase( range.first, range.second ); + } + } + + tmp.emplace_back( "--" ); + tmp.emplace_back( string_format( _( "Concealment: %d%%" ), coverage ) ); + if( has_flag( ter_furn_flag::TFLAG_TREE ) ) { + tmp.emplace_back( _( "Can be cut down with the right tools." ) ); + } + + // todo: generalize, copied from map::features which combines terrain and furniture info + std::string result; + const auto add = [&]( const std::string & text ) { + if( !result.empty() ) { + result += " "; + } + result += text; + }; + const auto add_if = [&]( const bool cond, const std::string & text ) { + if( cond ) { + add( text ); + } + }; + add_if( bash.str_max != -1 && !bash.bash_below, _( "Smashable." ) ); + add_if( has_flag( ter_furn_flag::TFLAG_DIGGABLE ), _( "Diggable." ) ); + add_if( has_flag( ter_furn_flag::TFLAG_PLOWABLE ), _( "Plowable." ) ); + add_if( has_flag( ter_furn_flag::TFLAG_ROUGH ), _( "Rough." ) ); + add_if( has_flag( ter_furn_flag::TFLAG_UNSTABLE ), _( "Unstable." ) ); + add_if( has_flag( ter_furn_flag::TFLAG_SHARP ), _( "Sharp." ) ); + add_if( has_flag( ter_furn_flag::TFLAG_FLAT ), _( "Flat." ) ); + add_if( has_flag( ter_furn_flag::TFLAG_EASY_DECONSTRUCT ), _( "Simple." ) ); + add_if( has_flag( ter_furn_flag::TFLAG_MOUNTABLE ), _( "Mountable." ) ); + add_if( has_flag( ter_furn_flag::TFLAG_FLAMMABLE ) || + has_flag( ter_furn_flag::TFLAG_FLAMMABLE_ASH ) || + has_flag( ter_furn_flag::TFLAG_FLAMMABLE_HARD ), _( "Flammable." ) ); + tmp.emplace_back( result ); + + std::vector ret; + ret.reserve( tmp.size() ); + for( const std::string &s : tmp ) { + ret.emplace_back( replace_colors( s ) ); + } + + return ret; +} + void load_furniture( const JsonObject &jo, const std::string &src ) { if( furniture_data.empty() ) { diff --git a/src/mapdata.h b/src/mapdata.h index 26dbb688b5042..f7df191e90167 100644 --- a/src/mapdata.h +++ b/src/mapdata.h @@ -587,7 +587,7 @@ struct map_data_common_t { */ const std::set &get_harvest_names() const; - std::string extended_description() const; + virtual std::vector extended_description() const; bool was_loaded = false; @@ -636,6 +636,8 @@ struct ter_t : map_data_common_t { bool is_null() const; + std::vector extended_description() const override; + void load( const JsonObject &jo, const std::string &src ) override; void check() const override; }; @@ -689,6 +691,8 @@ struct furn_t : map_data_common_t { bool is_movable() const; + std::vector extended_description() const override; + void load( const JsonObject &jo, const std::string &src ) override; void check() const override; }; diff --git a/src/mod_manager.h b/src/mod_manager.h index 94b07669fa756..bcbbc74ce8fbd 100644 --- a/src/mod_manager.h +++ b/src/mod_manager.h @@ -10,6 +10,7 @@ #include #include +#include "output.h" #include "path_info.h" #include "pimpl.h" #include "translations.h" @@ -65,6 +66,17 @@ struct MOD_INFORMATION { std::pair category = { -1, translation() }; }; +// Enumerates and formats the mod origin +template +std::string get_origin( const std::vector> &src ) +{ + std::string origin_str = enumerate_as_string( src.begin(), + src.end(), []( const std::pair &source ) { + return string_format( "'%s'", source.second->name() ); + }, enumeration_conjunction::arrow ); + return string_format( _( "Origin: %s" ), origin_str ); +} + class mod_manager { public: diff --git a/src/monster.cpp b/src/monster.cpp index dea5aac2b9b64..ec092956bab2e 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1061,14 +1061,32 @@ void monster::print_info_imgui() const } } -std::string monster::extended_description() const +std::vector monster::extended_description() const { - std::string ss; + std::vector tmp; + + tmp.emplace_back( get_origin( type->src ) ); + tmp.emplace_back( "--" ); + + if( debug_mode ) { + tmp.emplace_back( colorize( type->id.str(), c_white ) ); + } + + nc_color bar_color = c_white; + std::string bar_str; + get_HP_Bar( bar_color, bar_str ); + std::string hpbar = colorize( bar_str, bar_color ) + + colorize( std::string( 5 - utf8_width( bar_str ), '.' ), c_white ); Character &pc = get_player_character(); const bool player_knows = !pc.has_trait( trait_INATTENTIVE ); const std::pair att = get_attitude(); std::string att_colored = colorize( att.first, att.second ); + + tmp.emplace_back( string_format( _( "This is a %s %s. %s" ), hpbar, colorize( name(), + basic_symbol_color() ), player_knows ? att_colored : std::string() ) ); + tmp.emplace_back( "--" ); + std::string difficulty_str; if( debug_mode ) { difficulty_str = _( "Difficulty " ) + std::to_string( type->difficulty ); @@ -1087,62 +1105,52 @@ std::string monster::extended_description() const difficulty_str = _( "Fatally dangerous!" ); } } + tmp.emplace_back( difficulty_str ); - ss += _( "Origin: " ); - ss += enumerate_as_string( type->src.begin(), - type->src.end(), []( const std::pair &source ) { - return string_format( "'%s'", source.second->name() ); - }, enumeration_conjunction::arrow ); - - ss += "\n--\n"; - - if( debug_mode ) { - ss += type->id.str(); - ss += "\n"; + std::string eff = get_effect_status(); + if( !eff.empty() ) { + tmp.emplace_back( string_format( _( "It is %s." ), eff ) ); } - ss += string_format( _( "This is a %s. %s%s" ), name(), - player_knows ? att_colored + " " : std::string(), - difficulty_str ) + "\n"; - if( !get_effect_status().empty() ) { - ss += string_format( _( "It is %s." ), get_effect_status() ) + "\n"; - } + bool sees_player = sees( pc ); + std::string senses_str = sees_player ? colorize( _( "Can see to your current location" ), c_red ) : + colorize( _( "Can't see to your current location" ), c_green ); + tmp.emplace_back( senses_str ); - ss += "--\n"; const std::pair hp_bar = hp_description( hp, type->hp ); - ss += colorize( hp_bar.first, hp_bar.second ) + "\n"; + tmp.emplace_back( colorize( hp_bar.first, hp_bar.second ) ); const std::string speed_desc = speed_description( speed_rating(), has_flag( mon_flag_IMMOBILE ), type->speed_desc ); - ss += speed_desc + "\n"; + tmp.emplace_back( speed_desc ); - ss += "--\n"; - ss += string_format( "%s", type->get_description() ) + "\n"; - ss += "--\n"; + tmp.emplace_back( "--" ); + tmp.emplace_back( string_format( "%s", type->get_description() ) ); + tmp.emplace_back( "--" ); if( !mission_fused.empty() ) { // Mission monsters fused into this monster const std::string fused_desc = string_format( _( "Parts of %s protrude from its body." ), enumerate_as_string( mission_fused ) ); - ss += string_format( "%s", fused_desc ) + "\n"; - ss += "--\n"; + tmp.emplace_back( string_format( "%s", fused_desc ) ); + tmp.emplace_back( "--" ); } - ss += string_format( _( "It is %s in size." ), - size_names.at( get_size() ) ) + "\n"; + tmp.emplace_back( string_format( _( "It is %s in size." ), + size_names.at( get_size() ) ) ); std::vector types = type->species_descriptions(); if( type->has_flag( mon_flag_ANIMAL ) ) { types.emplace_back( _( "an animal" ) ); } if( !types.empty() ) { - ss += string_format( _( "It is %s." ), - enumerate_as_string( types ) ) + "\n"; + tmp.emplace_back( string_format( _( "It is %s." ), + enumerate_as_string( types ) ) ); } using flag_description = std::pair; - const auto describe_flags = [this, &ss]( + const auto describe_flags = [this, &tmp]( const std::string_view format, const std::vector &flags_names, const std::string &if_empty = "" ) { @@ -1151,14 +1159,14 @@ std::string monster::extended_description() const return type->has_flag( fd.first ) ? fd.second : ""; } ); if( !flag_descriptions.empty() ) { - ss += string_format( format, flag_descriptions ) + "\n"; + tmp.emplace_back( string_format( format, flag_descriptions ) ); } else if( !if_empty.empty() ) { - ss += if_empty + "\n"; + tmp.emplace_back( if_empty ); } }; using property_description = std::pair; - const auto describe_properties = [&ss]( + const auto describe_properties = [&tmp]( const std::string_view format, const std::vector &property_names, const std::string &if_empty = "" ) { @@ -1167,9 +1175,9 @@ std::string monster::extended_description() const return pd.first ? pd.second : ""; } ); if( !property_descriptions.empty() ) { - ss += string_format( format, property_descriptions ) + "\n"; + tmp.emplace_back( string_format( format, property_descriptions ) ); } else if( !if_empty.empty() ) { - ss += if_empty + "\n"; + tmp.emplace_back( if_empty ); } }; @@ -1193,62 +1201,69 @@ std::string monster::extended_description() const } ); if( !type->has_flag( mon_flag_NOHEAD ) ) { - ss += std::string( _( "It has a head." ) ) + "\n"; + tmp.emplace_back( _( "It has a head." ) ); } if( debug_mode ) { - ss += "--\n"; + tmp.emplace_back( "--" ); - ss += string_format( _( "Current Speed: %1$d" ), get_speed() ) + "\n"; - ss += string_format( _( "Anger: %1$d" ), anger ) + "\n"; - ss += string_format( _( "Friendly: %1$d" ), friendly ) + "\n"; - ss += string_format( _( "Morale: %1$d" ), morale ) + "\n"; + tmp.emplace_back( string_format( _( "Current Speed: %1$d" ), get_speed() ) ); + tmp.emplace_back( string_format( _( "Anger: %1$d" ), anger ) ); + tmp.emplace_back( string_format( _( "Friendly: %1$d" ), friendly ) ); + tmp.emplace_back( string_format( _( "Morale: %1$d" ), morale ) ); if( aggro_character ) { - ss += string_format( _( "Aggressive towards characters" ) ) + "\n"; + tmp.emplace_back( string_format( _( "Aggressive towards characters" ) ) ); } const time_duration current_time = calendar::turn - calendar::turn_zero; - ss += string_format( _( "Current Time: Turn %1$d | Day: %2$d" ), - to_turns( current_time ), - to_days( current_time ) ) + "\n"; + tmp.emplace_back( string_format( _( "Current Time: Turn %1$d | Day: %2$d" ), + to_turns( current_time ), + to_days( current_time ) ) ); - ss += string_format( _( "Upgrade time: %1$d (turns left %2$d) %3$s" ), - upgrade_time, - to_turns( time_duration::from_days( upgrade_time ) - current_time ), - can_upgrade() ? "" : _( "(can't upgrade)" ) ) + "\n"; + tmp.emplace_back( string_format( _( "Upgrade time: %1$d (turns left %2$d) %3$s" ), + upgrade_time, + to_turns( time_duration::from_days( upgrade_time ) - current_time ), + can_upgrade() ? "" : _( "(can't upgrade)" ) ) ); if( !special_attacks.empty() ) { - ss += string_format( _( "%d special attack(s): " ), special_attacks.size() ); + std::string sattack_str = string_format( _( "%d special attack(s): " ), special_attacks.size() ); for( const auto &attack : special_attacks ) { - ss += string_format( _( "%s, cooldown %d; " ), attack.first.c_str(), attack.second.cooldown ); + sattack_str += string_format( _( "%s, cooldown %d; " ), attack.first.c_str(), + attack.second.cooldown ); } - ss += "\n"; + tmp.emplace_back( sattack_str ); } if( baby_timer.has_value() ) { - ss += string_format( _( "Reproduce time: %1$d (turns left %2$d) %3$s" ), - to_turn( baby_timer.value() ), - to_turn( baby_timer.value() - current_time ), - reproduces ? "" : _( "(can't reproduce)" ) ) + "\n"; + tmp.emplace_back( string_format( _( "Reproduce time: %1$d (turns left %2$d) %3$s" ), + to_turn( baby_timer.value() ), + to_turn( baby_timer.value() - current_time ), + reproduces ? "" : _( "(can't reproduce)" ) ) ); } if( biosig_timer.has_value() ) { - ss += string_format( _( "Biosignature time: %1$d (turns left %2$d) %3$s" ), - to_turn( biosig_timer.value() ), - to_turn( biosig_timer.value() - current_time ), - biosignatures ? "" : _( "(no biosignature)" ) ) + "\n"; + tmp.emplace_back( string_format( _( "Biosignature time: %1$d (turns left %2$d) %3$s" ), + to_turn( biosig_timer.value() ), + to_turn( biosig_timer.value() - current_time ), + biosignatures ? "" : _( "(no biosignature)" ) ) ); } if( lifespan_end.has_value() ) { - ss += string_format( _( "Lifespan end time: %1$d (turns left %2$d)" ), - to_turn( lifespan_end.value() ), - to_turn( lifespan_end.value() - current_time ) ); + tmp.emplace_back( string_format( _( "Lifespan end time: %1$d (turns left %2$d)" ), + to_turn( lifespan_end.value() ), + to_turn( lifespan_end.value() - current_time ) ) ); } else { - ss += "Lifespan end time: n/a (indefinite)"; + tmp.emplace_back( "Lifespan end time: n/a (indefinite)" ); } } - return replace_colors( ss ); + std::vector ret; + ret.reserve( tmp.size() ); + for( const std::string &s : tmp ) { + ret.emplace_back( replace_colors( s ) ); + } + + return ret; } const std::string &monster::symbol() const diff --git a/src/monster.h b/src/monster.h index efe7ac06169a1..fea4cc3ca37e6 100644 --- a/src/monster.h +++ b/src/monster.h @@ -161,7 +161,7 @@ class monster : public Creature nc_color color_with_effects() const; // Color with fire, beartrapped, etc. - std::string extended_description() const override; + std::vector extended_description() const override; // Inverts color if inv==true // // Returns true if f is set (see mtype.h) bool has_flag( const mon_flag_id &f ) const final; diff --git a/src/npc.cpp b/src/npc.cpp index 3b9cc200a9525..50f069598d516 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -3367,36 +3367,34 @@ mfaction_id npc::get_monster_faction() const return monfaction_human.id(); } -std::string npc::extended_description() const +std::vector npc::extended_description() const { - std::string ss; - // For some reason setting it using str or constructor doesn't work - ss += Character::extended_description(); + std::vector tmp = Character::extended_description(); - ss += "\n--\n"; - if( attitude == NPCATT_KILL ) { - ss += _( "Is trying to kill you." ); - } else if( attitude == NPCATT_FLEE || attitude == NPCATT_FLEE_TEMP ) { - ss += _( "Is trying to flee from you." ); - } else if( is_player_ally() ) { - ss += _( "Is your friend." ); - } else if( is_following() ) { - ss += _( "Is following you." ); - } else if( is_leader() ) { - ss += _( "Is guiding you." ); - } else if( guaranteed_hostile() ) { - ss += _( "Will try to kill you or flee from you if you reveal yourself." ); - } else { - ss += _( "Is neutral." ); - } + tmp.emplace_back( "--" ); + + Character &player_character = get_player_character(); + Attitude att = attitude_to( player_character ); + const std::pair res = Creature::get_attitude_ui_data( att ); + tmp.emplace_back( string_format( "%s; %s", colorize( res.first, res.second ), + colorize( npc_attitude_name( get_attitude() ), symbol_color() ) ) ); + + tmp.emplace_back( sees( player_character ) ? colorize( _( "Aware of your presence" ), c_yellow ) : + colorize( _( "Unaware of you" ), c_green ) ); if( hit_by_player ) { - ss += "--\n"; - ss += _( "Is still innocent and killing them will be considered murder." ); + tmp.emplace_back( "--" ); + tmp.emplace_back( _( "Is still innocent and killing them will be considered murder." ) ); // TODO: "But you don't care because you're an edgy psycho" } - return replace_colors( ss ); + std::vector ret; + ret.reserve( tmp.size() ); + for( const std::string &s : tmp ) { + ret.emplace_back( replace_colors( s ) ); + } + + return ret; } std::string npc::get_epilogue() const diff --git a/src/npc.h b/src/npc.h index d7290344ad219..06da200240e03 100644 --- a/src/npc.h +++ b/src/npc.h @@ -1263,7 +1263,7 @@ class npc : public Character bool query_yn( const std::string &mes ) const override; - std::string extended_description() const override; + std::vector extended_description() const override; std::string get_epilogue() const; std::pair hp_description() const; diff --git a/src/ui_extended_description.cpp b/src/ui_extended_description.cpp new file mode 100644 index 0000000000000..fb7f5b5517b02 --- /dev/null +++ b/src/ui_extended_description.cpp @@ -0,0 +1,238 @@ +#include "ui_extended_description.h" + +#include "character.h" +#include "creature_tracker.h" +#include "faction.h" +#include "map.h" +#include "ui_manager.h" +#include "vehicle.h" +#include "veh_type.h" + +static const trait_id trait_ILLITERATE( "ILLITERATE" ); + +static description_target &operator++( description_target &c ) +{ + c = static_cast( static_cast( c ) + 1 ); + if( c == description_target::num_targets ) { + c = static_cast( 0 ); + } + return c; +} + +static description_target &operator--( description_target &c ) +{ + if( c == static_cast( 0 ) ) { + c = description_target::num_targets; + } + c = static_cast( static_cast( c ) - 1 ); + return c; +} + +static const Creature *seen_critter( const tripoint_bub_ms &p ) +{ + const Creature *critter = get_creature_tracker().creature_at( p, true ); + if( critter != nullptr && get_player_view().sees( *critter ) ) { + return critter; + } + + return nullptr; +} + +static void draw_sign_text( const tripoint_bub_ms &p ) +{ + std::string signage = get_map().get_signage( p ); + if( !signage.empty() ) { + ImGui::Separator(); + cataimgui::draw_colored_text( get_player_character().has_trait( trait_ILLITERATE ) ? + // NOLINTNEXTLINE(cata-text-style): the question mark does not end a sentence + _( "Sign: ???" ) : string_format( _( "Sign: %s" ), signage ) ); + } +} + +static void draw_graffiti_text( const tripoint_bub_ms &p ) +{ + std::string graffiti = get_map().graffiti_at( p ); + if( !graffiti.empty() ) { + ImGui::Separator(); + cataimgui::draw_colored_text( get_player_character().has_trait( trait_ILLITERATE ) ? + // NOLINTNEXTLINE(cata-text-style): the question mark does not end a sentence + _( "Inscription: ???" ) : string_format( _( "Inscription: %s" ), graffiti ) ); + } +} + +void draw_extended_description( const std::vector &description, const uint64_t width ) +{ + for( const std::string &s : description ) { + if( s == "--" ) { + ImGui::Separator(); + } else { + cataimgui::draw_colored_text( s, c_light_gray, width ); + } + } +} + +extended_description_window::extended_description_window( tripoint_bub_ms &p ) : + cataimgui::window( "ext_desc", + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoNavInputs ), + p( p ) +{ + ctxt = input_context( "EXTENDED_DESCRIPTION" ); + ctxt.register_action( "NEXT_TAB" ); + ctxt.register_action( "PREV_TAB" ); + ctxt.register_action( "CREATURE" ); + ctxt.register_action( "FURNITURE" ); + ctxt.register_action( "TERRAIN" ); + ctxt.register_action( "VEHICLE" ); + ctxt.register_action( "CONFIRM" ); + ctxt.register_action( "QUIT" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.set_timeout( 10 ); + + const Creature *critter = seen_critter( p ); + if( critter ) { + creature_description = critter->extended_description(); + } + bool sees = get_player_character().sees( p ); + furn_id furn = get_map().furn( p ); + if( sees && furn ) { + furniture_description = furn->extended_description(); + } + ter_id ter = get_map().ter( p ); + if( sees && ter ) { + terrain_description = ter->extended_description(); + } + const optional_vpart_position vp = get_map().veh_at( p ); + if( sees && vp ) { + veh_app_description = vp.extended_description(); + } + + if( critter ) { + switch_target = description_target::creature; + } else if( get_map().has_furn( p ) ) { + switch_target = description_target::furniture; + } else if( get_map().veh_at( p ) ) { + switch_target = description_target::vehicle; + } + + +} + +void extended_description_window::draw_controls() +{ + if( ImGui::BeginTabBar( "description tabs" ) ) { + draw_creature_tab(); + draw_furniture_tab(); + draw_terrain_tab(); + draw_vehicle_tab(); + ImGui::EndTabBar(); + } +} + +void extended_description_window::draw_creature_tab() +{ + ImGuiTabItemFlags_ flags = ImGuiTabItemFlags_None; + if( switch_target == description_target::creature ) { + flags = ImGuiTabItemFlags_SetSelected; + switch_target = description_target::num_targets; + } + std::string title = string_format( _( "[%s] Creature" ), ctxt.get_desc( "CREATURE" ) ); + if( ImGui::BeginTabItem( title.c_str(), nullptr, flags ) ) { + cur_target = description_target::creature; + if( !creature_description.empty() ) { + draw_extended_description( creature_description, str_width_to_pixels( TERMX ) ); + } else { + cataimgui::draw_colored_text( _( "You do not see any creature here." ), c_light_gray ); + } + ImGui::EndTabItem(); + } +} + +void extended_description_window::draw_furniture_tab() +{ + ImGuiTabItemFlags_ flags = ImGuiTabItemFlags_None; + if( switch_target == description_target::furniture ) { + flags = ImGuiTabItemFlags_SetSelected; + switch_target = description_target::num_targets; + } + std::string title = string_format( _( "[%s] Furniture" ), ctxt.get_desc( "FURNITURE" ) ); + if( ImGui::BeginTabItem( title.c_str(), nullptr, flags ) ) { + cur_target = description_target::furniture; + if( !furniture_description.empty() ) { + draw_extended_description( furniture_description, str_width_to_pixels( TERMX ) ); + } else { + cataimgui::draw_colored_text( _( "You do not see any furniture here." ), c_light_gray ); + } + draw_sign_text( p ); + ImGui::EndTabItem(); + } +} + +void extended_description_window::draw_terrain_tab() +{ + ImGuiTabItemFlags_ flags = ImGuiTabItemFlags_None; + if( switch_target == description_target::terrain ) { + flags = ImGuiTabItemFlags_SetSelected; + switch_target = description_target::num_targets; + } + std::string title = string_format( _( "[%s] Terrain" ), ctxt.get_desc( "TERRAIN" ) ); + if( ImGui::BeginTabItem( title.c_str(), nullptr, flags ) ) { + cur_target = description_target::terrain; + if( !terrain_description.empty() ) { + draw_extended_description( terrain_description, str_width_to_pixels( TERMX ) ); + } else { + cataimgui::draw_colored_text( _( "You can't see the terrain here." ), c_light_gray ); + } + draw_graffiti_text( p ); + ImGui::EndTabItem(); + } +} + +void extended_description_window::draw_vehicle_tab() +{ + ImGuiTabItemFlags_ flags = ImGuiTabItemFlags_None; + if( switch_target == description_target::vehicle ) { + flags = ImGuiTabItemFlags_SetSelected; + switch_target = description_target::num_targets; + } + std::string title = string_format( _( "[%s] Vehicle/Appliance" ), ctxt.get_desc( "VEHICLE" ) ); + if( ImGui::BeginTabItem( title.c_str(), nullptr, flags ) ) { + cur_target = description_target::vehicle; + if( !veh_app_description.empty() ) { + draw_extended_description( veh_app_description, str_width_to_pixels( TERMX ) ); + } else { + cataimgui::draw_colored_text( _( "You can't see vehicles or appliances here." ), c_light_gray ); + } + ImGui::EndTabItem(); + } +} + +cataimgui::bounds extended_description_window::get_bounds() +{ + return bounds; +} + +void extended_description_window::show() +{ + while( true ) { + ui_manager::redraw_invalidated(); + std::string action = ctxt.handle_input(); + if( action == "NEXT_TAB" ) { + switch_target = cur_target; + ++switch_target; + } else if( action == "PREV_TAB" ) { + switch_target = cur_target; + --switch_target; + } else if( action == "CREATURE" ) { + switch_target = description_target::creature; + } else if( action == "FURNITURE" ) { + switch_target = description_target::furniture; + } else if( action == "TERRAIN" ) { + switch_target = description_target::terrain; + } else if( action == "VEHICLE" ) { + switch_target = description_target::vehicle; + } else if( action == "CONFIRM" || action == "QUIT" ) { + return; + } + } +} diff --git a/src/ui_extended_description.h b/src/ui_extended_description.h new file mode 100644 index 0000000000000..99c1b5001f028 --- /dev/null +++ b/src/ui_extended_description.h @@ -0,0 +1,50 @@ +#pragma once +#ifndef CATA_SRC_UI_EXTENDED_DESCRIPTION_H +#define CATA_SRC_UI_EXTENDED_DESCRIPTION_H + +#include "cata_imgui.h" +#include "coordinates.h" +#include "input_context.h" +#include "imgui/imgui.h" +#include "output.h" + +enum class description_target : int { + creature = 0, + furniture, + terrain, + vehicle, + num_targets +}; + +void draw_extended_description( const std::vector &description, uint64_t width ); + +class extended_description_window : public cataimgui::window +{ + public: + explicit extended_description_window( tripoint_bub_ms &p ); + + void show(); + + protected: + void draw_controls() override; + cataimgui::bounds get_bounds() override; + + private: + void draw_creature_tab(); + void draw_furniture_tab(); + void draw_terrain_tab(); + void draw_vehicle_tab(); + + input_context ctxt; + description_target cur_target = description_target::terrain; + description_target switch_target = description_target::terrain; + tripoint_bub_ms p; + cataimgui::bounds bounds{ 0.0f, 0.0f, static_cast( str_width_to_pixels( TERMX ) ), static_cast( str_height_to_pixels( TERMY ) ) }; + + std::vector creature_description; + std::vector furniture_description; + std::vector terrain_description; + std::vector veh_app_description; +}; + +#endif // CATA_SRC_UI_EXTENDED_DESCRIPTION_H diff --git a/src/veh_type.h b/src/veh_type.h index c57e7af850412..675cad47ce1af 100644 --- a/src/veh_type.h +++ b/src/veh_type.h @@ -505,6 +505,7 @@ struct vehicle_prototype { std::vector parts; std::vector item_spawns; std::vector zone_defs; + std::vector> src; shared_ptr_fast blueprint; @@ -512,7 +513,6 @@ struct vehicle_prototype { static void save_vehicle_as_prototype( const vehicle &veh, JsonOut &json ); private: bool was_loaded = false; // used by generic_factory - std::vector> src; friend class generic_factory; friend struct mod_tracker; }; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 3d1ad7868a870..149d842dabb5b 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -56,6 +56,7 @@ #include "material.h" #include "math_defines.h" #include "messages.h" +#include "mod_manager.h" #include "monster.h" #include "move_mode.h" #include "npc.h" @@ -2762,20 +2763,25 @@ std::optional optional_vpart_position::part_with_tool( return has_value() ? value().part_with_tool( tool_type ) : std::nullopt; } -std::string optional_vpart_position::extended_description() const +std::vector optional_vpart_position::extended_description() const { + std::vector ret; if( !has_value() ) { - return std::string(); + return ret; } vehicle &v = value().vehicle(); - std::string desc = v.name; + ret.emplace_back( get_origin( v.type->src ) ); + ret.emplace_back( "--" ); + + ret.emplace_back( string_format( _( "%s (%s)" ), v.name, v.owner->name ) ); + ret.emplace_back( "--" ); for( int idx : v.parts_at_relative( value().mount(), true ) ) { - desc += "\n" + v.part( idx ).name(); + ret.emplace_back( v.part( idx ).name() ); } - return desc; + return ret; } int vehicle::part_with_feature( int part, vpart_bitflags flag, bool unbroken, diff --git a/src/vpart_position.h b/src/vpart_position.h index 47efb5466c1d6..9e4dec1cccf4a 100644 --- a/src/vpart_position.h +++ b/src/vpart_position.h @@ -141,7 +141,7 @@ class optional_vpart_position : public std::optional std::optional obstacle_at_part() const; std::optional part_displayed() const; std::optional part_with_tool( const itype_id &tool_type ) const; - std::string extended_description() const; + std::vector extended_description() const; }; /**