diff --git a/src/game.cpp b/src/game.cpp index 9a95a53d9f0b..a36aa8fd7cb3 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -636,7 +636,6 @@ bool game::start_game() } u.process_turn(); // process_turn adds the initial move points u.set_stamina( u.get_stamina_max() ); - get_weather().temperature = SPRING_TEMPERATURE; get_weather().update_weather(); u.next_climate_control_check = calendar::before_time_starts; // Force recheck at startup u.last_climate_control_ret = false; diff --git a/src/game_constants.h b/src/game_constants.h index c7af072bedaa..5de254092fc4 100644 --- a/src/game_constants.h +++ b/src/game_constants.h @@ -78,34 +78,37 @@ static constexpr int PLUTONIUM_CHARGES = 500; // Temperature constants. namespace temperatures { + +/// Average annual temperature used for climate, weather and temperature calculation. +constexpr units::temperature annual_average = 6_c; + // temperature at which something starts is considered HOT. -constexpr units::temperature hot = 100_f; // ~ 38 Celsius +constexpr units::temperature hot = 38_c; // the "normal" temperature midpoint between cold and hot. -constexpr units::temperature normal = 70_f; // ~ 21 Celsius +constexpr units::temperature normal = 21_c; -// Temperature inside an active fridge in Fahrenheit. -constexpr units::temperature fridge = 37_f; // ~ 2.7 Celsius +// Temperature inside an active fridge +constexpr units::temperature fridge = 2_c; // Temperature at which things are considered "cold". -constexpr units::temperature cold = 40_f; // ~4.4 C +constexpr units::temperature cold = 5_c; -// Temperature inside an active freezer in Fahrenheit. -constexpr units::temperature freezer = 23_f; // -5 Celsius +// Temperature inside an active freezer. +constexpr units::temperature freezer = -5_c; -// Temperature in which water freezes in Fahrenheit. -constexpr units::temperature freezing = 32_f; // 0 Celsius +// Temperature in which water freezes. +constexpr units::temperature freezing = 0_c; // Arbitrary constant for root cellar temperature -// Should be equal to AVERAGE_ANNUAL_TEMPERATURE, but is declared before it... -constexpr units::temperature root_cellar = 43_f; +constexpr units::temperature root_cellar = annual_average; } // namespace temperatures // Weight per level of LIFT/JACK tool quality. -#define TOOL_LIFT_FACTOR 500_kilogram // 500kg/level +static constexpr units::mass TOOL_LIFT_FACTOR = 500_kilogram; // 500kg/level // Cap JACK requirements to support arbitrarily large vehicles. -#define JACK_LIMIT 8500_kilogram // 8500kg ( 8.5 metric tonnes ) +static constexpr units::mass JACK_LIMIT = 8500_kilogram; // Slowest speed at which a gun can be aimed. static constexpr int MAX_AIM_COST = 10; @@ -139,18 +142,6 @@ static constexpr int BIO_CQB_LEVEL = 5; // Minimum size of a horde to show up on the minimap. static constexpr int HORDE_VISIBILITY_SIZE = 3; -/** - * Average annual temperature in F used for climate, weather and temperature calculation. - * Average New England temperature = 43F/6C rounded to int. -*/ -static constexpr int AVERAGE_ANNUAL_TEMPERATURE = 43; - -/** - * Base starting spring temperature in F used for climate, weather and temperature calculation. - * New England base spring temperature = 65F/18C rounded to int. -*/ -static constexpr int SPRING_TEMPERATURE = 65; - /** * Used to limit the random seed during noise calculation. A large value flattens the noise generator to zero. * Windows has a rand limit of 32768, other operating systems can have higher limits. diff --git a/src/item.cpp b/src/item.cpp index a1cb81ab4478..89618c6a05f7 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -95,6 +95,7 @@ #include "text_snippets.h" #include "translations.h" #include "units.h" +#include "units_temperature.h" #include "units_utility.h" #include "value_ptr.h" #include "vehicle.h" @@ -5610,78 +5611,57 @@ int item::spoilage_sort_order() const return bottom; } +namespace +{ + /** - * Food decay calculation. - * Calculate how much food rots per hour, based on 10 = 1 minute of decay @ 65 F. + * Hardcoded lookup table for food rots per hour calculation. + * * IRL this tends to double every 10c a few degrees above freezing, but past a certain * point the rate decreases until even extremophiles find it too hot. Here we just stop - * further acceleration at 105 F. This should only need to run once when the game starts. - * @see calc_rot_array - * @see rot_chart + * further acceleration at 40C. + * + * Original formula: + * @see https://github.com/cataclysmbnteam/Cataclysm-BN/blob/033901af4b52ad0bfcfd6abfe06bca4e403d44b1/src/item.cpp#L5612-L5640 */ -static int calc_hourly_rotpoints_at_temp( const int temp ) +constexpr auto rot_chart = std::array { - // default temp = 65, so generic->rotten() assumes 600 decay points per hour - const int dropoff = 38; // ditch our fancy equation and do a linear approach to 0 rot at 31f - const int cutoff = 105; // stop torturing the player at this temperature, which is - const int cutoffrot = 21240; // ..almost 6 times the base rate. bacteria hate the heat too - - const int dsteps = dropoff - units::to_fahrenheit( temperatures::freezing ); - const int dstep = ( 215.46 * std::pow( 2.0, static_cast( dropoff ) / 16.0 ) / dsteps ); - - if( temp < units::to_fahrenheit( temperatures::freezing ) ) { - return 0; - } else if( temp > cutoff ) { - return cutoffrot; - } else if( temp < dropoff ) { - return ( temp - units::to_fahrenheit( temperatures::freezing ) ) * dstep; - } else { - return std::lround( 215.46 * std::pow( 2.0, static_cast( temp ) / 16.0 ) ); - } -} + 0, 372, 744, 1118, 1219, 1273, 1388, 1514, 1651, 1800, + 1880, 2050, 2235, 2438, 2658, 2776, 3027, 3301, 3600, 3926, + 4100, 4471, 4875, 5317, 5798, 6054, 6602, 7200, 7852, 8562, + 8941, 9751, 10633, 11595, 12645, 13205, 14400, 15703, 17125, 18674, + 19501, +}; -/** - * Initialize the rot table. - * @see rot_chart - */ -static std::vector calc_rot_array( const size_t cap ) -{ - std::vector ret; - ret.reserve( cap ); - for( size_t i = 0; i < cap; ++i ) { - ret.push_back( calc_hourly_rotpoints_at_temp( static_cast( i ) ) ); - } - return ret; -} +} // namespace /** * Get the hourly rot for a given temperature from the precomputed table. * @see rot_chart */ -int get_hourly_rotpoints_at_temp( const int temp ) +auto get_hourly_rotpoints_at_temp( const units::temperature temp ) -> int { /** * Precomputed rot lookup table. */ - static const std::vector rot_chart = calc_rot_array( 200 ); - - if( temp < 0 ) { + if( temp < temperatures::freezing ) { return 0; } - if( temp > 150 ) { + if( temp > 40_c ) { return 21240; } - return rot_chart[temp]; + const int temp_c = units::to_celsius( temp ); + return rot_chart[temp_c]; } -time_duration item::calc_rot( time_point time, int temp ) const +auto item::calc_rot( time_point time, const units::temperature temp ) const -> time_duration { // Avoid needlessly calculating already rotten things. Corpses should // always rot away and food rots away at twice the shelf life. If the food // is in a sealed container they won't rot away, this avoids needlessly // calculating their rot in that case. if( !is_corpse() && get_relative_rot() > 2.0 ) { - return time_duration::from_seconds( 0 ); + return 0_seconds; } // rot modifier @@ -5690,7 +5670,7 @@ time_duration item::calc_rot( time_point time, int temp ) const factor = 0.75; } - time_duration added_rot = time_duration::from_seconds( 0 ); + time_duration added_rot = 0_seconds; // simulation of different age of food at the start of the game and good/bad storage // conditions by applying starting variation bonus/penalty of +/- 20% of base shelf-life // positive = food was produced some time before calendar::start and/or bad storage @@ -5699,34 +5679,38 @@ time_duration item::calc_rot( time_point time, int temp ) const time_duration spoil_variation = get_shelf_life() * 0.2f; added_rot += rng( -spoil_variation, spoil_variation ); } - time_duration time_delta = time - last_rot_check; added_rot += factor * time_delta / 1_hours * get_hourly_rotpoints_at_temp( temp ) * 1_turns; return added_rot; } -static int temperature_flag_to_highest_temperature( temperature_flag temperature ) +namespace +{ + +auto temperature_flag_to_highest_temperature( temperature_flag temperature ) -> units::temperature { switch( temperature ) { case temperature_flag::TEMP_NORMAL: - return INT_MAX; case temperature_flag::TEMP_HEATER: - return INT_MAX; + return units::temperature_max; case temperature_flag::TEMP_FRIDGE: - return to_fahrenheit( temperatures::fridge ); + return temperatures::fridge; case temperature_flag::TEMP_FREEZER: - return to_fahrenheit( temperatures::freezer ); + return temperatures::freezer; case temperature_flag::TEMP_ROOT_CELLAR: - return to_fahrenheit( temperatures::root_cellar ); + return temperatures::root_cellar; } - return INT_MAX; + return units::temperature_max; } +} // namespace + + time_duration item::minimum_freshness_duration( temperature_flag temperature ) const { - int temperature_f = temperature_flag_to_highest_temperature( temperature ); - unsigned long long rot_per_hour = get_hourly_rotpoints_at_temp( temperature_f ); + const units::temperature temp = temperature_flag_to_highest_temperature( temperature ); + unsigned long long rot_per_hour = get_hourly_rotpoints_at_temp( temp ); if( rot_per_hour <= 0 || !type->comestible ) { return calendar::INDEFINITELY_LONG_DURATION; @@ -9008,7 +8992,7 @@ bool item::process_rot( const bool seals, const tripoint &pos, // process rot at most once every 100_turns (10 min) // note we're also gated by item::processing_speed - time_duration smallest_interval = 10_minutes; + constexpr time_duration smallest_interval = 10_minutes; units::temperature temp = units::from_fahrenheit( weather.get_temperature( pos ) ); temp = clip_by_temperature_flag( temp, flag ); @@ -9040,17 +9024,13 @@ bool item::process_rot( const bool seals, const tripoint &pos, calendar::config, seed ); env_temperature_raw = weather_temperature + local_mod; } else { - env_temperature_raw = units::from_fahrenheit( AVERAGE_ANNUAL_TEMPERATURE ) + local_mod; + env_temperature_raw = temperatures::annual_average + local_mod; } units::temperature env_temperature_clipped = clip_by_temperature_flag( env_temperature_raw, flag ); - // Lookup table is in F - int final_temperature_in_fahrenheit = static_cast( std::round( units::to_fahrenheit - ( env_temperature_clipped ) ) ); - // Calculate item rot - rot += calc_rot( time, final_temperature_in_fahrenheit ); + rot += calc_rot( time, env_temperature_clipped ); last_rot_check = time; if( has_rotten_away() && carrier == nullptr && !seals ) { @@ -9063,10 +9043,7 @@ bool item::process_rot( const bool seals, const tripoint &pos, // Remaining <1 h from above // and items that are held near the player if( now - time > smallest_interval ) { - int final_temperature_in_fahrenheit = static_cast( std::round( units::to_fahrenheit - ( temp ) ) ); - - rot += calc_rot( now, final_temperature_in_fahrenheit ); + rot += calc_rot( now, temp ); last_rot_check = now; return has_rotten_away() && carrier == nullptr && !seals; diff --git a/src/item.h b/src/item.h index b1d4fa7b02fc..043e95d88890 100644 --- a/src/item.h +++ b/src/item.h @@ -771,7 +771,7 @@ class item : public visitable * @param time Time point to which rot is calculated * @param temp Temperature at which the rot is calculated */ - time_duration calc_rot( time_point time, int temp ) const; + auto calc_rot( time_point time, const units::temperature temp ) const -> time_duration; /** * Time that this item is guaranteed to stay fresh. diff --git a/src/timed_event.cpp b/src/timed_event.cpp index 7ccdd61adb05..156d0d98d7b6 100644 --- a/src/timed_event.cpp +++ b/src/timed_event.cpp @@ -283,7 +283,7 @@ void timed_event::per_turn() } } - if( calendar::once_every( time_duration::from_seconds( 10 ) ) && faults ) { + if( calendar::once_every( 10_seconds ) && faults ) { add_msg( m_info, "You hear someone whispering \"%s\"", SNIPPET.random_from_category( "amigara_whispers" ).value_or( translation() ) ); } diff --git a/src/units_temperature.h b/src/units_temperature.h index e5ae81b0c203..c5d1cec16d4f 100644 --- a/src/units_temperature.h +++ b/src/units_temperature.h @@ -148,6 +148,11 @@ inline constexpr units::temperature operator"" _c( const unsigned long long v ) return units::from_celsius( v ); } +inline constexpr units::temperature operator"" _c( const long double v ) +{ + return units::from_celsius( static_cast( v ) ); +} + inline constexpr units::temperature operator"" _f( const unsigned long long v ) { return units::from_fahrenheit( v ); diff --git a/src/weather.cpp b/src/weather.cpp index 9afab69b9ce0..01d596a68cd9 100644 --- a/src/weather.cpp +++ b/src/weather.cpp @@ -487,24 +487,27 @@ void weather_effect::light_acid( int intensity ) */ void weather_effect::acid( int intensity ) { - if( calendar::once_every( time_duration::from_seconds( intensity ) ) && is_player_outside() ) { - if( g->u.primary_weapon().has_flag( "RAIN_PROTECT" ) && one_in( 4 ) ) { - add_msg( _( "Your umbrella protects you from the acid rain." ) ); - } else { - if( g->u.worn_with_flag( "RAINPROOF" ) && one_in( 2 ) ) { - add_msg( _( "Your clothing protects you from the acid rain." ) ); - } else { - bool has_helmet = false; - if( g->u.is_wearing_power_armor( &has_helmet ) && ( has_helmet || !one_in( 2 ) ) ) { - add_msg( _( "Your power armor protects you from the acid rain." ) ); - } else { - add_msg( m_bad, _( "The acid rain burns!" ) ); - if( one_in( 2 ) && ( g->u.get_pain() < 100 ) ) { - g->u.mod_pain( rng( 1, 5 ) ); - } - } - } - } + if( !( calendar::once_every( time_duration::from_seconds( intensity ) ) && is_player_outside() ) ) { + return; + } + + auto &you = get_avatar(); + if( you.primary_weapon().has_flag( "RAIN_PROTECT" ) && one_in( 4 ) ) { + return add_msg( _( "Your umbrella protects you from the acid rain." ) ); + } + + if( you.worn_with_flag( "RAINPROOF" ) && one_in( 2 ) ) { + return add_msg( _( "Your clothing protects you from the acid rain." ) ); + } + + bool has_helmet = false; + if( you.is_wearing_power_armor( &has_helmet ) && ( has_helmet || !one_in( 2 ) ) ) { + return add_msg( _( "Your power armor protects you from the acid rain." ) ); + } + + add_msg( m_bad, _( "The acid rain burns!" ) ); + if( one_in( 2 ) && ( you.get_pain() < 100 ) ) { + you.mod_pain( rng( 1, 5 ) ); } } @@ -1058,44 +1061,47 @@ void weather_manager::update_weather() w_point &w = weather_precise; winddirection = wind_direction_override ? *wind_direction_override : w.winddirection; windspeed = windspeed_override ? *windspeed_override : w.windpower; - if( !weather_id || calendar::turn >= nextweather ) { - const weather_generator &weather_gen = get_cur_weather_gen(); - w = weather_gen.get_weather( g->u.global_square_location(), calendar::turn, g->get_seed() ); - weather_type_id old_weather = weather_id; - weather_id = weather_override ? weather_override : weather_gen.get_weather_conditions( w ); - if( !g->u.has_artifact_with( AEP_BAD_WEATHER ) ) { - weather_override = weather_type_id::NULL_ID(); - } - sfx::do_ambient(); - temperature = units::to_fahrenheit( w.temperature ); - lightning_active = false; - // Check weather every few turns, instead of every turn. - // TODO: predict when the weather changes and use that time. - nextweather = calendar::turn + 5_minutes; - if( weather_id != old_weather && weather_id->dangerous && - g->get_levz() >= 0 && get_map().is_outside( g->u.pos() ) - && !g->u.has_activity( ACT_WAIT_WEATHER ) ) { - g->cancel_activity_or_ignore_query( distraction_type::weather_change, - string_format( _( "The weather changed to %s!" ), weather_id->name ) ); - } + if( weather_id && calendar::turn < nextweather ) { + return; + } - if( weather_id != old_weather && g->u.has_activity( ACT_WAIT_WEATHER ) ) { - g->u.assign_activity( ACT_WAIT_WEATHER, 0, 0 ); - } + const weather_generator &weather_gen = get_cur_weather_gen(); + w = weather_gen.get_weather( g->u.global_square_location(), calendar::turn, g->get_seed() ); + weather_type_id old_weather = weather_id; + weather_id = weather_override ? weather_override : weather_gen.get_weather_conditions( w ); + if( !g->u.has_artifact_with( AEP_BAD_WEATHER ) ) { + weather_override = weather_type_id::NULL_ID(); + } - if( weather_id->sight_penalty != - old_weather->sight_penalty ) { - for( int i = -OVERMAP_DEPTH; i <= OVERMAP_HEIGHT; i++ ) { - get_map().set_transparency_cache_dirty( i ); - } - get_map().set_seen_cache_dirty( tripoint_zero ); - } + sfx::do_ambient(); + temperature = units::to_fahrenheit( w.temperature ); + lightning_active = false; + // Check weather every few turns, instead of every turn. + // TODO: predict when the weather changes and use that time. + nextweather = calendar::turn + 5_minutes; + if( weather_id != old_weather && weather_id->dangerous && + g->get_levz() >= 0 && get_map().is_outside( g->u.pos() ) + && !g->u.has_activity( ACT_WAIT_WEATHER ) ) { + g->cancel_activity_or_ignore_query( distraction_type::weather_change, + string_format( _( "The weather changed to %s!" ), weather_id->name ) ); + } - water_temperature = units::to_fahrenheit( - weather_gen.get_water_temperature( - tripoint_abs_ms( g->u.global_square_location() ), - calendar::turn, calendar::config, g->get_seed() ) ); + if( weather_id != old_weather && g->u.has_activity( ACT_WAIT_WEATHER ) ) { + g->u.assign_activity( ACT_WAIT_WEATHER, 0, 0 ); } + + if( weather_id->sight_penalty != + old_weather->sight_penalty ) { + for( int i = -OVERMAP_DEPTH; i <= OVERMAP_HEIGHT; i++ ) { + get_map().set_transparency_cache_dirty( i ); + } + get_map().set_seen_cache_dirty( tripoint_zero ); + } + + water_temperature = units::to_fahrenheit( + weather_gen.get_water_temperature( + tripoint_abs_ms( g->u.global_square_location() ), + calendar::turn, calendar::config, g->get_seed() ) ); } void weather_manager::set_nextweather( time_point t ) @@ -1118,9 +1124,12 @@ int weather_manager::get_temperature( const tripoint &location ) const temp_mod += get_heat_radiation( location, false ); temp_mod += get_convection_temperature( location ); } - //underground temperature = average New England temperature = 43F/6C rounded to int - const int temp = ( location.z < 0 ? AVERAGE_ANNUAL_TEMPERATURE : temperature ) + - ( g->new_game ? 0 : g->m.get_temperature( location ) + temp_mod ); + const int temp = ( location.z < 0 + ? units::to_fahrenheit( temperatures::annual_average ) + : temperature ) + + ( g->new_game + ? 0 + : g->m.get_temperature( location ) + temp_mod ); temperature_cache.emplace( std::make_pair( location, temp ) ); return temp; @@ -1129,7 +1138,7 @@ int weather_manager::get_temperature( const tripoint &location ) const int weather_manager::get_temperature( const tripoint_abs_omt &location ) { if( location.z() < 0 ) { - return AVERAGE_ANNUAL_TEMPERATURE; + return units::to_fahrenheit( temperatures::annual_average ); } tripoint abs_ms = project_to( location ).raw(); diff --git a/src/weather.h b/src/weather.h index 0a3d3e8bda96..3e8a4fb05c3f 100644 --- a/src/weather.h +++ b/src/weather.h @@ -145,7 +145,7 @@ nc_color get_wind_color( double ); /** * Calculates rot per hour at given temperature. Reference in weather_data.cpp */ -int get_hourly_rotpoints_at_temp( int temp ); +auto get_hourly_rotpoints_at_temp( const units::temperature temp ) -> int; /** * Is it warm enough to plant seeds? diff --git a/tests/ranged_aiming_test.cpp b/tests/ranged_aiming_test.cpp index fa6717c10bec..c8e21d506205 100644 --- a/tests/ranged_aiming_test.cpp +++ b/tests/ranged_aiming_test.cpp @@ -104,7 +104,7 @@ TEST_CASE( "Aiming at a target behind wall", "[ranged][aiming]" ) clear_all_state(); player &shooter = g->u; clear_character( shooter, true ); - shooter.add_effect( efftype_id( "debug_clairvoyance" ), time_duration::from_seconds( 1 ) ); + shooter.add_effect( efftype_id( "debug_clairvoyance" ), 1_seconds ); arm_character( shooter, "glock_19" ); int max_range = shooter.primary_weapon().gun_range( &shooter ); REQUIRE( max_range >= 5 );