Skip to content

Commit c9c0e4f

Browse files
committed
feat: add furniture multicooker
1 parent c1382b2 commit c9c0e4f

File tree

9 files changed

+267
-1
lines changed

9 files changed

+267
-1
lines changed

data/json/construction/construct_workshop.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,19 @@
259259
"pre_special": "check_empty",
260260
"post_furniture": "f_oven"
261261
},
262+
{
263+
"type": "construction",
264+
"id": "constr_multicooker",
265+
"group": "install_multicooker",
266+
"category": "WORKSHOP",
267+
"required_skills": [ ],
268+
"time": "10 m",
269+
"qualities": [ ],
270+
"components": [ [ [ "multi_cooker", 1 ] ] ],
271+
"pre_note": "Will only work if constructed in/on a building that has an electric grid with a mounted battery.",
272+
"pre_special": "check_empty",
273+
"post_furniture": "f_multicooker"
274+
},
262275
{
263276
"type": "construction",
264277
"id": "constr_gridfridge",

data/json/construction_group.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,11 @@
10091009
"id": "install_oven",
10101010
"name": "Plug In Oven"
10111011
},
1012+
{
1013+
"type": "construction_group",
1014+
"id": "install_multicooker",
1015+
"name": "Plug In Multi Cooker"
1016+
},
10121017
{
10131018
"type": "construction_group",
10141019
"id": "install_refrigerator",

data/json/furniture_and_terrain/furniture-appliances.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,45 @@
682682
"ranged": { "reduction": [ 4, 8 ], "destroy_threshold": 30, "block_unaimed_chance": "50%" }
683683
}
684684
},
685+
{
686+
"type": "furniture",
687+
"id": "f_multicooker",
688+
"name": "multi cooker",
689+
"looks_like": "f_oven",
690+
"symbol": "#",
691+
"description": "Used for automatic cooking food with electricity.",
692+
"color": "dark_gray",
693+
"move_cost_mod": 2,
694+
"coverage": 60,
695+
"required_str": 10,
696+
"crafting_pseudo_item": "fake_oven",
697+
"flags": [ "PLACE_ITEM", "TRANSPARENT", "FIRE_CONTAINER", "CONTAINER", "BLOCKSDOOR", "MOUNTABLE", "EASY_DECONSTRUCT" ],
698+
"examine_action": "multicooker",
699+
"deconstruct": { "items": [ { "item": "multi_cooker", "count": 1 } ] },
700+
"max_volume": "40 L",
701+
"bash": {
702+
"str_min": 8,
703+
"str_max": 30,
704+
"sound": "metal screeching!",
705+
"sound_fail": "clang!",
706+
"items": [
707+
{ "item": "sheet_metal", "count": [ 1, 4 ] },
708+
{ "item": "sheet_metal_small", "count": [ 8, 12 ] },
709+
{ "item": "steel_chunk", "count": [ 0, 3 ] },
710+
{ "item": "scrap", "count": [ 0, 6 ] },
711+
{ "item": "element", "count": [ 1, 3 ] },
712+
{ "item": "cable", "charges": [ 1, 3 ] },
713+
{ "item": "pilot_light", "count": 1 }
714+
],
715+
"//": "Variable reduction since might hit more or less material.",
716+
"ranged": { "reduction": [ 4, 8 ], "destroy_threshold": 30, "block_unaimed_chance": "50%" }
717+
},
718+
"default_vars": {
719+
"CATEGORYIDS": "[ \"CSC_FOOD_MEAT\", \"CSC_FOOD_VEGGI\", \"CSC_FOOD_PASTA\" ]",
720+
"CHARGE_PER_MIN": "10",
721+
"CHARGE_START": "10"
722+
}
723+
},
685724
{
686725
"type": "furniture",
687726
"id": "f_rvoven",

docs/en/mod/json/reference/tiles/furn_and_terrain.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ title: Furniture and Terrain
4141
"message": "The safe is hacksawed open!",
4242
"sound": "Gachunk!",
4343
"byproducts": [{ "item": "scrap", "count": 13 }]
44+
},
45+
"default_vars": {
46+
"CATEGORYIDS": "[ \"CSC_FOOD_MEAT\", \"CSC_FOOD_VEGGI\", \"CSC_FOOD_PASTA\" ]",
47+
"CHARGE_PER_MIN": "5",
48+
"CHARGE_START": "100",
49+
"CRAFTSPEEDMULT": "1.0"
4450
}
4551
}
4652
```
@@ -171,6 +177,17 @@ it for the purpose of surgery.
171177
(Optional) Dispenses infinite amounts of specified liquid item when interacted. Must be used with
172178
`"examine_action": "liquid_source"` to work.
173179

180+
#### `default_vars`
181+
182+
(Optional) Default string variables for objects, always a string string pair; Can be used to store arbitrary data or for some data for iuses such as
183+
184+
- Multicooker
185+
- "CATEGORYIDS"; String is a json array of categories for applicable recipes
186+
- "RECIPEIDS": String is a json array of valid recipes
187+
- "CHARGE_PER_MIN": Charges consumed per minute
188+
- "CHARGE_START": Charges consumed at start
189+
- "CRAFTSPEEDMULT": Multiplier on craft speed
190+
174191
### Terrain
175192

176193
```json

