diff --git a/src/creature.cpp b/src/creature.cpp index 11b257baa79e1..706d9728e71a0 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -168,7 +168,10 @@ Creature::Creature( Creature && ) noexcept( map_is_noexcept &&list_is_noexcept ) Creature &Creature::operator=( const Creature & ) = default; Creature &Creature::operator=( Creature && ) noexcept = default; -Creature::~Creature() = default; +Creature::~Creature() +{ + get_map().remove_creature_from_reachability( this ); +} tripoint Creature::pos() const { diff --git a/src/map.cpp b/src/map.cpp index f3a0b6b623ca5..ef4b03333aa2e 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -46,6 +46,7 @@ #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" @@ -10271,3 +10272,66 @@ 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 967c9c88ac44c..17dbf6727d42b 100644 --- a/src/map.h +++ b/src/map.h @@ -2236,14 +2236,59 @@ class map bool _main_requires_cleanup = false; std::optional _main_cleanup_override = std::nullopt; - // 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 + // Tracks the dirtiness of the visitable zones cache. 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 f7b04a2869cf4..f57268d64a38b 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -485,85 +485,6 @@ 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(); @@ -574,7 +495,6 @@ 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 ) ) { @@ -679,52 +599,40 @@ 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 ) ) { - for( const auto &fac_list : factions ) { - mf_attitude faction_att = faction.obj().attitude( fac_list.first ); + 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() ); if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) { - continue; + return; } - - for( const auto &fac : fac_list.second ) { - if( !seen_levels.test( fac.first + OVERMAP_DEPTH ) ) { - continue; + if( !seen_levels.test( other.posz() + OVERMAP_DEPTH ) ) { + return; + } + 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 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; - } + } + 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; } + 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/timed_event_list.output b/timed_event_list.output new file mode 100644 index 0000000000000..b8799964f1f65 --- /dev/null +++ b/timed_event_list.output @@ -0,0 +1 @@ +|;when;type;key;string_id;strength;map_point;faction_id;