diff --git a/data/json/items/comestibles/drink.json b/data/json/items/comestibles/drink.json index d9d107a681cea..a56bfc1733237 100644 --- a/data/json/items/comestibles/drink.json +++ b/data/json/items/comestibles/drink.json @@ -1500,6 +1500,7 @@ "container": "bottle_plastic", "sealed": false, "delete": { "flags": [ "DECAYS_IN_AIR" ] }, + "temperature_effects": [ { "temperature_above": "373 K", "transform": "water_clean", "remove_poison": true } ], "contamination": [ { "disease": "highly_contaminated_food", "probability": 0.1 }, { "disease": "bad_food", "probability": 1 } ] }, { @@ -1508,6 +1509,7 @@ "copy-from": "water", "name": { "str_sp": "murky water" }, "description": "Water, the stuff of life. This water looks like it has quite a bit of life in it in fact, and not the good kind. You should filter and sterilize it if you want to drink it.", + "temperature_effects": [ ], "quench": 30, "parasites": 5, "contamination": [ { "disease": "highly_contaminated_food", "probability": 100 } ] diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index af06b1ced0f57..a6bae946c470f 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -3502,6 +3502,14 @@ Weakpoints only match if they share the same id, so it's important to define the "recipe": "paste_nut_mill_10_1" // Reference to the recipe that performs the task. The syntax is _mill__. The recipe is then defined as a normal recipe for the source with the product as its result and an id_suffix of "mill_X_Y". // See data/json/recipes/food/milling.json for such recipes. Can also use "milling": { "into": "null", "recipe": "" } to override milling from a copied base item. }, +"temperature_effects": [{ // Optional. Specifies certain effects happening to the item when it reaches certain internal temperature thresholds. + // Holds an array of objects + "temperature_above": "373 K", // Temperature needs to be at least this high for the effect to happen. + "temperature_below": "0 K", // Temperature needs to be at least this low for the effect to happen. + // Exactly one of temperature_above and temperature_below must be set. + "transform": "water_clean", // The item id that we transform into upon reaching the temperature. Boiling water gives clean water, as an example. + "remove_poison": true // Whether we should remove poison from comestible item. Defaults to false. +}], "explode_in_fire": true, // Should the item explode if set on fire "nanofab_template_group": "nanofab_recipes", // This item is nanofabricator recipe, and point to itemgroup with items, that it could possibly contain; require nanofab_template_group "template_requirements": "nanofabricator", // `requirement`, that needed to craft any of this templates; used as "one full requirememt per 250 ml of item's volume" - item with volume 750 ml would require three times of `requirement`, item of 2L - eight times of `requirement` diff --git a/src/game_constants.h b/src/game_constants.h index 2a82625d81864..6312a30a81979 100644 --- a/src/game_constants.h +++ b/src/game_constants.h @@ -109,9 +109,6 @@ constexpr units::temperature freezer = units::from_celsius( -5 ); // -5 Celsius // Temperature in which water freezes. constexpr units::temperature freezing = units::from_celsius( 0 ); // 0 Celsius - -// Temperature in which water boils. -constexpr units::temperature boiling = units::from_celsius( 100 ); // 100 Celsius } // namespace temperatures // Slowest speed at which a gun can be aimed. diff --git a/src/item.cpp b/src/item.cpp index 4a06128fdf848..4d95e278135fe 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -182,8 +182,6 @@ static const itype_id itype_rad_badge( "rad_badge" ); static const itype_id itype_rm13_armor( "rm13_armor" ); static const itype_id itype_stock_none( "stock_none" ); static const itype_id itype_tuned_mechanism( "tuned_mechanism" ); -static const itype_id itype_water( "water" ); -static const itype_id itype_water_clean( "water_clean" ); static const itype_id itype_waterproof_gunmod( "waterproof_gunmod" ); static const json_character_flag json_flag_CANNIBAL( "CANNIBAL" ); @@ -13222,9 +13220,22 @@ void item::set_temp_flags( units::temperature new_temperature, float freeze_perc set_flag( flag_COLD ); } - // Convert water into clean water if it starts boiling - if( typeId() == itype_water && new_temperature > temperatures::boiling ) { - convert( itype_water_clean ).poison = 0; + // Execute temperature effects if a new threshold is reached. + // Water turning into clean water when boiling is a canonical example. + const islot_temperature_effects *te_data = type->temperature_effects_data.get(); + if( te_data != nullptr ) { + for( const temperature_effect &eff : te_data->effects ) { + if( ( eff.temperature_above.has_value() && eff.temperature_above.value() <= new_temperature ) || + ( eff.temperature_below.has_value() && eff.temperature_below.value() >= new_temperature ) + ) { + if( eff.transform_into ) { + convert( eff.transform_into ); + } + if( eff.remove_poison ) { + poison = 0; + } + } + } } } diff --git a/src/item_factory.cpp b/src/item_factory.cpp index ab112f3fb4ae7..851ffc57d563a 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -825,6 +825,20 @@ void Item_factory::finalize_post( itype &obj ) } } } + + // temperature_effects consistency + if( obj.temperature_effects_data ) { + for( const temperature_effect &eff : obj.temperature_effects_data->effects ) { + if( eff.temperature_above.has_value() == eff.temperature_below.has_value() ) { + debugmsg( "Exactly one of temperature_above and temperature_above must be set in %s temperature_effects", + obj.id.str() ); + } + if( eff.transform_into.is_null() || !eff.transform_into.is_valid() ) { + debugmsg( "'%s' is not a valid item id (in %s temperature_effects)", + eff.transform_into.c_str(), obj.id.str() ); + } + } + } } void Item_factory::finalize_post_armor( itype &obj ) @@ -2667,6 +2681,29 @@ void islot_milling::deserialize( const JsonObject &jo ) load( jo ); } +void temperature_effect::load( const JsonObject &jo ) +{ + optional( jo, was_loaded, "temperature_above", temperature_above ); + optional( jo, was_loaded, "temperature_below", temperature_below ); + optional( jo, was_loaded, "transform", transform_into ); + optional( jo, was_loaded, "remove_poison", remove_poison ); +} + +static void load_temperature_effects( + const JsonObject &jo, const std::string_view member, + cata::value_ptr &slotptr, const std::string &src ) +{ + if( !jo.has_member( member ) ) { + return; + } + slotptr = cata::make_value(); + for( const JsonObject &tejo : jo.get_array( member ) ) { + temperature_effect eff; + eff.load( tejo ); + slotptr->effects.push_back( eff ); + } +} + static void load_memory_card_data( memory_card_info &mcd, const JsonObject &jo ) { mcd.data_chance = jo.get_float( "data_chance", 1.0f ); @@ -4494,6 +4531,8 @@ void Item_factory::load_basic_info( const JsonObject &jo, itype &def, const std: assign( jo, "compostable", def.compostable, src == "dda" ); load_slot_optional( def.relic_data, jo, "relic_data", src ); assign( jo, "milling", def.milling_data, src == "dda" ); + load_temperature_effects( jo, "temperature_effects", def.temperature_effects_data, src ); + // optional gunmod slot may also specify mod data if( jo.has_member( "gunmod_data" ) ) { diff --git a/src/itype.h b/src/itype.h index 4066ca32bfb5f..3a483e188a832 100644 --- a/src/itype.h +++ b/src/itype.h @@ -1204,6 +1204,21 @@ class islot_milling void deserialize( const JsonObject &jo ); }; +struct temperature_effect { + std::optional temperature_above; + std::optional temperature_below; + + itype_id transform_into; + bool remove_poison = false; + + bool was_loaded = false; + void load( const JsonObject &jo ); +}; + +struct islot_temperature_effects { + std::vector effects; +}; + struct memory_card_info { float data_chance; itype_id on_read_convert_to; @@ -1228,6 +1243,14 @@ struct itype { using FlagsSetType = std::set; + protected: + // private because it should only be accessed through itype::nname! + // nname() is used for display purposes + translation name = no_translation( "none" ); + + public: + translation description; // Flavor text + /** * Slots for various item type properties. Each slot may contain a valid pointer or null, check * this before using it. @@ -1252,6 +1275,7 @@ struct itype { cata::value_ptr seed; cata::value_ptr relic_data; cata::value_ptr milling_data; + cata::value_ptr temperature_effects_data; /*@}*/ /** Action to take BEFORE the item is placed on map. If it returns non-zero, item won't be placed. */ @@ -1390,14 +1414,6 @@ struct itype { // How should the item explode explosion_data explosion; - translation description; // Flavor text - - protected: - // private because is should only be accessed through itype::nname! - // nname() is used for display purposes - translation name = no_translation( "none" ); - - public: // Total of item's material portions (materials->second) int mat_portion_total = 0;