src/iexamine.cpp

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
#include "catacharset.h"
3434
#include "character.h"
3535
#include "character_functions.h"
36+
#include "data_vars.h"
37+
#include "detached_ptr.h"
3638
#include "flag.h"
3739
#include "color.h"
3840
#include "construction.h"
@@ -107,6 +109,7 @@
107109
#include "uistate.h"
108110
#include "units.h"
109111
#include "units_utility.h"
112+
#include "recipe_dictionary.h"
110113
#include "value_ptr.h"
111114
#include "vehicle.h"
112115
#include "vehicle_part.h"
@@ -7469,6 +7472,188 @@ void iexamine::cardreader_plutgen( player &p, const tripoint &examp )
74697472
}
74707473
}
74717474

7475+
void iexamine::multicooker( player &p, const tripoint &pos )
7476+
{
7477+
map &here = get_map();
7478+
const furn_id furniture = here.furn( pos );
7479+
data_vars::data_set *vars = here.furn_vars( pos );
7480+
const tripoint_abs_ms abspos( here.getabs( pos ) );
7481+
auto grid = get_distribution_grid_tracker().grid_at( abspos );
7482+
int battery = grid.get_resource();
7483+
enum {
7484+
mc_start, mc_stop, mc_take, mc_upgrade
7485+
};
7486+
7487+
uilist menu;
7488+
menu.text = _( "Choose option:" );
7489+
7490+
if( vars->get( "ACTIVE", false ) ) {
7491+
if( vars->contains( "STARTTIME" ) &&
7492+
vars->get( "STARTTIME", 0 ) + vars->get( "COOKTIME", 0 ) > to_turn<int>( calendar::turn ) ) {
7493+
menu.addentry( mc_stop, true, 's', _( "Stop crafting" ) );
7494+
} else {
7495+
menu.addentry( mc_take, true, 't', _( "Remove Product" ) );
7496+
}
7497+
} else {
7498+
if( battery < vars->get( "CHARGE_START", 0 ) ) {
7499+
p.add_msg_if_player( _( "Batteries are low." ) );
7500+
return;
7501+
}
7502+
menu.addentry( mc_start, true, 's', _( "Start crafting " ) );
7503+
}
7504+
7505+
menu.query();
7506+
int choice = menu.ret;
7507+
7508+
if( choice < 0 ) {
7509+
return;
7510+
}
7511+
7512+
if( mc_stop == choice ) {
7513+
if( query_yn( _( "Really stop?" ) ) ) {
7514+
vars->erase( "RESULT" );
7515+
vars->erase( "ACTIVE" );
7516+
vars->erase( "STARTTIME" );
7517+
vars->erase( "COOKTIME" );
7518+
vars->erase( "BATCHCOUNT" );
7519+
vars->erase( "RECIPE" );
7520+
}
7521+
return;
7522+
}
7523+
7524+
if( mc_take == choice ) {
7525+
7526+
detached_ptr<item> dish = item::spawn( vars->get( "RESULT" ), calendar::turn,
7527+
vars->get( "BATCHCOUNT", 1 ) );
7528+
7529+
const std::string dish_name = dish->tname( dish->charges, false );
7530+
if( dish->made_of( LIQUID ) ) {
7531+
if( !p.check_eligible_containers_for_crafting( *recipe_id( vars->get( "RECIPE" ) ), 1 ) ) {
7532+
p.add_msg_if_player( m_info, _( "You don't have a suitable container to store your %s." ),
7533+
dish_name );
7534+
return;
7535+
}
7536+
liquid_handler::handle_all_liquid( std::move( dish ), PICKUP_RANGE );
7537+
} else {
7538+
p.i_add( std::move( dish ) );
7539+
}
7540+
7541+
grid.mod_resource( vars->get( "COOKTIME", 0 ) * vars->get( "CRAFTSPEEDMULT",
7542+
1.0 ) / 6000 * vars->get( "CHARGE_PER_MIN", 0.0 ) + vars->get( "CHARGE_START", 0.0 ) );
7543+
vars->erase( "RESULT" );
7544+
vars->erase( "ACTIVE" );
7545+
vars->erase( "STARTTIME" );
7546+
vars->erase( "COOKTIME" );
7547+
vars->erase( "BATCHCOUNT" );
7548+
vars->erase( "RECIPE" );
7549+
p.add_msg_if_player( m_good, _( "You got the %s from the %s." ),
7550+
dish_name, furniture->name() );
7551+
7552+
return;
7553+
}
7554+
7555+
if( mc_start == choice ) {
7556+
uilist dmenu;
7557+
dmenu.text = _( "Choose desired recipe:" );
7558+
7559+
std::vector<const recipe *> dishes;
7560+
7561+
inventory crafting_inv = g->u.crafting_inventory();
7562+
7563+
for( itype item : furniture->crafting_pseudo_item_types() ) {
7564+
crafting_inv.add_item( *item::spawn_temporary( item.get_id(), calendar::start_of_cataclysm ),
7565+
false );
7566+
}
7567+
crafting_inv.update_quality_cache();
7568+
7569+
int counter = 0;
7570+
7571+
for( const auto &r : g->u.get_learned_recipes() ) {
7572+
if( vars->get( "CATEGORYIDS", std::set<std::string>() ).contains( r->subcategory ) ||
7573+
vars->get( "RECIPEIDS", std::set<std::string>() ).contains( r->result().str() ) ) {
7574+
dishes.push_back( r );
7575+
const bool can_make = r->deduped_requirements().can_make_with_inventory(
7576+
crafting_inv, r->get_component_filter() );
7577+
dmenu.addentry( counter++, can_make, -1, string_format( _( "%s (%1.f charges)" ), r->result_name(),
7578+
r->time * vars->get( "CRAFTSPEEDMULT", 1.0 ) / 6000 * vars->get( "CHARGE_PER_MIN",
7579+
0.0 ) + vars->get( "CHARGE_START", 0.0 ) ) );
7580+
}
7581+
}
7582+
7583+
dmenu.query();
7584+
7585+
int choice = dmenu.ret;
7586+
7587+
if( choice < 0 ) {
7588+
7589+
if( choice == -1024 ) {
7590+
p.add_msg_if_player( m_warning,
7591+
_( "You don't know of anything you could craft with this." ) );
7592+
}
7593+
7594+
return;
7595+
} else {
7596+
const recipe *meal = dishes[choice];
7597+
7598+
uilist batchmenu;
7599+
batchmenu.text = _( "Choose batch count:" );
7600+
int counter = 0;
7601+
7602+
for( int i = 1; i < 51; i++ ) {
7603+
const bool can_make = meal->deduped_requirements().can_make_with_inventory(
7604+
crafting_inv, meal->get_component_filter(), i );
7605+
batchmenu.addentry( counter++, can_make, -1, string_format( _( "%s batches (%1.f charges)" ), i,
7606+
meal->batch_time( i, 1, 0 ) * vars->get( "CRAFTSPEEDMULT",
7607+
1.0 ) / 6000 * vars->get( "CHARGE_PER_MIN", 0.0 ) + vars->get( "CHARGE_START", 0.0 ) ) );
7608+
}
7609+
7610+
batchmenu.query();
7611+
7612+
int batchcount = batchmenu.ret;
7613+
7614+
if( batchcount < 0 ) {
7615+
return;
7616+
}
7617+
batchcount++;
7618+
7619+
// Seems to be divided by 100;
7620+
// See the CHARGE_PER_MIN calc being 6000 instead of 60
7621+
int mealtime = meal->batch_time( batchcount, 1, 0 ) * vars->get( "CRAFTSPEEDMULT", 1.0 ) / 100;
7622+
int all_charges = mealtime / 6000 * vars->get( "CHARGE_PER_MIN", 0.0 ) + vars->get( "CHARGE_START",
7623+
0.0 );
7624+
7625+
if( battery < all_charges ) {
7626+
7627+
p.add_msg_if_player( m_warning,
7628+
_( "The %s needs %d charges to create this." ),
7629+
furniture->name(), all_charges );
7630+
return;
7631+
}
7632+
7633+
const auto filter = is_crafting_component;
7634+
const requirement_data *reqs =
7635+
meal->deduped_requirements().select_alternative( p, crafting_inv, filter, batchcount );
7636+
if( !reqs ) {
7637+
return;
7638+
}
7639+
7640+
for( const auto &component : reqs->get_components() ) {
7641+
p.consume_items( component, batchcount, filter );
7642+
}
7643+
7644+
vars->set( "ACTIVE", true );
7645+
vars->set( "RECIPE", meal->ident().str() );
7646+
vars->set( "RESULT", meal->result().str() );
7647+
vars->set( "STARTTIME", to_turn<int>( calendar::turn ) );
7648+
vars->set( "COOKTIME", mealtime );
7649+
vars->set( "BATCHCOUNT", meal->makes_amount() * batchcount );
7650+
7651+
p.add_msg_if_player( m_good, _( "The %s begins to hum." ), furniture->name() );
7652+
7653+
return;
7654+
}
7655+
}
7656+
}
74727657
/**
74737658
* Given then name of one of the above functions, returns the matching function
74747659
* pointer. If no match is found, defaults to iexamine::none but prints out a
@@ -7566,6 +7751,7 @@ iexamine_function iexamine_function_from_string( const std::string &function_nam
75667751
{ "check_power", &iexamine::check_power },
75677752
{ "migo_nerve_cluster", &iexamine::migo_nerve_cluster },
75687753
{ "cardreader_plutgen", &iexamine::cardreader_plutgen },
7754+
{ "multicooker", &iexamine::multicooker },
75697755
}
75707756
};
75717757

src/iexamine.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ void dimensional_portal( player &p, const tripoint &examp );
117117
void check_power( player &p, const tripoint &examp );
118118
void migo_nerve_cluster( player &p, const tripoint &examp );
119119
void cardreader_plutgen( player &p, const tripoint &examp );
120+
void multicooker( player &p, const tripoint &pos );
120121

121122
detached_ptr<item> pour_into_keg( const tripoint &pos, detached_ptr<item> &&liquid );
122123
std::optional<tripoint> getGasPumpByNumber( const tripoint &p, int number );

src/mapdata.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,8 @@ void map_data_common_t::load( const JsonObject &jo, const std::string &src )
13931393
optional( jo, was_loaded, "prompt", prompt );
13941394

13951395
assign( jo, "flags", flags );
1396+
assign( jo, "default_vars", default_vars );
1397+
13961398
bitflags.reset();
13971399
transparent = false;
13981400

src/mapdata.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "type_id.h"
1919
#include "units.h"
2020
#include "value_ptr.h"
21+
#include "data_vars.h"
2122

2223
struct ter_t;
2324
using ter_str_id = string_id<ter_t>;
@@ -485,6 +486,8 @@ struct map_data_common_t {
485486

486487
iexamine_function examine; // What happens when the terrain/furniture is examined
487488

489+
data_vars::data_set default_vars;
490+
488491
/**
489492
* When will this terrain/furniture get harvested and what will drop?
490493
* Note: This excludes items that take extra tools to harvest.

src/submap.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ class submap : maptile_soa<SEEX, SEEY>
108108
void set_furn( point p, furn_id furn ) {
109109
is_uniform = false;
110110
frn[p.x][p.y] = furn;
111+
frn_vars[p].merge( furn->default_vars );
111112
if( furn != f_null ) {
112113
return;
113114
}
114-
// Reset furniture vars on clear
115115
frn_vars.erase( p );
116116
}
117117

0 commit comments

Comments
 (0)