Skip to content

Commit

Permalink
Exclude fire from exponential decay (CleverRaven#45431)
Browse files Browse the repository at this point in the history
* Exclude fire from exponential decay

* Add a scaling factor of log2(e), experimentally determined.

* Add some basic tests for exiry time of fields

* Apply suggestions from code review

Co-authored-by: Curtis Merrill <[email protected]>

* Apply suggestions from code review

* Add M_LOG2E to math defines

Co-authored-by: Zhilkin Serg <[email protected]>
Co-authored-by: Curtis Merrill <[email protected]>
  • Loading branch information
3 people authored Nov 24, 2020
1 parent 102850c commit df54339
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 1 deletion.
12 changes: 11 additions & 1 deletion src/field.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "calendar.h"
#include "int_id.h"
#include "make_static.h"
#include "rng.h"

int field_entry::move_cost() const
Expand Down Expand Up @@ -152,8 +153,17 @@ void field_entry::do_decay()
// Bypass set_field_age() so we don't reset decay_time;
age += 1_turns;
if( type.obj().half_life > 0_turns && get_field_age() > 0_turns ) {
// Legacy handling for fire because it's weird and complicated.
if( type == STATIC( field_type_str_id( "fd_fire" ) ) ) {
if( to_turns<int>( type->half_life ) < dice( 2, to_turns<int>( age ) ) ) {
set_field_age( 0_turns );
set_field_intensity( get_field_intensity() - 1 );
}
return;
}
if( decay_time == calendar::turn_zero ) {
std::exponential_distribution<> d( 1.0f / to_turns<float>( type.obj().half_life ) );
std::exponential_distribution<> d( 1.0f / ( M_LOG2E * to_turns<float>
( type.obj().half_life ) ) );
const time_duration decay_delay = time_duration::from_turns( d( rng_get_engine() ) );
decay_time = calendar::turn - age + decay_delay;
}
Expand Down
4 changes: 4 additions & 0 deletions src/math_defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@
#define M_SQRT2 1.41421356237309504880
#endif

#ifndef M_LOG2E
#define M_LOG2E 1.44269504088896340736
#endif

#endif // CATA_SRC_MATH_DEFINES_H
118 changes: 118 additions & 0 deletions tests/field_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include "catch/catch.hpp"

#include "field.h"
#include "map.h"
#include "map_iterator.h"
#include "type_id.h"

#include "map_helpers.h"

#include <algorithm>

static int count_fields( const field_type_str_id &field_type )
{
map &m = get_map();
int live_fields = 0;
for( const tripoint &cursor : m.points_on_zlevel() ) {
if( m.get_field( cursor, field_type ) != nullptr ) {
live_fields++;
}
}
return live_fields;
}

// This is a large scale integration test, it gets prohibitively expensive if it uses
// a long-lived field, and it isn't capable of handling fields that trigger spread/join mechanics
// or dynamically update their own lifetimes.
TEST_CASE( "acid_field_expiry_on_map", "[field]" )
{
clear_map();
map &m = get_map();
const field_type_str_id field_type( "fd_acid" );
// place a smoke field
for( const tripoint &cursor : m.points_on_zlevel() ) {
m.add_field( cursor, field_type, 1 );
}
REQUIRE( count_fields( field_type ) == 17424 );
const time_point before_time = calendar::turn;
// run time forward until it goes away
while( calendar::turn - before_time < field_type.obj().half_life ) {
m.process_fields();
calendar::turn += 1_seconds;
}

CHECK( count_fields( field_type ) == Approx( 8712 ).margin( 200 ) );
}

static void test_field_expiry( const std::string &field_type_str )
{
const field_type_str_id field_type( field_type_str );
std::vector<field_entry> test_fields;
for( int i = 0; i < 10000; ++i ) {
test_fields.emplace_back( field_type, 1, 0_seconds );
test_fields[i].do_decay();
}
// Reduce time advancement by 2 seconds because age gets incremented by do_decay()
calendar::turn += field_type.obj().half_life - 2_seconds;
float decayed = 0.0f;
float alive = 0.0f;
for( field_entry &test_field : test_fields ) {
test_field.do_decay();
if( test_field.is_field_alive() ) {
alive += 1.0f;
} else {
decayed += 1.0f;
}
}
CAPTURE( field_type_str );
CHECK( alive == Approx( decayed ).epsilon( 0.1f ) );
}

TEST_CASE( "field_expiry", "[field]" )
{
// Test fields with a wide range of half lives.
test_field_expiry( "fd_acid" );
test_field_expiry( "fd_smoke" );
test_field_expiry( "fd_blood" );
test_field_expiry( "fd_sludge" );
test_field_expiry( "fd_extinguisher" );
test_field_expiry( "fd_electricity" );
}

static void fire_duration( const std::string &terrain_type, const time_duration minimum,
const time_duration maximum )
{
clear_map();
const tripoint fire_loc{ 33, 33, 0 };
map &m = get_map();
m.ter_set( fire_loc, ter_id( terrain_type ) );
m.add_field( fire_loc, fd_fire, 1, 10_minutes );
REQUIRE( m.get_field( fire_loc, fd_fire ) );
CHECK( m.get_field( fire_loc, fd_fire )->is_field_alive() );
const time_point before_time = calendar::turn;
bool field_alive = true;
while( field_alive && calendar::turn - before_time < minimum ) {
m.process_fields();
calendar::turn += 1_seconds;
const int effective_age = to_turns<int>( calendar::turn - before_time );
INFO( effective_age << " seconds" );
field_entry *this_field = m.get_field( fire_loc, fd_fire );
field_alive = this_field && this_field->is_field_alive();
}
CHECK( field_alive );
while( field_alive && calendar::turn - before_time < maximum ) {
m.process_fields();
calendar::turn += 1_seconds;
const int effective_age = to_turns<int>( calendar::turn - before_time );
INFO( effective_age << " seconds" );
field_entry *this_field = m.get_field( fire_loc, fd_fire );
field_alive = this_field && this_field->is_field_alive();
}
CHECK( !field_alive );
}

TEST_CASE( "firebugs", "[field]" )
{
fire_duration( "t_grass", 5_minutes, 30_minutes );
fire_duration( "t_shrub_raspberry", 60_minutes, 120_minutes );
}

0 comments on commit df54339

Please sign in to comment.