diff --git a/src/npctalk.cpp b/src/npctalk.cpp index 6b4fa539a37f6..71f91e5c71ddc 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -104,6 +104,8 @@ static const efftype_id effect_riding( "riding" ); static const efftype_id effect_sleep( "sleep" ); static const efftype_id effect_under_operation( "under_operation" ); +static const flag_id json_flag_NO_UNLOAD( "NO_UNLOAD" ); + static const itype_id fuel_type_animal( "animal" ); static const itype_id itype_foodperson_mask( "foodperson_mask" ); static const itype_id itype_foodperson_mask_on( "foodperson_mask_on" ); @@ -3814,67 +3816,143 @@ void talk_effect_fun_t::set_bulk_trade_accept( const JsonObject &jo, std::string } else { dov_quantity.min.dbl_val = -1; } + bool is_trade = member == "u_bulk_trade_accept" || member == "npc_bulk_trade_accept"; function = [is_trade, is_npc, dov_quantity]( dialogue & d ) { talker *seller = d.actor( is_npc ); talker *buyer = d.actor( !is_npc ); - item tmp( d.cur_item ); - int quantity = dov_quantity.evaluate( d ); - int seller_has = 0; - if( tmp.count_by_charges() ) { - seller_has = seller->charges_of( d.cur_item ); - } else { - seller_has = seller->items_with( [&tmp]( const item & e ) { - return tmp.type == e.type; - } ).size(); + itype_id traded_itype_id = d.cur_item; + int number_to_transfer = dov_quantity.evaluate( d ); + + // Use a set! No duplicates! Duplicate pointers will be bad(crash) when we try to remove the item later. + std::set items_to_transfer; + + seller->get_character()->visit_items( [&]( item * visited, item * parent ) { + if( visited->typeId() == traded_itype_id ) { + if( parent && ( parent->all_pockets_sealed() || visited->made_of( phase_id::LIQUID ) || + parent->has_flag( json_flag_NO_UNLOAD ) ) ) { + items_to_transfer.emplace( parent ); + } else { + items_to_transfer.emplace( visited ); + } + } + return VisitResponse::NEXT; + } ); + + if( seller->get_character()->is_avatar() ) { + std::string warning = _( "Really continue? You will hand over the following items:" ); + int num_warnings = 0; + for( item *checked_item : items_to_transfer ) { + warning += "\n" + checked_item->tname(); + num_warnings++; + if( !is_trade && num_warnings >= number_to_transfer ) { + break; // bulk donate has a variable number to transfer but bulk trade always takes everything + } + } + if( !query_yn( warning ) ) { + return; + } } - seller_has = ( quantity == -1 ) ? seller_has : std::min( seller_has, quantity ); - tmp.charges = seller_has; + if( is_trade ) { + const int npc_debt = d.actor( true )->debt(); - int price = total_price( *seller, d.cur_item ) * ( is_npc ? -1 : 1 ) + npc_debt; - if( d.actor( true )->get_faction() && !d.actor( true )->get_faction()->currency.is_empty() ) { - const itype_id &pay_in = d.actor( true )->get_faction()->currency; - item pay( pay_in ); - const int value = d.actor( true )->value( pay ); - if( value > 0 ) { - int required = price / value; - int buyer_has = required; - if( is_npc ) { - buyer_has = std::min( buyer_has, buyer->charges_of( pay_in ) ); - buyer->use_charges( pay_in, buyer_has ); - } else { - if( buyer_has == 1 ) { - //~ %1%s is the NPC name, %2$s is an item - popup( _( "%1$s gives you a %2$s." ), buyer->disp_name(), - pay.tname() ); - } else if( buyer_has > 1 ) { - //~ %1%s is the NPC name, %2$d is a number of items, %3$s are items - popup( _( "%1$s gives you %2$d %3$s." ), buyer->disp_name(), buyer_has, - pay.tname() ); - } - } - for( int i = 0; i < buyer_has; i++ ) { - seller->i_add( pay ); - price -= value; - } - } else { - debugmsg( "%s pays in bulk_trade_accept with faction currency worth 0!", - d.actor( true )->disp_name() ); - } - } else { + int price = total_price( *seller, traded_itype_id ) * ( is_npc ? -1 : 1 ) + npc_debt; + if( d.actor( true )->get_faction() && d.actor( true )->get_faction()->currency.is_empty() ) { debugmsg( "%s has no faction currency to pay with in bulk_trade_accept!", d.actor( true )->disp_name() ); + return; // Fatal, no reasonable way to recover. } + + // This can be very confusing, so for the benefit of future contributors I have elected to make the variable names + // terribly verbose. + const itype_id ¤cy_type = d.actor( true )->get_faction()->currency; + item one_currency_unit( currency_type ); + const int int_value_of_one_currency_unit = d.actor( true )->value( one_currency_unit ); + if( int_value_of_one_currency_unit <= 0 ) { + debugmsg( "%s pays in bulk_trade_accept with faction currency worth %d!", + d.actor( true )->disp_name(), int_value_of_one_currency_unit ); + return; // Fatal, no reasonable way to recover. + } + + int num_transferred_currency_units = price / int_value_of_one_currency_unit; + if( is_npc ) { + num_transferred_currency_units = std::min( num_transferred_currency_units, + buyer->charges_of( currency_type ) ); + buyer->use_charges( currency_type, num_transferred_currency_units ); + } else { + + if( num_transferred_currency_units == 1 ) { + //~ %1%s is the NPC name, %2$s is an item + popup( _( "%1$s gives you a %2$s." ), buyer->disp_name(), + one_currency_unit.tname() ); + } else if( num_transferred_currency_units > 1 ) { + //~ %1%s is the NPC name, %2$d is a number of items, %3$s are items + popup( _( "%1$s gives you %2$d %3$s." ), buyer->disp_name(), num_transferred_currency_units, + one_currency_unit.tname() ); + } + + } + + // Currency is actually transferred + for( int i = 0; i < num_transferred_currency_units; i++ ) { + seller->i_add( one_currency_unit ); + price -= int_value_of_one_currency_unit; + } + + // Tally up debts d.actor( true )->add_debt( -npc_debt ); d.actor( true )->add_debt( price ); + + // Now let's actually transfer the items! + for( item *transferred : items_to_transfer ) { + item transfer_copy( *transferred ); + buyer->i_add_or_drop( transfer_copy ); + + auto remove_items_filter = [&]( const item & it ) { + return &it == const_cast( transferred ); + }; + + seller->remove_items_with( remove_items_filter ); + } + + + } else { // u_bulk_donate + + int number_transferred = 0; + for( item *transferred : items_to_transfer ) { + item transfer_copy( *transferred ); + buyer->i_add_or_drop( transfer_copy ); + + // Count now, while the item still exists + if( transferred->typeId() != traded_itype_id ) { + // Container traded over + if( item( traded_itype_id ).count_by_charges() ) { + number_transferred += transferred->charges_of( traded_itype_id ); + } else { + number_transferred += transferred->amount_of( traded_itype_id ); + } + } else { + // Loose items traded over + if( transferred->count_by_charges() ) { + number_transferred += transferred->charges; + } else { + number_transferred++; + } + } + + auto remove_items_filter = [&]( const item & it ) { + return &it == const_cast( transferred ); + }; + + seller->remove_items_with( remove_items_filter ); + + if( number_transferred >= number_to_transfer ) { + return; + } + } + } - if( tmp.count_by_charges() ) { - seller->use_charges( d.cur_item, seller_has ); - } else { - seller->use_amount( d.cur_item, seller_has ); - } - buyer->i_add( tmp ); }; }