1 /**
2  * @file
3  * @brief Functions related to teleportation and blinking.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "teleport.h"
9 
10 #include "cloud.h"
11 #include "coord.h"
12 #include "coordit.h"
13 #include "delay.h"
14 #include "env.h"
15 #include "fprop.h"
16 #include "libutil.h"
17 #include "losglobal.h"
18 #include "message.h"
19 #include "mon-behv.h"
20 #include "mon-death.h"
21 #include "mon-place.h"
22 #include "mon-tentacle.h"
23 #include "random.h"
24 #include "terrain.h"
25 #include "view.h"
26 
blink_to(const coord_def & dest,bool quiet)27 bool player::blink_to(const coord_def& dest, bool quiet)
28 {
29     // We rely on the non-generalized move_player_to_cell.
30     ASSERT(is_player());
31 
32     if (dest == pos())
33         return false;
34 
35     if (no_tele(true, true, true))
36     {
37         if (!quiet)
38             canned_msg(MSG_STRANGE_STASIS);
39         return false;
40     }
41 
42     if (!quiet)
43         canned_msg(MSG_YOU_BLINK);
44 
45     stop_delay(true);
46 
47     const coord_def origin = pos();
48     move_player_to_grid(dest, false);
49 
50     if (!cell_is_solid(origin))
51         place_cloud(CLOUD_TLOC_ENERGY, origin, 1 + random2(3), this);
52 
53     return true;
54 }
55 
blink_to(const coord_def & dest,bool quiet)56 bool monster::blink_to(const coord_def& dest, bool quiet)
57 {
58     // For this call, let the monster choose whether it's blinking
59     // or jumping. When blinked by another source, use the other
60     // overload.
61     return blink_to(dest, quiet, is_jumpy());
62 }
63 
blink_to(const coord_def & dest,bool quiet,bool jump)64 bool monster::blink_to(const coord_def& dest, bool quiet, bool jump)
65 {
66     if (dest == pos())
67         return false;
68 
69     bool was_constricted = false;
70     const string verb = (jump ? "leap" : "blink");
71 
72     if (is_constricted())
73     {
74         was_constricted = true;
75 
76         if (!attempt_escape(2))
77         {
78             if (!quiet)
79             {
80                 string message = " struggles to " + verb
81                                  + " free from constriction.";
82                 simple_monster_message(*this, message.c_str());
83             }
84             return false;
85         }
86     }
87 
88     if (!quiet)
89     {
90         string message = " " + conj_verb(verb)
91                          + (was_constricted ? " free!" : "!");
92         simple_monster_message(*this, message.c_str());
93     }
94 
95     if (!(flags & MF_WAS_IN_VIEW))
96         seen_context = jump ? SC_LEAP_IN : SC_TELEPORT_IN;
97 
98     const coord_def oldplace = pos();
99     if (!move_to_pos(dest, true))
100         return false;
101 
102     // Leave a cloud.
103     if (!props.exists(FAKE_BLINK_KEY) && !cell_is_solid(oldplace))
104     {
105         place_cloud(jump ? CLOUD_DUST : CLOUD_TLOC_ENERGY,
106                     oldplace, 1 + random2(3), this);
107     }
108 
109     check_redraw(oldplace);
110     apply_location_effects(oldplace);
111 
112     mons_relocated(this);
113 
114     return true;
115 }
116 
117 // If the returned value is mon.pos(), then nothing was found.
_random_monster_nearby_habitable_space(const monster & mon)118 static coord_def _random_monster_nearby_habitable_space(const monster& mon)
119 {
120     const bool respect_sanctuary = mon.wont_attack();
121 
122     coord_def target;
123     int tries;
124 
125     for (tries = 0; tries < 150; ++tries)
126     {
127         coord_def delta;
128         delta.x = random2(13) - 6;
129         delta.y = random2(13) - 6;
130 
131         // Check that we don't get something too close to the
132         // starting point.
133         if (delta.origin())
134             continue;
135 
136         // Blinks by 1 cell are not allowed.
137         if (delta.rdist() == 1)
138             continue;
139 
140         // Update target.
141         target = delta + mon.pos();
142 
143         // Check that the target is valid and survivable.
144         if (!in_bounds(target))
145             continue;
146 
147         if (!monster_habitable_grid(&mon, env.grid(target)))
148             continue;
149 
150         if (respect_sanctuary && is_sanctuary(target))
151             continue;
152 
153         if (target == you.pos())
154             continue;
155 
156         if (!cell_see_cell(mon.pos(), target, LOS_NO_TRANS))
157             continue;
158 
159         // Survived everything, break out (with a good value of target.)
160         break;
161     }
162 
163     if (tries == 150)
164         target = mon.pos();
165 
166     return target;
167 }
168 
monster_blink(monster * mons,bool quiet)169 bool monster_blink(monster* mons, bool quiet)
170 {
171     coord_def near = _random_monster_nearby_habitable_space(*mons);
172     return mons->blink_to(near, quiet);
173 }
174 
monster_space_valid(const monster * mons,coord_def target,bool forbid_sanctuary)175 bool monster_space_valid(const monster* mons, coord_def target,
176                          bool forbid_sanctuary)
177 {
178     if (!in_bounds(target))
179         return false;
180 
181     // Don't land on top of another monster.
182     if (actor_at(target))
183         return false;
184 
185     if (is_sanctuary(target) && forbid_sanctuary)
186         return false;
187 
188     if (testbits(env.pgrid(target), FPROP_NO_TELE_INTO))
189         return false;
190 
191     return monster_habitable_grid(mons, env.grid(target));
192 }
193 
_monster_random_space(const monster * mons,coord_def & target,bool forbid_sanctuary)194 static bool _monster_random_space(const monster* mons, coord_def& target,
195                                   bool forbid_sanctuary)
196 {
197     int tries = 0;
198     while (tries++ < 1000)
199     {
200         target = random_in_bounds();
201         if (monster_space_valid(mons, target, forbid_sanctuary))
202             return true;
203     }
204 
205     return false;
206 }
207 
mons_relocated(monster * mons)208 void mons_relocated(monster* mons)
209 {
210     if (mons_is_tentacle_head(mons_base_type(*mons)))
211         destroy_tentacles(mons); // If the main body teleports get rid of the tentacles
212     else if (mons->is_child_monster())
213         destroy_tentacle(mons); // If a tentacle/segment is relocated just kill the tentacle
214     else if (mons->type == MONS_ELDRITCH_TENTACLE
215              || mons->type == MONS_ELDRITCH_TENTACLE_SEGMENT)
216     {
217         // Kill an eldritch tentacle and all its segments.
218         monster* tentacle = mons->type == MONS_ELDRITCH_TENTACLE
219                             ? mons : monster_by_mid(mons->tentacle_connect);
220 
221         // this should take care of any tentacles
222         monster_die(*tentacle, KILL_RESET, -1, true, false);
223     }
224 }
225 
monster_teleport(monster * mons,bool instan,bool silent)226 void monster_teleport(monster* mons, bool instan, bool silent)
227 {
228     ASSERT(mons); // XXX: change to monster &mons
229     bool was_seen = !silent && you.can_see(*mons);
230 
231     if (!instan)
232     {
233         if (mons->del_ench(ENCH_TP))
234         {
235             if (!silent)
236                 simple_monster_message(*mons, " seems more stable.");
237         }
238         else
239         {
240             if (!silent)
241                 simple_monster_message(*mons, " looks slightly unstable.");
242 
243             mons->add_ench(mon_enchant(ENCH_TP, 0, 0,
244                                        random_range(20, 30)));
245         }
246 
247         return;
248     }
249 
250     coord_def newpos;
251 
252     if (!_monster_random_space(mons, newpos, !mons->wont_attack()))
253     {
254         simple_monster_message(*mons, " flickers for a moment.");
255         return;
256     }
257 
258     if (!silent)
259         simple_monster_message(*mons, " disappears!");
260 
261     const coord_def oldplace = mons->pos();
262 
263     // Move it to its new home.
264     mons->move_to_pos(newpos);
265 
266     const bool now_visible = you.see_cell(newpos);
267     if (!silent && now_visible)
268     {
269         if (was_seen)
270             simple_monster_message(*mons, " reappears nearby!");
271         else
272         {
273             // Even if it doesn't interrupt an activity (the player isn't
274             // delayed, the monster isn't hostile) we still want to give
275             // a message.
276             activity_interrupt_data ai(mons, SC_TELEPORT_IN);
277             if (!interrupt_activity(activity_interrupt::see_monster, ai))
278                 simple_monster_message(*mons, " appears out of thin air!");
279         }
280     }
281 
282     if (mons->visible_to(&you) && now_visible)
283         handle_seen_interrupt(mons);
284 
285     // Leave a purple cloud.
286     // XXX: If silent is true, this is not an actual teleport, but
287     //      the game moving a monster out of the way.
288     if (!silent && !cell_is_solid(oldplace))
289         place_cloud(CLOUD_TLOC_ENERGY, oldplace, 1 + random2(3), mons);
290 
291     mons->check_redraw(oldplace);
292     mons->apply_location_effects(oldplace);
293 
294     mons_relocated(mons);
295 
296     shake_off_monsters(mons);
297 }
298 
299 // Try to find a "safe" place for moved close or far from the target.
300 // keep_los indicates that the destination should be in LOS of the target.
301 //
302 // XXX: Check the result against in_bounds(), not coord_def::origin(),
303 // because of a memory problem described below. (isn't this fixed now? -rob)
random_space_weighted(actor * moved,actor * target,bool close,bool keep_los=true,bool allow_sanct=true)304 static coord_def random_space_weighted(actor* moved, actor* target,
305                                        bool close, bool keep_los = true,
306                                        bool allow_sanct = true)
307 {
308     vector<coord_weight> dests;
309     const coord_def tpos = target->pos();
310 
311     for (radius_iterator ri(moved->pos(), LOS_NO_TRANS); ri; ++ri)
312     {
313         if (!valid_blink_destination(moved, *ri, !allow_sanct)
314             || (keep_los && !target->see_cell_no_trans(*ri)))
315         {
316             continue;
317         }
318 
319         int weight;
320         int dist = (tpos - *ri).rdist();
321         if (close)
322             weight = (LOS_RADIUS - dist) * (LOS_RADIUS - dist);
323         else
324             weight = dist;
325         if (weight < 0)
326             weight = 0;
327         dests.emplace_back(*ri, weight);
328     }
329 
330     coord_def* choice = random_choose_weighted(dests);
331     return choice ? *choice : coord_def(0, 0);
332 }
333 
334 // Blink the victim closer to the monster at target.
blink_other_close(actor * victim,const coord_def & target)335 void blink_other_close(actor* victim, const coord_def &target)
336 {
337     actor* caster = actor_at(target);
338     if (!caster)
339         return;
340     if (is_sanctuary(you.pos()))
341         return;
342     coord_def dest = random_space_weighted(victim, caster, true);
343     if (!in_bounds(dest))
344         return;
345     // If it's a monster, force them to "blink" rather than "jump"
346     if (victim->is_monster())
347         victim->as_monster()->blink_to(dest, false, false);
348     else
349         victim->blink_to(dest);
350 }
351 
352 // Blink a monster away from the caster.
blink_away(monster * mon,actor * caster,bool from_seen,bool self_cast)353 bool blink_away(monster* mon, actor* caster, bool from_seen, bool self_cast)
354 {
355     ASSERT(mon); // XXX: change to monster &mon
356     ASSERT(caster); // XXX: change to actor &caster
357 
358     if (from_seen && !mon->can_see(*caster))
359         return false;
360     bool jumpy = self_cast && mon->is_jumpy();
361     coord_def dest = random_space_weighted(mon, caster, false, false, true);
362     if (dest.origin())
363         return false;
364     bool success = mon->blink_to(dest, false, jumpy);
365     ASSERT(success || mon->is_constricted());
366     return success;
367 }
368 
369 // Blink the monster away from its foe.
blink_away(monster * mon,bool self_cast)370 bool blink_away(monster* mon, bool self_cast)
371 {
372     actor* foe = mon->get_foe();
373     if (!foe)
374         return false;
375     return blink_away(mon, foe, true, self_cast);
376 }
377 
378 // Blink the monster within range but at distance to its foe.
blink_range(monster * mon)379 void blink_range(monster* mon)
380 {
381     ASSERT(mon); // XXX: change to monster &mon
382 
383     actor* foe = mon->get_foe();
384     if (!foe || !mon->can_see(*foe))
385         return;
386     coord_def dest = random_space_weighted(mon, foe, false, true);
387     if (dest.origin())
388         return;
389     bool success = mon->blink_to(dest);
390     ASSERT(success || mon->is_constricted());
391 #ifndef ASSERTS
392     UNUSED(success);
393 #endif
394 }
395 
396 // Blink the monster close to its foe.
blink_close(monster * mon)397 void blink_close(monster* mon)
398 {
399     ASSERT(mon); // XXX: change to monster &mon
400 
401     actor* foe = mon->get_foe();
402     if (!foe || !mon->can_see(*foe))
403         return;
404     coord_def dest = random_space_weighted(mon, foe, true, true, true);
405     if (dest.origin())
406         return;
407     bool success = mon->blink_to(dest, false);
408     ASSERT(success || mon->is_constricted());
409 #ifndef ASSERTS
410     UNUSED(success);
411 #endif
412 }
413 
414 // This only checks the contents of the tile - nothing in between.
415 // Could compact most of this into a big boolean if you wanted to trade
416 // readability for dubious speed improvements.
valid_blink_destination(const actor * moved,const coord_def & target,bool forbid_sanctuary,bool forbid_unhabitable)417 bool valid_blink_destination(const actor* moved, const coord_def& target,
418                              bool forbid_sanctuary,
419                              bool forbid_unhabitable)
420 {
421     ASSERT(moved);
422 
423     if (!in_bounds(target))
424         return false;
425     if (actor_at(target))
426         return false;
427     if (forbid_unhabitable)
428     {
429         if (!moved->is_habitable(target))
430             return false;
431         if (moved->is_player() && is_feat_dangerous(env.grid(target), true))
432             return false;
433     }
434     if (forbid_sanctuary && is_sanctuary(target))
435         return false;
436     if (!moved->see_cell_no_trans(target))
437         return false;
438 
439     return true;
440 }
441 
random_near_space(const actor * victim,const coord_def & origin,coord_def & target,bool allow_adjacent,bool forbid_sanctuary,bool forbid_unhabitable)442 bool random_near_space(const actor* victim,
443                        const coord_def& origin, coord_def& target,
444                        bool allow_adjacent, bool forbid_sanctuary,
445                        bool forbid_unhabitable)
446 {
447     // This might involve ray tracing (LOS calcs in valid_blink_destination),
448     // so cache results to avoid duplicating ray traces.
449 #define RNS_OFFSET 6
450 #define RNS_WIDTH (2*RNS_OFFSET + 1)
451     FixedArray<bool, RNS_WIDTH, RNS_WIDTH> tried;
452     const coord_def tried_o = coord_def(RNS_OFFSET, RNS_OFFSET);
453     tried.init(false);
454 
455     for (int tries = 0; tries < 150; tries++)
456     {
457         coord_def p;
458         p.x = random2(RNS_WIDTH);
459         p.y = random2(RNS_WIDTH);
460         if (tried(p))
461             continue;
462         else
463             tried(p) = true;
464 
465         target = origin + (p - tried_o);
466 
467         if (valid_blink_destination(victim, target,
468                                     forbid_sanctuary, forbid_unhabitable)
469             && (allow_adjacent || grid_distance(origin, target) > 1))
470         {
471             return true;
472         }
473     }
474 
475     return false;
476 }
477