From d6dbad046eac91a563434c5190812c43b0958724 Mon Sep 17 00:00:00 2001 From: John Bytheway Date: Sun, 5 Jan 2020 17:51:06 -0500 Subject: [PATCH] Handle basecamp component selection cancellation Various basecamp actions require components and tools to be consumed. When there are choices to be made amongst these, the player is presented with the choice between the various options in the usual way. However, if the player cancels, the action proceeded regardless. This allowed the player to circumvent the consumption of the requirements. Fix this by separating the selection of the components from the consumption. Now the order of events is: - Select components and tools. - Select NPC. - Send NPC on mission. - Consume components and tools. We needed to perform the split because we need to select both the requirements and the NPC before performing alterations to either. Implemented by introducing a new class basecamp_action_components to store this selection state. --- src/basecamp.cpp | 111 +++++++++++++++++++++++++++++-------------- src/basecamp.h | 28 +++++++++-- src/faction_camp.cpp | 23 +++++++-- 3 files changed, 119 insertions(+), 43 deletions(-) diff --git a/src/basecamp.cpp b/src/basecamp.cpp index 1921c4126683b..1cddbd75f418b 100644 --- a/src/basecamp.cpp +++ b/src/basecamp.cpp @@ -9,6 +9,7 @@ #include "avatar.h" #include "clzones.h" +#include "coordinate_conversions.h" #include "output.h" #include "string_formatter.h" #include "translations.h" @@ -579,41 +580,6 @@ std::list basecamp::use_charges( const itype_id &fake_id, int &quantity ) return ret; } -void basecamp::consume_components( map &target_map, const recipe &making, int batch_size ) -{ - const tripoint &origin = target_map.getlocal( get_dumping_spot() ); - const auto &req = making.requirements(); - for( const auto &it : req.get_components() ) { - g->u.consume_items( target_map, g->u.select_item_component( it, batch_size, _inv, - true, is_crafting_component, !by_radio ), batch_size, - is_crafting_component, origin, range ); - } - // this may consume pseudo-resources from fake items - for( const auto &it : req.get_tools() ) { - g->u.consume_tools( target_map, g->u.select_tool_component( it, batch_size, _inv, - DEFAULT_HOTKEYS, true, !by_radio ), batch_size, origin, range, this ); - } - // go back and consume the actual resources - for( basecamp_resource &bcp_r : resources ) { - if( bcp_r.consumed > 0 ) { - target_map.use_charges( origin, range, bcp_r.ammo_id, bcp_r.consumed ); - bcp_r.consumed = 0; - } - } -} - -void basecamp::consume_components( const recipe &making, int batch_size ) -{ - if( by_radio ) { - tinymap target_map; - target_map.load( tripoint( omt_pos.x * 2, omt_pos.y * 2, omt_pos.z ), false ); - consume_components( target_map, making, batch_size ); - target_map.save(); - } else { - consume_components( g->m, making, batch_size ); - } -} - void basecamp::form_crafting_inventory( map &target_map ) { _inv.clear(); @@ -643,7 +609,7 @@ void basecamp::form_crafting_inventory( map &target_map ) } // find available fuel - for( const tripoint &pt : target_map.points_in_radius( origin, range ) ) { + for( const tripoint &pt : target_map.points_in_radius( origin, inv_range ) ) { if( target_map.accessible_items( pt ) ) { for( const item &i : target_map.i_at( pt ) ) { for( basecamp_fuel &bcp_f : fuels ) { @@ -712,3 +678,76 @@ void basecamp::load_data( const std::string &data ) // add space to name replace( name.begin(), name.end(), '_', ' ' ); } + +basecamp_action_components::basecamp_action_components( + const recipe &making, int batch_size, basecamp &base ) : + making_( making ), + batch_size_( batch_size ), + base_( base ) +{ +} + +bool basecamp_action_components::choose_components() +{ + const auto filter = is_crafting_component; + const requirement_data &req = making_.requirements(); + if( !item_selections_.empty() || !tool_selections_.empty() ) { + debugmsg( "Reused basecamp_action_components" ); + return false; + } + for( const auto &it : req.get_components() ) { + comp_selection is = + g->u.select_item_component( it, batch_size_, base_._inv, true, filter, + !base_.by_radio ); + if( is.use_from == cancel ) { + return false; + } + item_selections_.push_back( is ); + } + // this may consume pseudo-resources from fake items + for( const auto &it : req.get_tools() ) { + comp_selection ts = + g->u.select_tool_component( it, batch_size_, base_._inv, DEFAULT_HOTKEYS, true, + !base_.by_radio ); + if( ts.use_from == cancel ) { + return false; + } + tool_selections_.push_back( ts ); + } + return true; +} + +void basecamp_action_components::consume_components() +{ + map *target_map = &g->m; + if( base_.by_radio ) { + map_ = std::make_unique(); + map_->load( omt_to_sm_copy( base_.camp_omt_pos() ), false ); + target_map = map_.get(); + } + const tripoint &origin = target_map->getlocal( base_.get_dumping_spot() ); + const auto &req = making_.requirements(); + if( item_selections_.size() != req.get_components().size() || + tool_selections_.size() != req.get_tools().size() ) { + debugmsg( "Not all selections have been made for basecamp_action_components" ); + } + for( const comp_selection &sel : item_selections_ ) { + g->u.consume_items( *target_map, sel, batch_size_, is_crafting_component, origin, + base_.inv_range ); + } + // this may consume pseudo-resources from fake items + for( const comp_selection &sel : tool_selections_ ) { + g->u.consume_tools( *target_map, sel, batch_size_, origin, base_.inv_range, &base_ ); + } + // go back and consume the actual resources + for( basecamp_resource &bcp_r : base_.resources ) { + if( bcp_r.consumed > 0 ) { + target_map->use_charges( origin, base_.inv_range, bcp_r.ammo_id, bcp_r.consumed ); + bcp_r.consumed = 0; + } + } + if( map_ ) { + map_->save(); + map_.reset(); + } +} diff --git a/src/basecamp.h b/src/basecamp.h index d8dd4aefbc431..572170343fd84 100644 --- a/src/basecamp.h +++ b/src/basecamp.h @@ -24,8 +24,10 @@ class time_duration; enum class farm_ops; class item; class map; -class recipe; class mission_data; +class recipe; +class requirements_data; +class tinymap; struct expansion_data { std::string type; @@ -192,8 +194,6 @@ class basecamp void form_crafting_inventory(); void form_crafting_inventory( map &target_map ); std::list use_charges( const itype_id &fake_id, int &quantity ); - void consume_components( const recipe &making, int batch_size = false ); - void consume_components( map &target_map, const recipe &making, int batch_size ); std::string get_gatherlist() const; /** * spawn items or corpses based on search attempts @@ -322,7 +322,11 @@ class basecamp void serialize( JsonOut &json ) const; void deserialize( JsonIn &jsin ); void load_data( const std::string &data ); + + static constexpr int inv_range = 20; private: + friend class basecamp_action_components; + // lazy re-evaluation of available camp resources void reset_camp_resources(); void add_resource( const itype_id &camp_resource ); @@ -339,9 +343,25 @@ class basecamp std::set fuel_types; std::vector fuels; std::vector resources; - static const int range = 20; inventory _inv; bool by_radio; }; +class basecamp_action_components +{ + public: + basecamp_action_components( const recipe &making, int batch_size, basecamp & ); + + // Returns true iff all necessary components were successfully chosen + bool choose_components(); + void consume_components(); + private: + const recipe &making_; + int batch_size_; + basecamp &base_; + std::vector> item_selections_; + std::vector> tool_selections_; + std::unique_ptr map_; // Used for by-radio crafting +}; + #endif diff --git a/src/faction_camp.cpp b/src/faction_camp.cpp index f991eb5a8de6b..5b61a1982ddc8 100644 --- a/src/faction_camp.cpp +++ b/src/faction_camp.cpp @@ -1536,6 +1536,11 @@ void basecamp::start_upgrade( const std::string &bldg, const point &dir, if( making.requirements().can_make_with_inventory( _inv, making.get_component_filter(), 1 ) ) { bool must_feed = bldg != "faction_base_camp_1"; + basecamp_action_components components( making, 1, *this ); + if( !components.choose_components() ) { + return; + } + time_duration work_days = base_camps::to_workdays( making.batch_duration() ); npc_ptr comp = nullptr; if( making.required_skills.empty() ) { @@ -1554,7 +1559,7 @@ void basecamp::start_upgrade( const std::string &bldg, const point &dir, if( comp == nullptr ) { return; } - consume_components( making, 1 ); + components.consume_components(); update_in_progress( bldg, dir ); } else { popup( _( "You don't have the materials for the upgrade." ) ); @@ -1945,11 +1950,17 @@ void basecamp::start_fortifications( std::string &bldg_exp ) return; } + const int batch_size = fortify_om.size() * 2 - 2; + basecamp_action_components components( making, batch_size, *this ); + if( !components.choose_components() ) { + return; + } + npc_ptr comp = start_mission( "_faction_camp_om_fortifications", total_time, true, _( "begins constructing fortifications…" ), false, {}, making.required_skills ); if( comp != nullptr ) { - consume_components( making, fortify_om.size() * 2 - 2 ); + components.consume_components(); comp->companion_mission_role_id = bldg_exp; for( auto pt : fortify_om ) { comp->companion_mission_points.push_back( pt ); @@ -2019,12 +2030,18 @@ void basecamp::start_crafting( const std::string &cur_id, const point &cur_dir, popup( _( "Your batch is too large!" ) ); return; } + + basecamp_action_components components( making, batch_size, *this ); + if( !components.choose_components() ) { + return; + } + time_duration work_days = base_camps::to_workdays( making.batch_duration( batch_size ) ); npc_ptr comp = start_mission( miss_id + cur_dir_id, work_days, true, _( "begins to work…" ), false, {}, making.required_skills ); if( comp != nullptr ) { - consume_components( making, batch_size ); + components.consume_components(); for( const item &results : making.create_results( batch_size ) ) { comp->companion_mission_inv.add_item( results ); }