diff --git a/data/mods/TEST_DATA/effect_on_condition.json b/data/mods/TEST_DATA/effect_on_condition.json new file mode 100644 index 0000000000000..db94e5d387ef1 --- /dev/null +++ b/data/mods/TEST_DATA/effect_on_condition.json @@ -0,0 +1,45 @@ +[ + { + "type": "effect_on_condition", + "id": "EOC_TEST_TRANSFORM_RADIUS", + "global": true, + "effect": [ + { "u_location_variable": { "global_val": "test_transform" } }, + { + "u_transform_radius": 5, + "ter_furn_transform": "test_grass_transform", + "target_var": { "global_val": "test_transform" } + }, + { + "u_transform_radius": 5, + "ter_furn_transform": "test_dirt_transform", + "target_var": { "global_val": "test_transform" }, + "time_in_future": "30 seconds" + } + ] + }, + { + "type": "effect_on_condition", + "id": "EOC_TEST_TRANSFORM_LINE", + "global": true, + "effect": [ + { "u_location_variable": { "global_val": "first" } }, + { "npc_location_variable": { "global_val": "second" } }, + { + "transform_line": "test_grass_transform", + "first": { "global_val": "first" }, + "second": { "global_val": "second" } + } + ] + }, + { + "type": "ter_furn_transform", + "id": "test_grass_transform", + "terrain": [ { "result": [ "t_dirt" ], "valid_terrain": [ "t_grass" ] } ] + }, + { + "type": "ter_furn_transform", + "id": "test_dirt_transform", + "terrain": [ { "result": [ "t_grass" ], "valid_terrain": [ "t_dirt" ] } ] + } +] diff --git a/src/coordinates.h b/src/coordinates.h index 44038946f9541..a63b2f4d4f827 100644 --- a/src/coordinates.h +++ b/src/coordinates.h @@ -309,6 +309,13 @@ inline std::ostream &operator<<( std::ostream &os, const coord_point +constexpr inline coord_point +coord_min( const coord_point &l, const coord_point &r ) +{ + return { std::min( l.x(), r.x() ), std::min( l.y(), r.y() ), std::min( l.z(), r.z() ) }; +} + template struct project_to_impl; diff --git a/src/magic_spell_effect.cpp b/src/magic_spell_effect.cpp index a42c040e8b9fa..6db0cceb84c04 100644 --- a/src/magic_spell_effect.cpp +++ b/src/magic_spell_effect.cpp @@ -1258,7 +1258,7 @@ void spell_effect::transform_blast( const spell &sp, Creature &caster, const std::set area = spell_effect_area( sp, target, caster ); for( const tripoint &location : area ) { if( one_in( sp.damage() ) ) { - transform->transform( location ); + transform->transform( get_map(), tripoint_bub_ms{ location } ); } } } diff --git a/src/magic_ter_fur_transform.cpp b/src/magic_ter_fur_transform.cpp index b70280c4c6522..4759442a420ba 100644 --- a/src/magic_ter_fur_transform.cpp +++ b/src/magic_ter_fur_transform.cpp @@ -213,12 +213,7 @@ cata::optional>> ter_furn_tr return next( trap_flag_transform, flag ); } -void ter_furn_transform::transform( const tripoint &location, bool shifted ) const -{ - transform( get_map(), location, shifted ); -} - -void ter_furn_transform::transform( map &m, const tripoint &location, bool shifted ) const +void ter_furn_transform::transform( map &m, const tripoint_bub_ms &location ) const { avatar &you = get_avatar(); const ter_id ter_at_loc = m.ter( location ); @@ -239,7 +234,7 @@ void ter_furn_transform::transform( map &m, const tripoint &location, bool shift m.add_field( location, field_potential->first, fld.second.get_field_intensity(), fld.second.get_field_age(), true ); m.remove_field( location, fld.first ); - if( !shifted && you.sees( location ) ) { + if( you.sees( location ) ) { you.add_msg_if_player( field_potential->second.first, field_potential->second.second ? m_good : m_bad ); } @@ -284,20 +279,20 @@ void ter_furn_transform::transform( map &m, const tripoint &location, bool shift if( ter_potential ) { m.ter_set( location, ter_potential->first ); - if( !shifted && you.sees( location ) ) { + if( you.sees( location ) ) { you.add_msg_if_player( ter_potential->second.first, ter_potential->second.second ? m_good : m_bad ); } } if( furn_potential ) { m.furn_set( location, furn_potential->first ); - if( !shifted && you.sees( location ) ) { + if( you.sees( location ) ) { you.add_msg_if_player( furn_potential->second.first, furn_potential->second.second ? m_good : m_bad ); } } if( trap_potential ) { m.trap_set( location, trap_potential->first ); - if( !shifted && you.sees( location ) ) { + if( you.sees( location ) ) { you.add_msg_if_player( trap_potential->second.first, trap_potential->second.second ? m_good : m_bad ); } diff --git a/src/magic_ter_furn_transform.h b/src/magic_ter_furn_transform.h index 9c18b32621801..0e82c21b09bb9 100644 --- a/src/magic_ter_furn_transform.h +++ b/src/magic_ter_furn_transform.h @@ -5,6 +5,7 @@ #include #include +#include "coordinates.h" #include "optional.h" #include "type_id.h" #include "weighted_list.h" @@ -77,8 +78,7 @@ class ter_furn_transform std::vector> src; bool was_loaded = false; - void transform( const tripoint &location, bool shifted = false ) const; - void transform( map &m, const tripoint &location, bool shifted = false ) const; + void transform( map &m, const tripoint_bub_ms &location ) const; static void reset(); static void load_transform( const JsonObject &jo, const std::string &src ); diff --git a/src/map.cpp b/src/map.cpp index 3009e5ed17f80..e94d3f81e714e 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -290,6 +290,11 @@ const_maptile map::maptile_at( const tripoint &p ) const return maptile_at_internal( p ); } +const_maptile map::maptile_at( const tripoint_bub_ms &p ) const +{ + return maptile_at( p.raw() ); +} + maptile map::maptile_at( const tripoint &p ) { if( !inbounds( p ) ) { @@ -299,6 +304,11 @@ maptile map::maptile_at( const tripoint &p ) return maptile_at_internal( p ); } +maptile map::maptile_at( const tripoint_bub_ms &p ) +{ + return maptile_at( p.raw() ); +} + const_maptile map::maptile_at_internal( const tripoint &p ) const { point l; @@ -4353,44 +4363,28 @@ void map::translate_radius( const ter_id &from, const ter_id &to, float radi, co } // NOLINTNEXTLINE(readability-make-member-function-const) -void map::transform_radius( ter_furn_transform_id transform, float radi, +void map::transform_radius( ter_furn_transform_id transform, int radi, const tripoint_abs_ms &p ) { - tripoint_abs_ms avatar_pos = get_avatar().get_location(); - bool shifted = false; - if( !get_map().inbounds( get_map().getlocal( p ) ) ) { - const tripoint_abs_ms abs_ms( p ); - g->place_player_overmap( project_to( abs_ms ), false ); - shifted = true; + if( !inbounds( p - point( radi, radi ) ) || !inbounds( p + point( radi, radi ) ) ) { + debugmsg( "transform_radius called for area out of bounds" ); } - for( const tripoint &t : points_on_zlevel() ) { - const float radiX = trig_dist( p, getglobal( t ) ); - // within distance, and either no submap limitation or same overmap coords. - if( radiX <= radi ) { - transform->transform( t, shifted ); + tripoint_bub_ms const loc = bub_from_abs( p ); + for( tripoint_bub_ms const &t : points_in_radius( loc, radi, 0 ) ) { + if( trig_dist( loc, t ) <= radi ) { + transform->transform( *this, t ); } } - if( shifted ) { - g->place_player_overmap( project_to( avatar_pos ), false ); - } } void map::transform_line( ter_furn_transform_id transform, const tripoint_abs_ms &first, const tripoint_abs_ms &second ) { - tripoint_abs_ms avatar_pos = get_avatar().get_location(); - bool shifted = false; - if( !get_map().inbounds( get_map().getlocal( first ) ) ) { - g->place_player_overmap( project_to( first ), false ); - shifted = true; + if( !inbounds( first ) || !inbounds( second ) ) { + debugmsg( "transform_line called for line out of bounds" ); } - for( const tripoint_abs_ms &t : line_to( first, second ) ) { - transform->transform( *this, getlocal( t ), shifted ); - } - - if( shifted ) { - g->place_player_overmap( project_to( avatar_pos ), false ); + transform->transform( *this, bub_from_abs( t ) ); } } @@ -7576,7 +7570,7 @@ void map::loadn( const tripoint &grid, const bool update_vehicles, bool _actuali tinymap tmp_map; tmp_map.main_cleanup_override( false ); tmp_map.generate( grid_abs_sub_rounded, calendar::turn ); - _main_requires_cleanup = main_inbounds && tmp_map.is_main_cleanup_queued(); + _main_requires_cleanup |= main_inbounds && tmp_map.is_main_cleanup_queued(); } // This is the same call to MAPBUFFER as above! diff --git a/src/map.h b/src/map.h index ce18563b12193..e335bff3d8890 100644 --- a/src/map.h +++ b/src/map.h @@ -475,7 +475,9 @@ class map void clear_traps(); const_maptile maptile_at( const tripoint &p ) const; + const_maptile maptile_at( const tripoint_bub_ms &p ) const; maptile maptile_at( const tripoint &p ); + maptile maptile_at( const tripoint_bub_ms &p ); private: // Versions of the above that don't do bounds checks const_maptile maptile_at_internal( const tripoint &p ) const; @@ -1090,7 +1092,7 @@ class map // Optionally toggles instances $from->$to & $to->$from void translate_radius( const ter_id &from, const ter_id &to, float radi, const tripoint &p, bool same_submap = false, bool toggle_between = false ); - void transform_radius( ter_furn_transform_id transform, float radi, + void transform_radius( ter_furn_transform_id transform, int radi, const tripoint_abs_ms &p ); void transform_line( ter_furn_transform_id transform, const tripoint_abs_ms &first, const tripoint_abs_ms &second ); diff --git a/src/mapgen.cpp b/src/mapgen.cpp index d9a3be1c52551..7a54b5b8b40cb 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -2821,7 +2821,7 @@ class jmapgen_ter_furn_transform: public jmapgen_piece if( chosen_id.is_null() ) { return; } - chosen_id->transform( dat.m, tripoint( x.get(), y.get(), dat.m.get_abs_sub().z() ) ); + chosen_id->transform( dat.m, tripoint_bub_ms( x.get(), y.get(), dat.m.get_abs_sub().z() ) ); } void check( const std::string &oter_name, const mapgen_parameters ¶meters, diff --git a/src/npctalk.cpp b/src/npctalk.cpp index 295d5480843d6..2a9a3bc7ef572 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -2562,8 +2562,9 @@ void talk_effect_fun_t::set_transform_radius( const JsonObject &jo, const std //Timed events happen before the player turn and eocs are during so we add a second here to sync them up using the same variable -1, target_pos, radius, transform.str(), key.evaluate( d ) ); } else { - get_map().transform_radius( transform, radius, target_pos ); - get_map().invalidate_map_cache( target_pos.z() ); + map tm; + tm.load( project_to( target_pos - point{ radius, radius} ), false ); + tm.transform_radius( transform, radius, target_pos ); } }; } @@ -2576,9 +2577,12 @@ void talk_effect_fun_t::set_transform_line( const JsonObject &jo, const std:: var_info second = read_var_info( jo.get_object( "second" ) ); function = [transform, first, second]( const T & d ) { - get_map().transform_line( transform, get_tripoint_from_var( first, d ), - get_tripoint_from_var( second, d ) ); - get_map().invalidate_map_cache( get_tripoint_from_var( first, d ).z() ); + tripoint_abs_ms const t_first = get_tripoint_from_var( first, d ); + tripoint_abs_ms const t_second = get_tripoint_from_var( second, d ); + tripoint_abs_ms const orig = coord_min( t_first, t_second ); + map tm; + tm.load( project_to( orig ), false ); + tm.transform_line( transform, t_first, t_second ); }; } diff --git a/src/timed_event.cpp b/src/timed_event.cpp index 849331eb367c5..4d418b9f4c2e7 100644 --- a/src/timed_event.cpp +++ b/src/timed_event.cpp @@ -300,12 +300,13 @@ void timed_event::actualize() } break; - case timed_event_type::TRANSFORM_RADIUS: - get_map().transform_radius( ter_furn_transform_id( string_id ), strength, - map_square ); - get_map().invalidate_map_cache( map_point.z() ); + case timed_event_type::TRANSFORM_RADIUS: { + map tm; + tm.load( project_to( map_square - point{ strength, strength} ), false ); + tm.transform_radius( ter_furn_transform_id( string_id ), strength, + map_square ); break; - + } case timed_event_type::UPDATE_MAPGEN: run_mapgen_update_func( update_mapgen_id( string_id ), project_to( map_point ), nullptr ); diff --git a/tests/eoc_test.cpp b/tests/eoc_test.cpp index 19a021808910a..49b2b03346a4e 100644 --- a/tests/eoc_test.cpp +++ b/tests/eoc_test.cpp @@ -1,12 +1,43 @@ #include "avatar.h" #include "cata_catch.h" #include "effect_on_condition.h" +#include "game.h" #include "map_helpers.h" +#include "timed_event.h" #include "player_helpers.h" #include "point.h" +static const effect_on_condition_id +effect_on_condition_EOC_TEST_TRANSFORM_LINE( "EOC_TEST_TRANSFORM_LINE" ); +static const effect_on_condition_id +effect_on_condition_EOC_TEST_TRANSFORM_RADIUS( "EOC_TEST_TRANSFORM_RADIUS" ); static const effect_on_condition_id effect_on_condition_EOC_teleport_test( "EOC_teleport_test" ); +namespace +{ +void check_ter_in_radius( tripoint_abs_ms const ¢er, int range, ter_id const &ter ) +{ + map tm; + tm.load( project_to( center - point{ range, range } ), false, false ); + tripoint_bub_ms const center_local = tm.bub_from_abs( center ); + for( tripoint_bub_ms p : tm.points_in_radius( center_local, range ) ) { + if( trig_dist( center_local, p ) <= range ) { + REQUIRE( tm.ter( p ) == ter ); + } + } +} + +void check_ter_in_line( tripoint_abs_ms const &first, tripoint_abs_ms const &second, + ter_id const &ter ) +{ + map tm; + tripoint_abs_ms const orig = coord_min( first, second ); + tm.load( project_to( orig ), false, false ); + for( tripoint_abs_ms p : line_to( first, second ) ) { + REQUIRE( tm.ter( tm.getlocal( p ) ) == ter ); + } +} +} // namespace TEST_CASE( "EOC_teleport", "[eoc]" ) { @@ -19,3 +50,54 @@ TEST_CASE( "EOC_teleport", "[eoc]" ) CHECK( before + tripoint_south_east == after ); } + +TEST_CASE( "EOC_transform_radius", "[eoc][timed_event]" ) +{ + // FIXME: remove once the overlap warning in `map::load()` has been fully solved + restore_on_out_of_scope restore_test_mode( test_mode ); + test_mode = false; + + // no introspection :( + constexpr int eoc_range = 5; + constexpr time_duration delay = 30_seconds; + clear_avatar(); + clear_map(); + tripoint_abs_ms const start = get_avatar().get_location(); + dialogue newDialog( get_talker_for( get_avatar() ), nullptr ); + check_ter_in_radius( start, eoc_range, t_grass ); + effect_on_condition_EOC_TEST_TRANSFORM_RADIUS->activate( newDialog ); + check_ter_in_radius( start, eoc_range, t_dirt ); + + g->place_player_overmap( project_to( start ) + point{ 60, 60 } ); + REQUIRE( !get_map().inbounds( start ) ); + + calendar::turn += delay - 1_seconds; + get_timed_events().process(); + check_ter_in_radius( start, eoc_range, t_dirt ); + calendar::turn += 2_seconds; + get_timed_events().process(); + check_ter_in_radius( start, eoc_range, t_grass ); +} + +TEST_CASE( "EOC_transform_line", "[eoc][timed_event]" ) +{ + // FIXME: remove once the overlap warning in `map::load()` has been fully solved + restore_on_out_of_scope restore_test_mode( test_mode ); + test_mode = false; + + clear_avatar(); + clear_map(); + standard_npc npc( "Mr. Testerman" ); + cata::optional const dest = random_point( get_map(), []( tripoint const & p ) { + return p.xy() != get_avatar().pos().xy(); + } ); + REQUIRE( dest.has_value() ); + npc.setpos( { dest.value().xy(), get_avatar().pos().z } ); + + tripoint_abs_ms const start = get_avatar().get_location(); + tripoint_abs_ms const end = npc.get_location(); + dialogue newDialog( get_talker_for( get_avatar() ), get_talker_for( npc ) ); + check_ter_in_line( start, end, t_grass ); + effect_on_condition_EOC_TEST_TRANSFORM_LINE->activate( newDialog ); + check_ter_in_line( start, end, t_dirt ); +}