/** * @file * @brief Tracks monsters that are in suspended animation between levels. **/ #include "AppHdr.h" #include #include "mon-transit.h" #include "artefact.h" #include "coord.h" #include "coordit.h" #include "dactions.h" #include "dungeon.h" #include "env.h" #include "godcompanions.h" #include "items.h" #include "mon-place.h" #include "mon-util.h" #include "random.h" #include "religion.h" #include "travel.h" #define MAX_LOST 100 monsters_in_transit the_lost_ones; items_in_transit transiting_items; void transit_lists_clear() { the_lost_ones.clear(); transiting_items.clear(); } static void level_place_lost_monsters(m_transit_list &m); static void level_place_followers(m_transit_list &m); static void cull_lost_mons(m_transit_list &mlist, int how_many) { // First pass, drop non-uniques. for (m_transit_list::iterator i = mlist.begin(); i != mlist.end();) { m_transit_list::iterator finger = i++; if (!mons_is_unique(finger->mons.type)) { mlist.erase(finger); if (--how_many <= MAX_LOST) return; } } // If we're still over the limit (unlikely), just lose // the old ones. while (how_many-- > MAX_LOST && !mlist.empty()) mlist.erase(mlist.begin()); } static void cull_lost_items(i_transit_list &ilist, int how_many) { // First pass, drop non-artefacts. for (i_transit_list::iterator i = ilist.begin(); i != ilist.end();) { i_transit_list::iterator finger = i++; if (!is_artefact(*finger)) { ilist.erase(finger); if (--how_many <= MAX_LOST) return; } } // Second pass, drop randarts. for (i_transit_list::iterator i = ilist.begin(); i != ilist.end();) { i_transit_list::iterator finger = i++; if (is_random_artefact(*finger)) { ilist.erase(finger); if (--how_many <= MAX_LOST) return; } } // Third pass, drop unrandarts. for (i_transit_list::iterator i = ilist.begin(); i != ilist.end();) { i_transit_list::iterator finger = i++; if (is_unrandom_artefact(*finger) && !is_special_unrandom_artefact(*finger)) { ilist.erase(finger); if (--how_many <= MAX_LOST) return; } } // If we're still over the limit (unlikely), just lose // the old ones. while (how_many-- > MAX_LOST && !ilist.empty()) ilist.erase(ilist.begin()); } m_transit_list *get_transit_list(const level_id &lid) { monsters_in_transit::iterator i = the_lost_ones.find(lid); return i != the_lost_ones.end()? &i->second : NULL; } void add_monster_to_transit(const level_id &lid, const monster& m) { ASSERT(m.alive()); m_transit_list &mlist = the_lost_ones[lid]; mlist.push_back(follower(m)); dprf("Monster in transit to %s: %s", lid.describe().c_str(), m.name(DESC_PLAIN, true).c_str()); if (m.is_divine_companion()) move_companion_to(&m, lid); const int how_many = mlist.size(); if (how_many > MAX_LOST) cull_lost_mons(mlist, how_many); } void remove_monster_from_transit(const level_id &lid, mid_t mid) { m_transit_list &mlist = the_lost_ones[lid]; for (m_transit_list::iterator i = mlist.begin(); i != mlist.end(); ++i) { if (i->mons.mid == mid) { mlist.erase(i); return; } } } static void _place_lost_ones(void (*placefn)(m_transit_list &ml)) { level_id c = level_id::current(); monsters_in_transit::iterator i = the_lost_ones.find(c); if (i == the_lost_ones.end()) return; placefn(i->second); if (i->second.empty()) the_lost_ones.erase(i); } void place_transiting_monsters() { _place_lost_ones(level_place_lost_monsters); } void place_followers() { _place_lost_ones(level_place_followers); } static bool place_lost_monster(follower &f) { dprf("Placing lost one: %s", f.mons.name(DESC_PLAIN, true).c_str()); return f.place(false); } static void level_place_lost_monsters(m_transit_list &m) { for (m_transit_list::iterator i = m.begin(); i != m.end();) { m_transit_list::iterator mon = i++; // Monsters transiting to the Abyss have a 50% chance of being // placed, otherwise a 100% chance. if (player_in_branch(BRANCH_ABYSS) && coinflip()) continue; if (place_lost_monster(*mon)) { m.erase(mon); // Now that the monster is onlevel, we can safely apply traps to it. monster* new_mon = monster_by_mid(mon->mons.mid); // old loc isn't really meaningful if (new_mon != NULL) new_mon->apply_location_effects(new_mon->pos()); } } } static void level_place_followers(m_transit_list &m) { for (m_transit_list::iterator i = m.begin(); i != m.end();) { m_transit_list::iterator mon = i++; if ((mon->mons.flags & MF_TAKING_STAIRS) && mon->place(true)) { if (mon->mons.is_divine_companion()) move_companion_to(monster_by_mid(mon->mons.mid), level_id::current()); m.erase(mon); // Now that the monster is onlevel, we can safely apply traps to it. monster* new_mon = monster_by_mid(mon->mons.mid); // old loc isn't really meaningful if (new_mon != NULL) new_mon->apply_location_effects(new_mon->pos()); } } } void add_item_to_transit(const level_id &lid, const item_def &i) { i_transit_list &ilist = transiting_items[lid]; ilist.push_back(i); dprf("Item in transit: %s", i.name(DESC_PLAIN).c_str()); const int how_many = ilist.size(); if (how_many > MAX_LOST) cull_lost_items(ilist, how_many); } void place_transiting_items() { level_id c = level_id::current(); items_in_transit::iterator i = transiting_items.find(c); if (i == transiting_items.end()) return; i_transit_list &ilist = i->second; i_transit_list keep; i_transit_list::iterator item; for (item = ilist.begin(); item != ilist.end(); ++item) { coord_def pos = item->pos; if (!in_bounds(pos)) pos = random_in_bounds(); const coord_def where_to_go = dgn_find_nearby_stair(DNGN_ESCAPE_HATCH_DOWN, pos, true); // List of items we couldn't place. if (!copy_item_to_grid(*item, where_to_go, -1, false, true)) keep.push_back(*item); } // Only unplaceable items are kept in list. ilist = keep; } void apply_daction_to_transit(daction_type act) { for (monsters_in_transit::iterator i = the_lost_ones.begin(); i != the_lost_ones.end(); ++i) { m_transit_list* m = &i->second; for (m_transit_list::iterator j = m->begin(); j != m->end(); ++j) { monster* mon = &j->mons; if (mons_matches_daction(mon, act)) apply_daction_to_mons(mon, act, false, true); // If that killed the monster, remove it from transit. // Removing this monster invalidates the iterator that // points to it, so decrement the iterator first. if (!mon->alive()) m->erase(j--); } } } int count_daction_in_transit(daction_type act) { int count = 0; for (monsters_in_transit::iterator i = the_lost_ones.begin(); i != the_lost_ones.end(); ++i) { m_transit_list* m = &i->second; for (m_transit_list::iterator j = m->begin(); j != m->end(); ++j) { monster* mon = &j->mons; if (mons_matches_daction(mon, act)) count++; } } return count; } ////////////////////////////////////////////////////////////////////////// // follower follower::follower(const monster& m) : mons(m), items() { ASSERT(m.alive()); load_mons_items(); } void follower::load_mons_items() { for (int i = 0; i < NUM_MONSTER_SLOTS; ++i) if (mons.inv[i] != NON_ITEM) items[i] = mitm[ mons.inv[i] ]; else items[i].clear(); } bool follower::place(bool near_player) { ASSERT(mons.alive()); monster *m = get_free_monster(); if (!m) return false; // Copy the saved data. *m = mons; // Shafts no longer retain the position, if anything else would // want to request a specific one, it should do so here if !near_player if (m->find_place_to_live(near_player)) { dprf("Placed follower: %s", m->name(DESC_PLAIN, true).c_str()); m->target.reset(); m->flags &= ~MF_TAKING_STAIRS & ~MF_BANISHED; m->flags |= MF_JUST_SUMMONED; restore_mons_items(*m); env.mid_cache[m->mid] = m->mindex(); return true; } m->reset(); return false; } void follower::restore_mons_items(monster& m) { for (int i = 0; i < NUM_MONSTER_SLOTS; ++i) { if (items[i].base_type == OBJ_UNASSIGNED) m.inv[i] = NON_ITEM; else { const int islot = get_mitm_slot(0); m.inv[i] = islot; if (islot == NON_ITEM) continue; item_def &it = mitm[islot]; it = items[i]; it.pos.set(-2, -2); it.link = NON_ITEM + 1 + m.mindex(); } } } static bool _is_religious_follower(const monster* mon) { return (you_worship(GOD_YREDELEMNUL) || you_worship(GOD_BEOGH) || you_worship(GOD_FEDHAS)) && is_follower(mon); } static bool _tag_follower_at(const coord_def &pos, bool &real_follower) { if (!in_bounds(pos) || pos == you.pos()) return false; monster* fol = monster_at(pos); if (fol == NULL) return false; if (!fol->alive() || fol->speed_increment < 50 || fol->incapacitated() || mons_is_boulder(fol) || fol->is_stationary()) { return false; } if (!monster_habitable_grid(fol, DNGN_FLOOR)) return false; // Only non-wandering friendly monsters or those actively // seeking the player will follow up/down stairs. if (!fol->friendly() && (!mons_is_seeking(fol) || fol->foe != MHITYOU) || fol->foe == MHITNOT) { return false; } // Unfriendly monsters must be directly adjacent to follow. if (!fol->friendly() && (pos - you.pos()).abs() > 2) return false; // Monsters that can't use stairs can still be marked as followers // (though they'll be ignored for transit), so any adjacent real // follower can follow through. (jpeg) if (!mons_can_use_stairs(fol)) { if (_is_religious_follower(fol)) { fol->flags |= MF_TAKING_STAIRS; return true; } return false; } real_follower = true; // Monster is chasing player through stairs. fol->flags |= MF_TAKING_STAIRS; // Clear patrolling/travel markers. fol->patrol_point.reset(); fol->travel_path.clear(); fol->travel_target = MTRAV_NONE; fol->clear_clinging(); dprf("%s is marked for following.", fol->name(DESC_THE, true).c_str()); return true; } static int follower_tag_radius2() { // If only friendlies are adjacent, we set a max radius of 6, otherwise // only adjacent friendlies may follow. for (adjacent_iterator ai(you.pos()); ai; ++ai) { if (const monster* mon = monster_at(*ai)) if (!mon->friendly()) return 2; } return 6 * 6; } void tag_followers() { const int radius2 = follower_tag_radius2(); int n_followers = 18; vector places[2]; int place_set = 0; places[place_set].push_back(you.pos()); memset(travel_point_distance, 0, sizeof(travel_distance_grid_t)); while (!places[place_set].empty()) { for (int i = 0, size = places[place_set].size(); i < size; ++i) { const coord_def &p = places[place_set][i]; for (adjacent_iterator ai(p); ai; ++ai) { if ((*ai - you.pos()).abs() > radius2 || travel_point_distance[ai->x][ai->y]) { continue; } travel_point_distance[ai->x][ai->y] = 1; bool real_follower = false; if (_tag_follower_at(*ai, real_follower)) { // If we've run out of our follower allowance, bail. if (real_follower && --n_followers <= 0) return; places[!place_set].push_back(*ai); } } } places[place_set].clear(); place_set = !place_set; } } void untag_followers() { for (int m = 0; m < MAX_MONSTERS; ++m) menv[m].flags &= (~MF_TAKING_STAIRS); }