1 /**
2 * @file
3 * @brief Tracks monsters that are in suspended animation between levels.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "mon-transit.h"
9
10 #include <algorithm>
11
12 #include "artefact.h"
13 #include "coordit.h"
14 #include "dactions.h"
15 #include "dungeon.h"
16 #include "god-companions.h"
17 #include "god-passive.h" // passive_t::convert_orcs
18 #include "items.h"
19 #include "libutil.h" // map_find
20 #include "mon-place.h"
21 #include "mpr.h"
22 #include "religion.h"
23 #include "tag-version.h"
24 #include "timed-effects.h"
25
26 #define MAX_LOST 100
27
28 monsters_in_transit the_lost_ones;
29
_cull_lost_mons(m_transit_list & mlist,int how_many)30 static void _cull_lost_mons(m_transit_list &mlist, int how_many)
31 {
32 // First pass, drop non-uniques.
33 for (auto i = mlist.begin(); i != mlist.end();)
34 {
35 auto finger = i++;
36 if (!mons_is_unique(finger->mons.type))
37 {
38 mlist.erase(finger);
39
40 if (--how_many <= MAX_LOST)
41 return;
42 }
43 }
44
45 // If we're still over the limit (unlikely), just lose
46 // the old ones.
47 while (how_many-- > MAX_LOST && !mlist.empty())
48 mlist.erase(mlist.begin());
49 }
50
51 /**
52 * Get the monster transit list for the given level.
53 * @param lid The level.
54 * @returns The monster transit list.
55 **/
get_transit_list(const level_id & lid)56 m_transit_list *get_transit_list(const level_id &lid)
57 {
58 return map_find(the_lost_ones, lid);
59 }
60
61 /**
62 * Add a monster to a level's transit list.
63 * @param lid The level.
64 * @param m The monster to add.
65 **/
add_monster_to_transit(const level_id & lid,const monster & m)66 void add_monster_to_transit(const level_id &lid, const monster& m)
67 {
68 ASSERT(m.alive());
69
70 m_transit_list &mlist = the_lost_ones[lid];
71 mlist.emplace_back(m);
72 mlist.back().transit_start_time = you.elapsed_time;
73
74 dprf("Monster in transit to %s: %s", lid.describe().c_str(),
75 m.name(DESC_PLAIN, true).c_str());
76
77 if (m.is_divine_companion())
78 move_companion_to(&m, lid);
79
80 const int how_many = mlist.size();
81 if (how_many > MAX_LOST)
82 _cull_lost_mons(mlist, how_many);
83 }
84
85 /**
86 * Remove a monster from a level's transit list.
87 * @param lid The level.
88 * @param mid The mid_t of the monster to remove.
89 **/
remove_monster_from_transit(const level_id & lid,mid_t mid)90 void remove_monster_from_transit(const level_id &lid, mid_t mid)
91 {
92 m_transit_list &mlist = the_lost_ones[lid];
93
94 for (auto i = mlist.begin(); i != mlist.end(); ++i)
95 {
96 if (i->mons.mid == mid)
97 {
98 mlist.erase(i);
99 return;
100 }
101 }
102 }
103
_level_place_followers(m_transit_list & m)104 static void _level_place_followers(m_transit_list &m)
105 {
106 for (auto i = m.begin(); i != m.end();)
107 {
108 auto mon = i++;
109 if ((mon->mons.flags & MF_TAKING_STAIRS) && mon->place(true))
110 {
111 if (mon->mons.is_divine_companion())
112 {
113 move_companion_to(monster_by_mid(mon->mons.mid),
114 level_id::current());
115 }
116
117 // Now that the monster is onlevel, we can safely apply traps to it.
118 if (monster* new_mon = monster_by_mid(mon->mons.mid))
119 // old loc isn't really meaningful
120 new_mon->apply_location_effects(new_mon->pos());
121 m.erase(mon);
122 }
123 }
124 }
125
_place_lost_ones(void (* placefn)(m_transit_list & ml))126 static void _place_lost_ones(void (*placefn)(m_transit_list &ml))
127 {
128 level_id c = level_id::current();
129
130 monsters_in_transit::iterator i = the_lost_ones.find(c);
131 if (i == the_lost_ones.end())
132 return;
133 placefn(i->second);
134 if (i->second.empty())
135 the_lost_ones.erase(i);
136 }
137
138 /**
139 * Place any followers transiting to this level.
140 **/
place_followers()141 void place_followers()
142 {
143 _place_lost_ones(_level_place_followers);
144 }
145
_place_lost_monster(follower & f)146 static monster* _place_lost_monster(follower &f)
147 {
148 dprf("Placing lost one: %s", f.mons.name(DESC_PLAIN, true).c_str());
149 if (monster* mons = f.place(false))
150 {
151 // Figure out how many turns we need to update the monster
152 int turns = (you.elapsed_time - f.transit_start_time)/10;
153
154 //Unflag as summoned or else monster will be ignored in update_monster
155 mons->flags &= ~MF_JUST_SUMMONED;
156 return update_monster(*mons, turns);
157 }
158 else
159 return nullptr;
160 }
161
_level_place_lost_monsters(m_transit_list & m)162 static void _level_place_lost_monsters(m_transit_list &m)
163 {
164 for (auto i = m.begin(); i != m.end(); )
165 {
166 auto mon = i++;
167
168 // Monsters transiting to the Abyss have a 50% chance of being
169 // placed, otherwise a 100% chance.
170 if (player_in_branch(BRANCH_ABYSS) && coinflip())
171 continue;
172
173 if (monster* new_mon =_place_lost_monster(*mon))
174 {
175 // Now that the monster is on the level, we can safely apply traps
176 // to it.
177 new_mon->apply_location_effects(new_mon->pos());
178 m.erase(mon);
179 }
180 }
181 }
182
183 /**
184 * Place any monsters in transit to this level.
185 **/
place_transiting_monsters()186 void place_transiting_monsters()
187 {
188 _place_lost_ones(_level_place_lost_monsters);
189 }
190
apply_daction_to_transit(daction_type act)191 void apply_daction_to_transit(daction_type act)
192 {
193 for (auto &entry : the_lost_ones)
194 {
195 m_transit_list* m = &entry.second;
196 for (auto j = m->begin(); j != m->end(); ++j)
197 {
198 monster* mon = &j->mons;
199 if (mons_matches_daction(mon, act))
200 apply_daction_to_mons(mon, act, false, true);
201
202 // If that killed the monster, remove it from transit.
203 // Removing this monster invalidates the iterator that
204 // points to it, so decrement the iterator first.
205 if (!mon->alive())
206 m->erase(j--);
207 }
208 }
209 }
210
count_daction_in_transit(daction_type act)211 int count_daction_in_transit(daction_type act)
212 {
213 int count = 0;
214 for (const auto &entry : the_lost_ones)
215 {
216 for (const auto &follower : entry.second)
217 if (mons_matches_daction(&follower.mons, act))
218 count++;
219 }
220
221 return count;
222 }
223
224 //////////////////////////////////////////////////////////////////////////
225 // follower
226
follower(const monster & m)227 follower::follower(const monster& m) : mons(m), items()
228 {
229 ASSERT(m.alive());
230 load_mons_items();
231 }
232
load_mons_items()233 void follower::load_mons_items()
234 {
235 for (int i = 0; i < NUM_MONSTER_SLOTS; ++i)
236 if (mons.inv[i] != NON_ITEM)
237 items[i] = env.item[ mons.inv[i] ];
238 else
239 items[i].clear();
240 }
241
place(bool near_player)242 monster* follower::place(bool near_player)
243 {
244 ASSERT(mons.alive());
245
246 monster *m = get_free_monster();
247 if (!m)
248 return nullptr;
249
250 // Copy the saved data.
251 *m = mons;
252
253 // Shafts no longer retain the position, if anything else would
254 // want to request a specific one, it should do so here if !near_player
255
256 if (m->find_place_to_live(near_player))
257 {
258 #if TAG_MAJOR_VERSION == 34
259 // fix up some potential cloned monsters for beogh chars.
260 // see comments on maybe_bad_priest monster and in tags.cc for details
261 monster *dup_m = monster_by_mid(m->mid);
262 if (dup_m && maybe_bad_priest_monster(*dup_m))
263 fixup_bad_priest_monster(*dup_m);
264 // any clones not covered under maybe_bad_priest_monster will result
265 // in duplicate mid errors.
266 #endif
267 dprf("Placed follower: %s", m->name(DESC_PLAIN, true).c_str());
268 m->target.reset();
269
270 m->flags &= ~MF_TAKING_STAIRS & ~MF_BANISHED;
271 m->flags |= MF_JUST_SUMMONED;
272 restore_mons_items(*m);
273 env.mid_cache[m->mid] = m->mindex();
274 return m;
275 }
276
277 m->reset();
278 return nullptr;
279 }
280
restore_mons_items(monster & m)281 void follower::restore_mons_items(monster& m)
282 {
283 for (int i = 0; i < NUM_MONSTER_SLOTS; ++i)
284 {
285 if (items[i].base_type == OBJ_UNASSIGNED)
286 m.inv[i] = NON_ITEM;
287 else
288 {
289 const int islot = get_mitm_slot(0);
290 m.inv[i] = islot;
291 if (islot == NON_ITEM)
292 continue;
293
294 item_def &it = env.item[islot];
295 it = items[i];
296 it.set_holding_monster(m);
297 }
298 }
299 }
300
_is_religious_follower(const monster & mon)301 static bool _is_religious_follower(const monster &mon)
302 {
303 return (you_worship(GOD_YREDELEMNUL)
304 || will_have_passive(passive_t::convert_orcs)
305 || you_worship(GOD_FEDHAS))
306 && is_follower(mon);
307 }
308
_mons_can_follow_player_from(const monster & mons,const coord_def from,bool within_level=false)309 static bool _mons_can_follow_player_from(const monster &mons,
310 const coord_def from,
311 bool within_level = false)
312 {
313 if (!mons.alive()
314 || mons.speed_increment < 50
315 || mons.incapacitated()
316 || mons.is_stationary())
317 {
318 return false;
319 }
320
321 if (!monster_habitable_grid(&mons, DNGN_FLOOR))
322 return false;
323
324 // Only non-wandering friendly monsters or those actively
325 // seeking the player will follow up/down stairs.
326 if (!mons.friendly()
327 && (!mons_is_seeking(mons) || mons.foe != MHITYOU)
328 || mons.foe == MHITNOT)
329 {
330 return false;
331 }
332
333 // Unfriendly monsters must be directly adjacent to follow.
334 if (!mons.friendly() && (mons.pos() - from).rdist() > 1)
335 return false;
336
337 // Monsters that can't use stairs can still be marked as followers
338 // (though they'll be ignored for transit), so any adjacent real
339 // follower can follow through. (jpeg)
340 if (within_level && !mons_class_can_use_transporter(mons.type)
341 || !within_level && !mons_can_use_stairs(mons, env.grid(from)))
342 {
343 if (_is_religious_follower(mons))
344 return true;
345
346 return false;
347 }
348 return true;
349 }
350
351 // Tag any monster following the player
_tag_follower_at(const coord_def & pos,const coord_def & from,bool & real_follower)352 static bool _tag_follower_at(const coord_def &pos, const coord_def &from,
353 bool &real_follower)
354 {
355 if (!in_bounds(pos) || pos == from)
356 return false;
357
358 monster* fol = monster_at(pos);
359 if (fol == nullptr)
360 return false;
361
362 if (!_mons_can_follow_player_from(*fol, from))
363 return false;
364
365 real_follower = true;
366 fol->flags |= MF_TAKING_STAIRS;
367
368 // Clear patrolling/travel markers.
369 fol->patrol_point.reset();
370 fol->travel_path.clear();
371 fol->travel_target = MTRAV_NONE;
372
373 dprf("%s is marked for following.", fol->name(DESC_THE, true).c_str());
374 return true;
375 }
376
_follower_tag_radius(const coord_def & from)377 static int _follower_tag_radius(const coord_def &from)
378 {
379 // If only friendlies are adjacent, we set a max radius of 5, otherwise
380 // only adjacent friendlies may follow.
381 for (adjacent_iterator ai(from); ai; ++ai)
382 {
383 if (const monster* mon = monster_at(*ai))
384 if (!mon->friendly())
385 return 1;
386 }
387
388 return 5;
389 }
390
391 /**
392 * Handle movement of adjacent player followers from a given location. This is
393 * used when traveling through stairs or a transporter.
394 *
395 * @param from The location from which the player moved.
396 * @param handler A handler function that does movement of the actor to the
397 * destination, returning true if the actor was friendly. The
398 * `real` argument tracks whether the actor was an actual
399 * follower that counts towards the follower limit.
400 **/
handle_followers(const coord_def & from,bool (* handler)(const coord_def & pos,const coord_def & from,bool & real))401 void handle_followers(const coord_def &from,
402 bool (*handler)(const coord_def &pos,
403 const coord_def &from, bool &real))
404 {
405 const int radius = _follower_tag_radius(from);
406 int n_followers = 18;
407
408 vector<coord_def> places[2];
409 int place_set = 0;
410
411 bool visited[GXM][GYM];
412 memset(&visited, 0, sizeof(visited));
413
414 places[place_set].push_back(from);
415 while (!places[place_set].empty())
416 {
417 for (const coord_def &p : places[place_set])
418 {
419 for (adjacent_iterator ai(p); ai; ++ai)
420 {
421 if ((*ai - from).rdist() > radius
422 || visited[ai->x][ai->y])
423 {
424 continue;
425 }
426 visited[ai->x][ai->y] = true;
427
428 bool real_follower = false;
429 if (handler(*ai, from, real_follower))
430 {
431 // If we've run out of our follower allowance, bail.
432 if (real_follower && --n_followers <= 0)
433 return;
434 places[!place_set].push_back(*ai);
435 }
436 }
437 }
438 places[place_set].clear();
439 place_set = !place_set;
440 }
441 }
442
443 /**
444 * Tag all followers at your position for level transit through stairs.
445 **/
tag_followers()446 void tag_followers()
447 {
448 handle_followers(you.pos(), _tag_follower_at);
449 }
450
451 /**
452 * Untag all followers at your position for level transit through stairs.
453 **/
untag_followers()454 void untag_followers()
455 {
456 for (auto &mons : menv_real)
457 mons.flags &= ~MF_TAKING_STAIRS;
458 }
459
_transport_follower_at(const coord_def & pos,const coord_def & from,bool & real_follower)460 static bool _transport_follower_at(const coord_def &pos, const coord_def &from,
461 bool &real_follower)
462 {
463 if (!in_bounds(pos) || pos == from)
464 return false;
465
466 monster* fol = monster_at(pos);
467 if (fol == nullptr)
468 return false;
469
470 if (!_mons_can_follow_player_from(*fol, from, true))
471 return false;
472
473 if (fol->find_place_to_live(true))
474 {
475 real_follower = true;
476 env.map_knowledge(pos).clear_monster();
477 dprf("%s is transported.", fol->name(DESC_THE, true).c_str());
478 }
479
480 return true;
481 }
482
483 /**
484 * Transport all followers from a position to a position near your current one.
485 *
486 * @param from The position from where you transported.
487 **/
transport_followers_from(const coord_def & from)488 void transport_followers_from(const coord_def &from)
489 {
490 handle_followers(from, _transport_follower_at);
491 }
492