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;