diff --git a/src/handle_liquid.cpp b/src/handle_liquid.cpp index 7334df31fc219..68d311229e241 100644 --- a/src/handle_liquid.cpp +++ b/src/handle_liquid.cpp @@ -124,48 +124,19 @@ bool consume_liquid( item &liquid, const int radius, const item *const avoid ) return original_charges != liquid.charges; } -bool handle_liquid_from_ground( const map_stack::iterator &on_ground, - const tripoint &pos, - const int radius ) +bool handle_all_liquids_from_container( item_location &container, int radius ) { - // TODO: not all code paths on handle_liquid consume move points, fix that. - handle_liquid( *on_ground, nullptr, radius, &pos ); - if( on_ground->charges > 0 ) { - return false; - } - get_map().i_at( pos ).erase( on_ground ); - return true; -} - -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. - const int old_charges = in_container->charges; - handle_liquid( *in_container, &container, radius ); - if( in_container->charges != old_charges ) { - container.on_contents_changed(); - } - - return in_container->charges <= 0; -} - -bool handle_liquid_from_container( item &container, int radius ) -{ - std::vector remove; bool handled = false; - for( item *contained : container.all_items_top() ) { - if( handle_liquid_from_container( contained, container, radius ) ) { - remove.push_back( contained ); + for( item *contained : container->all_items_top() ) { + item_location loc( container, contained ); + if( handle_liquid( loc, &*container, radius ) ) { handled = true; } } - for( item *contained : remove ) { - container.remove_item( *contained ); - } return handled; } +// todo: remove in favor of the item_location version static bool get_liquid_target( item &liquid, const item *const source, const int radius, const tripoint *const source_pos, const vehicle *const source_veh, @@ -348,13 +319,158 @@ static bool get_liquid_target( item &liquid, const item *const source, const int return true; } -bool perform_liquid_transfer( item &liquid, const tripoint *const source_pos, - vehicle *const source_veh, const int part_num, - const monster *const /*source_mon*/, liquid_dest_opt &target ) +static bool get_liquid_target( item_location &liquid, const item *const source, const int radius, + liquid_dest_opt &target ) +{ + const tripoint *source_pos = nullptr; + const vehicle *source_veh = nullptr; + const monster *source_mon = nullptr; + + switch( liquid.where() ) { + case item_location::type::container: + // intentionally empty + break; + case item_location::type::map: { + tripoint pos = liquid.position(); + source_pos = &pos; + break; + } + case item_location::type::vehicle: + source_veh = &liquid.veh_cursor()->veh; + break; + default: + debugmsg( "Tried to get liquid target %s from invalid location %s", liquid->display_name(), + liquid.where() ); + return false; + } + + return get_liquid_target( *liquid, source, radius, source_pos, source_veh, source_mon, target ); +} + +static bool handle_keg_or_ground_target( Character &player_character, item &liquid, + liquid_dest_opt &target, const std::function &create_activity ) +{ + if( create_activity() ) { + serialize_liquid_target( player_character.activity, target.pos ); + } else { + if( target.dest_opt == LD_KEG ) { + iexamine::pour_into_keg( tripoint_bub_ms( target.pos ), liquid ); + } else { + get_map().add_item_or_charges( target.pos, liquid ); + liquid.charges = 0; + } + player_character.mod_moves( -100 ); + } + return true; +} + +static bool handle_item_target( Character &player_character, item &liquid, liquid_dest_opt &target, + const std::function &create_activity ) +{ + // Currently activities can only store item position in the players inventory, + // not on ground or similar. TODO: implement storing arbitrary container locations. + if( target.item_loc && create_activity() ) { + serialize_liquid_target( player_character.activity, target.item_loc ); + } else if( player_character.pour_into( target.item_loc, liquid, true ) ) { + target.item_loc.make_active(); + player_character.mod_moves( -100 ); + } + return true; +} + +static bool handle_vehicle_target( Character &player_character, item &liquid, + liquid_dest_opt &target, const std::function &create_activity ) +{ + if( target.veh == nullptr ) { + return false; + } + auto sel = [&]( const vehicle_part & pt ) { + return pt.is_tank() && pt.can_reload( liquid ); + }; + + const units::volume stack = units::legacy_volume_factor / liquid.type->stack_size; + const std::string title = string_format( _( "Select target tank for %.1fL %s" ), + get_all_colors().get_name( liquid.color() ), + round_up( to_liter( liquid.charges * stack ), 1 ), + liquid.tname() ); + + const std::optional vpr = veh_interact::select_part( *target.veh, sel, title ); + if( !vpr ) { + return false; + } + + if( create_activity() ) { + serialize_liquid_target( player_character.activity, *vpr ); + return true; + } else if( player_character.pour_into( *vpr, liquid ) ) { + // this branch is used in milking and magiclysm butchery blood draining + player_character.mod_moves( -1000 ); // consistent with veh_interact::do_refill activity + return true; + } else { + // unclear what can reach this branch but return false just in case + return false; + } +} + +static bool check_liquid( item &liquid ) { if( !liquid.made_of_from_type( phase_id::LIQUID ) ) { dbg( D_ERROR ) << "game:handle_liquid: Tried to handle_liquid a non-liquid!"; debugmsg( "Tried to handle_liquid a non-liquid!" ); + return false; + } + return true; +} + +bool perform_liquid_transfer( item_location &liquid, liquid_dest_opt &target ) +{ + if( !check_liquid( *liquid ) ) { + // "canceled by the user" because we *can* not handle it. + return false; + } + + // todo: migrate ACT_FILL_LIQUID to activity_actor + Character &player_character = get_player_character(); + const auto create_activity = [&]() { + if( liquid.where() == item_location::type::vehicle ) { + player_character.assign_activity( ACT_FILL_LIQUID ); + const vehicle_cursor *veh_cur = liquid.veh_cursor(); + serialize_liquid_source( player_character.activity, veh_cur->veh, veh_cur->part, *liquid ); + return true; + } else if( liquid.where() == item_location::type::map ) { + player_character.assign_activity( ACT_FILL_LIQUID ); + serialize_liquid_source( player_character.activity, liquid.position(), *liquid ); + return true; + } else { + return false; + } + }; + + switch( target.dest_opt ) { + case LD_CONSUME: + player_character.assign_activity( consume_activity_actor( liquid ) ); + return true; + case LD_ITEM: { + return handle_item_target( player_character, *liquid, target, create_activity ); + } + case LD_VEH: { + return handle_vehicle_target( player_character, *liquid, target, create_activity ); + } + case LD_KEG: + case LD_GROUND: + return handle_keg_or_ground_target( player_character, *liquid, target, create_activity ); + case LD_NULL: + default: + return false; + } +} + +// todo: Remove in favor of the item_location version. +bool perform_liquid_transfer( item &liquid, const tripoint *const source_pos, + const vehicle *const source_veh, const int part_num, + const monster *const /*source_mon*/, liquid_dest_opt &target ) +{ + if( !check_liquid( liquid ) ) { // "canceled by the user" because we *can* not handle it. return false; } @@ -375,76 +491,20 @@ bool perform_liquid_transfer( item &liquid, const tripoint *const source_pos, }; map &here = get_map(); - item_location liquid_loc; switch( target.dest_opt ) { case LD_CONSUME: - if( source_pos ) { - liquid_loc = item_location( map_cursor( tripoint_bub_ms( *source_pos ) ), &liquid ); - } else if( source_veh ) { - liquid_loc = item_location( vehicle_cursor( *source_veh, part_num ), &liquid ); - } else { - player_character.assign_activity( consume_activity_actor( liquid ) ); - return true; - } - - player_character.assign_activity( consume_activity_actor( liquid_loc ) ); + player_character.assign_activity( consume_activity_actor( liquid ) ); + liquid.charges--; return true; case LD_ITEM: { - // Currently activities can only store item position in the players inventory, - // not on ground or similar. TODO: implement storing arbitrary container locations. - if( target.item_loc && create_activity() ) { - serialize_liquid_target( player_character.activity, target.item_loc ); - } else if( player_character.pour_into( target.item_loc, liquid, true ) ) { - target.item_loc.make_active(); - player_character.mod_moves( -100 ); - } - return true; + return handle_item_target( player_character, liquid, target, create_activity ); } case LD_VEH: { - if( target.veh == nullptr ) { - return false; - } - auto sel = [&]( const vehicle_part & pt ) { - return pt.is_tank() && pt.can_reload( liquid ); - }; - - const units::volume stack = units::legacy_volume_factor / liquid.type->stack_size; - const std::string title = string_format( _( "Select target tank for %.1fL %s" ), - get_all_colors().get_name( liquid.color() ), - round_up( to_liter( liquid.charges * stack ), 1 ), - liquid.tname() ); - - const std::optional vpr = veh_interact::select_part( *target.veh, sel, title ); - if( !vpr ) { - return false; - } - - if( create_activity() ) { - serialize_liquid_target( player_character.activity, *vpr ); - return true; - } else if( player_character.pour_into( *vpr, liquid ) ) { - // this branch is used in milking and magiclysm butchery blood draining - player_character.mod_moves( -1000 ); // consistent with veh_interact::do_refill activity - return true; - } else { - // unclear what can reach this branch but return false just in case - return false; - } + return handle_vehicle_target( player_character, liquid, target, create_activity ); } case LD_KEG: case LD_GROUND: - if( create_activity() ) { - serialize_liquid_target( player_character.activity, target.pos ); - } else { - if( target.dest_opt == LD_KEG ) { - iexamine::pour_into_keg( tripoint_bub_ms( target.pos ), liquid ); - } else { - here.add_item_or_charges( target.pos, liquid ); - liquid.charges = 0; - } - player_character.mod_moves( -100 ); - } - return true; + return handle_keg_or_ground_target( player_character, liquid, target, create_activity ); case LD_NULL: default: return false; @@ -468,7 +528,7 @@ bool can_handle_liquid( const item &liquid ) bool handle_liquid( item &liquid, const item *const source, const int radius, const tripoint *const source_pos, - vehicle *const source_veh, const int part_num, + const vehicle *const source_veh, const int part_num, const monster *const source_mon ) { bool success = false; @@ -488,4 +548,28 @@ bool handle_liquid( item &liquid, const item *const source, const int radius, } return false; } + +bool handle_liquid( item_location &liquid, const item *const source, const int radius ) +{ + bool success = false; + if( !can_handle_liquid( *liquid ) ) { + return false; + } + struct liquid_dest_opt liquid_target; + if( get_liquid_target( liquid, source, radius, liquid_target ) ) { + success = perform_liquid_transfer( liquid, liquid_target ); + if( success && ( ( liquid_target.dest_opt == LD_ITEM && + liquid_target.item_loc->is_watertight_container() ) || liquid_target.dest_opt == LD_KEG ) ) { + liquid->unset_flag( flag_id( json_flag_FROM_FROZEN_LIQUID ) ); + if( liquid.where() == item_location::type::container ) { + liquid.parent_item().on_contents_changed(); + } + if( liquid->charges == 0 ) { + liquid.remove_item(); + } + } + return success; + } + return false; +} } // namespace liquid_handler diff --git a/src/handle_liquid.h b/src/handle_liquid.h index 294d1b2b18fb9..fc9b2ba8d324f 100644 --- a/src/handle_liquid.h +++ b/src/handle_liquid.h @@ -57,35 +57,14 @@ void handle_all_liquid( item liquid, int radius, const item *avoid = nullptr ); bool consume_liquid( item &liquid, int radius = 0, const item *avoid = nullptr ); /** - * Handle finite liquid from ground. The function also handles consuming move points. - * This may start a player activity. - * @param on_ground Iterator to the item on the ground. Must be valid and point to an - * item in the stack at `m.i_at(pos)` - * @param pos The position of the item on the map. + * Handle liquids from inside a container item. The function also handles consuming move points. + * @param container Container of the liquid * @param radius around position to handle liquid for * @return Whether the item has been removed (which implies it was handled completely). * The iterator is invalidated in that case. Otherwise the item remains but may have * fewer charges. */ -bool handle_liquid_from_ground( const map_stack::iterator &on_ground, const tripoint &pos, - int radius = 0 ); - -/** - * Handle liquid from inside a container item. The function also handles consuming move points. - * @param in_container Iterator to the liquid. Must be valid and point to an - * item in the @ref item::contents of the container. - * @param container Container of the liquid - * @param radius around position to handle liquid for - * @return Whether the item has been removed (which implies it was handled completely). - * The iterator is invalidated in that case. Otherwise the item remains but may have - * fewer charges. - */ -bool handle_liquid_from_container( item *in_container, item &container, - int radius = 0 ); -/** - * Shortcut to the above: handles the first item in the container. - */ -bool handle_liquid_from_container( item &container, int radius = 0 ); +bool handle_all_liquids_from_container( item_location &container, int radius = 0 ); bool can_handle_liquid( const item &liquid ); @@ -110,13 +89,15 @@ bool can_handle_liquid( const item &liquid ); */ bool handle_liquid( item &liquid, const item *source = nullptr, int radius = 0, const tripoint *source_pos = nullptr, - vehicle *source_veh = nullptr, int part_num = -1, + const vehicle *source_veh = nullptr, int part_num = -1, const monster *source_mon = nullptr ); +bool handle_liquid( item_location &liquid, const item *source = nullptr, int radius = 0 ); /* Not to be used directly. Use liquid_handler::handle_liquid instead. */ bool perform_liquid_transfer( item &liquid, const tripoint *source_pos, - vehicle *source_veh, int part_num, + const vehicle *source_veh, int part_num, const monster * /*source_mon*/, liquid_dest_opt &target ); +bool perform_liquid_transfer( item_location &loc, liquid_dest_opt &target ); } // namespace liquid_handler #endif // CATA_SRC_HANDLE_LIQUID_H diff --git a/src/iexamine.cpp b/src/iexamine.cpp index 24cb940527460..2dd55e9212704 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -556,7 +556,8 @@ void iexamine::gaspump( Character &you, const tripoint_bub_ms &examp ) } } else { - liquid_handler::handle_liquid_from_ground( item_it, examp.raw(), 1 ); + item_location loc( map_cursor( examp ), &*item_it ); + liquid_handler::handle_liquid( loc, nullptr, 1 ); } return; } @@ -3642,7 +3643,8 @@ void iexamine::fvat_empty( Character &you, const tripoint_bub_ms &examp ) break; } case REMOVE_BREW: { - liquid_handler::handle_liquid_from_ground( here.i_at( examp ).begin(), examp.raw() ); + item_location loc( map_cursor( examp ), &*here.i_at( examp ).begin() ); + liquid_handler::handle_liquid( loc ); return; } case START_FERMENT: { @@ -3765,7 +3767,8 @@ void iexamine::fvat_full( Character &you, const tripoint_bub_ms &examp ) } const std::string booze_name = brew_i.tname(); - if( liquid_handler::handle_liquid_from_ground( items_here.begin(), examp.raw() ) ) { + item_location loc( map_cursor( examp ), &*items_here.begin() ); + if( liquid_handler::handle_liquid( loc ) ) { fvat_set_empty( examp ); add_msg( _( "You squeeze the last drops of %s from the vat." ), booze_name ); } @@ -3869,7 +3872,8 @@ void iexamine::compost_empty( Character &you, const tripoint_bub_ms &examp ) break; } case REMOVE_COMPOST: { - liquid_handler::handle_liquid_from_ground( here.i_at( examp ).begin(), examp.raw() ); + item_location loc( map_cursor( examp ), &*here.i_at( examp ).begin() ); + liquid_handler::handle_liquid( loc ); return; } case START_FERMENT: { @@ -4046,7 +4050,8 @@ void iexamine::compost_full( Character &you, const tripoint_bub_ms &examp ) } const std::string compost_name = compost_i.tname(); - if( liquid_handler::handle_liquid_from_ground( items_here.begin(), examp.raw() ) ) { + item_location loc( map_cursor( examp ), &*items_here.begin() ); + if( liquid_handler::handle_liquid( loc ) ) { compost_set_empty( examp ); add_msg( _( "You squeeze the last drops of %s from the tank." ), compost_name ); } @@ -4207,13 +4212,14 @@ void iexamine::keg( Character &you, const tripoint_bub_ms &examp ) selectmenu.query(); switch( selectmenu.ret ) { - case DISPENSE: - if( liquid_handler::handle_liquid_from_ground( items.begin(), examp.raw() ) ) { + case DISPENSE: { + item_location loc( map_cursor( examp ), &*items.begin() ); + if( liquid_handler::handle_liquid( loc ) ) { add_msg( _( "You squeeze the last drops of %1$s from the %2$s." ), drink_tname, keg_name ); } return; - + } case HAVE_A_DRINK: if( !you.can_consume_as_is( drink ) ) { return; // They didn't actually drink @@ -4498,7 +4504,8 @@ void iexamine::tree_maple_tapped( Character &you, const tripoint_bub_ms &examp ) } case HARVEST_SAP: { - liquid_handler::handle_liquid_from_container( *container, PICKUP_RANGE ); + item_location loc( map_cursor( examp ), container ); + liquid_handler::handle_all_liquids_from_container( loc, PICKUP_RANGE ); return; } @@ -4715,7 +4722,8 @@ void iexamine::finite_water_source( Character &, const tripoint_bub_ms &examp ) map_stack items = get_map().i_at( examp ); for( auto item_it = items.begin(); item_it != items.end(); ++item_it ) { if( item_it->made_of( phase_id::LIQUID ) ) { - liquid_handler::handle_liquid_from_ground( item_it, examp.raw() ); + item_location loc( map_cursor( examp ), &*item_it ); + liquid_handler::handle_liquid( loc ); break; } } diff --git a/src/item_location.cpp b/src/item_location.cpp index c15ffa2fb2d22..0559f300b8ec2 100644 --- a/src/item_location.cpp +++ b/src/item_location.cpp @@ -90,6 +90,9 @@ class item_location::impl } virtual tripoint position() const = 0; virtual Character *carrier() const = 0; + virtual const vehicle_cursor *veh_cursor() const { + return nullptr; + }; virtual std::string describe( const Character * ) const = 0; virtual item_location obtain( Character &, int ) = 0; virtual units::volume volume_capacity() const = 0; @@ -489,6 +492,10 @@ class item_location::impl::item_on_vehicle : public item_location::impl return nullptr; } + const vehicle_cursor *veh_cursor() const override { + return &cur; + } + std::string describe( const Character *ch ) const override { const vpart_position part_pos( cur.veh, cur.part ); std::string res; @@ -1109,6 +1116,11 @@ Character *item_location::carrier() const return ptr->carrier(); } +const vehicle_cursor *item_location::veh_cursor() const +{ + return ptr->veh_cursor(); +} + bool item_location::held_by( Character const &who ) const { return carrier() == &who; diff --git a/src/item_location.h b/src/item_location.h index 51e1e1e7edbf3..23a662f18dbe7 100644 --- a/src/item_location.h +++ b/src/item_location.h @@ -111,6 +111,9 @@ class item_location /** returns the character whose inventory contains this item, nullptr if none **/ Character *carrier() const; + /** returns the character whose inventory contains this item, nullptr if none **/ + const vehicle_cursor *veh_cursor() const; + /** returns true if the item is in the inventory of the given character **/ bool held_by( Character const &who ) const;