From 880fcd9aa22c7c424803d6d1656375ff0066e9f9 Mon Sep 17 00:00:00 2001 From: jove Date: Tue, 14 Jun 2022 17:49:37 +0100 Subject: [PATCH] Prevents moving through vehicle holes --- src/action.cpp | 20 ++- src/avatar_action.cpp | 5 + src/ballistics.cpp | 20 +++ src/creature.cpp | 5 +- src/explosion.cpp | 17 ++- src/lightmap.cpp | 228 ++++++++++++++++++++++++------- src/map.cpp | 137 +++++++++++++++++-- src/map.h | 35 ++++- src/map_field.cpp | 22 +-- src/mattack_actors.cpp | 4 + src/melee.cpp | 14 ++ src/monattack.cpp | 4 + src/monmove.cpp | 15 +- src/monster.cpp | 4 + src/monster.h | 2 + src/npcmove.cpp | 6 + src/pathfinding.cpp | 15 ++ src/scent_map.cpp | 130 +++++++++--------- src/scent_map.h | 6 + src/shadowcasting.h | 6 +- src/vehicle.cpp | 201 +++++++++++++++++++++++++-- src/vehicle.h | 26 +++- src/vehicle_autodrive.cpp | 8 +- tests/explosion_balance_test.cpp | 27 ++++ tests/monster_test.cpp | 19 +++ tests/monster_vision_test.cpp | 21 +++ tests/npc_test.cpp | 30 ++++ tests/player_test.cpp | 21 +++ tests/scent_test.cpp | 172 +++++++++++++++++++++++ tests/shadowcasting_test.cpp | 31 ++++- tests/vehicle_test.cpp | 64 +++++++++ tests/vision_test.cpp | 85 +++++++++++- 32 files changed, 1217 insertions(+), 183 deletions(-) create mode 100644 tests/scent_test.cpp diff --git a/src/action.cpp b/src/action.cpp index 94549b732af3..e8a1bd0992a3 100644 --- a/src/action.cpp +++ b/src/action.cpp @@ -693,7 +693,11 @@ action_id handle_action_menu() // display that action at the top of the list. for( const tripoint &pos : g->m.points_in_radius( g->u.pos(), 1 ) ) { if( pos != g->u.pos() ) { - // Check for actions that work on nearby tiles + // Check for actions that work on nearby tiles, skipping tiles blocked by vehicles + if( g->m.obstructed_by_vehicle_rotation( g->u.pos(), pos ) ) { + continue; + } + if( can_interact_at( ACTION_OPEN, pos ) ) { action_weightings[ACTION_OPEN] = 200; } @@ -1006,7 +1010,17 @@ cata::optional choose_direction( const std::string &message, const boo cata::optional choose_adjacent( const std::string &message, const bool allow_vertical ) { const cata::optional dir = choose_direction( message, allow_vertical ); - return dir ? *dir + g->u.pos() : dir; + + if( !dir ) { + return cata::nullopt; + } + + if( g->m.obstructed_by_vehicle_rotation( g->u.pos(), *dir + g->u.pos() ) ) { + add_msg( _( "You can't reach through that vehicle's wall." ) ); + return cata::nullopt; + } + + return *dir + g->u.pos(); } cata::optional choose_adjacent_highlight( const std::string &message, @@ -1025,7 +1039,7 @@ cata::optional choose_adjacent_highlight( const std::string &message, std::vector valid; if( allowed ) { for( const tripoint &pos : g->m.points_in_radius( g->u.pos(), 1 ) ) { - if( allowed( pos ) ) { + if( !g->m.obstructed_by_vehicle_rotation( g->u.pos(), pos ) && allowed( pos ) ) { valid.emplace_back( pos ); } } diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index df0ba8b522d1..5abffb0b58c6 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -250,6 +250,11 @@ bool avatar_action::move( avatar &you, map &m, const tripoint &d ) return false; } + if( m.obstructed_by_vehicle_rotation( you.pos(), dest_loc ) ) { + add_msg( _( "You can't walk through that vehicle's wall." ) ); + return false; + } + if( monster *const mon_ptr = g->critter_at( dest_loc, true ) ) { monster &critter = *mon_ptr; if( critter.friendly == 0 && diff --git a/src/ballistics.cpp b/src/ballistics.cpp index 5799f0cc855b..52d2ac16b3f4 100644 --- a/src/ballistics.cpp +++ b/src/ballistics.cpp @@ -427,6 +427,26 @@ dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tri } else if( in_veh != nullptr && veh_pointer_or_null( g->m.veh_at( tp ) ) == in_veh ) { // Don't do anything, especially don't call map::shoot as this would damage the vehicle } else { + + if( g->m.obstructed_by_vehicle_rotation( prev_point, tp ) ) { + //We're firing through an impassible gap in a rotated vehicle, randomly hit one of the two walls + tripoint rand = tp; + if( one_in( 2 ) ) { + rand.x = prev_point.x; + } else { + rand.y = prev_point.y; + } + if( in_veh == nullptr || veh_pointer_or_null( g->m.veh_at( rand ) ) != in_veh ) { + g->m.shoot( rand, proj, false ); + if( proj.impact.total_damage() <= 0 ) { + //If the projectile stops here move it back a square so it doesn't end up inside the vehicle + traj_len = i - 1; + tp = prev_point; + break; + } + } + } + g->m.shoot( tp, proj, !no_item_damage && tp == target ); has_momentum = proj.impact.total_damage() > 0; } diff --git a/src/creature.cpp b/src/creature.cpp index 0960fa320608..0d8f6c3baeb5 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -224,9 +224,12 @@ bool Creature::sees( const Creature &critter ) const const Character *ch = critter.as_character(); const int wanted_range = rl_dist( pos(), critter.pos() ); - // Can always see adjacent monsters on the same level. + // Can always see adjacent monsters on the same level, unless they're through a vehicle wall. // We also bypass lighting for vertically adjacent monsters, but still check for floors. if( wanted_range <= 1 && ( posz() == critter.posz() || g->m.sees( pos(), critter.pos(), 1 ) ) ) { + if( g->m.obscured_by_vehicle_rotation( pos(), critter.pos() ) ) { + return false; + } return visible( ch ); } else if( ( wanted_range > 1 && critter.digging() ) || ( critter.has_flag( MF_NIGHT_INVISIBILITY ) && g->m.light_at( critter.pos() ) <= lit_level::LOW ) || diff --git a/src/explosion.cpp b/src/explosion.cpp index 0310e173dc8c..b62e71e2c472 100644 --- a/src/explosion.cpp +++ b/src/explosion.cpp @@ -207,7 +207,8 @@ static std::map do_blast( const tripoint &p, const float // Iterate over all neighbors. Bash all of them, propagate to some for( size_t i = 0; i < max_index; i++ ) { tripoint dest( pt + tripoint( x_offset[i], y_offset[i], z_offset[i] ) ); - if( closed.count( dest ) != 0 || !g->m.inbounds( dest ) ) { + if( closed.count( dest ) != 0 || !g->m.inbounds( dest ) || + g->m.obstructed_by_vehicle_rotation( pt, dest ) ) { continue; } @@ -438,11 +439,16 @@ static std::map do_blast_new( const tripoint &blast_cente for( const dist_point_pair &pair : blast_map ) { float distance; tripoint position; + tripoint last_position = position; std::tie( distance, position ) = pair; const std::vector line_of_movement = line_to( blast_center, position ); const bool has_obstacles = std::any_of( line_of_movement.begin(), - line_of_movement.end(), [position]( tripoint ray_position ) { + line_of_movement.end(), [position, &last_position]( tripoint ray_position ) { + if( get_map().obstructed_by_vehicle_rotation( last_position, ray_position ) ) { + return true; + } + last_position = ray_position; return ray_position != position && get_map().impassable( ray_position ); } ); @@ -611,13 +617,15 @@ static std::map shrapnel( const tripoint &src, const proj float obstacle_cache[MAPSIZE_X][MAPSIZE_Y] = {}; float visited_cache[MAPSIZE_X][MAPSIZE_Y] = {}; + diagonal_blocks blocked_cache[MAPSIZE_X][MAPSIZE_Y] = {}; // TODO: Calculate range based on max effective range for projectiles. // Basically bisect between 0 and map diameter using shrapnel_calc(). // Need to update shadowcasting to support limiting range without adjusting initial distance. const tripoint_range area = g->m.points_on_zlevel( src.z ); - g->m.build_obstacle_cache( area.min(), area.max() + tripoint_south_east, obstacle_cache ); + g->m.build_obstacle_cache( area.min(), area.max() + tripoint_south_east, obstacle_cache, + blocked_cache ); // Shadowcasting normally ignores the origin square, // so apply it manually to catch monsters standing on the explosive. @@ -629,7 +637,8 @@ static std::map shrapnel( const tripoint &src, const proj const int offset_distance = 60 - 1 - fragment.range; castLightAll - ( visited_cache, obstacle_cache, src.xy(), offset_distance, fragment.range + 1.0f ); + ( visited_cache, obstacle_cache, blocked_cache, src.xy(), + offset_distance, fragment.range + 1.0f ); // Now visited_caches are populated with density and velocity of fragments. for( const tripoint &target : area ) { diff --git a/src/lightmap.cpp b/src/lightmap.cpp index 7d55fc5fafe0..317421b0c1ed 100644 --- a/src/lightmap.cpp +++ b/src/lightmap.cpp @@ -83,6 +83,7 @@ bool map::build_transparency_cache( const int zlev ) auto &map_cache = get_cache( zlev ); auto &transparency_cache = map_cache.transparency_cache; auto &outside_cache = map_cache.outside_cache; + auto &blocked_cache = map_cache.blocked_cache; if( map_cache.transparency_cache_dirty.none() ) { return false; @@ -95,6 +96,9 @@ bool map::build_transparency_cache( const int zlev ) // Default to just barely not transparent. std::uninitialized_fill_n( &transparency_cache[0][0], MAPSIZE_X * MAPSIZE_Y, static_cast( LIGHT_TRANSPARENCY_OPEN_AIR ) ); + diagonal_blocks fill = {false, false}; + std::uninitialized_fill_n( &blocked_cache[0][0], + MAPSIZE_X * MAPSIZE_Y, fill ); } const float sight_penalty = get_weather().weather_id->sight_penalty; @@ -156,6 +160,68 @@ bool map::build_transparency_cache( const int zlev ) } } } + + if( !rebuild_all ) { + for( int sx = 0; sx < SEEX; ++sx ) { + diagonal_blocks fill = {false, false}; + std::uninitialized_fill_n( &blocked_cache[sm_offset.x + sx][sm_offset.y], SEEY, fill ); + } + } + + for( auto &vehicle : cur_submap->vehicles ) { + std::set checked; + + auto parts = vehicle->get_all_parts(); + + for( auto part = parts.begin(); part != parts.end(); ++part ) { + + auto trip = vehicle->mount_to_tripoint( part->mount() ); + + for( int dy = -1; dy <= 1; dy++ ) { + for( int dx = -1; dx <= 1; dx++ ) { + + + tripoint from = { trip + point( dx, dy ) }; + + if( checked.find( from.xy() ) != checked.end() || from.x < 0 || from.y < 0 || + from.x >= MAPSIZE_X || from.y >= MAPSIZE_Y ) { + continue; + } + checked.insert( from.xy() ); + + + auto from_mount = vehicle->tripoint_to_mount( from ); + + for( int my = -1; my <= 1; my += 2 ) { + for( int mx = -1; mx <= 1; mx += 2 ) { + + auto t = vehicle->tripoint_to_mount( from + point( mx, my ) ); + + if( !vehicle->allowed_light( t, from_mount ) ) { + if( my == -1 && mx == -1 ) { + blocked_cache[from.x][from.y].nw = true; + } else if( my == -1 && mx == 1 ) { + blocked_cache[from.x][from.y].ne = true; + } else if( my == 1 && mx == -1 ) { + if( from.x == 0 || from.y == MAPSIZE_Y - 1 ) { + continue; + } + blocked_cache[from.x - 1][from.y + 1].ne = true; + } else { + if( from.x == MAPSIZE_X - 1 || from.y == MAPSIZE_Y - 1 ) { + continue; + } + blocked_cache[from.x + 1][from.y + 1].nw = true; + } + } + + } + } + } + } + } + } + } } map_cache.transparency_cache_dirty.reset(); @@ -797,6 +863,7 @@ void cast_zlight_segment( const array_of_grids_of &output_caches, const array_of_grids_of &input_arrays, const array_of_grids_of &floor_caches, + const array_of_grids_of &blocked_caches, const tripoint &offset, int offset_distance, T numerator = 1.0f, int row = 1, float start_major = 0.0f, float end_major = 1.0f, @@ -811,6 +878,7 @@ void cast_zlight_segment( const array_of_grids_of &output_caches, const array_of_grids_of &input_arrays, const array_of_grids_of &floor_caches, + const array_of_grids_of < const diagonal_blocks > &blocked_caches, const tripoint &offset, const int offset_distance, const T numerator, const int row, float start_major, const float end_major, @@ -821,6 +889,29 @@ void cast_zlight_segment( return; } + constexpr quadrant quad = quadrant_from_x_y( -xx - xy, -yx - yy ); + + const auto check_blocked = [ =, &blocked_caches]( const tripoint & p ) -> bool{ + switch( quad ) + { + case quadrant::NW: + return ( *blocked_caches[p.z + OVERMAP_DEPTH] )[p.x][p.y].nw; + break; + case quadrant::NE: + return ( *blocked_caches[p.z + OVERMAP_DEPTH] )[p.x][p.y].ne; + break; + case quadrant::SE: + return ( p.x < MAPSIZE_X - 1 && p.y < MAPSIZE_Y - 1 && + ( *blocked_caches[p.z + OVERMAP_DEPTH] )[p.x + 1][p.y + 1].nw ); + break; + case quadrant::SW: + return ( p.x > 1 && p.y < MAPSIZE_Y - 1 && + ( *blocked_caches[p.z + OVERMAP_DEPTH] )[p.x - 1][p.y + 1].ne ); + break; + } + }; + + float radius = 60.0f - offset_distance; constexpr int min_z = -OVERMAP_DEPTH; @@ -890,7 +981,7 @@ void cast_zlight_segment( const int dist = rl_dist( tripoint_zero, delta ) + offset_distance; last_intensity = calc( numerator, cumulative_transparency, dist ); - if( !floor_block ) { + if( !floor_block && !check_blocked( current ) ) { ( *output_caches[z_index] )[current.x][current.y] = std::max( ( *output_caches[z_index] )[current.x][current.y], last_intensity ); } @@ -936,14 +1027,14 @@ void cast_zlight_segment( const float trailing_clipped = std::max( trailing_edge_major, start_major ); const float major_mid = merge_blocks ? leading_edge_major : trailing_clipped; cast_zlight_segment( - output_caches, input_arrays, floor_caches, + output_caches, input_arrays, floor_caches, blocked_caches, offset, offset_distance, numerator, distance + 1, start_major, major_mid, start_minor, end_minor, next_cumulative_transparency ); if( !merge_blocks ) { // One line that is too short to be part of the rectangle above cast_zlight_segment( - output_caches, input_arrays, floor_caches, + output_caches, input_arrays, floor_caches, blocked_caches, offset, offset_distance, numerator, distance + 1, major_mid, leading_edge_major, start_minor, trailing_edge_minor, next_cumulative_transparency ); @@ -965,7 +1056,7 @@ void cast_zlight_segment( // leading_edge_major plus some epsilon float after_leading_edge_major = ( delta.z + 0.50001f ) / ( delta.y - 0.5f ); cast_zlight_segment( - output_caches, input_arrays, floor_caches, + output_caches, input_arrays, floor_caches, blocked_caches, offset, offset_distance, numerator, distance, after_leading_edge_major, end_major, old_start_minor, start_minor, cumulative_transparency ); @@ -1004,63 +1095,69 @@ void cast_zlight( const array_of_grids_of &output_caches, const array_of_grids_of &input_arrays, const array_of_grids_of &floor_caches, + const array_of_grids_of < const diagonal_blocks > &blocked_caches, const tripoint &origin, const int offset_distance, const T numerator ) { // Down cast_zlight_segment < 0, 1, 0, 1, 0, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < 1, 0, 0, 0, 1, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < 0, -1, 0, 1, 0, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < -1, 0, 0, 0, 1, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < 0, 1, 0, -1, 0, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < 1, 0, 0, 0, -1, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < 0, -1, 0, -1, 0, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < -1, 0, 0, 0, -1, 0, -1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); // Up cast_zlight_segment<0, 1, 0, 1, 0, 0, 1, T, calc, check, accumulate>( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment<1, 0, 0, 0, 1, 0, 1, T, calc, check, accumulate>( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < 0, -1, 0, 1, 0, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < -1, 0, 0, 0, 1, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < 0, 1, 0, -1, 0, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < 1, 0, 0, 0, -1, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < 0, -1, 0, -1, 0, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); cast_zlight_segment < -1, 0, 0, 0, -1, 0, 1, T, calc, check, accumulate > ( - output_caches, input_arrays, floor_caches, origin, offset_distance, numerator ); + output_caches, input_arrays, floor_caches, blocked_caches, origin, offset_distance, numerator ); } // I can't figure out how to make implicit instantiation work when the parameters of // the template-supplied function pointers are involved, so I'm explicitly instantiating instead. -template void cast_zlight( +template void +cast_zlight( const array_of_grids_of &output_caches, const array_of_grids_of &input_arrays, const array_of_grids_of &floor_caches, + const array_of_grids_of < const diagonal_blocks > &blocked_caches, const tripoint &origin, int offset_distance, float numerator ); -template void cast_zlight( +template void +cast_zlight +( const array_of_grids_of &output_caches, const array_of_grids_of &input_arrays, const array_of_grids_of &floor_caches, + const array_of_grids_of < const diagonal_blocks > &blocked_caches, const tripoint &origin, int offset_distance, float numerator ); template void castLight( Out( &output_cache )[MAPSIZE_X][MAPSIZE_Y], const T( &input_array )[MAPSIZE_X][MAPSIZE_Y], + const diagonal_blocks( &blocked_array )[MAPSIZE_X][MAPSIZE_Y], const point &offset, int offsetDistance, T numerator = VISIBILITY_FULL, int row = 1, float start = 1.0f, float end = 0.0f, @@ -1082,10 +1180,29 @@ template void castLight( Out( &output_cache )[MAPSIZE_X][MAPSIZE_Y], const T( &input_array )[MAPSIZE_X][MAPSIZE_Y], + const diagonal_blocks( &blocked_array )[MAPSIZE_X][MAPSIZE_Y], const point &offset, const int offsetDistance, const T numerator, const int row, float start, const float end, T cumulative_transparency ) { constexpr quadrant quad = quadrant_from_x_y( -xx - xy, -yx - yy ); + + const auto check_blocked = [ =, &blocked_array]( const point & p ) { + switch( quad ) { + case quadrant::NW: + return blocked_array[p.x][p.y].nw; + break; + case quadrant::NE: + return blocked_array[p.x][p.y].ne; + break; + case quadrant::SE: + return ( p.x < MAPSIZE_X - 1 && p.y < MAPSIZE_Y - 1 && blocked_array[p.x + 1][p.y + 1].nw ); + break; + case quadrant::SW: + return ( p.x > 1 && p.y < MAPSIZE_Y - 1 && blocked_array[p.x - 1][p.y + 1].ne ); + break; + } + }; + float newStart = 0.0f; float radius = 60.0f - offsetDistance; if( start < end ) { @@ -1114,6 +1231,9 @@ void castLight( Out( &output_cache )[MAPSIZE_X][MAPSIZE_Y], } else if( end > trailingEdge ) { break; } + if( check_blocked( current ) ) { + continue; + } if( !started_row ) { started_row = true; current_transparency = input_array[ current.x ][ current.y ]; @@ -1138,7 +1258,7 @@ void castLight( Out( &output_cache )[MAPSIZE_X][MAPSIZE_Y], // Only cast recursively if previous span was not opaque. if( check( current_transparency, last_intensity ) ) { castLight( - output_cache, input_array, offset, offsetDistance, + output_cache, input_array, blocked_array, offset, offsetDistance, numerator, distance + 1, start, trailingEdge, accumulate( cumulative_transparency, current_transparency, distance ) ); } @@ -1172,33 +1292,35 @@ template void castLightAll( Out( &output_cache )[MAPSIZE_X][MAPSIZE_Y], const T( &input_array )[MAPSIZE_X][MAPSIZE_Y], + const diagonal_blocks( &blocked_array )[MAPSIZE_X][MAPSIZE_Y], const point &offset, int offsetDistance, T numerator ) { castLight<0, 1, 1, 0, T, Out, calc, check, update_output, accumulate>( - output_cache, input_array, offset, offsetDistance, numerator ); + output_cache, input_array, blocked_array, offset, offsetDistance, numerator ); castLight<1, 0, 0, 1, T, Out, calc, check, update_output, accumulate>( - output_cache, input_array, offset, offsetDistance, numerator ); + output_cache, input_array, blocked_array, offset, offsetDistance, numerator ); castLight < 0, -1, 1, 0, T, Out, calc, check, update_output, accumulate > ( - output_cache, input_array, offset, offsetDistance, numerator ); + output_cache, input_array, blocked_array, offset, offsetDistance, numerator ); castLight < -1, 0, 0, 1, T, Out, calc, check, update_output, accumulate > ( - output_cache, input_array, offset, offsetDistance, numerator ); + output_cache, input_array, blocked_array, offset, offsetDistance, numerator ); castLight < 0, 1, -1, 0, T, Out, calc, check, update_output, accumulate > ( - output_cache, input_array, offset, offsetDistance, numerator ); + output_cache, input_array, blocked_array, offset, offsetDistance, numerator ); castLight < 1, 0, 0, -1, T, Out, calc, check, update_output, accumulate > ( - output_cache, input_array, offset, offsetDistance, numerator ); + output_cache, input_array, blocked_array, offset, offsetDistance, numerator ); castLight < 0, -1, -1, 0, T, Out, calc, check, update_output, accumulate > ( - output_cache, input_array, offset, offsetDistance, numerator ); + output_cache, input_array, blocked_array, offset, offsetDistance, numerator ); castLight < -1, 0, 0, -1, T, Out, calc, check, update_output, accumulate > ( - output_cache, input_array, offset, offsetDistance, numerator ); + output_cache, input_array, blocked_array, offset, offsetDistance, numerator ); } template void castLightAll( four_quadrants( &output_cache )[MAPSIZE_X][MAPSIZE_Y], const float ( &input_array )[MAPSIZE_X][MAPSIZE_Y], + const diagonal_blocks( &blocked_array )[MAPSIZE_X][MAPSIZE_Y], const point &offset, int offsetDistance, float numerator ); template void @@ -1207,6 +1329,7 @@ castLightAll( - seen_cache, transparency_cache, origin.xy(), 0 ); + seen_cache, transparency_cache, blocked_cache, origin.xy(), 0 ); } } } else { @@ -1253,11 +1377,13 @@ void map::build_seen_cache( const tripoint &origin, const int target_z ) array_of_grids_of transparency_caches; array_of_grids_of seen_caches; array_of_grids_of floor_caches; + array_of_grids_of < const diagonal_blocks > blocked_caches; for( int z = -OVERMAP_DEPTH; z <= OVERMAP_HEIGHT; z++ ) { auto &cur_cache = get_cache( z ); transparency_caches[z + OVERMAP_DEPTH] = &cur_cache.vision_transparency_cache; seen_caches[z + OVERMAP_DEPTH] = &cur_cache.seen_cache; floor_caches[z + OVERMAP_DEPTH] = &cur_cache.floor_cache; + blocked_caches[z + OVERMAP_DEPTH] = &cur_cache.blocked_cache; std::uninitialized_fill_n( &cur_cache.seen_cache[0][0], map_dimensions, light_transparency_solid ); cur_cache.seen_cache_dirty = false; @@ -1266,7 +1392,7 @@ void map::build_seen_cache( const tripoint &origin, const int target_z ) get_cache( origin.z ).seen_cache[origin.x][origin.y] = VISIBILITY_FULL; } cast_zlight( - seen_caches, transparency_caches, floor_caches, origin, 0, 1.0 ); + seen_caches, transparency_caches, floor_caches, blocked_caches, origin, 0, 1.0 ); } const optional_vpart_position vp = veh_at( origin ); @@ -1322,7 +1448,7 @@ void map::build_seen_cache( const tripoint &origin, const int target_z ) // The naive solution of making the mirrors act like a second player // at an offset appears to give reasonable results though. castLightAll( - camera_cache, transparency_cache, mirror_pos.xy(), offsetDistance ); + camera_cache, transparency_cache, blocked_cache, mirror_pos.xy(), offsetDistance ); } } @@ -1363,6 +1489,7 @@ void map::apply_light_source( const tripoint &p, float luminance ) float ( &sm )[MAPSIZE_X][MAPSIZE_Y] = cache.sm; float ( &transparency_cache )[MAPSIZE_X][MAPSIZE_Y] = cache.transparency_cache; float ( &light_source_buffer )[MAPSIZE_X][MAPSIZE_Y] = cache.light_source_buffer; + diagonal_blocks( &blocked_cache )[MAPSIZE_X][MAPSIZE_Y] = cache.blocked_cache; const point p2( p.xy() ); @@ -1402,37 +1529,37 @@ void map::apply_light_source( const tripoint &p, float luminance ) if( north ) { castLight < 1, 0, 0, -1, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); castLight < -1, 0, 0, -1, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); } if( east ) { castLight < 0, -1, 1, 0, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); castLight < 0, -1, -1, 0, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); } if( south ) { castLight<1, 0, 0, 1, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency>( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); castLight < -1, 0, 0, 1, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); } if( west ) { castLight<0, 1, 1, 0, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency>( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); castLight < 0, 1, -1, 0, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); } } @@ -1443,35 +1570,36 @@ void map::apply_directional_light( const tripoint &p, int direction, float lumin auto &cache = get_cache( p.z ); four_quadrants( &lm )[MAPSIZE_X][MAPSIZE_Y] = cache.lm; float ( &transparency_cache )[MAPSIZE_X][MAPSIZE_Y] = cache.transparency_cache; + diagonal_blocks( &blocked_cache )[MAPSIZE_X][MAPSIZE_Y] = cache.blocked_cache; if( direction == 90 ) { castLight < 1, 0, 0, -1, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); castLight < -1, 0, 0, -1, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); } else if( direction == 0 ) { castLight < 0, -1, 1, 0, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); castLight < 0, -1, -1, 0, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); } else if( direction == 270 ) { castLight<1, 0, 0, 1, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency>( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); castLight < -1, 0, 0, 1, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); } else if( direction == 180 ) { castLight<0, 1, 1, 0, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency>( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); castLight < 0, 1, -1, 0, float, four_quadrants, light_calc, light_check, update_light_quadrants, accumulate_transparency > ( - lm, transparency_cache, p2, 0, luminance ); + lm, transparency_cache, blocked_cache, p2, 0, luminance ); } } diff --git a/src/map.cpp b/src/map.cpp index c7d7bfa013b2..859e4003d30b 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -6413,6 +6413,51 @@ bool map::clear_path( const tripoint &f, const tripoint &t, const int range, return is_clear; } +bool map::obstructed_by_vehicle_rotation( const tripoint &from, const tripoint &to ) +{ + const optional_vpart_position vp0 = veh_at( from ); + vehicle *const veh0 = veh_pointer_or_null( vp0 ); + const optional_vpart_position vp1 = veh_at( to ); + vehicle *const veh1 = veh_pointer_or_null( vp1 ); + + if( veh1 != nullptr ) { + point veh1p = veh1->tripoint_to_mount( from ); + if( !veh1->allowed_move( veh1p, vp1->mount() ) ) { + return true; + } + } + if( veh0 != nullptr && veh1 != veh0 ) { + point veh0p = veh0->tripoint_to_mount( to ); + if( !veh0->allowed_move( vp0->mount(), veh0p ) ) { + return true; + } + } + return false; +} + + +bool map::obscured_by_vehicle_rotation( const tripoint &from, const tripoint &to ) +{ + const optional_vpart_position vp0 = veh_at( from ); + vehicle *const veh0 = veh_pointer_or_null( vp0 ); + const optional_vpart_position vp1 = veh_at( to ); + vehicle *const veh1 = veh_pointer_or_null( vp1 ); + + if( veh1 != nullptr ) { + point veh1p = veh1->tripoint_to_mount( from ); + if( !veh1->allowed_light( veh1p, vp1->mount() ) ) { + return true; + } + } + if( veh0 != nullptr && veh1 != veh0 ) { + point veh0p = veh0->tripoint_to_mount( to ); + if( !veh0->allowed_light( vp0->mount(), veh0p ) ) { + return true; + } + } + return false; +} + bool map::accessible_items( const tripoint &t ) const { return !has_flag( "SEALED", t ) || has_flag( "LIQUIDCONT", t ); @@ -7733,8 +7778,68 @@ void map::build_outside_cache( const int zlev ) ch.outside_cache_dirty = false; } +void map::build_vehicle_rotation_obstacles_cache( const tripoint &start, const tripoint &end, + diagonal_blocks( & blocked_obstacle_cache )[MAPSIZE_X][MAPSIZE_Y] ) +{ + + diagonal_blocks fill = {false, false}; + std::fill_n( &blocked_obstacle_cache[0][0], MAPSIZE_X * MAPSIZE_Y, fill ); + + auto set_blocked = [&blocked_obstacle_cache]( vehicle & veh, const tripoint from ) { + auto from_mount = veh.tripoint_to_mount( from ); + for( int dx = -1; dx <= 1; dx += 2 ) { + for( int dy = -1; dy <= 1; dy += 2 ) { + + auto t = veh.tripoint_to_mount( from + point( dx, dy ) ); + + if( !veh.allowed_move( from_mount, t ) ) { + if( dy == -1 && dx == -1 ) { + blocked_obstacle_cache[from.x][from.y].nw = true; + } else if( dy == -1 && dx == 1 ) { + blocked_obstacle_cache[from.x][from.y].ne = true; + } else if( dy == 1 && dx == -1 ) { + if( from.x == 0 || from.y == MAPSIZE_Y - 1 ) { + continue; + } + blocked_obstacle_cache[from.x - 1][from.y + 1].ne = true; + } else { + if( from.x == MAPSIZE_X - 1 || from.y == MAPSIZE_Y - 1 ) { + continue; + } + blocked_obstacle_cache[from.x + 1][from.y + 1].nw = true; + } + } + } + } + }; + + + VehicleList vehs = get_vehicles( start, end ); + const inclusive_cuboid bounds( start, end ); + for( auto &v : vehs ) { + for( const vpart_reference &vp : v.v->get_all_parts() ) { + tripoint p = v.pos + vp.part().precalc[0]; + if( p.z != start.z ) { + break; + } + if( !bounds.contains( p ) ) { + continue; + } + + for( int dx = -1; dx <= 1; dx += 2 ) { + set_blocked( *v.v, p + tripoint( dx, 0, 0 ) ); + } + + for( int dy = -1; dy <= 1; dy += 2 ) { + set_blocked( *v.v, p + tripoint( 0, dy, 0 ) ); + } + } + } +} + void map::build_obstacle_cache( const tripoint &start, const tripoint &end, - float( &obstacle_cache )[MAPSIZE_X][MAPSIZE_Y] ) + float( &obstacle_cache )[MAPSIZE_X][MAPSIZE_Y], + diagonal_blocks( & blocked_obstacle_cache )[MAPSIZE_X][MAPSIZE_Y] ) { const point min_submap{ std::max( 0, start.x / SEEX ), std::max( 0, start.y / SEEY ) }; const point max_submap{ @@ -7783,6 +7888,8 @@ void map::build_obstacle_cache( const tripoint &start, const tripoint &end, } } } + + build_vehicle_rotation_obstacles_cache( start, end, blocked_obstacle_cache ); } bool map::build_floor_cache( const int zlev ) @@ -8285,9 +8392,9 @@ void map::function_over( const tripoint &start, const tripoint &end, Functor fun } } -void map::scent_blockers( std::array, MAPSIZE_Y> &blocks_scent, - std::array, MAPSIZE_Y> &reduces_scent, - const point &min, const point &max ) +void map::scent_blockers( std::array, MAPSIZE_Y> &scent_transfer, + const point &min, const point &max, + diagonal_blocks( & blocked_obstacle_cache )[MAPSIZE_X][MAPSIZE_Y] ) { auto reduce = TFLAG_REDUCE_SCENT; auto block = TFLAG_NO_SCENT; @@ -8295,15 +8402,12 @@ void map::scent_blockers( std::array, MAPSIZE_Y> &bl // We need to generate the x/y coordinates, because we can't get them "for free" const point p = lp + sm_to_ms_copy( gp.xy() ); if( sm->get_ter( lp ).obj().has_flag( block ) ) { - blocks_scent[p.x][p.y] = true; - reduces_scent[p.x][p.y] = false; + scent_transfer[p.x][p.y] = 0; } else if( sm->get_ter( lp ).obj().has_flag( reduce ) || sm->get_furn( lp ).obj().has_flag( reduce ) ) { - blocks_scent[p.x][p.y] = false; - reduces_scent[p.x][p.y] = true; + scent_transfer[p.x][p.y] = 1; } else { - blocks_scent[p.x][p.y] = false; - reduces_scent[p.x][p.y] = false; + scent_transfer[p.x][p.y] = 5; } return ITER_CONTINUE; @@ -8320,8 +8424,8 @@ void map::scent_blockers( std::array, MAPSIZE_Y> &bl vehicle &veh = *( wrapped_veh.v ); for( const vpart_reference &vp : veh.get_any_parts( VPFLAG_OBSTACLE ) ) { const tripoint part_pos = vp.pos(); - if( local_bounds.contains( part_pos.xy() ) ) { - reduces_scent[part_pos.x][part_pos.y] = true; + if( local_bounds.contains( part_pos.xy() ) && scent_transfer[part_pos.x][part_pos.y] == 5 ) { + scent_transfer[part_pos.x][part_pos.y] = 1; } } @@ -8332,11 +8436,14 @@ void map::scent_blockers( std::array, MAPSIZE_Y> &bl } const tripoint part_pos = vp.pos(); - if( local_bounds.contains( part_pos.xy() ) ) { - reduces_scent[part_pos.x][part_pos.y] = true; + if( local_bounds.contains( part_pos.xy() ) && scent_transfer[part_pos.x][part_pos.y] == 5 ) { + scent_transfer[part_pos.x][part_pos.y] = 1; } } } + + build_vehicle_rotation_obstacles_cache( tripoint( min, abs_sub.z ), tripoint( max, abs_sub.z ), + blocked_obstacle_cache ); } tripoint_range map::points_in_rectangle( const tripoint &from, const tripoint &to ) const @@ -8482,6 +8589,8 @@ level_cache::level_cache() std::fill_n( &outside_cache[0][0], map_dimensions, false ); std::fill_n( &floor_cache[0][0], map_dimensions, false ); std::fill_n( &transparency_cache[0][0], map_dimensions, 0.0f ); + diagonal_blocks fill = {false, false}; + std::fill_n( &blocked_cache[0][0], map_dimensions, fill ); std::fill_n( &vision_transparency_cache[0][0], map_dimensions, 0.0f ); std::fill_n( &seen_cache[0][0], map_dimensions, 0.0f ); std::fill_n( &camera_cache[0][0], map_dimensions, 0.0f ); diff --git a/src/map.h b/src/map.h index 00c4910987dd..eaefa068039c 100644 --- a/src/map.h +++ b/src/map.h @@ -283,6 +283,12 @@ struct drawsq_params { //@} }; +//This is included in the global namespace rather than within level_cache as c++ doesn't allow forward declarations within a namespace +struct diagonal_blocks { + bool nw; + bool ne; +}; + struct level_cache { // Zeros all relevant values level_cache(); @@ -316,6 +322,10 @@ struct level_cache { // units: "transparency" (see LIGHT_TRANSPARENCY_OPEN_AIR) float transparency_cache[MAPSIZE_X][MAPSIZE_Y]; + // true when light entering a tile diagonally is blocked by the walls of a turned vehicle. The direction is the direction that the light must be travelling. + // check the nw value of x+1, y+1 to find the se value of a tile and the ne of x-1, y+1 for sw + diagonal_blocks blocked_cache[MAPSIZE_X][MAPSIZE_Y]; + // stores "adjusted transparency" of the tiles // initial values derived from transparency_cache, uses same units // examples of adjustment: changed transparency on player's tile and special case for crouching @@ -578,7 +588,7 @@ class map void spread_gas( field_entry &cur, const tripoint &p, int percent_spread, const time_duration &outdoor_age_speedup, scent_block &sblk ); void create_hot_air( const tripoint &p, int intensity ); - bool gas_can_spread_to( field_entry &cur, const maptile &dst ); + bool gas_can_spread_to( field_entry &cur, const tripoint &src, const tripoint &dst ); void gas_spread_to( field_entry &cur, maptile &dst, const tripoint &p ); int burn_body_part( player &u, field_entry &cur, body_part bp, int scale ); public: @@ -686,6 +696,16 @@ class map bool clear_path( const tripoint &f, const tripoint &t, int range, int cost_min, int cost_max ) const; + /** + * Checks if a rotated vehicle is blocking diagonal movement, tripoints must be adjacent + */ + bool obstructed_by_vehicle_rotation( const tripoint &from, const tripoint &to ); + + /** + * Checks if a rotated vehicle is blocking diagonal vision, tripoints must be adjacent + */ + bool obscured_by_vehicle_rotation( const tripoint &from, const tripoint &to ); + /** * Populates a vector of points that are reachable within a number of steps from a * point. It could be generalized to take advantage of z levels, but would need some @@ -1488,9 +1508,9 @@ class map * Build the map of scent-resistant tiles. * Should be way faster than if done in `game.cpp` using public map functions. */ - void scent_blockers( std::array, MAPSIZE_Y> &blocks_scent, - std::array, MAPSIZE_Y> &reduces_scent, - const point &min, const point &max ); + void scent_blockers( std::array, MAPSIZE_Y> &scent_transfer, + const point &min, const point &max, + diagonal_blocks( & blocked_obstacle_cache )[MAPSIZE_X][MAPSIZE_Y] ); // Computers computer *computer_at( const tripoint &p ); @@ -1571,7 +1591,12 @@ class map void build_map_cache( int zlev, bool skip_lightmap = false ); // Unlike the other caches, this populates a supplied cache instead of an internal cache. void build_obstacle_cache( const tripoint &start, const tripoint &end, - float( &obstacle_cache )[MAPSIZE_X][MAPSIZE_Y] ); + float( &obstacle_cache )[MAPSIZE_X][MAPSIZE_Y], + diagonal_blocks( &blocked_obstacle_cache )[MAPSIZE_X][MAPSIZE_Y] ); + + //populates a supplied cache with diagonal obstructions due to vehicle rotation + void build_vehicle_rotation_obstacles_cache( const tripoint &start, const tripoint &end, + diagonal_blocks( & blocked_obstacle_cache )[MAPSIZE_X][MAPSIZE_Y] ); vehicle *add_vehicle( const vgroup_id &type, const tripoint &p, units::angle dir, int init_veh_fuel = -1, int init_veh_status = -1, diff --git a/src/map_field.cpp b/src/map_field.cpp index ea21999095dd..5fd30328eba5 100644 --- a/src/map_field.cpp +++ b/src/map_field.cpp @@ -208,14 +208,16 @@ std::array, 8> map::get_neighbors( const tripoint & }; } -bool map::gas_can_spread_to( field_entry &cur, const maptile &dst ) +bool map::gas_can_spread_to( field_entry &cur, const tripoint &src, const tripoint &dst ) { - const field_entry *tmpfld = dst.get_field().find_field( cur.get_field_type() ); + maptile dst_tile = maptile_at( dst ); + const field_entry *tmpfld = dst_tile.get_field().find_field( cur.get_field_type() ); // Candidates are existing weaker fields or navigable/flagged tiles with no field. if( tmpfld == nullptr || tmpfld->get_field_intensity() < cur.get_field_intensity() ) { - const ter_t &ter = dst.get_ter_t(); - const furn_t &frn = dst.get_furn_t(); - return ter_furn_movecost( ter, frn ) > 0 || ter_furn_has_flag( ter, frn, TFLAG_PERMEABLE ); + const ter_t &ter = dst_tile.get_ter_t(); + const furn_t &frn = dst_tile.get_furn_t(); + return !obstructed_by_vehicle_rotation( src, dst ) && + ( ter_furn_movecost( ter, frn ) > 0 || ter_furn_has_flag( ter, frn, TFLAG_PERMEABLE ) ); } return false; } @@ -287,8 +289,8 @@ void map::spread_gas( field_entry &cur, const tripoint &p, int percent_spread, // TODO: Make fall and rise chances parameters to enable heavy/light gas if( zlevels && p.z > -OVERMAP_DEPTH ) { const tripoint down{ p.xy(), p.z - 1 }; - maptile down_tile = maptile_at_internal( down ); - if( gas_can_spread_to( cur, down_tile ) && valid_move( p, down, true, true ) ) { + if( gas_can_spread_to( cur, p, down ) && valid_move( p, down, true, true ) ) { + maptile down_tile = maptile_at_internal( down ); gas_spread_to( cur, down_tile, down ); return; } @@ -306,7 +308,7 @@ void map::spread_gas( field_entry &cur, const tripoint &p, int percent_spread, count != neighs.size(); i = ( i + 1 ) % neighs.size(), count++ ) { const auto &neigh = neighs[i]; - if( gas_can_spread_to( cur, neigh.second ) ) { + if( gas_can_spread_to( cur, p, neigh.first ) ) { spread.push_back( i ); } } @@ -342,8 +344,8 @@ void map::spread_gas( field_entry &cur, const tripoint &p, int percent_spread, } } else if( zlevels && p.z < OVERMAP_HEIGHT ) { const tripoint up{ p.xy(), p.z + 1 }; - maptile up_tile = maptile_at_internal( up ); - if( gas_can_spread_to( cur, up_tile ) && valid_move( p, up, true, true ) ) { + if( gas_can_spread_to( cur, p, up ) && valid_move( p, up, true, true ) ) { + maptile up_tile = maptile_at_internal( up ); gas_spread_to( cur, up_tile, up ); } } diff --git a/src/mattack_actors.cpp b/src/mattack_actors.cpp index 3f0c2647ed95..4a5180db958f 100644 --- a/src/mattack_actors.cpp +++ b/src/mattack_actors.cpp @@ -47,6 +47,10 @@ static bool is_adjacent( const monster &z, const Creature &target ) return false; } + if( !z.can_squeeze_to( target.pos() ) ) { + return false; + } + return z.posz() == target.posz(); } diff --git a/src/melee.cpp b/src/melee.cpp index 66a95c4911ea..b1a3010e86a8 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -639,6 +639,7 @@ void player::reach_attack( const tripoint &p ) int skill = std::min( 10, get_skill_level( skill_stabbing ) ); int t = 0; std::vector path = line_to( pos(), p, t, 0 ); + tripoint last_point = path.back(); path.pop_back(); // Last point is our critter for( const tripoint &path_point : path ) { // Possibly hit some unintended target instead @@ -652,6 +653,18 @@ void player::reach_attack( const tripoint &p ) critter = inter; break; /** @EFFECT_STABBING increases ability to reach attack through fences */ + } else if( g->m.obstructed_by_vehicle_rotation( last_point, path_point ) ) { + tripoint rand = path_point; + if( one_in( 2 ) ) { + rand.x = last_point.x; + } else { + rand.y = last_point.y; + } + + g->m.bash( rand, str_cur + weapon.damage_melee( DT_BASH ) ); + handle_melee_wear( weapon ); + mod_moves( -move_cost ); + return; } else if( g->m.impassable( path_point ) && // Fences etc. Spears can stab through those !( weapon.has_flag( "SPEAR" ) && @@ -663,6 +676,7 @@ void player::reach_attack( const tripoint &p ) mod_moves( -move_cost ); return; } + last_point = path_point; } if( critter == nullptr ) { diff --git a/src/monattack.cpp b/src/monattack.cpp index 68fffcd3fd2b..ece7733b95a0 100644 --- a/src/monattack.cpp +++ b/src/monattack.cpp @@ -282,6 +282,10 @@ static bool is_adjacent( const monster *z, const Creature *target, const bool al return false; } + if( !z->can_squeeze_to( target->pos() ) ) { + return false; + } + if( z->posz() == target->posz() ) { return true; } diff --git a/src/monmove.cpp b/src/monmove.cpp index 757e02fa9b9f..2bcdd40196ec 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -246,6 +246,13 @@ bool monster::can_reach_to( const tripoint &p ) const return true; } +bool monster::can_squeeze_to( const tripoint &p ) const +{ + map &m = get_map(); + + return !m.obstructed_by_vehicle_rotation( pos(), p ); +} + bool monster::can_move_to( const tripoint &p ) const { return can_reach_to( p ) && will_move_to( p ); @@ -801,7 +808,7 @@ void monster::move() bool try_to_move = false; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if( dest != pos() ) { - if( can_move_to( dest ) && + if( can_move_to( dest ) && can_squeeze_to( dest ) && g->critter_at( dest, true ) == nullptr ) { try_to_move = true; break; @@ -964,7 +971,7 @@ void monster::move() // Try to shove vehicle out of the way shove_vehicle( destination, candidate ); // Bail out if we can't move there and we can't bash. - if( !pathed && !can_move_to( candidate ) ) { + if( !pathed && ( !can_move_to( candidate ) || !can_squeeze_to( candidate ) ) ) { if( !can_bash ) { continue; } @@ -1540,6 +1547,10 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, return false; } + if( !can_squeeze_to( destination ) ) { + return false; + } + // Make sure that we can move there, unless force is true. if( !force && !can_move_to( destination ) ) { return false; diff --git a/src/monster.cpp b/src/monster.cpp index 0bf02f93c2f6..9aabc3f10ce0 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1386,6 +1386,10 @@ void monster::melee_attack( Creature &target, float accuracy ) return; } + if( !can_squeeze_to( target.pos() ) ) { + return; + } + if( target.is_player() || ( target.is_npc() && g->u.attitude_to( target ) == A_FRIENDLY ) ) { // Make us a valid target for a few turns diff --git a/src/monster.h b/src/monster.h index d6596516cf45..63164a7b2a64 100644 --- a/src/monster.h +++ b/src/monster.h @@ -186,10 +186,12 @@ class monster : public Creature, public visitable * will_move_to() checks for impassable terrain etc * can_reach_to() checks for z-level difference. * can_move_to() is a wrapper for both of them. + * can_squeeze_to() checks for vehicle holes. */ bool can_move_to( const tripoint &p ) const; bool can_reach_to( const tripoint &p ) const; bool will_move_to( const tripoint &p ) const; + bool can_squeeze_to( const tripoint &p ) const; bool will_reach( const point &p ); // Do we have plans to get to (x, y)? int turns_to_reach( const point &p ); // How long will it take? diff --git a/src/npcmove.cpp b/src/npcmove.cpp index f9da36cc21f5..10c72e78d66a 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -2252,6 +2252,12 @@ void npc::move_to( const tripoint &pt, bool no_bashing, std::set *nomo path.clear(); move_pause(); } + + if( g->m.obstructed_by_vehicle_rotation( pos(), p ) ) { + move_pause(); + return; + } + bool attacking = false; if( g->critter_at( p ) ) { attacking = true; diff --git a/src/pathfinding.cpp b/src/pathfinding.cpp index 9638a0b7f9a9..2e260ac848ae 100644 --- a/src/pathfinding.cpp +++ b/src/pathfinding.cpp @@ -293,6 +293,9 @@ std::vector map::route( const tripoint &f, const tripoint &t, const auto &pf_cache = get_pathfinding_cache_ref( cur.z ); const auto cur_special = pf_cache.special[cur.x][cur.y]; + int cur_part; + const vehicle *cur_veh = veh_at_internal( cur, cur_part ); + // 7 3 5 // 1 . 2 // 6 4 8 @@ -342,6 +345,18 @@ std::vector map::route( const tripoint &f, const tripoint &t, continue; } + if( cur_veh && + !cur_veh->allowed_move( cur_veh->tripoint_to_mount( cur ), cur_veh->tripoint_to_mount( p ) ) ) { + //Trying to squeeze through a vehicle hole, skip this movement but don't close the tile as other paths may lead to it + continue; + } + + if( veh && veh != cur_veh && + !veh->allowed_move( veh->tripoint_to_mount( cur ), veh->tripoint_to_mount( p ) ) ) { + //Same as above but moving into rather than out of a vehicle + continue; + } + newg += cost; if( cost == 0 ) { if( climb_cost > 0 && p_special & PF_CLIMBABLE ) { diff --git a/src/scent_map.cpp b/src/scent_map.cpp index 72edcb576050..5470d0fad279 100644 --- a/src/scent_map.cpp +++ b/src/scent_map.cpp @@ -147,7 +147,6 @@ bool scent_map::inbounds( const tripoint &p ) const return scent_map_boundaries.contains( p.xy() ); } - void scent_map::update( const tripoint ¢er, map &m ) { // Stop updating scent after X turns of the player not moving. @@ -159,17 +158,15 @@ void scent_map::update( const tripoint ¢er, map &m ) return; } - // note: the next four intermediate matrices need to be at least - // [2*SCENT_RADIUS+3][2*SCENT_RADIUS+1] in size to hold enough data - // The code I'm modifying used [MAPSIZE_X]. I'm staying with that to avoid new bugs. + //the block and reduce scent properties are folded into a single scent_transfer value here + //block=0 reduce=1 normal=5 + scent_array scent_transfer; - // These two matrices are transposed so that x addresses are contiguous in memory - scent_array sum_3_scent_y; - scent_array squares_used_y; + std::array < std::array < int, 3 + SCENT_RADIUS * 2 >, 1 + SCENT_RADIUS * 2 > new_scent; + std::array < std::array < int, 3 + SCENT_RADIUS * 2 >, 1 + SCENT_RADIUS * 2 > sum_3_scent_y; + std::array < std::array < char, 3 + SCENT_RADIUS * 2 >, 1 + SCENT_RADIUS * 2 > squares_used_y; - // these are for caching flag lookups - scent_array blocks_scent; // currently only TFLAG_NO_SCENT blocks scent - scent_array reduces_scent; + diagonal_blocks blocked_cache[MAPSIZE_X][MAPSIZE_Y]; // for loop constants const int scentmap_minx = center.x - SCENT_RADIUS; @@ -177,73 +174,70 @@ void scent_map::update( const tripoint ¢er, map &m ) const int scentmap_miny = center.y - SCENT_RADIUS; const int scentmap_maxy = center.y + SCENT_RADIUS; - // decrease this to reduce gas spread. Keep it under 125 for - // stability. This is essentially a decimal number * 1000. - const int diffusivity = 100; - // The new scent flag searching function. Should be wayyy faster than the old one. - m.scent_blockers( blocks_scent, reduces_scent, point( scentmap_minx - 1, scentmap_miny - 1 ), - point( scentmap_maxx + 1, scentmap_maxy + 1 ) ); - // Sum neighbors in the y direction. This way, each square gets called 3 times instead of 9 - // times. This cost us an extra loop here, but it also eliminated a loop at the end, so there - // is a net performance improvement over the old code. Could probably still be better. - // note: this method needs an array that is one square larger on each side in the x direction - // than the final scent matrix. I think this is fine since SCENT_RADIUS is less than - // MAPSIZE_X, but if that changes, this may need tweaking. - for( int x = scentmap_minx - 1; x <= scentmap_maxx + 1; ++x ) { - for( int y = scentmap_miny; y <= scentmap_maxy; ++y ) { + m.scent_blockers( scent_transfer, point( scentmap_minx - 1, scentmap_miny - 1 ), + point( scentmap_maxx + 1, scentmap_maxy + 1 ), blocked_cache ); + + for( int x = 0; x < SCENT_RADIUS * 2 + 3; ++x ) { + sum_3_scent_y[0][x] = 0; + squares_used_y[0][x] = 0; + sum_3_scent_y[SCENT_RADIUS * 2][x] = 0; + squares_used_y[SCENT_RADIUS * 2][x] = 0; + } + + for( int x = 0; x < SCENT_RADIUS * 2 + 3; ++x ) { + for( int y = 0; y < SCENT_RADIUS * 2 + 1; ++y ) { + + point abs( x + scentmap_minx - 1, y + scentmap_miny ); + // remember the sum of the scent val for the 3 neighboring squares that can defuse into sum_3_scent_y[y][x] = 0; squares_used_y[y][x] = 0; - for( int i = y - 1; i <= y + 1; ++i ) { - if( !blocks_scent[x][i] ) { - if( reduces_scent[x][i] ) { - // only 20% of scent can diffuse on REDUCE_SCENT squares - sum_3_scent_y[y][x] += 2 * grscent[x][i]; - squares_used_y[y][x] += 2; - } else { - sum_3_scent_y[y][x] += 10 * grscent[x][i]; - squares_used_y[y][x] += 10; - } - } + for( int i = abs.y - 1; i <= abs.y + 1; ++i ) { + sum_3_scent_y[y][x] += scent_transfer[abs.x][i] * grscent[abs.x][i]; + squares_used_y[y][x] += scent_transfer[abs.x][i]; } } } - // Rest of the scent map - for( int x = scentmap_minx; x <= scentmap_maxx; ++x ) { - for( int y = scentmap_miny; y <= scentmap_maxy; ++y ) { - int &scent_here = grscent[x][y]; - if( !blocks_scent[x][y] ) { - // to how many neighboring squares do we diffuse out? (include our own square - // since we also include our own square when diffusing in) - const int squares_used = squares_used_y[y][x - 1] - + squares_used_y[y][x] - + squares_used_y[y][x + 1]; - - int this_diffusivity; - if( !reduces_scent[x][y] ) { - this_diffusivity = diffusivity; - } else { - this_diffusivity = diffusivity / 5; //less air movement for REDUCE_SCENT square - } - // take the old scent and subtract what diffuses out - int temp_scent = scent_here * ( 10 * 1000 - squares_used * this_diffusivity ); - // neighboring REDUCE_SCENT squares absorb some scent - temp_scent -= scent_here * this_diffusivity * ( 90 - squares_used ) / 5; - // we've already summed neighboring scent values in the y direction in the previous - // loop. Now we do it for the x direction, multiply by diffusion, and this is what - // diffuses into our current square. - scent_here = - ( temp_scent - + this_diffusivity * ( sum_3_scent_y[y][x - 1] - + sum_3_scent_y[y][x] - + sum_3_scent_y[y][x + 1] ) - ) / ( 1000 * 10 ); - } else { - // this cell blocks scent via NO_SCENT (in json) - scent_here = 0; + for( int x = 1; x < SCENT_RADIUS * 2 + 2; ++x ) { + for( int y = 0; y < SCENT_RADIUS * 2 + 1; ++y ) { + const point abs( x + scentmap_minx - 1, y + scentmap_miny ); + + int squares_used = squares_used_y[y][x - 1] + squares_used_y[y][x] + squares_used_y[y][x + 1]; + int total = sum_3_scent_y[y][x - 1] + sum_3_scent_y[y][x] + sum_3_scent_y[y][x + 1]; + + //handle vehicle holes + if( blocked_cache[abs.x][abs.y].nw && scent_transfer[abs.x + 1][abs.y + 1] == 5 ) { + squares_used -= 4; + total -= 4 * grscent[abs.x + 1][abs.y + 1]; } + if( blocked_cache[abs.x][abs.y].ne && scent_transfer[abs.x - 1][abs.y + 1] == 5 ) { + squares_used -= 4; + total -= 4 * grscent[abs.x - 1][abs.y + 1]; + } + if( blocked_cache[abs.x - 1][abs.y - 1].nw && scent_transfer[abs.x - 1][abs.y - 1] == 5 ) { + squares_used -= 4; + total -= 4 * grscent[abs.x - 1][abs.y - 1]; + } + if( blocked_cache[abs.x + 1][abs.y - 1].ne && scent_transfer[abs.x + 1][abs.y - 1] == 5 ) { + squares_used -= 4; + total -= 4 * grscent[abs.x + 1][abs.y - 1]; + } + + //Lingering scent + int temp_scent = grscent[abs.x][abs.y] * ( 250 - squares_used * + scent_transfer[abs.x][abs.y] ) ; + temp_scent -= grscent[abs.x][abs.y] * scent_transfer[abs.x][abs.y] * + ( 45 - squares_used ) / 5; + + new_scent[y][x] = ( temp_scent + total * scent_transfer[abs.x][abs.y] ) / 250; + + } + } + for( int x = 1; x < SCENT_RADIUS * 2 + 2; ++x ) { + for( int y = 0; y < SCENT_RADIUS * 2 + 1; ++y ) { + grscent[x + scentmap_minx - 1 ][y + scentmap_miny] = new_scent[y][x]; } } } diff --git a/src/scent_map.h b/src/scent_map.h index 18e5618bf839..3f0b3bdd2e72 100644 --- a/src/scent_map.h +++ b/src/scent_map.h @@ -53,6 +53,12 @@ class scent_map const game &gm; public: + + struct scent_properties { + bool block; + bool reduce; + }; + scent_map( const game &g ) : gm( g ) { } void deserialize( const std::string &data, bool is_type = false ); diff --git a/src/shadowcasting.h b/src/shadowcasting.h index 7e8409ccfbc9..2988dbc08e0c 100644 --- a/src/shadowcasting.h +++ b/src/shadowcasting.h @@ -10,8 +10,10 @@ #include "game_constants.h" #include "lightmap.h" + struct point; struct tripoint; +struct diagonal_blocks; // For light we store four values, depending on the direction that the light // comes from. This allows us to determine whether the side of the wall the @@ -106,9 +108,10 @@ inline float accumulate_transparency( const float &cumulative_transparency, template + T( *accumulate )( const T &, const T &, const int & ) > void castLightAll( Out( &output_cache )[MAPSIZE_X][MAPSIZE_Y], const T( &input_array )[MAPSIZE_X][MAPSIZE_Y], + const diagonal_blocks( &blocked_array )[MAPSIZE_X][MAPSIZE_Y], const point &offset, int offsetDistance = 0, T numerator = 1.0 ); @@ -123,6 +126,7 @@ void cast_zlight( const array_of_grids_of &output_caches, const array_of_grids_of &input_arrays, const array_of_grids_of &floor_caches, + const array_of_grids_of &blocked_caches, const tripoint &origin, int offset_distance, T numerator ); #endif // CATA_SRC_SHADOWCASTING_H diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 982f6663ee3c..cdeed3c5bea2 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2576,6 +2576,40 @@ bool vehicle::has_part( const tripoint &pos, const std::string &flag, bool enabl return false; } +int vehicle::obstacle_at_position( const point &pos ) const +{ + int i = part_with_feature( pos, "OBSTACLE", true ); + + if( i == -1 ) { + return -1; + } + + auto ref = parts[i]; + + if( ref.info().has_flag( VPFLAG_OPENABLE ) && ref.open ) { + return -1; + } + + return i; +} + +int vehicle::opaque_at_position( const point &pos ) const +{ + int i = part_with_feature( pos, "OPAQUE", true ); + + if( i == -1 ) { + return -1; + } + + auto ref = parts[i]; + + if( ref.info().has_flag( VPFLAG_OPENABLE ) && ref.open ) { + return -1; + } + + return i; +} + std::vector vehicle::get_parts_at( const tripoint &pos, const std::string &flag, const part_status_flag condition ) { @@ -2991,21 +3025,91 @@ point vehicle::coord_translate( const point &p ) const return q.xy(); } +const struct { + float gradient; + bool flipH; + bool flipV; + bool swapXY; +} rotation_info[24] = { + {static_cast( tan( units::to_radians( 0_degrees ) ) ), false, false, false}, //0 degrees + {static_cast( tan( units::to_radians( 15_degrees ) ) ), false, false, false}, + {static_cast( tan( units::to_radians( 30_degrees ) ) ), false, false, false}, + {static_cast( -tan( units::to_radians( 45_degrees ) ) ), true, false, true}, //45 degrees + {static_cast( -tan( units::to_radians( 30_degrees ) ) ), true, false, true}, + {static_cast( -tan( units::to_radians( 15_degrees ) ) ), true, false, true}, + {static_cast( tan( units::to_radians( 0_degrees ) ) ), true, false, true}, //90 degrees + {static_cast( tan( units::to_radians( 15_degrees ) ) ), true, false, true}, + {static_cast( tan( units::to_radians( 30_degrees ) ) ), true, false, true}, + {static_cast( tan( units::to_radians( 45_degrees ) ) ), true, false, true}, //135 degrees + {static_cast( -tan( units::to_radians( 30_degrees ) ) ), true, true, false}, + {static_cast( -tan( units::to_radians( 15_degrees ) ) ), true, true, false}, + {static_cast( tan( units::to_radians( 0_degrees ) ) ), true, true, false}, //180 degrees + {static_cast( tan( units::to_radians( 15_degrees ) ) ), true, true, false}, + {static_cast( tan( units::to_radians( 30_degrees ) ) ), true, true, false}, + {static_cast( -tan( units::to_radians( 45_degrees ) ) ), false, true, true}, //225 degrees + {static_cast( -tan( units::to_radians( 30_degrees ) ) ), false, true, true}, + {static_cast( -tan( units::to_radians( 15_degrees ) ) ), false, true, true}, + {static_cast( tan( units::to_radians( 0_degrees ) ) ), false, true, true}, //270 degrees + {static_cast( tan( units::to_radians( 15_degrees ) ) ), false, true, true}, + {static_cast( tan( units::to_radians( 30_degrees ) ) ), false, true, true}, + {static_cast( tan( units::to_radians( 45_degrees ) ) ), false, true, true}, //315 degrees + {static_cast( -tan( units::to_radians( 30_degrees ) ) ), false, false, false}, + {static_cast( -tan( units::to_radians( 15_degrees ) ) ), false, false, false}, +}; + void vehicle::coord_translate( units::angle dir, const point &pivot, const point &p, tripoint &q ) const { - tileray tdir( dir ); - tdir.advance( p.x - pivot.x ); - q.x = tdir.dx() + tdir.ortho_dx( p.y - pivot.y ); - q.y = tdir.dy() + tdir.ortho_dy( p.y - pivot.y ); + + int increment = angle_to_increment( dir ); + point relative = p - pivot; + float skew = std::trunc( relative.x * rotation_info[increment].gradient ); + + q.x = relative.x; + q.y = relative.y + skew; + + if( rotation_info[increment].swapXY ) { + auto swap = q.x; + q.x = q.y; + q.y = swap; + } + if( rotation_info[increment].flipH ) { + q.x = -q.x; + } + if( rotation_info[increment].flipV ) { + q.y = -q.y; + } } -void vehicle::coord_translate( tileray tdir, const point &pivot, const point &p, tripoint &q ) const +void vehicle::coord_translate_reverse( units::angle dir, const point &pivot, const tripoint &p, + point &q ) const { - tdir.clear_advance(); - tdir.advance( p.x - pivot.x ); - q.x = tdir.dx() + tdir.ortho_dx( p.y - pivot.y ); - q.y = tdir.dy() + tdir.ortho_dy( p.y - pivot.y ); + int increment = angle_to_increment( dir ); + + q.x = p.x; + q.y = p.y; + + + if( rotation_info[increment].flipV ) { + q.y = -q.y; + } + + if( rotation_info[increment].flipH ) { + q.x = -q.x; + } + + if( rotation_info[increment].swapXY ) { + auto swap = q.x; + q.x = q.y; + q.y = swap; + } + + float skew = std::trunc( q.x * rotation_info[increment].gradient ); + + q.y -= skew; + + q += pivot; + } tripoint vehicle::mount_to_tripoint( const point &mount ) const @@ -3020,20 +3124,41 @@ tripoint vehicle::mount_to_tripoint( const point &mount, const point &offset ) c return global_pos3() + mnt_translated; } +point vehicle::tripoint_to_mount( const tripoint &p ) const +{ + tripoint translated = p - global_pos3(); + + point result; + coord_translate_reverse( pivot_rotation[0], pivot_anchor[0], translated, result ); + + return result; +} + +int vehicle::angle_to_increment( units::angle dir ) +{ + int increment = ( std::lround( to_degrees( dir ) ) % 360 ) / 15; + if( increment < 0 ) { + increment += 360 / 15; + } + return increment; +} + + void vehicle::precalc_mounts( int idir, units::angle dir, const point &pivot ) { if( idir < 0 || idir > 1 ) { idir = 0; } - tileray tdir( dir ); std::unordered_map mount_to_precalc; + for( auto &p : parts ) { if( p.removed ) { continue; } + auto q = mount_to_precalc.find( p.mount ); if( q == mount_to_precalc.end() ) { - coord_translate( tdir, pivot, p.mount, p.precalc[idir] ); + coord_translate( dir, pivot, p.mount, p.precalc[idir] ); mount_to_precalc.insert( { p.mount, p.precalc[idir] } ); } else { p.precalc[idir] = q->second; @@ -3043,6 +3168,60 @@ void vehicle::precalc_mounts( int idir, units::angle dir, const point &pivot ) pivot_rotation[idir] = dir; } +bool vehicle::check_rotated_intervening( const point &from, const point &to, + bool( *check )( const vehicle *, const point & ) ) const +{ + point delta = to - from; + if( abs( delta.x ) <= 1 && abs( delta.y ) <= 1 ) { //Just a normal move + return true; + } + + if( !( ( abs( delta.x ) == 2 && abs( delta.y ) == 1 ) || ( abs( delta.x ) == 1 && + abs( delta.y ) == 2 ) ) ) { //Check that we're moving like a knight + debugmsg( "Unexpected movement in rotated vehicle vector:%d,%d", delta.x, delta.y ); + return false; + } + + if( abs( delta.x ) == 2 ) { //Mostly horizontal move + point t1 = from + point( delta.x / 2, delta.y ); + if( check( this, t1 ) ) { + return true; + } + + point t2 = from + point( delta.x / 2, 0 ); + if( check( this, t2 ) ) { + return true; + } + + } else { //Mostly vertical move + point t1 = from + point( delta.x, delta.y / 2 ); + if( check( this, t1 ) ) { + return true; + } + + point t2 = from + point( 0, delta.y / 2 ); + if( check( this, t2 ) ) { + return true; + } + } + + return false; +} + +bool vehicle::allowed_light( const point &from, const point &to ) const +{ + return check_rotated_intervening( from, to, []( const vehicle * veh, const point & p ) { + return ( veh->opaque_at_position( p ) == -1 ); + } ); +} + +bool vehicle::allowed_move( const point &from, const point &to ) const +{ + return check_rotated_intervening( from, to, []( const vehicle * veh, const point & p ) { + return ( veh->obstacle_at_position( p ) == -1 ); + } ); +} + std::vector vehicle::boarded_parts() const { std::vector res; diff --git a/src/vehicle.h b/src/vehicle.h index e586209c48d9..898f5bafbc3a 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -993,6 +993,9 @@ class vehicle int avail_part_with_feature( const point &pt, const std::string &f, bool unbroken ) const; int avail_part_with_feature( int p, vpart_bitflags f, bool unbroken ) const; + int obstacle_at_position( const point &pos ) const; + int opaque_at_position( const point &pos ) const; + /** * Check if vehicle has at least one unbroken part with specified flag * @param flag Specified flag to search parts for @@ -1065,13 +1068,17 @@ class vehicle // Translate mount coordinates "p" into tile coordinates "q" using given pivot direction and anchor void coord_translate( units::angle dir, const point &pivot, const point &p, tripoint &q ) const; - // Translate mount coordinates "p" into tile coordinates "q" using given tileray and anchor - // should be faster than previous call for repeated translations - void coord_translate( tileray tdir, const point &pivot, const point &p, tripoint &q ) const; + + // Translate rotated tile coordinates "p" into mount coordinates "q" using given pivot direction and anchor + void coord_translate_reverse( units::angle dir, const point &pivot, const tripoint &p, + point &q ) const; tripoint mount_to_tripoint( const point &mount ) const; tripoint mount_to_tripoint( const point &mount, const point &offset ) const; + //Translate tile coordinates into mount coordinates + point tripoint_to_mount( const tripoint &p ) const; + // Seek a vehicle part which obstructs tile with given coordinates relative to vehicle position int part_at( const point &dp ) const; int part_displayed_at( const point &dp ) const; @@ -1206,6 +1213,9 @@ class vehicle */ void invalidate_mass(); + //Converts angles into turning increments + static int angle_to_increment( units::angle dir ); + // get the total mass of vehicle, including cargo and passengers units::mass total_mass() const; @@ -1716,6 +1726,16 @@ class vehicle void interact_with( const tripoint &pos, int interact_part ); + //Check if a movement is blocked, must be adjacent points + bool allowed_move( const point &from, const point &to ) const; + + //Check if light is blocked, must be adjacent points + bool allowed_light( const point &from, const point &to ) const; + + //Checks if the conditional holds for tiles that can be skipped due to rotation + bool check_rotated_intervening( const point &from, const point &to, bool( *check )( const vehicle *, + const point & ) ) const; + std::string disp_name() const; /** Required strength to be able to successfully lift the vehicle unaided by equipment */ diff --git a/src/vehicle_autodrive.cpp b/src/vehicle_autodrive.cpp index b6ffbfd1bdbc..ffcb3726e549 100644 --- a/src/vehicle_autodrive.cpp +++ b/src/vehicle_autodrive.cpp @@ -577,7 +577,9 @@ void vehicle::autodrive_controller::compute_coordinates() vehicle_profile vehicle::autodrive_controller::compute_profile( orientation facing ) const { vehicle_profile ret; - tileray tdir( to_angle( facing ) ); + + auto angle = to_angle( facing ); + tileray tdir( angle ); ret.tdir = tdir; std::map> extent_map; const point pivot = driven_veh.pivot_point(); @@ -586,7 +588,7 @@ vehicle_profile vehicle::autodrive_controller::compute_profile( orientation faci continue; } tripoint pos; - driven_veh.coord_translate( tdir, pivot, part.mount, pos ); + driven_veh.coord_translate( angle, pivot, part.mount, pos ); if( extent_map.find( pos.y ) == extent_map.end() ) { extent_map[pos.y] = { pos.x, pos.x }; } else { @@ -609,7 +611,7 @@ vehicle_profile vehicle::autodrive_controller::compute_profile( orientation faci const int radius = ( diameter + 1 ) / 2; if( radius > 0 ) { tripoint pos; - driven_veh.coord_translate( tdir, pivot, part.mount, pos ); + driven_veh.coord_translate( angle, pivot, part.mount, pos ); for( tripoint pt : points_in_radius( pos, radius ) ) { ret.occupied_zone.emplace_back( pt.xy() ); } diff --git a/tests/explosion_balance_test.cpp b/tests/explosion_balance_test.cpp index f0328224728e..93b385ce8289 100644 --- a/tests/explosion_balance_test.cpp +++ b/tests/explosion_balance_test.cpp @@ -241,3 +241,30 @@ TEST_CASE( "shrapnel at max grenade range", "[grenade],[explosion]" ) } } } + +TEST_CASE( "rotated_vehicle_walls_block_explosions" ) +{ + clear_map_and_put_player_underground(); + tripoint origin( 60, 60, 0 ); + + item grenade( "can_bomb_act" ); + + g->m.add_vehicle( vproto_id( "apc" ), origin, -45_degrees, 0, 0 ); + + tripoint mon_origin = origin + tripoint( -2, 1, 0 ); + + monster &s = spawn_test_monster( "mon_squirrel", mon_origin ); + + REQUIRE( veh_pointer_or_null( g->m.veh_at( mon_origin ) ) != nullptr ); + + tripoint explode_at = mon_origin + tripoint_north_west; + + REQUIRE( veh_pointer_or_null( g->m.veh_at( explode_at ) ) == nullptr ); + + set_off_explosion( grenade, explode_at ); + + const monster *m = g->critter_at( mon_origin ); + REQUIRE( m != nullptr ); + CHECK( m == &s ); + CHECK( m->get_hp() == m->get_hp_max() ); +} diff --git a/tests/monster_test.cpp b/tests/monster_test.cpp index 8546a2d18219..02b18aa580ee 100644 --- a/tests/monster_test.cpp +++ b/tests/monster_test.cpp @@ -328,3 +328,22 @@ TEST_CASE( "monster_speed_trig", "[speed]" ) trigdist = true; monster_check(); } + +TEST_CASE( "monster_move_through_vehicle_holes" ) +{ + clear_map_and_put_player_underground(); + tripoint origin( 60, 60, 0 ); + + g->m.add_vehicle( vproto_id( "apc" ), origin, -45_degrees, 0, 0 ); + + tripoint mon_origin = origin + tripoint( -2, 1, 0 ); + monster &zombie = spawn_test_monster( "mon_zombie", mon_origin ); + zombie.move_to( mon_origin + tripoint_north_west, false, false, 0.0f ); + + const monster *m = g->critter_at( mon_origin ); + CHECK( m != nullptr ); + + const monster *m2 = g->critter_at( mon_origin + tripoint_north_west ); + CHECK( m2 == nullptr ); + +} diff --git a/tests/monster_vision_test.cpp b/tests/monster_vision_test.cpp index e7f379b83953..2a4b8d99f128 100644 --- a/tests/monster_vision_test.cpp +++ b/tests/monster_vision_test.cpp @@ -78,3 +78,24 @@ TEST_CASE( "monsters shouldn't see through floors", "[vision]" ) CHECK( distant.sees( sky ) ); fov_3d = old_fov_3d; } + +TEST_CASE( "monsters_dont_see_through_vehicle_holes", "[vision]" ) +{ + calendar::turn = midday; + clear_map_and_put_player_underground(); + tripoint origin( 60, 60, 0 ); + + g->m.add_vehicle( vproto_id( "apc" ), origin, -45_degrees, 0, 0 ); + + tripoint mon_origin = origin + tripoint( -2, 1, 0 ); + + monster &inside = spawn_test_monster( "mon_zombie", mon_origin ); + + tripoint second_origin = mon_origin + tripoint_north_west; + + monster &outside = spawn_test_monster( "mon_zombie", second_origin ); + + CHECK( !inside.sees( outside ) ); + CHECK( !outside.sees( inside ) ); + +} diff --git a/tests/npc_test.cpp b/tests/npc_test.cpp index 61fabb417919..2e506e75a8e4 100644 --- a/tests/npc_test.cpp +++ b/tests/npc_test.cpp @@ -372,6 +372,8 @@ TEST_CASE( "npc-movement" ) // the NPC deems themselves to be guarding and stops them // wandering off in search of distant ammo caches, etc. guy->mission = NPC_MISSION_SHOPKEEP; + // This prevents npcs occasionally teleporting away + guy->assign_activity( activity_id( "ACT_MEDITATE" ) ); overmap_buffer.insert_npc( guy ); g->load_npcs(); guy->set_attitude( ( type == 'M' || type == 'C' ) ? NPCATT_NULL : NPCATT_FOLLOW ); @@ -455,3 +457,31 @@ TEST_CASE( "npc_can_target_player" ) REQUIRE( hostile.current_target() != nullptr ); CHECK( hostile.current_target() == static_cast( &player_character ) ); } + +TEST_CASE( "npc_move_through_vehicle_holes" ) +{ + g->place_player( tripoint( 65, 55, 0 ) ); + clear_map(); + tripoint origin( 60, 60, 0 ); + + g->m.add_vehicle( vproto_id( "apc" ), origin, -45_degrees, 0, 0 ); + + tripoint mon_origin = origin + tripoint( -2, 1, 0 ); + + shared_ptr_fast guy = make_shared_fast(); + guy->normalize(); + guy->randomize(); + guy->spawn_at_precise( {g->get_levx(), g->get_levy()}, mon_origin ); + + overmap_buffer.insert_npc( guy ); + g->load_npcs(); + + guy->move_to( mon_origin + tripoint_north_west, true, nullptr ); + + const npc *m = g->critter_at( mon_origin ); + CHECK( m != nullptr ); + + const npc *m2 = g->critter_at( mon_origin + tripoint_north_west ); + CHECK( m2 == nullptr ); + +} diff --git a/tests/player_test.cpp b/tests/player_test.cpp index 220c4dee3b5e..7aa849602f24 100644 --- a/tests/player_test.cpp +++ b/tests/player_test.cpp @@ -6,6 +6,8 @@ #include #include "avatar.h" +#include "avatar_action.h" +#include "catch/catch.hpp" #include "player.h" #include "weather.h" #include "bodypart.h" @@ -525,3 +527,22 @@ TEST_CASE( "Water hypothermia check.", "[.][bodytemp]" ) hypothermia_check( dummy, units::celsius_to_fahrenheit( 0 ), 5_minutes, BODYTEMP_FREEZING ); } } + +TEST_CASE( "player_move_through_vehicle_holes" ) +{ + clear_map(); + clear_avatar(); + + player &dummy = get_avatar(); + + const tripoint &pos = dummy.pos(); + + g->m.add_vehicle( vproto_id( "apc" ), pos + tripoint( 2, -1, 0 ), -45_degrees, 0, 0 ); + + REQUIRE( get_avatar().pos() == pos ); + + avatar_action::move( get_avatar(), g->m, point_north_west ); + + CHECK( get_avatar().pos() == pos ); + +} diff --git a/tests/scent_test.cpp b/tests/scent_test.cpp new file mode 100644 index 000000000000..c85b1fd35c1e --- /dev/null +++ b/tests/scent_test.cpp @@ -0,0 +1,172 @@ + +#include "scent_map.h" +#include "catch/catch.hpp" +#include "map.h" +#include "map_helpers.h" +#include "game.h" +void old_scent_map_update( const tripoint ¢er, map &m, + std::array, MAPSIZE_X> &grscent ); + +static constexpr int SCENT_RADIUS = 40; +void old_scent_map_update( const tripoint ¢er, map &m, + std::array, MAPSIZE_X> &grscent ) +{ + + // note: the next four intermediate matrices need to be at least + // [2*SCENT_RADIUS+3][2*SCENT_RADIUS+1] in size to hold enough data + // The code I'm modifying used [MAPSIZE_X]. I'm staying with that to avoid new bugs. + + // These two matrices are transposed so that x addresses are contiguous in memory + std::array, MAPSIZE_X> sum_3_scent_y; + std::array, MAPSIZE_X> squares_used_y; + + // these are for caching flag lookups + std::array, MAPSIZE_X> + blocks_scent; // currently only TFLAG_NO_SCENT blocks scent + std::array, MAPSIZE_X> reduces_scent; + + + std::array, MAPSIZE_X> monkey; + + diagonal_blocks monkey2[MAPSIZE_X][MAPSIZE_Y]; + + + // for loop constants + const int scentmap_minx = center.x - SCENT_RADIUS; + const int scentmap_maxx = center.x + SCENT_RADIUS; + const int scentmap_miny = center.y - SCENT_RADIUS; + const int scentmap_maxy = center.y + SCENT_RADIUS; + + // decrease this to reduce gas spread. Keep it under 125 for + // stability. This is essentially a decimal number * 1000. + const int diffusivity = 100; + + // The new scent flag searching function. Should be wayyy faster than the old one. + m.scent_blockers( monkey, point( scentmap_minx - 1, scentmap_miny - 1 ), + point( scentmap_maxx + 1, scentmap_maxy + 1 ), monkey2 ); + + for( int x = 0; x < MAPSIZE_X; x++ ) { + for( int y = 0; y < MAPSIZE_Y; y++ ) { + if( monkey[x][y] == 0 ) { + blocks_scent[x][y] = true; + reduces_scent[x][y] = false; + } else if( monkey[x][y] == 1 ) { + blocks_scent[x][y] = false; + reduces_scent[x][y] = true; + } else { + blocks_scent[x][y] = false; + reduces_scent[x][y] = false; + } + } + } + // Sum neighbors in the y direction. This way, each square gets called 3 times instead of 9 + // times. This cost us an extra loop here, but it also eliminated a loop at the end, so there + // is a net performance improvement over the old code. Could probably still be better. + // note: this method needs an array that is one square larger on each side in the x direction + // than the final scent matrix. I think this is fine since SCENT_RADIUS is less than + // MAPSIZE_X, but if that changes, this may need tweaking. + for( int x = scentmap_minx - 1; x <= scentmap_maxx + 1; ++x ) { + for( int y = scentmap_miny; y <= scentmap_maxy; ++y ) { + // remember the sum of the scent val for the 3 neighboring squares that can defuse into + sum_3_scent_y[y][x] = 0; + squares_used_y[y][x] = 0; + for( int i = y - 1; i <= y + 1; ++i ) { + if( !blocks_scent[x][i] ) { + if( reduces_scent[x][i] ) { + // only 20% of scent can diffuse on REDUCE_SCENT squares + sum_3_scent_y[y][x] += 2 * grscent[x][i]; + squares_used_y[y][x] += 2; + } else { + sum_3_scent_y[y][x] += 10 * grscent[x][i]; + squares_used_y[y][x] += 10; + } + } + } + } + } + + // Rest of the scent map + for( int x = scentmap_minx; x <= scentmap_maxx; ++x ) { + for( int y = scentmap_miny; y <= scentmap_maxy; ++y ) { + int &scent_here = grscent[x][y]; + if( !blocks_scent[x][y] ) { + // to how many neighboring squares do we diffuse out? (include our own square + // since we also include our own square when diffusing in) + const int squares_used = squares_used_y[y][x - 1] + + squares_used_y[y][x] + + squares_used_y[y][x + 1]; + + int this_diffusivity; + if( !reduces_scent[x][y] ) { + this_diffusivity = diffusivity; + } else { + this_diffusivity = diffusivity / 5; //less air movement for REDUCE_SCENT square + } + // take the old scent and subtract what diffuses out + int temp_scent = scent_here * ( 10 * 1000 - squares_used * this_diffusivity ); + // neighboring REDUCE_SCENT squares absorb some scent + temp_scent -= scent_here * this_diffusivity * ( 90 - squares_used ) / 5; + + // we've already summed neighboring scent values in the y direction in the previous + // loop. Now we do it for the x direction, multiply by diffusion, and this is what + // diffuses into our current square. + scent_here = + ( temp_scent + + this_diffusivity * ( sum_3_scent_y[y][x - 1] + + sum_3_scent_y[y][x] + + sum_3_scent_y[y][x + 1] ) + ) / ( 1000 * 10 ); + } else { + // this cell blocks scent via NO_SCENT (in json) + scent_here = 0; + } + } + } +} + +TEST_CASE( "scent_matches_old" ) +{ + clear_map(); + + tripoint origin( 60, 60, 0 ); + + g->place_player( origin ); + + get_map().ter_set( origin + tripoint_south_west, t_brick_wall ); + get_map().ter_set( origin + tripoint_west, t_brick_wall ); + get_map().ter_set( origin + tripoint_north, t_rock_wall_half ); + get_map().ter_set( origin, t_rock_wall_half ); + g->scent.reset(); + + g->scent.set( origin, 1000, scenttype_id( "sc_human" ) ); + + g->scent.update( origin, g->m ); + g->scent.update( origin, g->m ); + g->scent.update( origin, g->m ); + + std::array, MAPSIZE_X> old_scent; + for( auto &elem : old_scent ) { + for( auto &val : elem ) { + val = 0; + } + } + + old_scent[origin.x][origin.y] = 1000; + + old_scent_map_update( origin, g->m, old_scent ); + old_scent_map_update( origin, g->m, old_scent ); + old_scent_map_update( origin, g->m, old_scent ); + int x = 0; + for( auto &elem : old_scent ) { + int y = 0; + for( auto &val : elem ) { + + INFO( x ); + INFO( y ); + CHECK( val == g->scent.get( {x, y, 0} ) ); + y++; + } + x++; + } +} + diff --git a/tests/shadowcasting_test.cpp b/tests/shadowcasting_test.cpp index 9e3c03471344..af02f4af5040 100644 --- a/tests/shadowcasting_test.cpp +++ b/tests/shadowcasting_test.cpp @@ -222,6 +222,10 @@ static void shadowcasting_runoff( const int iterations, const bool test_bresenha float seen_squares_control[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{0}}; float seen_squares_experiment[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{0}}; float transparency_cache[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{0}}; + diagonal_blocks blocked_cache[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{{false, false}}}; + + diagonal_blocks fill = {false, false}; + std::uninitialized_fill_n( &blocked_cache[0][0], MAPSIZE * SEEX * MAPSIZE * SEEY, fill ); randomly_fill_transparency( transparency_cache ); @@ -250,7 +254,7 @@ static void shadowcasting_runoff( const int iterations, const bool test_bresenha for( int i = 0; i < iterations; i++ ) { // Then the current algorithm. castLightAll( - seen_squares_experiment, transparency_cache, offset ); + seen_squares_experiment, transparency_cache, blocked_cache, offset ); } const auto end2 = std::chrono::high_resolution_clock::now(); @@ -291,6 +295,10 @@ static void shadowcasting_float_quad( float lit_squares_float[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{0}}; four_quadrants lit_squares_quad[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{}}; float transparency_cache[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{0}}; + diagonal_blocks blocked_cache[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{{false, false}}}; + + diagonal_blocks fill = {false, false}; + std::uninitialized_fill_n( &blocked_cache[0][0], MAPSIZE * SEEX * MAPSIZE * SEEY, fill ); randomly_fill_transparency( transparency_cache, denominator ); @@ -302,7 +310,7 @@ static void shadowcasting_float_quad( for( int i = 0; i < iterations; i++ ) { castLightAll( - lit_squares_quad, transparency_cache, offset ); + lit_squares_quad, transparency_cache, blocked_cache, offset ); } const auto end1 = std::chrono::high_resolution_clock::now(); @@ -311,7 +319,7 @@ static void shadowcasting_float_quad( // Then the current algorithm. castLightAll( - lit_squares_float, transparency_cache, offset ); + lit_squares_float, transparency_cache, blocked_cache, offset ); } const auto end2 = std::chrono::high_resolution_clock::now(); @@ -344,6 +352,10 @@ static void shadowcasting_3d_2d( const int iterations ) float seen_squares_experiment[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{0}}; float transparency_cache[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{0}}; bool floor_cache[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{false}}; + diagonal_blocks blocked_cache[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{{false, false}}}; + + diagonal_blocks fill = {false, false}; + std::uninitialized_fill_n( &blocked_cache[0][0], MAPSIZE * SEEX * MAPSIZE * SEEY, fill ); randomly_fill_transparency( transparency_cache ); @@ -355,7 +367,7 @@ static void shadowcasting_3d_2d( const int iterations ) for( int i = 0; i < iterations; i++ ) { // First the control algorithm. castLightAll( - seen_squares_control, transparency_cache, offset.xy() ); + seen_squares_control, transparency_cache, blocked_cache, offset.xy() ); } const auto end1 = std::chrono::high_resolution_clock::now(); @@ -363,18 +375,21 @@ static void shadowcasting_3d_2d( const int iterations ) std::array transparency_caches; std::array seen_caches; std::array floor_caches; + std::array + blocked_caches; for( int z = -OVERMAP_DEPTH; z <= OVERMAP_HEIGHT; z++ ) { // TODO: Give some more proper values here transparency_caches[z + OVERMAP_DEPTH] = &transparency_cache; seen_caches[z + OVERMAP_DEPTH] = &seen_squares_experiment; floor_caches[z + OVERMAP_DEPTH] = &floor_cache; + blocked_caches[z + OVERMAP_DEPTH] = &blocked_cache; } const auto start2 = std::chrono::high_resolution_clock::now(); for( int i = 0; i < iterations; i++ ) { // Then the newer algorithm. cast_zlight( - seen_caches, transparency_caches, floor_caches, origin, 0, 1.0 ); + seen_caches, transparency_caches, floor_caches, blocked_caches, origin, 0, 1.0 ); } const auto end2 = std::chrono::high_resolution_clock::now(); @@ -447,6 +462,10 @@ static void run_spot_check( const grid_overlay &test_case, const grid_overlay &e { float seen_squares[ MAPSIZE * SEEY ][ MAPSIZE * SEEX ] = {{ 0 }}; float transparency_cache[ MAPSIZE * SEEY ][ MAPSIZE * SEEX ] = {{ 0 }}; + diagonal_blocks blocked_cache[MAPSIZE * SEEX][MAPSIZE * SEEY] = {{{false, false}}}; + + diagonal_blocks fill = {false, false}; + std::uninitialized_fill_n( &blocked_cache[0][0], MAPSIZE * SEEX * MAPSIZE * SEEY, fill ); for( int y = 0; y < static_cast( sizeof( transparency_cache ) / sizeof( transparency_cache[0] ) ); ++y ) { @@ -457,7 +476,7 @@ static void run_spot_check( const grid_overlay &test_case, const grid_overlay &e } castLightAll( - seen_squares, transparency_cache, ORIGIN ); + seen_squares, transparency_cache, blocked_cache, ORIGIN ); // Compares the whole grid, but out-of-bounds compares will de-facto pass. for( int y = 0; y < expected_result.height(); ++y ) { diff --git a/tests/vehicle_test.cpp b/tests/vehicle_test.cpp index d0db4a712c55..80f725b2aeb1 100644 --- a/tests/vehicle_test.cpp +++ b/tests/vehicle_test.cpp @@ -73,3 +73,67 @@ TEST_CASE( "add_item_to_broken_vehicle_part" ) const item itm2 = item( "jeans" ); REQUIRE( !veh_ptr->add_item( *cargo_part, itm2 ) ); } + +void test_coord_translate( units::angle dir, const point &pivot, const point &p, + tripoint &q ); + +void test_coord_translate( units::angle dir, const point &pivot, const point &p, + tripoint &q ) +{ + tileray tdir( dir ); + tdir.advance( p.x - pivot.x ); + q.x = tdir.dx() + tdir.ortho_dx( p.y - pivot.y ); + q.y = tdir.dy() + tdir.ortho_dy( p.y - pivot.y ); +} + +TEST_CASE( "check_vehicle_rotation_against_old" ) +{ + clear_map(); + const tripoint test_origin( 60, 60, 0 ); + const tripoint vehicle_origin = test_origin; + vehicle *veh_ptr = g->m.add_vehicle( vproto_id( "bicycle" ), vehicle_origin, 0_degrees, 0, 0 ); + const point pivot; + + for( int dir = 0; dir < 24; dir++ ) { + for( int x = -5; x <= 5; x++ ) { + for( int y = -5; y <= 5; y++ ) { + point p = {x, y}; + tripoint oldRes; + veh_ptr->coord_translate( 15_degrees * dir, pivot, p, oldRes ); + + tripoint newRes; + test_coord_translate( 15_degrees * dir, pivot, p, newRes ); + + CHECK( oldRes.x == newRes.x ); + CHECK( oldRes.y == newRes.y ); + + } + } + } +} + +TEST_CASE( "vehicle_rotation_reverse" ) +{ + clear_map(); + const tripoint test_origin( 60, 60, 0 ); + const tripoint vehicle_origin = test_origin; + vehicle *veh_ptr = g->m.add_vehicle( vproto_id( "bicycle" ), vehicle_origin, 0_degrees, 0, 0 ); + const point pivot; + + for( int dir = 0; dir < 24; dir++ ) { + for( int x = -5; x <= 5; x++ ) { + for( int y = -5; y <= 5; y++ ) { + point p = {x, y}; + tripoint result; + veh_ptr->coord_translate( 15_degrees * dir, pivot, p, result ); + + point reversed; + veh_ptr->coord_translate_reverse( 15_degrees * dir, pivot, result, reversed ); + + CHECK( reversed.x == p.x ); + CHECK( reversed.y == p.y ); + + } + } + } +} diff --git a/tests/vision_test.cpp b/tests/vision_test.cpp index 4fcd397ae323..212be3d652ad 100644 --- a/tests/vision_test.cpp +++ b/tests/vision_test.cpp @@ -24,6 +24,9 @@ #include "shadowcasting.h" #include "type_id.h" #include "weather.h" +#include "vehicle.h" +#include "vpart_position.h" +#include "vpart_range.h" enum class vision_test_flags { none = 0, @@ -45,7 +48,9 @@ static bool operator!( vision_test_flags f ) static void full_map_test( const std::vector &setup, const std::vector &expected_results, const time_point &time, - const vision_test_flags flags ) + const vision_test_flags flags, + const std::string vehicle_id, + const units::angle vehicle_rotation ) { const ter_id t_brick_wall( "t_brick_wall" ); const ter_id t_window_frame( "t_window_frame" ); @@ -120,6 +125,7 @@ static void full_map_test( const std::vector &setup, } map &here = get_map(); + vehicle *veh = nullptr; for( int y = 0; y < height; ++y ) { for( int x = 0; x < width; ++x ) { const tripoint p = origin + point( x, y ); @@ -149,6 +155,12 @@ static void full_map_test( const std::vector &setup, case 'V': // Already handled above break; + case 'C': + veh = here.add_vehicle( vproto_id( vehicle_id ), p, vehicle_rotation, 0, 0 ); + for( const vpart_reference &vp : veh->get_avail_parts( "OPENABLE" ) ) { + veh->close( vp.part_index() ); + } + break; default: FAIL( "unexpected setup char '" << setup[y][x] << "'" ); } @@ -257,6 +269,8 @@ struct vision_test_case { std::vector expected_results; time_point time; vision_test_flags flags; + std::string vehicle_id = ""; + units::angle vehicle_rotation = 0_degrees; static void transpose( std::vector &v ) { if( v.empty() ) { @@ -293,7 +307,7 @@ struct vision_test_case { } void test() const { - full_map_test( setup, expected_results, time, flags ); + full_map_test( setup, expected_results, time, flags, vehicle_id, vehicle_rotation ); } void test_all_transformations() const { @@ -345,6 +359,7 @@ static const time_point midday = calendar::turn_zero + 12_hours; // 'L' - light, indoors // '#' - wall // '=' - window frame +// 'C' - The origin of the vehicle TEST_CASE( "vision_daylight", "[shadowcasting][vision]" ) { @@ -622,3 +637,69 @@ TEST_CASE( "vision_player_opaque_neighbors_still_visible_night", "[shadowcasting t.test_all(); } + +TEST_CASE( "vision_see_out_of_vehicle", "[shadowcasting][vision]" ) +{ + + vision_test_case t { + { + " ", + " C ", + " ", + " ", + " U ", + " ", + " ", + " ", + }, + { + "66666666666666666", + "66666666666666666", + "66666666664111666", + "66666666641114666", + "66666666411146666", + "66666664111466666", + "66666661114666666", + "66666666666666666", + }, + midday, + vision_test_flags::none, + "cube_van", + -45_degrees + }; + + t.test(); +} + +TEST_CASE( "vision_see_into_vehicle", "[shadowcasting][vision]" ) +{ + + vision_test_case t { + { + " ", + " C ", + " ", + " ", + " ", + " U ", + " ", + " ", + }, + { + "66666666666666664", + "66666666666666644", + "66666666666666444", + "66666666666664444", + "66666666666644444", + "66666666666444444", + "66666666664444444", + "66666666644444444", + }, + midday, + vision_test_flags::none, + "cube_van", + -45_degrees + }; + + t.test(); +}