Skip to content

Commit

Permalink
Handle basecamp component selection cancellation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jbytheway committed Jan 7, 2020
1 parent 764aa9f commit d6dbad0
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 43 deletions.
111 changes: 75 additions & 36 deletions src/basecamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "avatar.h"
#include "clzones.h"
#include "coordinate_conversions.h"
#include "output.h"
#include "string_formatter.h"
#include "translations.h"
Expand Down Expand Up @@ -579,41 +580,6 @@ std::list<item> 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();
Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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<item_comp> 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<tool_comp> 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<tinymap>();
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<item_comp> &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<tool_comp> &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();
}
}
28 changes: 24 additions & 4 deletions src/basecamp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -192,8 +194,6 @@ class basecamp
void form_crafting_inventory();
void form_crafting_inventory( map &target_map );
std::list<item> 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
Expand Down Expand Up @@ -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 );
Expand All @@ -339,9 +343,25 @@ class basecamp
std::set<itype_id> fuel_types;
std::vector<basecamp_fuel> fuels;
std::vector<basecamp_resource> 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<comp_selection<item_comp>> item_selections_;
std::vector<comp_selection<tool_comp>> tool_selections_;
std::unique_ptr<tinymap> map_; // Used for by-radio crafting
};

#endif
23 changes: 20 additions & 3 deletions src/faction_camp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() ) {
Expand All @@ -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." ) );
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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 );
}
Expand Down

0 comments on commit d6dbad0

Please sign in to comment.