diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 566dd08d0a9e6..0c98c914db760 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -1033,7 +1033,7 @@ void activity_handlers::butcher_finish( player_activity *act, player *p ) } item &corpse_item = *target; - std::list contents = corpse_item.contents; + const std::list contents = corpse_item.contents.all_items(); const mtype *corpse = corpse_item.get_mtype(); const field_type_id type_blood = corpse->bloodType(); const field_type_id type_gib = corpse->gibType(); @@ -1120,7 +1120,7 @@ void activity_handlers::butcher_finish( player_activity *act, player *p ) // therefore operations on this activities targets and values may be invalidated. // reveal hidden items / hidden content if( action != F_DRESS && action != SKIN ) { - for( auto &content : contents ) { + for( const item &content : contents ) { if( ( roll_butchery() + 10 ) * 5 > rng( 0, 100 ) ) { //~ %1$s - item name, %2$s - monster name p->add_msg_if_player( m_good, _( "You discover a %1$s in the %2$s!" ), content.tname(), @@ -2624,7 +2624,7 @@ void activity_handlers::gunmod_add_finish( player_activity *act, player *p ) if( rng( 0, 100 ) <= roll ) { add_msg( m_good, _( "You successfully attached the %1$s to your %2$s." ), mod.tname(), gun.tname() ); - gun.contents.push_back( p->i_rem( &mod ) ); + gun.contents.insert_legacy( p->i_rem( &mod ) ); } else if( rng( 0, 100 ) <= risk ) { if( gun.inc_damage() ) { @@ -2659,7 +2659,7 @@ void activity_handlers::toolmod_add_finish( player_activity *act, player *p ) p->add_msg_if_player( m_good, _( "You successfully attached the %1$s to your %2$s." ), mod.tname(), tool.tname() ); mod.item_tags.insert( "IRREMOVABLE" ); - tool.contents.push_back( mod ); + tool.contents.insert_legacy( mod ); act->targets[1].remove_item(); } @@ -3935,14 +3935,13 @@ void activity_handlers::unload_mag_finish( player_activity *act, player *p ) item &it = *act->targets[ 0 ]; // remove the ammo leads in the belt - it.contents.erase( std::remove_if( it.contents.begin(), - it.contents.end(), [&]( item & e ) { + it.contents.remove_items_if( [&]( item & e ) { if( !p->add_or_drop_with_msg( e, true ) ) { return false; } qty += e.charges; return true; - } ), it.contents.end() ); + } ); // remove the belt linkage if( it.is_ammo_belt() ) { @@ -4552,6 +4551,6 @@ void activity_handlers::mind_splicer_finish( player_activity *act, player *p ) item &data_card = *act->targets[0]; p->add_msg_if_player( m_info, _( "…you finally find the memory banks." ) ); p->add_msg_if_player( m_info, _( "The kit makes a copy of the data inside the bionic." ) ); - data_card.contents.clear(); + data_card.contents.clear_items(); data_card.put_in( item( "mind_scan_robofac" ) ); } diff --git a/src/advanced_inv.cpp b/src/advanced_inv.cpp index adfc5e8eb861a..67854fcd97591 100644 --- a/src/advanced_inv.cpp +++ b/src/advanced_inv.cpp @@ -1594,7 +1594,7 @@ bool advanced_inventory::move_content( item &src_container, item &dest_container return false; } - item &src_contents = src_container.contents.front(); + item &src_contents = src_container.contents.legacy_front(); if( !src_contents.made_of( LIQUID ) ) { popup( _( "You can unload only liquids into target container." ) ); @@ -1617,9 +1617,9 @@ bool advanced_inventory::move_content( item &src_container, item &dest_container } dest_container.fill_with( src_contents, amount ); - uistate.adv_inv_container_content_type = dest_container.contents.front().typeId(); + uistate.adv_inv_container_content_type = dest_container.contents.legacy_front().typeId(); if( src_contents.charges <= 0 ) { - src_container.contents.clear(); + src_container.contents.clear_items(); } return true; diff --git a/src/advanced_inv_area.cpp b/src/advanced_inv_area.cpp index 2890677790bef..267face1820e8 100644 --- a/src/advanced_inv_area.cpp +++ b/src/advanced_inv_area.cpp @@ -334,7 +334,7 @@ void advanced_inv_area::set_container( const advanced_inv_listitem *advitem ) uistate.adv_inv_container_index = advitem->idx; uistate.adv_inv_container_type = it->typeId(); uistate.adv_inv_container_content_type = !it->is_container_empty() ? - it->contents.front().typeId() : "null"; + it->contents.legacy_front().typeId() : "null"; set_container_position(); } else { uistate.adv_inv_container_location = -1; @@ -354,7 +354,7 @@ bool advanced_inv_area::is_container_valid( const item *it ) const return true; } } else { - if( it->contents.front().typeId() == uistate.adv_inv_container_content_type ) { + if( it->contents.legacy_front().typeId() == uistate.adv_inv_container_content_type ) { return true; } } diff --git a/src/advanced_inv_pane.cpp b/src/advanced_inv_pane.cpp index f402408cf9f8a..13bef72dcd5fb 100644 --- a/src/advanced_inv_pane.cpp +++ b/src/advanced_inv_pane.cpp @@ -113,7 +113,7 @@ void advanced_inventory_pane::add_items_from_area( advanced_inv_area &square, if( cont != nullptr ) { if( !cont->is_container_empty() ) { // filtering does not make sense for liquid in container - item *it = &square.get_container( in_vehicle() )->contents.front(); + item *it = &square.get_container( in_vehicle() )->contents.legacy_front(); advanced_inv_listitem ait( it, 0, 1, square.id, in_vehicle() ); square.volume += ait.volume; square.weight += ait.weight; diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index 107285c4825fa..20c142e413ea7 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -923,7 +923,7 @@ void avatar_action::eat( avatar &you, item_location loc ) } else if( you.consume_item( *it ) ) { if( it->is_food_container() || !you.can_consume_as_is( *it ) ) { - it->contents.erase( it->contents.begin() ); + it->contents.remove_item( it->contents.legacy_front() ); add_msg( _( "You leave the empty %s." ), it->tname() ); } else { loc.remove_item(); diff --git a/src/ballistics.cpp b/src/ballistics.cpp index 5542ad775570d..a5d4064d8cb45 100644 --- a/src/ballistics.cpp +++ b/src/ballistics.cpp @@ -52,7 +52,7 @@ static void drop_or_embed_projectile( const dealt_projectile_attack &attack ) add_msg( _( "The %s shatters!" ), drop_item.tname() ); } - for( const item &i : drop_item.contents ) { + for( const item &i : drop_item.contents.all_items() ) { g->m.add_item_or_charges( pt, i ); } // TODO: Non-glass breaking @@ -68,7 +68,7 @@ static void drop_or_embed_projectile( const dealt_projectile_attack &attack ) add_msg( _( "The %s bursts!" ), drop_item.tname() ); } - for( const item &i : drop_item.contents ) { + for( const item &i : drop_item.contents.all_items() ) { g->m.add_item_or_charges( pt, i ); } diff --git a/src/cata_utility.cpp b/src/cata_utility.cpp index 630af9d2cfc65..78c3c33297e17 100644 --- a/src/cata_utility.cpp +++ b/src/cata_utility.cpp @@ -255,6 +255,12 @@ double convert_weight( const units::mass &weight ) return ret; } +std::string weight_to_string( const units::mass &weight ) +{ + const double converted_weight = convert_weight( weight ); + return string_format( "%.2f %s", converted_weight, weight_units() ); +} + double convert_volume( int volume ) { return convert_volume( volume, nullptr ); @@ -281,6 +287,16 @@ double convert_volume( int volume, int *out_scale ) return ret; } +std::string vol_to_string( const units::volume &vol ) +{ + int converted_volume_scale = 0; + const double converted_volume = + convert_volume( vol.value(), + &converted_volume_scale ); + + return string_format( "%.3f %s", converted_volume, volume_units_abbr() ); +} + double temp_to_celsius( double fahrenheit ) { return ( ( fahrenheit - 32.0 ) * 5.0 / 9.0 ); diff --git a/src/cata_utility.h b/src/cata_utility.h index 38b8a1927b983..915e1aeb3c456 100644 --- a/src/cata_utility.h +++ b/src/cata_utility.h @@ -232,6 +232,9 @@ double convert_velocity( int velocity, units_type vel_units ); */ double convert_weight( const units::mass &weight ); +// convert a mass unit to a string readable by a human +std::string weight_to_string( const units::mass &weight ); + /** * Convert volume from ml to units defined by user. */ @@ -243,6 +246,9 @@ double convert_volume( int volume ); */ double convert_volume( int volume, int *out_scale ); +// convert a volume unit to a string readable by a human +std::string vol_to_string( const units::volume &vol ); + /** * Convert a temperature from degrees Fahrenheit to degrees Celsius. * diff --git a/src/character.cpp b/src/character.cpp index 0d3a360342c25..9bc24328f30b7 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -1523,7 +1523,7 @@ int Character::i_add_to_container( const item &it, const bool unloading ) const itype_id item_type = it.typeId(); auto add_to_container = [&it, &charges]( item & container ) { - auto &contained_ammo = container.contents.front(); + item &contained_ammo = container.contents.legacy_front(); if( contained_ammo.charges < container.ammo_capacity() ) { const int diff = container.ammo_capacity() - contained_ammo.charges; //~ %1$s: item name, %2$s: container name @@ -1540,7 +1540,8 @@ int Character::i_add_to_container( const item &it, const bool unloading ) }; visit_items( [ & ]( item * item ) { - if( charges > 0 && item->is_ammo_container() && item_type == item->contents.front().typeId() ) { + if( charges > 0 && item->is_ammo_container() && + item_type == item->contents.legacy_front().typeId() ) { charges = add_to_container( *item ); item->handle_pickup_ownership( *this ); } @@ -1662,7 +1663,7 @@ item Character::i_rem( const item *it ) void Character::i_rem_keep_contents( const int pos ) { - for( auto &content : i_rem( pos ).contents ) { + for( item &content : i_rem( pos ).contents.all_items() ) { i_add_or_drop( content ); } } @@ -1792,7 +1793,7 @@ void find_ammo_helper( T &src, const item &obj, bool empty, Output out, bool nes { if( obj.is_watertight_container() ) { if( !obj.is_container_empty() ) { - auto contents_id = obj.contents.front().typeId(); + itype_id contents_id = obj.contents.legacy_front().typeId(); // Look for containers with the same type of liquid as that already in our container src.visit_items( [&src, &nested, &out, &contents_id, &obj]( item * node ) { @@ -1806,7 +1807,7 @@ void find_ammo_helper( T &src, const item &obj, bool empty, Output out, bool nes } if( node->is_container() && !node->is_container_empty() && - node->contents.front().typeId() == contents_id ) { + node->contents.legacy_front().typeId() == contents_id ) { out = item_location( src, node ); } return nested ? VisitResponse::NEXT : VisitResponse::SKIP; @@ -1842,7 +1843,7 @@ void find_ammo_helper( T &src, const item &obj, bool empty, Output out, bool nes if( node->is_ammo_container() && !node->contents.empty() && !node->contents_made_of( SOLID ) ) { for( const ammotype &at : ammo ) { - if( node->contents.front().ammo_type() == at ) { + if( node->contents.legacy_front().ammo_type() == at ) { out = item_location( src, node ); } } @@ -7355,7 +7356,8 @@ void Character::absorb_hit( body_part bp, damage_instance &dam ) destroyed_armor_msg( *this, pre_damage_name ); armor_destroyed = true; armor.on_takeoff( *this ); - worn_remains.insert( worn_remains.end(), armor.contents.begin(), armor.contents.end() ); + std::list armor_contents = armor.contents.all_items(); + worn_remains.insert( worn_remains.end(), armor_contents.begin(), armor_contents.end() ); // decltype is the type name of the iterator, note that reverse_iterator::base returns the // iterator to the next element, not the one the revers_iterator points to. // http://stackoverflow.com/questions/1830158/how-to-call-erase-with-a-reverse-iterator diff --git a/src/computer_session.cpp b/src/computer_session.cpp index 6bdb08a48b142..598d995164f92 100644 --- a/src/computer_session.cpp +++ b/src/computer_session.cpp @@ -338,9 +338,9 @@ void computer_session::action_sample() capa = std::min( sewage.charges, capa ); if( elem.contents.empty() ) { elem.put_in( sewage ); - elem.contents.front().charges = capa; + elem.contents.legacy_front().charges = capa; } else { - elem.contents.front().charges += capa; + elem.contents.legacy_front().charges += capa; } found_item = true; break; @@ -690,7 +690,7 @@ void computer_session::action_download_software() g->u.moves -= 30; item software( miss->get_item_id(), 0 ); software.mission_id = comp.mission_id; - usb->contents.clear(); + usb->contents.clear_items(); usb->put_in( software ); print_line( _( "Software downloaded." ) ); } else { @@ -711,10 +711,10 @@ void computer_session::action_blood_anal() print_error( _( "ERROR: Please remove all but one sample from centrifuge." ) ); } else if( items.only_item().contents.empty() ) { print_error( _( "ERROR: Please only use container with blood sample." ) ); - } else if( items.only_item().contents.front().typeId() != "blood" ) { + } else if( items.only_item().contents.legacy_front().typeId() != "blood" ) { print_error( _( "ERROR: Please only use blood samples." ) ); } else { // Success! - const item &blood = items.only_item().contents.front(); + const item &blood = items.only_item().contents.legacy_front(); const mtype *mt = blood.get_mtype(); if( mt == nullptr || mt->id == mtype_id::NULL_ID() ) { print_line( _( "Result: Human blood, no pathogens found." ) ); @@ -728,7 +728,7 @@ void computer_session::action_blood_anal() if( query_bool( _( "Download data?" ) ) ) { if( item *const usb = pick_usb() ) { item software( "software_blood_data", 0 ); - usb->contents.clear(); + usb->contents.clear_items(); usb->put_in( software ); print_line( _( "Software downloaded." ) ); } else { @@ -1315,7 +1315,7 @@ void computer_session::failure_destroy_blood() print_error( _( "ERROR: Please use blood-contained samples." ) ); } else if( items.only_item().contents.empty() ) { print_error( _( "ERROR: Blood draw kit, empty." ) ); - } else if( items.only_item().contents.front().typeId() != "blood" ) { + } else if( items.only_item().contents.legacy_front().typeId() != "blood" ) { print_error( _( "ERROR: Please only use blood samples." ) ); } else { print_error( _( "ERROR: Blood sample destroyed." ) ); diff --git a/src/consumption.cpp b/src/consumption.cpp index 86c2b2a840105..2144df34966f7 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -277,8 +277,8 @@ std::pair Character::compute_nutrient_range( nutrients this_max; item result_it = rec->create_result(); - if( result_it.contents.size() == 1 ) { - const item alt_result = result_it.contents.front(); + if( result_it.contents.legacy_size() == 1 ) { + const item alt_result = result_it.contents.legacy_front(); if( alt_result.typeId() == comest_it.typeId() ) { result_it = alt_result; } @@ -1488,13 +1488,13 @@ bool Character::can_consume( const item &it ) const } // Checking NO_RELOAD to prevent consumption of `battery` when contained in `battery_car` (#20012) return !it.is_container_empty() && !it.has_flag( flag_NO_RELOAD ) && - can_consume_as_is( it.contents.front() ); + can_consume_as_is( it.contents.legacy_front() ); } item &Character::get_consumable_from( item &it ) const { - if( !it.is_container_empty() && can_consume_as_is( it.contents.front() ) ) { - return it.contents.front(); + if( !it.is_container_empty() && can_consume_as_is( it.contents.legacy_front() ) ) { + return it.contents.legacy_front(); } else if( can_consume_as_is( it ) ) { return it; } diff --git a/src/crafting.cpp b/src/crafting.cpp index 647ae4f228835..8b6aced957712 100644 --- a/src/crafting.cpp +++ b/src/crafting.cpp @@ -70,8 +70,6 @@ class basecamp; -void drop_or_handle( const item &newit, player &p ); - static bool crafting_allowed( const player &p, const recipe &rec ) { if( p.morale_crafting_speed_multiplier( rec ) <= 0.0f ) { @@ -376,8 +374,8 @@ bool player::check_eligible_containers_for_crafting( const recipe &rec, int batc } if( !cont->is_container_empty() ) { - if( cont->contents.front().typeId() == prod.typeId() ) { - charges_to_store -= cont->get_remaining_capacity_for_liquid( cont->contents.front(), true ); + if( cont->contents.legacy_front().typeId() == prod.typeId() ) { + charges_to_store -= cont->get_remaining_capacity_for_liquid( cont->contents.legacy_front(), true ); } } else { charges_to_store -= cont->get_remaining_capacity_for_liquid( prod, true ); @@ -1064,7 +1062,7 @@ void player::complete_craft( item &craft, const tripoint &loc ) // Points to newit unless newit is a non-empty container, then it points to newit's contents. // Necessary for things like canning soup; sometimes we want to operate on the soup, not the can. item &food_contained = ( newit.is_container() && !newit.contents.empty() ) ? - newit.contents.back() : newit; + newit.contents.legacy_back() : newit; // messages, learning of recipe, food spoilage calculation only once if( first ) { @@ -1539,11 +1537,11 @@ static void empty_buckets( player &p ) return it.is_bucket_nonempty() && &it != &p.weapon; }, INT_MAX ); for( auto &it : buckets ) { - for( const item &in : it.contents ) { - drop_or_handle( in, p ); + for( const item *in : it.contents.all_items_ptr() ) { + drop_or_handle( *in, p ); } - it.contents.clear(); + it.contents.clear_items(); drop_or_handle( it, p ); } } @@ -2259,7 +2257,7 @@ void remove_ammo( std::list &dis_items, player &p ) } } -void drop_or_handle( const item &newit, player &p ) +void drop_or_handle( const item &newit, Character &p ) { if( newit.made_of( LIQUID ) && p.is_player() ) { // TODO: what about NPCs? liquid_handler::handle_all_liquid( newit, PICKUP_RANGE ); @@ -2271,14 +2269,7 @@ void drop_or_handle( const item &newit, player &p ) void remove_ammo( item &dis_item, player &p ) { - for( auto iter = dis_item.contents.begin(); iter != dis_item.contents.end(); ) { - if( iter->is_irremovable() ) { - iter++; - continue; - } - drop_or_handle( *iter, p ); - iter = dis_item.contents.erase( iter ); - } + dis_item.contents.remove_all_ammo( p ); if( dis_item.has_flag( flag_NO_UNLOAD ) ) { return; diff --git a/src/crafting.h b/src/crafting.h index 18a00750e5fa0..03ff2484ef661 100644 --- a/src/crafting.h +++ b/src/crafting.h @@ -4,6 +4,7 @@ #include +class Character; class item; class player; class recipe; @@ -25,5 +26,8 @@ void remove_ammo( item &dis_item, player &p ); void remove_ammo( std::list &dis_items, player &p ); const recipe *select_crafting_recipe( int &batch_size ); +void drop_or_handle( const item &newit, Character &p ); + +void drop_or_handle( const item &newit, Character &p ); #endif diff --git a/src/crafting_gui.cpp b/src/crafting_gui.cpp index 878c305810c73..ea5cd8b393c40 100644 --- a/src/crafting_gui.cpp +++ b/src/crafting_gui.cpp @@ -887,7 +887,7 @@ std::string peek_related_recipe( const recipe *current, const recipe_subset &ava if( tmp.contents.empty() ) { // use this item tid = tmp.typeId(); } else { // use the contained item - tid = tmp.contents.front().typeId(); + tid = tmp.contents.legacy_front().typeId(); } const std::set &known_recipes = g->u.get_learned_recipes().of_component( tid ); for( const auto &b : known_recipes ) { diff --git a/src/dump.cpp b/src/dump.cpp index 34c85d62329cd..1b4be60c0524d 100644 --- a/src/dump.cpp +++ b/src/dump.cpp @@ -67,7 +67,7 @@ bool game::dump_stats( const std::string &what, dump_mode mode, test_items[ "G2" ] = item( "hk_mp5" ).ammo_set( "9mm" ); test_items[ "G3" ] = item( "ar15" ).ammo_set( "223" ); test_items[ "G4" ] = item( "remington_700" ).ammo_set( "270" ); - test_items[ "G4" ].emplace_back( "rifle_scope" ); + test_items[ "G4" ].contents.insert_legacy( item( "rifle_scope" ) ); if( what == "AMMO" ) { header = { @@ -212,14 +212,14 @@ bool game::dump_stats( const std::string &what, dump_mode mode, if( e->gun ) { item gun( e ); if( !gun.magazine_integral() ) { - gun.emplace_back( gun.magazine_default() ); + gun.contents.insert_legacy( item( gun.magazine_default() ) ); } gun.ammo_set( gun.ammo_default( false ), gun.ammo_capacity() ); dump( test_npcs[ "S1" ], gun ); if( gun.type->gun->barrel_length > 0_ml ) { - gun.emplace_back( "barrel_small" ); + gun.contents.insert_legacy( item( "barrel_small" ) ); dump( test_npcs[ "S1" ], gun ); } } diff --git a/src/faction_camp.cpp b/src/faction_camp.cpp index fd3642b0dd0c8..f05b61565adf5 100644 --- a/src/faction_camp.cpp +++ b/src/faction_camp.cpp @@ -3702,7 +3702,7 @@ bool basecamp::distribute_food() for( item &i : initial_items ) { if( i.is_container() && i.get_contained().is_food() ) { auto comest = i.get_contained(); - i.contents.clear(); + i.contents.clear_items(); //NPCs are lazy bastards who leave empties all around the camp fire tripoint litter_spread = p_litter; litter_spread.x += rng( -3, 3 ); diff --git a/src/game.cpp b/src/game.cpp index 8c48b3aecbcdb..de5e7b3c59ffc 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -8326,7 +8326,7 @@ void game::reload( item_location &loc, bool prompt, bool empty ) // for holsters and ammo pouches try to reload any contained item if( it->type->can_use( "holster" ) && !it->contents.empty() ) { - it = &it->contents.front(); + it = &it->contents.legacy_front(); } // for bandoliers we currently defer to iuse_actor methods @@ -8535,6 +8535,8 @@ void game::wield( item_location &loc ) } break; } + case item_location::type::container: + break; case item_location::type::invalid: debugmsg( "Failed wield from invalid item location" ); break; diff --git a/src/gamemode_defense.cpp b/src/gamemode_defense.cpp index dc26e58211ba0..4c9ad53b4a58d 100644 --- a/src/gamemode_defense.cpp +++ b/src/gamemode_defense.cpp @@ -1084,7 +1084,7 @@ void defense_game::caravan() // Guns bought from the caravan should always come with an empty // magazine. if( tmp.is_gun() && !tmp.magazine_integral() ) { - tmp.emplace_back( tmp.magazine_default() ); + tmp.contents.insert_legacy( item( tmp.magazine_default() ) ); } for( int j = 0; j < item_count[0][i]; j++ ) { diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 7e3434c0bede9..0e7a79f15c45d 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -709,7 +709,7 @@ static void smash() if( u.weapon.made_of( material_id( "glass" ) ) && rng( 0, vol + 3 ) < vol ) { add_msg( m_bad, _( "Your %s shatters!" ), u.weapon.tname() ); - for( auto &elem : u.weapon.contents ) { + for( item &elem : u.weapon.contents.all_items() ) { m.add_item_or_charges( u.pos(), elem ); } sounds::sound( u.pos(), 24, sounds::sound_t::combat, "CRACK!", true, "smash", "glass" ); @@ -1281,13 +1281,13 @@ static void fire() for( auto &w : u.worn ) { if( w.type->can_use( "holster" ) && !w.has_flag( flag_NO_QUICKDRAW ) && - !w.contents.empty() && w.contents.front().is_gun() ) { + !w.contents.empty() && w.contents.legacy_front().is_gun() ) { //~ draw (first) gun contained in holster //~ %1$s: weapon name, %2$s: container name, %3$d: remaining ammo count options.push_back( string_format( pgettext( "holster", "%1$s from %2$s (%3$d)" ), - w.contents.front().tname(), + w.contents.legacy_front().tname(), w.type_name(), - w.contents.front().ammo_remaining() ) ); + w.contents.legacy_front().ammo_remaining() ) ); actions.emplace_back( [&] { u.invoke_item( &w, "holster" ); } ); diff --git a/src/handle_liquid.cpp b/src/handle_liquid.cpp index cfdad43df3832..bf6f0919ab71d 100644 --- a/src/handle_liquid.cpp +++ b/src/handle_liquid.cpp @@ -121,7 +121,7 @@ bool handle_liquid_from_ground( map_stack::iterator on_ground, return true; } -bool handle_liquid_from_container( std::list::iterator in_container, +bool handle_liquid_from_container( item *in_container, item &container, int radius ) { // TODO: not all code paths on handle_liquid consume move points, fix that. @@ -134,13 +134,13 @@ bool handle_liquid_from_container( std::list::iterator in_container, if( in_container->charges > 0 ) { return false; } - container.contents.erase( in_container ); + container.contents.remove_item( *in_container ); return true; } bool handle_liquid_from_container( item &container, int radius ) { - return handle_liquid_from_container( container.contents.begin(), container, radius ); + return handle_liquid_from_container( &container.contents.legacy_front(), container, radius ); } static bool get_liquid_target( item &liquid, item *const source, const int radius, @@ -195,7 +195,7 @@ static bool get_liquid_target( item &liquid, item *const source, const int radiu } // Sometimes the cont parameter is omitted, but the liquid is still within a container that counts // as valid target for the liquid. So check for that. - if( cont == source || ( !cont->contents.empty() && &cont->contents.front() == &liquid ) ) { + if( cont == source || ( !cont->contents.empty() && &cont->contents.legacy_front() == &liquid ) ) { add_msg( m_info, _( "That's the same container!" ) ); return; // The user has intended to do something, but mistyped. } @@ -338,6 +338,7 @@ static bool perform_liquid_transfer( item &liquid, const tripoint *const source_ g->m.veh_at( target.item_loc.position() )->vehicle().make_active( target.item_loc ); break; case item_location::type::character: + case item_location::type::container: case item_location::type::invalid: break; } diff --git a/src/handle_liquid.h b/src/handle_liquid.h index ec0bde98ee5c7..e078186f63f29 100644 --- a/src/handle_liquid.h +++ b/src/handle_liquid.h @@ -82,7 +82,7 @@ bool handle_liquid_from_ground( map_stack::iterator on_ground, const tripoint &p * The iterator is invalidated in that case. Otherwise the item remains but may have * fewer charges. */ -bool handle_liquid_from_container( std::list::iterator in_container, item &container, +bool handle_liquid_from_container( item *in_container, item &container, int radius = 0 ); /** * Shortcut to the above: handles the first item in the container. diff --git a/src/iexamine.cpp b/src/iexamine.cpp index 35dc578b5e460..0165462edf3c9 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -3314,9 +3314,9 @@ void iexamine::tree_maple_tapped( player &p, const tripoint &examp ) if( it.is_bucket() || it.is_watertight_container() ) { container = ⁢ - if( !it.is_container_empty() && it.contents.front().typeId() == "maple_sap" ) { + if( !it.is_container_empty() && it.contents.legacy_front().typeId() == "maple_sap" ) { has_sap = true; - charges = it.contents.front().charges; + charges = it.contents.legacy_front().charges; } } } diff --git a/src/item.cpp b/src/item.cpp index 63667fbf45dcb..ec73c48bf7f91 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -194,10 +194,12 @@ item::item() : bday( calendar::start_of_cataclysm ) { type = nullitem(); charges = 0; + contents = item_contents( type->pockets ); } item::item( const itype *type, time_point turn, int qty ) : type( type ), bday( turn ) { + contents = item_contents( type->pockets ); corpse = has_flag( "CORPSE" ) ? &mtype_id::NULL_ID().obj() : nullptr; item_counter = type->countdown_interval; @@ -219,15 +221,18 @@ item::item( const itype *type, time_point turn, int qty ) : type( type ), bday( if( type->gun ) { for( const std::string &mod : type->gun->built_in_mods ) { - emplace_back( mod, turn, qty ).item_tags.insert( "IRREMOVABLE" ); + item it( mod, turn, qty ); + it.item_tags.insert( "IRREMOVABLE" ); + contents.insert_legacy( it ); } for( const std::string &mod : type->gun->default_mods ) { - emplace_back( mod, turn, qty ); + contents.insert_legacy( item( mod, turn, qty ) ); } } else if( type->magazine ) { if( type->magazine->count > 0 ) { - emplace_back( type->magazine->default_ammo, calendar::turn, type->magazine->count ); + contents.insert_legacy( item( type->magazine->default_ammo, calendar::turn, + type->magazine->count ) ); } } else if( has_temperature() || goes_bad() ) { @@ -448,10 +453,10 @@ item &item::ammo_set( const itype_id &ammo, int qty ) if( is_magazine() ) { ammo_unset(); - emplace_back( ammo, calendar::turn, std::min( qty, ammo_capacity() ) ); + contents.insert_legacy( item( ammo, calendar::turn, std::min( qty, ammo_capacity() ) ) ); if( has_flag( "NO_UNLOAD" ) ) { - contents.back().item_tags.insert( "NO_DROP" ); - contents.back().item_tags.insert( "IRREMOVABLE" ); + contents.legacy_back().item_tags.insert( "NO_DROP" ); + contents.legacy_back().item_tags.insert( "IRREMOVABLE" ); } } else if( magazine_integral() ) { @@ -488,7 +493,7 @@ item &item::ammo_set( const itype_id &ammo, int qty ) } } } - emplace_back( mag ); + contents.insert_legacy( item( mag ) ); } magazine_current()->ammo_set( ammo, qty ); } @@ -501,7 +506,7 @@ item &item::ammo_unset() if( !is_tool() && !is_gun() && !is_magazine() ) { // do nothing } else if( is_magazine() ) { - contents.clear(); + contents.clear_items(); } else if( magazine_integral() ) { curammo = nullptr; charges = 0; @@ -665,11 +670,11 @@ item item::in_container( const itype_id &cont ) const { if( cont != "null" ) { item ret( cont, birthday() ); - ret.contents.push_back( *this ); + ret.put_in( *this ); if( made_of( LIQUID ) && ret.is_container() ) { // Note: we can't use any of the normal container functions as they check the // container being suitable (seals, watertight etc.) - ret.contents.back().charges = charges_per_volume( ret.get_container_capacity() ); + ret.contents.legacy_back().charges = charges_per_volume( ret.get_container_capacity() ); } ret.invlet = invlet; @@ -777,13 +782,7 @@ bool item::stacks_with( const item &rhs, bool check_components ) const } } } - if( contents.size() != rhs.contents.size() ) { - return false; - } - return std::equal( contents.begin(), contents.end(), rhs.contents.begin(), []( const item & a, - const item & b ) { - return a.charges == b.charges && a.stacks_with( b ); - } ); + return contents.stacks_with( rhs.contents ); } bool item::merge_charges( const item &rhs ) @@ -805,9 +804,16 @@ bool item::merge_charges( const item &rhs ) return true; } -void item::put_in( const item &payload ) +void item::put_in( const item &payload, item_pocket::pocket_type pk_type ) { - contents.push_back( payload ); + switch( pk_type ) { + case item_pocket::pocket_type::CONTAINER: + contents.insert_item( payload ); + break; + default: + contents.insert_legacy( payload ); + break; + } } void item::set_var( const std::string &name, const int value ) @@ -2569,16 +2575,16 @@ void item::qualities_info( std::vector &info, const iteminfo_query *pa name_quality( q ); } } - + std::list all_items = contents.all_items(); if( parts->test( iteminfo_parts::QUALITIES_CONTAINED ) && - std::any_of( contents.begin(), contents.end(), []( const item & e ) { + std::any_of( all_items.begin(), all_items.end(), []( const item & e ) { return !e.type->qualities.empty(); } ) ) { info.emplace_back( "QUALITIES", "", _( "Contains items with qualities:" ) ); std::map most_quality; - for( const item &e : contents ) { - for( const std::pair &q : e.type->qualities ) { + for( const item &e : all_items ) { + for( const std::pair &q : e.type->qualities ) { auto emplace_result = most_quality.emplace( q ); if( !emplace_result.second && most_quality.at( emplace_result.first->first ) < q.second ) { @@ -2592,7 +2598,7 @@ void item::qualities_info( std::vector &info, const iteminfo_query *pa } } -void item::final_info( std::vector &info, const iteminfo_query *parts, int batch, +void item::final_info( std::vector &info, const iteminfo_query *parts, int, bool /*debug*/ ) const { if( is_null() ) { @@ -3116,8 +3122,8 @@ void item::final_info( std::vector &info, const iteminfo_query *parts, "sickly green glow." ) ) ); } - if( is_brewable() || ( !contents.empty() && contents.front().is_brewable() ) ) { - const item &brewed = !is_brewable() ? contents.front() : *this; + if( is_brewable() || ( !contents.empty() && contents.legacy_front().is_brewable() ) ) { + const item &brewed = !is_brewable() ? contents.legacy_front() : *this; if( parts->test( iteminfo_parts::DESCRIPTION_BREWABLE_DURATION ) ) { const time_duration btime = brewed.brewing_time(); int btime_i = to_days( btime ); @@ -3155,13 +3161,13 @@ void item::final_info( std::vector &info, const iteminfo_query *parts, } // does the item fit in any holsters? - std::vector holsters = Item_factory::find( [this]( const itype & e ) { + std::vector holsters = Item_factory::find( []( const itype & e ) { if( !e.can_use( "holster" ) ) { return false; } const holster_actor *ptr = dynamic_cast ( e.get_use( "holster" )->get_actor_ptr() ); - return ptr->can_holster( *this ); + return ptr->can_holster( item( &e ) ); } ); if( !holsters.empty() && parts->test( iteminfo_parts::DESCRIPTION_HOLSTERS ) ) { @@ -3232,41 +3238,8 @@ void item::final_info( std::vector &info, const iteminfo_query *parts, info.emplace_back( "DESCRIPTION", mod_str ); info.emplace_back( "DESCRIPTION", mod->type->description.translated() ); } - bool contents_header = false; - for( const item &contents_item : contents ) { - if( !contents_item.type->mod ) { - if( !contents_header ) { - insert_separation_line( info ); - info.emplace_back( "DESCRIPTION", _( "Contents of this item:" ) ); - contents_header = true; - } else { - // Separate items with a blank line - info.emplace_back( "DESCRIPTION", space ); - } - - const translation &description = contents_item.type->description; - - if( contents_item.made_of_from_type( LIQUID ) ) { - units::volume contents_volume = contents_item.volume() * batch; - int converted_volume_scale = 0; - const double converted_volume = - round_up( convert_volume( contents_volume.value(), - &converted_volume_scale ), 2 ); - info.emplace_back( "DESCRIPTION", contents_item.display_name() ); - iteminfo::flags f = iteminfo::no_newline; - if( converted_volume_scale != 0 ) { - f |= iteminfo::is_decimal; - } - info.emplace_back( "CONTAINER", description + space, - string_format( " %s", volume_units_abbr() ), f, - converted_volume ); - } else { - info.emplace_back( "DESCRIPTION", contents_item.display_name() ); - info.emplace_back( "DESCRIPTION", description.translated() ); - } - } - } } + contents.info( info ); if( this->get_var( "die_num_sides", 0 ) != 0 ) { info.emplace_back( "DESCRIPTION", string_format( _( "* This item can be used as a die, " @@ -3280,7 +3253,7 @@ void item::final_info( std::vector &info, const iteminfo_query *parts, if( contents.empty() ) { // use this item tid = typeId(); } else { // use the contained item - tid = contents.front().typeId(); + tid = contents.legacy_front().typeId(); } const std::set &known_recipes = g->u.get_learned_recipes().of_component( tid ); if( !known_recipes.empty() && parts->test( iteminfo_parts::DESCRIPTION_APPLICABLE_RECIPES ) ) { @@ -3332,7 +3305,7 @@ std::string item::info( std::vector &info, const iteminfo_query *parts if( is_medication() ) { med_item = this; } else if( is_med_container() ) { - med_item = &contents.front(); + med_item = &contents.legacy_front(); } if( med_item != nullptr ) { med_info( med_item, info, parts, batch, debug ); @@ -3412,7 +3385,7 @@ int item::get_free_mod_locations( const gunmod_location &location ) const return 0; } int result = loc->second; - for( const item &elem : contents ) { + for( const item &elem : contents.all_items() ) { const cata::value_ptr &mod = elem.type->gunmod; if( mod && mod->location == location ) { result--; @@ -3747,11 +3720,11 @@ void item::on_pickup( Character &p ) handle_pickup_ownership( p ); } if( is_bucket_nonempty() ) { - for( const item &it : contents ) { + for( const item &it : contents.all_items() ) { g->m.add_item_or_charges( p.pos(), it ); } - contents.clear(); + contents.clear_items(); } p.flag_encumbrance(); @@ -3871,8 +3844,8 @@ std::string item::tname( unsigned int quantity, bool with_prefix, unsigned int t } const int percent_progress = item_counter / 100000; maintext += string_format( " (%d%%)", percent_progress ); - } else if( contents.size() == 1 ) { - const item &contents_item = contents.front(); + } else if( contents.legacy_size() == 1 ) { + const item &contents_item = contents.legacy_front(); if( contents_item.made_of( LIQUID ) || contents_item.is_food() ) { const unsigned contents_count = contents_item.charges > 1 ? contents_item.charges : quantity; //~ %1$s: item name, %2$s: content liquid, food, or drink name @@ -3887,8 +3860,8 @@ std::string item::tname( unsigned int quantity, bool with_prefix, unsigned int t maintext = string_format( npgettext( "item name", //~ %1$s: item name, %2$zd: content size "%1$s with %2$zd item", - "%1$s with %2$zd items", contents.size() ), - label( quantity ), contents.size() ); + "%1$s with %2$zd items", contents.num_item_stacks() ), + label( quantity ), contents.num_item_stacks() ); } else { maintext = label( quantity ); } @@ -4063,14 +4036,14 @@ std::string item::display_name( unsigned int quantity ) const } int amount = 0; int max_amount = 0; - bool has_item = is_container() && contents.size() == 1; - bool has_ammo = is_ammo_container() && contents.size() == 1; + bool has_item = is_container() && contents.legacy_size() == 1; + bool has_ammo = is_ammo_container() && contents.legacy_size() == 1; bool contains = has_item || has_ammo; bool show_amt = false; // We should handle infinite charges properly in all cases. if( contains ) { - amount = contents.front().charges; - max_amount = contents.front().charges_per_volume( get_container_capacity() ); + amount = contents.legacy_front().charges; + max_amount = contents.legacy_front().charges_per_volume( get_container_capacity() ); } else if( is_book() && get_chapters() > 0 ) { // a book which has remaining unread chapters amount = get_remaining_chapters( g->u ); @@ -4177,7 +4150,7 @@ int item::price( bool practical ) const } // TODO: MATERIALS add a density field to materials.json -units::mass item::weight( bool include_contents, bool integral ) const +units::mass item::weight( bool, bool integral ) const { if( is_null() ) { return 0_gram; @@ -4259,15 +4232,7 @@ units::mass item::weight( bool include_contents, bool integral ) const ret -= std::min( max_barrel_weight, barrel_weight ); } - if( is_gun() ) { - for( const item *elem : gunmods() ) { - ret += elem->weight( true, true ); - } - } else if( include_contents ) { - for( const item &elem : contents ) { - ret += elem.weight(); - } - } + ret += contents.item_weight_modifier(); return ret; } @@ -4364,12 +4329,7 @@ units::volume item::volume( bool integral ) const } } - // Non-rigid items add the volume of the content - if( !type->rigid ) { - for( const item &elem : contents ) { - ret += elem.volume(); - } - } + ret += contents.item_size_modifier(); // Some magazines sit (partly) flush with the item so add less extra volume if( magazine_current() != nullptr ) { @@ -4613,8 +4573,9 @@ int item::get_quality( const quality_id &id ) const * EXCEPTION: Items with quality BOIL only count as such if they are empty, * excluding items of their ammo type if they are tools. */ + const std::list all_items{ contents.all_items() }; if( id == quality_id( "BOIL" ) && !( contents.empty() || - ( is_tool() && std::all_of( contents.begin(), contents.end(), + ( is_tool() && std::all_of( all_items.begin(), all_items.end(), [this]( const item & itm ) { if( itm.is_ammo() ) { return ammo_types().count( itm.ammo_type() ) != 0; @@ -4640,7 +4601,7 @@ int item::get_quality( const quality_id &id ) const return_quality = quality.second; } } - for( const item &itm : contents ) { + for( const item &itm : contents.all_items() ) { return_quality = std::max( return_quality, itm.get_quality( id ) ); } @@ -4661,10 +4622,9 @@ std::vector item::toolmods() { std::vector res; if( is_tool() ) { - res.reserve( contents.size() ); - for( item &e : contents ) { - if( e.is_toolmod() ) { - res.push_back( &e ); + for( item *e : contents.all_items_ptr() ) { + if( e->is_toolmod() ) { + res.push_back( e ); } } } @@ -4675,10 +4635,9 @@ std::vector item::toolmods() const { std::vector res; if( is_tool() ) { - res.reserve( contents.size() ); - for( const item &e : contents ) { - if( e.is_toolmod() ) { - res.push_back( &e ); + for( const item *e : contents.all_items_ptr() ) { + if( e->is_toolmod() ) { + res.push_back( e ); } } } @@ -4774,7 +4733,7 @@ int item::spoilage_sort_order() if( type->container->preserves ) { return bottom - 3; } - subject = &contents.front(); + subject = &contents.legacy_front(); } else { subject = this; } @@ -4981,12 +4940,7 @@ bool item::is_power_armor() const int item::get_encumber( const Character &p ) const { - - units::volume contents_volume( 0_ml ); - - for( const item &e : contents ) { - contents_volume += e.volume(); - } + units::volume contents_volume( contents.item_size_modifier() ); if( p.is_worn( *this ) ) { const islot_armor *t = find_armor_data(); @@ -5624,7 +5578,7 @@ bool item::made_of( const material_id &mat_ident ) const bool item::contents_made_of( const phase_id phase ) const { - return !contents.empty() && contents.front().made_of( phase ); + return !contents.empty() && contents.legacy_front().made_of( phase ); } bool item::made_of( phase_id phase ) const @@ -5775,7 +5729,7 @@ bool item::is_brewable() const bool item::is_food_container() const { - return ( !contents.empty() && contents.front().is_food() ) || ( is_craft() && + return ( !contents.empty() && contents.legacy_front().is_food() ) || ( is_craft() && craft_data_->making->create_result().is_food_container() ); } @@ -5786,7 +5740,7 @@ bool item::has_temperature() const bool item::is_med_container() const { - return !contents.empty() && contents.front().is_medication(); + return !contents.empty() && contents.legacy_front().is_medication(); } bool item::is_corpse() const @@ -5841,7 +5795,7 @@ static Item *get_food_impl( Item *it ) if( it->is_food() ) { return it; } else if( it->is_food_container() && !it->contents.empty() ) { - return &it->contents.front(); + return &it->contents.legacy_front(); } else { return nullptr; } @@ -5869,7 +5823,7 @@ void item::set_mtype( const mtype *const m ) bool item::is_ammo_container() const { - return !is_magazine() && !contents.empty() && contents.front().is_ammo(); + return !is_magazine() && !contents.empty() && contents.legacy_front().is_ammo(); } bool item::is_melee() const @@ -6028,7 +5982,7 @@ bool item::is_container_full( bool allow_bucket ) const if( is_container_empty() ) { return false; } - return get_remaining_capacity_for_liquid( contents.front(), allow_bucket ) == 0; + return get_remaining_capacity_for_liquid( contents.legacy_front(), allow_bucket ) == 0; } bool item::can_unload_liquid() const @@ -6037,7 +5991,7 @@ bool item::can_unload_liquid() const return true; } - const item &cts = contents.front(); + const item &cts = contents.legacy_front(); bool cts_is_frozen_liquid = cts.made_of_from_type( LIQUID ) && cts.made_of( SOLID ); return is_bucket() || !cts_is_frozen_liquid; } @@ -6060,7 +6014,7 @@ bool item::is_reloadable_helper( const itype_id &ammo, bool now ) const } else if( is_watertight_container() ) { return ( ( now ? !is_container_full() : true ) && ( ammo.empty() || ( find_type( ammo )->phase == LIQUID && ( is_container_empty() - || contents.front().typeId() == ammo ) ) ) ); + || contents.legacy_front().typeId() == ammo ) ) ) ); } else if( magazine_integral() ) { if( !ammo.empty() ) { if( ammo_data() ) { @@ -6111,9 +6065,9 @@ bool item::is_funnel_container( units::volume &bigger_than ) const } if( contents.empty() || - contents.front().typeId() == "water" || - contents.front().typeId() == "water_acid" || - contents.front().typeId() == "water_acid_weak" ) { + contents.legacy_front().typeId() == "water" || + contents.legacy_front().typeId() == "water_acid" || + contents.legacy_front().typeId() == "water_acid_weak" ) { bigger_than = get_container_capacity(); return true; } @@ -6222,7 +6176,7 @@ const item &item::get_contained() const if( contents.empty() ) { return null_item_reference(); } - return contents.front(); + return contents.legacy_front(); } bool item::spill_contents( Character &c ) @@ -6242,8 +6196,8 @@ bool item::spill_contents( Character &c ) return false; } } else { - c.i_add_or_drop( contents.front() ); - contents.erase( contents.begin() ); + c.i_add_or_drop( contents.legacy_front() ); + contents.remove_item( contents.legacy_front() ); } } @@ -6255,13 +6209,7 @@ bool item::spill_contents( const tripoint &pos ) if( !is_container() || is_container_empty() ) { return true; } - - for( item &it : contents ) { - g->m.add_item_or_charges( pos, it ); - } - - contents.clear(); - return true; + return contents.spill_contents( pos ); } int item::get_chapters() const @@ -6347,9 +6295,9 @@ bool item::operator<( const item &other ) const if( cat_a != cat_b ) { return cat_a < cat_b; } else { - const item *me = is_container() && !contents.empty() ? &contents.front() : this; + const item *me = is_container() && !contents.empty() ? &contents.legacy_front() : this; const item *rhs = other.is_container() && - !other.contents.empty() ? &other.contents.front() : &other; + !other.contents.empty() ? &other.contents.legacy_front() : &other; if( me->typeId() == rhs->typeId() ) { if( me->is_money() ) { @@ -6598,7 +6546,7 @@ int item::ammo_remaining() const if( is_magazine() || is_bandolier() ) { int res = 0; - for( const item &e : contents ) { + for( const item &e : contents.all_items() ) { res += e.charges; } return res; @@ -6690,14 +6638,10 @@ int item::ammo_consume( int qty, const tripoint &pos ) const int res = mag->ammo_consume( qty, pos ); if( res && ammo_remaining() == 0 ) { if( mag->has_flag( "MAG_DESTROY" ) ) { - contents.remove_if( [&mag]( const item & e ) { - return mag == &e; - } ); + contents.remove_item( *mag ); } else if( mag->has_flag( "MAG_EJECT" ) ) { g->m.add_item( pos, *mag ); - contents.remove_if( [&mag]( const item & e ) { - return mag == &e; - } ); + contents.remove_item( *mag ); } } return res; @@ -6706,10 +6650,10 @@ int item::ammo_consume( int qty, const tripoint &pos ) if( is_magazine() ) { int need = qty; while( !contents.empty() ) { - item &e = *contents.rbegin(); + item &e = contents.legacy_front(); if( need >= e.charges ) { need -= e.charges; - contents.pop_back(); + contents.legacy_pop_back(); } else { e.charges -= need; need = 0; @@ -6746,7 +6690,7 @@ const itype *item::ammo_data() const } if( is_magazine() ) { - return !contents.empty() ? contents.front().ammo_data() : nullptr; + return !contents.empty() ? contents.legacy_front().ammo_data() : nullptr; } auto mods = is_gun() ? gunmods() : toolmods(); @@ -6920,10 +6864,7 @@ std::set item::magazine_compatible( bool conversion ) const item *item::magazine_current() { - auto iter = std::find_if( contents.begin(), contents.end(), []( const item & it ) { - return it.is_magazine(); - } ); - return iter != contents.end() ? &*iter : nullptr; + return contents.magazine_current(); } const item *item::magazine_current() const @@ -6935,10 +6876,9 @@ std::vector item::gunmods() { std::vector res; if( is_gun() ) { - res.reserve( contents.size() ); - for( item &e : contents ) { - if( e.is_gunmod() ) { - res.push_back( &e ); + for( item *e : contents.all_items_ptr() ) { + if( e->is_gunmod() ) { + res.push_back( e ); } } } @@ -6949,10 +6889,9 @@ std::vector item::gunmods() const { std::vector res; if( is_gun() ) { - res.reserve( contents.size() ); - for( const item &e : contents ) { - if( e.is_gunmod() ) { - res.push_back( &e ); + for( const item *e : contents.all_items_ptr() ) { + if( e->is_gunmod() ) { + res.push_back( e ); } } } @@ -7153,7 +7092,7 @@ const use_function *item::get_use( const std::string &use_name ) const return type->get_use( use_name ); } - for( const item &elem : contents ) { + for( const item &elem : contents.all_items() ) { const use_function *fun = elem.get_use( use_name ); if( fun != nullptr ) { return fun; @@ -7169,10 +7108,10 @@ item *item::get_usable_item( const std::string &use_name ) return this; } - for( item &elem : contents ) { - const use_function *fun = elem.get_use( use_name ); + for( item *elem : contents.all_items_ptr() ) { + const use_function *fun = elem->get_use( use_name ); if( fun != nullptr ) { - return &elem; + return elem; } } @@ -7234,7 +7173,7 @@ void item::reload_option::qty( int val ) bool ammo_in_container = ammo->is_ammo_container(); bool ammo_in_liquid_container = ammo->is_watertight_container(); item &ammo_obj = ( ammo_in_container || ammo_in_liquid_container ) ? - ammo->contents.front() : *ammo; + ammo->contents.legacy_front() : *ammo; if( ( ammo_in_container && !ammo_obj.is_ammo() ) || ( ammo_in_liquid_container && !ammo_obj.made_of( LIQUID ) ) ) { @@ -7285,19 +7224,7 @@ void item::casings_handle( const std::function &func ) if( !is_gun() ) { return; } - - for( auto it = contents.begin(); it != contents.end(); ) { - if( it->has_flag( "CASING" ) ) { - it->unset_flag( "CASING" ); - if( func( *it ) ) { - it = contents.erase( it ); - continue; - } - // didn't handle the casing so reset the flag ready for next call - it->set_flag( "CASING" ); - } - ++it; - } + contents.casings_handle( func ); } bool item::reload( player &u, item_location loc, int qty ) @@ -7315,7 +7242,7 @@ bool item::reload( player &u, item_location loc, int qty ) item *container = nullptr; if( ammo->is_ammo_container() || ammo->is_container() ) { container = ammo; - ammo = &ammo->contents.front(); + ammo = &ammo->contents.legacy_front(); } if( !is_reloadable_with( ammo->typeId() ) ) { @@ -7350,14 +7277,14 @@ bool item::reload( player &u, item_location loc, int qty ) to_reload.charges = qty; ammo->charges -= qty; bool merged = false; - for( item &it : contents ) { - if( it.merge_charges( to_reload ) ) { + for( item *it : contents.all_items_ptr() ) { + if( it->merge_charges( to_reload ) ) { merged = true; break; } } if( !merged ) { - contents.emplace_back( to_reload ); + contents.insert_legacy( to_reload ); } } else if( is_watertight_container() ) { if( !ammo->made_of_from_type( LIQUID ) ) { @@ -7384,13 +7311,13 @@ bool item::reload( player &u, item_location loc, int qty ) remove_item( *magazine_current() ); } - contents.emplace_back( *ammo ); + contents.insert_legacy( *ammo ); loc.remove_item(); return true; } else { if( ammo->has_flag( "SPEEDLOADER" ) ) { - curammo = ammo->contents.front().type; + curammo = ammo->contents.legacy_front().type; qty = std::min( qty, ammo->ammo_remaining() ); ammo->ammo_consume( qty, tripoint_zero ); charges += qty; @@ -7411,7 +7338,7 @@ bool item::reload( player &u, item_location loc, int qty ) if( ammo->charges == 0 && !ammo->has_flag( "SPEEDLOADER" ) ) { if( container != nullptr ) { - container->contents.erase( container->contents.begin() ); + container->contents.remove_item( container->contents.legacy_front() ); u.inv.restack( u ); // emptied containers do not stack with non-empty ones } else { loc.remove_item(); @@ -7505,7 +7432,7 @@ bool item::burn( fire_data &frd ) } else if( has_temperature() ) { heat_up(); } else if( is_food_container() ) { - contents.front().heat_up(); + contents.legacy_front().heat_up(); } burnt += roll_remainder( burn_added ); @@ -7616,8 +7543,7 @@ units::volume item::get_total_capacity() const } if( is_holster() ) { - result += dynamic_cast - ( type->get_use( "holster" )->get_actor_ptr() )->max_stored_volume(); + result += contents.total_container_capacity(); } return result; @@ -7649,12 +7575,12 @@ int item::get_remaining_capacity_for_liquid( const item &liquid, bool allow_buck return error( string_format( is_bucket() ? _( "That %s must be on the ground or held to hold contents!" ) : _( "You can't seal that %s!" ), tname() ) ); - } else if( !contents.empty() && contents.front().typeId() != liquid.typeId() ) { + } else if( !contents.empty() && contents.legacy_front().typeId() != liquid.typeId() ) { return error( string_format( _( "You can't mix loads in your %s." ), tname() ) ); } remaining_capacity = liquid.charges_per_volume( get_container_capacity() ); if( !contents.empty() ) { - remaining_capacity -= contents.front().charges; + remaining_capacity -= contents.legacy_front().charges; } } else { return error( string_format( _( "That %1$s won't hold %2$s." ), tname(), @@ -7695,13 +7621,7 @@ bool item::use_amount( const itype_id &it, int &quantity, std::list &used, // Remember quantity so that we can unseal self int old_quantity = quantity; // First, check contents - for( auto a = contents.begin(); a != contents.end() && quantity > 0; ) { - if( a->use_amount( it, quantity, used ) ) { - a = contents.erase( a ); - } else { - ++a; - } - } + contents.use_amount( it, quantity, used ); if( quantity != old_quantity ) { on_contents_changed(); @@ -7730,7 +7650,8 @@ bool item::allow_crafting_component() const // fixes #18886 - turret installation may require items with irremovable mods if( is_gun() ) { - return std::all_of( contents.begin(), contents.end(), [&]( const item & e ) { + std::list all_contents = contents.all_items(); + return std::all_of( all_contents.begin(), all_contents.end(), [&]( const item & e ) { return e.is_magazine() || ( e.is_gunmod() && e.is_irremovable() ); } ); } @@ -7895,7 +7816,7 @@ void item::fill_with( item &liquid, int amount ) ammo_set( liquid.typeId(), ammo_remaining() + amount ); } else if( is_food_container() ) { // if container already has liquid we need to sum the energy - item &cts = contents.front(); + item &cts = contents.legacy_front(); const float lhs_energy = cts.get_item_thermal_energy(); const float rhs_energy = liquid.get_item_thermal_energy(); if( rhs_energy < 0 ) { @@ -7910,7 +7831,7 @@ void item::fill_with( item &liquid, int amount ) cts.mod_charges( amount ); } else if( !is_container_empty() ) { // if container already has liquid we need to set the amount - item &cts = contents.front(); + item &cts = contents.legacy_front(); cts.mod_charges( amount ); } else { item liquid_copy( liquid ); @@ -8013,7 +7934,7 @@ void item::set_snippet( const snippet_id &id ) const item_category &item::get_category() const { if( is_container() && !contents.empty() ) { - return contents.front().get_category(); + return contents.legacy_front().get_category(); } static item_category null_category; @@ -8050,6 +7971,31 @@ iteminfo::iteminfo( const std::string &Type, const std::string &Name, double Val { } +iteminfo vol_to_info( const std::string &type, const std::string &left, + const units::volume &vol ) +{ + iteminfo::flags f = iteminfo::lower_is_better | iteminfo::no_newline; + int converted_volume_scale = 0; + const double converted_volume = + convert_volume( vol.value(), + &converted_volume_scale ); + if( converted_volume_scale != 0 ) { + f |= iteminfo::is_decimal; + } + return iteminfo( type, left, string_format( " %s", volume_units_abbr() ), f, + converted_volume ); +} + +iteminfo weight_to_info( const std::string &type, const std::string &left, + const units::mass &weight ) +{ + iteminfo::flags f = iteminfo::lower_is_better | iteminfo::no_newline; + const double converted_weight = convert_weight( weight ); + f |= iteminfo::is_decimal; + return iteminfo( type, left, string_format( " %s", volume_units_abbr() ), f, + converted_weight ); +} + bool item::will_explode_in_fire() const { if( type->explode_in_fire ) { @@ -8062,9 +8008,7 @@ bool item::will_explode_in_fire() const // Most containers do nothing to protect the contents from fire if( !is_magazine() || !type->magazine->protects_contents ) { - return std::any_of( contents.begin(), contents.end(), []( const item & it ) { - return it.will_explode_in_fire(); - } ); + return contents.will_explode_in_a_fire(); } return false; @@ -8095,14 +8039,7 @@ bool item::detonate( const tripoint &p, std::vector &drops ) return true; } else if( !contents.empty() && ( !type->magazine || !type->magazine->protects_contents ) ) { - const auto new_end = std::remove_if( contents.begin(), contents.end(), [ &p, &drops ]( item & it ) { - return it.detonate( p, drops ); - } ); - if( new_end != contents.end() ) { - contents.erase( new_end, contents.end() ); - // If any of the contents explodes, so does the container - return true; - } + contents.detonate( p, drops ); } return false; @@ -8115,7 +8052,7 @@ bool item_ptr_compare_by_charges( const item *left, const item *right ) } else if( right->contents.empty() ) { return true; } else { - return right->contents.front().charges < left->contents.front().charges; + return right->contents.legacy_front().charges < left->contents.legacy_front().charges; } } @@ -8149,7 +8086,7 @@ void item::mark_as_used_by_player( const player &p ) used_by_ids += string_format( "%d;", p.getID().get_value() ); } -bool item::can_holster( const item &obj, bool ignore ) const +bool item::can_holster( const item &obj, bool ) const { if( !type->can_use( "holster" ) ) { return false; // item is not a holster @@ -8157,15 +8094,8 @@ bool item::can_holster( const item &obj, bool ignore ) const const holster_actor *ptr = dynamic_cast ( type->get_use( "holster" )->get_actor_ptr() ); - if( !ptr->can_holster( obj ) ) { - return false; // item is not a suitable holster for obj - } - - if( !ignore && static_cast( contents.size() ) >= ptr->multi ) { - return false; // item is already full - } - - return true; + return ptr->multi > static_cast( contents.num_item_stacks() ) + && ptr->can_holster( obj ); } std::string item::components_to_string() const @@ -8210,7 +8140,7 @@ uint64_t item::make_component_hash() const bool item::needs_processing() const { return active || has_flag( "RADIO_ACTIVATION" ) || has_flag( "ETHEREAL_ITEM" ) || - ( is_container() && !contents.empty() && contents.front().needs_processing() ) || + ( is_container() && !contents.empty() && contents.legacy_front().needs_processing() ) || is_artifact() || is_food(); } @@ -9042,22 +8972,15 @@ bool item::process_blackpowder_fouling( player *carrier ) return false; } +void item::set_last_rot_check( const time_point &pt ) +{ + last_rot_check = pt; +} + bool item::process( player *carrier, const tripoint &pos, bool activate, float insulation, const temperature_flag flag ) { - const bool preserves = type->container && type->container->preserves; - for( auto it = contents.begin(); it != contents.end(); ) { - if( preserves ) { - // Simulate that the item has already "rotten" up to last_rot_check, but as item::rot - // is not changed, the item is still fresh. - it->last_rot_check = calendar::turn; - } - if( it->process( carrier, pos, activate, type->insulation_factor * insulation, flag ) ) { - it = contents.erase( it ); - } else { - ++it; - } - } + contents.process( *type, carrier, pos, activate, insulation, flag ); if( has_flag( "ETHEREAL_ITEM" ) ) { if( !has_var( "ethereal" ) ) { @@ -9190,7 +9113,7 @@ bool item::has_effect_when_carried( art_effect_passive effect ) const if( std::find( ec.begin(), ec.end(), effect ) != ec.end() ) { return true; } - for( const item &i : contents ) { + for( const item &i : contents.all_items() ) { if( i.has_effect_when_carried( effect ) ) { return true; } @@ -9233,7 +9156,8 @@ bool item::is_dangerous() const // Note: Item should be dangerous regardless of what type of a container is it // Visitable interface would skip some options - return std::any_of( contents.begin(), contents.end(), []( const item & it ) { + const std::list all_items = contents.all_items(); + return std::any_of( all_items.begin(), all_items.end(), []( const item & it ) { return it.is_dangerous(); } ); } diff --git a/src/item.h b/src/item.h index 30c372000da8d..6bf8691c8a109 100644 --- a/src/item.h +++ b/src/item.h @@ -22,6 +22,7 @@ #include "enums.h" #include "flat_set.h" #include "io_tags.h" +#include "item_contents.h" #include "item_location.h" #include "optional.h" #include "relic.h" @@ -155,6 +156,11 @@ struct iteminfo { iteminfo( const std::string &Type, const std::string &Name, double Value ); }; +iteminfo vol_to_info( const std::string &type, const std::string &left, + const units::volume &vol ); +iteminfo weight_to_info( const std::string &type, const std::string &left, + const units::mass &weight ); + inline iteminfo::flags operator|( iteminfo::flags l, iteminfo::flags r ) { using I = std::underlying_type::type; @@ -675,17 +681,8 @@ class item : public visitable /** * Puts the given item into this one, no checks are performed. */ - void put_in( const item &payload ); - - /** Stores a newly constructed item at the end of this item's contents */ - template - item &emplace_back( Args &&... args ) { - contents.emplace_back( std::forward( args )... ); - if( contents.back().is_null() ) { - debugmsg( "Tried to emplace null item" ); - } - return contents.back(); - } + void put_in( const item &payload, + item_pocket::pocket_type pk_type = item_pocket::pocket_type::LEGACY_CONTAINER ); /** * Returns this item into its default container. If it does not have a default container, @@ -1142,6 +1139,8 @@ class item : public visitable item *get_food(); const item *get_food() const; + void set_last_rot_check( const time_point &pt ); + /** What faults can potentially occur with this item? */ std::set faults_potential() const; @@ -2102,7 +2101,7 @@ class item : public visitable static const int INFINITE_CHARGES; const itype *type; - std::list contents; + item_contents contents; std::list components; /** What faults (if any) currently apply to this item */ std::set faults; diff --git a/src/item_action.cpp b/src/item_action.cpp index a91ee8c9a9d19..cc84eb438236c 100644 --- a/src/item_action.cpp +++ b/src/item_action.cpp @@ -74,7 +74,7 @@ static bool item_has_uses_recursive( const item &it ) return true; } - for( const auto &elem : it.contents ) { + for( const auto &elem : it.contents.all_items() ) { if( item_has_uses_recursive( elem ) ) { return true; } diff --git a/src/item_contents.cpp b/src/item_contents.cpp new file mode 100644 index 0000000000000..0df4b18a7a424 --- /dev/null +++ b/src/item_contents.cpp @@ -0,0 +1,510 @@ +#include "item_contents.h" + +#include "generic_factory.h" +#include "item.h" +#include "item_pocket.h" +#include "optional.h" +#include "point.h" +#include "units.h" + +namespace fake_item +{ +static item_pocket none_pocket( nullptr ); +static pocket_data legacy_pocket_data; +static item_pocket legacy_pocket( &legacy_pocket_data ); +} // namespace fake_item + +void item_contents::add_legacy_pocket() +{ + for( const item_pocket &pocket : contents ) { + if( pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + // an item should not have more than one legacy pocket + return; + } + } + contents.emplace_front( fake_item::legacy_pocket ); +} + +bool item_contents::stacks_with( const item_contents &rhs ) const +{ + if( contents.size() != rhs.contents.size() ) { + return false; + } + return std::equal( contents.begin(), contents.end(), + rhs.contents.begin(), + []( const item_pocket & a, const item_pocket & b ) { + return a.stacks_with( b ); + } ); +} + +void item_contents::combine( const item_contents &rhs ) +{ + for( const item_pocket &pocket : rhs.contents ) { + if( !pocket.is_valid() || pocket.saved_type() == item_pocket::pocket_type::LEGACY_CONTAINER ) { + for( const item *it : pocket.all_items_top() ) { + insert_legacy( *it ); + } + } else { + for( const item *it : pocket.all_items_top() ) { + const ret_val inserted = insert_item( *it, pocket.saved_type() ); + if( !inserted.success() ) { + debugmsg( "error: tried to put an item into a pocket that can't fit it while loading. err: %s", + inserted.str() ); + } + } + } + } +} + +ret_val item_contents::can_contain( const item &it ) const +{ + ret_val ret = ret_val::make_failure( _( "is not a container" ) ); + for( const item_pocket &pocket : contents ) { + const ret_val pocket_contain_code = pocket.can_contain( it ); + if( pocket_contain_code.success() ) { + return ret_val::make_success(); + } + if( pocket_contain_code.value() != item_pocket::contain_code::ERR_LEGACY_CONTAINER ) { + ret = ret_val::make_failure( pocket_contain_code.str() ); + } + } + return ret; +} + +item *item_contents::magazine_current() +{ + for( item_pocket &pocket : contents ) { + item *mag = pocket.magazine_current(); + if( mag != nullptr ) { + return mag; + } + } + return nullptr; +} + +void item_contents::casings_handle( const std::function &func ) +{ + for( item_pocket &pocket : contents ) { + pocket.casings_handle( func ); + } +} + +bool item_contents::use_amount( const itype_id &it, int &quantity, std::list &used ) +{ + bool used_item = false; + for( item_pocket &pocket : contents ) { + used_item = pocket.use_amount( it, quantity, used ) || used_item; + } + return used_item; +} + +bool item_contents::will_explode_in_a_fire() const +{ + for( const item_pocket &pocket : contents ) { + if( pocket.will_explode_in_a_fire() ) { + return true; + } + } + return false; +} + +bool item_contents::detonate( const tripoint &p, std::vector &drops ) +{ + bool detonated = false; + for( item_pocket &pocket : contents ) { + detonated = pocket.detonate( p, drops ) || detonated; + } + return detonated; +} + +bool item_contents::process( const itype &type, player *carrier, const tripoint &pos, bool activate, + float insulation, const temperature_flag flag ) +{ + for( item_pocket &pocket : contents ) { + pocket.process( type, carrier, pos, activate, insulation, flag ); + } + return true; +} + +bool item_contents::legacy_unload( player &guy, bool &changed ) +{ + for( item_pocket &pocket : contents ) { + pocket.legacy_unload( guy, changed ); + } + return true; +} + +void item_contents::remove_all_ammo( Character &guy ) +{ + for( item_pocket &pocket : contents ) { + pocket.remove_all_ammo( guy ); + } +} + +void item_contents::remove_all_mods( Character &guy ) +{ + for( item_pocket &pocket : contents ) { + pocket.remove_all_mods( guy ); + } +} + +bool item_contents::empty() const +{ + if( contents.empty() ) { + return true; + } + for( const item_pocket &pocket : contents ) { + if( pocket.empty() ) { + return true; + } + } + return false; +} + +item &item_contents::legacy_back() +{ + return legacy_pocket().back(); +} + +const item &item_contents::legacy_back() const +{ + return legacy_pocket().back(); +} + +item &item_contents::legacy_front() +{ + return legacy_pocket().front(); +} + +const item &item_contents::legacy_front() const +{ + return legacy_pocket().front(); +} + +size_t item_contents::legacy_size() const +{ + if( contents.empty() ) { + return 0; + } + return legacy_pocket().size(); +} + +void item_contents::legacy_pop_back() +{ + legacy_pocket().pop_back(); +} + +size_t item_contents::num_item_stacks() const +{ + size_t num = 0; + for( const item_pocket &pocket : contents ) { + num += pocket.size(); + } + return num; +} + +size_t item_contents::size() const +{ + // always has a legacy pocket due to contstructor + // we want to ignore the legacy pocket + return contents.size() - 1; +} + +item_pocket &item_contents::legacy_pocket() +{ + for( item_pocket &pocket : contents ) { + if( pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + return pocket; + } + } + debugmsg( "Tried to access non-existing legacy pocket" ); + return fake_item::none_pocket; +} + +const item_pocket &item_contents::legacy_pocket() const +{ + for( const item_pocket &pocket : contents ) { + if( pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + return pocket; + } + } + debugmsg( "Tried to access non-existing legacy pocket" ); + return fake_item::none_pocket; +} + +std::list item_contents::all_items() +{ + std::list item_list; + for( item_pocket &pocket : contents ) { + std::list contained_items = pocket.all_items(); + item_list.insert( item_list.end(), contained_items.begin(), contained_items.end() ); + } + return item_list; +} + +std::list item_contents::all_items() const +{ + std::list item_list; + for( const item_pocket &pocket : contents ) { + std::list contained_items = pocket.all_items(); + item_list.insert( item_list.end(), contained_items.begin(), contained_items.end() ); + } + return item_list; +} + +std::list item_contents::all_items_ptr( item_pocket::pocket_type pk_type ) +{ + std::list all_items_internal; + for( item_pocket &pocket : contents ) { + if( pocket.is_type( pk_type ) ) { + std::list contained_items = pocket.all_items_ptr( pk_type ); + all_items_internal.insert( all_items_internal.end(), contained_items.begin(), + contained_items.end() ); + } + } + return all_items_internal; +} + +std::list item_contents::all_items_ptr( item_pocket::pocket_type pk_type ) const +{ + std::list all_items_internal; + for( const item_pocket &pocket : contents ) { + if( pocket.is_type( pk_type ) ) { + std::list contained_items = pocket.all_items_ptr( pk_type ); + all_items_internal.insert( all_items_internal.end(), contained_items.begin(), + contained_items.end() ); + } + } + return all_items_internal; +} + +std::list item_contents::all_items_ptr() +{ + std::list all_items_internal; + for( item_pocket &pocket : contents ) { + for( int i = 0; i < item_pocket::pocket_type::LAST; i++ ) { + std::list pocket_items = pocket.all_items_ptr( static_cast( i ) ); + all_items_internal.insert( all_items_internal.end(), pocket_items.begin(), pocket_items.end() ); + } + } + return all_items_internal; +} + +std::list item_contents::all_items_ptr() const +{ + std::list all_items_internal; + for( const item_pocket &pocket : contents ) { + for( int i = 0; i < item_pocket::pocket_type::LAST; i++ ) { + std::list pocket_items = pocket.all_items_ptr( static_cast + ( i ) ); + all_items_internal.insert( all_items_internal.end(), pocket_items.begin(), pocket_items.end() ); + } + } + return all_items_internal; +} + +std::list item_contents::all_items_top( item_pocket::pocket_type pk_type ) +{ + std::list all_items_internal; + for( item_pocket &pocket : contents ) { + if( pocket.is_type( pk_type ) ) { + std::list contained_items = pocket.all_items_top(); + all_items_internal.insert( all_items_internal.end(), contained_items.begin(), + contained_items.end() ); + } + } + return all_items_internal; +} + +units::volume item_contents::item_size_modifier() const +{ + units::volume total_vol = 0_ml; + for( const item_pocket &pocket : contents ) { + if( !pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + total_vol += pocket.item_size_modifier(); + } + } + return total_vol; +} + +units::volume item_contents::total_container_capacity() const +{ + units::volume total_vol = 0_ml; + for( const item_pocket &pocket : contents ) { + if( pocket.is_type( item_pocket::pocket_type::CONTAINER ) ) { + total_vol += pocket.volume_capacity(); + } + } + return total_vol; +} + +units::mass item_contents::item_weight_modifier() const +{ + units::mass total_mass = 0_gram; + for( const item_pocket &pocket : contents ) { + total_mass += pocket.item_weight_modifier(); + } + return total_mass; +} + +bool item_contents::spill_contents( const tripoint &pos ) +{ + for( item_pocket &pocket : contents ) { + pocket.spill_contents( pos ); + } + return true; +} + +cata::optional item_contents::remove_item( const item &it ) +{ + for( item_pocket &pocket : contents ) { + cata::optional ret = pocket.remove_item( it ); + if( ret ) { + return ret; + } + } + return cata::nullopt; +} + +cata::optional item_contents::remove_item( const item_location &it ) +{ + if( !it ) { + return cata::nullopt; + } + return remove_item( *it ); +} + +void item_contents::remove_internal( const std::function &filter, + int &count, std::list &res ) +{ + for( item_pocket &pocket : contents ) { + if( pocket.remove_internal( filter, count, res ) ) { + return; + } + } +} + +void item_contents::clear_items() +{ + for( item_pocket &pocket : contents ) { + pocket.clear_items(); + } +} + +bool item_contents::has_item( const item &it ) const +{ + for( const item_pocket &pocket : contents ) { + if( pocket.has_item( it ) ) { + return true; + } + } + return false; +} + +item *item_contents::get_item_with( const std::function &filter ) +{ + for( item_pocket &pocket : contents ) { + item *it = pocket.get_item_with( filter ); + if( it != nullptr ) { + return it; + } + } + return nullptr; +} + +void item_contents::remove_items_if( const std::function &filter ) +{ + for( item_pocket &pocket : contents ) { + pocket.remove_items_if( filter ); + } +} + +void item_contents::has_rotten_away( const tripoint &pnt ) +{ + for( item_pocket &pocket : contents ) { + pocket.has_rotten_away( pnt ); + } +} + +ret_val item_contents::insert_item( const item &it, item_pocket::pocket_type pk_type ) +{ + ret_val ret = ret_val::make_failure( _( "is not a container" ) ); + for( item_pocket &pocket : contents ) { + if( !pocket.is_type( pk_type ) ) { + continue; + } + const ret_val pocket_contain_code = pocket.insert_item( it ); + if( pocket_contain_code.success() ) { + return ret_val::make_success(); + } + if( pocket_contain_code.value() != item_pocket::contain_code::ERR_LEGACY_CONTAINER ) { + ret = ret_val::make_failure( pocket_contain_code.str() ); + } + } + return ret; +} + +int item_contents::obtain_cost( const item &it ) const +{ + for( const item_pocket &pocket : contents ) { + const int mv = pocket.obtain_cost( it ); + if( mv != 0 ) { + return mv; + } + } + return 0; +} + +static void insert_separation_line( std::vector &info ) +{ + if( info.empty() || info.back().sName != "--" ) { + info.push_back( iteminfo( "DESCRIPTION", "--" ) ); + } +} + +void item_contents::info( std::vector &info ) const +{ + int pocket_number = 1; + std::vector contents_info; + std::vector found_pockets; + std::map pocket_num; // index, amount + for( const item_pocket &pocket : contents ) { + if( !pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + bool found = false; + int idx = 0; + for( const item_pocket &found_pocket : found_pockets ) { + if( found_pocket == pocket ) { + found = true; + pocket_num[idx]++; + } + idx++; + } + if( !found ) { + found_pockets.push_back( pocket ); + pocket_num[idx]++; + } + pocket.contents_info( contents_info, pocket_number++, size() != 1 ); + } + } + int idx = 0; + for( const item_pocket &pocket : found_pockets ) { + insert_separation_line( info ); + if( pocket_num[idx] > 1 ) { + info.emplace_back( "DESCRIPTION", _( string_format( "Pockets (%d)", + pocket_num[idx] ) ) ); + } + idx++; + pocket.general_info( info, 0, false ); + } + info.insert( info.end(), contents_info.begin(), contents_info.end() ); +} + +void item_contents::insert_legacy( const item &it ) +{ + legacy_pocket().add( it ); +} + +std::list &item_contents::legacy_items() +{ + return legacy_pocket().edit_contents(); +} diff --git a/src/item_contents.h b/src/item_contents.h new file mode 100644 index 0000000000000..1e70ba53afd7b --- /dev/null +++ b/src/item_contents.h @@ -0,0 +1,126 @@ +#pragma once +#ifndef ITEM_CONTENTS_H +#define ITEM_CONTENTS_H + +#include "enums.h" +#include "item_pocket.h" +#include "optional.h" +#include "ret_val.h" +#include "units.h" +#include "visitable.h" + +class item; +class item_location; +class player; + +struct iteminfo; +struct tripoint; + +class item_contents +{ + public: + item_contents() { + // items should have a legacy pocket until everything is migrated + add_legacy_pocket(); + } + + // used for loading itype + item_contents( const std::vector &pockets ) { + for( const pocket_data &data : pockets ) { + contents.push_back( item_pocket( &data ) ); + } + add_legacy_pocket(); + } + + // for usage with loading to aid migration + void add_legacy_pocket(); + + bool stacks_with( const item_contents &rhs ) const; + + ret_val can_contain( const item &it ) const; + bool empty() const; + + // all the items contained in each pocket combined into one list + std::list all_items(); + std::list all_items() const; + // all item pointers in a specific pocket type + // used for inventory screen + std::list all_items_ptr( item_pocket::pocket_type pk_type ); + std::list all_items_ptr( item_pocket::pocket_type pk_type ) const; + std::list all_items_ptr(); + std::list all_items_ptr() const; + + std::list all_items_top( item_pocket::pocket_type pk_type = + item_pocket::pocket_type::CONTAINER ); + + // total size the parent item needs to be modified based on rigidity of pockets + units::volume item_size_modifier() const; + units::volume total_container_capacity() const; + // total weight the parent item needs to be modified based on weight modifiers of pockets + units::mass item_weight_modifier() const; + + item *magazine_current(); + void casings_handle( const std::function &func ); + bool use_amount( const itype_id &it, int &quantity, std::list &used ); + bool will_explode_in_a_fire() const; + bool detonate( const tripoint &p, std::vector &drops ); + bool process( const itype &type, player *carrier, const tripoint &pos, bool activate, + float insulation, temperature_flag flag ); + bool legacy_unload( player &guy, bool &changed ); + void remove_all_ammo( Character &guy ); + void remove_all_mods( Character &guy ); + + // removes and returns the item from the pocket. + cata::optional remove_item( const item &it ); + cata::optional remove_item( const item_location &it ); + + // attempts to put the input contents into the item_contents + // copies the content items + // used to help with loading + void combine( const item_contents &rhs ); + // tries to put an item in a pocket. returns false on failure + // has similar code to can_contain in order to avoid running it twice + ret_val insert_item( const item &it, + item_pocket::pocket_type pk_type = item_pocket::pocket_type::CONTAINER ); + // finds or makes a fake pocket and puts this item into it + void insert_legacy( const item &it ); + // equivalent to contents.back() when item::contents was a std::list + std::list &legacy_items(); + item &legacy_back(); + const item &legacy_back() const; + item &legacy_front(); + const item &legacy_front() const; + size_t legacy_size() const; + // ignores legacy_pocket, so -1 + size_t size() const; + void legacy_pop_back(); + size_t num_item_stacks() const; + bool spill_contents( const tripoint &pos ); + void clear_items(); + bool has_item( const item &it ) const; + item *get_item_with( const std::function &filter ); + void remove_items_if( const std::function &filter ); + void has_rotten_away( const tripoint &pnt ); + + int obtain_cost( const item &it ) const; + + void remove_internal( const std::function &filter, + int &count, std::list &res ); + // @relates visitable + // NOTE: upon expansion, this may need to be filtered by type enum depending on accessibility + VisitResponse visit_contents( const std::function &func, + item *parent = nullptr ); + + void info( std::vector &info ) const; + + void serialize( JsonOut &json ) const; + void deserialize( JsonIn &jsin ); + private: + // gets the pocket described as legacy, or creates one + item_pocket &legacy_pocket(); + const item_pocket &legacy_pocket() const; + + std::list contents; +}; + +#endif diff --git a/src/item_factory.cpp b/src/item_factory.cpp index e68b9eb9176be..a8f5782be1f26 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -2221,6 +2221,8 @@ void Item_factory::load_basic_info( const JsonObject &jo, itype &def, const std: } } + assign( jo, "pocket_data", def.pockets ); + load_slot_optional( def.container, jo, "container_data", src ); load_slot_optional( def.armor, jo, "armor_data", src ); load_slot_optional( def.pet_armor, jo, "pet_armor_data", src ); @@ -2303,17 +2305,17 @@ void Item_factory::migrate_item( const itype_id &id, item &obj ) obj.charges = iter->second.charges; } - for( const auto &c : iter->second.contents ) { - if( std::none_of( obj.contents.begin(), obj.contents.end(), [&]( const item & e ) { - return e.typeId() == c; - } ) ) { - obj.emplace_back( c, obj.birthday() ); + for( const std::string &c : iter->second.contents ) { + if( obj.contents.get_item_with( [&]( const item & e ) { + return e.typeId() != c; + } ) == nullptr ) { + obj.contents.insert_legacy( item( c, obj.birthday() ) ); } } // check contents of migrated containers do not exceed capacity if( obj.is_container() && !obj.contents.empty() ) { - item &child = obj.contents.back(); + item &child = obj.contents.legacy_back(); const int capacity = child.charges_per_volume( obj.get_container_capacity() ); child.charges = std::min( child.charges, capacity ); } diff --git a/src/item_group.cpp b/src/item_group.cpp index 7d0064bc47904..68e4f5501b68e 100644 --- a/src/item_group.cpp +++ b/src/item_group.cpp @@ -337,7 +337,7 @@ void Item_modifier::modify( item &new_item ) const !new_item.magazine_current(); if( spawn_mag ) { - new_item.contents.emplace_back( new_item.magazine_default(), new_item.birthday() ); + new_item.contents.insert_legacy( item( new_item.magazine_default(), new_item.birthday() ) ); } if( spawn_ammo ) { @@ -357,7 +357,9 @@ void Item_modifier::modify( item &new_item ) const if( contents != nullptr ) { Item_spawn_data::ItemList contentitems = contents->create( new_item.birthday() ); - new_item.contents.insert( new_item.contents.end(), contentitems.begin(), contentitems.end() ); + for( item &it : contentitems ) { + new_item.contents.insert_legacy( it ); + } } for( auto &flag : custom_flags ) { diff --git a/src/item_location.cpp b/src/item_location.cpp index 903cd46cdf293..9bdcd0a32fd76 100644 --- a/src/item_location.cpp +++ b/src/item_location.cpp @@ -64,6 +64,7 @@ class item_location::impl class item_on_map; class item_on_person; class item_on_vehicle; + class item_in_container; impl() = default; impl( item *i ) : what( i->get_safe_reference() ), needs_unpacking( false ) {} @@ -344,9 +345,8 @@ class item_location::impl::item_on_person : public item_location::impl // holsters may also adjust the volume cost factor if( parents.back()->can_holster( obj, true ) ) { - auto ptr = dynamic_cast - ( parents.back()->type->get_use( "holster" )->get_actor_ptr() ); - mv += dynamic_cast( who )->item_handling_cost( obj, false, ptr->draw_cost ); + mv += who->as_player()->item_handling_cost( obj, false, + parents.back()->contents.obtain_cost( obj ) ); } else if( parents.back()->is_bandolier() ) { auto ptr = dynamic_cast @@ -478,6 +478,101 @@ class item_location::impl::item_on_vehicle : public item_location::impl } }; +class item_location::impl::item_in_container : public item_location::impl +{ + private: + item_location container; + + // figures out the index for the item, which is where it is in the total list of contents + // note: could be a better way of handling this? + int calc_index() const { + int idx = 0; + for( const item &it : container->contents.all_items() ) { + if( target() == &it ) { + return idx; + } + idx++; + } + if( idx == 0 ) { + return -1; + } + return idx; + } + public: + item_in_container( const item_location &container, item *which ) : + impl( which ), container( container ) {} + + void serialize( JsonOut &js ) const override { + js.start_object(); + js.member( "idx", calc_index() ); + js.member( "type", "in_container" ); + js.member( "parent", container ); + js.end_object(); + } + + item *unpack( int idx ) const override { + std::list all_items = container->contents.all_items(); + auto iter = all_items.begin(); + std::advance( iter, idx ); + if( iter != all_items.end() ) { + return &*iter; + } else { + return nullptr; + } + } + + std::string describe( const Character * ) const override { + if( !target() ) { + return std::string(); + } + return _( string_format( "inside %s", container->tname() ) ); + } + + type where() const override { + return type::container; + } + + tripoint position() const override { + return container.position(); + } + + void remove_item() override { + container->contents.remove_item( *target() ); + } + + int obtain( Character &ch, int qty ) override { + ch.mod_moves( -obtain_cost( ch, qty ) ); + + item obj = target()->split( qty ); + if( !obj.is_null() ) { + return ch.get_item_position( &ch.i_add( obj, should_stack ) ); + } else { + int inv = ch.get_item_position( &ch.i_add( *target(), should_stack ) ); + remove_item(); + return inv; + } + } + + int obtain_cost( const Character &ch, int qty ) const override { + if( !target() ) { + return 0; + } + + item obj = *target(); + obj = obj.split( qty ); + if( obj.is_null() ) { + obj = *target(); + } + + const int container_mv = container->contents.obtain_cost( *target() ); + if( container_mv == 0 ) { + debugmsg( "ERROR: %s does not contain %s", container->tname(), target()->tname() ); + return 0; + } + return container_mv + container.obtain_cost( ch, qty ); + } +}; + const item_location item_location::nowhere; item_location::item_location() @@ -492,6 +587,9 @@ item_location::item_location( Character &ch, item *which ) item_location::item_location( const vehicle_cursor &vc, item *which ) : ptr( new impl::item_on_vehicle( vc, which ) ) {} +item_location::item_location( const item_location &container, item *which ) + : ptr( new impl::item_in_container( container, which ) ) {} + bool item_location::operator==( const item_location &rhs ) const { return ptr->target() == rhs.ptr->target(); diff --git a/src/item_location.h b/src/item_location.h index f5f04cc698268..ec8ae422a79a1 100644 --- a/src/item_location.h +++ b/src/item_location.h @@ -27,7 +27,8 @@ class item_location invalid = 0, character = 1, map = 2, - vehicle = 3 + vehicle = 3, + container }; item_location(); @@ -37,6 +38,7 @@ class item_location item_location( Character &ch, item *which ); item_location( const map_cursor &mc, item *which ); item_location( const vehicle_cursor &vc, item *which ); + item_location( const item_location &container, item *which ); void serialize( JsonOut &js ) const; void deserialize( JsonIn &js ); diff --git a/src/item_pocket.cpp b/src/item_pocket.cpp new file mode 100644 index 0000000000000..5bc0b1daf7c4f --- /dev/null +++ b/src/item_pocket.cpp @@ -0,0 +1,644 @@ +#include "item_pocket.h" + +#include "assign.h" +#include "cata_utility.h" +#include "crafting.h" +#include "enums.h" +#include "game.h" +#include "generic_factory.h" +#include "item.h" +#include "itype.h" +#include "json.h" +#include "map.h" +#include "player.h" +#include "point.h" +#include "units.h" + +namespace io +{ +// *INDENT-OFF* +template<> +std::string enum_to_string( item_pocket::pocket_type data ) +{ + switch ( data ) { + case item_pocket::pocket_type::CONTAINER: return "CONTAINER"; + case item_pocket::pocket_type::MAGAZINE: return "MAGAZINE"; + case item_pocket::pocket_type::LEGACY_CONTAINER: return "LEGACY_CONTAINER"; + case item_pocket::pocket_type::LAST: break; + } + debugmsg( "Invalid valid_target" ); + abort(); +} +// *INDENT-ON* +} // namespace io + +void pocket_data::load( const JsonObject &jo ) +{ + optional( jo, was_loaded, "pocket_type", type, item_pocket::pocket_type::CONTAINER ); + optional( jo, was_loaded, "min_item_volume", min_item_volume, volume_reader(), 0_ml ); + mandatory( jo, was_loaded, "max_contains_volume", max_contains_volume, volume_reader() ); + mandatory( jo, was_loaded, "max_contains_weight", max_contains_weight, mass_reader() ); + optional( jo, was_loaded, "spoil_multiplier", spoil_multiplier, 1.0f ); + optional( jo, was_loaded, "weight_multiplier", weight_multiplier, 1.0f ); + optional( jo, was_loaded, "moves", moves, 100 ); + optional( jo, was_loaded, "fire_protection", fire_protection, false ); + optional( jo, was_loaded, "watertight", watertight, false ); + optional( jo, was_loaded, "gastight", gastight, false ); + optional( jo, was_loaded, "open_container", open_container, false ); + optional( jo, was_loaded, "flag_restriction", flag_restriction ); + optional( jo, was_loaded, "rigid", rigid, false ); +} + +bool item_pocket::operator==( const item_pocket &rhs ) const +{ + return *data == *rhs.data; +} + +bool pocket_data::operator==( const pocket_data &rhs ) const +{ + return rigid == rhs.rigid && + watertight == rhs.watertight && + gastight == rhs.gastight && + fire_protection == rhs.fire_protection && + flag_restriction == rhs.flag_restriction && + type == rhs.type && + max_contains_volume == rhs.max_contains_volume && + min_item_volume == rhs.min_item_volume && + max_contains_weight == rhs.max_contains_weight && + spoil_multiplier == rhs.spoil_multiplier && + weight_multiplier == rhs.weight_multiplier && + moves == rhs.moves; +} + +bool item_pocket::stacks_with( const item_pocket &rhs ) const +{ + return std::equal( contents.begin(), contents.end(), + rhs.contents.begin(), rhs.contents.end(), + []( const item & a, const item & b ) { + return a.charges == b.charges && a.stacks_with( b ); + } ); +} + +std::list item_pocket::all_items() +{ + std::list all_items; + for( item &it : contents ) { + all_items.emplace_back( it ); + } + for( item &it : all_items ) { + std::list all_items_internal{ it.contents.all_items() }; + all_items.insert( all_items.end(), all_items_internal.begin(), all_items_internal.end() ); + } + return all_items; +} + +std::list item_pocket::all_items() const +{ + std::list all_items; + for( const item &it : contents ) { + all_items.emplace_back( it ); + } + for( const item &it : all_items ) { + std::list all_items_internal{ it.contents.all_items() }; + all_items.insert( all_items.end(), all_items_internal.begin(), all_items_internal.end() ); + } + return all_items; +} + +std::list item_pocket::all_items_ptr( item_pocket::pocket_type pk_type ) +{ + std::list all_items_top; + if( is_type( pk_type ) ) { + for( item &it : contents ) { + all_items_top.push_back( &it ); + } + } + for( item *it : all_items_top ) { + std::list all_items_internal{ it->contents.all_items_ptr( pk_type ) }; + all_items_top.insert( all_items_top.end(), all_items_internal.begin(), all_items_internal.end() ); + } + return all_items_top; +} + +std::list item_pocket::all_items_ptr( item_pocket::pocket_type pk_type ) const +{ + std::list all_items_top; + if( is_type( pk_type ) ) { + for( const item &it : contents ) { + all_items_top.push_back( &it ); + } + } + for( const item *it : all_items_top ) { + std::list all_items_internal{ it->contents.all_items_ptr( pk_type ) }; + all_items_top.insert( all_items_top.end(), all_items_internal.begin(), all_items_internal.end() ); + } + return all_items_top; +} + +std::list item_pocket::all_items_top() +{ + std::list all_items_top; + for( item &it : contents ) { + all_items_top.push_back( &it ); + } + return all_items_top; +} + +std::list item_pocket::all_items_top() const +{ + std::list all_items_top; + for( const item &it : contents ) { + all_items_top.push_back( &it ); + } + return all_items_top; +} + +item &item_pocket::back() +{ + return contents.back(); +} + +const item &item_pocket::back() const +{ + return contents.back(); +} + +item &item_pocket::front() +{ + return contents.front(); +} + +const item &item_pocket::front() const +{ + return contents.front(); +} + +void item_pocket::pop_back() +{ + contents.pop_back(); +} + +size_t item_pocket::size() const +{ + return contents.size(); +} + +units::volume item_pocket::volume_capacity() const +{ + return data->max_contains_volume; +} + +units::volume item_pocket::remaining_volume() const +{ + return data->max_contains_volume - contains_volume(); +} + +units::volume item_pocket::item_size_modifier() const +{ + if( data->rigid ) { + return 0_ml; + } + units::volume total_vol = 0_ml; + for( const item &it : contents ) { + total_vol += it.volume(); + } + return total_vol; +} + +units::mass item_pocket::item_weight_modifier() const +{ + units::mass total_mass = 0_gram; + for( const item &it : contents ) { + if( it.is_gunmod() ) { + total_mass += it.weight( true, true ) * data->weight_multiplier; + } else { + total_mass += it.weight() * data->weight_multiplier; + } + } + return total_mass; +} + +item *item_pocket::magazine_current() +{ + auto iter = std::find_if( contents.begin(), contents.end(), []( const item & it ) { + return it.is_magazine(); + } ); + return iter != contents.end() ? &*iter : nullptr; +} + +void item_pocket::casings_handle( const std::function &func ) +{ + for( auto it = contents.begin(); it != contents.end(); ) { + if( it->has_flag( "CASING" ) ) { + it->unset_flag( "CASING" ); + if( func( *it ) ) { + it = contents.erase( it ); + continue; + } + // didn't handle the casing so reset the flag ready for next call + it->set_flag( "CASING" ); + } + ++it; + } +} + +bool item_pocket::use_amount( const itype_id &it, int &quantity, std::list &used ) +{ + bool used_item = false; + for( auto a = contents.begin(); a != contents.end() && quantity > 0; ) { + if( a->use_amount( it, quantity, used ) ) { + used_item = true; + a = contents.erase( a ); + } else { + ++a; + } + } + return used_item; +} + +bool item_pocket::will_explode_in_a_fire() const +{ + if( data->fire_protection ) { + return false; + } + return std::any_of( contents.begin(), contents.end(), []( const item & it ) { + return it.will_explode_in_fire(); + } ); +} + +bool item_pocket::detonate( const tripoint &pos, std::vector &drops ) +{ + const auto new_end = std::remove_if( contents.begin(), contents.end(), [&pos, &drops]( item & it ) { + return it.detonate( pos, drops ); + } ); + if( new_end != contents.end() ) { + contents.erase( new_end, contents.end() ); + // If any of the contents explodes, so does the container + return true; + } + return false; +} + +bool item_pocket::process( const itype &type, player *carrier, const tripoint &pos, bool activate, + float insulation, const temperature_flag flag ) +{ + const bool preserves = type.container && type.container->preserves; + bool processed = false; + for( auto it = contents.begin(); it != contents.end(); ) { + if( preserves ) { + // Simulate that the item has already "rotten" up to last_rot_check, but as item::rot + // is not changed, the item is still fresh. + it->set_last_rot_check( calendar::turn ); + } + if( it->process( carrier, pos, activate, type.insulation_factor * insulation, flag ) ) { + it = contents.erase( it ); + processed = true; + } else { + ++it; + } + } + return processed; +} + +bool item_pocket::legacy_unload( player &guy, bool &changed ) +{ + contents.erase( std::remove_if( contents.begin(), contents.end(), + [&guy, &changed]( item & e ) { + int old_charges = e.charges; + const bool consumed = guy.add_or_drop_with_msg( e, true ); + changed = changed || consumed || e.charges != old_charges; + if( consumed ) { + guy.mod_moves( -guy.item_handling_cost( e ) ); + } + return consumed; + } ), contents.end() ); + return changed; +} + +void item_pocket::remove_all_ammo( Character &guy ) +{ + for( auto iter = contents.begin(); iter != contents.end(); ) { + if( iter->is_irremovable() ) { + iter++; + continue; + } + drop_or_handle( *iter, guy ); + iter = contents.erase( iter ); + } +} + +void item_pocket::remove_all_mods( Character &guy ) +{ + for( auto iter = contents.begin(); iter != contents.end(); ) { + if( iter->is_toolmod() ) { + guy.i_add_or_drop( *iter ); + iter = contents.erase( iter ); + } else { + ++iter; + } + } +} + +static void insert_separation_line( std::vector &info ) +{ + if( info.empty() || info.back().sName != "--" ) { + info.push_back( iteminfo( "DESCRIPTION", "--" ) ); + } +} + +void item_pocket::general_info( std::vector &info, int pocket_number, + bool disp_pocket_number ) const +{ + const std::string space = " "; + + if( data->type != LEGACY_CONTAINER ) { + if( disp_pocket_number ) { + info.emplace_back( "DESCRIPTION", _( "Pocket %d:" ), pocket_number ); + } + if( data->rigid ) { + info.emplace_back( "DESCRIPTION", _( "This pocket is rigid." ) ); + } + if( data->min_item_volume > 0_ml ) { + info.emplace_back( vol_to_info( "DESCRIPTION", + _( "Minimum volume of item allowed:" ), + data->min_item_volume ) ); + } + info.emplace_back( vol_to_info( "DESCRIPTION", + _( "Volume Capacity:" ), + data->max_contains_volume ) ); + info.emplace_back( weight_to_info( "DESCRIPTION", + _( "Weight Capacity:" ), + data->max_contains_weight ) ); + + info.emplace_back( "DESCRIPTION", + _( "This pocket takes %d base moves to take an item out." ), + data->moves ); + + if( data->watertight ) { + info.emplace_back( "DESCRIPTION", + _( "This pocket can contain a liquid." ) ); + } + if( data->gastight ) { + info.emplace_back( "DESCRIPTION", + _( "This pocket can contain a gas." ) ); + } + if( data->open_container ) { + info.emplace_back( "DESCRIPTION", + _( "This pocket will spill if placed into another item or worn." ) ); + } + if( data->fire_protection ) { + info.emplace_back( "DESCRIPTION", + _( "This pocket protects its contents from fire." ) ); + } + if( data->spoil_multiplier != 1.0f ) { + info.emplace_back( "DESCRIPTION", + _( "This pocket makes contained items spoil at %.0f%% their original rate." ), + data->spoil_multiplier * 100 ); + } + if( data->weight_multiplier != 1.0f ) { + info.emplace_back( "DESCRIPTION", + _( "Items in this pocket weigh %.0f%% their original weight." ), + data->weight_multiplier * 100 ); + } + } +} + +void item_pocket::contents_info( std::vector &info, int pocket_number, + bool disp_pocket_number ) const +{ + const std::string space = " "; + + insert_separation_line( info ); + if( disp_pocket_number ) { + info.emplace_back( "DESCRIPTION", _( "Pocket %d" ), pocket_number ); + } + if( contents.empty() ) { + info.emplace_back( "DESCRIPTION", _( "This pocket is empty." ) ); + return; + } + info.emplace_back( "DESCRIPTION", + string_format( "%s: %s / %s", _( "Volume" ), + vol_to_string( contains_volume() ), + vol_to_string( data->max_contains_volume ) ) ); + info.emplace_back( "DESCRIPTION", + string_format( "%s: %s / %s", _( "Weight" ), + weight_to_string( contains_weight() ), + weight_to_string( data->max_contains_weight ) ) ); + + bool contents_header = false; + for( const item &contents_item : contents ) { + if( !contents_item.type->mod ) { + if( !contents_header ) { + info.emplace_back( "DESCRIPTION", _( "Contents of this pocket:" ) ); + contents_header = true; + } else { + // Separate items with a blank line + info.emplace_back( "DESCRIPTION", space ); + } + + const translation &description = contents_item.type->description; + + if( contents_item.made_of_from_type( LIQUID ) ) { + info.emplace_back( "DESCRIPTION", contents_item.display_name() ); + info.emplace_back( vol_to_info( "CONTAINER", description + space, + contents_item.volume() ) ); + } else { + info.emplace_back( "DESCRIPTION", contents_item.display_name() ); + } + } + } +} + +ret_val item_pocket::can_contain( const item &it ) const +{ + // legacy container must be added to explicitly + if( data->type == pocket_type::LEGACY_CONTAINER ) { + return ret_val::make_failure( contain_code::ERR_LEGACY_CONTAINER ); + } + if( it.made_of( phase_id::LIQUID ) && !data->watertight ) { + return ret_val::make_failure( + contain_code::ERR_LIQUID, _( "can't contain liquid" ) ); + } + if( it.made_of( phase_id::GAS ) && !data->gastight ) { + return ret_val::make_failure( + contain_code::ERR_GAS, _( "can't contain gas" ) ); + } + if( it.volume() < data->min_item_volume ) { + return ret_val::make_failure( + contain_code::ERR_TOO_SMALL, _( "item is too small" ) ); + } + if( it.weight() > data->max_contains_weight ) { + return ret_val::make_failure( + contain_code::ERR_TOO_HEAVY, _( "item is too heavy" ) ); + } + if( it.weight() > remaining_weight() ) { + return ret_val::make_failure( + contain_code::ERR_CANNOT_SUPPORT, _( "pocket is holding too much weight" ) ); + } + if( !data->flag_restriction.empty() && !it.has_any_flag( data->flag_restriction ) ) { + return ret_val::make_failure( + contain_code::ERR_FLAG, _( "item does not have correct flag" ) ); + } + if( it.volume() > data->max_contains_volume ) { + return ret_val::make_failure( + contain_code::ERR_TOO_BIG, _( "item too big" ) ); + } + if( it.volume() > remaining_volume() ) { + return ret_val::make_failure( + contain_code::ERR_NO_SPACE, _( "not enough space" ) ); + } + return ret_val::make_success(); +} + +cata::optional item_pocket::remove_item( const item &it ) +{ + item ret( it ); + const size_t sz = contents.size(); + contents.remove_if( [&it]( const item & rhs ) { + return &rhs == ⁢ + } ); + if( sz == contents.size() ) { + return cata::nullopt; + } else { + return ret; + } +} + +bool item_pocket::remove_internal( const std::function &filter, + int &count, std::list &res ) +{ + for( auto it = contents.begin(); it != contents.end(); ) { + if( filter( *it ) ) { + res.splice( res.end(), contents, it++ ); + if( --count == 0 ) { + return true; + } + } else { + it->contents.remove_internal( filter, count, res ); + ++it; + } + } + return false; +} + +cata::optional item_pocket::remove_item( const item_location &it ) +{ + if( !it ) { + return cata::nullopt; + } + return remove_item( *it ); +} + +bool item_pocket::spill_contents( const tripoint &pos ) +{ + for( item &it : contents ) { + g->m.add_item_or_charges( pos, it ); + } + + contents.clear(); + return true; +} + +void item_pocket::clear_items() +{ + contents.clear(); +} + +bool item_pocket::has_item( const item &it ) const +{ + return contents.end() != + std::find_if( contents.begin(), contents.end(), [&it]( const item & e ) { + return &it == &e; + } ); +} + +item *item_pocket::get_item_with( const std::function &filter ) +{ + for( item &it : contents ) { + if( filter( it ) ) { + return ⁢ + } + } + return nullptr; +} + +void item_pocket::remove_items_if( const std::function &filter ) +{ + contents.remove_if( filter ); +} + +void item_pocket::has_rotten_away( const tripoint &pnt ) +{ + for( auto it = contents.begin(); it != contents.end(); ) { + if( g->m.has_rotten_away( *it, pnt ) ) { + it = contents.erase( it ); + } else { + ++it; + } + } +} + +bool item_pocket::empty() const +{ + return contents.empty(); +} + +void item_pocket::add( const item &it ) +{ + contents.push_back( it ); +} + +std::list &item_pocket::edit_contents() +{ + return contents; +} + +ret_val item_pocket::insert_item( const item &it ) +{ + const ret_val ret = can_contain( it ); + if( ret.success() ) { + contents.push_back( it ); + } + return ret; +} + +int item_pocket::obtain_cost( const item &it ) const +{ + if( has_item( it ) ) { + return data->moves; + } + return 0; +} + +bool item_pocket::is_type( pocket_type ptype ) const +{ + return ptype == data->type; +} + +bool item_pocket::is_valid() const +{ + return data != nullptr; +} + +units::volume item_pocket::contains_volume() const +{ + units::volume vol = 0_ml; + for( const item &it : contents ) { + vol += it.volume(); + } + return vol; +} + +units::mass item_pocket::contains_weight() const +{ + units::mass weight = 0_gram; + for( const item &it : contents ) { + weight += it.weight(); + } + return weight; +} + +units::mass item_pocket::remaining_weight() const +{ + return data->max_contains_weight - contains_weight(); +} diff --git a/src/item_pocket.h b/src/item_pocket.h new file mode 100644 index 0000000000000..a4ebfaf37d8c8 --- /dev/null +++ b/src/item_pocket.h @@ -0,0 +1,203 @@ +#pragma once +#ifndef ITEM_POCKET_H +#define ITEM_POCKET_H + +#include + +#include "enums.h" +#include "enum_traits.h" +#include "optional.h" +#include "type_id.h" +#include "ret_val.h" +#include "translations.h" +#include "units.h" +#include "visitable.h" + +class Character; +class item; +class item_location; +class player; +class pocket_data; + +struct iteminfo; +struct itype; +struct tripoint; + +using itype_id = std::string; + +class item_pocket +{ + public: + enum pocket_type { + // this is to aid the transition from the previous way item contents were handled. + // this will have the rules that previous contents would have + LEGACY_CONTAINER, + CONTAINER, + MAGAZINE, + LAST + }; + enum class contain_code { + SUCCESS, + // legacy containers can't technically contain anything + ERR_LEGACY_CONTAINER, + // trying to put a liquid into a non-watertight container + ERR_LIQUID, + // trying to put a gas in a non-gastight container + ERR_GAS, + // trying to put an item that wouldn't fit if the container were empty + ERR_TOO_BIG, + // trying to put an item that wouldn't fit if the container were empty + ERR_TOO_HEAVY, + // trying to put an item that wouldn't fit if the container were empty + ERR_TOO_SMALL, + // pocket doesn't have sufficient space left + ERR_NO_SPACE, + // pocket doesn't have sufficient weight left + ERR_CANNOT_SUPPORT, + // requires a flag + ERR_FLAG + }; + + item_pocket() = default; + item_pocket( const pocket_data *data ) : data( data ) {} + + bool stacks_with( const item_pocket &rhs ) const; + + bool is_valid() const; + bool is_type( pocket_type ptype ) const; + bool empty() const; + + std::list all_items(); + std::list all_items() const; + std::list all_items_ptr( pocket_type pk_type ); + std::list all_items_ptr( pocket_type pk_type ) const; + + std::list all_items_top(); + std::list all_items_top() const; + + item &back(); + const item &back() const; + item &front(); + const item &front() const; + size_t size() const; + void pop_back(); + + ret_val can_contain( const item &it ) const; + + // combined volume of contained items + units::volume contains_volume() const; + units::volume remaining_volume() const; + units::volume volume_capacity() const; + // combined weight of contained items + units::mass contains_weight() const; + units::mass remaining_weight() const; + + units::volume item_size_modifier() const; + units::mass item_weight_modifier() const; + + item *magazine_current(); + void casings_handle( const std::function &func ); + bool use_amount( const itype_id &it, int &quantity, std::list &used ); + bool will_explode_in_a_fire() const; + bool detonate( const tripoint &p, std::vector &drops ); + bool process( const itype &type, player *carrier, const tripoint &pos, bool activate, + float insulation, temperature_flag flag ); + bool legacy_unload( player &guy, bool &changed ); + void remove_all_ammo( Character &guy ); + void remove_all_mods( Character &guy ); + + // removes and returns the item from the pocket. + cata::optional remove_item( const item &it ); + cata::optional remove_item( const item_location &it ); + bool spill_contents( const tripoint &pos ); + void clear_items(); + bool has_item( const item &it ) const; + item *get_item_with( const std::function &filter ); + void remove_items_if( const std::function &filter ); + void has_rotten_away( const tripoint &pnt ); + pocket_type saved_type() const { + return _saved_type; + } + + // tries to put an item in the pocket. returns false if failure + ret_val insert_item( const item &it ); + void add( const item &it ); + + // only available to help with migration from previous usage of std::list + std::list &edit_contents(); + + // cost of getting an item from this pocket + // @TODO: make move cost vary based on other contained items + int obtain_cost( const item &it ) const; + + // this is used for the visitable interface. returns true if no further visiting is required + bool remove_internal( const std::function &filter, + int &count, std::list &res ); + // @relates visitable + VisitResponse visit_contents( const std::function &func, + item *parent = nullptr ); + + void general_info( std::vector &info, int pocket_number, bool disp_pocket_number ) const; + void contents_info( std::vector &info, int pocket_number, bool disp_pocket_number ) const; + + void serialize( JsonOut &json ) const; + void deserialize( JsonIn &jsin ); + + bool operator==( const item_pocket &rhs ) const; + private: + // the type of pocket, saved to json + pocket_type _saved_type = pocket_type::LAST; + const pocket_data *data = nullptr; + // the items inside the pocket + std::list contents; +}; + +class pocket_data +{ + public: + bool was_loaded; + + item_pocket::pocket_type type = item_pocket::pocket_type::LEGACY_CONTAINER; + // max volume of stuff the pocket can hold + units::volume max_contains_volume = 0_ml; + // min volume of item that can be contained, otherwise it spills + units::volume min_item_volume = 0_ml; + // max weight of stuff the pocket can hold + units::mass max_contains_weight = 0_gram; + // multiplier for spoilage rate of contained items + float spoil_multiplier = 1.0f; + // items' weight in this pocket are modified by this number + float weight_multiplier = 1.0f; + // base time it takes to pull an item out of the pocket + int moves = 100; + // protects contents from exploding in a fire + bool fire_protection = false; + // can hold liquids + bool watertight = false; + // can hold gas + bool gastight = false; + // the pocket will spill its contents if placed in another container + bool open_container = false; + // allows only items with at least one of the following flags to be stored inside + // empty means no restriction + std::vector flag_restriction; + // container's size and encumbrance does not change based on contents. + bool rigid = false; + + bool operator==( const pocket_data &rhs ) const; + + void load( const JsonObject &jo ); + void deserialize( JsonIn &jsin ); +}; + +template<> +struct enum_traits { + static constexpr auto last = item_pocket::pocket_type::LAST; +}; + +template<> +struct ret_val::default_success + : public std::integral_constant {}; + +#endif diff --git a/src/itype.h b/src/itype.h index d4e641aaa2d8f..14658c615279f 100644 --- a/src/itype.h +++ b/src/itype.h @@ -15,6 +15,7 @@ #include "enums.h" // point #include "explosion.h" #include "game_constants.h" +#include "item_contents.h" #include "iuse.h" // use_function #include "optional.h" #include "pldata.h" // add_type @@ -926,6 +927,9 @@ struct itype { /** Weight difference with the part it replaces for mods */ units::mass integral_weight = -1_gram; + // information related to being able to store things inside the item. + std::vector pockets; + /** * Space occupied by items of this type * CAUTION: value given is for a default-sized stack. Avoid using where @ref count_by_charges items may be encountered; see @ref item::volume instead. diff --git a/src/iuse.cpp b/src/iuse.cpp index 1bbec0c360196..3e9da5fa9fdbd 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -1682,14 +1682,7 @@ int iuse::remove_all_mods( player *p, item *, bool, const tripoint & ) } if( !loc->ammo_remaining() || g->unload( *loc ) ) { - auto mod = std::find_if( loc->contents.begin(), loc->contents.end(), []( const item & e ) { - return e.is_toolmod() && !e.is_irremovable(); - } ); - add_msg( m_info, _( "You remove the %s from the tool." ), mod->tname() ); - p->i_add_or_drop( *mod ); - loc->contents.erase( mod ); - - remove_radio_mod( *loc, *p ); + loc->contents.remove_all_mods( *p ); } return 0; } @@ -2066,7 +2059,7 @@ int iuse::water_purifier( player *p, item *it, bool, const tripoint & ) return 0; } auto obj = g->inv_map_splice( []( const item & e ) { - return !e.contents.empty() && e.contents.front().typeId() == "water"; + return !e.contents.empty() && e.contents.legacy_front().typeId() == "water"; }, _( "Purify what?" ), 1, _( "You don't have water to purify." ) ); if( !obj ) { @@ -2074,7 +2067,7 @@ int iuse::water_purifier( player *p, item *it, bool, const tripoint & ) return 0; } - item &liquid = obj->contents.front(); + item &liquid = obj->contents.legacy_front(); if( !it->units_sufficient( *p, liquid.charges ) ) { p->add_msg_if_player( m_info, _( "That volume of water is too large to purify." ) ); return 0; @@ -7949,16 +7942,17 @@ int iuse::foodperson( player *p, item *it, bool t, const tripoint &pos ) int iuse::radiocar( player *p, item *it, bool, const tripoint & ) { int choice = -1; - auto bomb_it = std::find_if( it->contents.begin(), it->contents.end(), []( const item & c ) { - return c.has_flag( "RADIOCARITEM" ); + std::list all_items{ it->contents.all_items_ptr() }; + auto bomb_it = std::find_if( all_items.begin(), all_items.end(), []( const item * c ) { + return c->has_flag( "RADIOCARITEM" ); } ); - if( bomb_it == it->contents.end() ) { + if( bomb_it == all_items.end() ) { choice = uilist( _( "Using RC car:" ), { _( "Turn on" ), _( "Put a bomb to car" ) } ); } else { choice = uilist( _( "Using RC car:" ), { - _( "Turn on" ), bomb_it->tname() + _( "Turn on" ), ( *bomb_it )->tname() } ); } if( choice < 0 ) { @@ -7981,7 +7975,7 @@ int iuse::radiocar( player *p, item *it, bool, const tripoint & ) if( choice == 1 ) { - if( bomb_it == it->contents.end() ) { //arming car with bomb + if( bomb_it == all_items.end() ) { //arming car with bomb avatar *you = p->as_avatar(); item_location loc; @@ -8012,9 +8006,9 @@ int iuse::radiocar( player *p, item *it, bool, const tripoint & ) } else { // Disarm the car p->moves -= to_moves( 2_seconds ); - p->inv.assign_empty_invlet( *bomb_it, *p, true ); // force getting an invlet. - p->i_add( *bomb_it ); - it->contents.erase( bomb_it ); + p->inv.assign_empty_invlet( **bomb_it, *p, true ); // force getting an invlet. + p->i_add( **bomb_it ); + it->contents.remove_item( **bomb_it ); p->add_msg_if_player( _( "You disarmed your RC car." ) ); } @@ -8066,12 +8060,11 @@ static void sendRadioSignal( player &p, const std::string &signal ) it.ammo_unset(); } } else if( it.has_flag( "RADIO_CONTAINER" ) && !it.contents.empty() ) { - auto itm = std::find_if( it.contents.begin(), - it.contents.end(), [&signal]( const item & c ) { + item *itm = it.contents.get_item_with( [&signal]( const item & c ) { return c.has_flag( signal ); } ); - if( itm != it.contents.end() ) { + if( itm != nullptr ) { sounds::sound( p.pos(), 6, sounds::sound_t::alarm, _( "beep" ), true, "misc", "beep" ); // Invoke twice: first to transform, then later to proc if( itm->has_flag( "RADIO_INVOKE_PROC" ) ) { @@ -8081,7 +8074,7 @@ static void sendRadioSignal( player &p, const std::string &signal ) } if( itm->has_flag( "BOMB" ) ) { itm->type->invoke( p, *itm, loc ); - it.contents.clear(); + it.contents.clear_items(); } } } @@ -8164,12 +8157,11 @@ int iuse::radiocontrol( player *p, item *it, bool t, const tripoint & ) if( !radio_containers.empty() ) { for( auto items : radio_containers ) { - auto itm = std::find_if( items->contents.begin(), - items->contents.end(), [&]( const item & c ) { + item *itm = items->contents.get_item_with( [&]( const item & c ) { return c.has_flag( "BOMB" ) && c.has_flag( signal ); } ); - if( itm != items->contents.end() ) { + if( itm != nullptr ) { p->add_msg_if_player( m_warning, _( "The %1$s in your %2$s would explode on this signal. Place it down before sending the signal." ), itm->display_name(), items->display_name() ); @@ -8430,9 +8422,9 @@ int iuse::autoclave( player *p, item *it, bool t, const tripoint &pos ) if( Cycle_time <= 0 ) { it->active = false; it->erase_var( "CYCLETIME" ); - for( item &bio : it->contents ) { - if( bio.is_bionic() && !bio.has_flag( "NO_PACKED" ) ) { - bio.unset_flag( "NO_STERILE" ); + for( item *bio : it->contents.all_items_ptr() ) { + if( bio->is_bionic() && !bio->has_flag( "NO_PACKED" ) ) { + bio->unset_flag( "NO_STERILE" ); } } } else { @@ -8446,9 +8438,9 @@ int iuse::autoclave( player *p, item *it, bool t, const tripoint &pos ) bool empty = true; item *clean_cbm = nullptr; - for( item &bio : it->contents ) { - if( bio.is_bionic() ) { - clean_cbm = &bio; + for( item *bio : it->contents.all_items_ptr() ) { + if( bio->is_bionic() ) { + clean_cbm = bio; } } if( clean_cbm ) { @@ -8533,7 +8525,8 @@ int iuse::multicooker( player *p, item *it, bool t, const tripoint &pos ) } if( cooktime <= 0 ) { - item &meal = it->emplace_back( it->get_var( "DISH" ) ); + it->contents.insert_legacy( item( it->get_var( "DISH" ) ) ); + item &meal = it->contents.legacy_back(); if( ( *recipe_id( it->get_var( "RECIPE" ) ) ).hot_result() ) { meal.heat_up(); } else { @@ -8588,14 +8581,14 @@ int iuse::multicooker( player *p, item *it, bool t, const tripoint &pos ) menu.text = _( "Welcome to the RobotChef3000. Choose option:" ); // Find actual contents rather than attached mod or battery. - auto dish_it = std::find_if_not( it->contents.begin(), it->contents.end(), []( const item & c ) { - return c.is_toolmod() || c.is_magazine(); + item *dish_it = it->contents.get_item_with( []( const item & c ) { + return !( c.is_toolmod() || c.is_magazine() ); } ); if( it->active ) { menu.addentry( mc_stop, true, 's', _( "Stop cooking" ) ); } else { - if( dish_it == it->contents.end() ) { + if( dish_it == nullptr ) { if( it->ammo_remaining() < charges_to_start ) { p->add_msg_if_player( _( "Batteries are low." ) ); return 0; @@ -8658,7 +8651,7 @@ int iuse::multicooker( player *p, item *it, bool t, const tripoint &pos ) p->i_add( dish ); } - it->contents.erase( dish_it ); + it->contents.remove_item( *dish_it ); it->erase_var( "RECIPE" ); if( is_delicious ) { diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index 34fd35111e0a8..fb600a9e0c148 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -203,7 +203,8 @@ int iuse_transform::use( player &p, item &it, bool t, const tripoint &pos ) cons } } else { it.convert( container ); - obj = &it.emplace_back( target, calendar::turn, std::max( ammo_qty, 1 ) ); + it.contents.insert_legacy( item( target, calendar::turn, std::max( ammo_qty, 1 ) ) ); + obj = &it.contents.legacy_back(); } if( p.is_worn( *obj ) ) { p.reset_encumbrance(); @@ -2552,15 +2553,16 @@ int holster_actor::use( player &p, item &it, bool, const tripoint & ) const int pos = 0; std::vector opts; - if( static_cast( it.contents.size() ) < multi ) { + if( static_cast( it.contents.num_item_stacks() ) < multi ) { std::string prompt = holster_prompt.empty() ? _( "Holster item" ) : _( holster_prompt ); opts.push_back( prompt ); pos = -1; } - - std::transform( it.contents.begin(), it.contents.end(), std::back_inserter( opts ), - []( const item & elem ) { - return string_format( _( "Draw %s" ), elem.display_name() ); + std::list all_items = it.contents.all_items_top( + item_pocket::pocket_type::LEGACY_CONTAINER ); + std::transform( all_items.begin(), all_items.end(), std::back_inserter( opts ), + []( const item * elem ) { + return string_format( _( "Draw %s" ), elem->display_name() ); } ); item *internal_item = nullptr; @@ -2570,14 +2572,14 @@ int holster_actor::use( player &p, item &it, bool, const tripoint & ) const pos = -2; } else { pos += ret; - if( opts.size() != it.contents.size() ) { + if( opts.size() != it.contents.num_item_stacks() ) { ret--; } - auto iter = std::next( it.contents.begin(), ret ); - internal_item = &*iter; + auto iter = std::next( all_items.begin(), ret ); + internal_item = *iter; } } else { - internal_item = &it.contents.front(); + internal_item = all_items.front(); } if( pos < -1 ) { @@ -2677,8 +2679,8 @@ bool bandolier_actor::is_valid_ammo_type( const itype &t ) const bool bandolier_actor::can_store( const item &bandolier, const item &obj ) const { - if( !bandolier.contents.empty() && ( bandolier.contents.front().typeId() != obj.typeId() || - bandolier.contents.front().charges >= capacity ) ) { + if( !bandolier.contents.empty() && ( bandolier.contents.legacy_front().typeId() != obj.typeId() || + bandolier.contents.legacy_front().charges >= capacity ) ) { return false; } @@ -2726,7 +2728,7 @@ bool bandolier_actor::reload( player &p, item &obj ) const sel.ammo.remove_item(); } } else { - obj.contents.front().charges += sel.qty(); + obj.contents.legacy_front().charges += sel.qty(); if( sel.ammo->charges > sel.qty() ) { sel.ammo->charges -= sel.qty(); } else { @@ -2735,7 +2737,7 @@ bool bandolier_actor::reload( player &p, item &obj ) const } p.add_msg_if_player( _( "You store the %1$s in your %2$s" ), - obj.contents.front().tname( sel.qty() ), + obj.contents.legacy_front().tname( sel.qty() ), obj.type_name() ); return true; @@ -2754,7 +2756,7 @@ int bandolier_actor::use( player &p, item &it, bool, const tripoint & ) const std::vector> actions; - menu.addentry( -1, it.contents.empty() || it.contents.front().charges < capacity, + menu.addentry( -1, it.contents.empty() || it.contents.legacy_front().charges < capacity, 'r', _( "Store ammo in %s" ), it.type_name() ); actions.emplace_back( [&] { reload( p, it ); } ); @@ -2762,9 +2764,9 @@ int bandolier_actor::use( player &p, item &it, bool, const tripoint & ) const menu.addentry( -1, !it.contents.empty(), 'u', _( "Unload %s" ), it.type_name() ); actions.emplace_back( [&] { - if( p.i_add_or_drop( it.contents.front() ) ) + if( p.i_add_or_drop( it.contents.legacy_front() ) ) { - it.contents.erase( it.contents.begin() ); + it.contents.remove_item( it.contents.legacy_front() ); } else { p.add_msg_if_player( _( "Never mind." ) ); @@ -3252,7 +3254,8 @@ static bool damage_item( player &pl, item_location &fix ) if( fix.where() == item_location::type::character ) { pl.i_rem_keep_contents( pl.get_item_position( fix.get_item() ) ); } else { - put_into_vehicle_or_drop( pl, item_drop_reason::deliberate, fix->contents, fix.position() ); + put_into_vehicle_or_drop( pl, item_drop_reason::deliberate, fix->contents.all_items(), + fix.position() ); fix.remove_item(); } @@ -4073,7 +4076,7 @@ int saw_barrel_actor::use( player &p, item &it, bool t, const tripoint & ) const item &obj = p.i_at( loc.obtain( p ) ); p.add_msg_if_player( _( "You saw down the barrel of your %s." ), obj.tname() ); - obj.contents.emplace_back( "barrel_small", calendar::turn ); + obj.contents.insert_legacy( item( "barrel_small", calendar::turn ) ); return 0; } diff --git a/src/map.cpp b/src/map.cpp index 55a5b8e260849..18d5ea84f547f 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2818,7 +2818,7 @@ void map::smash_items( const tripoint &p, const int power, const std::string &ca // Remove them if they were damaged too much if( i->damage() == i->max_damage() || ( by_charges && i->charges == 0 ) ) { // But save the contents, except for irremovable gunmods - for( auto &elem : i->contents ) { + for( item &elem : i->contents.all_items() ) { if( !elem.is_irremovable() ) { contents.push_back( elem ); } @@ -3226,7 +3226,7 @@ void map::bash_items( const tripoint &p, bash_params ¶ms ) if( bashed_item->made_of( material_id( "glass" ) ) && !bashed_item->active && one_in( 2 ) ) { params.did_bash = true; smashed_glass = true; - for( const item &bashed_content : bashed_item->contents ) { + for( const item &bashed_content : bashed_item->contents.all_items() ) { smashed_contents.push_back( bashed_content ); } bashed_item = bashed_items.erase( bashed_item ); @@ -6673,7 +6673,7 @@ bool map::has_rotten_away( item &itm, const tripoint &pnt ) const return false; } else if( itm.type->container && itm.type->container->seals ) { // Items inside rot but do not vanish as the container seals them in. - for( auto &c : itm.contents ) { + for( item &c : itm.contents.all_items() ) { if( c.goes_bad() ) { c.process_temperature_rot( 1, pnt, nullptr ); } @@ -6681,13 +6681,7 @@ bool map::has_rotten_away( item &itm, const tripoint &pnt ) const return false; } else { // Check and remove rotten contents, but always keep the container. - for( auto it = itm.contents.begin(); it != itm.contents.end(); ) { - if( has_rotten_away( *it, pnt ) ) { - it = itm.contents.erase( it ); - } else { - ++it; - } - } + itm.contents.has_rotten_away( pnt ); return false; } diff --git a/src/map.h b/src/map.h index afe4be392d29f..b7f55c7a765b4 100644 --- a/src/map.h +++ b/src/map.h @@ -1441,6 +1441,14 @@ class map * If false, monsters are not spawned in view of player character. */ void spawn_monsters( bool ignore_sight ); + /** + * Whether the item has to be removed as it has rotten away completely. + * @param itm Item to check for rotting + * @param pnt The *absolute* position of the item in the world (not just on this map!), + * used for rot calculation. + * @return true if the item has rotten away and should be removed, false otherwise. + */ + bool has_rotten_away( item &itm, const tripoint &pnt ) const; private: // Helper #1 - spawns monsters on one submap void spawn_monsters_submap( const tripoint &gp, bool ignore_sight ); @@ -1474,14 +1482,6 @@ class map * Hacks in missing roofs. Should be removed when 3D mapgen is done. */ void add_roofs( const tripoint &grid ); - /** - * Whether the item has to be removed as it has rotten away completely. - * @param itm Item to check for rotting - * @param pnt The *absolute* position of the item in the world (not just on this map!), - * used for rot calculation. - * @return true if the item has rotten away and should be removed, false otherwise. - */ - bool has_rotten_away( item &itm, const tripoint &pnt ) const; /** * Go through the list of items, update their rotten status and remove items * that have rotten away completely. diff --git a/src/map_field.cpp b/src/map_field.cpp index 1cd4247eb2e15..c5ee522976f8e 100644 --- a/src/map_field.cpp +++ b/src/map_field.cpp @@ -522,7 +522,8 @@ bool map::process_fields_in_submap( submap *const current_submap, if( destroyed ) { // If we decided the item was destroyed by fire, remove it. // But remember its contents, except for irremovable mods, if any - std::copy( fuel->contents.begin(), fuel->contents.end(), + const std::list all_items{ fuel->contents.all_items() }; + std::copy( all_items.begin(), all_items.end(), std::back_inserter( new_content ) ); new_content.erase( std::remove_if( new_content.begin(), new_content.end(), [&]( const item & i ) { return i.is_irremovable(); diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 409c9139810e7..6215ce8955e7c 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -5881,7 +5881,7 @@ std::vector map::place_items( const items_location &loc, const int chanc for( auto e : res ) { if( e->is_tool() || e->is_gun() || e->is_magazine() ) { if( rng( 0, 99 ) < magazine && !e->magazine_integral() && !e->magazine_current() ) { - e->contents.emplace_back( e->magazine_default(), e->birthday() ); + e->contents.insert_legacy( item( e->magazine_default(), e->birthday() ) ); } if( rng( 0, 99 ) < ammo && e->ammo_remaining() == 0 ) { e->ammo_set( e->ammo_default(), e->ammo_capacity() ); diff --git a/src/melee.cpp b/src/melee.cpp index 5d6633a510d1b..fd6b48cbf7da1 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -230,7 +230,7 @@ bool player::handle_melee_wear( item &shield, float wear_multiplier ) } } - for( auto &elem : shield.contents ) { + for( item &elem : shield.contents.all_items() ) { g->m.add_item_or_charges( pos(), elem ); } @@ -1789,9 +1789,7 @@ std::string player::melee_special_effects( Creature &t, damage_instance &d, item sounds::sound( pos(), 16, sounds::sound_t::combat, "Crack!", true, "smash_success", "smash_glass_contents" ); // Dump its contents on the ground - for( auto &elem : weap.contents ) { - g->m.add_item_or_charges( pos(), elem ); - } + weap.spill_contents( pos() ); // Take damage deal_damage( nullptr, bp_arm_r, damage_instance::physical( 0, rng( 0, vol * 2 ), 0 ) ); if( weap.is_two_handed( *this ) ) { // Hurt left arm too, if it was big diff --git a/src/memorial_logger.cpp b/src/memorial_logger.cpp index 3dd7fe47bacba..dd70c16d1473f 100644 --- a/src/memorial_logger.cpp +++ b/src/memorial_logger.cpp @@ -290,8 +290,9 @@ void memorial_logger::write( std::ostream &file, const std::string &epitaph ) co file << indent << next_item.invlet << " - " << next_item.tname( 1, false ); if( next_item.charges > 0 ) { file << " (" << next_item.charges << ")"; - } else if( next_item.contents.size() == 1 && next_item.contents.front().charges > 0 ) { - file << " (" << next_item.contents.front().charges << ")"; + } else if( next_item.contents.legacy_size() == 1 && + next_item.contents.legacy_front().charges > 0 ) { + file << " (" << next_item.contents.legacy_front().charges << ")"; } file << eol; } @@ -310,8 +311,9 @@ void memorial_logger::write( std::ostream &file, const std::string &epitaph ) co } if( next_item.charges > 0 ) { file << " (" << next_item.charges << ")"; - } else if( next_item.contents.size() == 1 && next_item.contents.front().charges > 0 ) { - file << " (" << next_item.contents.front().charges << ")"; + } else if( next_item.contents.legacy_size() == 1 && + next_item.contents.legacy_front().charges > 0 ) { + file << " (" << next_item.contents.legacy_front().charges << ")"; } file << eol; } diff --git a/src/mission.cpp b/src/mission.cpp index 05374d5af7af2..157b10ee90c7e 100644 --- a/src/mission.cpp +++ b/src/mission.cpp @@ -488,7 +488,7 @@ void mission::get_all_item_group_matches( std::vector &items, //recursivly check item contents for target if( itm->is_container() && !itm->is_container_empty() ) { - std::list content_list = itm->contents; + std::list content_list = itm->contents.all_items(); std::vector content = std::vector(); diff --git a/src/mutation.cpp b/src/mutation.cpp index 01013a305999b..427fa57c0565b 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -223,7 +223,7 @@ void Character::mutation_effect( const trait_id &mut ) _( "Your %s is destroyed!" ), _( "'s %s is destroyed!" ), armor.tname() ); - for( item &remain : armor.contents ) { + for( item &remain : armor.contents.all_items() ) { g->m.add_item_or_charges( pos(), remain ); } } else { diff --git a/src/npc.cpp b/src/npc.cpp index 402147311529e..a07d02d54301e 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -2770,7 +2770,7 @@ bool npc::dispose_item( item_location &&obj, const std::string & ) if( e.can_holster( *obj ) ) { auto ptr = dynamic_cast( e.type->get_use( "holster" )->get_actor_ptr() ); opts.emplace_back( dispose_option { - item_store_cost( *obj, e, false, ptr->draw_cost ), + item_store_cost( *obj, e, false, e.contents.obtain_cost( *obj ) ), [this, ptr, &e, &obj]{ ptr->store( *this, e, *obj ); } } ); } diff --git a/src/npcmove.cpp b/src/npcmove.cpp index 6c2e959967ec7..e1fd81e7c7dc0 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -1567,7 +1567,7 @@ bool npc::consume_cbm_items( const std::function &filter ) int index = -1; for( size_t i = 0; i < slice.size(); i++ ) { const item &it = slice[i]->front(); - const item &real_item = it.is_container() ? it.contents.front() : it; + const item &real_item = it.is_container() ? it.contents.legacy_front() : it; if( filter( real_item ) ) { index = i; break; @@ -1600,7 +1600,8 @@ bool npc::recharge_cbm() } else { const std::function fuel_filter = [bid]( const item & it ) { for( const itype_id &fid : bid->fuel_opts ) { - return it.typeId() == fid || ( !it.is_container_empty() && it.contents.front().typeId() == fid ); + return it.typeId() == fid || ( !it.is_container_empty() && + it.contents.legacy_front().typeId() == fid ); } return false; }; diff --git a/src/npctalk.cpp b/src/npctalk.cpp index b17101e27dc69..2131b1dd22862 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -1952,7 +1952,7 @@ void talk_effect_fun_t::set_u_buy_item( const std::string &item_name, int cost, } } else { item container( container_name, calendar::turn ); - container.emplace_back( item_name, calendar::turn, count ); + container.contents.insert_legacy( item( item_name, calendar::turn, count ) ); u.i_add( container ); //~ %1%s is the NPC name, %2$s is an item popup( _( "%1$s gives you a %2$s." ), p.name, container.tname() ); @@ -3136,7 +3136,7 @@ static consumption_result try_consume( npc &p, item &it, std::string &reason ) { // TODO: Unify this with 'player::consume_item()' bool consuming_contents = it.is_container() && !it.contents.empty(); - item &to_eat = consuming_contents ? it.contents.front() : it; + item &to_eat = consuming_contents ? it.contents.legacy_front() : it; const auto &comest = to_eat.get_comestible(); if( !comest ) { // Don't inform the player that we don't want to eat the lighter @@ -3188,7 +3188,7 @@ static consumption_result try_consume( npc &p, item &it, std::string &reason ) } if( consuming_contents ) { - it.contents.erase( it.contents.begin() ); + it.contents.remove_item( it.contents.legacy_front() ); return CONSUMED_SOME; } diff --git a/src/pickup.cpp b/src/pickup.cpp index e673f734eff04..d0dd3f7427366 100644 --- a/src/pickup.cpp +++ b/src/pickup.cpp @@ -158,7 +158,7 @@ static pickup_answer handle_problematic_pickup( const item &it, bool &offered_sw } if( it.is_bucket_nonempty() ) { amenu.addentry( SPILL, u.can_pickVolume( it ), 's', _( "Spill %s, then pick up %s" ), - it.contents.front().tname(), it.display_name() ); + it.contents.legacy_front().tname(), it.display_name() ); } amenu.query(); @@ -1050,7 +1050,7 @@ void show_pickup_message( const PickupMap &mapPickup ) bool Pickup::handle_spillable_contents( Character &c, item &it, map &m ) { if( it.is_bucket_nonempty() ) { - const item &it_cont = it.contents.front(); + const item &it_cont = it.contents.legacy_front(); int num_charges = it_cont.charges; while( !it.spill_contents( c ) ) { if( num_charges > it_cont.charges ) { diff --git a/src/player.cpp b/src/player.cpp index b70dd6907b0b6..cfc0cc4c371e7 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -99,6 +99,7 @@ #include "flat_set.h" #include "stomach.h" #include "teleport.h" +#include "item_contents.h" const double MAX_RECOIL = 3000; @@ -2729,7 +2730,7 @@ bool player::consume( item_location loc ) const bool was_in_container = !can_consume_as_is( target ); if( was_in_container ) { - i_rem( &target.contents.front() ); + i_rem( &target.contents.legacy_front() ); } else { i_rem( &target ); } @@ -2854,7 +2855,7 @@ item::reload_option player::select_ammo( const item &base, } else if( e.ammo->is_watertight_container() || ( e.ammo->is_ammo_container() && g->u.is_worn( *e.ammo ) ) ) { // worn ammo containers should be named by their contents with their location also updated below - return e.ammo->contents.front().display_name(); + return e.ammo->contents.legacy_front().display_name(); } else { return ( ammo_location && ammo_location == e.ammo ? "* " : "" ) + e.ammo->display_name(); @@ -2914,7 +2915,7 @@ item::reload_option player::select_ammo( const item &base, row += string_format( " %-7d ", sel.moves() ); if( base.is_gun() || base.is_magazine() ) { - const itype *ammo = sel.ammo->is_ammo_container() ? sel.ammo->contents.front().ammo_data() : + const itype *ammo = sel.ammo->is_ammo_container() ? sel.ammo->contents.legacy_front().ammo_data() : sel.ammo->ammo_data(); if( ammo ) { if( ammo->ammo->prop_damage ) { @@ -2947,7 +2948,7 @@ item::reload_option player::select_ammo( const item &base, } for( auto i = 0; i < static_cast( opts.size() ); ++i ) { - const item &ammo = opts[ i ].ammo->is_ammo_container() ? opts[ i ].ammo->contents.front() : + const item &ammo = opts[ i ].ammo->is_ammo_container() ? opts[ i ].ammo->contents.legacy_front() : *opts[ i ].ammo; char hotkey = -1; @@ -3041,7 +3042,7 @@ item::reload_option player::select_ammo( const item &base, const item_location &sel = opts[ menu.ret ].ammo; uistate.lastreload[ ammotype( base.ammo_default() ) ] = sel->is_ammo_container() ? - sel->contents.front().typeId() : + sel->contents.legacy_front().typeId() : sel->typeId(); return opts[ menu.ret ]; } @@ -3070,7 +3071,7 @@ bool player::list_ammo( const item &base, std::vector &ammo continue; } auto id = ( ammo->is_ammo_container() || ammo->is_container() ) - ? ammo->contents.front().typeId() + ? ammo->contents.legacy_front().typeId() : ammo->typeId(); if( e->can_reload_with( id ) ) { // Speedloaders require an empty target. @@ -3104,7 +3105,7 @@ item::reload_option player::select_ammo( const item &base, bool prompt, bool emp if( base.ammo_data() ) { name = base.ammo_data()->nname( 1 ); } else if( base.is_watertight_container() ) { - name = base.is_container_empty() ? "liquid" : base.contents.front().tname(); + name = base.is_container_empty() ? "liquid" : base.contents.legacy_front().tname(); } else { name = enumerate_as_string( base.ammo_types().begin(), base.ammo_types().end(), []( const ammotype & at ) { @@ -3460,7 +3461,7 @@ int player::item_reload_cost( const item &it, const item &ammo, int qty ) const if( ammo.is_ammo() || ammo.is_frozen_liquid() ) { qty = std::max( std::min( ammo.charges, qty ), 1 ); } else if( ammo.is_ammo_container() || ammo.is_container() ) { - qty = std::max( std::min( ammo.contents.front().charges, qty ), 1 ); + qty = clamp( ammo.contents.legacy_front().charges, 1, qty ); } else if( ammo.is_magazine() ) { qty = 1; } else { @@ -3678,16 +3679,7 @@ bool player::unload( item &it ) } bool changed = false; - it.contents.erase( std::remove_if( it.contents.begin(), it.contents.end(), [this, - &changed]( item & e ) { - int old_charges = e.charges; - const bool consumed = this->add_or_drop_with_msg( e, true ); - changed = changed || consumed || e.charges != old_charges; - if( consumed ) { - this->mod_moves( -this->item_handling_cost( e ) ); - } - return consumed; - } ), it.contents.end() ); + it.contents.legacy_unload( *this, changed ); if( changed ) { it.on_contents_changed(); } @@ -3698,7 +3690,7 @@ bool player::unload( item &it ) std::vector msgs( 1, it.tname() ); std::vector opts( 1, &it ); - for( auto e : it.gunmods() ) { + for( item *e : it.gunmods() ) { if( e->is_gun() && !e->has_flag( "NO_UNLOAD" ) && ( e->magazine_current() || e->ammo_remaining() > 0 || e->casings_count() > 0 ) ) { msgs.emplace_back( e->tname() ); @@ -3755,8 +3747,8 @@ bool player::unload( item &it ) // Calculate the time to remove the contained ammo (consuming half as much time as required to load the magazine) int mv = 0; - for( auto &content : target->contents ) { - mv += this->item_reload_cost( it, content, content.charges ) / 2; + for( const item &content : target->contents.all_items() ) { + mv += item_reload_cost( it, content, content.charges ) / 2; } g->u.activity.moves_left += mv; @@ -3775,9 +3767,7 @@ bool player::unload( item &it ) // Eject magazine consuming half as much time as required to insert it this->moves -= this->item_reload_cost( *target, *target->magazine_current(), -1 ) / 2; - target->contents.remove_if( [&target]( const item & e ) { - return target->magazine_current() == &e; - } ); + target->contents.remove_item( *target->magazine_current() ); } else if( target->ammo_remaining() ) { int qty = target->ammo_remaining(); @@ -4016,10 +4006,7 @@ void player::reassign_item( item &it, int invlet ) bool player::gunmod_remove( item &gun, item &mod ) { - auto iter = std::find_if( gun.contents.begin(), gun.contents.end(), [&mod]( const item & e ) { - return &mod == &e; - } ); - if( iter == gun.contents.end() ) { + if( !gun.contents.has_item( mod ) ) { debugmsg( "Cannot remove non-existent gunmod" ); return false; } @@ -4039,7 +4026,7 @@ bool player::gunmod_remove( item &gun, item &mod ) const itype *modtype = mod.type; i_add_or_drop( mod ); - gun.contents.erase( iter ); + gun.contents.remove_item( mod ); //If the removed gunmod added mod locations, check to see if any mods are in invalid locations if( !modtype->gunmod->add_mod.empty() ) { @@ -4803,9 +4790,9 @@ std::string player::weapname( unsigned int truncate ) const } return str; - } else if( weapon.is_container() && weapon.contents.size() == 1 ) { + } else if( weapon.is_container() && weapon.contents.legacy_size() == 1 ) { return string_format( "%s (%d)", weapon.tname(), - weapon.contents.front().charges ); + weapon.contents.legacy_front().charges ); } else if( !is_armed() ) { return _( "fists" ); @@ -4819,8 +4806,9 @@ bool player::wield_contents( item &container, item *internal_item, bool penaltie { // if index not specified and container has multiple items then ask the player to choose one if( internal_item == nullptr ) { + std::list all_items = container.contents.all_items(); std::vector opts; - std::transform( container.contents.begin(), container.contents.end(), + std::transform( all_items.begin(), all_items.end(), std::back_inserter( opts ), []( const item & elem ) { return elem.display_name(); } ); @@ -4830,12 +4818,12 @@ bool player::wield_contents( item &container, item *internal_item, bool penaltie return false; } } else { - internal_item = &container.contents.front(); + internal_item = &container.contents.legacy_front(); } } - const bool has = std::any_of( container.contents.begin(), - container.contents.end(), [internal_item]( const item & it ) { + const bool has = container.contents.get_item_with( + [internal_item]( const item & it ) { return internal_item == ⁢ } ); if( !has ) { @@ -4859,7 +4847,7 @@ bool player::wield_contents( item &container, item *internal_item, bool penaltie } weapon = std::move( *internal_item ); - container.contents.remove_if( [internal_item]( const item & it ) { + container.contents.remove_items_if( [internal_item]( const item & it ) { return internal_item == ⁢ } ); container.on_contents_changed(); @@ -4888,10 +4876,11 @@ bool player::wield_contents( item &container, item *internal_item, bool penaltie return true; } -void player::store( item &container, item &put, bool penalties, int base_cost ) +void player::store( item &container, item &put, bool penalties, int base_cost, + item_pocket::pocket_type pk_type ) { moves -= item_store_cost( put, container, penalties, base_cost ); - container.put_in( i_rem( &put ) ); + container.put_in( i_rem( &put ), pk_type ); reset_encumbrance(); } @@ -5449,10 +5438,10 @@ void player::place_corpse() // Restore amount of installed pseudo-modules of Power Storage Units std::pair storage_modules = amount_of_storage_bionics(); for( int i = 0; i < storage_modules.first; ++i ) { - body.emplace_back( "bio_power_storage" ); + body.contents.insert_legacy( item( "bio_power_storage" ) ); } for( int i = 0; i < storage_modules.second; ++i ) { - body.emplace_back( "bio_power_storage_mkII" ); + body.contents.insert_legacy( item( "bio_power_storage_mkII" ) ); } g->m.add_item_or_charges( pos(), body ); } @@ -5493,10 +5482,10 @@ void player::place_corpse( const tripoint &om_target ) // Restore amount of installed pseudo-modules of Power Storage Units std::pair storage_modules = amount_of_storage_bionics(); for( int i = 0; i < storage_modules.first; ++i ) { - body.emplace_back( "bio_power_storage" ); + body.contents.insert_legacy( item( "bio_power_storage" ) ); } for( int i = 0; i < storage_modules.second; ++i ) { - body.emplace_back( "bio_power_storage_mkII" ); + body.contents.insert_legacy( item( "bio_power_storage_mkII" ) ); } bay.add_item_or_charges( point( finX, finY ), body ); } diff --git a/src/player.h b/src/player.h index 6e0869214b666..a938668044f09 100644 --- a/src/player.h +++ b/src/player.h @@ -619,7 +619,8 @@ class player : public Character * @param base_cost Cost due to storage type. */ void store( item &container, item &put, bool penalties = true, - int base_cost = INVENTORY_HANDLING_PENALTY ); + int base_cost = INVENTORY_HANDLING_PENALTY, + item_pocket::pocket_type pk_type = item_pocket::pocket_type::LEGACY_CONTAINER ); /** Draws the UI and handles player input for the armor re-ordering window */ void sort_armor(); /** Uses a tool */ diff --git a/src/profession.cpp b/src/profession.cpp index 20765f0229e7b..d447f9224587b 100644 --- a/src/profession.cpp +++ b/src/profession.cpp @@ -401,7 +401,7 @@ std::list profession::items( bool male, const std::vector &trait for( item &it : result ) { clear_faults( it ); if( it.is_holster() && it.contents.size() == 1 ) { - clear_faults( it.contents.front() ); + clear_faults( it.contents.legacy_front() ); } if( it.has_flag( "VARSIZE" ) ) { it.item_tags.insert( "FIT" ); @@ -626,8 +626,8 @@ std::vector json_item_substitution::get_substitution( const item &it, auto iter = substitutions.find( it.typeId() ); std::vector ret; if( iter == substitutions.end() ) { - for( const item &con : it.contents ) { - const auto sub = get_substitution( con, traits ); + for( const item &con : it.contents.all_items() ) { + const std::vector sub = get_substitution( con, traits ); ret.insert( ret.end(), sub.begin(), sub.end() ); } return ret; @@ -655,7 +655,8 @@ std::vector json_item_substitution::get_substitution( const item &it, while( result.charges > 0 ) { const item pushed = result.in_its_container(); ret.push_back( pushed ); - result.mod_charges( pushed.contents.empty() ? -pushed.charges : -pushed.contents.back().charges ); + result.mod_charges( pushed.contents.empty() ? -pushed.charges : + -pushed.contents.legacy_back().charges ); } } } diff --git a/src/ranged.cpp b/src/ranged.cpp index 5ebf1c7b551be..787e2fed38ced 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -2212,7 +2212,7 @@ static void cycle_action( item &weap, const tripoint &pos ) if( weap.ammo_data() && weap.ammo_data()->ammo->casing ) { const itype_id casing = *weap.ammo_data()->ammo->casing; if( weap.has_flag( "RELOAD_EJECT" ) || weap.gunmod_find( "brass_catcher" ) ) { - weap.contents.push_back( item( casing ).set_flag( "CASING" ) ); + weap.contents.insert_legacy( item( casing ).set_flag( "CASING" ) ); } else { if( cargo.empty() ) { g->m.add_item_or_charges( eject, item( casing ) ); @@ -2231,7 +2231,7 @@ static void cycle_action( item &weap, const tripoint &pos ) item linkage( *mag->type->magazine->linkage, calendar::turn, 1 ); if( weap.gunmod_find( "brass_catcher" ) ) { linkage.set_flag( "CASING" ); - weap.contents.push_back( linkage ); + weap.contents.insert_legacy( linkage ); } else if( cargo.empty() ) { g->m.add_item_or_charges( eject, linkage ); } else { diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 43572a2f0a278..7dbd7a5a444f5 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -93,6 +93,7 @@ #include "requirements.h" #include "stats_tracker.h" #include "vpart_position.h" +#include "generic_factory.h" struct oter_type_t; struct mutation_branch; @@ -150,6 +151,47 @@ static void deserialize( weak_ptr_fast &obj, JsonIn &jsin ) // } } +void item_contents::serialize( JsonOut &json ) const +{ + if( !contents.empty() ) { + json.start_object(); + + json.member( "contents", contents ); + + json.end_object(); + } +} + +void item_contents::deserialize( JsonIn &jsin ) +{ + JsonObject data = jsin.get_object(); + data.read( "contents", contents ); +} + +void item_pocket::serialize( JsonOut &json ) const +{ + json.start_object(); + if( !contents.empty() ) { + json.member( "contents", contents ); + } + json.member( "pocket_type", data->type ); + + json.end_object(); +} + +void item_pocket::deserialize( JsonIn &jsin ) +{ + JsonObject data = jsin.get_object(); + data.read( "contents", contents ); + data.read( "pocket_type", _saved_type ); +} + +void pocket_data::deserialize( JsonIn &jsin ) +{ + JsonObject data = jsin.get_object(); + load( data ); +} + std::vector item::magazine_convert() { std::vector res; @@ -182,9 +224,9 @@ std::vector item::magazine_convert() calendar::turn, qty ); } - contents.erase( std::remove_if( contents.begin(), contents.end(), []( const item & e ) { + contents.remove_items_if( []( const item & e ) { return e.typeId() == "spare_mag" || e.typeId() == "clip" || e.typeId() == "clip2"; - } ), contents.end() ); + } ); return res; } @@ -196,11 +238,11 @@ std::vector item::magazine_convert() // give base item an appropriate magazine and add to that any ammo originally stored in base item if( !magazine_current() ) { - contents.push_back( mag ); + contents.insert_legacy( mag ); if( charges > 0 ) { ammo.charges = std::min( charges, mag.ammo_capacity() ); charges -= ammo.charges; - contents.back().contents.push_back( ammo ); + contents.legacy_back().contents.insert_legacy( ammo ); } } @@ -210,7 +252,7 @@ std::vector item::magazine_convert() if( spare_mag->charges > 0 ) { ammo.charges = std::min( spare_mag->charges, mag.ammo_capacity() ); charges += spare_mag->charges - ammo.charges; - res.back().contents.push_back( ammo ); + res.back().contents.insert_legacy( ammo ); } } @@ -221,9 +263,9 @@ std::vector item::magazine_convert() } // remove incompatible magazine mods - contents.erase( std::remove_if( contents.begin(), contents.end(), []( const item & e ) { + contents.remove_items_if( []( const item & e ) { return e.typeId() == "spare_mag" || e.typeId() == "clip" || e.typeId() == "clip2"; - } ), contents.end() ); + } ); // normalize the base item and mark it as converted charges = 0; @@ -2174,7 +2216,6 @@ void item::io( Archive &archive ) archive.io( "techniques", techniques, io::empty_default_tag() ); archive.io( "faults", faults, io::empty_default_tag() ); archive.io( "item_tags", item_tags, io::empty_default_tag() ); - archive.io( "contents", contents, io::empty_default_tag() ); archive.io( "components", components, io::empty_default_tag() ); archive.io( "specific_energy", specific_energy, -10 ); archive.io( "temperature", temperature, 0 ); @@ -2260,9 +2301,9 @@ void item::io( Archive &archive ) } // Fixes #16751 (items could have null contents due to faulty spawn code) - contents.erase( std::remove_if( contents.begin(), contents.end(), []( const item & cont ) { + contents.remove_items_if( []( const item & cont ) { return cont.is_null(); - } ), contents.end() ); + } ); // Sealed item migration: items with "unseals_into" set should always have contents if( contents.empty() && is_non_resealable_container() ) { @@ -2340,57 +2381,23 @@ static void migrate_toolmod( item &it ) it.item_tags.erase( "NO_UNLOAD" ); it.item_tags.erase( "RADIOACTIVE" ); it.item_tags.erase( "LEAK_DAM" ); - it.emplace_back( "battery_atomic" ); + it.contents.insert_legacy( item( "battery_atomic" ) ); } else if( it.item_tags.count( "DOUBLE_REACTOR" ) ) { it.item_tags.erase( "DOUBLE_REACTOR" ); it.item_tags.erase( "DOUBLE_AMMO" ); - it.emplace_back( "double_plutonium_core" ); + it.contents.insert_legacy( item( "double_plutonium_core" ) ); } else if( it.item_tags.count( "DOUBLE_AMMO" ) ) { it.item_tags.erase( "DOUBLE_AMMO" ); - it.emplace_back( "battery_compartment" ); + it.contents.insert_legacy( item( "battery_compartment" ) ); } else if( it.item_tags.count( "USE_UPS" ) ) { it.item_tags.erase( "USE_UPS" ); it.item_tags.erase( "NO_RELOAD" ); it.item_tags.erase( "NO_UNLOAD" ); - it.emplace_back( "battery_ups" ); - - } - } + it.contents.insert_legacy( item( "battery_ups" ) ); - // Fix fallout from #18797, which exponentially duplicates migrated toolmods - if( it.is_toolmod() ) { - // duplication would add an extra toolmod inside each toolmod on load; - // delete the nested copies - if( it.typeId() == "battery_atomic" || it.typeId() == "battery_compartment" || - it.typeId() == "battery_ups" || it.typeId() == "double_plutonium_core" ) { - // Be conservative and only delete nested mods of the same type - it.contents.remove_if( [&]( const item & cont ) { - return cont.typeId() == it.typeId(); - } ); - } - } - - if( it.is_tool() ) { - // duplication would add an extra toolmod inside each tool on load; - // delete the duplicates so there is only one copy of each toolmod - int n_atomic = 0; - int n_compartment = 0; - int n_ups = 0; - int n_plutonium = 0; - - // not safe to use remove_if with a stateful predicate - for( auto i = it.contents.begin(); i != it.contents.end(); ) { - if( ( i->typeId() == "battery_atomic" && ++n_atomic > 1 ) || - ( i->typeId() == "battery_compartment" && ++n_compartment > 1 ) || - ( i->typeId() == "battery_ups" && ++n_ups > 1 ) || - ( i->typeId() == "double_plutonium_core" && ++n_plutonium > 1 ) ) { - i = it.contents.erase( i ); - } else { - ++i; - } } } } @@ -2404,12 +2411,27 @@ void item::deserialize( JsonIn &jsin ) if( savegame_loading_version < 27 ) { legacy_fast_forward_time(); } + // migration code, used to be std::list + if( data.has_array( "contents" ) ) { + std::list item_list; + data.read( "contents", item_list ); + for( const item &it : item_list ) { + contents.insert_legacy( it ); + } + } else { + item_contents temp_contents; + data.read( "contents", temp_contents ); + contents.combine( temp_contents ); + } } void item::serialize( JsonOut &json ) const { io::JsonObjectOutputArchive archive( json ); const_cast( this )->io( archive ); + if( !contents.empty() ) { + json.member( "contents", contents ); + } } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/veh_interact.cpp b/src/veh_interact.cpp index 906aac3e87493..e8a3be335980a 100644 --- a/src/veh_interact.cpp +++ b/src/veh_interact.cpp @@ -1238,7 +1238,7 @@ bool veh_interact::do_refill( std::string &msg ) auto validate = [&]( const item & obj ) { if( pt.is_tank() ) { if( obj.is_container() && !obj.contents.empty() ) { - return pt.can_reload( obj.contents.front() ); + return pt.can_reload( obj.contents.legacy_front() ); } } else if( pt.is_fuel_store() ) { bool can_reload = pt.can_reload( obj ); @@ -1384,12 +1384,12 @@ bool veh_interact::overview( std::function enabl } } - for( auto &pt : veh->parts ) { + for( vehicle_part &pt : veh->parts ) { if( pt.is_tank() && pt.is_available() ) { auto details = []( const vehicle_part & pt, const catacurses::window & w, int y ) { if( pt.ammo_current() != "null" ) { std::string specials; - const item &it = pt.base.contents.front(); + const item &it = pt.base.contents.legacy_front(); // a space isn't actually needed in front of the tags here, // but item::display_name tags use a space so this prevents // needing *second* translation for the same thing with a @@ -1814,7 +1814,7 @@ bool veh_interact::do_siphon( std::string &msg ) auto act = [&]( const vehicle_part & pt ) { const item &base = pt.get_base(); const int idx = veh->find_part( base ); - item liquid( base.contents.back() ); + item liquid( base.contents.legacy_back() ); const int liq_charges = liquid.charges; if( liquid_handler::handle_liquid( liquid, nullptr, 1, nullptr, veh, idx ) ) { veh->drain( idx, liq_charges - liquid.charges ); @@ -2712,7 +2712,7 @@ void act_vehicle_siphon( vehicle *veh ) if( tank ) { const item &base = tank.get_base(); const int idx = veh->find_part( base ); - item liquid( base.contents.back() ); + item liquid( base.contents.legacy_back() ); const int liq_charges = liquid.charges; if( liquid_handler::handle_liquid( liquid, nullptr, 1, nullptr, veh, idx ) ) { veh->drain( idx, liq_charges - liquid.charges ); @@ -2922,12 +2922,11 @@ void veh_interact::complete_vehicle( player &p ) break; } - auto &src = p.activity.targets.front(); + item_location &src = p.activity.targets.front(); struct vehicle_part &pt = veh->parts[ vehicle_part ]; - std::list &contents = src->contents; - if( pt.is_tank() && src->is_container() && !contents.empty() ) { + if( pt.is_tank() && src->is_container() && !src->contents.empty() ) { - pt.base.fill_with( contents.front() ); + pt.base.fill_with( src->contents.legacy_front() ); src->on_contents_changed(); if( pt.ammo_remaining() != pt.ammo_capacity() ) { @@ -2938,8 +2937,8 @@ void veh_interact::complete_vehicle( player &p ) p.add_msg_if_player( m_good, _( "You completely refill the %1$s's %2$s." ), veh->name, pt.name() ); } - if( contents.front().charges == 0 ) { - contents.erase( contents.begin() ); + if( src->contents.legacy_front().charges == 0 ) { + src->contents.remove_item( src->contents.legacy_front() ); } else { p.add_msg_if_player( m_good, _( "There's some left over!" ) ); } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index fa61b48b283d3..27e9a2edd26f1 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -3376,8 +3376,8 @@ float vehicle::fuel_specific_energy( const itype_id &ftype ) const for( auto vehicle_part : parts ) { if( vehicle_part.is_tank() && vehicle_part.ammo_current() == ftype && vehicle_part.base.contents_made_of( LIQUID ) ) { - float energy = vehicle_part.base.contents.front().specific_energy; - float mass = to_gram( vehicle_part.base.contents.front().weight() ); + float energy = vehicle_part.base.contents.legacy_front().specific_energy; + float mass = to_gram( vehicle_part.base.contents.legacy_front().weight() ); total_energy += energy * mass; total_mass += mass; } @@ -5123,11 +5123,11 @@ cata::optional vehicle::add_item( int part, const item item itm_copy = itm; if( itm_copy.is_bucket_nonempty() ) { - for( auto &elem : itm_copy.contents ) { + for( auto &elem : itm_copy.contents.all_items() ) { g->m.add_item_or_charges( global_part_pos3( part ), elem ); } - itm_copy.contents.clear(); + itm_copy.contents.clear_items(); } const vehicle_stack::iterator new_pos = p.items.insert( itm_copy ); @@ -5227,7 +5227,7 @@ void vehicle::place_spawn_items() !e.magazine_current(); if( spawn_mag ) { - e.contents.emplace_back( e.magazine_default(), e.birthday() ); + e.contents.insert_legacy( item( e.magazine_default(), e.birthday() ) ); } if( spawn_ammo ) { e.ammo_set( e.ammo_default() ); diff --git a/src/vehicle_part.cpp b/src/vehicle_part.cpp index 65b38dcb691d7..2796f6095d76b 100644 --- a/src/vehicle_part.cpp +++ b/src/vehicle_part.cpp @@ -196,7 +196,7 @@ itype_id vehicle_part::ammo_current() const } if( is_tank() && !base.contents.empty() ) { - return base.contents.front().typeId(); + return base.contents.legacy_front().typeId(); } if( is_fuel_store( false ) || is_turret() ) { @@ -222,7 +222,7 @@ int vehicle_part::ammo_capacity() const int vehicle_part::ammo_remaining() const { if( is_tank() ) { - return base.contents.empty() ? 0 : base.contents.back().charges; + return base.contents.empty() ? 0 : base.contents.legacy_back().charges; } if( is_fuel_store( false ) || is_turret() ) { @@ -238,10 +238,11 @@ int vehicle_part::ammo_set( const itype_id &ammo, int qty ) // We often check if ammo is set to see if tank is empty, if qty == 0 don't set ammo if( is_tank() && liquid->phase >= LIQUID && qty != 0 ) { - base.contents.clear(); + base.contents.clear_items(); const auto stack = units::legacy_volume_factor / std::max( liquid->stack_size, 1 ); const int limit = units::from_milliliter( ammo_capacity() ) / stack; - base.emplace_back( ammo, calendar::turn, qty > 0 ? std::min( qty, limit ) : limit ); + base.contents.insert_legacy( item( ammo, calendar::turn, qty > 0 ? std::min( qty, + limit ) : limit ) ); return qty; } @@ -260,7 +261,7 @@ int vehicle_part::ammo_set( const itype_id &ammo, int qty ) void vehicle_part::ammo_unset() { if( is_tank() ) { - base.contents.clear(); + base.contents.clear_items(); } else if( is_fuel_store() ) { base.ammo_unset(); } @@ -270,10 +271,10 @@ int vehicle_part::ammo_consume( int qty, const tripoint &pos ) { if( is_tank() && !base.contents.empty() ) { const int res = std::min( ammo_remaining(), qty ); - item &liquid = base.contents.back(); + item &liquid = base.contents.legacy_back(); liquid.charges -= res; if( liquid.charges == 0 ) { - base.contents.clear(); + base.contents.clear_items(); } return res; } @@ -286,7 +287,7 @@ double vehicle_part::consume_energy( const itype_id &ftype, double energy_j ) return 0.0f; } - item &fuel = base.contents.back(); + item &fuel = base.contents.legacy_back(); if( fuel.typeId() == ftype ) { assert( fuel.is_fuel() ); // convert energy density in MJ/L to J/ml @@ -299,7 +300,7 @@ double vehicle_part::consume_energy( const itype_id &ftype, double energy_j ) } if( charges_to_use > fuel.charges ) { charges_to_use = fuel.charges; - base.contents.clear(); + base.contents.clear_items(); } else { fuel.charges -= charges_to_use; } diff --git a/src/visitable.cpp b/src/visitable.cpp index 607c2589c9dc9..b535c19a2f821 100644 --- a/src/visitable.cpp +++ b/src/visitable.cpp @@ -364,10 +364,8 @@ static VisitResponse visit_internal( const std::functioncontents ) { - if( visit_internal( func, &e, node ) == VisitResponse::ABORT ) { - return VisitResponse::ABORT; - } + if( node->contents.visit_contents( func, parent ) == VisitResponse::ABORT ) { + return VisitResponse::ABORT; } /* intentional fallthrough */ @@ -379,6 +377,34 @@ static VisitResponse visit_internal( const std::function + &func, item *parent ) +{ + for( item_pocket &pocket : contents ) { + switch( pocket.visit_contents( func, parent ) ) { + case VisitResponse::ABORT: + return VisitResponse::ABORT; + default: + break; + } + } + return VisitResponse::NEXT; +} + +VisitResponse item_pocket::visit_contents( const std::function + &func, item *parent ) +{ + for( item &e : contents ) { + switch( visit_internal( func, &e, parent ) ) { + case VisitResponse::ABORT: + return VisitResponse::ABORT; + default: + break; + } + } + return VisitResponse::NEXT; +} + /** @relates visitable */ template <> VisitResponse visitable::visit_items( @@ -507,22 +533,6 @@ item visitable::remove_item( item &it ) } } -static void remove_internal( const std::function &filter, item &node, int &count, - std::list &res ) -{ - for( auto it = node.contents.begin(); it != node.contents.end(); ) { - if( filter( *it ) ) { - res.splice( res.end(), node.contents, it++ ); - if( --count == 0 ) { - return; - } - } else { - remove_internal( filter, *it, count, res ); - ++it; - } - } -} - /** @relates visitable */ template <> std::list visitable::remove_items_with( const std::function @@ -536,7 +546,7 @@ std::list visitable::remove_items_with( const std::functioncontents.remove_internal( filter, count, res ); return res; } @@ -570,7 +580,7 @@ std::list visitable::remove_items_with( const } } else { - remove_internal( filter, *istack_iter, count, res ); + istack_iter->contents.remove_internal( filter, count, res ); ++istack_iter; } } @@ -617,7 +627,7 @@ std::list visitable::remove_items_with( const return res; } } else { - remove_internal( filter, *iter, count, res ); + iter->contents.remove_internal( filter, count, res ); if( count == 0 ) { return res; } @@ -630,7 +640,7 @@ std::list visitable::remove_items_with( const res.push_back( ch->remove_weapon() ); count--; } else { - remove_internal( filter, ch->weapon, count, res ); + ch->weapon.contents.remove_internal( filter, count, res ); } return res; @@ -675,7 +685,7 @@ std::list visitable::remove_items_with( const return res; } } else { - remove_internal( filter, *iter, count, res ); + iter->contents.remove_internal( filter, count, res ); if( count == 0 ) { return res; } @@ -733,7 +743,7 @@ std::list visitable::remove_items_with( const return res; } } else { - remove_internal( filter, *iter, count, res ); + iter->contents.remove_internal( filter, count, res ); if( count == 0 ) { return res; } diff --git a/src/weather.cpp b/src/weather.cpp index 2cdbac5932c84..3bea3d2e28fcf 100644 --- a/src/weather.cpp +++ b/src/weather.cpp @@ -212,7 +212,7 @@ void item::add_rain_to_container( bool acid, int charges ) put_in( ret ); } else { // The container already has a liquid. - item &liq = contents.front(); + item &liq = contents.legacy_front(); int orig = liq.charges; int added = std::min( charges, capa ); if( capa > 0 ) { @@ -235,7 +235,7 @@ void item::add_rain_to_container( bool acid, int charges ) const bool transmute = x_in_y( 2 * added, liq.charges ); if( transmute ) { - contents.front() = item( "water_acid_weak", calendar::turn, liq.charges ); + contents.legacy_front() = item( "water_acid_weak", calendar::turn, liq.charges ); } else if( liq.typeId() == "water" ) { // The container has water, and the acid rain didn't turn it // into weak acid. Poison the water instead, assuming 1 diff --git a/tests/comestible_test.cpp b/tests/comestible_test.cpp index 4d51d5285829c..dd1e2f12ec484 100644 --- a/tests/comestible_test.cpp +++ b/tests/comestible_test.cpp @@ -107,7 +107,7 @@ static all_stats run_stats( const std::vector> &permutati static item food_or_food_container( const item &it ) { - return it.is_food_container() ? it.contents.front() : it; + return it.is_food_container() ? it.contents.legacy_front() : it; } TEST_CASE( "recipe_permutations", "[recipe]" ) diff --git a/tests/crafting_test.cpp b/tests/crafting_test.cpp index cd1407aebb724..70e84e6eb1247 100644 --- a/tests/crafting_test.cpp +++ b/tests/crafting_test.cpp @@ -323,12 +323,22 @@ static int actually_test_craft( const recipe_id &rid, const std::vector &t return turns; } +static item tool_with_battery( const itype_id &tool, const int battery_charges ) +{ + item it( tool ); + item mag( it.magazine_default() ); + item ammo( mag.ammo_default(), -1, battery_charges ); + mag.contents.insert_legacy( ammo ); + it.contents.insert_legacy( mag ); + return it; +} + TEST_CASE( "charge_handling", "[crafting]" ) { SECTION( "carver" ) { std::vector tools; - tools.emplace_back( "hotplate", -1, 20 ); - tools.emplace_back( "soldering_iron", -1, 20 ); + tools.push_back( tool_with_battery( "hotplate", 20 ) ); + tools.push_back( tool_with_battery( "soldering_iron", 20 ) ); tools.insert( tools.end(), 10, item( "solder_wire" ) ); tools.emplace_back( "screwdriver" ); tools.emplace_back( "mold_plastic" ); @@ -345,10 +355,8 @@ TEST_CASE( "charge_handling", "[crafting]" ) } SECTION( "carver_split_charges" ) { std::vector tools; - tools.emplace_back( "hotplate", -1, 5 ); - tools.emplace_back( "hotplate", -1, 5 ); - tools.emplace_back( "soldering_iron", -1, 5 ); - tools.emplace_back( "soldering_iron", -1, 5 ); + tools.insert( tools.end(), 2, tool_with_battery( "hotplate", 5 ) ); + tools.insert( tools.end(), 2, tool_with_battery( "soldering_iron", 5 ) ); tools.insert( tools.end(), 10, item( "solder_wire" ) ); tools.emplace_back( "screwdriver" ); tools.emplace_back( "mold_plastic" ); @@ -365,12 +373,12 @@ TEST_CASE( "charge_handling", "[crafting]" ) } SECTION( "UPS_modded_carver" ) { std::vector tools; - item hotplate( "hotplate", -1, 0 ); - hotplate.contents.emplace_back( "battery_ups" ); + item hotplate( "hotplate" ); + hotplate.contents.insert_legacy( item( "battery_ups" ) ); tools.push_back( hotplate ); - item soldering_iron( "soldering_iron", -1, 0 ); + item soldering_iron( "soldering_iron" ); tools.insert( tools.end(), 10, item( "solder_wire" ) ); - soldering_iron.contents.emplace_back( "battery_ups" ); + soldering_iron.contents.insert_legacy( item( "battery_ups" ) ); tools.push_back( soldering_iron ); tools.emplace_back( "screwdriver" ); tools.emplace_back( "mold_plastic" ); @@ -380,7 +388,7 @@ TEST_CASE( "charge_handling", "[crafting]" ) tools.emplace_back( "motor_tiny" ); tools.emplace_back( "power_supply" ); tools.emplace_back( "scrap" ); - tools.emplace_back( "UPS_off", -1, 500 ); + tools.push_back( tool_with_battery( "UPS_off", 500 ) ); actually_test_craft( recipe_id( "carver_off" ), tools, INT_MAX ); CHECK( get_remaining_charges( "hotplate" ) == 0 ); @@ -389,12 +397,12 @@ TEST_CASE( "charge_handling", "[crafting]" ) } SECTION( "UPS_modded_carver_missing_charges" ) { std::vector tools; - item hotplate( "hotplate", -1, 0 ); - hotplate.contents.emplace_back( "battery_ups" ); + item hotplate( "hotplate" ); + hotplate.contents.insert_legacy( item( "battery_ups" ) ); tools.push_back( hotplate ); - item soldering_iron( "soldering_iron", -1, 0 ); + item soldering_iron( "soldering_iron" ); tools.insert( tools.end(), 10, item( "solder_wire" ) ); - soldering_iron.contents.emplace_back( "battery_ups" ); + soldering_iron.contents.insert_legacy( item( "battery_ups" ) ); tools.push_back( soldering_iron ); tools.emplace_back( "screwdriver" ); tools.emplace_back( "mold_plastic" ); @@ -404,7 +412,7 @@ TEST_CASE( "charge_handling", "[crafting]" ) tools.emplace_back( "motor_tiny" ); tools.emplace_back( "power_supply" ); tools.emplace_back( "scrap" ); - tools.emplace_back( "UPS_off", -1, 10 ); + tools.push_back( tool_with_battery( "UPS_off", 10 ) ); prep_craft( recipe_id( "carver_off" ), tools, false ); } @@ -414,9 +422,9 @@ TEST_CASE( "tool_use", "[crafting]" ) { SECTION( "clean_water" ) { std::vector tools; - tools.emplace_back( "hotplate", -1, 20 ); + tools.push_back( tool_with_battery( "hotplate", 20 ) ); item plastic_bottle( "bottle_plastic" ); - plastic_bottle.contents.emplace_back( "water", -1, 2 ); + plastic_bottle.contents.insert_legacy( item( "water", -1, 2 ) ); tools.push_back( plastic_bottle ); tools.emplace_back( "pot" ); @@ -425,14 +433,14 @@ TEST_CASE( "tool_use", "[crafting]" ) } SECTION( "clean_water_in_occupied_cooking_vessel" ) { std::vector tools; - tools.emplace_back( "hotplate", -1, 20 ); + tools.push_back( tool_with_battery( "hotplate", 20 ) ); item plastic_bottle( "bottle_plastic" ); - plastic_bottle.contents.emplace_back( "water", -1, 2 ); + plastic_bottle.contents.insert_legacy( item( "water", -1, 2 ) ); tools.push_back( plastic_bottle ); item jar( "jar_glass" ); // If it's not watertight the water will spill. REQUIRE( jar.is_watertight_container() ); - jar.contents.emplace_back( "water", -1, 2 ); + jar.contents.insert_legacy( item( "water", -1, 2 ) ); tools.push_back( jar ); prep_craft( recipe_id( "water_clean" ), tools, false ); diff --git a/tests/item_test.cpp b/tests/item_test.cpp index 4d9ded0ad6fde..52651f308f618 100644 --- a/tests/item_test.cpp +++ b/tests/item_test.cpp @@ -45,7 +45,7 @@ TEST_CASE( "gun_layer", "[item]" ) item gun( "win70" ); item mod( "shoulder_strap" ); CHECK( gun.is_gunmod_compatible( mod ).success() ); - gun.contents.push_back( mod ); + gun.contents.insert_legacy( mod ); CHECK( gun.get_layer() == BELTED_LAYER ); } diff --git a/tests/new_character_test.cpp b/tests/new_character_test.cpp index cc4bcf520e582..437c5b4f1e5c6 100644 --- a/tests/new_character_test.cpp +++ b/tests/new_character_test.cpp @@ -145,7 +145,8 @@ TEST_CASE( "starting_items" ) g->u.male = i == 0; std::list items = prof->items( g->u.male, traits ); for( const item &it : items ) { - items.insert( items.begin(), it.contents.begin(), it.contents.end() ); + std::list all_items{ it.contents.all_items() }; + items.insert( items.begin(), all_items.begin(), all_items.end() ); } for( const item &it : items ) { diff --git a/tests/ranged_balance_test.cpp b/tests/ranged_balance_test.cpp index fb53f8686875c..c871b09f9566d 100644 --- a/tests/ranged_balance_test.cpp +++ b/tests/ranged_balance_test.cpp @@ -96,7 +96,7 @@ static void arm_shooter( npc &shooter, const std::string &gun_type, gun.reload( shooter, item_location( shooter, &magazine ), magazine.ammo_capacity() ); } for( const auto &mod : mods ) { - gun.contents.push_back( item( itype_id( mod ) ) ); + gun.contents.insert_legacy( item( itype_id( mod ) ) ); } shooter.wield( gun ); } diff --git a/tests/reload_option_test.cpp b/tests/reload_option_test.cpp index 4064ee57b4b2c..185615bdd13ac 100644 --- a/tests/reload_option_test.cpp +++ b/tests/reload_option_test.cpp @@ -27,7 +27,7 @@ TEST_CASE( "revolver_reload_option", "[reload],[reload_option],[gun]" ) ammo_location ); CHECK( speedloader_option.qty() == speedloader.ammo_capacity() ); - speedloader.contents.push_back( ammo ); + speedloader.contents.insert_legacy( ammo ); item_location speedloader_location( dummy, &speedloader ); const item::reload_option gun_speedloader_option( &dummy, &gun, &gun, speedloader_location ); @@ -46,7 +46,7 @@ TEST_CASE( "magazine_reload_option", "[reload],[reload_option],[gun]" ) ammo_location ); CHECK( magazine_option.qty() == magazine.ammo_capacity() ); - magazine.contents.push_back( ammo ); + magazine.contents.insert_legacy( ammo ); item_location magazine_location( dummy, &magazine ); item &gun = dummy.i_add( item( "glock_19", 0, 0 ) ); const item::reload_option gun_option( &dummy, &gun, &gun, magazine_location ); @@ -68,7 +68,7 @@ TEST_CASE( "belt_reload_option", "[reload],[reload_option],[gun]" ) const item::reload_option belt_option( &dummy, &belt, &belt, ammo_location ); CHECK( belt_option.qty() == belt.ammo_capacity() ); - belt.contents.push_back( ammo ); + belt.contents.insert_legacy( ammo ); item_location belt_location( dummy, &ammo ); item &gun = dummy.i_add( item( "m134", 0, 0 ) ); diff --git a/tests/reloading_test.cpp b/tests/reloading_test.cpp index 09d8aa5da8d79..0bd190ae63bb3 100644 --- a/tests/reloading_test.cpp +++ b/tests/reloading_test.cpp @@ -211,7 +211,7 @@ TEST_CASE( "automatic_reloading_action", "[reload],[gun]" ) THEN( "the associated magazine is reloaded" ) { CHECK( mag.ammo_remaining() > 0 ); - CHECK( mag.contents.front().type == ammo.type ); + CHECK( mag.contents.legacy_front().type == ammo.type ); } WHEN( "the player triggers auto reload again" ) { g->reload_weapon( false ); @@ -243,7 +243,7 @@ TEST_CASE( "automatic_reloading_action", "[reload],[gun]" ) THEN( "the associated magazine is reloaded" ) { CHECK( mag.ammo_remaining() > 0 ); - CHECK( mag.contents.front().type == ammo.type ); + CHECK( mag.contents.legacy_front().type == ammo.type ); } WHEN( "the player triggers auto reload again" ) { g->reload_weapon( false ); @@ -260,7 +260,7 @@ TEST_CASE( "automatic_reloading_action", "[reload],[gun]" ) THEN( "the second associated magazine is reloaded" ) { CHECK( mag2.ammo_remaining() > 0 ); - CHECK( mag2.contents.front().type == ammo.type ); + CHECK( mag2.contents.legacy_front().type == ammo.type ); } WHEN( "the player triggers auto reload again" ) { g->reload_weapon( false ); diff --git a/tests/vehicle_interact_test.cpp b/tests/vehicle_interact_test.cpp index f0da8b1a662f8..e60688ddb1791 100644 --- a/tests/vehicle_interact_test.cpp +++ b/tests/vehicle_interact_test.cpp @@ -70,7 +70,7 @@ TEST_CASE( "repair_vehicle_part" ) SECTION( "UPS_modded_welder" ) { std::vector tools; item welder( "welder", -1, 0 ); - welder.contents.emplace_back( "battery_ups" ); + welder.contents.insert_legacy( item( "battery_ups" ) ); tools.push_back( welder ); tools.emplace_back( "UPS_off", -1, 500 ); tools.emplace_back( "goggles_welding" ); @@ -90,7 +90,7 @@ TEST_CASE( "repair_vehicle_part" ) SECTION( "UPS_modded_welder_missing_charges" ) { std::vector tools; item welder( "welder", -1, 0 ); - welder.contents.emplace_back( "battery_ups" ); + welder.contents.insert_legacy( item( "battery_ups" ) ); tools.push_back( welder ); tools.emplace_back( "UPS_off", -1, 5 ); tools.emplace_back( "goggles_welding" ); diff --git a/tests/visitable_remove_test.cpp b/tests/visitable_remove_test.cpp index 2b8fce5a50d07..77056d357ceaa 100644 --- a/tests/visitable_remove_test.cpp +++ b/tests/visitable_remove_test.cpp @@ -75,8 +75,8 @@ TEST_CASE( "visitable_remove", "[visitable]" ) item temp_liquid( liquid_id ); item obj = temp_liquid.in_container( temp_liquid.type->default_container.value_or( "null" ) ); - REQUIRE( obj.contents.size() == 1 ); - REQUIRE( obj.contents.front().typeId() == liquid_id ); + REQUIRE( obj.contents.legacy_size() == 1 ); + REQUIRE( obj.contents.legacy_front().typeId() == liquid_id ); GIVEN( "A player with several bottles of water" ) { for( int i = 0; i != count; ++i ) { @@ -106,7 +106,7 @@ TEST_CASE( "visitable_remove", "[visitable]" ) } AND_THEN( "the removed items all contain water" ) { CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) { - return e.contents.size() == 1 && e.contents.front().typeId() == liquid_id; + return e.contents.legacy_size() == 1 && e.contents.legacy_front().typeId() == liquid_id; } ) ); } } @@ -133,7 +133,7 @@ TEST_CASE( "visitable_remove", "[visitable]" ) } AND_THEN( "the removed items all contained water" ) { CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) { - return e.contents.size() == 1 && e.contents.front().typeId() == liquid_id; + return e.contents.legacy_size() == 1 && e.contents.legacy_front().typeId() == liquid_id; } ) ); } } @@ -169,7 +169,7 @@ TEST_CASE( "visitable_remove", "[visitable]" ) } AND_THEN( "the removed items all contain water" ) { CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) { - return e.contents.size() == 1 && e.contents.front().typeId() == liquid_id; + return e.contents.legacy_size() == 1 && e.contents.legacy_front().typeId() == liquid_id; } ) ); } } @@ -186,8 +186,8 @@ TEST_CASE( "visitable_remove", "[visitable]" ) REQUIRE( p.weapon.typeId() == container_id ); AND_THEN( "the remaining water is contained by the currently wielded bottle" ) { - REQUIRE( p.weapon.contents.size() == 1 ); - REQUIRE( p.weapon.contents.front().typeId() == liquid_id ); + REQUIRE( p.weapon.contents.legacy_size() == 1 ); + REQUIRE( p.weapon.contents.legacy_front().typeId() == liquid_id ); } } } @@ -205,7 +205,7 @@ TEST_CASE( "visitable_remove", "[visitable]" ) } AND_THEN( "the removed items all contained water" ) { CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) { - return e.contents.size() == 1 && e.contents.front().typeId() == liquid_id; + return e.contents.legacy_size() == 1 && e.contents.legacy_front().typeId() == liquid_id; } ) ); } } @@ -214,8 +214,8 @@ TEST_CASE( "visitable_remove", "[visitable]" ) WHEN( "a hip flask containing water is worn" ) { item obj( worn_id ); - obj.emplace_back( liquid_id, calendar::turn, - temp_liquid.charges_per_volume( obj.get_container_capacity() ) ); + obj.contents.insert_legacy( item( liquid_id, calendar::turn, + temp_liquid.charges_per_volume( obj.get_container_capacity() ) ) ); p.wear_item( obj ); REQUIRE( count_items( p, container_id ) == count ); @@ -244,8 +244,8 @@ TEST_CASE( "visitable_remove", "[visitable]" ) REQUIRE( p.is_worn( *found[0] ) ); AND_THEN( "the hip flask contains water" ) { - REQUIRE( found[0]->contents.size() == 1 ); - REQUIRE( found[0]->contents.front().typeId() == liquid_id ); + REQUIRE( found[0]->contents.legacy_size() == 1 ); + REQUIRE( found[0]->contents.legacy_front().typeId() == liquid_id ); } } } @@ -339,7 +339,7 @@ TEST_CASE( "visitable_remove", "[visitable]" ) } AND_THEN( "the removed items all contain water" ) { CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) { - return e.contents.size() == 1 && e.contents.front().typeId() == liquid_id; + return e.contents.legacy_size() == 1 && e.contents.legacy_front().typeId() == liquid_id; } ) ); } } @@ -366,7 +366,7 @@ TEST_CASE( "visitable_remove", "[visitable]" ) } AND_THEN( "the removed items all contained water" ) { CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) { - return e.contents.size() == 1 && e.contents.front().typeId() == liquid_id; + return e.contents.legacy_size() == 1 && e.contents.legacy_front().typeId() == liquid_id; } ) ); } } @@ -399,7 +399,7 @@ TEST_CASE( "visitable_remove", "[visitable]" ) } AND_THEN( "the removed items all contained water" ) { CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) { - return e.contents.size() == 1 && e.contents.front().typeId() == liquid_id; + return e.contents.legacy_size() == 1 && e.contents.legacy_front().typeId() == liquid_id; } ) ); } } @@ -453,7 +453,7 @@ TEST_CASE( "visitable_remove", "[visitable]" ) } AND_THEN( "the removed items all contain water" ) { CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) { - return e.contents.size() == 1 && e.contents.front().typeId() == liquid_id; + return e.contents.legacy_size() == 1 && e.contents.legacy_front().typeId() == liquid_id; } ) ); } } @@ -480,7 +480,7 @@ TEST_CASE( "visitable_remove", "[visitable]" ) } AND_THEN( "the removed items all contained water" ) { CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) { - return e.contents.size() == 1 && e.contents.front().typeId() == liquid_id; + return e.contents.legacy_size() == 1 && e.contents.legacy_front().typeId() == liquid_id; } ) ); } }