diff --git a/data/json/flags.json b/data/json/flags.json index 513618b54a254..4c9350ed62836 100644 --- a/data/json/flags.json +++ b/data/json/flags.json @@ -2479,5 +2479,10 @@ "id": "PREFIX_XS", "type": "json_flag", "item_prefix": "XS " + }, + { + "id": "DECAYS_IN_AIR", + "type": "json_flag", + "info": "This will eventually go bad if left in the open air too long." } ] diff --git a/data/json/items/comestibles/drink.json b/data/json/items/comestibles/drink.json index cfb308f4c232e..57c3d12aedaf1 100644 --- a/data/json/items/comestibles/drink.json +++ b/data/json/items/comestibles/drink.json @@ -1491,6 +1491,7 @@ "color": "light_blue", "container": "bottle_plastic", "sealed": false, + "delete": { "flags": [ "DECAYS_IN_AIR" ] }, "contamination": [ { "disease": "highly_contaminated_food", "probability": 0.1 }, { "disease": "bad_food", "probability": 1 } ] }, { @@ -1518,12 +1519,15 @@ "phase": "liquid", "quench": 50, "ammo_data": { "ammo_type": "water" }, - "flags": [ "EATEN_COLD", "NUTRIENT_OVERRIDE" ], + "flags": [ "EATEN_COLD", "NUTRIENT_OVERRIDE", "DECAYS_IN_AIR" ], "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" + "color": "light_cyan", + "//": "Clean water becomes regular water in about 4 weeks if exposed to air (1 week if outdoors).", + "properties": { "max_air_exposure_hours": "720" }, + "revert_to": "water" }, { "id": "water_spring", diff --git a/src/active_item_cache.cpp b/src/active_item_cache.cpp index 0d7d109d4ff5e..f7aa017c1d742 100644 --- a/src/active_item_cache.cpp +++ b/src/active_item_cache.cpp @@ -17,6 +17,15 @@ float item_reference::spoil_multiplier() } ); } +bool item_reference::has_watertight_container() +{ + return std::any_of( + pocket_chain.begin(), pocket_chain.end(), + []( item_pocket const * pk ) { + return pk->can_contain_liquid( false ); + } ); +} + bool active_item_cache::add( item &it, point location, item *parent, std::vector const &pocket_chain ) { diff --git a/src/active_item_cache.h b/src/active_item_cache.h index 2533198110642..41f097130adb9 100644 --- a/src/active_item_cache.h +++ b/src/active_item_cache.h @@ -22,6 +22,7 @@ struct item_reference { std::vector pocket_chain; float spoil_multiplier(); + bool has_watertight_container(); }; enum class special_item_type : int { diff --git a/src/flag.cpp b/src/flag.cpp index 7ab1e8039d0c3..93fdc2f49f446 100644 --- a/src/flag.cpp +++ b/src/flag.cpp @@ -86,6 +86,7 @@ const flag_id flag_CUT_HARVEST( "CUT_HARVEST" ); const flag_id flag_CUT_IMMUNE( "CUT_IMMUNE" ); const flag_id flag_DANGEROUS( "DANGEROUS" ); const flag_id flag_DEAF( "DEAF" ); +const flag_id flag_DECAYS_IN_AIR( "DECAYS_IN_AIR" ); const flag_id flag_DIAMOND( "DIAMOND" ); const flag_id flag_DIG_TOOL( "DIG_TOOL" ); const flag_id flag_DIMENSIONAL_ANCHOR( "DIMENSIONAL_ANCHOR" ); diff --git a/src/flag.h b/src/flag.h index 38cf65efa7376..30d0ddc2e35cf 100644 --- a/src/flag.h +++ b/src/flag.h @@ -94,6 +94,7 @@ extern const flag_id flag_CUT_HARVEST; extern const flag_id flag_CUT_IMMUNE; extern const flag_id flag_DANGEROUS; extern const flag_id flag_DEAF; +extern const flag_id flag_DECAYS_IN_AIR; extern const flag_id flag_DIAMOND; extern const flag_id flag_DIG_TOOL; extern const flag_id flag_DIMENSIONAL_ANCHOR; diff --git a/src/item.cpp b/src/item.cpp index e6e4df62e4fdd..e0292f486b85f 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -661,6 +661,7 @@ item &item::convert( const itype_id &new_type, Character *carrier ) carrier->on_item_acquire( *this ); } + item_counter = 0; update_prefix_suffix_flags(); return *this; } @@ -1413,6 +1414,10 @@ bool item::combine( const item &rhs ) set_item_specific_energy( units::from_joule_per_gram( combined_specific_energy ) ); } + if( item_counter > 0 || rhs.item_counter > 0 ) { + item_counter = ( static_cast( item_counter ) * charges + static_cast + ( rhs.item_counter ) * rhs.charges ) / ( charges + rhs.charges ); + } } charges += rhs.charges; if( !rhs.has_flag( flag_NO_PARASITES ) ) { @@ -2539,6 +2544,8 @@ void item::debug_info( std::vector &info, const iteminfo_query *parts, active ); info.emplace_back( "BASE", _( "burn: " ), "", iteminfo::lower_is_better, burnt ); + info.emplace_back( "BASE", _( "counter: " ), "", iteminfo::lower_is_better, + item_counter ); if( countdown_point != calendar::turn_max ) { info.emplace_back( "BASE", _( "countdown: " ), "", iteminfo::lower_is_better, to_seconds( countdown_point - calendar::turn ) ); @@ -8016,6 +8023,23 @@ void item::calc_rot_while_processing( time_duration processing_duration ) last_temp_check += processing_duration; } +bool item::process_decay_in_air( map &here, Character *carrier, const tripoint &pos, + int max_air_exposure_hours, + time_duration time_delta ) +{ + if( !has_own_flag( flag_FROZEN ) ) { + double environment_multiplier = here.is_outside( pos ) ? 2.0 : 1.0; + time_duration new_air_exposure = time_duration::from_seconds( item_counter ) + time_delta * + rng_normal( 0.9, 1.1 ) * environment_multiplier; + if( new_air_exposure >= time_duration::from_hours( max_air_exposure_hours ) ) { + convert( *type->revert_to, carrier ); + return true; + } + item_counter = to_seconds( new_air_exposure ); + } + return false; +} + int item::get_env_resist( int override_base_resist ) const { const islot_armor *t = find_armor_data(); @@ -12689,7 +12713,7 @@ void item::apply_freezerburn() } bool item::process_temperature_rot( float insulation, const tripoint &pos, map &here, - Character *carrier, const temperature_flag flag, float spoil_modifier ) + Character *carrier, const temperature_flag flag, float spoil_modifier, bool watertight_container ) { const time_point now = calendar::turn; @@ -12739,6 +12763,10 @@ bool item::process_temperature_rot( float insulation, const tripoint &pos, map & time_point time = last_temp_check; item_internal::scoped_goes_bad_cache _cache( this ); const bool process_rot = goes_bad() && spoil_modifier != 0; + const bool decays_in_air = !watertight_container && has_flag( flag_DECAYS_IN_AIR ) && + type->revert_to; + int64_t max_air_exposure_hours = decays_in_air ? get_property_int64_t( "max_air_exposure_hours" ) : + 0; if( now - time > 1_hours ) { // This code is for items that were left out of reality bubble for long time @@ -12803,6 +12831,11 @@ bool item::process_temperature_rot( float insulation, const tripoint &pos, map & } last_temp_check = time; + if( decays_in_air && + process_decay_in_air( here, carrier, pos, max_air_exposure_hours, time_delta ) ) { + return false; + } + // Calculate item rot if( process_rot ) { calc_rot( env_temperature, spoil_modifier, time_delta ); @@ -12820,6 +12853,12 @@ bool item::process_temperature_rot( float insulation, const tripoint &pos, map & if( now - time > smallest_interval ) { calc_temp( temp, insulation, now - time ); last_temp_check = now; + + if( decays_in_air && + process_decay_in_air( here, carrier, pos, max_air_exposure_hours, now - time ) ) { + return false; + } + if( process_rot ) { calc_rot( temp, spoil_modifier, now - time ); return has_rotten_away() && carrier == nullptr; @@ -14075,14 +14114,15 @@ bool item::process_gun_cooling( Character *carrier ) } bool item::process( map &here, Character *carrier, const tripoint &pos, float insulation, - temperature_flag flag, float spoil_multiplier_parent, bool recursive ) + temperature_flag flag, float spoil_multiplier_parent, bool watertight_container, bool recursive ) { process_relic( carrier, pos ); if( recursive ) { contents.process( here, carrier, pos, type->insulation_factor * insulation, flag, - spoil_multiplier_parent ); + spoil_multiplier_parent, watertight_container ); } - return process_internal( here, carrier, pos, insulation, flag, spoil_multiplier_parent ); + return process_internal( here, carrier, pos, insulation, flag, spoil_multiplier_parent, + watertight_container ); } bool item::leak( map &here, Character *carrier, const tripoint &pos, item_pocket *pocke ) @@ -14109,7 +14149,7 @@ void item::set_last_temp_check( const time_point &pt ) } bool item::process_internal( map &here, Character *carrier, const tripoint &pos, - float insulation, const temperature_flag flag, float spoil_modifier ) + float insulation, const temperature_flag flag, float spoil_modifier, bool watertight_container ) { if( ethereal ) { if( !has_var( "ethereal" ) ) { @@ -14228,7 +14268,8 @@ bool item::process_internal( map &here, Character *carrier, const tripoint &pos, } // All foods that go bad have temperature if( has_temperature() && - process_temperature_rot( insulation, pos, here, carrier, flag, spoil_modifier ) ) { + process_temperature_rot( insulation, pos, here, carrier, flag, spoil_modifier, + watertight_container ) ) { if( is_comestible() ) { here.rotten_item_spawn( *this, pos ); } diff --git a/src/item.h b/src/item.h index b62183adf45ad..2e0f19f5d23d2 100644 --- a/src/item.h +++ b/src/item.h @@ -1051,7 +1051,8 @@ class item : public visitable * @return true if the item is fully rotten and is ready to be removed */ bool process_temperature_rot( float insulation, const tripoint &pos, map &here, Character *carrier, - temperature_flag flag = temperature_flag::NORMAL, float spoil_modifier = 1.0f ); + temperature_flag flag = temperature_flag::NORMAL, float spoil_modifier = 1.0f, + bool watertight_container = false ); /** Set the item to HOT and resets last_temp_check */ void heat_up(); @@ -1448,7 +1449,7 @@ class item : public visitable */ bool process( map &here, Character *carrier, const tripoint &pos, float insulation = 1, temperature_flag flag = temperature_flag::NORMAL, float spoil_multiplier_parent = 1.0f, - bool recursive = true ); + bool watertight_container = false, bool recursive = true ); bool leak( map &here, Character *carrier, const tripoint &pos, item_pocket *pocke = nullptr ); @@ -3006,8 +3007,8 @@ class item : public visitable const use_function *get_use_internal( const std::string &use_name ) const; template static Item *get_usable_item_helper( Item &self, const std::string &use_name ); - bool process_internal( map &here, Character *carrier, const tripoint &pos, float insulation = 1, - temperature_flag flag = temperature_flag::NORMAL, float spoil_modifier = 1.0f ); + bool process_internal( map &here, Character *carrier, const tripoint &pos, float insulation, + temperature_flag flag, float spoil_modifier, bool watertight_container ); void iterate_covered_body_parts_internal( side s, const std::function &cb ) const; void iterate_covered_sub_body_parts_internal( side s, @@ -3073,6 +3074,10 @@ class item : public visitable bool process_blackpowder_fouling( Character *carrier ); bool process_gun_cooling( Character *carrier ); bool process_tool( Character *carrier, const tripoint &pos ); + bool process_decay_in_air( map &here, Character *carrier, const tripoint &pos, + int max_air_exposure_hours, + time_duration time_delta ); + public: static const int INFINITE_CHARGES; diff --git a/src/item_contents.cpp b/src/item_contents.cpp index cc12c26f422df..696ec839c7972 100644 --- a/src/item_contents.cpp +++ b/src/item_contents.cpp @@ -2494,11 +2494,12 @@ void item_contents::remove_internal( const std::function &filter } void item_contents::process( map &here, Character *carrier, const tripoint &pos, float insulation, - temperature_flag flag, float spoil_multiplier_parent ) + temperature_flag flag, float spoil_multiplier_parent, bool watertight_container ) { for( item_pocket &pocket : contents ) { if( pocket.is_type( pocket_type::CONTAINER ) ) { - pocket.process( here, carrier, pos, insulation, flag, spoil_multiplier_parent ); + pocket.process( here, carrier, pos, insulation, flag, spoil_multiplier_parent, + watertight_container ); } } } diff --git a/src/item_contents.h b/src/item_contents.h index 6406dc57c4a32..f24b341347369 100644 --- a/src/item_contents.h +++ b/src/item_contents.h @@ -352,7 +352,8 @@ class item_contents * NOTE: this destroys the items that get processed */ void process( map &here, Character *carrier, const tripoint &pos, float insulation = 1, - temperature_flag flag = temperature_flag::NORMAL, float spoil_multiplier_parent = 1.0f ); + temperature_flag flag = temperature_flag::NORMAL, float spoil_multiplier_parent = 1.0f, + bool watertight_container = false ); void leak( map &here, Character *carrier, const tripoint &pos, item_pocket *pocke = nullptr ); diff --git a/src/item_pocket.cpp b/src/item_pocket.cpp index 1a74aa1d1681c..3444ca72cf722 100644 --- a/src/item_pocket.cpp +++ b/src/item_pocket.cpp @@ -899,7 +899,7 @@ bool item_pocket::detonate( const tripoint &pos, std::vector &drops ) } bool item_pocket::process( const itype &type, map &here, Character *carrier, const tripoint &pos, - float insulation, const temperature_flag flag ) + float insulation, temperature_flag flag, bool watertight_container ) { bool processed = false; float spoil_multiplier = 1.0f; @@ -908,7 +908,7 @@ bool item_pocket::process( const itype &type, map &here, Character *carrier, con spoil_multiplier = 0.0f; } if( it->process( here, carrier, pos, type.insulation_factor * insulation, flag, - spoil_multiplier ) ) { + spoil_multiplier, watertight_container ) ) { it->spill_contents( pos ); it = contents.erase( it ); processed = true; @@ -1983,12 +1983,13 @@ void item_pocket::remove_items_if( const std::function &filter ) } void item_pocket::process( map &here, Character *carrier, const tripoint &pos, float insulation, - temperature_flag flag, float spoil_multiplier_parent ) + temperature_flag flag, float spoil_multiplier_parent, bool watertight_container ) { for( auto iter = contents.begin(); iter != contents.end(); ) { if( iter->process( here, carrier, pos, insulation, flag, // spoil multipliers on pockets are not additive or multiplicative, they choose the best - std::min( spoil_multiplier_parent, spoil_multiplier() ) ) ) { + std::min( spoil_multiplier_parent, spoil_multiplier() ), + watertight_container || can_contain_liquid( false ) ) ) { iter->spill_contents( pos ); iter = contents.erase( iter ); } else { diff --git a/src/item_pocket.h b/src/item_pocket.h index 3b85426c3a169..ce31ea4b8b6f1 100644 --- a/src/item_pocket.h +++ b/src/item_pocket.h @@ -279,7 +279,7 @@ class item_pocket std::string translated_sealed_prefix() const; bool detonate( const tripoint &p, std::vector &drops ); bool process( const itype &type, map &here, Character *carrier, const tripoint &pos, - float insulation, temperature_flag flag ); + float insulation, temperature_flag flag, bool watertight_container ); void remove_all_ammo( Character &guy ); void remove_all_mods( Character &guy ); @@ -304,7 +304,8 @@ class item_pocket * NOTE: this destroys the items that get processed */ void process( map &here, Character *carrier, const tripoint &pos, float insulation = 1, - temperature_flag flag = temperature_flag::NORMAL, float spoil_multiplier_parent = 1.0f ); + temperature_flag flag = temperature_flag::NORMAL, float spoil_multiplier_parent = 1.0f, + bool watertight_container = false ); void leak( map &here, Character *carrier, const tripoint &pos, item_pocket *pocke = nullptr ); diff --git a/src/map.cpp b/src/map.cpp index 3efccc6cb9183..550f3608ead3f 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -5561,10 +5561,11 @@ void map::update_lum( item_location &loc, bool add ) } static bool process_map_items( map &here, item_stack &items, safe_reference &item_ref, - item *parent, const tripoint &location, const float insulation, - const temperature_flag flag, const float spoil_multiplier ) + item *parent, const tripoint &location, float insulation, + temperature_flag flag, float spoil_multiplier, bool watertight_container ) { - if( item_ref->process( here, nullptr, location, insulation, flag, spoil_multiplier, false ) ) { + if( item_ref->process( here, nullptr, location, insulation, flag, spoil_multiplier, + watertight_container, false ) ) { // Item is to be destroyed so erase it from the map stack // unless it was already destroyed by processing. if( item_ref ) { @@ -5760,7 +5761,8 @@ void map::process_items_in_submap( submap ¤t_submap, const tripoint &gridp process_map_items( *this, items, active_item_ref.item_ref, active_item_ref.parent, map_location, 1, flag, - spoil_multiplier * active_item_ref.spoil_multiplier() ); + spoil_multiplier * active_item_ref.spoil_multiplier(), + active_item_ref.has_watertight_container() ); } } @@ -5841,7 +5843,7 @@ void map::process_items_in_vehicle( vehicle &cur_veh, submap ¤t_submap ) } if( !process_map_items( *this, items, active_item_ref.item_ref, active_item_ref.parent, item_loc, it_insulation, flag, - active_item_ref.spoil_multiplier() ) ) { + active_item_ref.spoil_multiplier(), active_item_ref.has_watertight_container() ) ) { // If the item was NOT destroyed, we can skip the remainder, // which handles fallout from the vehicle being damaged. continue;