diff --git a/data/json/bionics.json b/data/json/bionics.json index 5f1fcc4507c65..f12214723e504 100644 --- a/data/json/bionics.json +++ b/data/json/bionics.json @@ -719,7 +719,8 @@ "name": "Power Storage", "capacity": 100, "description": "A Compact Bionics Module that increases your power capacity by 100 units. Having at least one of these is a prerequisite to using powered bionics. You will also need a power supply, found in various CBMs.", - "flags": [ "BIONIC_NPC_USABLE" ] + "occupied_bodyparts": [ [ "TORSO", 1 ] ], + "flags": [ "BIONIC_NPC_USABLE", "BIONIC_ALLOW_DUPLICATES" ] }, { "id": "bio_power_storage_mkII", @@ -727,7 +728,8 @@ "name": "Power Storage Mk. II", "capacity": 250, "description": "A Compact Bionics Module that increases your power capacity by 250 units.", - "flags": [ "BIONIC_NPC_USABLE" ] + "occupied_bodyparts": [ [ "TORSO", 1 ] ], + "flags": [ "BIONIC_NPC_USABLE", "BIONIC_ALLOW_DUPLICATES" ] }, { "id": "bio_power_weakness", diff --git a/src/bionics.cpp b/src/bionics.cpp index 0e206bda884d1..f8d1a29b27cf4 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -1939,18 +1939,16 @@ int player::get_free_bionics_slots( const body_part bp ) const void player::add_bionic( const bionic_id &b ) { - if( has_bionic( b ) ) { + if( has_bionic( b ) && !bionics[b].allow_duplicates ) { debugmsg( "Tried to install bionic %s that is already installed!", b.c_str() ); return; } units::energy pow_up = bionics[b].capacity; mod_max_power_level( pow_up ); - if( b == "bio_power_storage" || b == "bio_power_storage_mkII" ) { + if( pow_up > 0_mJ ) { add_msg_if_player( m_good, _( "Increased storage capacity by %i." ), units::to_kilojoule( pow_up ) ); - // Power Storage CBMs are not real bionic units, so return without adding it to my_bionics - return; } my_bionics->push_back( bionic( b, get_free_invlet( *this ) ) ); @@ -1969,8 +1967,13 @@ void player::add_bionic( const bionic_id &b ) void player::remove_bionic( const bionic_id &b ) { bionic_collection new_my_bionics; + // Multiple copies of power storage bionics may be installed + // now. This prevents all with the same id from getting + // uninstalled at the same time. + bool already_removed = false; for( auto &i : *my_bionics ) { - if( b == i.id ) { + if( b == i.id && !already_removed ) { + already_removed = true; continue; } @@ -2102,6 +2105,9 @@ void load_bionic( JsonObject &jsobj ) new_bionic.sleep_friendly = get_bool_or_flag( jsobj, "sleep_friendly", "BIONIC_SLEEP_FRIENDLY", false ); new_bionic.shockproof = get_bool_or_flag( jsobj, "shockproof", "BIONIC_SHOCKPROOF", false ); + new_bionic.allow_duplicates = get_bool_or_flag( jsobj, "allow_duplicates", + "BIONIC_ALLOW_DUPLICATES", + false ); new_bionic.fuel_efficiency = jsobj.get_float( "fuel_efficiency", 0 ); new_bionic.passive_fuel_efficiency = jsobj.get_float( "passive_fuel_efficiency", 0 ); diff --git a/src/bionics.h b/src/bionics.h index b00a11117fc6e..bfc80f690eedb 100644 --- a/src/bionics.h +++ b/src/bionics.h @@ -74,6 +74,10 @@ struct bionic_data { */ bool shockproof = false; /** + * If true, multiple instances of this bionic may be installed in the player. Only for power storage right now. + */ + bool allow_duplicates = false; + /** * If true, this bionic is included with another. */ bool included = false; diff --git a/src/bionics_ui.cpp b/src/bionics_ui.cpp index 2c1e41c282d51..beff834dd53f5 100644 --- a/src/bionics_ui.cpp +++ b/src/bionics_ui.cpp @@ -195,7 +195,7 @@ static void draw_description( const catacurses::window &win, const bionic &bio ) } static void draw_connectors( const catacurses::window &win, const int start_y, const int start_x, - const int last_x, const bionic_id &bio_id ) + const int last_x, const bionic_id &bio_id, const int bio_count ) { const int LIST_START_Y = 6; // first: pos_y, second: occupied slots @@ -251,7 +251,7 @@ static void draw_connectors( const catacurses::window &win, const int start_y, c mvwputch( win, point( last_x, y ), BORDER_COLOR, '<' ); // draw amount of consumed slots by this CBM - const std::string fmt_num = string_format( "(%d)", elem.second ); + const std::string fmt_num = string_format( "(%d)", elem.second * bio_count ); mvwprintz( win, point( turn_x + std::max( 1, ( last_x - turn_x - utf8_width( fmt_num ) ) / 2 ), y ), c_yellow, fmt_num ); } @@ -429,7 +429,17 @@ void player::power_bionics() //track which list we are looking at std::vector *current_bionic_list = ( tab_mode == TAB_ACTIVE ? &active : &passive ); - max_scroll_position = std::max( 0, static_cast( current_bionic_list->size() ) - LIST_HEIGHT ); + std::map bionic_counts; + std::vector bionics_unique; + for( size_t i = 0; i < current_bionic_list->size(); i++ ) { + if( bionic_counts.count( ( *current_bionic_list )[i]->info().name.translated() ) == 0 ) { + bionic_counts[( *current_bionic_list )[i]->info().name.translated() ] = 1; + bionics_unique.push_back( *( *current_bionic_list )[i] ); + } else { + bionic_counts[( *current_bionic_list )[i]->info().name.translated() ] += 1; + } + } + max_scroll_position = std::max( 0, static_cast( bionics_unique.size() ) - LIST_HEIGHT ); if( redraw ) { redraw = false; @@ -454,7 +464,7 @@ void player::power_bionics() } } - if( current_bionic_list->empty() ) { + if( bionics_unique.empty() ) { std::string msg; switch( tab_mode ) { case TAB_ACTIVE: @@ -466,22 +476,32 @@ void player::power_bionics() } fold_and_print( wBio, point( 2, list_start_y ), pos_x - 1, c_light_gray, msg ); } else { - for( size_t i = scroll_position; i < current_bionic_list->size(); i++ ) { + for( size_t i = scroll_position; i < bionics_unique.size(); i++ ) { if( list_start_y + static_cast( i ) - scroll_position == HEIGHT - 1 ) { break; } const bool is_highlighted = cursor == static_cast( i ); - const nc_color col = get_bionic_text_color( *( *current_bionic_list )[i], + const nc_color col = get_bionic_text_color( bionics_unique[i], is_highlighted ); - const std::string desc = string_format( "%c %s", ( *current_bionic_list )[i]->invlet, - build_bionic_powerdesc_string( - *( *current_bionic_list )[i] ).c_str() ); + int bionic_count = bionic_counts[ bionics_unique[ i ].info().name.translated() ]; + std::string desc; + if( bionic_count > 1 ) { + desc = string_format( "%c %d %s", + bionics_unique[i].invlet, + bionic_count, + build_bionic_powerdesc_string( bionics_unique[i] ).c_str() + ); + } else { + desc = string_format( "%c %s", bionics_unique[i].invlet, + build_bionic_powerdesc_string( + bionics_unique[i] ).c_str() ); + } trim_and_print( wBio, point( 2, list_start_y + i - scroll_position ), WIDTH - 3, col, desc ); if( is_highlighted && menu_mode != EXAMINING && get_option < bool >( "CBM_SLOTS_ENABLED" ) ) { - const bionic_id bio_id = ( *current_bionic_list )[i]->id; + const bionic_id bio_id = bionics_unique[i].id; draw_connectors( wBio, list_start_y + i - scroll_position, utf8_width( desc ) + 3, - pos_x - 2, bio_id ); + pos_x - 2, bio_id, bionic_count ); // redraw highlighted (occupied) body parts for( auto &elem : bio_id->occupied_bodyparts ) { @@ -493,13 +513,13 @@ void player::power_bionics() } } - draw_scrollbar( wBio, cursor, LIST_HEIGHT, current_bionic_list->size(), point( 0, list_start_y ) ); + draw_scrollbar( wBio, cursor, LIST_HEIGHT, bionics_unique.size(), point( 0, list_start_y ) ); #if defined(__ANDROID__) ctxt.get_registered_manual_keys().clear(); - for( size_t i = 0; i < current_bionic_list->size(); i++ ) { - ctxt.register_manual_key( ( *current_bionic_list )[i]->invlet, - build_bionic_powerdesc_string( *( *current_bionic_list )[i] ).c_str() ); + for( size_t i = 0; i < bionics_unique.size(); i++ ) { + ctxt.register_manual_key( bionics_unique[i].invlet, + build_bionic_powerdesc_string( bionics_unique[i] ).c_str() ); } #endif @@ -508,8 +528,8 @@ void player::power_bionics() draw_bionics_tabs( w_tabs, active.size(), passive.size(), tab_mode ); draw_bionics_titlebar( w_title, this, menu_mode ); - if( menu_mode == EXAMINING && !current_bionic_list->empty() ) { - draw_description( w_description, *( *current_bionic_list )[cursor] ); + if( menu_mode == EXAMINING && !bionics_unique.empty() ) { + draw_description( w_description, bionics_unique[cursor] ); } const std::string action = ctxt.handle_input(); @@ -519,7 +539,7 @@ void player::power_bionics() if( action == "DOWN" ) { redraw = true; - if( static_cast( cursor ) < current_bionic_list->size() - 1 ) { + if( static_cast( cursor ) < bionics_unique.size() - 1 ) { cursor++; } else { cursor = 0; @@ -536,7 +556,7 @@ void player::power_bionics() if( cursor > 0 ) { cursor--; } else { - cursor = current_bionic_list->size() - 1; + cursor = bionics_unique.size() - 1; } if( scroll_position > 0 && cursor - scroll_position < half_list_view_location ) { scroll_position--; @@ -544,13 +564,13 @@ void player::power_bionics() if( scroll_position < max_scroll_position && cursor - scroll_position > LIST_HEIGHT - half_list_view_location ) { scroll_position = - std::max( std::min( current_bionic_list->size() - LIST_HEIGHT, + std::max( std::min( bionics_unique.size() - LIST_HEIGHT, cursor - half_list_view_location ), 0 ); } } else if( menu_mode == REASSIGNING ) { menu_mode = ACTIVATING; - if( action == "CONFIRM" && !current_bionic_list->empty() ) { + if( action == "CONFIRM" && !bionics_unique.empty() ) { auto &bio_list = tab_mode == TAB_ACTIVE ? active : passive; tmp = bio_list[cursor]; } else { @@ -617,7 +637,7 @@ void player::power_bionics() //confirmation either occurred by pressing enter where the bionic cursor is, or the hotkey was selected if( confirmCheck ) { auto &bio_list = tab_mode == TAB_ACTIVE ? active : passive; - if( action == "CONFIRM" && !current_bionic_list->empty() ) { + if( action == "CONFIRM" && !bionics_unique.empty() ) { tmp = bio_list[cursor]; } else { tmp = bionic_by_invlet( ch ); diff --git a/src/game_inventory.cpp b/src/game_inventory.cpp index 86b52793f789e..9a2c6af4f99a7 100644 --- a/src/game_inventory.cpp +++ b/src/game_inventory.cpp @@ -1620,7 +1620,7 @@ class bionic_install_preset: public inventory_selector_preset return _( "/!\\ CBM is not sterile. /!\\" ) ; } else if( it->has_fault( fault_id( "fault_bionic_salvaged" ) ) ) { return _( "CBM already deployed. Please reset to factory state." ); - } else if( pa.has_bionic( bid ) ) { + } else if( pa.has_bionic( bid ) && !bid->allow_duplicates ) { return _( "CBM already installed" ); } else if( !pa.can_install_cbm_on_bp( get_occupied_bodyparts( bid ) ) ) { return _( "CBM not compatible with patient's body." ); diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index e4aa327aecb28..cda846f80c828 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -4116,7 +4116,7 @@ ret_val install_bionic_actor::can_use( const Character &p, const item &it, } } - if( p.has_bionic( bid ) ) { + if( p.has_bionic( bid ) && !bid->allow_duplicates ) { return ret_val::make_failure( _( "You have already installed this bionic." ) ); } else if( bid->upgraded_bionic && !p.has_bionic( bid->upgraded_bionic ) ) { return ret_val::make_failure( _( "There is nothing to upgrade." ) ); diff --git a/src/player_display.cpp b/src/player_display.cpp index 85f2a2c324bf2..12b03f0c0461b 100644 --- a/src/player_display.cpp +++ b/src/player_display.cpp @@ -501,19 +501,27 @@ static void draw_traits_tab( const catacurses::window &w_traits, const catacurse } } -static void draw_bionics_tab( const catacurses::window &w_bionics, const catacurses::window &w_info, - player &you, unsigned int &line, int &curtab, input_context &ctxt, bool &done, - std::string &action, std::vector &bionicslist, - const size_t bionics_win_size_y ) +static std::vector draw_bionics_list( const catacurses::window &w_bionics, player &you, + unsigned int &line, + std::vector &bionicslist, const size_t bionics_win_size_y, bool highlight ) { - werase( w_bionics ); - mvwprintz( w_bionics, point_zero, h_light_gray, header_spaces ); - center_print( w_bionics, 0, h_light_gray, _( title_BIONICS ) ); + center_print( w_bionics, 0, highlight ? h_light_gray : c_light_gray, _( title_BIONICS ) ); // NOLINTNEXTLINE(cata-use-named-point-constants) trim_and_print( w_bionics, point( 1, 1 ), getmaxx( w_bionics ) - 1, c_white, string_format( _( "Bionic Power: %1$d / %2$d" ), units::to_kilojoule( you.get_power_level() ), units::to_kilojoule( you.get_max_power_level() ) ) ); + std::map bionic_counts; + std::vector bionics_unique; + for( size_t i = 0; i < bionicslist.size(); i++ ) { + if( bionic_counts.count( bionicslist[i].info().name.translated() ) == 0 ) { + bionic_counts[ bionicslist[i].info().name.translated() ] = 1; + bionics_unique.push_back( bionicslist[i] ); + } else { + bionic_counts[ bionicslist[i].info().name.translated() ] += 1; + } + } + const size_t useful_y = bionics_win_size_y - 1; const size_t half_y = useful_y / 2; @@ -522,30 +530,53 @@ static void draw_bionics_tab( const catacurses::window &w_bionics, const catacur if( line <= half_y ) { // near the top min = 0; - max = std::min( bionicslist.size(), useful_y ); - } else if( line >= bionicslist.size() - half_y ) { // near the bottom - min = ( bionicslist.size() <= useful_y ? 0 : bionicslist.size() - useful_y ); - max = bionicslist.size(); + max = std::min( bionics_unique.size(), useful_y ); + } else if( line >= bionics_unique.size() - half_y ) { // near the bottom + min = ( bionics_unique.size() <= useful_y ? 0 : bionics_unique.size() - useful_y ); + max = bionics_unique.size(); } else { // scrolling min = line - half_y; - max = std::min( bionicslist.size(), line + useful_y - half_y ); + max = std::min( bionics_unique.size(), line + useful_y - half_y ); } for( size_t i = min; i < max; i++ ) { + int bionic_count = bionic_counts[ bionics_unique[ i ].info().name.translated() ]; + std::string bionic_string; + if( bionic_count > 1 ) { + bionic_string = string_format( "%d %s", bionic_count, + bionics_unique[ i ].info().name.translated() ); + } else { + bionic_string = bionics_unique[ i ].info().name.translated(); + } + mvwprintz( w_bionics, point( 1, static_cast( i + 2 ) ), c_black, " " ); trim_and_print( w_bionics, point( 1, static_cast( 2 + i - min ) ), getmaxx( w_bionics ) - 1, - i == line ? hilite( c_white ) : c_white, "%s", bionicslist[i].info().name ); + i == line && highlight ? hilite( c_white ) : c_white, bionic_string ); } - if( line < bionicslist.size() ) { + + // used to make sure draw_bionics_tab highlights the unique list properly + return bionics_unique; +} + +static void draw_bionics_tab( const catacurses::window &w_bionics, const catacurses::window &w_info, + player &you, unsigned int &line, int &curtab, input_context &ctxt, bool &done, + std::string &action, std::vector &bionicslist, + const size_t bionics_win_size_y ) +{ + werase( w_bionics ); + mvwprintz( w_bionics, point_zero, h_light_gray, header_spaces ); + std::vector unique_bionics = draw_bionics_list( w_bionics, you, line, bionicslist, + bionics_win_size_y, true ); + if( line < unique_bionics.size() ) { // NOLINTNEXTLINE(cata-use-named-point-constants) - fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_white, "%s", - bionicslist[line].info().description ); + fold_and_print( w_info, point( 1, 0 ), FULL_SCREEN_WIDTH - 2, c_white, + unique_bionics[line].info().description.translated() ); } wrefresh( w_bionics ); wrefresh( w_info ); action = ctxt.handle_input(); if( action == "DOWN" ) { - if( line < bionicslist.size() - 1 ) { + if( line < unique_bionics.size() - 1 ) { line++; } return; @@ -555,16 +586,7 @@ static void draw_bionics_tab( const catacurses::window &w_bionics, const catacur } } else if( action == "NEXT_TAB" || action == "PREV_TAB" ) { mvwprintz( w_bionics, point_zero, c_light_gray, header_spaces ); - center_print( w_bionics, 0, c_light_gray, _( title_BIONICS ) ); - // NOLINTNEXTLINE(cata-use-named-point-constants) - trim_and_print( w_bionics, point( 1, 1 ), getmaxx( w_bionics ) - 1, c_white, - string_format( _( "Bionic Power: %1$d / %2$d" ), - units::to_kilojoule( you.get_power_level() ), units::to_kilojoule( you.get_max_power_level() ) ) ); - for( size_t i = 0; i < bionicslist.size() && i < bionics_win_size_y - 1; i++ ) { - mvwprintz( w_bionics, point( 1, static_cast( i + 2 ) ), c_black, " " ); - trim_and_print( w_bionics, point( 1, static_cast( i + 2 ) ), getmaxx( w_bionics ) - 1, - c_white, "%s", bionicslist[i].info().name ); - } + draw_bionics_list( w_bionics, you, line, bionicslist, bionics_win_size_y, false ); wrefresh( w_bionics ); line = 0; curtab = action == "NEXT_TAB" ? curtab + 1 : curtab - 1; @@ -978,15 +1000,7 @@ static void draw_initial_windows( const catacurses::window &w_stats, wrefresh( w_traits ); // Next, draw bionics - center_print( w_bionics, 0, c_light_gray, _( title_BIONICS ) ); - // NOLINTNEXTLINE(cata-use-named-point-constants) - trim_and_print( w_bionics, point( 1, 1 ), getmaxx( w_bionics ) - 1, c_white, - string_format( _( "Bionic Power: %1$d / %2$d" ), - units::to_kilojoule( you.get_power_level() ), units::to_kilojoule( you.get_max_power_level() ) ) ); - for( size_t i = 0; i < bionicslist.size() && i < bionics_win_size_y - 1; i++ ) { - trim_and_print( w_bionics, point( 1, static_cast( i ) + 2 ), getmaxx( w_bionics ) - 1, c_white, - "%s", bionicslist[i].info().name ); - } + draw_bionics_list( w_bionics, you, line, bionicslist, bionics_win_size_y, false ); wrefresh( w_bionics ); // Next, draw effects.