diff --git a/src/active_item_cache.cpp b/src/active_item_cache.cpp index f7aa017c1d742..e0075c663b020 100644 --- a/src/active_item_cache.cpp +++ b/src/active_item_cache.cpp @@ -8,7 +8,7 @@ #include "item_pocket.h" #include "safe_reference.h" -float item_reference::spoil_multiplier() +float item_reference::spoil_multiplier() const { return std::accumulate( pocket_chain.begin(), pocket_chain.end(), 1.0F, @@ -17,7 +17,7 @@ float item_reference::spoil_multiplier() } ); } -bool item_reference::has_watertight_container() +bool item_reference::has_watertight_container() const { return std::any_of( pocket_chain.begin(), pocket_chain.end(), diff --git a/src/active_item_cache.h b/src/active_item_cache.h index 41f097130adb9..47f516d649e77 100644 --- a/src/active_item_cache.h +++ b/src/active_item_cache.h @@ -21,8 +21,8 @@ struct item_reference { item *parent = nullptr; std::vector pocket_chain; - float spoil_multiplier(); - bool has_watertight_container(); + float spoil_multiplier() const; + bool has_watertight_container() const; }; enum class special_item_type : int { diff --git a/src/map.h b/src/map.h index 281bd7bd297e8..0ec63e33cf12a 100644 --- a/src/map.h +++ b/src/map.h @@ -101,6 +101,8 @@ template struct weighted_int_list; struct field_proc_data; +enum pf_special : int; + using relic_procgen_id = string_id; class map_stack : public item_stack @@ -737,6 +739,20 @@ class map // Get a straight route from f to t, only along non-rough terrain. Returns an empty vector // if that is not possible. std::vector straight_route( const tripoint &f, const tripoint &t ) const; + private: + // Pathfinding cost helper that computes the cost of moving into |p| from |cur|. + // Includes climbing, bashing and opening doors. + int cost_to_pass( const tripoint &cur, const tripoint &p, const pathfinding_settings &settings, + pf_special p_special ) const; + // Pathfinding cost helper that computes the cost of moving into |p| + // from |cur| based on perceived danger. + // Includes moving through traps. + int cost_to_avoid( const tripoint &cur, const tripoint &p, const pathfinding_settings &settings, + pf_special p_special ) const; + // Sum of cost_to_pass and cost_to_avoid. + int extra_cost( const tripoint &cur, const tripoint &p, const pathfinding_settings &settings, + pf_special p_special ) const; + public: // Vehicles: Common to 2D and 3D VehicleList get_vehicles(); diff --git a/src/pathfinding.cpp b/src/pathfinding.cpp index 18e1760f6e21b..dc1efcffc0382 100644 --- a/src/pathfinding.cpp +++ b/src/pathfinding.cpp @@ -189,6 +189,166 @@ std::vector map::straight_route( const tripoint &f, const tripoint &t return ret; } +static constexpr int PF_IMPASSABLE = -1; +static constexpr int PF_IMPASSABLE_FROM_HERE = -2; +int map::cost_to_pass( const tripoint &cur, const tripoint &p, const pathfinding_settings &settings, + pf_special p_special ) const +{ + constexpr pf_special non_normal = PF_SLOW | PF_WALL | PF_VEHICLE | PF_TRAP | PF_SHARP; + if( !( p_special & non_normal ) ) { + // Boring flat dirt - the most common case above the ground + return 2; + } + + if( settings.avoid_rough_terrain ) { + return PF_IMPASSABLE; + } + + if( settings.avoid_sharp && ( p_special & PF_SHARP ) ) { + return PF_IMPASSABLE; + } + + const int bash = settings.bash_strength; + const bool allow_open_doors = settings.allow_open_doors; + const bool allow_unlock_doors = settings.allow_unlock_doors; + const int climb_cost = settings.climb_cost; + + int part = -1; + const const_maptile &tile = maptile_at_internal( p ); + const ter_t &terrain = tile.get_ter_t(); + const furn_t &furniture = tile.get_furn_t(); + const field &field = tile.get_field(); + const vehicle *veh = veh_at_internal( p, part ); + + const int cost = move_cost_internal( furniture, terrain, field, veh, part ); + + // If we can just walk into the tile, great. That's the cost. + if( cost != 0 ) { + return cost; + } + + // Otherwise we'll consider climbing, opening doors, and bashing, in that order. + // Should match logic in monster::move_to and npc::move_to. + + // If there's a vehicle here, we need to assess that instead of the terrain. + if( veh != nullptr ) { + const auto vpobst = vpart_position( const_cast( *veh ), part ).obstacle_at_part(); + part = vpobst ? vpobst->part_index() : -1; + int dummy = -1; + const bool is_outside_veh = veh_at_internal( cur, dummy ) != veh; + + if( part >= 0 ) { + if( allow_open_doors && veh->next_part_to_open( part, is_outside_veh ) != -1 ) { + // Handle car doors, but don't try to path through curtains + return 10; // One turn to open, 4 to move there + } else if( allow_unlock_doors && veh->next_part_to_unlock( part, is_outside_veh ) != -1 ) { + return 12; // 2 turns to open, 4 to move there + } else if( bash > 0 ) { + // Car obstacle that isn't a door + // TODO: Account for armor + int hp = veh->part( part ).hp(); + if( hp / 20 > bash ) { + // Threshold damage thing means we just can't bash this down + return PF_IMPASSABLE; + } else if( hp / 10 > bash ) { + // Threshold damage thing means we will fail to deal damage pretty often + hp *= 2; + } + + return 2 * hp / bash + 8 + 4; + } else { + const vehicle_part &vp = veh->part( part ); + if( allow_open_doors && vp.info().has_flag( VPFLAG_OPENABLE ) ) { + // If we can open doors in general but weren't + // able to open this one, we might be able to + // open it if we try from another direction. + return PF_IMPASSABLE_FROM_HERE; + } else { + // Won't be openable, don't try from other sides + return PF_IMPASSABLE; + } + } + } + } + + // If we can climb it, great! + if( climb_cost > 0 && p_special & PF_CLIMBABLE ) { + return climb_cost; + } + + // If it's a door and we can open it from the tile we're on, cool. + if( allow_open_doors && ( terrain.open || furniture.open ) && + ( ( !terrain.has_flag( ter_furn_flag::TFLAG_OPENCLOSE_INSIDE ) && + !furniture.has_flag( ter_furn_flag::TFLAG_OPENCLOSE_INSIDE ) ) || + !is_outside( cur ) ) ) { + // Only try to open INSIDE doors from the inside + // To open and then move onto the tile + return 4; + } + + // Otherwise, if we can bash, we'll consider that. + if( bash > 0 ) { + const int rating = bash_rating_internal( bash, furniture, terrain, false, veh, part ); + + if( rating > 1 ) { + // Expected number of turns to bash it down, 1 turn to move there + // and 5 turns of penalty not to trash everything just because we can + return ( 20 / rating ) + 2 + 10; + } + + if( rating == 1 ) { + // Desperate measures, avoid whenever possible + return 500; + } + } + + // If we can open doors generally but couldn't open this one, maybe we can + // try from another direction. + if( allow_open_doors && terrain.open && furniture.open ) { + return PF_IMPASSABLE_FROM_HERE; + } + + return PF_IMPASSABLE; +} + +int map::cost_to_avoid( const tripoint & /*cur*/, const tripoint &p, + const pathfinding_settings &settings, pf_special p_special ) const +{ + if( settings.avoid_traps && ( p_special & PF_TRAP ) ) { + const const_maptile &tile = maptile_at_internal( p ); + const ter_t &terrain = tile.get_ter_t(); + const trap &ter_trp = terrain.trap.obj(); + const trap &trp = ter_trp.is_benign() ? tile.get_trap_t() : ter_trp; + + // NO_FLOOR is a special case handled in map::route + if( !trp.is_benign() && !terrain.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) ) { + return 500; + } + } + + if( settings.avoid_dangerous_fields && ( p_special & PF_FIELD ) ) { + // We'll walk through even known-dangerous fields if we absolutely have to. + return 500; + } + + return 0; +} + +int map::extra_cost( const tripoint &cur, const tripoint &p, const pathfinding_settings &settings, + pf_special p_special ) const +{ + int pass_cost = cost_to_pass( cur, p, settings, p_special ); + if( pass_cost < 0 ) { + return pass_cost; + } + + int avoid_cost = cost_to_avoid( cur, p, settings, p_special ); + if( avoid_cost < 0 ) { + return avoid_cost; + } + return pass_cost + avoid_cost; +} + std::vector map::route( const tripoint &f, const tripoint &t, const pathfinding_settings &settings, const std::unordered_set &pre_closed ) const @@ -226,14 +386,6 @@ std::vector map::route( const tripoint &f, const tripoint &t, } const int max_length = settings.max_length; - const int bash = settings.bash_strength; - const int climb_cost = settings.climb_cost; - const bool doors = settings.allow_open_doors; - const bool locks = settings.allow_unlock_doors; - const bool trapavoid = settings.avoid_traps; - const bool roughavoid = settings.avoid_rough_terrain; - const bool sharpavoid = settings.avoid_sharp; - const bool fieldavoid = settings.avoid_dangerous_fields; const int pad = 16; // Should be much bigger - low value makes pathfinders dumb! tripoint min( std::min( f.x, t.x ) - pad, std::min( f.y, t.y ) - pad, std::min( f.z, t.z ) ); @@ -257,7 +409,6 @@ std::vector map::route( const tripoint &f, const tripoint &t, bool done = false; - constexpr pf_special non_normal = PF_SLOW | PF_WALL | PF_VEHICLE | PF_TRAP | PF_SHARP; do { tripoint cur = pf.get_next(); @@ -304,136 +455,41 @@ std::vector map::route( const tripoint &f, const tripoint &t, int newg = layer.gscore[parent_index] + ( ( cur.x != p.x && cur.y != p.y ) ? 1 : 0 ); const pf_special p_special = pf_cache.special[p.x][p.y]; - // TODO: De-uglify, de-huge-n - if( !( p_special & non_normal ) ) { - // Boring flat dirt - the most common case above the ground - newg += 2; - } else { - if( roughavoid ) { - layer.closed[index] = true; // Close all rough terrain tiles - continue; + const int cost = extra_cost( cur, p, settings, p_special ); + if( cost < 0 ) { + if( cost == PF_IMPASSABLE ) { + layer.closed[index] = true; } + continue; + } + newg += cost; - int part = -1; + // Special case: pathfinders that avoid traps can avoid ledges by + // climbing down. This can't be covered by |extra_cost| because it + // can add a new point to the search. + if( settings.avoid_traps && ( p_special & PF_TRAP ) ) { const const_maptile &tile = maptile_at_internal( p ); const ter_t &terrain = tile.get_ter_t(); - const furn_t &furniture = tile.get_furn_t(); - const field &field = tile.get_field(); - const vehicle *veh = veh_at_internal( p, part ); - - const int cost = move_cost_internal( furniture, terrain, field, veh, part ); - // Don't calculate bash rating unless we intend to actually use it - const int rating = ( bash == 0 || cost != 0 ) ? -1 : - bash_rating_internal( bash, furniture, terrain, false, veh, part ); - - if( cost == 0 && rating <= 0 && ( !doors || !terrain.open || !furniture.open ) && veh == nullptr && - climb_cost <= 0 ) { - layer.closed[index] = true; // Close it so that next time we won't try to calculate costs - continue; - } - - newg += cost; - if( cost == 0 ) { - if( climb_cost > 0 && ( p_special & PF_CLIMBABLE ) ) { - // Climbing fences - newg += climb_cost; - } else if( doors && ( terrain.open || furniture.open ) && - ( ( !terrain.has_flag( ter_furn_flag::TFLAG_OPENCLOSE_INSIDE ) && - !furniture.has_flag( ter_furn_flag::TFLAG_OPENCLOSE_INSIDE ) ) || - !is_outside( cur ) ) ) { - // Only try to open INSIDE doors from the inside - // To open and then move onto the tile - newg += 4; - } else if( veh != nullptr ) { - const auto vpobst = vpart_position( const_cast( *veh ), part ).obstacle_at_part(); - part = vpobst ? vpobst->part_index() : -1; - int dummy = -1; - const bool is_outside_veh = veh_at_internal( cur, dummy ) != veh; - - if( doors && part != -1 && veh->next_part_to_open( part, is_outside_veh ) != -1 ) { - // Handle car doors, but don't try to path through curtains - newg += 10; // One turn to open, 4 to move there - } else if( locks && veh->next_part_to_unlock( part, is_outside_veh ) != -1 ) { - newg += 12; // 2 turns to open, 4 to move there - } else if( part >= 0 && bash > 0 ) { - // Car obstacle that isn't a door - // TODO: Account for armor - int hp = veh->part( part ).hp(); - if( hp / 20 > bash ) { - // Threshold damage thing means we just can't bash this down - layer.closed[index] = true; - continue; - } else if( hp / 10 > bash ) { - // Threshold damage thing means we will fail to deal damage pretty often - hp *= 2; - } - - newg += 2 * hp / bash + 8 + 4; - } else if( part >= 0 ) { - const vehicle_part &vp = veh->part( part ); - if( !doors || !vp.info().has_flag( VPFLAG_OPENABLE ) ) { - // Won't be openable, don't try from other sides - layer.closed[index] = true; - } - - continue; - } - } else if( rating > 1 ) { - // Expected number of turns to bash it down, 1 turn to move there - // and 5 turns of penalty not to trash everything just because we can - newg += ( 20 / rating ) + 2 + 10; - } else if( rating == 1 ) { - // Desperate measures, avoid whenever possible - newg += 500; - } else { - // Unbashable and unopenable from here - if( !doors || !terrain.open || !furniture.open ) { - // Or anywhere else for that matter - layer.closed[index] = true; + const trap &ter_trp = terrain.trap.obj(); + const trap &trp = ter_trp.is_benign() ? tile.get_trap_t() : ter_trp; + if( !trp.is_benign() && terrain.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) ) { + // Warning: really expensive, needs a cache + tripoint below( p.xy(), p.z - 1 ); + if( valid_move( p, below, false, true ) ) { + if( !has_flag( ter_furn_flag::TFLAG_NO_FLOOR, below ) ) { + // Otherwise this would have been a huge fall + path_data_layer &layer = pf.get_layer( p.z - 1 ); + // From cur, not p, because we won't be walking on air + pf.add_point( layer.gscore[parent_index] + 10, + layer.score[parent_index] + 10 + 2 * rl_dist( below, t ), + cur, below ); } + // Close p, because we won't be walking on it + layer.closed[index] = true; continue; } } - - if( trapavoid && ( p_special & PF_TRAP ) ) { - const trap &ter_trp = terrain.trap.obj(); - const trap &trp = ter_trp.is_benign() ? tile.get_trap_t() : ter_trp; - if( !trp.is_benign() ) { - // For now make them detect all traps - if( terrain.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) ) { - // Special case - ledge in z-levels - // Warning: really expensive, needs a cache - if( valid_move( p, tripoint( p.xy(), p.z - 1 ), false, true ) ) { - tripoint below( p.xy(), p.z - 1 ); - if( !has_flag( ter_furn_flag::TFLAG_NO_FLOOR, below ) ) { - // Otherwise this would have been a huge fall - path_data_layer &layer = pf.get_layer( p.z - 1 ); - // From cur, not p, because we won't be walking on air - pf.add_point( layer.gscore[parent_index] + 10, - layer.score[parent_index] + 10 + 2 * rl_dist( below, t ), - cur, below ); - } - - // Close p, because we won't be walking on it - layer.closed[index] = true; - continue; - } - } else { - // Otherwise it's walkable - newg += 500; - } - } - } - - if( sharpavoid && ( p_special & PF_SHARP ) ) { - layer.closed[index] = true; // Avoid sharp things - } - - if( fieldavoid && ( p_special & PF_FIELD ) ) { - // We'll walk through even known-dangerous fields if we absolutely have to. - newg += 500; - } } pf.add_point( newg, newg + 2 * rl_dist( p, t ), cur, p ); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index e357958eb0751..4ecefec42b246 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2995,6 +2995,8 @@ int vehicle::next_part_to_lock( int p, bool outside ) const int vehicle::next_part_to_unlock( int p, bool outside ) const { + cata_assert( p >= 0 ); + cata_assert( p < part_count() ); if( !part_has_lock( p ) ) { return -1; }