From 0a9b8e87f500e32e88a47be3417e093242ec6ce0 Mon Sep 17 00:00:00 2001 From: CLIDragon <84266961+CLIDragon@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:52:56 +1000 Subject: [PATCH] Replace the old pathfinding_settings with two new classes PathfindingSettings is the direct replacement for the old struct. It stores how the creature will react to a given terrain, and what actions they can take. RealityBubblePathfindingSettings contains settings that govern z-level movement and parameters related to pathfinding, such as the maximum path cost. Currently the extra features of these two classes are completely unused, as they are intended for integration with the new pathfinder. Co-authored-by: prharvey <2677507+prharvey@users.noreply.github.com> --- src/avatar.cpp | 2 +- src/character.cpp | 11 +- src/character.h | 6 +- src/creature.h | 4 +- src/game.cpp | 7 +- src/map.cpp | 11 +- src/map.h | 12 +- src/monattack.cpp | 11 +- src/monmove.cpp | 10 +- src/monster.cpp | 8 +- src/monster.h | 4 +- src/monstergenerator.cpp | 45 ++++-- src/mtype.h | 2 +- src/mutation.cpp | 2 +- src/npc.cpp | 24 ++- src/npc.h | 6 +- src/pathfinding.cpp | 103 +++++++++--- src/pathfinding.h | 339 ++++++++++++++++++++++++++++++++++++--- 18 files changed, 499 insertions(+), 108 deletions(-) diff --git a/src/avatar.cpp b/src/avatar.cpp index 317ce4c1911ce..0abae95599fe9 100644 --- a/src/avatar.cpp +++ b/src/avatar.cpp @@ -712,7 +712,7 @@ void avatar::grab( object_type grab_type_new, const tripoint_rel_ms &grab_point_ // eliminate ghost vehicles/furnitures/etc. update_memory( grab_type, grab_point, /* erase = */ true ); - path_settings->avoid_rough_terrain = grab_type != object_type::NONE; + path_settings->set_avoid_rough_terrain( grab_type != object_type::NONE ); } object_type avatar::get_grab_type() const diff --git a/src/character.cpp b/src/character.cpp index 79859d300a56b..fffbb8a3f2ea0 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -591,7 +591,14 @@ Character::Character() : name.clear(); custom_profession.clear(); - *path_settings = pathfinding_settings{ 0, 1000, 1000, 0, true, true, true, true, false, true, creature_size::medium }; + PathfindingSettings pf_settings; + pf_settings.set_avoid_bashing(true); + pf_settings.set_max_distance(1000); + pf_settings.set_max_cost(1000 * 50); + pf_settings.set_avoid_dangerous_traps(true); + pf_settings.set_avoid_sharp(true); + pf_settings.set_size_restriction(creature_size::medium); + *path_settings = pf_settings; move_mode = move_mode_walk; next_expected_position = std::nullopt; @@ -10010,7 +10017,7 @@ std::function Character::get_path_avoid() const }; } -const pathfinding_settings &Character::get_pathfinding_settings() const +const PathfindingSettings &Character::get_pathfinding_settings() const { return *path_settings; } diff --git a/src/character.h b/src/character.h index 5772d56393dc9..4e4b32483d3d9 100644 --- a/src/character.h +++ b/src/character.h @@ -103,7 +103,7 @@ struct itype; struct mutation_branch; struct mutation_category_trait; struct mutation_variant; -struct pathfinding_settings; +class PathfindingSettings; struct projectile; struct requirement_data; struct tool_comp; @@ -3278,7 +3278,7 @@ class Character : public Creature, public visitable /** Returns the player's modified base movement cost */ int run_cost( int base_cost, bool diag = false ) const; - const pathfinding_settings &get_pathfinding_settings() const override; + const PathfindingSettings &get_pathfinding_settings() const override; std::function get_path_avoid() const override; /** * Get all hostile creatures currently visible to this player. @@ -4019,7 +4019,7 @@ class Character : public Creature, public visitable * Cache for pathfinding settings. * Most of it isn't changed too often, hence mutable. */ - mutable pimpl path_settings; + mutable pimpl path_settings; // faction API versions // 2 - allies are in your_followers faction; NPCATT_FOLLOW is follower but not an ally diff --git a/src/creature.h b/src/creature.h index 3f75db8814263..39a8e8a8fbb36 100644 --- a/src/creature.h +++ b/src/creature.h @@ -57,7 +57,7 @@ class window; } // namespace catacurses struct dealt_projectile_attack; struct field_immunity_data; -struct pathfinding_settings; +class PathfindingSettings; struct projectile; struct projectile_attack_results; struct trap; @@ -944,7 +944,7 @@ class Creature : public viewer virtual units::mass weight_capacity() const; /** Returns settings for pathfinding. */ - virtual const pathfinding_settings &get_pathfinding_settings() const = 0; + virtual const PathfindingSettings &get_pathfinding_settings() const = 0; /** Returns a set of points we do not want to path through. */ virtual std::function get_path_avoid() const = 0; diff --git a/src/game.cpp b/src/game.cpp index cc9316af9c103..59138d349cbaa 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -4327,8 +4327,11 @@ Creature *game::is_hostile_within( int distance, bool dangerous ) if( critter->is_ranged_attacker() ) { return critter; } - - const pathfinding_settings pf_settings = pathfinding_settings{ 8, distance, distance * 2, 4, true, true, false, true, false, false }; + PathfindingSettings pf_settings; + pf_settings.set_bash_strength( 8 ); + pf_settings.set_max_distance( distance ); + pf_settings.set_max_cost( distance * 2 * 50 ); + pf_settings.set_climb_cost( 4 ); if( !get_map().route( u.pos_bub(), critter->pos_bub(), pf_settings ).empty() ) { return critter; diff --git a/src/map.cpp b/src/map.cpp index 886067f0bc50e..c87e0a1194a3e 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -5486,8 +5486,15 @@ std::pair map::_add_item_or_charges( const tripoint_bub std::vector tiles = closest_points_first( pos, max_dist ); tiles.erase( tiles.begin() ); // we already tried this position const int max_path_length = 4 * max_dist; - const pathfinding_settings setting( 0, max_dist, max_path_length, 0, false, false, true, false, - false, false ); + PathfindingSettings setting; + setting.set_avoid_bashing( true ); + setting.set_max_distance( max_dist ); + setting.set_max_cost( max_path_length * 50 ); + setting.set_avoid_opening_doors( true ); + setting.set_avoid_unlocking_doors( true ); + setting.set_avoid_dangerous_traps( true ); + setting.set_avoid_climb_stairway( true ); + for( const tripoint_bub_ms &e : tiles ) { if( copies_remaining <= 0 ) { break; diff --git a/src/map.h b/src/map.h index 6d19b412f2f03..03f04df57ff36 100644 --- a/src/map.h +++ b/src/map.h @@ -97,7 +97,7 @@ class map; enum class ter_furn_flag : int; struct pathfinding_cache; -struct pathfinding_settings; +class PathfindingSettings; template struct weighted_int_list; struct field_proc_data; @@ -769,12 +769,12 @@ class map */ // TODO: fix point types (remove the first overload) std::vector route( const tripoint &f, const tripoint &t, - const pathfinding_settings &settings, + const PathfindingSettings &settings, const std::function &avoid = []( const tripoint & ) { return false; } ) const; std::vector route( const tripoint_bub_ms &f, const tripoint_bub_ms &t, - const pathfinding_settings &settings, + const PathfindingSettings &settings, const std::function &avoid = []( const tripoint & ) { return false; } ) const; @@ -789,17 +789,17 @@ class map // 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_bub_ms &cur, const tripoint_bub_ms &p, - const pathfinding_settings &settings, + const PathfindingSettings &settings, PathfindingFlags 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_bub_ms &cur, const tripoint_bub_ms &p, - const pathfinding_settings &settings, + const PathfindingSettings &settings, PathfindingFlags p_special ) const; // Sum of cost_to_pass and cost_to_avoid. int extra_cost( const tripoint_bub_ms &cur, const tripoint_bub_ms &p, - const pathfinding_settings &settings, + const PathfindingSettings &settings, PathfindingFlags p_special ) const; public: diff --git a/src/monattack.cpp b/src/monattack.cpp index cfdabc0fe76b9..a0ba036604347 100644 --- a/src/monattack.cpp +++ b/src/monattack.cpp @@ -1575,8 +1575,15 @@ bool mattack::triffid_heartbeat( monster *z ) map &here = get_map(); creature_tracker &creatures = get_creature_tracker(); - static pathfinding_settings root_pathfind( 10, 20, 50, 0, false, false, false, false, false, - false ); + // TODO: Move this to a separate initialiser. + static PathfindingSettings root_pathfind; + root_pathfind.set_bash_strength( 10 ); + root_pathfind.set_max_distance( 20 ); + root_pathfind.set_max_cost( 50 * 50 ); + root_pathfind.set_avoid_opening_doors( true ); + root_pathfind.set_avoid_unlocking_doors( true ); + root_pathfind.set_avoid_climb_stairway( true ); + if( rl_dist( z->pos_bub(), player_character.pos_bub() ) > 5 && !here.route( player_character.pos_bub(), z->pos_bub(), root_pathfind ).empty() ) { add_msg( m_warning, _( "The root walls creak around you." ) ); diff --git a/src/monmove.cpp b/src/monmove.cpp index 7766c4ed31600..8a6806b714f13 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -188,10 +188,10 @@ bool monster::know_danger_at( const tripoint &p ) const bool avoid_fire = avoid_simple || has_flag( mon_flag_PATH_AVOID_FIRE ); bool avoid_fall = avoid_simple || has_flag( mon_flag_PATH_AVOID_FALL ); - bool avoid_sharp = avoid_simple || get_pathfinding_settings().avoid_sharp; + bool avoid_sharp = avoid_simple || get_pathfinding_settings().avoid_sharp(); - bool avoid_dangerous_fields = get_pathfinding_settings().avoid_dangerous_fields; - bool avoid_traps = get_pathfinding_settings().avoid_traps; + bool avoid_dangerous_fields = get_pathfinding_settings().avoid_dangerous_fields(); + bool avoid_traps = get_pathfinding_settings().avoid_dangerous_traps(); // technically this will shortcut in evaluation from fire or fall // before hitting simple or complex but this is more explicit @@ -1017,8 +1017,8 @@ void monster::move() path.erase( path.begin() ); } - const pathfinding_settings &pf_settings = get_pathfinding_settings(); - if( pf_settings.max_dist >= rl_dist( get_location(), get_dest() ) && + const PathfindingSettings &pf_settings = get_pathfinding_settings(); + if( pf_settings.max_distance() >= rl_dist( get_location(), get_dest() ) && ( path.empty() || rl_dist( pos(), path.front() ) >= 2 || path.back() != local_dest.raw() ) ) { // We need a new path if( can_pathfind() ) { diff --git a/src/monster.cpp b/src/monster.cpp index a1ee71155459c..34de665907531 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -198,7 +198,7 @@ static const trait_id trait_PHEROMONE_MAMMAL( "PHEROMONE_MAMMAL" ); static const trait_id trait_TERRIFYING( "TERRIFYING" ); static const trait_id trait_THRESH_MYCUS( "THRESH_MYCUS" ); -struct pathfinding_settings; +class PathfindingSettings; // Limit the number of iterations for next upgrade_time calculations. // This also sets the percentage of monsters that will never upgrade. @@ -1483,8 +1483,8 @@ bool monster::has_intelligence() const has_flag( mon_flag_PATH_AVOID_FIRE ) || has_flag( mon_flag_PATH_AVOID_DANGER ) || has_flag( mon_flag_PRIORITIZE_TARGETS ) || - get_pathfinding_settings().avoid_sharp || - get_pathfinding_settings().avoid_traps; + type->path_settings.avoid_sharp() || + type->path_settings.avoid_dangerous_traps(); } std::vector monster::get_absorb_material() const @@ -4052,7 +4052,7 @@ void monster::on_load() name(), to_turns( dt ), healed, healed_speed ); } -const pathfinding_settings &monster::get_pathfinding_settings() const +const PathfindingSettings &monster::get_pathfinding_settings() const { return type->path_settings; } diff --git a/src/monster.h b/src/monster.h index f9be4177a9772..8d7ae200a772a 100644 --- a/src/monster.h +++ b/src/monster.h @@ -39,7 +39,7 @@ namespace catacurses class window; } // namespace catacurses struct dealt_projectile_attack; -struct pathfinding_settings; +class PathfindingSettings; struct trap; enum class mon_trigger : int; @@ -618,7 +618,7 @@ class monster : public Creature */ void on_load(); - const pathfinding_settings &get_pathfinding_settings() const override; + const PathfindingSettings &get_pathfinding_settings() const override; std::function get_path_avoid() const override; double calculate_by_enchantment( double modify, enchant_vals::mod value, bool round_output = false ) const; diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index 55844d292eedd..349d2d17dd253 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -560,16 +560,16 @@ void MonsterGenerator::apply_species_attributes( mtype &mon ) void MonsterGenerator::finalize_pathfinding_settings( mtype &mon ) { - if( mon.path_settings.max_length < 0 ) { - mon.path_settings.max_length = mon.path_settings.max_dist * 5; + if( mon.path_settings.max_cost() / 50 < 0 ) { + mon.path_settings.set_max_cost( mon.path_settings.max_distance() * 5 * 50 ) ; } - if( mon.path_settings.bash_strength < 0 ) { - mon.path_settings.bash_strength = mon.bash_skill; + if( mon.path_settings.bash_strength() < 0 ) { + mon.path_settings.set_bash_strength( mon.bash_skill ); } if( mon.has_flag( mon_flag_CLIMBS ) ) { - mon.path_settings.climb_cost = 3; + mon.path_settings.set_climb_cost( 3 ); } } @@ -1300,14 +1300,33 @@ void mtype::load( const JsonObject &jo, const std::string &src ) if( jo.has_member( "path_settings" ) ) { JsonObject jop = jo.get_object( "path_settings" ); // Here rather than in pathfinding.cpp because we want monster-specific defaults and was_loaded - optional( jop, was_loaded, "max_dist", path_settings.max_dist, 0 ); - optional( jop, was_loaded, "max_length", path_settings.max_length, -1 ); - optional( jop, was_loaded, "bash_strength", path_settings.bash_strength, -1 ); - optional( jop, was_loaded, "allow_open_doors", path_settings.allow_open_doors, false ); - optional( jop, was_loaded, "avoid_traps", path_settings.avoid_traps, false ); - optional( jop, was_loaded, "allow_climb_stairs", path_settings.allow_climb_stairs, true ); - optional( jop, was_loaded, "avoid_sharp", path_settings.avoid_sharp, false ); - optional( jop, was_loaded, "avoid_dangerous_fields", path_settings.avoid_dangerous_fields, false ); + int max_dist; + int bash_strength; + int max_length; + bool allow_open_doors; + bool avoid_traps; + bool allow_climb_stairs; + bool avoid_sharp; + bool avoid_dangerous_fields; + + optional( jop, was_loaded, "max_dist", max_dist, 0 ); + optional( jop, was_loaded, "max_length", max_length, -1 ); + optional( jop, was_loaded, "bash_strength", bash_strength, -1 ); + optional( jop, was_loaded, "allow_open_doors", allow_open_doors, false ); + optional( jop, was_loaded, "avoid_traps", avoid_traps, false ); + optional( jop, was_loaded, "allow_climb_stairs", allow_climb_stairs, true ); + optional( jop, was_loaded, "avoid_sharp", avoid_sharp, false ); + optional( jop, was_loaded, "avoid_dangerous_fields", avoid_dangerous_fields, false ); + + path_settings = PathfindingSettings(); + path_settings.set_max_distance( max_dist ); + path_settings.set_bash_strength( bash_strength ); + // FIXME: cost -> length conversion is a stopgap before the new pathfinding system is added. + path_settings.set_max_cost( max_length * 50 ); + path_settings.set_avoid_opening_doors( !allow_open_doors ); + path_settings.set_avoid_dangerous_traps( !avoid_traps ); + path_settings.set_avoid_climb_stairway( !allow_climb_stairs ); + path_settings.set_avoid_dangerous_fields( avoid_dangerous_fields ); } } diff --git a/src/mtype.h b/src/mtype.h index 7de7637598d69..9461387b63198 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -493,7 +493,7 @@ struct mtype { // Monster biosignature variables std::optional biosig_timer; - pathfinding_settings path_settings; + PathfindingSettings path_settings; // All the bools together for space efficiency // diff --git a/src/mutation.cpp b/src/mutation.cpp index 25a3fe500c414..0619b4cff8bee 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -556,7 +556,7 @@ void Character::recalculate_size() size_class = creature_size::medium; } } - path_settings->size = size_class; + path_settings->set_size_restriction( size_class ); } void Character::mutation_effect( const trait_id &mut, const bool worn_destroyed_override ) diff --git a/src/npc.cpp b/src/npc.cpp index a8c96021e3b12..c16646d983260 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -261,8 +261,16 @@ npc::npc() patience = 0; attitude = NPCATT_NULL; - *path_settings = pathfinding_settings( 0, 1000, 1000, 10, true, true, true, true, false, true, - get_size() ); + PathfindingSettings pf_settings; + pf_settings.set_avoid_bashing( true ); + pf_settings.set_max_distance( 1000 ); + pf_settings.set_max_cost( 1000 * 50 ); + pf_settings.set_climb_cost( 10 ); + pf_settings.set_avoid_dangerous_traps( true ); + pf_settings.set_avoid_sharp( true ); + pf_settings.set_size_restriction( get_size() ); + *path_settings = pf_settings; + for( direction threat_dir : npc_threat_dir ) { ai_cache.threat_map[ threat_dir ] = 0.0f; } @@ -3453,26 +3461,26 @@ bool npc::will_accept_from_player( const item &it ) const return true; } -const pathfinding_settings &npc::get_pathfinding_settings() const +const PathfindingSettings &npc::get_pathfinding_settings() const { return get_pathfinding_settings( false ); } -const pathfinding_settings &npc::get_pathfinding_settings( bool no_bashing ) const +const PathfindingSettings &npc::get_pathfinding_settings( bool no_bashing ) const { - path_settings->bash_strength = no_bashing ? 0 : smash_ability(); + path_settings->set_bash_strength( no_bashing ? 0 : smash_ability() ); if( has_trait( trait_NO_BASH ) ) { - path_settings->bash_strength = 0; + path_settings->set_bash_strength( 0 ); } // TODO: Extract climb skill const int climb = std::min( 20, get_dex() ); if( climb > 1 ) { // Success is !one_in(dex), so 0%, 50%, 66%, 75%... // Penalty for failure chance is 1/success = 1/(1-failure) = 1/(1-(1/dex)) = dex/(dex-1) - path_settings->climb_cost = ( 10 - climb / 5.0f ) * climb / ( climb - 1 ); + path_settings->set_climb_cost( ( 10 - climb / 5.0f ) * climb / ( climb - 1 ) ); } else { // Climbing at this dexterity will always fail - path_settings->climb_cost = 0; + path_settings->set_climb_cost( 0 ); } return *path_settings; diff --git a/src/npc.h b/src/npc.h index 8ffbc0e44bf1d..69625bd265f6f 100644 --- a/src/npc.h +++ b/src/npc.h @@ -72,7 +72,7 @@ class window; struct bionic_data; struct mission_type; struct overmap_location; -struct pathfinding_settings; +class PathfindingSettings; enum game_message_type : int; class gun_mode; @@ -1197,8 +1197,8 @@ class npc : public Character void set_movement_mode( const move_mode_id &mode ) override; - const pathfinding_settings &get_pathfinding_settings() const override; - const pathfinding_settings &get_pathfinding_settings( bool no_bashing ) const; + const PathfindingSettings &get_pathfinding_settings() const override; + const PathfindingSettings &get_pathfinding_settings( bool no_bashing ) const; std::function get_path_avoid() const override; // Item discovery and fetching diff --git a/src/pathfinding.cpp b/src/pathfinding.cpp index 2230af349baf7..04ec6c5301f66 100644 --- a/src/pathfinding.cpp +++ b/src/pathfinding.cpp @@ -164,6 +164,50 @@ static bool is_disjoint( const Set1 &set1, const Set2 &set2 ) return true; } +void PathfindingSettings::set_size_restriction( std::optional size_restriction ) +{ + size_restriction_mask_.clear(); + if( size_restriction ) { + switch( *size_restriction ) { + case creature_size::tiny: + size_restriction_mask_ = PathfindingFlag::RestrictTiny; + break; + case creature_size::small: + size_restriction_mask_ = PathfindingFlag::RestrictSmall; + break; + case creature_size::medium: + size_restriction_mask_ = PathfindingFlag::RestrictMedium; + break; + case creature_size::large: + size_restriction_mask_ = PathfindingFlag::RestrictLarge; + break; + case creature_size::huge: + size_restriction_mask_ = PathfindingFlag::RestrictHuge; + break; + default: + break; + } + } + size_restriction_ = size_restriction; +} + +int PathfindingSettings::bash_rating_from_range( int min, int max ) const +{ + if( avoid_bashing_ ) { + return 0; + } + // TODO: Move all the bash stuff to map so this logic isn't duplicated. + ///\EFFECT_STR increases smashing damage + if( bash_strength_ < min ) { + return 0; + } else if( bash_strength_ >= max ) { + return 10; + } + const double ret = ( 10.0 * ( bash_strength_ - min ) ) / ( max - min ); + // Round up to 1, so that desperate NPCs can try to bash down walls + return std::max( ret, 1.0 ); +} + std::vector map::straight_route( const tripoint &f, const tripoint &t ) const { const std::vector temp = map::straight_route( tripoint_bub_ms( f ), @@ -209,7 +253,7 @@ std::vector map::straight_route( const tripoint_bub_ms &f, static constexpr int PF_IMPASSABLE = -1; static constexpr int PF_IMPASSABLE_FROM_HERE = -2; int map::cost_to_pass( const tripoint_bub_ms &cur, const tripoint_bub_ms &p, - const pathfinding_settings &settings, + const PathfindingSettings &settings, PathfindingFlags p_special ) const { constexpr PathfindingFlags non_normal = PathfindingFlag::Slow | @@ -220,27 +264,32 @@ int map::cost_to_pass( const tripoint_bub_ms &cur, const tripoint_bub_ms &p, return 2; } - if( settings.avoid_rough_terrain ) { + if( settings.avoid_rough_terrain() ) { return PF_IMPASSABLE; } - if( settings.avoid_sharp && ( p_special & PathfindingFlag::Sharp ) ) { + if( settings.avoid_sharp() && ( p_special & PathfindingFlag::Sharp ) ) { return PF_IMPASSABLE; } // RestrictTiny isn't checked since it's unclear how it would actually work as there's no category smaller than tiny - if( settings.size && ( - ( p_special & PathfindingFlag::RestrictSmall && settings.size > creature_size::tiny ) || - ( p_special & PathfindingFlag::RestrictMedium && settings.size > creature_size::small ) || - ( p_special & PathfindingFlag::RestrictLarge && settings.size > creature_size::medium ) || - ( p_special & PathfindingFlag::RestrictHuge && settings.size > creature_size::large ) ) ) { + // FIXME: Make this more ergonomic. + if( ( p_special & PathfindingSettings::AnySizeRestriction ) && settings.size_restriction() && ( + ( p_special & PathfindingFlag::RestrictSmall && + settings.size_restriction() > creature_size::tiny ) || + ( p_special & PathfindingFlag::RestrictMedium && + settings.size_restriction() > creature_size::small ) || + ( p_special & PathfindingFlag::RestrictLarge && + settings.size_restriction() > creature_size::medium ) || + ( p_special & PathfindingFlag::RestrictHuge && + settings.size_restriction() > creature_size::large ) ) ) { 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; + const int bash = settings.bash_strength(); + const bool allow_open_doors = !settings.avoid_opening_doors(); + const bool allow_unlock_doors = !settings.avoid_unlocking_doors(); + const int climb_cost = settings.climb_cost(); int part = -1; const const_maptile &tile = maptile_at_internal( p ); @@ -306,12 +355,12 @@ int map::cost_to_pass( const tripoint_bub_ms &cur, const tripoint_bub_ms &p, } // If terrain/furniture is openable but we can't fit through the open version, ignore the tile - if( settings.size && allow_open_doors && + if( settings.size_restriction() && allow_open_doors && ( ( terrain.open && terrain.open->has_flag( ter_furn_flag::TFLAG_SMALL_PASSAGE ) ) || ( furniture.open && furniture.open->has_flag( ter_furn_flag::TFLAG_SMALL_PASSAGE ) ) || // Windows with curtains need to be opened twice ( terrain.open->open && terrain.open->open->has_flag( ter_furn_flag::TFLAG_SMALL_PASSAGE ) ) ) && - settings.size > creature_size::medium + settings.size_restriction() > creature_size::medium ) { return PF_IMPASSABLE; } @@ -352,9 +401,9 @@ int map::cost_to_pass( const tripoint_bub_ms &cur, const tripoint_bub_ms &p, } int map::cost_to_avoid( const tripoint_bub_ms & /*cur*/, const tripoint_bub_ms &p, - const pathfinding_settings &settings, PathfindingFlags p_special ) const + const PathfindingSettings &settings, PathfindingFlags p_special ) const { - if( settings.avoid_traps && ( p_special & PathfindingFlag::DangerousTrap ) ) { + if( settings.avoid_dangerous_traps() && ( p_special & PathfindingFlag::DangerousTrap ) ) { const const_maptile &tile = maptile_at_internal( p ); const ter_t &terrain = tile.get_ter_t(); const trap &ter_trp = terrain.trap.obj(); @@ -366,7 +415,8 @@ int map::cost_to_avoid( const tripoint_bub_ms & /*cur*/, const tripoint_bub_ms & } } - if( settings.avoid_dangerous_fields && ( p_special & PathfindingFlag::DangerousField ) ) { + if( settings.avoid_dangerous_fields() && + ( p_special & PathfindingFlag::DangerousField ) ) { // We'll walk through even known-dangerous fields if we absolutely have to. return 500; } @@ -375,7 +425,7 @@ int map::cost_to_avoid( const tripoint_bub_ms & /*cur*/, const tripoint_bub_ms & } int map::extra_cost( const tripoint_bub_ms &cur, const tripoint_bub_ms &p, - const pathfinding_settings &settings, + const PathfindingSettings &settings, PathfindingFlags p_special ) const { int pass_cost = cost_to_pass( tripoint_bub_ms( cur ), tripoint_bub_ms( p ), settings, p_special ); @@ -391,7 +441,7 @@ int map::extra_cost( const tripoint_bub_ms &cur, const tripoint_bub_ms &p, } std::vector map::route( const tripoint &f, const tripoint &t, - const pathfinding_settings &settings, + const PathfindingSettings &settings, const std::function &avoid ) const { /* TODO: If the origin or destination is out of bound, figure out the closest @@ -420,11 +470,12 @@ std::vector map::route( const tripoint &f, const tripoint &t, } // If expected path length is greater than max distance, allow only line path, like above - if( rl_dist( f, t ) > settings.max_dist ) { + if( rl_dist( f, t ) > settings.max_distance() ) { return ret; } - const int max_length = settings.max_length; + // FIXME: Temporary number while migration is ongoing. + const int max_length = settings.max_cost() / 50; const int pad = 16; // Should be much bigger - low value makes pathfinders dumb! tripoint_bub_ms min( std::min( f.x, t.x ) - pad, std::min( f.y, t.y ) - pad, std::min( f.z, t.z ) ); @@ -501,7 +552,7 @@ std::vector map::route( const tripoint &f, const tripoint &t, // 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 & PathfindingFlag::DangerousTrap ) ) { + if( settings.avoid_dangerous_traps() && ( p_special & PathfindingFlag::DangerousTrap ) ) { const const_maptile &tile = maptile_at_internal( p ); const ter_t &terrain = tile.get_ter_t(); const trap &ter_trp = terrain.trap.obj(); @@ -531,7 +582,7 @@ std::vector map::route( const tripoint &f, const tripoint &t, // TODO: We should be able to go up ramps even if we can't climb stairs. if( !( cur_special & ( PathfindingFlag::GoesUp | PathfindingFlag::GoesDown ) ) || - !settings.allow_climb_stairs ) { + settings.avoid_climb_stairway() ) { // The part below is only for z-level pathing continue; } @@ -539,7 +590,7 @@ std::vector map::route( const tripoint &f, const tripoint &t, bool rope_ladder = false; const const_maptile &parent_tile = maptile_at_internal( cur ); const ter_t &parent_terrain = parent_tile.get_ter_t(); - if( settings.allow_climb_stairs && cur.z() > min.z() && + if( !settings.avoid_climb_stairway() && cur.z() > min.z() && parent_terrain.has_flag( ter_furn_flag::TFLAG_GOES_DOWN ) ) { std::optional opt_dest = g->find_or_make_stairs( get_map(), cur.z() - 1, rope_ladder, false, cur.raw() ); @@ -557,7 +608,7 @@ std::vector map::route( const tripoint &f, const tripoint &t, cur.raw(), dest.raw() ); } } - if( settings.allow_climb_stairs && cur.z() < max.z() && + if( !settings.avoid_climb_stairway() && cur.z() < max.z() && parent_terrain.has_flag( ter_furn_flag::TFLAG_GOES_UP ) ) { std::optional opt_dest = g->find_or_make_stairs( get_map(), cur.z() + 1, rope_ladder, false, cur.raw() ); @@ -648,7 +699,7 @@ std::vector map::route( const tripoint &f, const tripoint &t, } std::vector map::route( const tripoint_bub_ms &f, const tripoint_bub_ms &t, - const pathfinding_settings &settings, + const PathfindingSettings &settings, const std::function &avoid ) const { std::vector raw_result = route( f.raw(), t.raw(), settings, avoid ); diff --git a/src/pathfinding.h b/src/pathfinding.h index 7b83e3c49e6e1..3f5711e76eedf 100644 --- a/src/pathfinding.h +++ b/src/pathfinding.h @@ -4,6 +4,7 @@ #include +#include "creature.h" #include "coords_fwd.h" #include "game_constants.h" #include "mdarray.h" @@ -18,12 +19,15 @@ enum class PathfindingFlag : uint8_t { Swimmable, // Can swim in Air, // Empty air Unsheltered, // Outside and above ground level - Obstacle, // Something stopping us, might be bashable. + Obstacle, // Something stopping us, might be bashable, climbable, diggable, or openable. Bashable, // Something bashable. Impassable, // Impassable obstacle. Vehicle, // Vehicle tile (passable or not) DangerousField, // Dangerous field DangerousTrap, // Dangerous trap (i.e. not flagged benign) + + // TODO: Rename these to StairsUp and StairsDown. + // TODO: Handle ladders, elevators, etc. GoesUp, // Valid stairs up GoesDown, // Valid stairs down RampUp, // Valid ramp up @@ -131,36 +135,321 @@ struct pathfinding_cache { cata::mdarray special; }; -struct pathfinding_settings { - int bash_strength = 0; - int max_dist = 0; - // At least 2 times the above, usually more - int max_length = 0; - // Expected terrain cost (2 is flat ground) of climbing a wire fence - // 0 means no climbing - int climb_cost = 0; +// Settings that affect either parameters of the pathfinding algorithm +// or fundamental assumptions about movement, e.g. vertical / horizontal movement. +// TODO: Support burrowing as a way to change Z-levels. +// TODO: Allow climbing as a way to change Z-levels. +// TODO: Support swimming as a way to change Z-levels. +// TODO: Support the CLIMB_FLYING flag (e.g. #75515). +class RealityBubblePathfindingSettings +{ + public: + bool allow_flying() const { + return allow_flying_; + } + void set_allow_flying( bool v = true ) { + allow_flying_ = v; + } + + bool allow_falling() const { + return allow_falling_; + } + void set_allow_falling( bool v = true ) { + allow_falling_ = v; + } + + bool allow_stairways() const { + return allow_stairways_; + } + void set_allow_stairways( bool v = true ) { + allow_stairways_ = v; + } + + int max_cost() const { + return max_cost_; + } + void set_max_cost( int v = 0 ) { + max_cost_ = v; + } + + const tripoint_rel_ms &padding() const { + return padding_; + } + void set_padding( const tripoint_rel_ms &v ) { + padding_ = v; + } + + private: + bool allow_flying_ = false; + bool allow_falling_ = false; + bool allow_stairways_ = false; + int max_cost_ = 0; + tripoint_rel_ms padding_ = tripoint_rel_ms( 16, 16, 1 ); +}; + + +// Characteristics of pathfinding as they relate to how an individual creature +// reacts to an individual terrain tile. Attributes that relate to how a creature +// may react to movement that affects Z-Levels, or that act on parameters of the +// pathfinding algorithm (e.g. max_cost_ or padding), belong in RealityBubblePathfindingSettings. +// +// For example, a creature may want to avoid lava, unsheltered terrain, etc. +// Under the hood, a PathfindingFlags instance is used. This allows us to avoid +// any tile with an attribute corresponding to a PathfindingFlag. +// +// All values except avoid impassible initialise to false. +class PathfindingSettings +{ + public: + static constexpr PathfindingFlags RoughTerrain = PathfindingFlag::Slow | PathfindingFlag::Obstacle | + PathfindingFlag::Vehicle | PathfindingFlag::Sharp | PathfindingFlag::DangerousTrap; + static constexpr PathfindingFlags AnySizeRestriction = PathfindingFlag::RestrictTiny | + PathfindingFlag::RestrictSmall | + PathfindingFlag::RestrictMedium | PathfindingFlag::RestrictLarge | PathfindingFlag::RestrictHuge; + + bool avoid_falling() const { + return !rb_settings_.allow_falling(); + } + void set_avoid_falling( bool v = true ) { + rb_settings_.set_allow_falling( !v ); + } + + bool avoid_unsheltered() const { + return is_set( PathfindingFlag::Unsheltered ); + } + void set_avoid_unsheltered( bool v = true ) { + set( PathfindingFlag::Unsheltered, v ); + } + + bool avoid_swimming() const { + return is_set( PathfindingFlag::Swimmable ); + } + void set_avoid_swimming( bool v = true ) { + set( PathfindingFlag::Swimmable, v ); + } + + bool avoid_ground() const { + return is_set( PathfindingFlag::Ground ); + } + void set_avoid_ground( bool v = true ) { + set( PathfindingFlag::Ground, v ); + } + + bool avoid_vehicle() const { + return is_set( PathfindingFlag::Vehicle ); + } + void set_avoid_vehicle( bool v = true ) { + set( PathfindingFlag::Vehicle, v ); + } + + // This refers to climbing within the context of moving on the same + // z-level, e.g. as over a fence. + bool avoid_climbing() const { + return avoid_climbing_; + } + void set_avoid_climbing( bool v = true ) { + avoid_climbing_ = v; + maybe_set_avoid_obstacle(); + } + + bool avoid_climb_stairway() const { + return !rb_settings_.allow_stairways(); + } + void set_avoid_climb_stairway( bool v = true ) { + rb_settings_.set_allow_stairways( !v ); + } + + bool avoid_deep_water() const { + return is_set( PathfindingFlag::DeepWater ); + } + void set_avoid_deep_water( bool v = true ) { + set( PathfindingFlag::DeepWater, v ); + } + + std::optional size_restriction() const { + return size_restriction_; + } + PathfindingFlags size_restriction_mask() const { + return size_restriction_mask_; + } + void set_size_restriction( std::optional size_restriction = std::nullopt ); + + bool avoid_pits() const { + return is_set( PathfindingFlag::Pit ); + } + void set_avoid_pits( bool v = true ) { + set( PathfindingFlag::Pit, v ); + } + + bool avoid_opening_doors() const { + return avoid_opening_doors_; + } + void set_avoid_opening_doors( bool v = true ) { + avoid_opening_doors_ = v; + maybe_set_avoid_obstacle(); + } + + bool avoid_unlocking_doors() const { + return avoid_unlocking_doors_; + } + void set_avoid_unlocking_doors( bool v = true ) { + avoid_unlocking_doors_ = v; + } + + bool avoid_rough_terrain() const { + return is_set( RoughTerrain ); + } + void set_avoid_rough_terrain( bool v = true ) { + set( RoughTerrain, v ); + } + + bool avoid_dangerous_traps() const { + return is_set( PathfindingFlag::DangerousTrap ); + } + void set_avoid_dangerous_traps( bool v ) { + set( PathfindingFlag::DangerousTrap, v ); + } + + bool avoid_sharp() const { + return is_set( PathfindingFlag::Sharp ); + } + void set_avoid_sharp( bool v = true ) { + set( PathfindingFlag::Sharp, v ); + } + + bool avoid_lava() const { + return is_set( PathfindingFlag::Lava ); + } + void set_avoid_lava( bool v = true ) { + set( PathfindingFlag::Lava, v ); + } + + bool avoid_hard_ground() const { + return is_set( PathfindingFlag::HardGround ); + } + void set_avoid_hard_ground( bool v = true ) { + set( PathfindingFlag::HardGround, v ); + } + + bool avoid_dangerous_fields() const { + return is_set( PathfindingFlag::DangerousField ); + } + void set_avoid_dangerous_fields( bool v = true ) { + set( PathfindingFlag::DangerousField, v ); + } + + const std::function &maybe_avoid_fn() const { + return maybe_avoid_fn_; + } + void set_maybe_avoid_fn( std::function fn = nullptr ) { + maybe_avoid_fn_ = std::move( fn ); + } + + int max_distance() const { + return max_distance_; + } + void set_max_distance( int v = 0 ) { + max_distance_ = v; + } + + int max_cost() const { + return rb_settings_.max_cost(); + } + void set_max_cost( int v = 0 ) { + rb_settings_.set_max_cost( v ); + } + + int climb_cost() const { + return climb_cost_; + } + void set_climb_cost( int v = 0 ) { + climb_cost_ = v; + maybe_set_avoid_obstacle(); + } + + bool is_digging() const { + return is_digging_; + } + void set_is_digging( bool v = true ) { + is_digging_ = v; + maybe_set_avoid_obstacle(); + } + + bool is_flying() const { + return rb_settings_.allow_flying(); + } + void set_is_flying( bool v = true ) { + rb_settings_.set_allow_flying( v ); + } + + PathfindingFlags avoid_mask() const { + return avoid_mask_; + } + + bool avoid_bashing() const { + return avoid_bashing_; + } + void set_avoid_bashing( bool v = true ) { + avoid_bashing_ = v; + maybe_set_avoid_obstacle(); + } + + int bash_strength() const { + return bash_strength_; + } + void set_bash_strength( int v = 0 ) { + bash_strength_ = v; + if( v <= 0 ) { + set_avoid_bashing( true ); + } + maybe_set_avoid_obstacle(); + } + int bash_rating_from_range( int min, int max ) const; + + protected: + const RealityBubblePathfindingSettings &rb_settings() const { + return rb_settings_; + } + + private: + bool is_set( PathfindingFlags flag ) const { + return avoid_mask_.is_set( flag ); + } + // Set the corresponding flags to true if v, and false if not v. + void set( PathfindingFlags flags, bool v ) { + if( v ) { + avoid_mask_.set_union( flags ); + } else { + avoid_mask_.set_clear( flags ); + } + } + + void maybe_set_avoid_obstacle() { + // Check if we can short circuit checking obstacles. Significantly faster if so. + set( PathfindingFlag::Obstacle, !is_digging() && ( climb_cost() <= 0 || avoid_climbing() ) && + avoid_opening_doors() && ( avoid_bashing() || bash_strength() <= 0 ) ); + } + + PathfindingFlags avoid_mask_ = PathfindingFlag::Impassable; - bool allow_open_doors = false; - bool allow_unlock_doors = false; - bool avoid_traps = false; - bool allow_climb_stairs = true; - bool avoid_rough_terrain = false; - bool avoid_sharp = false; - bool avoid_dangerous_fields = false; + std::optional size_restriction_; + PathfindingFlags size_restriction_mask_; + int max_distance_ = 0; - std::optional size = std::nullopt; + bool avoid_bashing_ = false; + int bash_strength_ = 0; - pathfinding_settings() = default; - pathfinding_settings( const pathfinding_settings & ) = default; + // Expected terrain cost (2 is flat ground) of climbing a wire fence, 0 means no climbing + bool avoid_climbing_ = false; + int climb_cost_ = 0; - pathfinding_settings( int bs, int md, int ml, int cc, bool aod, bool aud, bool at, bool acs, - bool art, bool as, std::optional sz = std::nullopt ) - : bash_strength( bs ), max_dist( md ), max_length( ml ), climb_cost( cc ), - allow_open_doors( aod ), allow_unlock_doors( aud ), avoid_traps( at ), allow_climb_stairs( acs ), - avoid_rough_terrain( art ), avoid_sharp( as ), size( sz ) {} + bool is_digging_ = false; + RealityBubblePathfindingSettings rb_settings_; - pathfinding_settings &operator=( const pathfinding_settings & ) = default; + bool avoid_opening_doors_ = false; + bool avoid_unlocking_doors_ = false; + std::function maybe_avoid_fn_; }; #endif // CATA_SRC_PATHFINDING_H