diff --git a/src/map.cpp b/src/map.cpp index ef4b03333aa2e..f3a0b6b623ca5 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -46,7 +46,6 @@ #include "field.h" #include "field_type.h" #include "flag.h" -#include "flood_fill.h" #include "fragment_cloud.h" #include "fungal_effects.h" #include "game.h" @@ -10272,66 +10271,3 @@ tripoint drawsq_params::center() const return view_center; } } - -/** This is lazily evaluated on demand. Each creature in a zone is visited - * as it flood fills, then the zone number is incremented. At the end all creatures in - * the same zone will have the same zone number assigned, which can be used to have creatures in - * different zones ignore each other very cheaply. - */ -void map::flood_fill_zone( const Creature &origin ) -{ - creature_tracker &tracker = get_creature_tracker(); - - ff::flood_fill_visit_10_connected( origin.pos_bub(), - [this]( const tripoint_bub_ms & loc, int direction ) { - if( direction == 0 ) { - return inbounds( loc ) && ( is_transparent_wo_fields( loc.raw() ) || - passable( loc ) ); - } - if( direction == 1 ) { - const maptile &up = maptile_at( loc ); - const ter_t &up_ter = up.get_ter_t(); - if( up_ter.id.is_null() ) { - return false; - } - if( ( ( up_ter.movecost != 0 && up.get_furn_t().movecost >= 0 ) || - is_transparent_wo_fields( loc.raw() ) ) && - ( up_ter.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) || - up_ter.has_flag( ter_furn_flag::TFLAG_GOES_DOWN ) ) ) { - return true; - } - } - if( direction == -1 ) { - const maptile &up = maptile_at( loc + tripoint_above ); - const ter_t &up_ter = up.get_ter_t(); - if( up_ter.id.is_null() ) { - return false; - } - const maptile &down = maptile_at( loc ); - const ter_t &down_ter = up.get_ter_t(); - if( down_ter.id.is_null() ) { - return false; - } - if( ( ( down_ter.movecost != 0 && down.get_furn_t().movecost >= 0 ) || - is_transparent_wo_fields( loc.raw() ) ) && - ( up_ter.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) || - up_ter.has_flag( ter_furn_flag::TFLAG_GOES_DOWN ) ) ) { - return true; - } - } - return false; - }, - [&tracker, this]( const tripoint_bub_ms & loc ) { - Creature *creature = tracker.creature_at( loc ); - if( creature ) { - const int n = zone_number * zone_tick; - creatures_by_zone[n].push_back( creature ); - creature->set_reachable_zone( n ); - } - } ); - if( zone_number == std::numeric_limits::max() ) { - zone_number = 1; - } else { - zone_number++; - } -} diff --git a/src/map.h b/src/map.h index 17dbf6727d42b..967c9c88ac44c 100644 --- a/src/map.h +++ b/src/map.h @@ -2236,59 +2236,14 @@ class map bool _main_requires_cleanup = false; std::optional _main_cleanup_override = std::nullopt; - // Tracks the dirtiness of the visitable zones cache. This must be flipped when + // Tracks the dirtiness of the visitable zones cache, but that cache does not live here, + // it is distributed among the active monsters. This must be flipped when // persistent visibility from terrain or furniture changes // (this excludes vehicles and fields) or when persistent traversability changes, // which means walls and floors. bool visitable_cache_dirty = false; - int zone_number = 1; - int zone_tick = 1; - std::unordered_map> creatures_by_zone; - std::unordered_set to_remove; - - void flood_fill_zone( const Creature &origin ); - - void flood_fill_if_needed( const Creature &origin ) { - if( get_visitable_zones_cache_dirty() ) { - creatures_by_zone.clear(); - to_remove.clear(); - zone_tick = zone_tick > 0 ? -1 : 1; - set_visitable_zones_cache_dirty( false ); - zone_number = 1; - } - // This check insures we only flood fill when the target monster has an uninitialized zone, - // or if it has a zone from last turn. In other words it only triggers on - // the first monster in a zone each turn. We can detect this because the sign - // of the zone numbers changes on every invalidation. - int old_zone = origin.get_reachable_zone(); - // Compare with zone_tick == old_zone && old_zone != 0 - if( old_zone * zone_tick <= 0 ) { - flood_fill_zone( origin ); - } - } public: - // Only call from the Creature destructor. - void remove_creature_from_reachability( Creature *creature ) { - to_remove.insert( creature ); - } - - template - void visit_reachable_creatures( const Creature &origin, Functor f ) { - flood_fill_if_needed( origin ); - const auto map_iter = creatures_by_zone.find( origin.get_reachable_zone() ); - if( map_iter != creatures_by_zone.end() ) { - auto vector_iter = map_iter->second.begin(); - const auto vector_end = map_iter->second.end(); - for( ; vector_iter != vector_end; ++vector_iter ) { - Creature *other = *vector_iter; - if( to_remove.count( other ) == 0 ) { - f( *other ); - } - } - } - } - void queue_main_cleanup(); bool is_main_cleanup_queued() const; void main_cleanup_override( bool over ); diff --git a/src/monmove.cpp b/src/monmove.cpp index f57268d64a38b..f7b04a2869cf4 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -485,6 +485,85 @@ bool monster::mating_angry() const return mating_angry; } +/** This is lazily evaluated in monster::plan(). Each monster in a zone is visited + * as it flood fills, then the zone number is incremented. At the end all monsters in + * the same zone will have the same zone number assigned, which can be used to have monsters in + * different zones ignore each other very cheaply. + */ +static void flood_fill_zone( Creature &origin ) +{ + static int zone_number = 1; + static int zone_tick = 1; + map &here = get_map(); + if( here.get_visitable_zones_cache_dirty() ) { + zone_tick = zone_tick > 0 ? -1 : 1; + here.set_visitable_zones_cache_dirty( false ); + zone_number = 1; + } + // This check insures we only flood fill when the target monster has an uninitialized zone, + // or if it has a zone from last turn. In other words it only triggers on + // the first monster in a zone each turn. We can detect this because the sign + // of the zone numbers changes on every invalidation. + int old_zone = origin.get_reachable_zone(); + // Compare with zone_tick == old_zone && old_zone != 0 + if( ( zone_tick > 0 && old_zone > 0 ) || + ( zone_tick < 0 && old_zone < 0 ) ) { + return; + } + creature_tracker &tracker = get_creature_tracker(); + + ff::flood_fill_visit_10_connected( origin.pos_bub(), + [&here]( const tripoint_bub_ms & loc, int direction ) { + if( direction == 0 ) { + return here.inbounds( loc ) && ( here.is_transparent_wo_fields( loc.raw() ) || + here.passable( loc ) ); + } + if( direction == 1 ) { + const maptile &up = here.maptile_at( loc ); + const ter_t &up_ter = up.get_ter_t(); + if( up_ter.id.is_null() ) { + return false; + } + if( ( ( up_ter.movecost != 0 && up.get_furn_t().movecost >= 0 ) || + here.is_transparent_wo_fields( loc.raw() ) ) && + ( up_ter.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) || + up_ter.has_flag( ter_furn_flag::TFLAG_GOES_DOWN ) ) ) { + return true; + } + } + if( direction == -1 ) { + const maptile &up = here.maptile_at( loc + tripoint_above ); + const ter_t &up_ter = up.get_ter_t(); + if( up_ter.id.is_null() ) { + return false; + } + const maptile &down = here.maptile_at( loc ); + const ter_t &down_ter = up.get_ter_t(); + if( down_ter.id.is_null() ) { + return false; + } + if( ( ( down_ter.movecost != 0 && down.get_furn_t().movecost >= 0 ) || + here.is_transparent_wo_fields( loc.raw() ) ) && + ( up_ter.has_flag( ter_furn_flag::TFLAG_NO_FLOOR ) || + up_ter.has_flag( ter_furn_flag::TFLAG_GOES_DOWN ) ) ) { + return true; + } + } + return false; + }, + [&tracker]( const tripoint_bub_ms & loc ) { + Creature *creature = tracker.creature_at( loc ); + if( creature ) { + creature->set_reachable_zone( zone_number * zone_tick ); + } + } ); + if( zone_number == std::numeric_limits::max() ) { + zone_number = 1; + } else { + zone_number++; + } +} + void monster::plan() { const auto &factions = g->critter_tracker->factions(); @@ -495,6 +574,7 @@ void monster::plan() std::bitset seen_levels = here.get_inter_level_visibility( pos().z ); monster_attitude mood = attitude(); Character &player_character = get_player_character(); + flood_fill_zone( *this ); // If we can see the player, move toward them or flee. if( friendly == 0 && seen_levels.test( player_character.pos().z + OVERMAP_DEPTH ) && sees( player_character ) ) { @@ -599,40 +679,52 @@ void monster::plan() turns_since_target ); int turns_to_skip = max_turns_to_skip * rate_limiting_factor; if( friendly == 0 && ( turns_to_skip == 0 || turns_since_target % turns_to_skip == 0 ) ) { - here.visit_reachable_creatures( *this, [this, &seen_levels, &mon_plan, - &valid_targets]( Creature & other ) { - mf_attitude faction_att = faction.obj().attitude( other.get_monster_faction() ); + for( const auto &fac_list : factions ) { + mf_attitude faction_att = faction.obj().attitude( fac_list.first ); if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) { - return; - } - if( !seen_levels.test( other.posz() + OVERMAP_DEPTH ) ) { - return; + continue; } - float rating = rate_target( other, mon_plan.dist, mon_plan.smart_planning ); - if( rating == mon_plan.dist ) { - ++valid_targets; - if( one_in( valid_targets ) ) { - mon_plan.target = &other; + + for( const auto &fac : fac_list.second ) { + if( !seen_levels.test( fac.first + OVERMAP_DEPTH ) ) { + continue; } - } - if( rating < mon_plan.dist ) { - mon_plan.target = &other; - mon_plan.dist = rating; - valid_targets = 1; - } - if( rating <= 5 ) { - if( anger <= 30 ) { - anger += mon_plan.angers_hostile_near; + for( const weak_ptr_fast &weak : fac.second ) { + const shared_ptr_fast shared = weak.lock(); + if( !shared ) { + continue; + } + monster &mon = *shared; + if( get_reachable_zone() != mon.get_reachable_zone() ) { + continue; + } + float rating = rate_target( mon, mon_plan.dist, mon_plan.smart_planning ); + if( rating == mon_plan.dist ) { + ++valid_targets; + if( one_in( valid_targets ) ) { + mon_plan.target = &mon; + } + } + if( rating < mon_plan.dist ) { + mon_plan.target = &mon; + mon_plan.dist = rating; + valid_targets = 1; + } + if( rating <= 5 ) { + if( anger <= 30 ) { + anger += mon_plan.angers_hostile_near; + } + morale -= mon_plan.fears_hostile_near; + } + if( !mon_plan.fleeing && anger <= 20 && valid_targets != 0 ) { + anger += mon_plan.angers_hostile_seen; + } + if( !mon_plan.fleeing && valid_targets != 0 ) { + morale -= mon_plan.fears_hostile_seen; + } } - morale -= mon_plan.fears_hostile_near; - } - if( !mon_plan.fleeing && anger <= 20 && valid_targets != 0 ) { - anger += mon_plan.angers_hostile_seen; } - if( !mon_plan.fleeing && valid_targets != 0 ) { - morale -= mon_plan.fears_hostile_seen; - } - } ); + } } if( mon_plan.target == nullptr ) { // Just avoiding overflow. diff --git a/src/monster.cpp b/src/monster.cpp index 34c5c0ed34fc3..427c71995eb4d 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -3703,7 +3703,6 @@ bool monster::will_join_horde( int size ) void monster::on_unload() { last_updated = calendar::turn; - get_map().remove_creature_from_reachability( this ); } void monster::on_load() diff --git a/src/npc.cpp b/src/npc.cpp index e48cc66d2f53e..a467497c68164 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -3196,7 +3196,6 @@ void npc::add_new_mission( class mission *miss ) void npc::on_unload() { - get_map().remove_creature_from_reachability( this ); } // A throtled version of player::update_body since npc's don't need to-the-turn updates.