Skip to content

Commit

Permalink
basecamps: add support for blueprint_excludes
Browse files Browse the repository at this point in the history
add a new blueprint keyword "blueprint_excludes" which can be used to
create mutually exclusive branches in an camp upgrade path.

the list of completed blueprint_provides is compared to an upgrade's
blueprint_excludes.  If there is a match, the recipe isn't available.

There is a second list of in_progress provides, which is used to prevent
people from building two mutually exclusive upgrades at the same time,
while also preventing them from building upgrade B that requires
upgrade A before upgrade A is completed.
  • Loading branch information
mlangsdorf committed Jun 7, 2019
1 parent 2b22aad commit 8021fe4
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 23 deletions.
21 changes: 14 additions & 7 deletions data/json/recipes/basecamps/recipe_primitive_field.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"never_learn": true,
"time": "60 m",
"construction_blueprint": "faction_base_field_camp_0",
"blueprint_name": "basic survey"
"blueprint_name": "basic survey",
"blueprint_requires": [ { "id": "not_an_upgrade" } ]
},
{
"type": "recipe",
Expand Down Expand Up @@ -462,7 +463,8 @@
"never_learn": true,
"construction_blueprint": "faction_base_field_camp_farm_0",
"blueprint_name": "Farm survey",
"time": "180 m"
"time": "180 m",
"blueprint_requires": [ { "id": "not_an_upgrade" } ]
},
{
"type": "recipe",
Expand Down Expand Up @@ -544,7 +546,8 @@
"construction_blueprint": "faction_base_field_camp_garage_0",
"blueprint_name": "Garage survey",
"never_learn": true,
"time": "180 m"
"time": "180 m",
"blueprint_requires": [ { "id": "not_an_upgrade" } ]
},
{
"type": "recipe",
Expand Down Expand Up @@ -685,7 +688,8 @@
"never_learn": true,
"construction_blueprint": "faction_base_field_camp_kitchen_0",
"blueprint_name": "Kitchen survey",
"time": "180 m"
"time": "180 m",
"blueprint_requires": [ { "id": "not_an_upgrade" } ]
},
{
"type": "recipe",
Expand Down Expand Up @@ -890,7 +894,8 @@
"never_learn": true,
"time": "510 m",
"qualities": [ [ { "id": "DIG", "level": 1 } ] ],
"components": [ [ [ "stick", 3 ] ] ]
"components": [ [ [ "stick", 3 ] ] ],
"blueprint_requires": [ { "id": "not_an_upgrade" } ]
},
{
"type": "recipe",
Expand All @@ -904,7 +909,8 @@
"never_learn": true,
"time": "1530 m",
"qualities": [ [ { "id": "DIG", "level": 2 } ] ],
"components": [ [ [ "pointy_stick", 68 ] ] ]
"components": [ [ [ "pointy_stick", 68 ] ] ],
"blueprint_requires": [ { "id": "not_an_upgrade" } ]
},
{
"type": "recipe",
Expand All @@ -917,7 +923,8 @@
"never_learn": true,
"construction_blueprint": "faction_base_field_camp_blacksmith_0",
"blueprint_name": "Blacksmithy survey",
"time": "180 m"
"time": "180 m",
"blueprint_requires": [ { "id": "not_an_upgrade" } ]
},
{
"type": "recipe",
Expand Down
7 changes: 5 additions & 2 deletions doc/BASECAMP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ New field | Description
`"construction_name"` | string, a short description/name of the upgrade mission that is displayed in the base camp mission selector. The recipe's `"description"` field has the extended description that explains why a player might want to have an NPC perform this upgrade mission.
`"blueprint_provides"` | array of objects, with each object having an `"id"` string and an optional `"amount"` integer. These are the camp features that are available when the upgrade mission is complete. Every upgrade mission provides its recipe `"result"` with an amount of 1 automatically and that string does not need to be listed in `"blueprint_provides"`.
`"blueprint_requires"` | array of objects, with each object having an `"id"` string and an optional `"amount"` integer. These are the camp features that are required before the upgrade mission can be attempted.
`"blueprint_excludes"` | array of objects, with each object having an `"id"` string and an optional `"amount"` integer. These are the camp features that prevent the upgrade mission from being attempted if they exist.
`"blueprint_resources"` | array of `"itype_id"`s. Items with those ids will be added to the camp inventory after the upgrade mission is completed and can be used for crafting or additional upgrade missions.

### blueprint requires and provides
blueprint requires and blueprint provides are abstract concepts or flags that an upgrade mission requires to start, or that are provided by a previous upgrade mission to satisfy the blueprint requirements of a current upgrade mission. Each one has an `"id"` and an `"amount"`. Multiple requires or provides with the same `"id"` sum their `"amount"` if they're on the same basecamp expansion.
### blueprint requires, provides, and excludes
blueprint requires, blueprint provides, and blueprint exlcudes are abstract concepts or flags that an upgrade mission requires to start, or that are provided by a previous upgrade mission to satisfy the blueprint requirements of a current upgrade mission, or that prevent an upgrade mission from being available. Each one has an `"id"` and an `"amount"`. Multiple requires, provides, or excludes with the same `"id"` sum their `"amount"` if they're on the same basecamp expansion.

Every upgrade mission has its recipe `"result"` as a blueprint_provides and a blueprint_excludes, so upgrade missions will automatically prevent themselves from being repatable.

These are arbitrary strings and can be used to control the branching of the upgrade paths. However, some strings have meaning within the basecamp code:

Expand Down
57 changes: 52 additions & 5 deletions src/basecamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ void basecamp::add_expansion( const std::string &terrain, const tripoint &new_po

const std::string dir = talk_function::om_simple_dir( omt_pos, new_pos );
expansions[ dir ] = parse_expansion( terrain, new_pos );
update_provides( terrain, expansions[ dir ] );
directions.push_back( dir );
}

Expand Down Expand Up @@ -244,18 +245,42 @@ const std::vector<basecamp_upgrade> basecamp::available_upgrades( const std::str
for( int number = 1; number < base_camps::max_upgrade_by_type( e_data.type ); number++ ) {
const std::string &bldg = base_camps::faction_encode_abs( e_data, number );
const recipe &recp = recipe_id( bldg ).obj();
bool should_display = false;
// skip buildings that are completed
if( e_data.provides.find( bldg ) != e_data.provides.end() ) {
continue;
}
// skip building that have unmet requirements
size_t needed_requires = recp.blueprint_requires().size();
size_t met_requires = 0;
for( const auto &bp_require : recp.blueprint_requires() ) {
if( e_data.provides.find( bldg ) != e_data.provides.end() ) {
break;
}
if( e_data.provides.find( bp_require.first ) == e_data.provides.end() ) {
break;
}
if( e_data.provides[bp_require.first] < bp_require.second ) {
break;
}
should_display = true;
met_requires += 1;
}
if( met_requires < needed_requires ) {
continue;
}
bool should_display = true;
bool in_progress = false;
for( const auto &bp_exclude : recp.blueprint_excludes() ) {
// skip buildings that are excluded by previous builds
if( e_data.provides.find( bp_exclude.first ) != e_data.provides.end() ) {
if( e_data.provides[bp_exclude.first] >= bp_exclude.second ) {
should_display = false;
break;
}
}
// track buildings that are currently being built
if( e_data.in_progress.find( bp_exclude.first ) != e_data.in_progress.end() ) {
if( e_data.in_progress[bp_exclude.first] >= bp_exclude.second ) {
in_progress = true;
break;
}
}
}
if( !should_display ) {
continue;
Expand All @@ -265,6 +290,7 @@ const std::vector<basecamp_upgrade> basecamp::available_upgrades( const std::str
data.name = recp.blueprint_name();
const auto &reqs = recp.requirements();
data.avail = reqs.can_make_with_inventory( _inv, recp.get_component_filter(), 1 );
data.in_progress = in_progress;
ret_data.emplace_back( data );
}
}
Expand Down Expand Up @@ -340,6 +366,27 @@ void basecamp::update_provides( const std::string &bldg, expansion_data &e_data
}
}


void basecamp::update_in_progress( const std::string &bldg, const std::string &dir )
{
if( !recipe_id( bldg ).is_valid() ) {
return;
}
auto e = expansions.find( dir );
if( e == expansions.end() ) {
return;
}
expansion_data &e_data = e->second;

const recipe &making = recipe_id( bldg ).obj();
for( const auto &bp_provides : making.blueprint_provides() ) {
if( e_data.in_progress.find( bp_provides.first ) == e_data.in_progress.end() ) {
e_data.in_progress[bp_provides.first] = 0;
}
e_data.in_progress[bp_provides.first] += bp_provides.second;
}
}

void basecamp::reset_camp_resources( bool by_radio )
{
reset_camp_workers();
Expand Down
7 changes: 5 additions & 2 deletions src/basecamp.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class mission_data;
struct expansion_data {
std::string type;
std::map<std::string, int> provides;
std::map<std::string, int> in_progress;
tripoint pos;
// legacy camp level, replaced by provides map and set to -1
int cur_level;
Expand Down Expand Up @@ -74,6 +75,7 @@ struct basecamp_upgrade {
std::string bldg;
std::string name;
bool avail = false;
bool in_progress = false;
};

class basecamp
Expand Down Expand Up @@ -119,7 +121,7 @@ class basecamp
bool has_provides( const std::string &req, const std::string &dir = "all", int level = 0 ) const;
void update_resources( const std::string &bldg );
void update_provides( const std::string &bldg, expansion_data &e_data );

void update_in_progress( const std::string &bldg, const std::string &dir );

bool can_expand();
/// Returns the name of the building the current building @ref dir upgrades into,
Expand Down Expand Up @@ -215,7 +217,8 @@ class basecamp
bool must_feed, const std::string &desc, bool group,
const std::vector<item *> &equipment,
const std::string &skill_tested, int skill_level );
void start_upgrade( const std::string &bldg, const std::string &key, bool by_radio );
void start_upgrade( const std::string &bldg, const std::string &dir, const std::string &key,
bool by_radio );
std::string om_upgrade_description( const std::string &bldg, bool trunc = false ) const;
void start_menial_labor();
void start_crafting( const std::string &cur_id, const std::string &cur_dir,
Expand Down
17 changes: 10 additions & 7 deletions src/faction_camp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,11 +601,11 @@ void basecamp::get_available_missions( mission_data &mission_key, bool by_radio
gather_bldg = upgrade.bldg;
const base_camps::miss_data &miss_info = base_camps::miss_info[ "_faction_upgrade_camp" ];
comp_list npc_list = get_mission_workers( upgrade.bldg + "_faction_upgrade_camp" );
if( npc_list.empty() ) {
if( npc_list.empty() && !upgrade.in_progress ) {
entry = om_upgrade_description( upgrade.bldg );
mission_key.add_start( miss_info.miss_id + upgrade.bldg,
miss_info.desc + " " + upgrade.name, "", entry, upgrade.avail );
} else {
} else if( !npc_list.empty() && upgrade.in_progress ) {
entry = miss_info.action;
bool avail = update_time_left( entry, npc_list );
mission_key.add_return( miss_info.ret_miss_id + upgrade.bldg,
Expand Down Expand Up @@ -1190,7 +1190,7 @@ bool basecamp::handle_mission( const std::string &miss_id, const std::string &mi

if( miss_id.size() > 12 && miss_id.substr( 0, 12 ) == "Upgrade Camp" ) {
const std::string bldg = miss_id.substr( 12 );
start_upgrade( bldg, bldg + "_faction_upgrade_camp", by_radio );
start_upgrade( bldg, base_dir, bldg + "_faction_upgrade_camp", by_radio );
} else if( miss_id == "Recover Ally from Upgrading" ) {
upgrade_return( base_dir, "_faction_upgrade_camp" );
} else if( miss_id.size() > 27 && miss_id.substr( 0, 27 ) == "Recover Ally from Upgrading" ) {
Expand Down Expand Up @@ -1313,7 +1313,7 @@ bool basecamp::handle_mission( const std::string &miss_id, const std::string &mi
if( miss_id.size() > 19 &&
miss_id.substr( 0, 19 ) == ( miss_dir + " Expansion Upgrade" ) ) {
const std::string bldg = miss_id.substr( 19 );
start_upgrade( bldg, bldg + "_faction_upgrade_exp_" + miss_dir, by_radio );
start_upgrade( bldg, dir, bldg + "_faction_upgrade_exp_" + miss_dir, by_radio );
} else if( miss_id == "Recover Ally, " + miss_dir + " Expansion" ) {
upgrade_return( dir, "_faction_upgrade_exp_" + miss_dir );
} else {
Expand Down Expand Up @@ -1406,7 +1406,8 @@ npc_ptr basecamp::start_mission( const std::string &miss_id, time_duration durat
return comp;
}

void basecamp::start_upgrade( const std::string &bldg, const std::string &key, bool by_radio )
void basecamp::start_upgrade( const std::string &bldg, const std::string &dir,
const std::string &key, bool by_radio )
{
const recipe &making = recipe_id( bldg ).obj();
//Stop upgrade if you don't have materials
Expand All @@ -1417,9 +1418,11 @@ void basecamp::start_upgrade( const std::string &bldg, const std::string &key, b
npc_ptr comp = start_mission( key, work_days, must_feed,
_( "begins to upgrade the camp..." ), false, {},
making.skill_used.str(), making.difficulty );
if( comp != nullptr ) {
consume_components( making, 1, by_radio );
if( comp == nullptr ) {
return;
}
consume_components( making, 1, by_radio );
update_in_progress( bldg, dir );
} else {
popup( _( "You don't have the materials for the upgrade." ) );
}
Expand Down
13 changes: 13 additions & 0 deletions src/recipe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ void recipe::load( JsonObject &jo, const std::string &src )
bp_requires.emplace_back( std::make_pair( require.get_string( "id" ),
require.get_int( "amount", 1 ) ) );
}
// all blueprints exclude themselves with needing it written in JSON
bp_excludes.emplace_back( std::make_pair( result_, 1 ) );
bp_array = jo.get_array( "blueprint_excludes" );
while( bp_array.has_more() ) {
JsonObject exclude = bp_array.next_object();
bp_excludes.emplace_back( std::make_pair( exclude.get_string( "id" ),
exclude.get_int( "amount", 1 ) ) );
}
}
} else if( type == "uncraft" ) {
reversible = true;
Expand Down Expand Up @@ -535,6 +543,11 @@ const std::vector<std::pair<std::string, int>> &recipe::blueprint_requires() co
return bp_requires;
}

const std::vector<std::pair<std::string, int>> &recipe::blueprint_excludes() const
{
return bp_excludes;
}

bool recipe::hot_result() const
{
// Check if the recipe tools make this food item hot upon making it.
Expand Down
2 changes: 2 additions & 0 deletions src/recipe.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class recipe
const std::vector<itype_id> &blueprint_resources() const;
const std::vector<std::pair<std::string, int>> &blueprint_provides() const;
const std::vector<std::pair<std::string, int>> &blueprint_requires() const;
const std::vector<std::pair<std::string, int>> &blueprint_excludes() const;

bool hot_result() const;

Expand Down Expand Up @@ -174,6 +175,7 @@ class recipe
std::vector<itype_id> bp_resources;
std::vector<std::pair<std::string, int>> bp_provides;
std::vector<std::pair<std::string, int>> bp_requires;
std::vector<std::pair<std::string, int>> bp_excludes;
};

#endif // RECIPE_H
16 changes: 16 additions & 0 deletions src/savegame_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3081,6 +3081,15 @@ void basecamp::serialize( JsonOut &json ) const
json.end_object();
}
json.end_array();
json.member( "in_progress" );
json.start_array();
for( const auto working : expansion.second.in_progress ) {
json.start_object();
json.member( "id", working.first );
json.member( "amount", working.second );
json.end_object();
}
json.end_array();
json.member( "pos", expansion.second.pos );
json.end_object();
}
Expand Down Expand Up @@ -3129,6 +3138,13 @@ void basecamp::deserialize( JsonIn &jsin )
if( e.provides.find( initial_provide ) == e.provides.end() ) {
e.provides[ initial_provide ] = 1;
}
JsonArray in_progress_arr = edata.get_array( "in_progress" );
while( in_progress_arr.has_more() ) {
JsonObject in_progress_data = in_progress_arr.next_object();
std::string id = in_progress_data.get_string( "id" );
int amount = in_progress_data.get_int( "amount" );
e.in_progress[ id ] = amount;
}
edata.read( "pos", e.pos );
expansions[ dir ] = e;
if( dir != "[B]" ) {
Expand Down

0 comments on commit 8021fe4

Please sign in to comment.