diff --git a/data/json/furniture_and_terrain/terrain-liquids.json b/data/json/furniture_and_terrain/terrain-liquids.json index 50b3585fd9073..3755176131a1e 100644 --- a/data/json/furniture_and_terrain/terrain-liquids.json +++ b/data/json/furniture_and_terrain/terrain-liquids.json @@ -478,7 +478,7 @@ "symbol": "~", "color": "light_blue", "move_cost": 3, - "flags": [ "TRANSPARENT", "LIQUIDCONT", "SPAWN_WITH_LIQUID", "FRESH_WATER", "MURKY" ], + "flags": [ "TRANSPARENT", "LIQUIDCONT", "SPAWN_WITH_WATER", "MURKY" ], "examine_action": "finite_water_source" }, { @@ -491,7 +491,7 @@ "color": "light_blue", "move_cost": 3, "connect_groups": "INDOORFLOOR", - "flags": [ "TRANSPARENT", "LIQUIDCONT", "SPAWN_WITH_LIQUID", "FRESH_WATER", "MURKY", "INDOORS" ], + "flags": [ "TRANSPARENT", "LIQUIDCONT", "SPAWN_WITH_WATER", "MURKY", "INDOORS" ], "examine_action": "finite_water_source" }, { diff --git a/data/json/items/comestibles/drink.json b/data/json/items/comestibles/drink.json index 005f4bae711a6..cfb308f4c232e 100644 --- a/data/json/items/comestibles/drink.json +++ b/data/json/items/comestibles/drink.json @@ -1485,10 +1485,29 @@ { "id": "water", "type": "COMESTIBLE", - "comestible_type": "DRINK", - "category": "food", + "copy-from": "water_clean", "name": { "str_sp": "water" }, "description": "Water, the stuff of life, necessary for all kinds of crafting, cleaning, and avoiding an agonizing death by dehydration. You should boil or purify it before using it as drinking water.", + "color": "light_blue", + "container": "bottle_plastic", + "sealed": false, + "contamination": [ { "disease": "highly_contaminated_food", "probability": 0.1 }, { "disease": "bad_food", "probability": 1 } ] + }, + { + "id": "water_murky", + "type": "COMESTIBLE", + "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.", + "quench": 30, + "parasites": 5, + "contamination": [ { "disease": "highly_contaminated_food", "probability": 100 } ] + }, + { + "id": "water_clean", + "type": "COMESTIBLE", + "comestible_type": "DRINK", + "category": "food", "material": [ "water" ], "weight": "250 g", "volume": "250 ml", @@ -1496,38 +1515,32 @@ "price": "50 cent", "price_postapoc": "1 cent", "symbol": "~", - "color": "light_blue", "phase": "liquid", - "container": "bottle_plastic", - "sealed": false, "quench": 50, "ammo_data": { "ammo_type": "water" }, - "flags": [ "EATEN_COLD" ], - "use_action": [ "BLECH_BECAUSE_UNCLEAN" ] - }, - { - "id": "water_clean", - "copy-from": "water", - "type": "COMESTIBLE", + "flags": [ "EATEN_COLD", "NUTRIENT_OVERRIDE" ], "name": { "str_sp": "clean water" }, "description": "Fresh, clean water. Truly the best thing to quench your thirst.", "container": "bottle_plastic", "sealed": true, - "color": "light_cyan", - "extend": { "flags": [ "NUTRIENT_OVERRIDE" ] }, - "use_action": [ ] + "color": "light_cyan" + }, + { + "id": "water_spring", + "copy-from": "water_clean", + "type": "COMESTIBLE", + "name": { "str_sp": "spring water" }, + "description": "Natural alpine spring water, bottled at the source.", + "relative": { "fun": 1 } }, { "id": "water_mineral", - "copy-from": "water", + "copy-from": "water_clean", "type": "COMESTIBLE", "name": { "str_sp": "mineral water" }, "description": "Fancy mineral water, so fancy it makes you feel fancy just holding it.", - "container": "bottle_plastic", - "sealed": true, "proportional": { "quench": 1.2 }, - "relative": { "fun": 1 }, - "use_action": [ ] + "relative": { "fun": 1 } }, { "type": "COMESTIBLE", diff --git a/data/json/items/tool/cooking.json b/data/json/items/tool/cooking.json index ce0d485d409fb..775f2ff4e6a44 100644 --- a/data/json/items/tool/cooking.json +++ b/data/json/items/tool/cooking.json @@ -1067,6 +1067,22 @@ ], "melee_damage": { "bash": 2 } }, + { + "id": "water_filter_sand", + "type": "TOOL", + "name": { "str": "slow-sand water filter" }, + "//": "Slow sand biofilter: https://wwwnc.cdc.gov/travel/yellowbook/2024/preparing/water-disinfection#:~:text=Figure%202%2D02.%20Emergency%20gravel%20and%20sand%20filter", + "description": "An improvised water filter made from a plastic bucket filled with a layer of sand and a layer of gravel. It can be used to remove particulates and some pathogens from water, but it is not 100% effective.", + "//2": "Density of sand is around 1.6g/cm^3. Gravel is similar. pi * 5in * 5in * (10 in + 4 in) * 1.6 g/cm^3 ~= 29 kg. Add 1 kg for the bucket itself.", + "weight": "14800 g", + "volume": "21000 ml", + "price": 1000, + "price_postapoc": 150, + "to_hit": -3, + "material": [ "plastic", "sand" ], + "symbol": ";", + "color": "light_blue" + }, { "id": "propane_cooker", "type": "TOOL", diff --git a/data/json/recipes/recipe_food.json b/data/json/recipes/recipe_food.json index a79b601a3121b..0dae0fe075ed8 100644 --- a/data/json/recipes/recipe_food.json +++ b/data/json/recipes/recipe_food.json @@ -75,7 +75,7 @@ "category": "CC_FOOD", "id_suffix": "using_water_purifier", "subcategory": "CSC_FOOD_DRINKS", - "skill_used": "cooking", + "skill_used": "survival", "time": "9 s", "//": "https://www.netsolwater.com/how-much-energy-does-an-ro-system-use.php?blog=484", "//1": "Unclear if this is a RO filter but basing it off that. 0.03 KwH per 2 liters in a RO filter = 14kJ for 1u of water", @@ -85,6 +85,32 @@ "tools": [ [ [ "water_purifier", 14 ], [ "pur_tablets", 1 ], [ "char_purifier", 12 ], [ "char_purifier_clay", 12 ] ] ], "components": [ [ [ "water", 1 ] ] ] }, + { + "type": "recipe", + "activity_level": "NO_EXERCISE", + "result": "water", + "category": "CC_FOOD", + "id_suffix": "using_water_purifier", + "subcategory": "CSC_FOOD_DRINKS", + "skill_used": "survival", + "time": "1 m", + "autolearn": true, + "components": [ [ [ "water_murky", 1 ] ], [ [ "pur_tablets", 1 ] ] ] + }, + { + "type": "recipe", + "activity_level": "NO_EXERCISE", + "result": "water", + "charges": 10, + "category": "CC_FOOD", + "id_suffix": "using_water_filter_sand", + "subcategory": "CSC_FOOD_DRINKS", + "skill_used": "survival", + "time": "15 m", + "autolearn": true, + "tools": [ [ "water_filter_sand" ] ], + "components": [ [ [ "water_murky", 10 ] ] ] + }, { "type": "recipe", "activity_level": "NO_EXERCISE", diff --git a/data/json/recipes/tools/tools_primitive.json b/data/json/recipes/tools/tools_primitive.json index c67827bba4fae..fecb60d413745 100644 --- a/data/json/recipes/tools/tools_primitive.json +++ b/data/json/recipes/tools/tools_primitive.json @@ -496,5 +496,24 @@ ], "using": [ [ "blacksmithing_standard", 13 ], [ "steel_standard", 1 ] ], "components": [ [ [ "2x4", 2 ] ] ] + }, + { + "type": "recipe", + "result": "water_filter_sand", + "activity_level": "MODERATE_EXERCISE", + "category": "CC_OTHER", + "subcategory": "CSC_OTHER_TOOLS", + "skill_used": "survival", + "skills_required": [ [ "survival", 2 ] ], + "difficulty": 2, + "time": "30 m", + "autolearn": true, + "reversible": true, + "components": [ + [ [ "bucket_5gal", 1 ] ], + [ [ "material_sand", 1250 ] ], + [ [ "material_gravel", 500 ] ], + [ [ "cotton_patchwork", 4 ], [ "wire_mesh", 4 ] ] + ] } ] diff --git a/src/consumption.cpp b/src/consumption.cpp index 9bb8dcc0d0dd4..d3cb65b59ad94 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -1208,8 +1208,8 @@ static bool eat( item &food, Character &you, bool force ) } } - for( const std::pair &elem : food.get_comestible()->contamination ) { - if( rng( 1, 100 ) <= elem.second ) { + for( const std::pair &elem : food.get_comestible()->contamination ) { + if( rng_float( 0, 100 ) < elem.second ) { you.expose_to_disease( elem.first ); } } diff --git a/src/item_factory.cpp b/src/item_factory.cpp index 7c883c125bd64..dea99b60142ff 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -799,7 +799,7 @@ void Item_factory::finalize_post( itype &obj ) } if( obj.comestible ) { - for( const std::pair &elem : obj.comestible->contamination ) { + for( const std::pair &elem : obj.comestible->contamination ) { const diseasetype_id dtype = elem.first; if( !dtype.is_valid() ) { debugmsg( "contamination in %s contains invalid diseasetype_id %s.", @@ -3308,7 +3308,7 @@ void Item_factory::load( islot_comestible &slot, const JsonObject &jo, const std for( const JsonObject jsobj : jo.get_array( "contamination" ) ) { slot.contamination.emplace( diseasetype_id( jsobj.get_string( "disease" ) ), - jsobj.get_int( "probability" ) ); + jsobj.get_float( "probability" ) ); } if( jo.has_member( "primary_material" ) ) { diff --git a/src/itype.h b/src/itype.h index bb006a1e1b3aa..813c934447b6f 100644 --- a/src/itype.h +++ b/src/itype.h @@ -171,7 +171,7 @@ struct islot_comestible { std::vector consumption_eocs; /**List of diseases carried by this comestible and their associated probability*/ - std::map contamination; + std::map contamination; // Materials to generate the below std::map materials; diff --git a/src/map.cpp b/src/map.cpp index 12db8479e26ea..e5309598f7810 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -113,8 +113,6 @@ static const ammotype ammo_battery( "battery" ); static const damage_type_id damage_bash( "bash" ); -static const diseasetype_id disease_bad_food( "bad_food" ); - static const efftype_id effect_boomered( "boomered" ); static const efftype_id effect_crushed( "crushed" ); static const efftype_id effect_fake_common_cold( "fake_common_cold" ); @@ -141,6 +139,8 @@ static const item_group_id Item_spawn_data_default_zombie_items( "default_zombie static const itype_id itype_battery( "battery" ); static const itype_id itype_nail( "nail" ); +static const itype_id itype_water( "water" ); +static const itype_id itype_water_murky( "water_murky" ); static const material_id material_glass( "glass" ); @@ -2302,18 +2302,13 @@ bool map::ter_set( const tripoint &p, const ter_id &new_terrain, bool avoid_crea set_seen_cache_dirty( p ); } - if( new_t.has_flag( "SPAWN_WITH_LIQUID" ) ) { - if( new_t.has_flag( "FRESH_WATER" ) ) { - item water( "water", calendar::start_of_cataclysm ); - // TODO: Move all numeric values to json - water.charges = rng( 40, 240 ); - if( new_t.has_flag( ter_furn_flag::TFLAG_MURKY ) ) { - water.poison = rng( 1, 6 ); - water.get_comestible()->parasites = 5; - water.get_comestible()->contamination = { { disease_bad_food, 5 } }; - } - add_item( p, water ); - } + if( new_t.has_flag( "SPAWN_WITH_WATER" ) ) { + itype_id water_type = new_t.has_flag( ter_furn_flag::TFLAG_MURKY ) ? itype_water_murky : + itype_water; + item water( water_type, calendar::start_of_cataclysm ); + // TODO: Move all numeric values to json + water.charges = rng( 40, 240 ); + add_item( p, water ); } invalidate_max_populated_zlev( p.z ); @@ -5493,64 +5488,21 @@ item map::water_from( const tripoint &p ) return ret; } - if( has_flag( ter_furn_flag::TFLAG_MURKY, p ) ) { - for( item &ret : get_map().i_at( p ) ) { - if( ret.made_of( phase_id::LIQUID ) ) { - ret.set_item_temperature( std::max( weather.get_temperature( p ), - temperatures::cold ) ); - ret.poison = rng( 1, 6 ); - ret.get_comestible()->parasites = 5; - ret.get_comestible()->contamination = { { disease_bad_food, 5 } }; - return ret; - } - } - } - - if( has_flag( ter_furn_flag::TFLAG_TOILET_WATER, p ) ) { - for( item &ret : get_map().i_at( p ) ) { - if( ret.made_of( phase_id::LIQUID ) ) { - ret.set_item_temperature( std::max( weather.get_temperature( p ), - temperatures::cold ) ); - ret.poison = one_in( 3 ) ? 0 : rng( 1, 3 ); - return ret; - } - } - } - const ter_id terrain_id = ter( p ); if( terrain_id == ter_t_sewage ) { item ret( "water_sewage", calendar::turn, item::INFINITE_CHARGES ); ret.set_item_temperature( std::max( weather.get_temperature( p ), temperatures::cold ) ); - ret.poison = rng( 1, 7 ); return ret; } - item ret( "water", calendar::turn, item::INFINITE_CHARGES ); - ret.set_item_temperature( std::max( weather.get_temperature( p ), - temperatures::cold ) ); // iexamine::water_source requires a valid liquid from this function. - if( terrain_id->has_examine( iexamine::water_source ) ) { - int poison_chance = 0; - if( terrain_id.obj().has_flag( ter_furn_flag::TFLAG_DEEP_WATER ) ) { - if( terrain_id.obj().has_flag( ter_furn_flag::TFLAG_CURRENT ) ) { - poison_chance = 20; - } else { - poison_chance = 4; - } - } else { - if( terrain_id.obj().has_flag( ter_furn_flag::TFLAG_CURRENT ) ) { - poison_chance = 10; - } else { - poison_chance = 3; - } - } - if( one_in( poison_chance ) ) { - ret.poison = rng( 1, 4 ); - } - return ret; - } - if( furn( p )->has_examine( iexamine::water_source ) ) { + if( terrain_id->has_examine( iexamine::water_source ) || + furn( p )->has_examine( iexamine::water_source ) ) { + itype_id liquid_id = has_flag( ter_furn_flag::TFLAG_MURKY, p ) ? itype_water_murky : itype_water; + item ret( liquid_id, calendar::turn, item::INFINITE_CHARGES ); + ret.set_item_temperature( std::max( weather.get_temperature( p ), + temperatures::cold ) ); return ret; } return item(); diff --git a/src/weather.cpp b/src/weather.cpp index 6886c7d62ee63..a7689c0c8ae3f 100644 --- a/src/weather.cpp +++ b/src/weather.cpp @@ -246,15 +246,10 @@ void item::add_rain_to_container( int charges ) ret.charges = std::min( charges, capa ); if( contents.can_contain( ret ).success() ) { // This is easy. Just add 1 charge of the rain liquid to the container. - // Funnels aren't always clean enough for water. // TODO: disinfectant squeegie->funnel - ret.poison = one_in( 10 ) ? 1 : 0; put_in( ret, pocket_type::CONTAINER ); } else { - static const std::set allowed_liquid_types{ - itype_water - }; item *found_liq = contents.get_item_with( [&]( const item & liquid ) { - return allowed_liquid_types.count( liquid.typeId() ); + return liquid.typeId() == itype_water; } ); if( found_liq == nullptr ) { debugmsg( "Rainwater failed to add to container" );